目录

用户登录

添加图书

图书列表

修改图书

删除图书

批量删除

拦截器

🍃前言

什么是拦截器?

拦截器的基本使用

自定义拦截器

注册配置拦截器

拦截路径

拦截器执行流程

项目实现统一拦截

定义拦截器

注册配置拦截器


前⾯图书管理系统, 咱们只完成了⽤⼾登录和图书列表, 并且数据是模拟的. 接下来我们把其他功能进⾏完善.

这里没图书管理系统之前模板的可以参考我之前博客[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);}});}

然后页面就可以正常访问了

 前后对比:

图书列表

可以看到, 添加图书之后, 跳转到图书列表⻚⾯, 并没有显⽰刚才添加的图书信息, 接下来我们来实现图 书列表
我们之前做的表⽩墙查询功能,是将数据库中所有的数据查询出来并展⽰到⻚⾯上,试想如果数据库 中的数据有很多(假设有⼗⼏万条)的时候,将数据全部展⽰出来肯定不现实,那如何解决这个问题呢?

使⽤分⻚解决这个问题。每次只展⽰⼀⻚的数据,⽐如:⼀⻚展⽰10条数据,如果还想看其他的数
据,可以通过点击⻚码进⾏查询

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 当前⻚码 //默认值为1
        pageSize 每⻚显⽰条数 //默认值为10
5后端响应时, 需要响应给前端的数据
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.约定前后端交互接⼝

我们约定, 浏览器给服务器发送⼀个 /book/getListByPage 这样的 HTTP 请求, 通过
currentPage 参数告诉服务器, 当前请求为第⼏⻚的数据, 后端根据请求参数, 返回对应⻚的数据
第⼀⻚可以不传参数, currentPage默认值为 1
7.创建enmus⽬录, 创建BookStatus类:
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 | 菜鸟教程

使用教程,修改里面的部分值即可

onPageChange :回调函数,当换⻚时触发(包括初始化第⼀⻚的时候),会传⼊两个参数:
1、"⽬标⻚"的⻚码page,Number类型
2、触发类型type,可能的值:"init"(初始化),"change"(点击分⻚)

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>
我们修改图书信息, 是根据图书ID来修改的, 所以需要前端传递的参数中, 包含图书ID
在form表单中, 再增加⼀个隐藏输⼊框, 存储图书ID, 随 $("#updateBook").serialize()
⼀起提交到后端
1. 获取url中参数的值(⽐较复杂, 需要拆分url)
2. 在form表单中, 再增加⼀个隐藏输⼊框, 存储图书ID, 随 $("#updateBook").serialize()
⼀起提交到后端
我们采⽤第⼆种⽅式
在form表单中,添加隐藏输⼊框
<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 元素在渲染完成的⻚⾯中完全不可⻅,⽽ 且没有⽅法可以使它重新变为可⻅
⻚⾯加载时, 给该hidden框赋值
     $("#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中是否有登录⽤⼾的信息.如果有就可以放⾏,如果没有就进⾏拦截.

就⽐如我们去银⾏办理业务,在办理业务前后,就可以加⼀些拦截操作,办理业务之前,先取号,如果带⾝份证了就取号成功。业务办理结束,给业务办理⼈员的服务进⾏评价.这些就是"拦截器"做的⼯作

拦截器的基本使用

拦截器的使⽤步骤分为两步:

  1. 定义拦截器
  2. 注册配置拦截器

自定义拦截器

⾃定义拦截器:实现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;}
}

代码解释如下:

  1. 对服务中所存在的session进行判断,如果存在,则返回true,放行
  2. 若不存在,则使用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");}}

访问用户登录接口:此时就可以访问了

希望你们可以从这一个简单项目中学到一个项目的基础思路知识

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/90800.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/90800.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/90800.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于同花顺API的熊市与牛市识别模型开发及因子分析

基于同花顺API的熊市与牛市识别模型开发及因子分析 1. 引言 1.1 研究背景与意义 金融市场中的牛市与熊市识别一直是投资者和研究人员关注的重点问题。牛市(Bull Market)通常指价格持续上涨的市场环境,投资者信心充足,交易活跃;而熊市(Bear Market)则指价格持续下跌的市场…

