当前位置: 首页 > news >正文

【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);}});}

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

 前后对比:

图书列表

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

使⽤分⻚解决这个问题。每次只展⽰⼀⻚的数据,⽐如:⼀⻚展⽰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.dtcms.com/a/270509.html

相关文章:

  • 【学习笔记】OkHttp源码架构解析:从设计模式到核心实现
  • 保姆级安装 Ruby 环境下载及安装教程, RubyInstaller下载及安装教程
  • Javaweb - 10.7 乱码和路径问题
  • 影石(insta360)X4运动相机视频删除的恢复方法
  • SHA-256算法详解——Github工程结合示例和动画演示
  • 中望CAD2026亮点速递(5):【相似查找】高效自动化识别定位
  • Python(30)基于itertools生成器的量子计算模拟技术深度解析
  • 【SQL】使用UPDATE修改表字段的时候,遇到1054 或者1064的问题怎么办?
  • (八)PS识别:使用 Python 自动化生成图像PS数据集
  • Linux驱动05 --- TCP 服务器
  • 分库分表之实战-sharding-JDBC绑定表配置实战
  • uniapp+vue3+ts项目:实现小程序文件下载、预览、进度监听(含项目、案例、插件)
  • PostgreSQL如何进行跨服务器迁移数据
  • ARIA UWB安全雷达主要产品型号与核心功能全解析
  • 【数字后端】- Standard Cell Status
  • 亚马逊广告进阶指南:CPC与竞价的底层逻辑
  • 游戏开发学习记录
  • 基于Flask 3.1和Python 3.13的简易CMS
  • LLM中 最后一个词语的表征(隐藏状态)通常会融合前面所有词语的信息吗?
  • Java项目集成Log4j2全攻略
  • 速卖通跨境运营破局:亚矩阵云手机如何用“本地化黑科技”撬动俄罗斯市场25%客单价增长
  • 今日行情明日机会——20250709
  • 伪装计算器软件,隐藏手机隐私文件
  • 3.常⽤控件
  • jmeter做跨线程组
  • 第二章:创建登录页面
  • 函数-3-日期函数
  • Java垃圾收集机制Test1
  • css 设置 input 插入光标样式
  • OpenCV图片操作100例:从入门到精通指南(2)