9. MySQL
- 数据库设计:
- MySQL 概述
- 数据库设计 - DDL
- 多表设计
- 数据库操作:
- 数据库操作 - DML
- 数据库操作 - DQL
- 事务
- 多表查询
- 数据库优化
- 索引
- SQL 优化
- 分库分表
企业开发使用方式:
mysql -u用户名 -p密码 [-h数据库服务器IP地址 -p端口号]
数据模型:关系型数据库,建立在关键模型基础上,由多张相互连接的二维表组成的数据库
9.1 DDL - 数据库设计
用来定义数据库、表
- 查询:
- show databases; -- 查询所有数据库 select database(); -- 查询当前数据库
- 使用:
- use 数据库名();
- 创建:
- create database [if not exists] 数据库名;
- 删除:
- drop database [if exists] 数据库名;
- 上述语法中的 database 可以替换成 schema。如:create schema db01;
- 创建:
- create table 表名( 字段1 字段类型 [约束] [comment 注释], …… 字段2 字段类型 [约束] [comment 注释] ) [comment 表注释];
- 约束描述关键字非空约束限制该字段的数据不能为nullnot null唯一约束保证该字段的所有数据都是唯一、不重复的unique主键约束主键是一行数据的唯一标识,要求非空且唯一primary key(auto_increment 自增)默认约束保存数据时,如果未指定该字段的值,则采用默认值default外键约束用来让两张表的数据之间建立连接,保证数据的一致性和完整性foreign key
- 表操作:
- show tables; -- 查询当前所有表 desc 表名; -- 查询表结构 show create table 表名; -- 查询建表语句
9.2 DML - 数据库操作
- 添加数据(insert):
- 修改数据(update):
- 删除数据(delete):
9.3 DQL - 数据库查询
9.3.1 条件查询
where
比较运算符 | 功能 |
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
= | 等于 |
<> 或者 != | 不等于 |
between ... and ... | 在某个范围之内(含最小、最大值) |
in( ... ) | 在 in 之后的列表中的值,多选一 |
like 占位符 | 模糊匹配(- 匹配单个字符,% 匹配任意个字符) |
is null | 是 null |
逻辑运算符 | 功能 |
and 或 && | 并且(多个条件同时成立) |
or 或 | | | 或者(多个条件任意一个成立) |
not 或 ! | 非,不是 |
表中多个数据: 类似Java中的case
case 表达式 when 值1 then 结果1 when 值2 then 结果2 ... else ... end
9.3.2 分组查询
group by having
聚合函数
介绍:将一列数据作为一个整体,进行纵向计算
语法:select 聚合函数(字段列表) from 表名;
函数 | 功能 |
count | 统计数量 |
max | 最大值 |
min | 最小值 |
avg | 平均值 |
sum | 求和 |
分组查询:
select 字段列表 from 表名 [where 条件] group by 分组字段名 [having 分组后过滤的条件];
- 根据性别分组,统计员工数量
- select gender, count(*) from tb_emp group by gender;
- where 与 having 区别:
- 执行时机不同:where 是分组之前进行过滤,不满足 where 条件,不参与分组;而 having 是分组之后对结果进行过滤。
- 判断条件不同:where 不能对聚合函数进行判断,而 having 可以。
- 注意事项:分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。
- 执行顺序: where > 聚合函数 > having
9.3.3 排序查询
order by
select 字段列表 from 表名 [where 条件列表] [group by 分组字段] order by 字段1 排序方式1, 字段2 排序方式2 … ;
ASC:升序(默认)
DESC:降序
- 如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序。
9.3.4 分页查询
limit
select 字段列表 from 表名 limit 起始索引, 查询记录数;
- 查询记录数为每一页要展示的数据的条数
- 注意事项:
- 起始索引从 0 开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
- 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是 limit。
- 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10。
常用函数
if (表达式, tvalue, fvalue); -- 当表达式为true时,取tvalue;当表达式为false时,取fvalue | |
case expr when value1 then result1 [when value2 then result2 ...] [else result] end -- 类似于switch语句 |
9.4 多表设计
外键
- 物理外键:
- 概念:使用foreign key定义外键关联另外一张表。
- 缺点:
- 影像增删改的效率(需要检查外键关系)
- 仅用于单节点数据库,不适用于分布式、集群场景
- 容易引发数据库的死锁问题,消耗性能
- -- 创建表时指定 create table 表名( 字段名 数据类型; ... [constraint] [外键名称] foreign key (外键字段名) references 主表(字段名) )
- -- 建完表后,添加外键 alter table 表名 add constraint 外键名称 foreign key (外键字段名) references 主表(字段名);
- 逻辑外键:
- 概念:在业务逻辑中,解决外键关联
- 通过逻辑外键,就可以很方便的解决上述问题
一对多:在多的一方添加外键关联一的一方的主键
-- 部门管理 | |
create table tb_dept( | |
id int unsigned primary key auto_increment comment '主键ID', | |
name varchar(10) not null unique comment '部门名称', | |
create_time datetime not null comment '创建时间', | |
update_time datetime not null comment '修改时间' | |
) comment '部门表'; | |
| |
| |
-- 员工管理(带约束) | |
create table tb_emp ( | |
id int unsigned primary key auto_increment comment 'ID', | |
username varchar(20) not null unique comment '用户名', | |
password varchar(32) default '123456' comment '密码', | |
name varchar(10) not null comment '姓名', | |
gender tinyint unsigned not null comment '性别, 说明: 1 男, 2 女', | |
image varchar(300) comment '图像', | |
job tinyint unsigned comment '职位, 说明: 1 班主任,2 讲师, 3 学工主管, 4 教研主管', | |
entrydate date comment '入职时间', | |
dept_id int unsigned comment '部门ID', | |
create_time datetime not null comment '创建时间', | |
update_time datetime not null comment '修改时间', | |
constraint fk_dept foreign key (dept_id) references tb_dept(id) | |
) comment '员工表'; |
一对一:
- 案例:用户 与 身份证信息 的关系
- 关系:一对一关系,多用于单表拆分,将一张表的基础字段放在一张表中,其他字段放在另一张表中,以提升效率
- 实现:在任意一方假如外键,关联另外一方的主键,并设置外键为唯一的(UNIQUE)
create table tb_user | |
( | |
id int auto_increment primary key comment '主键ID', | |
name varchar(10) comment '姓名', | |
age int comment '年龄', | |
gender char(1) comment '1: 男,2:女', | |
phone char(11) comment '手机号' | |
) comment '用户基本信息表'; | |
| |
create table tb_user_edu | |
( | |
id int auto_increment primary key comment '主键ID', | |
degree varchar(20) comment '学历', | |
major varchar(50) comment '专业', | |
primaryschool varchar(50) comment '小学', | |
middleschool varchar(50) comment '中学', | |
university varchar(50) comment '大学', | |
userid int unique comment '用户ID', -- 外键关联基本信息表的主键(加上了unique唯一约束)constraint fk_userid foreign key (userid) references tb_user (id) | |
) comment '用户教育信息表'; |
多对多:
- 案例:学生 与 课程的关系
- 关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
- 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
create table student | |
( | |
id int auto_increment primary key comment '主键ID', | |
name varchar(10) comment '姓名', | |
no varchar(10) comment '学号' | |
) comment '学生表'; | |
| |
insert into student | |
values (null, '黛绮丝', '2000100101'), | |
(null, '谢逊', '2000100102'), | |
(null, '韦一笑', '2000200103'), | |
(null, '殷天正', '2000200103'), | |
(null, '韦一笑', '2000200104'); | |
| |
| |
create table course | |
( | |
id int auto_increment primary key comment '主键ID', | |
name varchar(10) comment '课程名称' | |
) comment '课程表'; | |
insert into course | |
values (null, 'Java'), | |
(null, 'PHP'), | |
(null, 'MySQL'), | |
(null, 'Hadoop'); | |
| |
create table student_course | |
( | |
id int auto_increment comment '主键' primary key, | |
studentid int not null comment '学生ID', | |
courseid int not null comment '课程ID', | |
constraint fk_courseid foreign key (courseid) references course (id), | |
constraint fk_studentid foreign key (studentid) references course (id) | |
) comment '学生课程中间表'; | |
| |
insert into student_course value (null, 1, 1), (null, 1, 2), (null, 1, 3), (null, 2, 2), (null, 2, 3), (null, 3, 4); |
9.5 多表查询
- 多表查询:指从多张表中查询数据
- 笛卡尔积:两个集合的所有组合情况(在多表查询时,需要消除无效的笛卡尔积)
9.5.1 连接查询
- 内连接:相当于查询A、B的交集部分数据
隐式内连接:
select 字段列表 from 表1, 表2 where 条件 ……;
显式内连接:
select 字段列表 from 表1 [inner] join 表2 on 连接条件 ……;
- 外连接:
- 左外连接:查询 左表 所有数据(包含两张表交集部分数据)
- select 字段列表 from 表1 left [outer] join 表2 on 连接条件 ……;
- 右外连接:查询 右表 所有数据(包含两张表交集部分数据)
- select 字段列表 from 表1 right [outer] join 表2 on 连接条件 ……;
9.5.2 子查询
- 介绍:SQL语句中嵌套 select 语句,称为嵌套查询,又称子查询。
- 形式: select * from t1 where column1 = (select column1 from t2 ...);
- 子查询外部的语句可以是 insert / update / delete / select 的任何一个,最常见的是 select
- 分类:
- 标量子查询:子查询返回的结果为单个值。
- 列子查询:子查询返回的结果为一列。
- 行子查询:子查询返回的结果为一行。
- 表子查询:子查询返回的结果为多行多列。
9.6 事务
默认MySQL的事务时自动提交的,也就是说当执行一条DML语句,MySQL会立即隐式的提交事务。
- 开启事务:
- start transaction; begin;
- 提交事务:
- commit;
- 回滚事务:
- rollback;
- 四大特性:
- 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
- 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
- 隔离性(lsolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
- 持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
9.7 索引
- 介绍:时帮助数据库 高效获取数据的 数据结构
- 优点:
- 提高数据查询的效率,降低数据库的 IO 成本
- 通过索引列对数据进行排序,降低数据排序的成本,降低 CPU 消耗。
- 缺点:
- 索引会占用存储空间
- 索引大大提高了查询效率,同时也降低了 insert、update、delete 的效率。
- 结构:默认指定 B+Tree
- 创建索引:
- create [unique] index 索引名 on 表名 (字段名, ...);
- 查看索引:
- show index from 表名;
- 删除索引:
- drop index 索引名 on 表名;
- 注意:
- 主键字段,在建表时,会自动创建主键索引。
- 添加唯一约束时,数据库实际上会添加唯一索引。
10. Mybatis
是一款优秀的 持久层 框架,用于简化 JDBC 的开发
官网:https://mybatis.org/mybatis-3/zh/index.html
10.1 入门程序:
- 准备工作(创建 springboot )工程、数据库表User、实体类User
- 数据库表
create table user | |
( id int unsigned primary key auto_increment comment 'ID', | |
name varchar(100) comment '姓名', | |
age tinyint unsigned comment '年龄', | |
gender tinyint unsigned comment '性别, 1:男, 2:女', | |
phone varchar(11) comment '手机号' | |
) comment '用户表'; | |
insert into user(id, name, age, gender, phone) | |
VALUES (null,'白眉鹰王',55,'1','18800000000'); | |
insert into user(id, name, age, gender, phone) | |
VALUES (null,'金毛狮王',45,'1','18800000001'); | |
insert into user(id, name, age, gender, phone) | |
VALUES (null,'青翼蝠王',38,'1','18800000002'); | |
insert into user(id, name, age, gender, phone) | |
VALUES (null,'紫衫龙王',42,'2','18800000003'); | |
insert into user(id, name, age, gender, phone) | |
VALUES (null,'光明左使',37,'1','18800000004'); | |
insert into user(id, name, age, gender, phone) | |
VALUES (null,'光明右使',48,'1','18800000005'); | |
//无参构造 | |
//全参构造 | |
public class User { | |
private Integer id; | |
private String name; | |
private short age; | |
private short gender; | |
private String phone; | |
} |
- 引入Mybatis相关依赖,配置Mybatis(数据库连接信息)
#驱动类名称 | |
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver | |
#数据库连接的url | |
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis | |
#连接数据库的用户名 | |
spring.datasource.username=root | |
#连接数据库的密码 | |
spring.datasource.password=123456 |
- 编写SQL语句
//在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理 | |
public interface UserMapper { | |
//查询全部用户信息 @Select("select * from user") public List<User> list(); | |
} |
- 单元测试
//springboot整合单元测试的注解 | |
class SpringbootMybatisQuickstartApplicationTests { | |
//依赖注入private UserMapper userMapper; @Test public void testListUser(){ | |
List<User> userList = userMapper.list(); | |
userList.stream().forEach(user -> { | |
System.out.println(user); | |
}); | |
} | |
} |
10.2 JDBC
JDBC:使用 Java 语言操作关系型数据库的一套API
- sun 公司官方定义的一套操作所有关系型数据库的规范,即接口
- 各个数据库厂商去实现这套接口,提供数据库驱动 jar 包
- 我们可以使用这套接口 (JDBC)编程,真正执行的代码是驱动 jar 包中的实现类
10.3 数据库连接池
- 数据库连接池是个容器,负责分配、、管理数据库连接( Connection )
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
- 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
- 标准接口:DataSource
- 官方(sun)提供的数据库连接池接口,由第三方组织实现此接口
- 功能:获取连接
- Connection getConnection() throws SQLException;
- 常见产品:
- C3P0、DBCP、Druid、Hikari
- Druid(德鲁伊)
- Druid连接池是阿里巴巴开源的数据库连接池项目
- 功能强大,性能优秀,是Java语言最好的数据库连接池之一
- 切换:
<dependency><groupId>com.alibaba</groupId> | |
<artifactId>druid-spring-boot-starter</artifactId> | |
<version>1.2.8</version> | |
</dependency> |
10.4 lombok
Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter / setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发,提高效率
注解 | 作用 |
@Getter / @Setter | 为所有的属性提供 get / set 方法 |
@ToString | 会给类自动生成易阅读的 toString 方法 |
@EqualsAndHashCode | 根据类拥有的非静态字段自动重写 equals 方法和 hashCode 方法 |
@Data | 提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @NoArgsConstructor) |
@NoArgsConstructor | 为实体类生成无参的构造器方法 |
@AllArgsConstructor | 为实体类生成除了 static 修饰的字段之外带有各参数的构造器方法 |
注意:Lombok 会再编译时自动生成 Java 代码。我们使用Lombok时,还需安装一个 lombok 的插件(idea自带)
10.5 基础操作
删除
根据主键删除:
- sql 语句
delete from emp where id = 17;
- 接口方法
//根据ID删除数据 | |
//可以使用$代替#,#能预防sql注入 | |
//根据传输进来的id动态删除表中的内容 | |
public void delete(Integer id);//有返回值,返回值代表影响操作的记录数 |
- 注意:如果 mapper 接口方法形参只有一个普通类型的参数,# {...} 里面的属性名可以随便写,如:#{id}、#{value}
日志输出
- 可以再application.properies中,打开mybatis的日志,并指定输出到控制台
#配置mybatis的日志,指定输出到控制台 | |
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl |
- 预编译SQL优势:
- 性能更高
- 更安全(防止SQL注入)
新增
SQL语句:
insert into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (#{username}, #{name}, #{gender},#{image},#{job},#{entrydate},#{deptId}, #{createTime},#{updateTime})
接口方法:
@Insert("insert into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + | |
"values (#{username}, #{name}, #{gender},#{image},#{job},#{entrydate},#{deptId}, #{createTime},#{updateTime})") | |
public void insert(Emp emp); |
主键返回:
描述:在数据添加成功后,需要获取插入数据库数据的主键
@Options(useGeneratedKeys = true, keyProperty = "id") //会自动将生成的主键值,赋值给emp对象的id属性@Insert("insert into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +"values (#{username}, #{name}, #{gender},#{image},#{job},#{entrydate},#{deptId}, #{createTime},#{updateTime})") | |
public void insert(Emp emp); |
更新
接口方法
@Update("update emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}, entrydate = #{entrydate},dept_id = #{deptId}, update_time = #{updateTime} where id = #{id}") | |
public void update(Emp emp); |
查询
@Select("select * from emp where id = #{id}")public Emp getById(Integer id);
数据封装
- 实体类属性名 和 数据库表查询返回的字段名一致,mybatis 会自动封装
- 如果实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装
- 起别名:在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
"dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}") | |
public Emp getById(Integer id) |
- 手动结果映射:通过@Results及@Result进行手动结果映射
@Results({ | |
@Result(column = "dept_id", property = "deptId"), | |
@Result(column = "create_time", property = "createTime"), | |
@Result(column = "update_time", property = "updateTime") | |
}) | |
@Select("select * from emp where id = #{id}") | |
public Emp getById(Integer id); |
- 开启驼峰命名:如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射
- 需要严格的遵守数据库中的字段名是下划线分隔,实体类中的变量名是驼峰命名
- #开启mybatis的驼峰命名自动映射开关 mybatis.configuration.map-underscore-to-camel-case=true
条件查询
- 接口方法
//条件查询员工 | |
"entrydate between #{begin} and #{end} order by update_time desc ;") | |
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end |
- 改进:
//条件查询员工 | |
"entrydate between #{begin} and #{end} order by update_time desc") | |
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end); |
10.6 XML映射文件
- 规范:
- XML映射文件的名称和 Mapper 接口名称一致,并且将 XML 映射文件和 Mapper 接口放在相同包下(同包同名)
- XML映射文件的 namespace 属性为 Mapper 接口全限定名一致
- XML映射文件中 sql 语句的 id 与 Mapper 接口中的方法名一致,并保持返回类型一致
Mapper接口:
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
XML 映射文件:
<mapper namespace="com.itheima.mapper.EmpMapper"><select id="list" resultType="com.itheima.pojo.Emp"> | |
select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and | |
entrydate between #{begin} and #{end} order by update_time desc | |
</select> | |
</mapper> |
使用 Mybatis 的注解,主要是来完成一些简单的增删改查功能,如果需要实现复杂的SQL功能,建议使用XML来排至映射的语句
官方说明:https://mybatis.net.cn/getting-started.html
10.7 动态 SQL
随着用户的输入或外部条件变化而变化的 SQL 语句
10.7.1 if
<if>
:用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接
<where>
:where 元素只会在子标签有内容的情况下才插入 where 子句。而且会自动去除子句的开头的 and 或 or
<set>
:动态的在行首插入 SET 关键字,并会删掉额外的逗号(用在 update 语句中)
<select id="list" resultType="com.itheima.pojo.Emp"> | |
select * | |
from emp | |
<where> | |
<if test="name != null"> | |
name like concat('%', #{name}, '%') | |
</if> | |
<if test="gender != null"> | |
and gender = #{gender} | |
</if> | |
<if test="begin != null and end != null"> | |
and entrydate between #{begin} and #{end} | |
</if> | |
</where> | |
order by update_time desc | |
</select> |
案例:完善更新员工功能,修改为动态更新员工数据信息
<!--动态更新员工信息--> | |
<update id="update2"> | |
update emp | |
<set> | |
<if test="username != null"> | |
username = #{username}, | |
</if> | |
<if test="name != null"> | |
name = #{name}, | |
</if> | |
<if test="gender != null"> | |
gender = #{gender}, | |
</if> | |
<if test="image"> | |
image = #{image}, | |
</if> | |
<if test="job != null"> | |
job = #{job}, | |
</if> | |
<if test="entrydate"> | |
entrydate = #{entrydate}, | |
</if> | |
<if test="deptId != null"> | |
dept_id = #{deptId}, | |
</if> | |
<if test="updateTime"> | |
update_time = #{updateTime} | |
</if> | |
</set> | |
where id = #{id} | |
</update> |
10.7.2 foreach
- 属性:
- collection:要遍历的集合
- item:遍历出来的元素
- separator:遍历出来元素的分隔符
- open:遍历开始前拼接的SQL片段
- close:遍历结束后拼接的SQL片段
- 接口方法:
//批量删除员工public void deleteByIds(List<Integer> ids);
- XML 映射文件
<delete id="deleteByIds"> | |
delete | |
from emp | |
where id in | |
<foreach collection="ids" item="id" separator="," open="(" close=")"> | |
#{id} | |
</foreach> | |
</delete> |
10.7.3 sql、include
<sql>
:定义可重用的 SQL 片段
<include>
:通过属性 refid,指定包含的 sql 片段
<sql id="commonSelect"> | |
select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time | |
from emp | |
</sql> | |
<select id="list" resultType="com.itheima.pojo.Emp"> | |
<include refid="commonSelect"/> | |
<where> | |
<if test="name != null"> | |
name like concat('%', #{name}, '%') | |
</if> | |
<if test="gender != null"> | |
and gender = #{gender} | |
</if> | |
<if test="begin != null and end != null"> | |
and entrydate between #{begin} and #{end} | |
</if> | |
</where> | |
order by update_time desc | |
</select> |
11. SpringBootWeb案例
11.1 准备工作
环境搭建
- 准备数据库表(dept、emp)
- 创建 springboot 工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)
- 配置文件 application.properties 中引入 mybatis 的配置信息,准备对应的实体类
- 准备对应的 Mapper、Service(接口、实现类)、Controller 基础结构
开发规范
- 案例基于当前最为主流的前后端分离开发模式
- 开发者规范--Restful:
- REST,表属性状态转换,它是一种软件架构风格
https://localhost:8080/users/1 get:查询id为1的用户 | |
https://localhost:8080/users post:新增用户 | |
https://localhost:8080/users put:修改用户 | |
https://localhost:8080/users/1 delete:删除id为1的用户 |
- 开发流程:
- 查看页面原型明确需求
- 阅读接口文档
- 思路分析
- 接口开发
- 接口测试
- 前后端联调
11.2 部门管理
查询部门
- 前端发送请求访问到 Controller方法
//可以直接调用log下面的info方法来记录日志 | |
public class DeptController { | |
//注入service对象private DeptService deptService; | |
//@RequestMapping(value = "/depts", method = RequestMethod.GET)@GetMapping("/depts") /*限定请求方式为 get ,post请求调用 @PostMapping*/public Result list(){ | |
log.info("查询全部部门数据"); | |
//调用service查询部门数据 | |
List<Dept> deptList = deptService.list(); | |
return Result.success(deptList); | |
} | |
} |
- 方法中调用 Service 获取数据,在 Service 方法中调用 mapper 接口中的方法来查询全部的部门信息
public class DeptServiceImpl implements DeptService { | |
DeptMapper deptMapper; | |
List<Dept> list() { | |
return deptMapper.list(); | |
} | |
} |
- mapper 接口会像数据库发送 sql 语句查询全部的部门,并把查询的信息封装到 List 集合中
public interface DeptMapper { | |
/** | |
* 查询全部部门数据 | |
* @return | |
*/ | |
List<Dept> list(); | |
} |
- 最终返回给Service,Service拿到后再返回给Controller,Controller拿到后再返回给前端
前后端联调
- 将nginx解压放到没无中文无空格的目录下(develop目录下)
- 启动 nginx(打开点击nginx.exe),访问测试:http://localhost:90
删除部门
- 一个完整的请求路径,应该是类上的 @RequestMapping 的 value 属性 + 方法上的 @RequestMapping 的 value 属性
/** | |
* 部门管理Controller | |
*/ | |
@Slf4j //可以直接调用log下面的info方法来记录日志 | |
@RestController | |
@RequestMapping("/depts") | |
public class DeptController { | |
@Autowired //注入service对象private DeptService deptService; | |
/** | |
* 查询部门数据 | |
* @return Result | |
*///@RequestMapping(value = "/depts", method = RequestMethod.GET)@GetMapping /*限定请求方式为 get ,post请求调用 @PostMapping*/public Result list(){ | |
log.info("查询全部部门数据"); | |
//调用service查询部门数据List<Dept> deptList = deptService.list(); | |
return Result.success(deptList); | |
} | |
/** | |
* 删除部门 | |
* @return Result | |
*/@DeleteMapping("/{id}") | |
public Result delete(@PathVariable Integer id){ | |
log.info("根据id删除部门:{}",id); | |
//调用service删除部门 | |
deptService.delete(id); | |
return Result.success(); | |
} | |
/** | |
* 新增部门 | |
* @return Result | |
*/@PostMappingpublic Result add(@RequestBody Dept dept){ | |
log.info("新增部门:{}", dept); | |
deptService.add(dept); | |
return Result.success(); | |
} | |
} |
11.3 员工管理
- 分页查询
- 请求参数:页码、每页展示的记录数
- 响应结果:总记录数、结果列表(PageBean)
- 注解:
@RequestParam(defaultValue="1") //设置请求参数默认值
- 分页插件
//引入依赖 | |
<dependency> | |
<groupId>com.github.pagehelper</groupId> | |
<artifactId>pagehelper-spring-boot-starter</artifactId> | |
<version>1.4.6</version> | |
</dependency> | |
PageHelper.startPage(pageNum, pageSize); | |
List<Emp> list = empMapper.list(); | |
Page<Emp> page = (Page<Emp>)list; |
- 条件分页查询:
- 条件查询:动态SQL - XML 映射文件
- 分页查询:PageHelper 分页插件
public class EmpController { | |
| |
EmpService empService; | |
| |
public Result page( Integer page, | |
Integer pageSize, | |
String name, Short gender, | |
LocalDate begin, | |
LocalDate end){ | |
//@RequestParam:设置默认值,如果前端没设置,默认值是1 | |
log.info("分页查询,参数:{},{},{},{},{},{}",page, pageSize, name, gender, begin, end); | |
//调用service分页查询 | |
PageBean pageBean = empService.page(page, pageSize, name, gender, begin, end); | |
return Result.success(pageBean); | |
} | |
} | |
public interface EmpService { | |
/** | |
* 分页查询 | |
* @param page | |
* @param pageSize | |
* @return | |
*/ | |
PageBean page(Integer page, Integer pageSize,String name, Short gender, LocalDate begin, LocalDate end); | |
} | |
public class EmpServiceImpl implements EmpService { | |
EmpMapper empMapper; | |
Short gender, LocalDate begin, LocalDate end) { | PageBean page(Integer page, Integer pageSize, String name,|
//设置分页参数 | |
PageHelper.startPage(page, pageSize); | |
//执行查询 | |
List<Emp> empList = empMapper.list(name, gender, begin, end); | |
Page<Emp> p = (Page<Emp>) empList; | |
//封装PageBean对象 | |
PageBean pageBean = new PageBean(p.getTotal(), p.getResult()); | |
return pageBean; | |
} | |
} | |
public interface EmpMapper { | |
/** | |
* 查询总记录数 | |
* @return long | |
*/public List<Emp> list( String name, Short gender, LocalDate begin, LocalDate end); | |
} | |
<mapper namespace="com.example.mapper.EmpMapper"> | |
<!--条件查询--> | |
<select id="list" resultType="com.example.pojo.Emp"> | |
select * | |
from emp | |
<where> | |
<if test="name != null"> | |
name like concat('%', #{name}, '%') | |
</if> | |
<if test="gender != null"> | |
and gender = #{gender} | |
</if> | |
<if test="begin != null and end != null"> | |
and entrydate between #{begin} and #{end} | |
</if> | |
</where> | |
order by update_time desc | |
</select> | |
</mapper> |
删除员工
@DeleteMapping("/{ids}") | |
public Result delete(@PathVariable List<Integer> ids){ | |
log.info("批量删除,ids:{}", ids); | |
empService.delete(ids); | |
return Result.success(); | |
} | |
void delete(List<Integer> ids); | |
@Overridepublic void delete(List<Integer> ids) { | |
empMapper.delete(ids); | |
} | |
void delete(List<Integer> ids); | |
<!--批量删除 (1, 2, 3)--><delete id="delete"> | |
delete | |
from emp | |
where id in | |
<foreach collection="ids" item="id" separator="," open="(" close=")"> | |
#{id} | |
</foreach></delete> |
新增员工
@PostMapping | |
public Result save(@RequestBody Emp emp){ | |
log.info("新增员工, emp:{}", emp); | |
empService.save(emp); | |
return Result.success(); | |
} | |
@Override | |
public void save(Emp emp) { | |
emp.setCreateTime(LocalDateTime.now()); | |
emp.setUpdateTime(LocalDateTime.now()); | |
empMapper.insert(emp); | |
} | |
@Insert("insert into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " + | |
"values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") | |
void insert(Emp emp); |
文件上传
- 简介
- 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程
- 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传
- 前端页面三要素:
- 表单项 type = "file"
- 表单提交方式 post
- 表单的 enctype 属性 multipart / formm-data
- 服务端接收文件:
- MultipartFile
@Slf4j | |
@RestController | |
public class UploadController { | |
public Result upload(String username, Integer age, MultipartFile image){ | |
log.info("文件上传:{}, {}, {}", username,age, image); | |
return Result.success(); | |
} | |
} |
- 本地存储:
- 在服务端,接收到上传来的文件之后,将文件存储在本地服务器磁盘中。
@Slf4j | |
@RestController | |
public class UploadController { | |
@PostMapping("/upload")public Result upload(String username, Integer age, MultipartFile image) throws Exception { | |
log.info("文件上传:{}, {}, {}", username,age, image); | |
//获取原始文件名String originalFilename = image.getOriginalFilename(); | |
| |
//构造唯一的文件名(不能重复) -- uuid(通用唯一识别码)int index = originalFilename.lastIndexOf("."); //用 . 分割String extname = originalFilename.substring(index); //截取后缀名String newFileName = UUID.randomUUID().toString() + extname; //获取新的字符串作为name | |
log.info("新的文件名:{}", newFileName); | |
| |
/* | |
String newFileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(".")); | |
*///将文件存储在服务器的磁盘目录中 | |
image.transferTo(new File("D:\\DeliveryOptimization\\Cache" + newFileName)); | |
return Result.success(); | |
} | |
} |
- 在Springboot中,文件上传默认单个文件允许最大大小为1MB,如果需要大文件的上传,可以进行如下配置:
#配置单个文件上传大小的限制 | |
spring.servlet.multipart.max-file-size=10MB | |
#配置单个请求最大大小的限制(一次请求可以上传多个文件) | |
spring.servlet.multipart.max-request-size=100MB |
阿里云
- 阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商
- 对象云存储OSS
- 注册阿里云(实名认证)
- 充值
- 开通对象存储服务(OSS)
- 创建bucket
- Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间
- 获取 AccessKey(密钥)
- 参照官方SDK编写入门程序
- SDK:软件开发工具包,包括副宗主软件开发的依赖(jar包)、代码示例等,都可以叫做SDK
- 案例集成OSS
修改员工
查询回显
log.info("根据id查询员工信息,id: {}", id); | |
Emp emp = empService.getById(id); | |
return Result.success(); | |
} | |
} | |
Emp getById(Integer id); |
修改员工
@PutMapping | |
public Result update(@RequestBody Emp emp){ | |
//json格式数据想封装到实体类中,需要加@RequestBody注解 | |
log.info("更新员工信息:{}", emp); | |
empService.update(emp); | |
return Result.success(); | |
} | |
@Override | |
public void update(Emp emp) { | |
emp.setUpdateTime(LocalDateTime.now()); | |
empMapper.update(emp); | |
} | |
<!--更新员工--> | |
<update id="update"> | |
update emp | |
<set> | |
<if test="username != null and username != ''"> | |
username = #{username}, | |
</if> | |
<if test="password != null and password != ''"> | |
password = #{password}, | |
</if> | |
<if test="name != null and name != ''"> | |
name = #{name}, | |
</if> | |
<if test="gender != null"> | |
gender = #{gender}, | |
</if> | |
<if test="image != null and image != ''"> | |
image = #{image}, | |
</if> | |
<if test="job != null"> | |
job = #{job}, | |
</if> | |
<if test="entrydate != null"> | |
entrydate = #{entrydate}, | |
</if> | |
<if test="deptId != null"> | |
dept_id = #{deptId}, | |
</if> | |
<if test="updateTime != null"> | |
update_time = #{updateTime} | |
</if> | |
</set> | |
where id = #{id} | |
</update> |
11.4 配置文件
参数配置化
- @Value 注解通常用于外部配置的属性注入,具体用法为:@Value("${配置文件中的key}")
aliyun.oss.endpoint=https://oss-cn-hhangzhou.aliyuncs.com | |
aliyun.oss.accessKeyId=LTAI4GCH1vX6DKqJWxd6nEuW | |
aliyun.oss.accessKeySecret=yBshYweHOpqDuhCArrVHwIiBKpyqSL | |
aliyun.oss.bucketName=web-tlias | |
public class AliOSSUtils { | |
private String endpoint; | |
private String accessKeyId; | |
private String accessKeySecret; | |
private String bucketName; | |
} |
yml配置文件
- 常见配置文件格式对比
- XML:
<server><port>8080</port> | |
<address>127.0.0.1</address> | |
</server> | |
server.port=8080 server.address=127.0.0.1 | |
server: port: 8080 address: 127.0.0.1 |
- yml 基本语法:
- 大小写敏感
- 数值前边必须有空格,作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用 Tab 键,只能用空格(idea 中会自动将 Tab 转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左侧对其即可
- # 表示注释,从这个字符一致到行尾,都会被解析器忽略
- 对象 / Map 集合:
- user: name: Tom age: 20 address: beijing
- 数组 / List / Set 集合:
- hobby: - java - C - game - sport
spring: | |
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true |
@ConfigurationProperties
- 和
@Value
相同点: - 都是用来注入外部配置的属性的
- 不同点:
- @Value注解只能一个一个的进行外部属性的注入
- @ConfigurationProperties 可以批量的将外部的属性配置注入到 bean 对象的属性中
11.5 登录认证
public class LoginController { | |
EmpService empService; | |
| |
public Result login( { Emp emp) | |
log.info("员工登录:{}", emp); | |
Emp e = empService.login(emp); | |
return e != null ? Result.success() : Result.error("用户名或密码错误"); | |
} | |
} | |
Emp getByUsernameAndPassword(Emp emp); |
11.6 登录校验
- 在未登录情况下,我们也可以直接访问部门管理、员工管理等功能。
- 登录标记:
- 用户登录成功之后,每一次请求中,都可以获取到该标记
- 统一拦截:
- 过滤器:Filter
- 拦截器:Interceptor
11.6.1 会话技术
- 会话:用户打开浏览器,访问 web 服务器的资源,会话建立,直到有一方断开连接,会话结束。在一次会话中可以包含 多次 请求和响应
- 会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求之间 共享数据
- 会话跟踪方案:
- 客户端会话跟踪技术:Cookie
- 服务端会话跟踪技术:Session
- 令牌技术
Cookie
- 优点:HTTP 协议中支持的技术
- 缺点:
- 移动端 APP 无法使用 Cookie
- 不安全,用户可以自己禁用Cookie
- Cookie 不能跨域
Session
- 优点:存储在服务端,安全
- 缺点:
- 服务器集群环境下无法直接使用 Session
- Cookie 的缺点
令牌技术(主流方案)
- 优点:
- 支持 PC 端、移动端
- 解决集群环境下的认证问题
- 减轻服务器端存储压力
- 缺点:
- 需要自己实现
11.6.2 JWT令牌
- 定义了一种简洁的、自包含的格式,用于在通信双方以 json 数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。
- 组成:
- 第一部分:Header(头),记录令牌类型、签名算法等。例如:{"alg":"HS256", "type":"JWT"}
- 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。例如:{"id":"1", "username":"Tom"}
- 第三部分:Signature(签名),防止 Token 被篡改、确保安全性。将 header、payload,并加入指定密钥,通过指定签名算法计算而来。
- 场景:登录认证
- 登录成功后,生成令牌
- 后续每个请求,都要携带 JWT 令牌,系统在每次请求处理之前,先校验令牌,通过后再处理
对应依赖:
<!--JWP令牌--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
生成:
/** | |
* 生成JWT | |
*/ void testGenJWT(){ | |
Map<String, Object> claims = new HashMap<>(); | |
claims.put("id", 1); | |
claims.put("name", "Tom"); | |
| |
String jwt = Jwts.builder() //构建Jwt令牌 | |
.signWith(SignatureAlgorithm.HS256, "example")//签名算法 | |
.setClaims(claims)//设置自定义内容(载荷) | |
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))//设置有效期为 1h | |
.compact(); | |
System.out.println(jwt); | |
} |
解析:
/** | |
* 校验JWT | |
*/ | |
@Test | |
public void testParseJwt(){ | |
Claims claims = Jwts.parser() | |
.setSigningKey("example") //指定签名密钥 | |
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiVG9tIiwiaWQiOjEsImV4cCI6MTY5MDAyOTI4Nn0.bxU-aSO5VpOAi7U56Uz2jazLSzj9cu0E-MSE8VkKbSo") //解析令牌 | |
.getBody(); | |
System.out.println(claims); | |
} |
- 注意事项:
- JWT 校验时使用的签名密钥,必须和生成 JWT 令牌时使用的密钥是配套的
- 如果 JWT 令牌解析校验时报错,则说明 JWT 令牌被篡改 或 失效了,令牌非法。
- 思路:
- 令牌生成:登录成功后,生成JWT令牌,并返回给前端
- 令牌校验:在请求到达服务端后,对令牌进行统一拦截、校验
- 步骤
- 引入JWT令牌操作工具类
- 登录完成后,调用工具类生成JWT令牌,并返回
public class LoginController { | |
EmpService empService; | |
"/login") | (|
public Result login() { Emp emp | |
log.info("员工登录:{}", emp); | |
Emp e = empService.login(emp); | |
/*登录成功,生成令牌并下发令牌*/if (e != null){ | |
Map<String, Object> claims = new HashMap<>(); | |
claims.put("id", e.getId()); | |
claims.put("name", e.getName()); | |
claims.put("username", e.getUsername()); | |
String jwt = JwtUtils.generateJwt(claims);//jwt包含了当前登录的员工信息return Result.success(jwt); | |
} | |
/*登录失败,返回错误信息*/return Result.error("用户名或密码错误"); | |
} | |
} |
11.6.3 过滤器 Filter
- 概念:Filter 过滤器,是 JavaWeb 三大组件(Servlet、Filter、Listener)之一
- 过滤器可以把对资源的请求 拦截 下来,从而实现一些特殊的功能
- 过滤器一般完成一些 通用 的操作,比如:登录校验、统一编码处理、敏感字符处理等。
定义 Filter:定义一个类,实现 Filter 接口,并重写其所有方法
配置 Filter:Filter 类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启 Servlet 组件支持
@WebFilter(urlPatterns = "/*") | |
public class DemoFilter implements Filter { | |
@Override //初始化方法,只调用一次public void init(FilterConfig filterConfig) throws ServletException { | |
System.out.println("init初始化"); | |
} | |
@Override //拦截到请求之后,调用多次public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | |
System.out.println("拦截"); | |
//放行 | |
chain.doFilter(request, response); | |
} | |
@Override //销毁方法,只调用一次public void destroy() { | |
System.out.println("init销毁"); | |
} | |
} |
启动类中:
@ServletComponentScan //Filter是JavaWeb三大组件之一,想在springboot上使用JavaWeb组件必须使用注解 | |
@SpringBootApplication | |
public class TliasWebManagementApplication { | |
public static void main(String[] args) { | |
SpringApplication.run(TliasWebManagementApplication.class, args); | |
} | |
} |
执行流程:
请求 ----> 放行前逻辑 --->放行 --> 资源 --->放行后逻辑
拦截路径
Filter 可以根据需求,配置不同的拦截资源路径:
public class DemoFilter implements Filter { | |
} |
拦截路径 | urlPatterns | 含义 |
拦截具体路径 | /login | 只有访问 /login 路径时,才会被拦截 |
目录拦截 | /emps/* | 访问 /emps 下的所有资源,都会被拦截 |
拦截所有 | /* | 访问所有资源,都会被拦截 |
过滤器链
- 介绍:一个 web 应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
- 顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序
登录校验
- 步骤:
- 获取请求url
- 判断请求url中是否包含 login, 如果包含,说明是登录操作,放行。
- 获取请求头中的令牌(token)。
- 判断令牌是否存在,如果不存在,返回错误结果(未登录)
- 解析 token,如果解析失败,返回错误结果(未登录)
- 放行
public class LoginCheckFilter implements Filter { | |
void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { | |
HttpServletRequest request = (HttpServletRequest) servletRequest; | |
HttpServletResponse response = (HttpServletResponse) servletResponse; | |
/*获取请求url*/String url = request.getRequestURI().toString(); | |
log.info("请求的url:{}", url); | |
/*判断请求url是否包含login,如果包含,说明是登录操作,放行*/if (url.contains("login")) { //如果包含了login关键字是登录请求 | |
log.info("登录操作,放行……"); | |
filterChain.doFilter(servletRequest, servletResponse); | |
return; | |
} | |
/*获取请求头中的令牌(token)*/String jwt = request.getHeader("token"); | |
/*判断令牌是否存在,如果不存在,返回错误结果(未登录)*/if (!StringUtils.hasLength(jwt)) { | |
log.info("请求头token为空,返回未登录信息"); | |
Result error = Result.error("NOT_LOGIN"); | |
//手动转换 对象 -->json ---------阿里巴巴fastJSONString notLogin = JSONObject.toJSONString(error); | |
response.getWriter().write(notLogin); | |
} | |
/*解析token,如果解析失败,返回错误结果(未登录)*/try { | |
JwtUtils.parseJWT(jwt); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
log.info("解析令牌失败,返回未登录错误信息"); | |
Result error = Result.error("NOT_LOGIN"); | |
//手动转换 对象 -->json ---------阿里巴巴fastJSONString notLogin = JSONObject.toJSONString(error); | |
response.getWriter().write(notLogin); | |
return; | |
} | |
/*放行*/ | |
log.info("令牌合法"); | |
filterChain. doFilter(servletRequest, servletResponse); | |
} | |
} |
11.6.4 拦截器 Interceptor
入门
- 概念:是一种动态拦截方法调用的机制,类似于过滤器,Spring 框架中提供的,用来动态拦截控制器方法的执行。
- 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
@Component | |
public class LoginCheckInterceptor implements HandlerInterceptor { | |
@Override //目标资源方法运行前运行,返回true 放行;false 不放行public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("preHandle……"); | |
return true; | |
} | |
//Controller方法运行 | |
@Override //目标资源方法运行后运行public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println("postHandle ...");; | |
} | |
@Override //视图渲染完毕后运行,最后运行public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println("afterCompletion ...");; | |
} | |
} | |
@Configuration //配置类 | |
public class WebConfig implements WebMvcConfigurer { | |
@Autowiredprivate LoginCheckInterceptor loginCheckInterceptor; | |
@Overridepublic void addInterceptors(InterceptorRegistry registry) { | |
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//拦截所有资源 | |
} | |
} |
详解
- 拦截器可以根据需求,配置不同的拦截路径:
void addInterceptors(InterceptorRegistry registry) { | |
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); | |
// 需要拦截哪些资源 不需要拦截哪些资源 | |
} |
拦截路径 | 含义 | 举例 |
/* | 一级路径 | 能匹配/depts,/emps,/login,不能匹配 /depts/1 |
/** | 任意级路径 | 能匹配 /depts,/depts/1,/depts/1/2 |
/depts/* | /depts 下的一级路径 | 能匹配 /depts/1,不能匹配 /depts/1/2,/depts |
/depts/** | /depts 下的任意级路径 | 能匹配 /depts,/depts/1,/depts/1/2,不能匹配 /emps/1 |
- Filter 与 Interceptor
- 接口规范不同:过滤器需要实现 Filter 接口,而拦截器需要实现 HandlerInterceptor 接口
- 拦截范围不同:过滤器 Filter 会拦截所有的资源,而 Interceptor 只会拦截 Spring 环境中的资源
登录校验 - Interceptor
- 步骤:
- 获取请求url
- 判断请求url中是否包含 login, 如果包含,说明是登录操作,放行。
- 获取请求头中的令牌(token)。
- 判断令牌是否存在,如果不存在,返回错误结果(未登录)
- 解析 token,如果解析失败,返回错误结果(未登录)
- 放行
public class LoginCheckInterceptor implements HandlerInterceptor { | |
//目标资源方法运行前运行,返回true 放行;false 不放行public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {/*获取请求url*/String url = req.getRequestURI().toString(); | |
log.info("请求的url:{}", url); | |
/*判断请求url是否包含login,如果包含,说明是登录操作,放行*/if (url.contains("login")) { //如果包含了login关键字是登录请求 | |
log.info("登录操作,放行……"); | |
return true; | |
} | |
/*获取请求头中的令牌(token)*/String jwt = req.getHeader("token"); | |
/*判断令牌是否存在,如果不存在,返回错误结果(未登录)*/if (!StringUtils.hasLength(jwt)) { | |
log.info("请求头token为空,返回未登录信息"); | |
Result error = Result.error("NOT_LOGIN"); | |
//手动转换 对象 -->json ---------阿里巴巴fastJSONString notLogin = JSONObject.toJSONString(error); | |
resp.getWriter().write(notLogin); | |
return false; | |
} | |
/*解析token,如果解析失败,返回错误结果(未登录)*/try { | |
JwtUtils.parseJWT(jwt); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
log.info("解析令牌失败,返回未登录错误信息"); | |
Result error = Result.error("NOT_LOGIN"); | |
//手动转换 对象 -->json ---------阿里巴巴fastJSONString notLogin = JSONObject.toJSONString(error); | |
resp.getWriter().write(notLogin); | |
return false; | |
} | |
/*放行*/ | |
log.info("令牌合法"); | |
return true; | |
} | |
//目标资源方法运行后运行public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { | |
System.out.println("postHandle ..."); | |
} | |
//视图渲染完毕后运行,最后运行public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { | |
System.out.println("afterCompletion ..."); | |
} | |
} |
11.7 异常处理
- 全局异常处理器:
@RestControllerAdvice | |
@ExceptionHandler(Exception.class) //捕获所有异常 |
系列文章
嘎嘎基础的JavaWeb(中)