JavaWeb Web案例
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.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;
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;
public class DeptController {
} - mapper
- DeptMapper.java
1
2
3
4
5
6
7package com.itheima.mapper;
import org.apache.ibatis.annotations.Mapper;
public interface DeptMapper {
}- EmpMapper.java
1
2
3
4
5
6
7package com.itheima.mapper;
import org.apache.ibatis.annotations.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
20package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 部门实体类
*/
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
28package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 员工实体类
*/
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
4package com.itheima.service;
public interface DeptService {
}- EmpService.java
1
2
3
4package com.itheima.service;
public interface EmpService {
}- DeptServiceImpl.java
1
2
3
4
5
6
7
8package com.itheima.service.impl;
import com.itheima.service.DeptService;
import org.springframework.stereotype.Service;
public class DeptServiceImpl implements DeptService {
}- EmpServiceImpl.java
1
2
3
4
5
6
7
8package com.itheima.service.impl;
import com.itheima.service.EmpService;
import org.springframework.stereotype.Service;
public class EmpServiceImpl implements EmpService {
}
三、开发流程
3.1 开发规范
- 前后端分离开发
- 接口文档:
- 开发规范-Restful
- REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格

- REST是风格不是规定,可以不遵守
- 描述模块的功能通常使用复数,表示此类资源而非单个资源
- REST(Representational State Transfer),表述性状态转换,它是一种软件架构风格
- 前后端交互统一响应结果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
27package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
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
27package 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;
public class DeptController {
private DeptService deptService;
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
13package 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
21package 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;
public class DeptServiceImpl implements DeptService {
private DeptMapper deptMapper;
public List<Dept> list() {
return deptMapper.list();
}
}- DeptMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package com.itheima.mapper;
import com.itheima.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface DeptMapper {
/*
查询全部部门数据
*/
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/*
删除部门
*/
public Result delete( 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
public void delete(Integer id) {
deptMapper.deleteById(id);
}- DeptMapper.java
1
2
3
4
5/*
根据ID删除数据
*/
void deleteById(int id);
3.3.4 新增
- 思路

- 代码
- DeptController.java
1
2
3
4
5
6
7
8
9
10
11/*
新增部门
*/
public Result add( 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
public void add(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.insert(dept);
}- DeptMapper.java
1
2
3
4
5/*
新增部门
*/
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查询
*/
public Result get( Integer id) {
log.info("查询到的部门为: {}", id);
Dept dept = deptService.get(id);
return Result.success(dept);
}
/*
修改部门
*/
public Result update( 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
public Dept get(Integer id) {
return deptMapper.getById(id);
}
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查询
*/
Dept getById(Integer 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
14package com.itheima.pojo;
import lombok.Data;
import java.util.List;
/*
分页查询结果封装
*/
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
30package 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;
public class EmpController {
private EmpService empService;
public Result page( Integer page, 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
10package 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
34package 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;
public class EmpServiceImpl implements EmpService {
private EmpMapper empMapper;
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
23package 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;
public interface EmpMapper {
/*
查询总记录数
*/
public Long count();
/*
获取列表数据
*/
public List<Emp> page(Integer start, Integer pageSize);
}
3.4.2 分页插件PageHelper
- 引入依赖
1 | <dependency> |
- 代码
- EmpServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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/*
员工信息查询
*/
public List<Emp> list();
3.4.3 条件分页查询
- 思路

- 代码
- EmpController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
public Result page( Integer page,
Integer pageSize,
String name,
Short gender,
LocalDate begin,
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
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
<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
public Result delete( 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
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
public Result save( 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
public void save(Emp emp) {
emp.setCreateTime(LocalDateTime.now());
emp.setUpdateTime(LocalDateTime.now());
empMapper.insert(emp);
}- EmpMapper.java
1
2
3
4
5
6/*
新增员工
*/
void insert(Emp emp);
3.4.6 修改-查询回显
- 思路

- 代码
- EmpController.java
1
2
3
4
5
6
7
8
public Result getById( 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
public Emp getById(Integer id) {
return empMapper.getById(id);
}- EmpMapper.java
1
2
3
4
5/*
根据id查询员工
*/
Emp getById(Integer id);
3.4.7 修改-修改员工
- 思路

- 代码
- EmpController.java
1
2
3
4
5
6
7
8
public Result update( 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
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

- 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
35package 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;
public class UploadController {
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
public class UploadController {
private AliOSSUtils aliOSSUtils;
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 | # 自定义的阿里云OSS配置信息 |
1 | // AliOSSUtils.java |
3.6.2 yml配置文件
- yml简介
- yml是YAML Ain’t Markup Language的缩写,是一种数据序列化格式
- 相比于properties文件,yml文件更易读,支持多层级结构
- SpringBoot提供了多种属性配置方式
- application.properties
1
2server.port=8080
server.address=127.0.0.1- application.yml
1
2
3server:
port: 8080
address: 127.0.0.1- application.yaml
1
2
3server:
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
3server:
port: 8080
address: 127.0.0.1 - yml基本语法
- 大小写敏感
- 数值前边必须有空格,作为分隔符
- 使用缩进表示层级关系,缩进时,不允许使用Tab键,只能用空格(idea中会 自动将Tab转换为空格)
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- #表示注释,从这个字符一直到行尾,都会被解析器忽略
- yml数据格式
- 对象/Map集合
1
2
3
4user:
name: zhangsan
age: 18
password: 123456- 数组/List/Set集合
1
2
3
4hobby:
- basketball
- football
- swimming
3.6.3 注解@ConfigurationProperties
- @ConfigurationProperties注解
- 用于将配置文件中的属性映射到Java对象中
- 可以将配置文件中的属性分组,方便管理和使用
- 使用步骤
- 创建类,并使用@ConfigurationProperties指定前缀,并加上@Data和@Component注解
- 在需要使用变量时使用@AutoWired自动注入对象,使用get方法获取值
- @ConfigurationProperties与@Value
- 相同点
- 都是用来注入外部配置的属性
- 不同点
- @Value注解只能一个一个的进行外部属性的注入
- @ConfigurationProperties可以批量的将外部的属性配置注入到bean对象的属性中
- 示例
1
2
3
4user:
name: Hansen
age: 23
email: hansen@example.com1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 配置类
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// 使用
public class UserService {
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
30package 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;
public class LoginController {
private EmpService empService;
public LoginController(EmpService empService) {
this.empService = empService;
}
public Result login( 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
public Emp login(Emp emp) {
return empMapper.getByUsernameAndPassword(emp);
}- EmpMapper.java
1
2
3
4
5/*
员工登录
*/
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
*/
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
*/
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
40package 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
public Result login( Emp emp) {
log.info("员工登录:{}", emp);
Emp e = empService.login(emp);
// 登录成功--生成令牌,下发令牌
if (e != null) {
Map<String, Object> claims = new HashMap<>();
claims.put("id", e.getId());
claims.put("name", e.getName());
claims.put("username", e.getUsername());
String jwt = JwtUtils.generateJwt(claims);
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// 拦截所有请求
public class MyFilter implements Filter {
// 初始化方法,Web服务器启动,创建Filter时调用,只调用一次
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化方法
Filter.super.init(filterConfig);
}
// 拦截到请求时,调用该方法,可调用多次
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 过滤逻辑
System.out.println("拦截方法执行,拦截到了请求...");
// 放行请求
chain.doFilter(request, response);
}
// 销毁方法,Web服务器关闭时调用,只调用一次
public void destroy() {
// 销毁方法
Filter.super.destroy();
}
}
4.7.2 Filter执行流程
- Filter执行流程:放行前逻辑->放行请求->放行后逻辑
- 问题:
- 放行后访问对应资源,资源访问完成后,还会回到Filter中吗?——会
- 如果回到Filter中,是重新执行还是执行放行后逻辑?——执行放行后逻辑

-
Filter拦截路径
- Filter可以根据需求配置不同的拦截资源路径:
拦截路径 urlPatterns值 含义 拦截具体路径 /login 只有访问/login路径时才会被拦截 目录拦截 /emps/* 访问/emps下所有资源都会被拦截 拦截所有 /* 访问所有资源都会被拦截
4.7.3 Filter链
- Filter链
- 当多个Filter拦截同一个请求时,Filter会形成一个链条,按照配置的顺序依次执行
- 每个Filter都可以选择是否放行请求,如果不放行,则后续的Filter和目标资源都不会被执行
- 顺序:注解配置的Filter,优先级是按照过滤器类名(字符串)自然排序

4.7.4 登录校验Filter
- 登录校验Filter
- 在登录系统中,登录校验Filter用于拦截所有请求,判断用户是否已登录
- 如果未登录,则返回错误信息;如果已登录,则放行请求
- 问题
- 所有请求拦截到了之后都需要校验令牌吗?——有一个例外:登录请求
- 拦截到请求后什么情况下才可以放行,执行业务操作?——有令牌且令牌校验通过;否则都返回未登录错误信息
- 登录校验Filter流程
- 获取请求url
- 判断请求url中是否包含login,如果包含,说明是登录操作,放行
- 获取请求头中的令牌(token)
- 判断令牌是否存在,如果不存在,返回错误结果(未登录)
- 解析token,如果解析失败,返回错误结果(未登录)
- 放行

- 代码实现
- 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;
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
15package com.itheima;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
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
26package 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;
public class LoginCheckInterceptor implements HandlerInterceptor {
// 目标资源方法运行前运行,返回true:放行,返回false:不放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle ...");
return true;
}
// 目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ...");
}
// 视图渲染完毕后运行,最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion ...");
}
}- 注册拦截器WebConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package 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;
// 配置类
public class WebConfig implements WebMvcConfigurer {
private LoginCheckInterceptor loginCheckInterceptor;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}
4.8.2 Interceptor详解
- 拦截路径
- 拦截器可以根据需求,配置不同的拦截路径
1
2
3
4
5
6
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流程
- 获取请求url
- 判断请求url中是否包含login,如果包含,说明是登录操作,放行
- 获取请求头中的令牌(token)
- 判断令牌是否存在,如果不存在,返回错误结果(未登录)
- 解析token,如果解析失败,返回错误结果(未登录)
- 放行

- 代码实现
- 与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
71package 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;
public class LoginCheckInterceptor implements HandlerInterceptor {
// 目标资源方法运行前运行,返回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;
}
// 目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle ...");
}
// 视图渲染完毕后运行,最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion ...");
}
} - 与Filter相比,只是放行的实现方式不同
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
19package 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
*/
public class GlobalExceptionHandler {
public Result ex(Exception e) {
e.printStackTrace();
return Result.error("对不起,操作失败,请联系管理员");
}
}
