前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >嘎嘎基础的JavaWeb(中)

嘎嘎基础的JavaWeb(中)

原创
作者头像
xiao李
修改2024-01-10 23:05:56
2120
修改2024-01-10 23:05:56
举报

9. MySQL

  • 数据库设计:
    • MySQL 概述
    • 数据库设计 - DDL
    • 多表设计
  • 数据库操作:
    • 数据库操作 - DML
    • 数据库操作 - DQL
    • 事务
    • 多表查询
  • 数据库优化
    • 索引
    • SQL 优化
    • 分库分表

企业开发使用方式:

代码语言: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

代码语言:sql
复制
case 表达式 when 值1 then 结果1 when 值2 then 结果2 ... else ... end

9.3.2 分组查询

group by having

聚合函数

介绍:将一列数据作为一个整体,进行纵向计算

语法:select 聚合函数(字段列表) from 表名;

函数

功能

count

统计数量

max

最大值

min

最小值

avg

平均值

sum

求和


分组查询:

代码语言:sql
复制
select 字段列表 from 表名 [where 条件] group by 分组字段名 [having 分组后过滤的条件];
  • 根据性别分组,统计员工数量
    • select gender, count(*) from tb_emp group by gender;
  • where 与 having 区别:
    1. 执行时机不同:where 是分组之前进行过滤,不满足 where 条件,不参与分组;而 having 是分组之后对结果进行过滤。
    2. 判断条件不同:where 不能对聚合函数进行判断,而 having 可以。
  • 注意事项:
    1. 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。
    2. 执行顺序: where > 聚合函数 > having


9.3.3 排序查询

order by

代码语言:sql
复制
select 字段列表 from 表名 [where 条件列表] [group by 分组字段] order by 字段1 排序方式1, 字段2 排序方式2 … ;

ASC:升序(默认)

DESC:降序

  • 如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序。

9.3.4 分页查询

limit

代码语言:sql
复制
select 字段列表 from 表名 limit 起始索引, 查询记录数;
  • 查询记录数为每一页要展示的数据的条数
  • 注意事项:
    1. 起始索引从 0 开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
    2. 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是 limit。
    3. 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10。

常用函数

代码语言:sql
复制
if (表达式, tvalue, fvalue);  -- 当表达式为true时,取tvalue;当表达式为false时,取fvalue
代码语言:sql
复制
case expr when value1 then result1 [when value2 then result2 ...] [else result] end     -- 类似于switch语句

9.4 多表设计

外键

  • 物理外键:
    • 概念:使用foreign key定义外键关联另外一张表。
    • 缺点:
      1. 影像增删改的效率(需要检查外键关系)
      2. 仅用于单节点数据库,不适用于分布式、集群场景
      3. 容易引发数据库的死锁问题,消耗性能
    • -- 创建表时指定 create table 表名( 字段名 数据类型; ... [constraint] [外键名称] foreign key (外键字段名) references 主表(字段名) )
    • -- 建完表后,添加外键 alter table 表名 add constraint 外键名称 foreign key (外键字段名) references 主表(字段名);
  • 逻辑外键:
    • 概念:在业务逻辑中,解决外键关联
    • 通过逻辑外键,就可以很方便的解决上述问题

一对多:在多的一方添加外键关联一的一方的主键

代码语言:sql
复制
-- 部门管理
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)
代码语言:sql
复制
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 '用户教育信息表';

多对多:

  • 案例:学生 与 课程的关系
  • 关系:一个学生可以选修多门课程,一门课程也可以供多个学生选择
  • 实现:建立第三张中间表,中间表至少包含两个外键,分别关联两方主键
代码语言:sql
复制
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的交集部分数据

隐式内连接:

代码语言:sql
复制
select 字段列表 from 表1, 表2 where 条件 ……;

显式内连接:

代码语言:sql
复制
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
  • 分类:
    1. 标量子查询:子查询返回的结果为单个值。
    2. 列子查询:子查询返回的结果为一列。
    3. 行子查询:子查询返回的结果为一行。
    4. 表子查询:子查询返回的结果为多行多列。

9.6 事务

