图书管理系统(5)强制登陆(后端实现)
文章目录
- 图书管理系统(5)强制登陆(后端实现)
-
- 观前提醒:
-
- 无Mybatis版本获取:
- [基于 Mybatis版本 的获取:](#基于 Mybatis版本 的获取:)
- 目录结构:
- 个人建议:
- [1. 强制登陆](#1. 强制登陆)
-
-
- [实现思路分析 :](#实现思路分析 :)
- Cookie,Session,SessionID之间的相互工作机制:
- [1.1 各种代码的封装:](#1.1 各种代码的封装:)
- [1.2 图书列表接口代码编写:](#1.2 图书列表接口代码编写:)
-
- [枚举类型(封装 code):](#枚举类型(封装 code):)
- 正常返回图书列表信息:
- 代码改进:代码封装
- [完整的 图书列表接口:](#完整的 图书列表接口:)
- [1.3 强制登录功能测试:](#1.3 强制登录功能测试:)
- [1.4 阶段总结(代码封装):](#1.4 阶段总结(代码封装):)
-
- 强制登录功能,封装的整体思路:
- [代码封装 的优点:](#代码封装 的优点:)
-
- [2. 后端其他接口的强制登陆:](#2. 后端其他接口的强制登陆:)
- [3. 总结:](#3. 总结:)
观前提醒:
这个图书管理系统,非常的简陋,仅作为练习使用。不建议大家使用我介绍的 图书管理系统 ,去作为 课程设计。
如果你是第一次点击这篇博客的,需要你将我 图书管理系统 的博客列表中,从这篇开始看:
图书管理系统(1)项目准备,用户登录接口,添加图书接口
无Mybatis版本获取:
这个 图书管理系统 的实现,需要你从我的 gitee上,将 无Mybatis版本 的图书管理系统源代码下载下来。
gitee链接:https://gitee.com/mrbgvhbhjv/java-ee-course/tree/master/后端代码/springboot_bookManage_System

基于 Mybatis版本 的获取:
如果你想直接获取 基于 Mybatis 完全实现了增删查改功能的图书管理系统,就从我的 gitee 上面获取,下载源代码。
gitee链接:https://gitee.com/mrbgvhbhjv/java-ee-course/tree/master/后端代码/Book_System_20251107

目录结构:
以防大家会乱,或者我博客写的不详细,各种类的存放位置,我放个目录结构在这里:


个人建议:
这篇博客的代码,建议自己敲一下。
跟着我的步骤,使用 无Mybatis版本 的图书管理系统源代码 ,一步一步的将代码实现出来。
这里使用的数据库是 MySQL。
图形化工具:Navicat
虽然,截止到 2026年,cursor,TRAE等 AI工具,能够一键编写代码,甚至一个系统。
但是,如果你不会基础知识,只会让 ai 生成代码,不会看代码,不会修改代码,代码运行报错,你不会解决问题等等。其实,你都不算是一个 Java开发者。
如果我们将 AI工具,对我们的工作效率提升,有巨大的帮助,它一定是一个乘法结算的结果。
你的基础开发能力 × AI工具效率 == 工作效率。
如果你的基础开发能力不行,约等于 0,那么,无论 AI工具效率多高,99倍也好,0 * 99 == 0
所以,只有将我们自己的基础开发能力提升了,使用 AI工具提升效率,才是事半功倍的效果。
1. 强制登陆
虽然我们做了用户登录,但是我们发现,用户不登录,依然可以操作图书。
这是有极大风险的。 所以我们需要进行强制登录。
如果用户未登录就访问图书列表或者添加图书等页面,强制跳转到登录页面。
实现思路分析 :
用户登录时,我们已经把登录用户的信息存储在了Session中。 那就可以通过Session中的信息来判断用户都是登录。
- 如果Session中可以取到登录用户的信息,说明用户已经登录了,可以进行后续操作
- 如果Session中取不到登录用户的信息,说明用户未登录,则跳转到登录页面。
Cookie,Session,SessionID之间的相互工作机制:

1.1 各种代码的封装:
在图书列表展示页面,进行登录的验证:
-
首先先获取 Session对象,根据 Session对象,获取 UserInfo对象
-
新建一个常量类(Constant),登录接口处,存储的Session中的 Key,设置为 常量(常量名称,统统大写)


-
判断错误情况:
判断 UserInfo对象,是否为空
判断 后端 是否出现问题,如:UserInfo对象,某些字段是否为空,例如 id为空
java
@RequestMapping(value = "/getListByPage")
public ResponseResult<BookInfo> getListByPage(PageRequest pageRequest, HttpSession httpSession){
// 1.参数校验
// 2.返回结果
// 通过 Session 的 key,获取 Session中的UserInfo对象
UserInfo userInfo = (UserInfo)httpSession.getAttribute(Constants.SESSION_USER_KEY);
// 判断 UserInfo对象,是否为空,是否因为后端原因,导致 UserInfo对象的 id 出现异常,<=0 的情况
if (userInfo == null || userInfo.getId() <= 0){
//用户未登录
}
return bookService.getListByPage(pageRequest);
}
返回的数据:
判断完之后,我们应该要返回什么样的数据?
返回 ResponseResult<BookInfo >,前端没办法确认用户是否登录了。
并且后端返回数据为空时, 前端也无法确认是后端无数据, 还是后端出错了。
解决办法是:
Http协议中,我们学习过状态码。
我们可以效仿状态码,采用 业务状态码 的方式,以及对应的状态信息,告诉前端。
业务状态码 和 HTTP的状态码:
业务状态码 能够使用(存在)的前提:是 HTTP的请求 成功的情况下。
我们可以规定,什么样的状态码,对应什么样的状态信息,前端就可以根据状态码,分析状态信息,作出处理了。
假定:
code=-1,errMsg="用户未登录"
code=-2,errMsg="后端出错了"
code=200,errMsg="后端正常响应"
状态码,状态信息:
做法是,在 ResponseResult<T>类,设置 状态码属性,状态信息属性:

因为我们的验证登录,不仅仅是图书列表接口,还有新增图书接口,修改图书接口等。
每个接口的返回值,都不一样:
图书列表接口:ResponseResult<BookInfo >
修改图书接口:String
......
实际上,每个接口,都需要 code 和 errMsg 这两个属性,告诉前端,接口的返回结果是什么。
索性,我们就不在 ResponseResult<T > 这个类中,去写 code 和 errMsg 这两个属性。
我们对于每个接口的返回类型,再次封装成另一个类,专门返回 code 和 errMsg 这两个属性,还有数据 data。
Result<T>类(作为接口的返回值):
代码:
java
import lombok.Data;
@Data
public class Result<T> {
// 返回给前端的业务信息:
private int code;
private String errMsg;
private T data;
}
这个类,就定义 code 和 errMsg 这两个属性,还有数据 data,后续作为接口返回类型,返回 状态码code ,状态信息errMsg ,后端数据data。
每个接口,返回的类型不同,返回的后端数据 data 也不同,我们可以使用泛型,构造一个泛型类:Result<T>
接口返回值,就是这个泛型类(Result<T>),T 可以接收不同类型的返回数据,data属性,会根据 T,动态设置为不同类型的后端数据。
接下来,就以 图书列表接口,作为演示,应该怎么使用 Result<T>类,返回后端数据,以及接口的处理结果(状态码,状态信息)
1.2 图书列表接口代码编写:
判断代码:
java
// 判断 UserInfo对象,是否为空,是否因为后端原因,导致 UserInfo对象的 id 出现异常,<=0 的情况
if (userInfo == null || userInfo.getId() <= 0){
//用户未登录
Result result = new Result();
result.setCode();
result.setErrMsg();
result.setData();
}
我们的code,是这么设置的:
code=-1,errMsg="用户未登录"
code=-2,errMsg="后端出错了"
code=200,errMsg="后端正常响应"
我们可以使用枚举类型,将这三种 code ,设置为固定值,映射起来。
后续维护代码的时候,如果状态码和状态信息,有新的需求,我们就只用改 枚举类 的代码,Controller层的代码,就不需要一个一个的去修改了。
枚举类型(封装 code):

java
public enum ResultCodeEnum {
UNLOGIN(-1),
ERROE(-2),
SUCCESS(200);
private int code;
private ResultCodeEnum(int code) {
this.code = code;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
相应的,我们 Result对象,设置 code 的时候,是从 ResultCodeEnum类 中的变量,获取 code 的。(使用枚举类中的 get方法)
也就是说,Result对象,设置 code ,获取的是枚举类型中的变量中存放的 code 。
判断代码:
java
// 判断 UserInfo对象,是否为空,是否因为后端原因,导致 UserInfo对象的 id 出现异常,<=0 的情况
if (userInfo == null || userInfo.getId() <= 0){
//用户未登录
Result result = new Result();
result.setCode(ResultCodeEnum.UNLOGIN.getCode());
result.setErrMsg("用户未登录");
result.setData(null);
return result;
}
正常返回图书列表信息:
用户登录成功,状态信息(errMsg)是 后端正常响应(根据接口文档,返回 "" 就行),状态码(code)200
返回的数据(data)是:图书列表信息
图书列表接口完整代码:
java
@RequestMapping(value = "/getListByPage")
public Result<ResponseResult<BookInfo>> getListByPage(PageRequest pageRequest, HttpSession httpSession){
// 1.参数校验
// 2.返回结果
// 通过 Session 的 key,获取 Session中的UserInfo对象
UserInfo userInfo = (UserInfo)httpSession.getAttribute(Constants.SESSION_USER_KEY);
// 判断 UserInfo对象,是否为空,是否因为后端原因,导致 UserInfo对象的 id 出现异常,<=0 的情况
if (userInfo == null || userInfo.getId() <= 0){
//用户未登录
Result result = new Result();
result.setCode(ResultCodeEnum.UNLOGIN.getCode());
result.setErrMsg("用户未登录");
result.setData(null);
return result;
}
// 用户登录成功,状态信息是 后端正常响应,状态码200
// 返回的数据(data)是:图书列表信息
ResponseResult<BookInfo> listByPage = bookService.getListByPage(pageRequest);
Result result = new Result();
result.setCode(ResultCodeEnum.SUCCESS.getCode());
result.setErrMsg("");
result.setData(listByPage);
return result;
}
代码改进:代码封装
但是,还是有能够改进的地方,Result<T> 设置属性的时候,代码有点冗余了,有点重复了。
我们可以将这些重复的代码,在 Result<T> 类中,封装成静态方法 ,我们只需要传值,在 Result<T> 类中,就可以帮我们设置状态码,状态信息,返回的后端数据了。
方法封装后的 Result<T> 类:
java
import lombok.Data;
import org.example.book_system_20251107.book.enums.ResultCodeEnum;
@Data
public class Result<T> {
// 返回给前端的业务信息:
private int code;
private String errMsg;
private T data;
public static Result unLogin(){
Result result = new Result();
result.setCode(ResultCodeEnum.UNLOGIN.getCode());
result.setErrMsg("用户未登录");
return result;
}
public static <T> Result success(T data){
Result result = new Result();
result.setCode(ResultCodeEnum.SUCCESS.getCode());
result.setErrMsg("");
result.setData(data);
return result;
}
public static Result fail(String errMsg){
Result result = new Result();
result.setCode(ResultCodeEnum.FAIL.getCode());
result.setErrMsg(errMsg);
return result;
}
}
封装了三个方法:
-
静态 unLogin 方法,调用该方法,表示用户未登录,设置 Result
-
静态 success 泛型方法,调用该方法,表示后端接口响应正常,传入 data(返回给前端的后端数据),设置 Result
注意:泛型方法,必须在 static 后面,再次声明泛型。
-
静态 fail 方法,调用该方法,表示后端错误,设置 Result
接下来,将 图书列表接口 中,重复性的代码,替换成 Result<T > 类中定义的方法:

完整的 图书列表接口:
替换之后,完整的图书列表接口代码:
java
@RequestMapping(value = "/getListByPage")
public Result<ResponseResult<BookInfo>> getListByPage(PageRequest pageRequest, HttpSession httpSession){
// 1.参数校验
// 2.返回结果
// 通过 Session 的 key,获取 Session中的UserInfo对象
UserInfo userInfo = (UserInfo)httpSession.getAttribute(Constants.SESSION_USER_KEY);
// 判断 UserInfo对象,是否为空,是否因为后端原因,导致 UserInfo对象的 id 出现异常,<=0 的情况
if (userInfo == null || userInfo.getId() <= 0){
//用户未登录
return Result.unLogin();
}
// 用户登录成功,状态信息是 后端正常响应,状态码200
// 返回的数据(data)是:图书列表信息
ResponseResult<BookInfo> listByPage = bookService.getListByPage(pageRequest);
return Result.success(listByPage);
}
这样的代码,看起来就简洁了非常多。
代码不多,但是都是在调用方法,调用 Result<T>类,各种封装。
简洁的代码,做的事情,却是非常的多。
1.3 强制登录功能测试:
Postman:
登陆前:
url:http://127.0.0.1:8080/book/getListByPage?currentPage=1

登录:
url:http://127.0.0.1:8080/user/login?name=admin&password=admin

登陆后:
url:http://127.0.0.1:8080/book/getListByPage?currentPage=1

网页:
登陆前:

登录:

登陆后:

1.4 阶段总结(代码封装):
上述代码,我们在逐步使用代码封装的操作了。
强制登录功能,封装的整体思路:
我们的整体思路是这样的:
-
开始,我们想给图书列表加一个强制登陆的功能,获取 Session,根据 Session,进行判断。
如果修改常量session的key,后续需要修改,就需要修改所有使用到这个key的地方。
出于高内聚低耦合的思想,减少后续项目代码的维护,修改成本,
我们把常量(存储的Session中的 Key)集中在⼀个类中,设置为 常量(常量名称,统统大写)。
登录接口处,存储的Session中的 Key,新建一个 Constants类,封装在Constants类中, 类中,存储的Session中的 Key,设置为 常量(SESSION_USER_KEY)(常量名称,统统大写)
登录接口代码,登录成功之后,保存 Session 的代码,Key,用 常量(SESSION_USER_KEY)即可
-
判断之后,确认用户没有登录,我们要告诉前端:需要让用户进行登录,才能使用系统
-
应该要返回什么样的信息,告诉前端?
我们可以使用状态码,以及对应的状态信息,告诉前端。
我们可以规定,什么样的状态码,对应什么样的状态信息,前端就可以根据状态码,分析状态信息,作出处理了。
我们的code(状态码),是这么设置的:
code=-1,errMsg="用户未登录"
code=-2,errMsg="后端出错了"
code=200,errMsg="后端正常响应"
-
我们在 ResponseResult类,定义 状态码属性code ,状态信息属性 errMsg。
ResponseResult类,表示的是 分页的图书数据(所有图书的信息数据)
-
但是,每个接口,可能都需要强制登录功能,每个接口,返回值不同,不一定都是ResponseResult类,也有的是 String
-
我们对于每个接口的返回类型 ,再次封装成另一个类(Result),定义 code 和 errMsg 这两个属性,还有数据 data,后续作为接口返回类型,返回 状态码code ,状态信息errMsg ,后端数据data。
每个接口,返回的数据,有的是 所有图书信息,有的是一个String(错误信息),有的是一个 Integer(图书修改之后,影响的行数)......
不同的接口,返回的数据,都是不同的,使用 Result<T>,需要注意到每个接口,还有数据需要返回的。
-
code,有三种不同的状态码,我们可以使用枚举类型,对 code,进行封装 ,ResultCodeEnum类,设置 get方法,能够获取枚举变
量中的 code 的就行
-
根据不同的情况,设置不同的 Result对象:创建 Result对象,设置属性,作为接口的返回值,返回 Result。
这几步操纵,不同的情况,代码都是这么些代码,我们可以将这些重复的代码,在 Result 类中,封装成静态方法。
我们只需要传值,在 Result<T> 类中,就可以帮我们设置状态码,状态信息,返回的后端数据了。
-
最后,我们只需要调用 Result 类中封装好的 静态方法,就能一键 设置状态码code ,状态信息errMsg ,后端数据data
这就是我们 代码封装 的一个整体思路了。
大家也可以把这个当作是一个 代码封装 的一个基础练习。
代码封装 的优点:
后续,我们进行项目开发,都是会大量使用 代码封装 的思想,有这么几个优点:
- 减少代码的重复性,增强代码的简洁程度
- 封装之后的代码,也比较方便进行维护
- 减少编写代码时,修改的难度
2. 后端其他接口的强制登陆:
那么,其他接口的强制登陆功能,我们应该一个一个代码的粘贴复制到其他接口吗?
目前来说:是这样的
但是,我们之后,会学习 统一功能处理,这些重复性的工作,Spring 会提供一个功能,简化这些操作。
所以,目前,我们不对其他接口,实现强制登录功能了。
学完 统一功能 之后,我们会在图书管理系统(7)完善强制登录功能
进一步完善图书管理系统,目前,图书管理系统,暂且告一段落了。
3. 总结:
这里主要学习了两点:
- 首次接触,如何实现强制登录功能,掌握其实现思路
- 第一次在小型项目中,多次对代码,进行封装,认识到代码封装的好处。
最后,如果这篇博客能帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!
下一篇博客:图书管理系统(6)强制登陆前端