2025.05.25

JavaWeb学习之JavaWebWeb案例

一、案例需求

1.1 需求

  • 完成tlias智能学习辅助系统中的部门管理和员工管理功能
    案例需求

二、环境搭建

  • 环境搭建主要包含:
    • 准备数据库表(dept、emp)
    • 创建springboot工程,引入对应的起步依赖(web、mybatis、mysql驱动、lombok)
    • 配置文件application.properties中引入mybatis的配置信息,准备对应的实体类
    • 准备对应的Mapper、Service(接口、实现类)、Controller基础结构

环境搭建

2.1 准备数据库表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-- 部门管理
create table 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 '部门表';

insert into dept (id, name, create_time, update_time) values(1,'学工部',now(),now()),(2,'教研部',now(),now()),(3,'咨询部',now(),now()), (4,'就业部',now(),now()),(5,'人事部',now(),now());



-- 员工管理(带约束)
create table 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 教研主管, 5 咨询师',
entrydate date comment '入职时间',
dept_id int unsigned comment '部门ID',
create_time datetime not null comment '创建时间',
update_time datetime not null comment '修改时间'
) comment '员工表';

INSERT INTO emp
(id, username, password, name, gender, image, job, entrydate,dept_id, create_time, update_time) VALUES
(1,'jinyong','123456','金庸',1,'1.jpg',4,'2000-01-01',2,now(),now()),
(2,'zhangwuji','123456','张无忌',1,'2.jpg',2,'2015-01-01',2,now(),now()),
(3,'yangxiao','123456','杨逍',1,'3.jpg',2,'2008-05-01',2,now(),now()),
(4,'weiyixiao','123456','韦一笑',1,'4.jpg',2,'2007-01-01',2,now(),now()),
(5,'changyuchun','123456','常遇春',1,'5.jpg',2,'2012-12-05',2,now(),now()),
(6,'xiaozhao','123456','小昭',2,'6.jpg',3,'2013-09-05',1,now(),now()),
(7,'jixiaofu','123456','纪晓芙',2,'7.jpg',1,'2005-08-01',1,now(),now()),
(8,'zhouzhiruo','123456','周芷若',2,'8.jpg',1,'2014-11-09',1,now(),now()),
(9,'dingminjun','123456','丁敏君',2,'9.jpg',1,'2011-03-11',1,now(),now()),
(10,'zhaomin','123456','赵敏',2,'10.jpg',1,'2013-09-05',1,now(),now()),
(11,'luzhangke','123456','鹿杖客',1,'11.jpg',5,'2007-02-01',3,now(),now()),
(12,'hebiweng','123456','鹤笔翁',1,'12.jpg',5,'2008-08-18',3,now(),now()),
(13,'fangdongbai','123456','方东白',1,'13.jpg',5,'2012-11-01',3,now(),now()),
(14,'zhangsanfeng','123456','张三丰',1,'14.jpg',2,'2002-08-01',2,now(),now()),
(15,'yulianzhou','123456','俞莲舟',1,'15.jpg',2,'2011-05-01',2,now(),now()),
(16,'songyuanqiao','123456','宋远桥',1,'16.jpg',2,'2007-01-01',2,now(),now()),
(17,'chenyouliang','123456','陈友谅',1,'17.jpg',NULL,'2015-03-21',NULL,now(),now());

2.2 创建springboot工程

2.2.1 创建springboot工程


2.2.2 项目结构

项目结构

2.2.3 项目文件

  • controller
    • DeptController.java
    1
    2
    3
    4
    5
    6
    7
    8
    // EmpController.java
    package com.itheima.controller;

    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class EmpController {
    }
    • EmpController.java
    1
    2
    3
    4
    5
    6
    7
    8
    // DeptController.java
    package com.itheima.controller;

    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class DeptController {
    }
  • mapper
    • DeptMapper.java
    1
    2
    3
    4
    5
    6
    7
    package com.itheima.mapper;

    import org.apache.ibatis.annotations.Mapper;

    @Mapper
    public interface DeptMapper {
    }
    • EmpMapper.java
    1
    2
    3
    4
    5
    6
    7
    package com.itheima.mapper;

    import org.apache.ibatis.annotations.Mapper;

    @Mapper
    public interface EmpMapper {
    }
  • pojo
    • Dept.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.itheima.pojo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.time.LocalDateTime;

    /**
    * 部门实体类
    */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Dept {
    private Integer id; //ID
    private String name; //部门名称
    private LocalDateTime createTime; //创建时间
    private LocalDateTime updateTime; //修改时间
    }
    • Emp.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.itheima.pojo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.time.LocalDate;
    import java.time.LocalDateTime;

    /**
    * 员工实体类
    */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Emp {
    private Integer id; //ID
    private String username; //用户名
    private String password; //密码
    private String name; //姓名
    private Short gender; //性别 , 1 男, 2 女
    private String image; //图像url
    private Short job; //职位 , 1 班主任 , 2 讲师 , 3 学工主管 , 4 教研主管 , 5 咨询师
    private LocalDate entrydate; //入职日期
    private Integer deptId; //部门ID
    private LocalDateTime createTime; //创建时间
    private LocalDateTime updateTime; //修改时间
    }
  • service
    • DeptService.java
    1
    2
    3
    4
    package com.itheima.service;

    public interface DeptService {
    }
    • EmpService.java
    1
    2
    3
    4
    package com.itheima.service;

    public interface EmpService {
    }
    • DeptServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    package com.itheima.service.impl;

    import com.itheima.service.DeptService;
    import org.springframework.stereotype.Service;

    @Service
    public class DeptServiceImpl implements DeptService {
    }
    • EmpServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    package com.itheima.service.impl;

    import com.itheima.service.EmpService;
    import org.springframework.stereotype.Service;

    @Service
    public class EmpServiceImpl implements EmpService {
    }