默认MySQL的事务时自动提交的,也就是说当执行一条DML语句,MySQL会立即隐式的提交事务。

  • 开启事务:
    • start transaction; begin;
  • 提交事务:
    • commit;
  • 回滚事务:
    • rollback;
  • 四大特性:
    1. 原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
    2. 一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
    3. 隔离性(lsolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
    4. 持久性(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 入门程序:

  1. 准备工作(创建 springboot )工程、数据库表User、实体类User
    • 数据库表
代码语言:sql
复制
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');
代码语言:java
复制
@Data @NoArgsConstructor     
//无参构造 
@AllArgsConstructor    
//全参构造 
public class User {
     private Integer id;     
     private String name;     
     private short age;     
     private short gender;     
     private String phone; 
}
  1. 引入Mybatis相关依赖,配置Mybatis(数据库连接信息)
代码语言:yml
复制
#驱动类名称 
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语句
代码语言:java
复制
@Mapper   
//在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理 
public interface UserMapper {
     //查询全部用户信息     
     @Select("select * from user")     
     public List<User> list(); 
}

  • 单元测试
代码语言:java
复制
@SpringBootTest    
//springboot整合单元测试的注解 
class SpringbootMybatisQuickstartApplicationTests {
 ​     @Autowired    //依赖注入
      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语言最好的数据库连接池之一
  • 切换:
代码语言:xml
复制
<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 语句
代码语言:sql
复制
delete from emp where id = 17;
  • 接口方法
代码语言:java
复制
//根据ID删除数据
@Delete("delete from emp where id = #{id}")     //可以使用$代替#,#能预防sql注入
//根据传输进来的id动态删除表中的内容
public void delete(Integer id);//有返回值,返回值代表影响操作的记录数
  • 注意:如果 mapper 接口方法形参只有一个普通类型的参数,# {...} 里面的属性名可以随便写,如:#{id}、#{value}

日志输出

  • 可以再application.properies中,打开mybatis的日志,并指定输出到控制台
代码语言:yml
复制
#配置mybatis的日志,指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  • 预编译SQL优势:
    • 性能更高
    • 更安全(防止SQL注入)

新增

SQL语句:

代码语言:javascript
复制
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})

接口方法:

代码语言:javascript
复制
    @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);

主键返回:

描述:在数据添加成功后,需要获取插入数据库数据的主键

代码语言:javascript
复制
@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);

更新

接口方法

代码语言:javascript
复制
@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);

查询

代码语言:javascript
复制
@Select("select * from emp where id = #{id}")
    public Emp getById(Integer id);

数据封装

  • 实体类属性名 和 数据库表查询返回的字段名一致,mybatis 会自动封装
  • 如果实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装
  • 起别名:在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
代码语言:java
复制
@Select("select id, username, password, name, gender, image, job, entrydate, " +
             "dept_id deptId, create_time createTime, update_time updateTime from emp where id = #{id}")     
public Emp getById(Integer id)
  • 手动结果映射:通过@Results及@Result进行手动结果映射
代码语言:java
复制
@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

条件查询

  • 接口方法
代码语言:sql
复制
//条件查询员工     
@Select("select * from emp where name like '%${name}%' and gender = #{gender} and " +             
"entrydate between #{begin} and #{end} order by update_time desc ;")     
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end
  • 改进:
代码语言:java
复制
//条件查询员工     
@Select("select * from emp where name like concat('%',#{name},'%') and gender = #{gender} and " +             
    "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接口:

代码语言:javascript
复制
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

XML 映射文件:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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 语句中)

代码语言:javascript
复制
<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>

案例:完善更新员工功能,修改为动态更新员工数据信息

代码语言:javascript
复制
<!--动态更新员工信息-->
    <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片段
  • 接口方法:
代码语言:java
复制
//批量删除员工
    public void deleteByIds(List<Integer> ids);
  • XML 映射文件
代码语言: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 片段

代码语言:javascript
复制
<sql id="commonSelect">
    select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
    from emp
</sql>
代码语言:javascript
复制
<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,表属性状态转换,它是一种软件架构风格
代码语言:xml
复制
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的用户
  • 开发流程:
    1. 查看页面原型明确需求
    2. 阅读接口文档
    3. 思路分析
    4. 接口开发
    5. 接口测试
    6. 前后端联调

11.2 部门管理

查询部门

  • 前端发送请求访问到 Controller方法
代码语言:java
复制
@Slf4j   //可以直接调用log下面的info方法来记录日志
@RestController
public class DeptController {

    @Autowired  //注入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 接口中的方法来查询全部的部门信息
代码语言:java
复制
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptMapper deptMapper;
    @Override
    public List<Dept> list() {
        return deptMapper.list();
    }
}
  • mapper 接口会像数据库发送 sql 语句查询全部的部门,并把查询的信息封装到 List 集合中
