SpringBoot经典案例

Dept

DeptController

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
@Slf4j  // Lombok提供注解,可直接使用log,无需创建对象
@RequestMapping("/depts") //公共的请求路径抽取到类上
@RestController
public class DeptController {
// private static Logger log = LoggerFactory.getLogger(DeptController.class);
// 在类上加上@Slf4j注解时,会自动生成Logger对象,对象名:log
@Autowired
private DeptService deptService;

/* 查询部门数据 @return
请求路径:/depts, 请求方式:GET, 请求参数:无。
响应数据:JSON。 样例:{"code":1,"msg":"success","data":
{"id":1,"name":"学工部","createTime":"2022-09-01","updateTime":"2024-09-01"}}*/
// @RequestMapping(value = "/depts",method = RequestMethod.GET)
// @GetMapping(/depts)
@GetMapping
public Result list(){
log.info("查询全部部门数据"); // 调log的info方法记录日志(项目开发很少用sout输出)
List<Dept> deptList = deptService.list(); // //调deptService放入list方法
return Result.success(deptList); // 返回值集合deptList,用来接收全部dept对象
}

/* 删除部门 @return
请求路径:/depts/{id}, 请求方式:Delete,
请求参数格式:路径参数 样例:/depts/1,
响应数据:JSON 样例:{"code":1,"msg":"success","data":null} */
@Log
@DeleteMapping("/{id}") // @PathVariable接受路径参数
public Result delete(@PathVariable Integer id) throws Exception {
log.info("根据id删除部门:{}",id);
deptService.delete(id); //调用service删除部门
return Result.success();
}

/* 新增部门 @return
请求路径:/depts, 请求方式:POST,
请求参数格式:JSON 样例:{"name":"教研部"},
响应数据:JSON 样例:{"code":1,"msg":"success","data":null} */
@Log
@PostMapping
public Result add(@RequestBody Dept dept){ // JSON格式数据封装到实体类中
log.info("新增部门: {}" , dept);
deptService.add(dept); //调用service新增部门
return Result.success();
}
}

DeptService

1
2
3
4
5
6
7
8
9
10
public interface DeptService {
// 查询全部部门数据 @return
List<Dept> list();

// 删除部门 @param id
void delete(Integer id) throws Exception;

// 新增部门 @param dept
void add(Dept dept);
}

DeptServiceImpl

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
@Service
public class DeptServiceImpl implements DeptService {

@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Autowired
private DeptLogService deptLogService;

@Override
public List<Dept> list() {
return deptMapper.list(); //调deptMapper中的list方法
}

//@Transactional(rollbackFor = Exception.class) //spring事务管理
@Transactional
@Override
public void delete(Integer id) throws Exception {
try {
deptMapper.deleteById(id); //根据ID删除部门数据

//int i = 1/0;
//if(true){throw new Exception("出错啦...");}

empMapper.deleteByDeptId(id); //根据部门ID删除该部门下的员工
} finally {
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门的操作,此次解散的是"+id+"号部门");
deptLogService.insert(deptLog);
}
}

@Override
public void add(Dept dept) {
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());

deptMapper.insert(dept);
}
}

DeptMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface DeptMapper {
// 查询全部部门 @return
@Select("select * from dept") // 注解开发
List<Dept> list();

// 根据ID删除部门 @param id
@Delete("delete from dept where id = #{id}")
void deleteById(Integer id);

// 新增部门 @param dept
@Insert("insert into dept(name, create_time, update_time)
values(#{name},#{createTime},#{updateTime})")
void insert(Dept dept);
}

Emp

EmpController

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
72
73
74
@Slf4j
@RestController
@RequestMapping("/emps") // 抽取到类上
public class EmpController {

@Autowired
private EmpService empService;

/* 查询分页 默认当前页码1,默认每页显示条数10
请求路径:/emps。 请求方式:GET。 接口描述:员工列表数据的条件分页查询
请求参数:
样例:/emps?name=张&gender=1&begin=2007-09-01&end=2022-09-01&page=1&pageSize=10
响应数据:JSON
样例{"code":1,"msg":success,"data":{"total":2,"rows":[{...}, {...}]}}*/
@GetMapping
// @RequestParam(defaultValue = "默认值")设置请求参数默认值
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);
//调用service分页查询
PageBean pageBean = empService.page(page,pageSize,name,gender,begin,end);
return Result.success(pageBean);
}

/* 删除员工 controller接收路径参数@PathVariable
请求路径:/emps/{ids}。 请求方式:DELETE。 接口描述:批量删除员工的数据信息
请求参数:路径参数。 样例:/emps/1,2,3
响应数据:参数格式JSON。 样例{"code":1, "msg":success, "data":null } */
@Log
@DeleteMapping("/{ids}")
public Result delete(@PathVariable List<Integer> ids){
log.info("批量删除操作, ids:{}",ids);
empService.delete(ids);
return Result.success();
}

/* 新增员工 controller接收Json格式数据@RequestBody
请求路径:/emps。 请求方式:POST。 接口描述:添加员工的信息
请求参数:JSON。 样例:{"image":"https...","username":"liping",...,"deptId":1}
响应数据:参数格式JSON。 样例{"code":1, "msg":success, "data":null } */
@Log
@PostMapping
public Result save(@RequestBody Emp emp){
log.info("新增员工, emp: {}", emp); // 记录日志
empService.save(emp);
return Result.success();
}

/* 根据ID查询员工信息
请求路径:/emps/{id}。 请求方式:GET。 接口描述:根据主键ID查询员工信息
请求参数格式:路径参数。 样例:/emps/1
响应数据:JSON。样例:{"code":1, "msg":success, "data":{"id":2,"username":...} } */
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id){
log.info("根据ID查询员工信息, id: {}",id);
Emp emp = empService.getById(id);
return Result.success(emp);
}