三、开发流程

3.1 开发规范

  • 前后端分离开发
  • 接口文档:
  • 开发规范-Restful
    • REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格
    • REST是风格不是规定,可以不遵守
    • 描述模块的功能通常使用复数,表示此类资源而非单个资源
  • 前后端交互统一响应结果Result
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.itheima.pojo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Result {
    private Integer code;//响应码,1 代表成功; 0 代表失败
    private String msg; //响应信息 描述字符串
    private Object data; //返回的数据

    //增删改 成功响应
    public static Result success(){
    return new Result(1,"success",null);
    }
    //查询 成功响应
    public static Result success(Object data){
    return new Result(1,"success",data);
    }
    //失败响应
    public static Result error(String msg){
    return new Result(0,msg,null);
    }
    }

3.2 开发流程

开发流程:

查看页面原型明确需求->阅读接口文档->思路分析->接口开发->接口测试->前后端联调

3.3 部门管理

3.3.1 查询

  • 思路
  • 代码
    • DeptController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.itheima.controller;

    import com.itheima.pojo.Dept;
    import com.itheima.pojo.Result;
    import com.itheima.service.DeptService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.List;

    @Slf4j
    @RestController
    public class DeptController {

    @Autowired
    private DeptService deptService;

    @GetMapping("/depts")
    public Result list() {
    log.info("查询全部部门数据");
    List<Dept> deptList = deptService.list();

    return Result.success(deptList);
    }
    }
    • DeptService.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.itheima.service;

    import com.itheima.pojo.Dept;

    import java.util.List;

    public interface DeptService {

    /*
    查询全部部门数据
    */
    List<Dept> list();
    }
    • DeptServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.itheima.service.impl;

    import com.itheima.mapper.DeptMapper;
    import com.itheima.pojo.Dept;
    import com.itheima.service.DeptService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.util.List;

    @Service
    public class DeptServiceImpl implements DeptService {

    @Autowired
    private DeptMapper deptMapper;

    @Override
    public List<Dept> list() {
    return deptMapper.list();
    }
    }
    • DeptMapper.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.itheima.mapper;

    import com.itheima.pojo.Dept;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;

    import java.util.List;

    @Mapper
    public interface DeptMapper {
    /*
    查询全部部门数据
    */
    @Select("select * from dept")
    List<Dept> list();
    }

3.3.2 前后端联调

  • 将资料中提供的“前端工程”文件夹中的压缩包拷贝到一个没有中文不带空格的目录下解压
  • 启动nginx,访问http://localhost:90

3.3.3 删除

  • 思路
  • 代码
    • DeptController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
    删除部门
    */
    @DeleteMapping("/depts/{id}")
    public Result delete(@PathVariable Integer id) {
    log.info("根据id删除部门数据: {}", id);

    deptService.delete(id);

    return Result.success();
    }
    • DeptService.java
    1
    2
    3
    4
    /*
    根据ID删除部门数据
    */
    void delete(Integer id);
    • DeptServiceImpl.java
    1
    2
    3
    4
    @Override
    public void delete(Integer id) {
    deptMapper.deleteById(id);
    }
    • DeptMapper.java
    1
    2
    3
    4
    5
    /*
    根据ID删除数据
    */
    @Delete("delete from dept where id = #{id}")
    void deleteById(int id);

3.3.4 新增

  • 思路
  • 代码
    • DeptController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
    新增部门
    */
    @PostMapping("/depts")
    public Result add(@RequestBody Dept dept) {
    log.info("添加的部门为: {}", dept);

    deptService.add(dept);

    return Result.success();
    }
    • DeptService.java
    1
    2
    3
    4
    /*
    新增部门
    */
    void add(Dept dept);
    • DeptServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    @Override
    public void add(Dept dept) {
    dept.setCreateTime(LocalDateTime.now());
    dept.setUpdateTime(LocalDateTime.now());

    deptMapper.insert(dept);
    }
    • DeptMapper.java
    1
    2
    3
    4
    5
    /*
    新增部门
    */
    @Insert("insert into dept (name, create_time, update_time) values (#{name},#{createTime},#{createTime})")
    void insert(Dept dept);

3.3.5 优化

  • 公共路径抽取