代码语言:java
复制
@Mapper
public interface DeptMapper {
    /**
     * 查询全部部门数据
     * @return
     */
    @Select("select * from dept")
    List<Dept> list();
}
  • 最终返回给Service,Service拿到后再返回给Controller,Controller拿到后再返回给前端

前后端联调

  • 将nginx解压放到没无中文无空格的目录下(develop目录下)
  • 启动 nginx(打开点击nginx.exe),访问测试:http://localhost:90

删除部门

  • 一个完整的请求路径,应该是类上的 @RequestMapping 的 value 属性 + 方法上的 @RequestMapping 的 value 属性
代码语言:java
复制
/**
 * 部门管理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
     */
    @PostMapping
    public Result add(@RequestBody Dept dept){
        log.info("新增部门:{}", dept);
        deptService.add(dept);
        return Result.success();
    }
}

11.3 员工管理

  • 分页查询
    • 请求参数:页码、每页展示的记录数
    • 响应结果:总记录数、结果列表(PageBean)
  • 注解:
代码语言:java
复制
@RequestParam(defaultValue="1")       //设置请求参数默认值
  • 分页插件
代码语言:xml
复制
//引入依赖
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>1.4.6</version>
</dependency>
代码语言:java
复制
PageHelper.startPage(pageNum, pageSize);
List<Emp> list = empMapper.list();
Page<Emp> page = (Page<Emp>)list;
  • 条件分页查询:
    • 条件查询:动态SQL - XML 映射文件
    • 分页查询:PageHelper 分页插件
代码语言:javascript
复制
@Slf4j
@RestController
public class EmpController {
​
    @Autowired
    private EmpService empService;
​
    @GetMapping("/emps")
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize,
                       String name, Short gender,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") 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);
    }
}
代码语言:javascript
复制
public interface EmpService {
    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @return
     */
    PageBean page(Integer page, Integer pageSize,String name, Short gender, LocalDate begin, LocalDate end);
}
代码语言:javascript
复制
@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    private EmpMapper empMapper;
    @Override
    public PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
        //设置分页参数
        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;
    }
}
代码语言:javascript
复制
@Mapper
public interface EmpMapper {
    /**
     * 查询总记录数
     * @return long
     */
    public List<Emp> list(@Param("name") String name, @Param("gender") Short gender, @Param("begin") LocalDate begin, @Param("end") LocalDate end);
}
代码语言:javascript
复制
<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>

删除员工

代码语言:javascript
复制
@DeleteMapping("/{ids}")
    public Result delete(@PathVariable List<Integer> ids){
        log.info("批量删除,ids:{}", ids);
        empService.delete(ids);
        return Result.success();
    }
代码语言:javascript
复制
void delete(List<Integer> ids);
代码语言:javascript
复制
@Override
    public void delete(List<Integer> ids) {
        empMapper.delete(ids);
    }
代码语言:javascript
复制
void delete(List<Integer> ids);
代码语言:javascript
复制
<!--批量删除 (1, 2, 3)-->
    <delete id="delete">
        delete
        from emp
        where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>

新增员工

代码语言:javascript
复制
@PostMapping
    public Result save(@RequestBody Emp emp){
        log.info("新增员工, emp:{}", emp);
        empService.save(emp);
        return Result.success();
    }
代码语言:javascript
复制
@Override
    public void save(Emp emp) {
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        empMapper.insert(emp);
    }
代码语言:javascript
复制
@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);

文件上传

  • 简介
    • 文件上传,是指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程
    • 文件上传在项目中应用非常广泛,我们经常发微博、发微信朋友圈都用到了文件上传
  • 前端页面三要素:
    1. 表单项 type = "file"
    2. 表单提交方式 post
    3. 表单的 enctype 属性 multipart / formm-data
  • 服务端接收文件:
    • MultipartFile
代码语言:javascript
复制
@Slf4j
@RestController
public class UploadController {
    public Result upload(String username, Integer age, MultipartFile image){
        log.info("文件上传:{}, {}, {}", username,age, image);
        return Result.success();
    }
}

  • 本地存储:
    • 在服务端,接收到上传来的文件之后,将文件存储在本地服务器磁盘中。
代码语言:javascript
复制
@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,如果需要大文件的上传,可以进行如下配置:
代码语言:yml
复制
#配置单个文件上传大小的限制 
spring.servlet.multipart.max-file-size=10MB ​ 
#配置单个请求最大大小的限制(一次请求可以上传多个文件) 
spring.servlet.multipart.max-request-size=100MB