AMD 锐龙 AI MAX+ 395 处理器与端侧 AI 部署的行业实践

2025 年 7 月 10 日&#xff0c;AMD 在深圳召开 Mini AI 工作站行业解决方案峰会&#xff0c;正式发布基于锐龙 AI MAX 395 处理器的端侧 AI 部署方案&#xff0c;与 200 余家生态伙伴共同探讨 AI 技术在千行百业的落地路径。这一硬件平台通过异构计算架构与开放生态设计&#…

期权盘位是什么意思?

本文主要介绍期权盘位是什么意思&#xff1f;“期权盘位”并非金融交易中的标准术语&#xff0c;可能是口语化表达或对某些概念的简化描述。期权盘位是什么意思&#xff1f;1. 期权盘口的“价位”&#xff08;买卖报价位置&#xff09;在期权交易中&#xff0c;“盘口”通常指实…

【Trea】Trea国内版|国际版|海外版下载|Mac版|Windows版|Linux下载配置教程

【Trea】Trea国内版&#xff5c;国际版&#xff5c;海外版下载&#xff5c;Mac版&#xff5c;Windows版下载配置教程 本文适用读者&#xff1a; 想要第一次安装 Trea需要在 Windows 或 macOS 上完成环境配置想深入了解 Doubao、DeepSeek、ChatGPT、Claude 等模型在 Trea 中的接…

MyBatis实现分页查询-苍穹外卖笔记

首先分页查询的原理是SQL的limit关键字。LIMIT 子句用于限制 SQL 查询返回的记录数。它接受一个或两个整数参数&#xff0c;第一个参数表示偏移量&#xff0c;第二个参数表示返回的最大记录数。我们完全可以使用前端传给我们的page,pageSize,自己去计算limit的参数&#xff0c;…

系统性能评估方法深度解析:从经典到现代

评估本质&#xff1a;系统性能评估是通过量化分析衡量计算机系统在特定工作负载下的表现能力&#xff0c;核心目标是建立可比较的性能基准&#xff0c;为系统设计、选型和优化提供科学依据。一、评估方法分类体系 #mermaid-svg-0ceD4AA2KDwzwtb6 {font-family:"trebuchet …

WebSocket实现多人实时在线聊天

最近公司在做一个婚恋app&#xff0c;需要增加一个功能&#xff0c;实现多人实时在线聊天。基于WebSocket在Springboot中的使用&#xff0c;前端使用vue开发。 一&#xff1a;后端 1. 引入 websocket 的 maven 依赖 <dependency><groupId>org.springframework.bo…

学习笔记随记-FPGA/硬件加速

一、FPGA&#xff1a;Field Programmable Gate Array 现场可编程门阵列 可编程输入/输出单元、基本可编程逻辑单元、嵌入式块RAM、丰富的布线资源、底层嵌入功能单元和内嵌专用硬核。 可编程输入/输出单元&#xff08;I/O&#xff09;单元 输入/输出&#xff08;Input/Ouput&…

docker宿主机修改ip后起不来问题解决

确保容器已经连接到了正确的网络。如果没有&#xff0c;你可以使用以下命令将容器连接到网络&#xff1a; 1、停止docker网络 ifconfig docker0 down1. 停止 Docker 服务 sudo systemctl stop docker2. 删除 docker0 接口 sudo ip link delete docker03、删除旧的网桥 docker n…

G1 垃圾回收算法详解

目录 简介 G1 GC 的设计目标 内存结构 回收过程 初始标记&#xff08;Initial Mark&#xff09;并发标记&#xff08;Concurrent Mark&#xff09;最终标记&#xff08;Final Mark / Remark&#xff09;筛选回收&#xff08;Cleanup / Evacuation&#xff09; 混合回收&…

JavaEE多线程——锁策略 CAS synchronized优化