3.3.6 修改

  • 思路:根据ID查询+修改部门
  • 代码
    • DeptController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /*
    根据ID查询
    */
    @GetMapping("/depts/{id}")
    public Result get(@PathVariable Integer id) {
    log.info("查询到的部门为: {}", id);

    Dept dept = deptService.get(id);

    return Result.success(dept);
    }

    /*
    修改部门
    */
    @PutMapping("/depts")
    public Result update(@RequestBody Dept dept) {
    log.info("修改的部门为: {}", dept);

    deptService.update(dept);

    return Result.success();
    }
    • DeptService.java
    1
    2
    3
    4
    5
    6
    7
    8
    /*
    根据ID查询部门
    */
    Dept get(Integer id);
    /*
    修改部门
    */
    void update(Dept dept);
    • DeptServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Override
    public Dept get(Integer id) {
    return deptMapper.getById(id);
    }

    @Override
    public void update(Dept dept) {
    dept.setUpdateTime(LocalDateTime.now());
    deptMapper.update(dept);
    }
    • DeptMapper.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /*
    根据ID查询
    */
    @Select("select * from dept where id = #{id}")
    Dept getById(Integer id);

    /*
    修改部门
    */
    @Update("update dept set name = #{name},update_time = #{updateTime} where id = #{id}")
    void update(Dept dept);

3.3.7 部门管理总结

  • 功能实现流程:
    • 首先在Controller中定义请求路径和请求方法
    • 然后在Service中定义对应的接口
    • 接着在ServiceImpl中实现接口
    • 最后在Mapper中定义对应的SQL语句

3.4 员工管理

3.4.1 分页查询

  • 思路

  • 代码
    • PageBean.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package com.itheima.pojo;

    import lombok.Data;

    import java.util.List;

    /*
    分页查询结果封装
    */
    @Data
    public class PageBean {
    private Long total;
    private List rows;
    }
    • EmpController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.itheima.controller;

    import com.itheima.pojo.Emp;
    import com.itheima.pojo.PageBean;
    import com.itheima.pojo.Result;
    import com.itheima.service.EmpService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.annotations.Select;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.List;

    @Slf4j
    @RestController
    public class EmpController {
    @Autowired
    private EmpService empService;

    @GetMapping("/emps")
    public Result page(@RequestParam(defaultValue = "1") Integer page,@RequestParam(defaultValue = "10") Integer pageSize) {
    log.info("分页查询参数:{}, {}", page, pageSize);

    PageBean pageBean = empService.page(page, pageSize);

    return Result.success(pageBean);
    }
    }
    • EmpService.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.itheima.service;

    import com.itheima.pojo.PageBean;

    public interface EmpService {
    /*
    分页查询
    */
    PageBean page(Integer page, Integer pageSize);
    }
    • EmpServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package com.itheima.service.impl;

    import com.itheima.mapper.EmpMapper;
    import com.itheima.pojo.Emp;
    import com.itheima.pojo.PageBean;
    import com.itheima.service.EmpService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.util.List;

    @Service
    public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpMapper empMapper;

    @Override
    public PageBean page(Integer page, Integer pageSize) {
    // 1. 获取总记录数
    Long total = empMapper.count();

    // 2. 获取分页查询结果
    Integer start = (page - 1) * pageSize;
    List<Emp> empList = empMapper.page(start, pageSize);

    // 3.封装为PageBean
    PageBean pageBean = new PageBean();
    pageBean.setTotal(total);
    pageBean.setRows(empList);

    return pageBean;
    }
    }
    • EmpMapper.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.itheima.mapper;

    import com.itheima.pojo.Emp;
    import org.apache.ibatis.annotations.Lang;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Select;

    import java.util.List;

    @Mapper
    public interface EmpMapper {
    /*
    查询总记录数
    */
    @Select("select count(*) from emp")
    public Long count();

    /*
    获取列表数据
    */
    @Select("select * from emp limit #{start},#{pageSize}")
    public List<Emp> page(Integer start, Integer pageSize);
    }

3.4.2 分页插件PageHelper

  • 引入依赖
1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
  • 代码
    • EmpServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    public PageBean page(Integer page, Integer pageSize) {
    // 1. 设置分页参数
    PageHelper.startPage(page, pageSize);

    // 2. 执行查询
    List<Emp> empList = empMapper.list();
    Page<Emp> p = (Page<Emp>) empList;

    // 3.封装为PageBean
    PageBean pageBean = new PageBean();
    pageBean.setTotal(p.getTotal());
    pageBean.setRows(p.getResult());

    return pageBean;
    }
    • EmpMapper.java
    1
    2
    3
    4
    5
    /*
    员工信息查询
    */
    @Select("select * from emp")
    public List<Emp> list();