/* 更新员工信息
请求路径:/emps。 请求方式:PUT。 接口描述:修改员工的数据信息
请求参数格式:JSON。 样例: {"id":1,"image":"https...","username"...,"deptId":1}
响应数据:JSON。 样例{"code":1, "msg":success, "data":null } */
@Log
@PutMapping
public Result update(@RequestBody Emp emp){
log.info("更新员工信息 : {}", emp);
empService.update(emp);
return Result.success();
}
}

PageBean

1
2
3
4
5
6
7
8
9
// 分页查询结果封装类
// 分页查询需要的数据,封装在PageBean对象中
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
private Long total; // 总记录数 select count(*) from emp
private List rows; // 数据列表 select * from emp
}

EmpService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface EmpService {
// 分页查询 @param page @param pageSize @return
// PageBean page(Integer page, Integer pageSize);
PageBean page(Integer page, Integer pageSize,String name, Short gender,LocalDate begin,LocalDate end);

// 批量删除 @param ids
void delete(List<Integer> ids);

// 新增员工 @param emp
void save(Emp emp);

// 根据ID查询员工 @param id @return
Emp getById(Integer id);

// 更新员工 @param emp
void update(Emp emp);

// 员工登录 @param emp @return
Emp login(Emp emp);
}

EmpServiceImpl

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
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;

/* *PageHelper*插件代替以下繁琐代码
@Override
public PageBean page(Integer page, Integer pageSize) {
//1. 获取总记录数
Long count = empMapper.count();
//2. 获取分页查询结果列表
Integer start = (page - 1) * pageSize;
List<Emp> empList = empMapper.page(start, pageSize);
//3. 封装PageBean对象
PageBean pageBean = new PageBean(count, empList);
return pageBean;
}*/

// pageHelper插件
@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(p.getTotal(), p.getResult());
return pageBean;
}

@Override
public void delete(List<Integer> ids) {
empMapper.delete(ids);
}

@Override
public void save(Emp emp) {
emp.setCreateTime(LocalDateTime.now()); // 补全数据
emp.setUpdateTime(LocalDateTime.now()); // 补全数据
empMapper.insert(emp);
}

@Override
public Emp getById(Integer id) {
return empMapper.getById(id);
}

@Override
public void update(Emp emp) {
emp.setUpdateTime(LocalDateTime.now());

empMapper.update(emp);
}

@Override
public Emp login(Emp emp) {
return empMapper.getByUsernameAndPassword(emp);
}
}

EmpMapper

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
@Mapper
public interface EmpMapper {

/* *PageHelper*插件代替以下繁琐代码
//查询总记录数 @return
@Select("select count(*) from emp")
public Long count();
// 分页查询,获取列表数据 @param start @param pageSize @return
@Select("select * from emp limit #{start},#{pageSize}")
public List<Emp> page(Integer start, Integer pageSize); */

// 员工信息查询 @return
//@Select("select * from emp")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

// 批量删除 @param ids
void delete(List<Integer> ids);

// 新增员工 @param emp
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
"values(#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})")
void insert(Emp emp);

// 根据ID查询员工 @param id @return
@Select("select * from emp where id = #{id}")
Emp getById(Integer id);

// 更新员工 @param emp
void update(Emp emp);

// 根据用户名和密码查询员工 @param emp @return
@Select("select * from emp where username = #{username} and password =#{password}")
Emp getByUsernameAndPassword(Emp emp);