阿里云

  • 阿里云是阿里巴巴集团旗下全球领先的云计算公司,也是国内最大的云服务提供商
  • 对象云存储OSS
    1. 注册阿里云(实名认证)
    2. 充值
    3. 开通对象存储服务(OSS)
    4. 创建bucket
      • Bucket:存储空间是用户用于存储对象(Object,就是文件)的容器,所有的对象都必须隶属于某个存储空间
    5. 获取 AccessKey(密钥)
    6. 参照官方SDK编写入门程序
      • SDK:软件开发工具包,包括副宗主软件开发的依赖(jar包)、代码示例等,都可以叫做SDK
    7. 案例集成OSS

修改员工

查询回显

代码语言:javascript
复制
@GetMapping("/{id}")   //id为路径参数
    public Result getById(@PathVariable Integer id) {
        log.info("根据id查询员工信息,id: {}", id);
        Emp emp = empService.getById(id);
        return Result.success();
    }
代码语言:javascript
复制
@Override
    public Emp getById(Integer id) {
        return empMapper.getById(id);
    }
代码语言:javascript
复制
@Select("select * from emp where id = #{id}")
    Emp getById(Integer id);

修改员工

代码语言:javascript
复制
@PutMapping
    public Result update(@RequestBody Emp emp){
        //json格式数据想封装到实体类中,需要加@RequestBody注解
        log.info("更新员工信息:{}", emp);
        empService.update(emp);
        return Result.success();
    }
代码语言:javascript
复制
@Override
    public void update(Emp emp) {
        emp.setUpdateTime(LocalDateTime.now());
        empMapper.update(emp);
    }
代码语言:javascript
复制
<!--更新员工-->
    <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}")
代码语言:javascript
复制
aliyun.oss.endpoint=https://oss-cn-hhangzhou.aliyuncs.com
aliyun.oss.accessKeyId=LTAI4GCH1vX6DKqJWxd6nEuW
aliyun.oss.accessKeySecret=yBshYweHOpqDuhCArrVHwIiBKpyqSL
aliyun.oss.bucketName=web-tlias
代码语言:javascript
复制
@Component
public class AliOSSUtils {
    @Value("${}")
    private String endpoint;
    @Value("${}")
    private String accessKeyId;
    @Value("${}")
    private String accessKeySecret;
    @Value("${}")
    private String bucketName;
}

yml配置文件

  • 常见配置文件格式对比
    • XML:
代码语言:xml
复制
<server>
     <port>8080</port>     
     <address>127.0.0.1</address> 
</server>
代码语言:xml
复制
server.port=8080 server.address=127.0.0.1
代码语言:xml
复制
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
代码语言:javascript
复制
spring:
  #数据库连接信息的配置
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/tlias
    username: root
    password: 123456
#    文件上传
  servlet:
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
#Mybatis配置
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    map-underscore-to-camel-case: true

@ConfigurationProperties

  • @Value相同点:
    • 都是用来注入外部配置的属性的
  • 不同点:
    • @Value注解只能一个一个的进行外部属性的注入
    • @ConfigurationProperties 可以批量的将外部的属性配置注入到 bean 对象的属性中

11.5 登录认证

代码语言:javascript
复制
@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;
​
    @PostMapping("/login")
    public Result login(@RequestBody Emp emp) {
        log.info("员工登录:{}", emp);
        Emp e = empService.login(emp);
        return e != null ? Result.success() : Result.error("用户名或密码错误");
    }
}
代码语言:javascript
复制
@Select("select * from emp where username = #{username} and password = #{password}")
    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,并加入指定密钥,通过指定签名算法计算而来。
  • 场景:登录认证
    1. 登录成功后,生成令牌
    2. 后续每个请求,都要携带 JWT 令牌,系统在每次请求处理之前,先校验令牌,通过后再处理

对应依赖:

代码语言:javascript
复制
<!--JWP令牌-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

生成:

代码语言:javascript
复制
    /**
     * 生成JWT
     */
    @Test
    public 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);
    }

解析:

代码语言:javascript
复制
    /**
     * 校验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令牌,并返回
代码语言:javascript
复制
@Slf4j
@RestController
public class LoginController {
    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody 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 组件支持

代码语言:javascript
复制
@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销毁");
    }
}

启动类中:

代码语言:javascript
复制
@ServletComponentScan    //Filter是JavaWeb三大组件之一,想在springboot上使用JavaWeb组件必须使用注解
@SpringBootApplication
public class TliasWebManagementApplication {

    public static void main(String[] args) {
        SpringApplication.run(TliasWebManagementApplication.class, args);
    }

}

执行流程:

请求 ----> 放行前逻辑 --->放行 --> 资源 --->放行后逻辑


拦截路径

Filter 可以根据需求,配置不同的拦截资源路径:

代码语言:javascript
复制
@WebFilter(urlPatterns = "/depts")
public class DemoFilter implements Filter {

}

拦截路径

urlPatterns

含义

拦截具体路径

/login

只有访问 /login 路径时,才会被拦截

目录拦截

/emps/*

访问 /emps 下的所有资源,都会被拦截

拦截所有

/*

访问所有资源,都会被拦截


过滤器链

  • 介绍:一个 web 应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
  • 顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序

登录校验

  • 步骤:
    1. 获取请求url
    2. 判断请求url中是否包含 login, 如果包含,说明是登录操作,放行。
    3. 获取请求头中的令牌(token)。
    4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
    5. 解析 token,如果解析失败,返回错误结果(未登录)
    6. 放行
代码语言:javascript
复制
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    @Override
    public 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     ---------阿里巴巴fastJSON
            String 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     ---------阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            response.getWriter().write(notLogin);
            return;
        }
        /*放行*/
        log.info("令牌合法");
        filterChain. doFilter(servletRequest, servletResponse);
    }
}

11.6.4 拦截器 Interceptor

入门

  • 概念:是一种动态拦截方法调用的机制,类似于过滤器,Spring 框架中提供的,用来动态拦截控制器方法的执行。
  • 作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
代码语言:javascript
复制
@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 ...");;
    }
}
代码语言:javascript
复制
@Configuration   //配置类
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");//拦截所有资源
    }
}

详解

  • 拦截器可以根据需求,配置不同的拦截路径:
代码语言:java
复制
@Override
    public 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

  • 步骤:
    1. 获取请求url
    2. 判断请求url中是否包含 login, 如果包含,说明是登录操作,放行。
    3. 获取请求头中的令牌(token)。
    4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
    5. 解析 token,如果解析失败,返回错误结果(未登录)
    6. 放行
代码语言:javascript
复制
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override    //目标资源方法运行前运行,返回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     ---------阿里巴巴fastJSON
            String 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     ---------阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }
        /*放行*/
        log.info("令牌合法");
        return true;

    }

    @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 ...");
    }
}

11.7 异常处理

  • 全局异常处理器:
代码语言:java
复制
@RestControllerAdvice
代码语言:java
复制
@ExceptionHandler(Exception.class)    //捕获所有异常

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 9. MySQL
    • 9.1 DDL - 数据库设计
      • 9.2 DML - 数据库操作
        • 9.3 DQL - 数据库查询
          • 9.3.1 条件查询
          • 9.3.2 分组查询
          • 9.3.3 排序查询
          • 9.3.4 分页查询
          • 常用函数
        • 9.4 多表设计
          • 外键
        • 9.5 多表查询
          • 9.5.1 连接查询
          • 9.5.2 子查询
        • 9.6 事务
          • 9.7 索引
          • 10. Mybatis
            • 10.1 入门程序:
              • 10.2 JDBC
                • 10.3 数据库连接池
                  • 10.4 lombok
                    • 10.5 基础操作
                      • 删除
                      • 新增
                      • 更新
                      • 查询
                      • 数据封装
                      • 条件查询
                    • 10.6 XML映射文件
                      • 10.7 动态 SQL
                        • 10.7.1 if
                        • 10.7.2 foreach
                        • 10.7.3 sql、include
                    • 11. SpringBootWeb案例
                      • 11.1 准备工作
                        • 环境搭建
                        • 开发规范
                      • 11.2 部门管理
                        • 查询部门
                        • 前后端联调
                        • 删除部门
                      • 11.3 员工管理
                        • 删除员工
                        • 新增员工
                        • 文件上传
                        • 修改员工
                      • 11.4 配置文件
                        • 参数配置化
                        • yml配置文件
                        • @ConfigurationProperties
                      • 11.5 登录认证
                        • 11.6 登录校验
                          • 11.6.1 会话技术
                          • 11.6.2 JWT令牌
                          • 11.6.3 过滤器 Filter
                          • 11.6.4 拦截器 Interceptor
                        • 11.7 异常处理
                        相关产品与服务
                        云数据库 MySQL
                        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档