3.4.3 条件分页查询

  • 思路
  • 代码
    • EmpController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @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) {
    log.info("分页查询参数:{}, {}, {}, {}, {}, {}", page, pageSize, name, gender, begin, end);

    PageBean pageBean = empService.page(page, pageSize, name, gender, begin, end);

    return Result.success(pageBean);
    }
    • EmpService.java
    1
    2
    3
    4
    /*
    分页查询
    */
    PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end);
    • EmpServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    public PageBean page(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
    // 1. 设置分页参数
    PageHelper.startPage(page, pageSize);

    // 2. 执行查询
    List<Emp> empList = empMapper.list(name, gender, begin, end);
    Page<Emp> p = (Page<Emp>) empList;

    // 3.封装为PageBean
    PageBean pageBean = new PageBean();
    pageBean.setTotal(p.getTotal());
    pageBean.setRows(p.getResult());

    return pageBean;
    }
    • EmpMapper.java
    1
    2
    3
    4
    /*
    员工信息查询
    */
    public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
    • EmpMapper.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "https://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>
    <if test="name != null and name != ''">
    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>

3.4.4 删除

  • 思路
  • 代码
    • EmpController.java
    1
    2
    3
    4
    5
    6
    7
    8
    @DeleteMapping("/emps/{ids}")
    public Result delete(@PathVariable List<Integer> ids) {
    log.info("删除员工", ids);

    empService.delete(ids);

    return Result.success();
    }
    • EmpService.java
    1
    2
    3
    4
    /*
    删除员工
    */
    void delete(List<Integer> ids);
    • EmpServiceImpl.java
    1
    2
    3
    4
    @Override
    public void delete(List<Integer> ids) {
    empMapper.delete(ids);
    }
    • EmpMapper.java
    1
    2
    3
    4
    /*
    批量删除
    */
    void delete(List<Integer> ids);
    • EmpMapper.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!--批量删除-->
    <delete id="delete">
    delete
    from emp
    where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
    #{id}
    </foreach>
    </delete>

2025.05.26

3.4.5 新增

  • 思路
  • 代码
    • EmpController.java
    1
    2
    3
    4
    5
    6
    7
    8
    @PostMapping("/emps")
    public Result save(@RequestBody Emp emp) {
    log.info("新增员工:{}", emp);

    empService.save(emp);

    return Result.success();
    }
    • EmpService.java
    1
    2
    3
    4
    /*
    新增员工
    */
    void save(Emp emp);
    • EmpServiceImpl.java
    1
    2
    3
    4
    5
    6
    7
    @Override
    public void save(Emp emp) {
    emp.setCreateTime(LocalDateTime.now());
    emp.setUpdateTime(LocalDateTime.now());

    empMapper.insert(emp);
    }
    • EmpMapper.java
    1
    2
    3
    4
    5
    6
    /*
    新增员工
    */
    @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);

3.4.6 修改-查询回显

  • 思路
  • 代码
    • EmpController.java
    1
    2
    3
    4
    5
    6
    7
    8
    @GetMapping("/emps/{id}")
    public Result getById(@PathVariable Integer id) {
    log.info("根据id查询员工:{}", id);

    Emp emp = empService.getById(id);

    return Result.success(emp);
    }
    • EmpService.java
    1
    2
    3
    4
    /*
    根据id查询员工
    */
    Emp getById(Integer id);
    • EmpServiceImpl.java
    1
    2
    3
    4
    @Override
    public Emp getById(Integer id) {
    return empMapper.getById(id);
    }
    • EmpMapper.java
    1
    2
    3
    4
    5
    /*
    根据id查询员工
    */
    @Select("select * from emp where id = #{id}")
    Emp getById(Integer id);

3.4.7 修改-修改员工

  • 思路
  • 代码
    • EmpController.java
    1
    2
    3
    4
    5
    6
    7
    8
    @PutMapping("/emps")
    public Result update(@RequestBody Emp emp) {
    log.info("更新员工:{}", emp);

    empService.update(emp);

    return Result.success();
    }
    • EmpService.java
    1
    2
    3
    4
    /*
    更新数据
    */
    void update(Emp emp);
    • EmpServiceImpl.java
    1
    2
    3
    4
    5
    6
    @Override
    public void update(Emp emp) {
    emp.setUpdateTime(LocalDateTime.now());

    empMapper.update(emp);
    }
    • EmpMapper.java
    1
    2
    3
    4
    /*
    更新员工信息
    */
    void update(Emp emp);
    • EmpMapper.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <!--更新员工信息-->
    <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>

3.5 文件上传

3.5.1 文件上传

  • 文件上传
    • 指将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程
  • 文件上传简介
    • 前端页面三要素
      • 表单项type=“file”
      • 表单提交方式post
      • 表单提交enctype=“multipart/form-data”
    • 服务端接收文件
      • MultipartFile