// 根据部门ID删除该部门下的员工数据 @param deptId
@Delete("delete from emp where dept_id = #{deptId}")
void deleteByDeptId(Integer deptId);
}

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
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
<?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>
<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>

<!--批量删除员工 (1, 2, 3)-->
<delete id="delete">
delete
from emp
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>

<!--更新员工-->
<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>

</mapper>

Upload

UploadController

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
@Slf4j
@RestController
public class UploadController {
@Autowired
private AliOSSUtils aliOSSUtils;
//本地存储文件 使用MultipartFile类提供的方法,把临时文件转存到本地磁盘目录下
/*
@PostMapping("/upload")
public Result upload(String username , Integer age , MultipartFile image) throws Exception {
log.info("文件上传: {}, {}, {}", username, age, image);

// 获取原始文件名 - 1.jpg 123.0.0.jpg
String originalFilename = image.getOriginalFilename();

// 构造唯一的文件名 (不能重复) ,否则之前上传的文件会被覆盖掉
// uuid(通用唯一识别码) de49685b-61c0-4b11-80fa-c71e95924018
int index = originalFilename.lastIndexOf(".");
String extname = originalFilename.substring(index); // 文件扩展名
// 随机名+文件扩展名
String newFileName = UUID.randomUUID().toString() + extname;
log.info("新的文件名: {}", newFileName);

// 将文件存储在服务器的磁盘目录中 E:\images
image.transferTo(new File("E:\\images\\"+newFileName));

return Result.success();
}*/

/* 存储在服务器,阿里云对象存储OSS。(项目开发时,某些服务不需要自己开发。)
(短信发送功能不用跟三大运营商对接,阿里云提供了短信服务,只需调用阿里云提供的短信服务)
请求路径:/upload。 请求方式:POST。 接口描述:上传图片接口
请求参数:样例:/emps?name=张&gender=1&begin=2007-09-01&end=2022-09-01&page=1&pageSize=10
响应数据:JSON 样例{"code":1, "msg":success, "data":"https://web-framework.oss-cn-hangzhou.aliyun..."}*/
@PostMapping("/upload")
public Result upload(MultipartFile image) throws IOException {
log.info("文件上传, 文件名: {}", image.getOriginalFilename());

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

return Result.success(url);
}
}