目录前言1.锁策略1.1 乐观锁和悲观锁1.2 重量级锁和轻量级锁1.3 挂起等待锁和自旋锁1.4 公平锁和非公平锁1.5 可重入锁和不可重入锁1.6 读写锁2.CAS2.1 CAS的应用2.2 CAS的ABA问题3.synchronized优化3.1锁升级3.2锁消除3.3锁粗化总结前言 本篇文章主要介绍多线程中锁策略、CAS…

Windows符号链接解决vscode和pycharm占用C盘空间太大的问题

Windows符号链接解决vscode和pycharm占用C盘空间太大的问题 参考文章&#xff1a;Windows符号链接 1、找到vscode和pycharm在C盘的缓存文件夹。 C:\Users\用户名\AppData\Roaming\Code C:\Users\用户名\.vscode\extensionsC:\Users\用户名\AppData\Local\JetBrains C:\Users…

赋能家庭、行业与工业场景,智微智能新一代Twin Lake 全栈智能终端发布

在数字化浪潮席卷全球的今天&#xff0c;智能终端已成为连接物理世界与数字世界的核心枢纽。智微智能基于Intel Twin Lake平台&#xff0c;推出覆盖家庭/行业应用及工业物联网的全场景产品矩阵&#xff0c;为不同场景下的用户提供高效、可靠的产品和解决方案。Intel Twin Lake架…

复习笔记 31

前言 好好复习。今天距离考研初试还剩一百六十一天。我的时间其实没剩多少了呀。我得好好加油。 归并排序 #include<algorithm> #include<iostream>using namespace std;const int N 100010; int n; int a[N], tmp[N];void merge ( int a[], int l, int r ) {if (…

el-tree 懒加载 loadNode

el-tree 是 Element UI 提供的树形组件&#xff0c;其懒加载功能通过 loadNode 方法实现&#xff0c;可以在用户展开节点时动态加载子节点数据&#xff0c;避免一次性加载大量数据。下面介绍 loadNode 的具体用法和示例。基本用法loadNode 是 el-tree 的一个重要属性&#xff0…

【机器学习入门巨详细】(研0版)二创OPEN MLSYS

自学机器学习&#xff0c;从入门到精通导论机器学习的基本框架设计目标机器学习框架基本组成原理机器学习生态机器学习工作流环境配置数据处理模型定义损失函数和优化器训练及保存模型测试及验证模型定义深度神经网络以层为核心定义神经网络神经网络层实现原理自定义神经网络层…

Excel 转 JSON by WTSolutions API 文档

Excel 转 JSON by WTSolutions API 文档 简介 Excel 转 JSON API 提供了一种简单的方式将 Excel 和 CSV 数据转换为 JSON 格式。该 API 接受制表符分隔或逗号分隔的文本数据&#xff0c;并返回结构化的 JSON。 接口端点 POST https://mcp.wtsolutions.cn/excel-to-json-api 请求…

git版本发布

cvs和svn都是集中式版本控制系统,而git是分布式版本控制系统。 1、集中式版本控制系统必须联网才能工作&#xff0c;如果在局域网内还好&#xff0c;带宽够大&#xff0c;速度够快&#xff0c;可如果在互联网上&#xff0c;遇到网速慢的话&#xff0c;呵呵。分布式版本控制系统…

138-EMD-KPCA-CPO-CNN-BiGRU-Attention模型!

138-EMD-KPCA-CPO-CNN-BiGRU-Attention基于经验模态分解和核主成分分析的长短期记忆网络改进多维时间序列预测MATLAB代码&#xff01;其中&#xff08;含CPO-CNN-BiGRU-attention、EMD-CPO-CNN-BiGRU-Attention、EMD-KPCA-CPO-CNN-BiGRU-Attention三个模型的对比&#xff09; 可…

系统思考:多元胜过能力

系统思考&#xff1a;从整体出发&#xff0c;打破瓶颈&#xff0c;拥抱多元 我们是否曾经陷入过这样的困境&#xff1f; 1、专注能力提升&#xff0c;却无法突破瓶颈&#xff1a;我和团队日复一日地努力提升个人能力&#xff0c;投入无数时间和精力&#xff0c;但始终无法打破现…