3.5.2 本地存储

  • 本地存储
    • 将上传的文件存储到本地磁盘
  • 实现
    • UploadController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package com.itheima.controller;

    import com.itheima.pojo.Result;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.File;
    import java.io.IOException;
    import java.util.UUID;

    @Slf4j
    @RestController
    public class UploadController {

    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile image) throws IOException {
    log.info("文件上传:{}, {}, {}", username, age, image);

    // 获取文件名
    String originalFilename = image.getOriginalFilename();

    // 获取拓展名
    int index = originalFilename.lastIndexOf(".");
    String extname = originalFilename.substring(index);
    String newFileName = UUID.randomUUID().toString() + extname;
    log.info("新文件名:{}", newFileName);

    // 本地存储
    image.transferTo(new File("D:\\Java Study\\java_study\\image\\" + newFileName));

    return Result.success();
    }
    }
  • 在SpringBoot中,文件,上传,默认单个文件允许最大大小为1M。如果需要.上传大文件,可以进行如下配置:
    • application.properties
    1
    2
    3
    4
    5
    # 配置单个文件最大上传大小
    spring.servlet.multipart.max-file-size=10MB

    # 配置单个请求最大上传大小(一次请求可以上传多个文件)
    spring.servlet.multipart.max-request-size=10MB
  • MultipartFile中常用方法
    • String getOriginalFilename():获取上传文件的原始文件名
    • void transferTo(File dest):将上传的文件保存到指定的目标文件中
    • long getSize():获取上传文件的大小,单位为字节
    • byte[] getBytes():获取上传文件内容的字节数组
    • InputStream getInputStream():获取接收到的文件内容的输入流

3.5.3 文件上传到服务器:阿里云OSS

  • 阿里云OSS简介
    • OSS(Object Storage Service)是阿里云提供的对象存储服务
    • 可以将文件上传到阿里云OSS中,供其他用户浏览或下载
    • 地址:https://www.aliyun.com
    • 使用步骤
      • 注册阿里云->充值->开通对象存储服务(OSS)->创建Bucket(存储空间)->获取AccessKey(访问密钥)->参照官方SDK编写入门程序->案例集成OSS
  • 通用思路
    • 准备工作->参照官方SDK编写入门程序->集成使用
    • SDK:Software Development Kit,软件开发工具包
  • 案例集成
    • UploadController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @RestController
    public class UploadController {

    @Autowired
    private AliOSSUtils aliOSSUtils;

    @PostMapping("/upload")
    public Result upload(MultipartFile image) throws IOException {
    log.info("文件上传:{}", image.getOriginalFilename());

    // 调用阿里云OSS工具类进行文件上传
    String url = aliOSSUtils.upload(image);
    log.info("文件上传成功,文件访问的url:{}", url);

    return Result.success(url);
    }
    }

3.6 配置文件

3.6.1 参数配置化

  • 问题
    • 变量赋值在分散的文件中,不便于管理和修改
  • 解决办法
    • 使用application.properties文件进行统一管理
    • 使用注解@Value进行属性注入,具体用法:@Value(“${配置文件中的key}”)
1
2
3
4
5
# 自定义的阿里云OSS配置信息
aliyun.oss.endpoint=https://oss-cn-hangzhou.aliyuncs.com
aliyun.oss.accessKeyId=LTAI4GCH1vX6DKqJWxd6nEuW
aliyun.oss.accessKeySecret=yBshYweHOpqDuhCArrVHwIiBKpyqSL
aliyun.oss.bucketName=web-tlias
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// AliOSSUtils.java
@Component
public class AliOSSUtils {

@Value("${aliyun.oss.endpoint}")
private String endpoint;

@Value("${aliyun.oss.accessKeyId}")
private String accessKeyId;

@Value("${aliyun.oss.accessKeySecret}")
private String accessKeySecret;

@Value("${aliyun.oss.bucketName}")
private Sting bucketName;
}

