【JavaEE进阶】图书管理系统(未完待续)
目录
用户登录
添加图书
图书列表
修改图书
删除图书
批量删除
拦截器
🍃前言
什么是拦截器?
拦截器的基本使用
自定义拦截器
注册配置拦截器
拦截路径
拦截器执行流程
项目实现统一拦截
定义拦截器
注册配置拦截器

这里没图书管理系统之前模板的可以参考我之前博客[SpringBoot]Spring MVC(6.0)----图书管理系统(初)-CSDN博客
在那基础上,我们继续进行代码书写
用户登录
第一步引入依赖,以及配置yml文件
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/book_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration: # ???? MyBatis ??? SQLlog-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #??????
# ???? MyBatis ??? SQL
logging:file:name: logs/springboot.log
引入依赖需要我们加上一个插件
然后在pom.xml文件中右键
然后进行选择即可,这里我们需要选择我右面的依赖
其次,我们书写Controller--Service--Mapper结构
package com.example.demo.controller;import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/login")public boolean login(String userName, String password, HttpSession session) {//校验用户信息是否合法.if(!StringUtils.hasLength(userName) || !StringUtils.hasLength(password)) {return false;}UserInfo userInfo=userService.queryUserByName(userName);if(userInfo==null){return false;}//判断用户名和密码是否正确//理论上应该从数据库中获取, 但是目前还没学习 mybatis, 所以先这么写.if(userInfo!=null&&password.equals(userInfo.getPassword())) {return true;}userInfo.setPassword("");session.setAttribute("userName",userName);return false;}
}
为什么要把userInfo的密码设置为空再设置session呢?
因为存储到session中了.你如果不将密码设置为空的话,在获取到session就能看到密码
import com.example.demo.model.UserInfo;
import com.example.demo.model.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;@Service
public class UserService{@Autowiredprivate UserInfoMapper userInfoMapper;public UserInfo queryUserByName(String name) {return userInfoMapper.queryUserByName(name);}
}
package com.example.demo.model;import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;@Mapper
public interface UserInfoMapper {@Select("select * from user_info where delete_flag=0 and user_name = #{name}")UserInfo queryUserByName(String name);
}
这样我们就可以完成一个登录,数据库中存储信息
同时我们需要修改前端代码(因为我们将返回类型改变了)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="css/bootstrap.min.css"><link rel="stylesheet" href="css/login.css"><script type="text/javascript" src="js/jquery.min.js"></script>
</head><body><div class="container-login"><div class="container-pic"><img src="pic/computer.png" width="350px"></div><div class="login-dialog"><h3>登陆</h3><div class="row"><span>用户名</span><input type="text" name="userName" id="userName" class="form-control"></div><div class="row"><span>密码</span><input type="password" name="password" id="password" class="form-control"></div><div class="row"><button type="button" class="btn btn-info btn-lg" onclick="login()">登录</button></div></div></div><script src="js/jquery.min.js"></script><script>function login() {$.ajax({url : "/user/login",type : "post",data : {userName : $("#userName").val(),password : $("#password").val(),},success:function(result) {if(result==false) {alert("用户名或密码错误,请重新输入");}else {location.href = "book_list.html";}}});}</script>
</body></html>
添加图书
@RequestMapping("/addBook")public String addBook(BookInfo bookInfo){System.out.println("添加图书"+bookInfo);if (!StringUtils.hasLength(bookInfo.getBookName())|| !StringUtils.hasLength(bookInfo.getAuthor())|| bookInfo.getCount()==null|| bookInfo.getPrice()==null|| !StringUtils.hasLength(bookInfo.getPublish())|| bookInfo.getStatus() ==null){return "输⼊参数不合法, 请检查⼊参!";}try {bookService.addBook(bookInfo);return "";}catch (Exception e){System.out.println("增添错误");return e.getMessage();}}
业务层
public void addBook(BookInfo bookInfo) {bookInfoMapper.insertBook(bookInfo);}
数据层:
@Mapper
public interface BookInfoMapper {@Insert("insert into book_info (book_name,author,count,price,publish,status) " + "values (#{bookName},#{author},#{count},#{price},#{publish},#{status})")Integer insertBook(BookInfo bookInfo);
}
前端修改代码
function add() {$.ajax({type: "post",url: "/book/addBook",data: $("#addBook").serialize(),success: function (result) {if (result == "") {location.href = "book_list.html"} else {console.log(result);alert("添加失败:" + result);}},error: function (error) {console.log(error);}});}
然后页面就可以正常访问了
前后对比:
图书列表
1.添加数据
INSERT INTO `book_info` ( book_name, author, count, price, publish )
VALUES
( '图书2', '作者2', 29, 22.00, '出版社2' ),( '图书3', '作者2', 29, 22.00, '出版社
3' ),
( '图书4', '作者2', 29, 22.00, '出版社1' ),( '图书5', '作者2', 29, 22.00, '出版社
1' ),
( '图书6', '作者2', 29, 22.00, '出版社1' ),( '图书7', '作者2', 29, 22.00, '出版社
1' ),
( '图书8', '作者2', 29, 22.00, '出版社1' ),( '图书9', '作者2', 29, 22.00, '出版社
1' ),
( '图书10', '作者2', 29, 22.00, '出版社1'),( '图书11', '作者2', 29, 22.00, '出版 社1'),
( '图书12', '作者2', 29, 22.00, '出版社1'),( '图书13', '作者2', 29, 22.00, '出版 社1'),
( '图书14', '作者2', 29, 22.00, '出版社1'),( '图书15', '作者2', 29, 22.00, '出版 社1'),
( '图书16', '作者2', 29, 22.00, '出版社1'),( '图书17', '作者2', 29, 22.00, '出版 社1'),
( '图书18', '作者2', 29, 22.00, '出版社1'),( '图书19', '作者2', 29, 22.00, '出版 社1'),
( '图书20', '作者2', 29, 22.00, '出版社1'),( '图书21', '作者2', 29, 22.00, '出版 社1');
2.了解分页的sql原理
查找第1到10条
SELECT * FROM book_info LIMIT 0,10
3.算法可知:开始索引 = (当前⻚码 - 1) * 每⻚显⽰条数
4.前端在发起查询请求时,需要向服务端传递的参数
currentPage 当前⻚码 //默认值为1pageSize 每⻚显⽰条数 //默认值为10
records 所查询到的数据列表(存储到List 集合中)total 总记录数 (⽤于告诉前端显⽰多少⻚, 显⽰⻚数为: (total + pageSize -1)/pageSize显⽰⻚数totalPage 计算公式为 : total % pagesize == 0 ? total / pagesize : (total /pagesize)+1;)
6.翻⻚请求和响应部分, 我们通常封装在两个对象中
翻页请求对象
@Data
public class PageRequest {private int currentPage = 1; // 当前⻚private int pageSize = 10; // 每⻚中的记录数 }
我们需要根据currentPage 和pageSize ,计算出来开始索引
所以PageRequest修改为
package com.example.demo.model;import lombok.Data;@Data
public class PageRequest {private int currentPage = 1; // 当前⻚private int pageSize = 10; // 每⻚中的记录数private int offset=1;public int getOffset() {return (currentPage-1) * pageSize;}
}
翻⻚列表结果类:
import lombok.Data;
import java.util.List;
@Data
public class PageResult<T> {private int total;//所有记录数private List<T> records; // 当前⻚数据public PageResult(Integer total, List<T> records) {this.total = total;this.records = records;}
}
7.约定前后端交互接⼝
package com.example.demo.model;public enum BookStatus {DELETED(0,"⽆效"),NORMAL(1,"可借阅"),FORBIDDEN(2,"不可借阅");private Integer code;private String name;BookStatus(int code, String name) {this.code = code;this.name = name;}public static BookStatus getNameByCode(Integer code){switch (code){case 0: return DELETED;case 1: return NORMAL;case 2: return FORBIDDEN;}return null;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
8.完善PageResult类
package com.example.demo.model;import lombok.Data;
import java.util.List;
@Data
public class PageResult<T> {public int total;//所有记录数private List<T> records; // 当前⻚数据private PageRequest pageRequest;public PageResult(Integer total, PageRequest pageRequest, List<T> records){this.total = total;this.pageRequest = pageRequest;this.records = records;}
}
将PageRequest类引入,便于后面的分页插件获取信息
分⻚组件需要提供⼀些信息: totalCounts: 总记录数, pageSize: 每⻚的个数, visiblePages: 可视⻚数 ,currentPage: 当前⻚码这些信息中, pageSize 和 visiblePages 前端直接设置即可. totalCounts 后端已经提供, currentPage 也 可以从参数中取到, 但太复杂了, 咱们直接由后端返回即可.
9.完善 BookController,BookService,BookInfoMapper
@RequestMapping("/getListByPage")public PageResult<BookInfo> getListByPage(PageRequest pageRequest) {log.info("获取图书列表{}",pageRequest);PageResult<BookInfo> pageResult = bookService.getBookListByPage(pageRequest);return pageResult;}
public PageResult<BookInfo> getBookListByPage(PageRequest pageRequest) {Integer count = bookInfoMapper.count();List<BookInfo> books = bookInfoMapper.queryBookListByPage(pageRequest);for (BookInfo book:books){book.setStatusCN(BookStatus.getNameByCode(book.getStatus()).getName());}return new PageResult<>(count,pageRequest, books);}
@Mapper
public interface BookInfoMapper {@Insert("insert into book_info (book_name,author,count,price,publish,status) " + "values (#{bookName},#{author},#{count},#{price},#{publish},#{status})")Integer insertBook(BookInfo bookInfo);@Select("select count(1) from book_info where status<>0")Integer count();@Select("select * from book_info where status !=0 order by id desc limit #{offset}, #{pageSize}")List<BookInfo> queryBookListByPage(PageRequest pageRequest);
}
10.接下来处理分⻚信息
jQuery 分页插件 : jqPaginator | 菜鸟教程
使用教程,修改里面的部分值即可
11.编写前端代码(前后端交互,个人认为最为难人的部分,需要重点理解)
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>图书列表展示</title><link rel="stylesheet" href="css/bootstrap.min.css"><link rel="stylesheet" href="css/list.css"><script type="text/javascript" src="js/jquery.min.js"></script><script type="text/javascript" src="js/bootstrap.min.js"></script><script src="js/jq-paginator.js"></script></head><body><div class="bookContainer"><h2>图书列表展示</h2><div class="navbar-justify-between"><div><button class="btn btn-outline-info" type="button" onclick="location.href='book_add.html'">添加图书</button><button class="btn btn-outline-info" type="button" onclick="batchDelete()">批量删除</button></div></div><table><thead><tr><td>选择</td><td class="width100">图书ID</td><td>书名</td><td>作者</td><td>数量</td><td>定价</td><td>出版社</td><td>状态</td><td class="width200">操作</td></tr></thead><tbody></tbody></table><div class="demo"><ul id="pageContainer" class="pagination justify-content-center"></ul></div><script>function getQueryParam(name) {const urlParams = new URLSearchParams(window.location.search);return urlParams.get(name);}// 修复:设置默认参数currentPage = 1,避免初始调用时参数缺失function getBookList(currentPage=1) {$.ajax({type: "get",// 修复:确保参数正确拼接,避免出现undefinedurl: "/book/getListByPage?currentPage=" + currentPage,success: function (result) {console.log(result);if (result != null && result.records) { // 修复:字段名records(原resords拼写错误)var finalHtml = "";// 遍历图书数据(修复字段名resords -> records)for (var book of result.records) {finalHtml += '<tr>';finalHtml += '<td><input type="checkbox" name="selectBook" value="' + book.id + '" class="book-select"></td>';finalHtml += '<td>' + book.id + '</td>';finalHtml += '<td>' + book.bookName + '</td>';finalHtml += '<td>' + book.author + '</td>';finalHtml += '<td>' + book.count + '</td>';finalHtml += '<td>' + book.price + '</td>';finalHtml += '<td>' + book.publish + '</td>';finalHtml += '<td>' + book.statusCN + '</td>';finalHtml += '<td><div class="op">';finalHtml += '<a href="book_update.html?bookId=' + book.id + '">修改</a>'; // 修复:链接中去除空格(bookId后无空格)finalHtml += '<a href="javascript:void(0)" onclick="deleteBook(' + book.id + ')">删除</a>';finalHtml += '</div></td>';finalHtml += "</tr>";}$("tbody").html(finalHtml);// 初始化分页插件$("#pageContainer").jqPaginator({totalCounts: result.total, // 总记录数(需与后端返回字段一致)pageSize: 10, // 每页条数visiblePages: 5, // 可见页码数currentPage: result.pageRequest.currentPage, // 修复:从返回结果中获取当前页码(根据后端实际字段名调整)first: '<li class="page-item"><a class="page-link">首页</a></li>',prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">上一页</a></li>',next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">下一页</a></li>',last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">最后一页</a></li>',page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}</a></li>',// 修复:分页切换时直接调用getBookList加载数据,而非跳转页面onPageChange: function (Page, type) {if (type != 'init') {location.href = "book_list.html?currentPage=" + Page;// getBookList(Page);}}});}},error: function (xhr) {console.error("请求失败:", xhr.responseText); // 增加错误日志,便于调试}});}// 初始调用时无需传参(函数已设置默认值)const initialPage = parseInt(getQueryParam("currentPage")) || 1;getBookList(initialPage);// getBookList();function deleteBook(id) {var isDelete = confirm("确认删除?");if (isDelete) {//删除图书alert("删除成功");}}function batchDelete() {var isDelete = confirm("确认批量删除?");if (isDelete) {//获取复选框的idvar ids = [];$("input:checkbox[name='selectBook']:checked").each(function () {ids.push($(this).val());});console.log(ids);alert("批量删除成功");}}</script></div>
</body></html>
修改图书
进⼊修改⻚⾯, 需要显⽰当前图书的信息
[请求]
/book/queryBookById?bookId=25
[参数] ⽆[响应] {"id": 25,"bookName": "图书21","author": "作者2","count": 999,"price": 222.00,"publish": "出版社1","status": 2,"statusCN": null,"createTime": "2023-09-04T04:01:27.000+00:00","updateTime": "2023-09-05T03:37:03.000+00:00"
}
点击修改按钮, 修改图书信息
[请求]
/book/updateBook
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
[参数]
id=1&bookName=图书1&author=作者1&count=23&price=36&publish=出版社1&status=1
[响应]
"" //失败信息, 成功时返回空字符串
我们约定, 浏览器给服务器发送⼀个 /book/updateBook 这样的 HTTP 请求, form表单的形式来 提交数据服务器返回处理结果, 返回""表⽰添加图书成功, 否则, 返回失败信息
实现服务器代码
BookController代码
@RequestMapping("/queryBookById")public BookInfo queryBookById(Integer bookId) {if (bookId == null || bookId <= 0) {return new BookInfo();}BookInfo bookInfo = bookService.queryBookById(bookId);return bookInfo;}@RequestMapping("/updateBook")public String updateBook(BookInfo bookInfo) {log.info("修改图书:{}", bookInfo);try {bookService.updateBook(bookInfo);return "";} catch (Exception e) {log.error("修改图书失败", e);return e.getMessage();}}
BookService代码
public BookInfo queryBookById(Integer bookId) {return bookInfoMapper.queryBookById(bookId);}public void updateBook(BookInfo bookInfo) {bookInfoMapper.updateBook(bookInfo);}
BookInfoMapper代码
@Select("select id, book_name, author, count, price, publish, `status`,
create_time, update_time " +"from book_info where id=#{bookId} and status<>0")
BookInfo queryBookById(Integer bookId);
前端代码
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>修改图书</title><link rel="stylesheet" href="css/bootstrap.min.css"><link rel="stylesheet" href="css/add.css">
</head><body><div class="container"><div class="form-inline"><h2 style="text-align: left; margin-left: 10px;"><svg xmlns="http://www.w3.org/2000/svg" width="40"fill="#17a2b8" class="bi bi-book-half" viewBox="0 0 16 16"><pathd="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" /></svg><span>修改图书</span></h2></div><form id="updateBook"><input type="hidden" class="form-control" id="bookId" name="id"><div class="form-group"><label for="bookName">图书名称:</label><input type="text" class="form-control" id="bookName" name="bookName"></div><div class="form-group"><label for="bookAuthor">图书作者</label><input type="text" class="form-control" id="bookAuthor" name="author" /></div><div class="form-group"><label for="bookStock">图书库存</label><input type="text" class="form-control" id="bookStock" name="count" /></div><div class="form-group"><label for="bookPrice">图书定价:</label><input type="number" class="form-control" id="bookPrice" name="price"></div><div class="form-group"><label for="bookPublisher">出版社</label><input type="text" id="bookPublisher" class="form-control" name="publish" /></div><div class="form-group"><label for="bookStatus">图书状态</label><select class="custom-select" id="bookStatus" name="status"><option value="1" selected>可借阅</option><option value="2">不可借阅</option></select></div><div class="form-group" style="text-align: right"><button type="button" class="btn btn-info btn-lg" onclick="update()">确定</button><button type="button" class="btn btn-secondary btn-lg" onclick="javascript:history.back()">返回</button></div></form></div><script type="text/javascript" src="js/jquery.min.js"></script><script>$.ajax({type: "get",url: "/book/queryBookById" + location.search,success: function (book) {if (book != null) {$("#bookId").val(book.id);$("#bookName").val(book.bookName);$("#bookAuthor").val(book.author);$("#bookStock").val(book.count);$("#bookPrice").val(book.price);$("#bookPublisher").val(book.publish);$("#bookStatus").val(book.status);}}});function update() {$.ajax({type: "post",url: "/book/updateBook",data: $("#updateBook").serialize(),success: function (result) {if (result == "") {location.href = "book_list.html"} else {console.log(result);alert("修改失败:" + result);}},error: function (error) {console.log(error);}});}</script>
</body></html>
1. 获取url中参数的值(⽐较复杂, 需要拆分url)2. 在form表单中, 再增加⼀个隐藏输⼊框, 存储图书ID, 随 $("#updateBook").serialize()
<form id="updateBook"><input type="hidden" class="form-control" id="bookId" name="id"><div class="form-group"><label for="bookName">图书名称:</label><input type="text" class="form-control" id="bookName" name="bookName"></div>
hidden 类型的 <input> 元素隐藏表单, ⽤⼾不可⻅、不可改的数据,在⽤⼾提交表单时,这些数据会⼀并发送出使⽤场景: 正被请求或编辑的内容的 ID. 这些隐藏的 input 元素在渲染完成的⻚⾯中完全不可⻅,⽽ 且没有⽅法可以使它重新变为可⻅
$("#bookId").val(book.id);
删除图书
删除分为 逻辑删除 和物理删除逻辑删除逻辑删除也称为软删除、假删除、Soft Delete,即不真正删除数据,⽽在某⾏数据上增加类型 is_deleted的删除标识,⼀般使⽤UPDATE语句物理删除物理删除也称为硬删除,从数据库表中删除某⼀⾏或某⼀集合数据,一般用DELETE语句
function deleteBook(id) {var isDelete = confirm("确认删除?");if (isDelete) {//删除图书$.ajax({type: "post",url: "/book/updateBook",data: {id: id,status: 0},success: function () {//重新刷新⻚⾯location.href = "book_list.html"}});}}
批量删除
点击[批量删除]按钮时, 只需要把复选框选中的图书的ID,发送到后端即可多个id, 我们使⽤List的形式来传递参数
@RequestMapping("/batchDeleteBook")public boolean batchDeleteBook(@RequestParam List<Integer> ids){log.info("批量删除图书, ids:{}",ids);try{bookService.batchDeleteBook(ids);}catch (Exception e){log.error("批量删除异常,e:",e);return false;}return true;}public void batchDeleteBook(List<Integer> ids) {bookInfoMapper.batchDeleteBook(ids);
void batchDeleteBook(List<Integer> ids);
xml
<update id="batchDeleteBook">update book_info set status=0 where id in<foreach collection="ids" item="id" separator="," open="(" close=")">#{id}</foreach></update>
拦截器
🍃前言
前面我们基本实现了图书股管理系统的功能,但是我们依旧存在一个问题。
就是我们不论是否登录,我们直接访问图书列表。也可以进行访问及修改
而我们希望达到的效果是,必须要进行登录后才能进行一系列操作
这里我们使用拦截器来完成着一系列操作
什么是拦截器?
拦截器是Spring框架提供的核⼼功能之⼀,主要⽤来拦截⽤⼾的请求,在指定⽅法前后,根据业务需要执⾏预先设定的代码.
也就是说,允许开发⼈员提前预定义⼀些逻辑,在⽤⼾的请求响应前后执⾏.也可以在⽤⼾请求前阻⽌其执⾏.
在拦截器当中,开发⼈员可以在应⽤程序中做⼀些通⽤性的操作,⽐如通过拦截器来拦截前端发来的请求,判断Session中是否有登录⽤⼾的信息.如果有就可以放⾏,如果没有就进⾏拦截.
就⽐如我们去银⾏办理业务,在办理业务前后,就可以加⼀些拦截操作,办理业务之前,先取号,如果带⾝份证了就取号成功。业务办理结束,给业务办理⼈员的服务进⾏评价.这些就是"拦截器"做的⼯作
拦截器的基本使用
拦截器的使⽤步骤分为两步:
- 定义拦截器
- 注册配置拦截器
自定义拦截器
⾃定义拦截器:实现HandlerInterceptor接⼝,并重写其所有⽅法
package com.example.demo.configuration;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("执行目标方法前的代码");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("目标执行完后的代码");HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("目标试图渲染后的代码");HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}
这里涉及到的三个方法
preHandle()⽅法:⽬标⽅法执⾏前执⾏. 返回true:继续执⾏后续操作;返回false:中断后续操作.
postHandle()⽅法:⽬标⽅法执⾏后执⾏
afterCompletion()⽅法:视图渲染完毕后执⾏,最后执⾏(后端开发现在⼏乎不涉及视图,暂不了解)
注册配置拦截器
注册配置拦截器:实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法
WebMvcConfigurer这个接口并不是只给拦截器使用的,而是WebMVC相关的配置都在这里
package com.example.demo.configuration;import com.example.demo.model.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/**");}
}
启动服务,试试访问任意请求,观察后端⽇志
们把拦截器中preHandle⽅法的返回值改为false,再观察运⾏结果
运行结果:可以看到,拦截器拦截了请求,没有进⾏响应
拦截路径
关于注册配置拦截器的拦截路劲,拦截路径是指我们定义的这个拦截器,对哪些请求⽣效.我们在注册配置拦截器的时候,通过 addPathPatterns() ⽅法指定要拦截哪些请求.也可以通过
excludePathPatterns() 指定不拦截哪些请求.上述代码中,我们配置的是 /** ,表⽰拦截所有的请求.
拦截器中除了可以设置 /** 拦截所有资源外,还有⼀些常⻅拦截路径设置,比如在该项目中
拦截器执行流程
我们先来看一下正常的执行流程
当我们有了拦截器以后,我们的执行流程为
1.添加拦截器后,执⾏Controller的⽅法之前,请求会先被拦截器拦截住.执⾏ preHandle() ⽅法,这个⽅法需要返回⼀个布尔类型的值.如果返回true,就表⽰放⾏本次操作,继续访问controller中的⽅法.如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).
2.controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据.
项目实现统一拦截
定义拦截器
首先定义一个常量类来存放我们的sessionid常量等等
package com.example.demo.model;public class Constants {public static final String SESSION_USER_KEY="userName";
}
再来就是拦截器(逻辑简单,查看有没有现在的这个session即可)
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("登录拦截器校验...");HttpSession session = request.getSession();UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);if (userInfo!=null && userInfo.getId()>=0){return true;}response.setStatus(401);//401 表示未认证登录return false;}
}
代码解释如下:
- 对服务中所存在的session进行判断,如果存在,则返回true,放行
- 若不存在,则使用setStatus()方法设置http状态码为401,前端收到响应进行跳转到登录页面
当前按照上述配置拦截器的代码来看,会拦截所有的路径,那么此时在没有登录的情况下,访问每个接口都会进行拦截,包括登录接口
所以我们需要把上述配置拦截器中的拦截路径重新配置一下
注册配置拦截器
注意:拦截器也会拦截前端请求
@Configurationpublic class WebConfig implements WebMvcConfigurer {//⾃定义的拦截器对象 @Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册⾃定义拦截器对象registry.addInterceptor(loginInterceptor).addPathPatterns("/**")//设置拦截器拦截的请求路径(/**表⽰拦截所有请求 .excludePathPatterns("/user/login")//设置拦截器排除拦截的路径.excludePathPatterns("/**/*.js") //排除前端静态资源.excludePathPatterns("/**/*.css").excludePathPatterns("/**/*.png").excludePathPatterns("/**/*.html");}}
访问用户登录接口:此时就可以访问了
希望你们可以从这一个简单项目中学到一个项目的基础思路知识