aliyun

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
public class DemoXR {
public static void main(String[] args) throws Exception {
// Endpoint以华东 1(杭州)为例,其它 Region请按实际情况填写。
String endpoint = "oss-cn-hangzhou.aliyuncs.com";
// 阿里云账号 AccessKey拥有所有 API的访问权限,风险很高。强烈建议您 创建并使用 RAM用户进行 API访问或日常运维,请登录 RAM控制台创建 RAM用户。

// *改动1* 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
/* *原1* String accessKeyId = "LTAI4G...........";
String accessKeySecret = "yBsh.........";*/

// 填写 Bucket名称,例如 examplebucket。
String bucketName = "w.....xirui";
// 填写 Object完整路径,完整路径中不能包含 Bucket名称,例如 exampledir/exampleobject.txt。
String objectName = "孙芮.jpg";
// 填写本地文件的完整路径,例如 D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文 件流。
String filePath= "C:\\Users\\xirui\\Pictures\\Saved Pictures\\孙芮.jpg";

// *改动2* 创建 OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
/* *原2* OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);*/

try {
InputStream inputStream = new FileInputStream(filePath);
// 创建 PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
// 设置该属性可以返回 response。如果不设置,则返回的 response为 空。
putObjectRequest.setProcess("true");
// 创建 PutObject请求。
PutObjectResult result = ossClient.putObject(putObjectRequest);
// 如果上传成功,则返回 200。

System.out.println(result.getResponse().getStatusCode());

/*String content = "Hello OSS";
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));*/
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}

AliOSSUtils

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
// 阿里云 OSS 工具类
@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 String bucketName ;

@Autowired
private AliOSSProperties aliOSSProperties;

// 实现上传图片到OSS
public String upload(MultipartFile file) throws IOException {
//获取阿里云OSS参数
String endpoint = aliOSSProperties.getEndpoint();
String accessKeyId = aliOSSProperties.getAccessKeyId();
String accessKeySecret = aliOSSProperties.getAccessKeySecret();
String bucketName = aliOSSProperties.getBucketName();

// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();

// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);

//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}

AliOSSProperties

1
2
3
4
5
6
7
8
9
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliOSSProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}

application.yml

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
spring:
#数据库连接信息
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/tlias
username: root
password: 1234
#文件上传的配置
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

#阿里云OSS
aliyun:
oss:
endpoint: https://oss-cn-hangzhou.aliyuncs.com
accessKeyId: LT..................
accessKeySecret: yBs...................
bucketName: w....
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug

SessionController

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
// Cookie、HttpSession演示
@Slf4j
@RestController
public class SessionController {

//设置Cookie
@GetMapping("/c1")
public Result cookie1(HttpServletResponse response){
response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
return Result.success();
}

//获取Cookie
@GetMapping("/c2")
public Result cookie2(HttpServletRequest request){
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if(cookie.getName().equals("login_username")){
System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
}
}
return Result.success();
}

@GetMapping("/s1")
public Result session1(HttpSession session){
log.info("HttpSession-s1: {}", session.hashCode());

session.setAttribute("loginUser", "tom"); //往session中存储数据
return Result.success();
}

@GetMapping("/s2")
public Result session2(HttpServletRequest request){
HttpSession session = request.getSession();
log.info("HttpSession-s2: {}", session.hashCode());

Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
log.info("loginUser: {}", loginUser);
return Result.success(loginUser);
}
}

测试

WebManagementApplicationTests

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
//@SpringBootTest	// 因程序中Test与SpringBoot程序无关,所以不需加载全部资源,注释即可
class TliasWebManagementApplicationTests {
@Test
public void testUuid(){
for (int i = 0; i < 1000; i++) {
String uuid = UUID.randomUUID().toString();
System.out.println(uuid);
}
}

// 生成JWT
@Test
public void testGenJwt(){
Map<String, Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("name","tom");

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

// 解析JWT
@Test
public void testParseJwt(){
Claims claims = Jwts.parser()
.setSigningKey("it") // 签名密钥。必须与生成时一致
.parseClaimsJws("eyJhbG.................kmyw")//传递JWT令牌 改动任一部分都报错。
.getBody(); // 拿到JWT令牌的第二部分。即我们自定义的内容
System.out.println(claims); // {name=tom, id=1, exp=1725782938}
}
}

JwtUtils(JWT工具类)

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
public class JwtUtils {

private static String signKey = "itheima";
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)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}

LoginController(登陆成功,生成JWT令牌并返回)

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
@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("用户名或密码错误");
}
}
Ø DemoFilter(自定义过滤器)
//@WebFilter(urlPatterns = "/*") // 当前类拦截所有请求
public class DemoFilter implements Filter { // 导.servlet这个包
@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("Demo 拦截到了请求...放行前逻辑");
//放行
chain.doFilter(request,response);

System.out.println("Demo 拦截到了请求...放行后逻辑");
}

@Override //销毁方法, 只调用一次
public void destroy() {
System.out.println("destroy 销毁方法执行了");
}
}

TliasWebManagementApplication(引导类/启动类)

1
2
3
4
5
6
7
8
@ServletComponentScan //开启了对servlet组件的支持
@SpringBootApplication
public class TliasWebManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TliasWebManagementApplication.class, args);
}
}

LoginCheckFilter(登录校验过滤器)

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
@Slf4j  // 记录日志
//@WebFilter(urlPatterns = "/*") // 拦截所有
public class LoginCheckFilter implements Filter {
@Override
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; // 放行完之后无需执行下面3456逻辑,直接return跳出方法,让代码不再wangxia 执行
}

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

//4.判断令牌是否存在,如果不存在,返回错误结果(未登录)。
if(!StringUtils.hasLength(jwt)){ // jwt是否有长度 无长度,返回错误结果
log.info("请求头token为空,返回未登录的信息"); // 返回错误结果
Result error = Result.error("NOT_LOGIN");
// 响应json格式数据,之前controller中@RestController自动将方法返回值转成json,现在在过滤器中需要手动转换, 对象-->json --------> 利用阿里巴巴fastJSON工具包,导入依赖
String notLogin = JSONObject.toJSONString(error); // 调方法
resp.getWriter().write(notLogin); // 响应未登录结果给浏览器
return; // 跳出方法
}

//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt); // 解析JWT令牌
} catch (Exception e) { // jwt解析失败
e.printStackTrace(); // try-catch捕获异常
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return; // 跳出方法
}

//6.放行。
log.info("令牌合法, 放行");
chain.doFilter(request, response); // 放行
}
}

LoginCheckInterceptor(登录校验拦截器)

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
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标资源方法运行前运行, 返回true: 放行, 放回false, 不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
//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为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}