3.6.2 yml配置文件

  • yml简介
    • yml是YAML Ain’t Markup Language的缩写,是一种数据序列化格式
    • 相比于properties文件,yml文件更易读,支持多层级结构
  • SpringBoot提供了多种属性配置方式
    • application.properties
    1
    2
    server.port=8080
    server.address=127.0.0.1
    • application.yml
    1
    2
    3
    server:
    port: 8080
    address: 127.0.0.1
    • application.yaml
    1
    2
    3
    server:
    port: 8080
    address: 127.0.0.1
  • 常见配置文件格式对比
    • XML
    1
    2
    3
    4
    5
    6
    7
    8
    <server>
    <port>8080</port>
    <address>127.0.0.1</address>
    </server>
    * properties
    ```properties
    server.port=8080
    server.address=127.0.0.1
    • yml/yaml
    1
    2
    3
    server:
    port: 8080
    address: 127.0.0.1
  • yml基本语法
    • 大小写敏感
    • 数值前边必须有空格,作为分隔符
    • 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会 自动将Tab转换为空格)
    • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
    • #表示注释,从这个字符一直到行尾,都会被解析器忽略
  • yml数据格式
    • 对象/Map集合
    1
    2
    3
    4
    user:
    name: zhangsan
    age: 18
    password: 123456
    • 数组/List/Set集合
    1
    2
    3
    4
    hobby:
    - basketball
    - football
    - swimming

3.6.3 注解@ConfigurationProperties

  • @ConfigurationProperties注解
    • 用于将配置文件中的属性映射到Java对象中
    • 可以将配置文件中的属性分组,方便管理和使用
  • 使用步骤
    • 创建类,并使用@ConfigurationProperties指定前缀,并加上@Data和@Component注解
    • 在需要使用变量时使用@AutoWired自动注入对象,使用get方法获取值
  • @ConfigurationProperties与@Value
    • 相同点
      • 都是用来注入外部配置的属性
    • 不同点
      • @Value注解只能一个一个的进行外部属性的注入
      • @ConfigurationProperties可以批量的将外部的属性配置注入到bean对象的属性中
    • 示例
    1
    2
    3
    4
    user:
    name: Hansen
    age: 23
    email: hansen@example.com
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 配置类
    @Component
    @ConfigurationProperties(prefix = "user")
    public class UserProperties {
    private String name;
    private int age;
    private String email;

    // 必须提供 getter 和 setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 使用
    @Service
    public class UserService {

    @Autowired
    private UserProperties userProperties;

    public void printUserInfo() {
    System.out.println(userProperties.getName());
    }
    }

2025.05.27

四、登录系统

4.1 登录系统

  • 思路
  • 代码
    • LoginController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.itheima.controller;

    import com.itheima.pojo.Emp;
    import com.itheima.pojo.Result;
    import com.itheima.service.EmpService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;

    @Slf4j
    @RestController
    public class LoginController {

    @Autowired
    private EmpService empService;

    public LoginController(EmpService empService) {
    this.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("用户名或密码错误");
    }
    }
    • EmpService.java
    1
    2
    3
    4
    /*
    员工登录
    */
    Emp login(Emp emp);
    • EmpServiceImpl.java
    1
    2
    3
    4
    @Override
    public Emp login(Emp emp) {
    return empMapper.getByUsernameAndPassword(emp);
    }
    • EmpMapper.java
    1
    2
    3
    4
    5
    /*
    员工登录
    */
    @Select("select * from emp where username = #{username} and password = #{password}")
    Emp getByUsernameAndPassword(Emp emp);

4.2 登录校验

  • 登录校验
    • 使用登录标记和统一拦截实现登录校验
    • 登录标记:用户登录成功后,每一次请求中都可以获取到该标记,使用会话技术
    • 统一拦截:过滤器Filter或拦截器Interceptor

4.3 会话技术

  • 会话
    • 会话是指用户与服务器之间的一次交互过程
    • 在一次会话中,用户可以发送多个请求,服务器可以返回多个响应
  • 会话跟踪
    • 一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一会话的多次请求间共享数据
  • 会话跟踪方案
    • 客户端会话跟踪技术:Cookie
    • 服务器端会话跟踪技术:Session
    • 令牌技术

会话跟踪方案对比

4.4 JWT令牌

  • JWT简介
    • JWT(JSON Web Token)
    • JWT定义了一种简洁、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的
    • 官网:https://jwt.io/
  • JWT的结构
    • Header:头部,记录令牌类型、签名算法等。例如:{“alg”: “HS256”, “typ”: “JWT”}
    • Payload:有效载荷,携带一些自定义信息、默认信息等。例如:{“id”:“1”,“username”:“zhangsan”}
    • Signature:签名,防止Token被篡改,确保安全性。将header、payload并加入指定秘钥,通过指定签名算法计算而来
    • Base64:是一种基于64个可打印字符(A-Z、a-z、0-9、+、/)来表示二进制数据的编码方式
  • 场景:登录认证
    • 用户登录成功后,服务器生成一个JWT令牌
    • 后续每个请求都要携带JWT令牌,系统每次在请求处理之前,先校验令牌,通过后再处理

4.5 JWT令牌的生成

  • 生成JWT令牌
    • 使用第三方库jjwt
    • 引入依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
    </dependency>
  • 测试代码
    • 生成JWT令牌
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /*
    生成JWT
    */
    @Test
    public void testGenJWT() {
    Map<String, Object> claims = new HashMap<String, Object>();
    claims.put("id", "1");
    claims.put("name", "tom");

    String jwt = Jwts.builder()
    .signWith(SignatureAlgorithm.HS256, "itheimahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh") // 签名、算法
    .setClaims(claims) // 自定义内容(载荷)
    .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) // 设置有效期为1h
    .compact();

    System.out.println(jwt);
    }
    • 解析JWT令牌
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
    解析JWT
    */
    @Test
    public void testParseJWT() {
    Claims claims = Jwts.parser()
    .setSigningKey("itheimahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")
    .build().parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOiIxIiwiZXhwIjoxNzQ4MzQwNDUwfQ.VvNgRGXKkxafjoNA26hM_RI_SQiMNnr8k3hJGlY8iM4")
    .getBody();

    System.out.println(claims);
    }
  • 注意事项
    • JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的
    • 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法

4.6 登录后下发令牌

  • 思路
    • 令牌生成:登录成功后,生成JWT令牌,并将令牌返回给前端
    • 令牌校验:在请求到达服务端后,对令牌进行统一拦截、校验
  • 令牌生成代码
    • JwtUtils.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package com.itheima.utils;

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import java.util.Date;
    import java.util.Map;

    public class JwtUtils {

    private static String signKey = "itheimahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh";
    private static Long expire = 43200000L;

    /**
    * 生成JWT令牌
    * @param claims JWT第二部分负载 payload 中存储的内容
    * @return
    */
    public static String generateJwt(Map<String, Object> claims){
    String jwt = Jwts.builder()
    .addClaims(claims)
    .signWith(SignatureAlgorithm.HS256, signKey)
    .setExpiration(new Date(System.currentTimeMillis() + expire))
    .compact();
    return jwt;
    }

    /**
    * 解析JWT令牌
    * @param jwt JWT令牌
    * @return JWT第二部分负载 payload 中存储的内容
    */
    public static Claims parseJWT(String jwt){
    Claims claims = Jwts.parser()
    .setSigningKey(signKey)
    .build().parseClaimsJws(jwt)
    .getBody();
    return claims;
    }
    }
    • LoginController.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @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);
    return Result.success(jwt);
    }

    // 登录失败--返回错误信息
    return Result.error("用户名或密码错误");
    }

2025…05.28

4.7 过滤器Filter

4.7.1 Filter入门

  • 过滤器Filter
    • 概念: Filter过滤器,是JavaWeb三大组件(Servlet、Filter, Listener)之一
    • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
    • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等
  • Filter快速入门
    • 定义Filter:定义一个类,实现Filter接口,并重写其所有方法
    • 配置Filter:Filter类上加上@WebFilter注解,配置拦截资源的路径。引导类上加@ServletComponentScan注解,开启Servlet组件支持
    • 代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @WebFilter(urlPatterns = "/*") // 拦截所有请求
    public class MyFilter implements Filter {

    // 初始化方法,Web服务器启动,创建Filter时调用,只调用一次
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    // 初始化方法
    Filter.super.init(filterConfig);
    }

    // 拦截到请求时,调用该方法,可调用多次
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    // 过滤逻辑
    System.out.println("拦截方法执行,拦截到了请求...");

    // 放行请求
    chain.doFilter(request, response);
    }

    // 销毁方法,Web服务器关闭时调用,只调用一次
    @Override
    public void destroy() {
    // 销毁方法
    Filter.super.destroy();
    }
    }

4.7.2 Filter执行流程

  • Filter执行流程:放行前逻辑->放行请求->放行后逻辑
  • 问题:
    • 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?——会
    • 如果回到Filter中,是重新执行还是执行放行后逻辑?——执行放行后逻辑

Filter执行流程

  • Filter拦截路径

    • Filter可以根据需求配置不同的拦截资源路径:
    拦截路径 urlPatterns值 含义
    拦截具体路径 /login 只有访问/login路径时才会被拦截
    目录拦截 /emps/* 访问/emps下所有资源都会被拦截
    拦截所有 /* 访问所有资源都会被拦截

4.7.3 Filter链

  • Filter链
    • 当多个Filter拦截同一个请求时,Filter会形成一个链条,按照配置的顺序依次执行
    • 每个Filter都可以选择是否放行请求,如果不放行,则后续的Filter和目标资源都不会被执行
    • 顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)自然排序
    • Filter链

4.7.4 登录校验Filter

  • 登录校验Filter
    • 在登录系统中,登录校验Filter用于拦截所有请求,判断用户是否已登录
    • 如果未登录,则返回错误信息;如果已登录,则放行请求
  • 问题
    • 所有请求拦截到了之后都需要校验令牌吗?——有一个例外:登录请求
    • 拦截到请求后什么情况下才可以放行,执行业务操作?——有令牌且令牌校验通过;否则都返回未登录错误信息
  • 登录校验Filter流程
    1. 获取请求url
    2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
    3. 获取请求头中的令牌(token)
    4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
    5. 解析token,如果解析失败,返回错误结果(未登录)
    6. 放行
  • 代码实现
    • LoginCheckFilter.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    // LoginCheckFilter.java
    package com.itheima.filter;

    import ch.qos.logback.core.util.StringUtil;
    import com.alibaba.fastjson.JSONObject;
    import com.itheima.pojo.Result;
    import com.itheima.utils.JwtUtils;
    import jakarta.servlet.*;
    import jakarta.servlet.annotation.WebFilter;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.StringUtils;

    import java.io.IOException;

    @Slf4j
    @WebFilter(urlPatterns = "/*")
    public class LoginCheckFilter implements Filter {

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse resp = (HttpServletResponse) response;

    // 1. 获取请求url
    String url = req.getRequestURL().toString();
    log.info("请求的url: {} ", url);

    // 2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
    if (url.contains("login")) {
    log.info("登录操作,放行...");
    chain.doFilter(request, response);
    return;
    }

    // 3. 获取请求头中的令牌(token)
    String jwt = req.getHeader("token");

    // 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
    if (!StringUtils.hasLength(jwt)) {
    log.info("请求头token为null或空,返回未登录的信息");
    Result error = Result.error("NOT_LOGIN");
    // 手动转换对象->json
    String notlogin = JSONObject.toJSONString(error);
    resp.getWriter().write(notlogin);
    return;
    }

    // 5. 解析token,如果解析失败,返回错误结果(未登录)
    try {
    JwtUtils.parseJWT(jwt);
    } catch (Exception e) {
    e.printStackTrace();
    log.info("解析令牌失败,返回未登录错误信息");
    Result error = Result.error("NOT_LOGIN");
    // 手动转换对象->json
    String notlogin = JSONObject.toJSONString(error);
    resp.getWriter().write(notlogin);
    return;
    }

    // 6. 放行
    log.info("令牌合法,放行");
    chain.doFilter(request, response);
    }
    }
    • 测试类SpringbootTliasApplication.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.itheima;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;

    @ServletComponentScan
    @SpringBootApplication
    public class SpringbootTliasApplication {

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

    }

4.8 拦截器Interceptor

4.8.1 Interceptor入门

  • 拦截器Interceptor
    • 概念:是一种动态拦截方法调用的机制,类似于过滤器,Spring框架中提供的,用来动态拦截控制器方法的执行
    • 作用:拦截请求,在指定的方法调用前后根据业务需要执行预先设定的代码
  • Interceptor快速入门
    • 定义拦截器,实现HandlerInterceptor接口,并重写其所有方法
    • 注册拦截器
    • LoginCheckInterceptor.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package com.itheima.interceptor;

    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;

    @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;
    }

    @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 ...");
    }
    }
    • 注册拦截器WebConfig.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.itheima.config;

    import com.itheima.interceptor.LoginCheckInterceptor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    @Configuration // 配置类
    public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
    }

4.8.2 Interceptor详解

  • 拦截路径
    • 拦截器可以根据需求,配置不同的拦截路径
    1
    2
    3
    4
    5
    6
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    // addPathPatterns():需要拦截哪些资源
    // excludePathPatterns():排除哪些资源不拦截
    registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login,不能匹配/depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1,不能匹配/depts,/depts/1/2
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1
  • 拦截器执行流程
    拦截器执行流程
  • Filter与Interceptor的区别
    • 接口规范不同:过滤器需要实现Filter接口,拦截器需要实现HandlerInterceptor接口
    • 拦截范围不同:过滤器会拦截所有资源,而拦截器只会拦截Spring环境中的资源

4.8.3 登录校验Interceptor

  • 登录校验Interceptor
    • 在登录系统中,登录校验Interceptor用于拦截所有请求,判断用户是否已登录
    • 如果未登录,则返回错误信息;如果已登录,则放行请求
  • 登录校验Interceptor流程
    1. 获取请求url
    2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
    3. 获取请求头中的令牌(token)
    4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
    5. 解析token,如果解析失败,返回错误结果(未登录)
    6. 放行
  • 代码实现
    • 与Filter相比,只是放行的实现方式不同
      • Filter使用chain.doFilter(request, response)放行
      • Interceptor使用return true放行
    • LoginCheckInterceptor.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    package com.itheima.interceptor;

    import com.alibaba.fastjson.JSONObject;
    import com.itheima.pojo.Result;
    import com.itheima.utils.JwtUtils;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    import org.springframework.util.StringUtils;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;

    @Slf4j
    @Component
    public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override // 目标资源方法运行前运行,返回true:放行,返回false:不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
    System.out.println("preHandle ...");

    // 1. 获取请求url
    String url = req.getRequestURL().toString();
    log.info("请求的url: {} ", url);

    // 2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
    if (url.contains("login")) {
    log.info("登录操作,放行...");
    return true;
    }

    // 3. 获取请求头中的令牌(token)
    String jwt = req.getHeader("token");

    // 4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
    if (!StringUtils.hasLength(jwt)) {
    log.info("请求头token为null或空,返回未登录的信息");
    Result error = Result.error("NOT_LOGIN");
    // 手动转换对象->json
    String notlogin = JSONObject.toJSONString(error);
    resp.getWriter().write(notlogin);
    return false;
    }

    // 5. 解析token,如果解析失败,返回错误结果(未登录)
    try {
    JwtUtils.parseJWT(jwt);
    } catch (Exception e) {
    e.printStackTrace();
    log.info("解析令牌失败,返回未登录错误信息");
    Result error = Result.error("NOT_LOGIN");
    // 手动转换对象->json
    String notlogin = JSONObject.toJSONString(error);
    resp.getWriter().write(notlogin);
    return false;
    }

    // 6. 放行
    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 ...");
    }
    }

4.9 异常处理

  • 出现异常该如何处理
    • 方案一:在Controller中使用try-catch捕获异常,返回错误信息,但代码臃肿,不推荐使用
    • 方案二:使用全局异常处理器,统一处理所有Controller中的异常
  • 定义全局异常处理器
    • GlobalExceptionHandler.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package com.itheima.exception;

    import com.itheima.pojo.Result;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;

    /*
    全局异常处理器
    @RestControllerAdvice = @ControllerAdvice + @ResponseBody
    */
    @RestControllerAdvice
    public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public Result ex(Exception e) {
    e.printStackTrace();
    return Result.error("对不起,操作失败,请联系管理员");
    }
    }