//5.解析token,如果解析失败,返回错误结果(未登录)。
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {//jwt解析失败
e.printStackTrace();
log.info("解析令牌失败, 返回未登录错误信息");
Result error = Result.error("NOT_LOGIN");
//手动转换 对象--json --------> 阿里巴巴fastJSON
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...");
}
}

WebConfig(注册配置拦截器)

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration //配置类
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;

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

GlobalExceptionHandler(全局异常处理器)

1
2
3
4
5
6
7
8
9
// 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)//捕获所有异常
public Result ex(Exception ex){
ex.printStackTrace(); // 打印堆栈中异常信息
return Result.error("对不起,操作失败,请联系管理员");
}
}

Log

DeptLog(事务Spring管理)

1
2
3
4
5
6
7
8
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptLog {
private Integer id;
private LocalDateTime createTime;
private String description;
}

DeptLogMapper

1
2
3
4
5
6
7
@Mapper
public interface DeptLogMapper {
@Insert("insert into dept_log(create_time, description)
values(#{createTime}, #{description})")
void insert(DeptLog log);

}

DeptLogService

1
2
3
public interface DeptLogService {
void insert(DeptLog deptLog);
}

DeptLogServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
@Service
public class DeptLogServiceImpl implements DeptLogService {

@Autowired
private DeptLogMapper deptLogMapper;

@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}

DeptServiceImpl

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
@Service
public class DeptServiceImpl implements DeptService {

@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;
@Autowired
private DeptLogService deptLogService;

//@Transactional(rollbackFor = Exception.class) //spring事务管理
@Transactional
@Override
public void delete(Integer id) throws Exception {
try {
deptMapper.deleteById(id); //根据ID删除部门数据

//int i = 1/0;
//if(true){throw new Exception("出错啦...");}

empMapper.deleteByDeptId(id); //根据部门ID删除该部门下的员工
} finally {
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门的操作,此次解散的是"+id+"号部门");
deptLogService.insert(deptLog);
}
}
}

OperateLogMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //ID
private Integer operateUser; //操作人ID
private LocalDateTime operateTime; //操作时间
private String className; //操作类名
private String methodName; //操作方法名
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
private Long costTime; //操作耗时
}

OperateLogMapper

1
2
3
4
5
6
7
8
9
@Mapper
public interface OperateLogMapper {

//插入日志数据
@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
public void insert(OperateLog log);

}

Log

1
2
3
4
5
// 定义注解 @Log
@Retention(RetentionPolicy.RUNTIME) // 注解使用,运行时有效
@Target(ElementType.METHOD) // 应用在方法上
public @interface Log {
}

DeptController(在增删改业务方法上添加@Log注解)

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
@Slf4j  
@RequestMapping("/depts")
@RestController
public class DeptController {
@Autowired
private DeptService deptService;

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

// 删除部门 @return
@Log
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) throws Exception {
log.info("根据id删除部门:{}",id);
deptService.delete(id); //调用service删除部门
return Result.success();
}

// 新增部门 @return
@Log
@PostMapping
public Result add(@RequestBody Dept dept){ // JSON格式数据封装到实体类中
log.info("新增部门: {}" , dept);
deptService.add(dept); //调用service新增部门
return Result.success();
}
}

LogAspect(定义切面类,完成记录操作日志的逻辑)

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
@Slf4j
@Component
@Aspect //切面类
public class LogAspect {
@Autowired
private HttpServletRequest request;

@Autowired
private OperateLogMapper operateLogMapper;

@Around("@annotation(com.itheima.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人ID - 当前登录员工ID
//获取请求头中的jwt令牌, 解析令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");

//操作时间
LocalDateTime operateTime = LocalDateTime.now();

//操作类名
String className = joinPoint.getTarget().getClass().getName();

//操作方法名
String methodName = joinPoint.getSignature().getName();

//操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);

long begin = System.currentTimeMillis();
//调用原始目标方法运行
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();

//方法返回值
String returnValue = JSONObject.toJSONString(result);

//操作耗时
Long costTime = end - begin;

//记录操作日志
OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
operateLogMapper.insert(operateLog);

log.info("AOP记录操作日志: {}" , operateLog);

return result;
}

}

pom.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
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>tlias-web-management</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>tlias-web-management</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!--web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--mybatis起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>

<!--mysql驱动-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>

<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!--springboot单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--PageHelper分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>

<!--阿里云OSS-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<!-- no more than 2.3.3-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

<!--JWT令牌-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>

<!--fastJSON-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>

<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.5</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

image-20250908134850238

image-20250908134911523


SpringBoot经典案例
https://blog.xirui.work/posts/fc22d27e.html
作者
xirui
发布于
2024年6月21日
许可协议