图书管理系统(5)强制登陆(后端实现)

图书管理系统(5)强制登陆(后端实现)

文章目录

观前提醒:

这个图书管理系统,非常的简陋,仅作为练习使用。不建议大家使用我介绍的 图书管理系统 ,去作为 课程设计。

如果你是第一次点击这篇博客的,需要你将我 图书管理系统 的博客列表中,从这篇开始看:
图书管理系统(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中的信息来判断用户都是登录。

  1. 如果Session中可以取到登录用户的信息,说明用户已经登录了,可以进行后续操作
  2. 如果Session中取不到登录用户的信息,说明用户未登录,则跳转到登录页面。

Cookie,Session,SessionID之间的相互工作机制:

1.1 各种代码的封装:

在图书列表展示页面,进行登录的验证:

  1. 首先先获取 Session对象,根据 Session对象,获取 UserInfo对象

  2. 新建一个常量类(Constant),登录接口处,存储的Session中的 Key,设置为 常量(常量名称,统统大写)


  3. 判断错误情况:

判断 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;
    }

}

封装了三个方法:

  1. 静态 unLogin 方法,调用该方法,表示用户未登录,设置 Result

  2. 静态 success 泛型方法,调用该方法,表示后端接口响应正常,传入 data(返回给前端的后端数据),设置 Result

    注意:泛型方法,必须在 static 后面,再次声明泛型

  3. 静态 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 阶段总结(代码封装):

上述代码,我们在逐步使用代码封装的操作了。

强制登录功能,封装的整体思路:

我们的整体思路是这样的:

  1. 开始,我们想给图书列表加一个强制登陆的功能,获取 Session,根据 Session,进行判断。

    如果修改常量session的key,后续需要修改,就需要修改所有使用到这个key的地方。

    出于高内聚低耦合的思想,减少后续项目代码的维护,修改成本,

    我们把常量(存储的Session中的 Key)集中在⼀个类中,设置为 常量(常量名称,统统大写)。

    登录接口处,存储的Session中的 Key,新建一个 Constants类,封装在Constants类中, 类中,存储的Session中的 Key,设置为 常量(SESSION_USER_KEY)(常量名称,统统大写)

    登录接口代码,登录成功之后,保存 Session 的代码,Key,用 常量(SESSION_USER_KEY)即可

  2. 判断之后,确认用户没有登录,我们要告诉前端:需要让用户进行登录,才能使用系统

  3. 应该要返回什么样的信息,告诉前端?

    我们可以使用状态码,以及对应的状态信息,告诉前端。

    我们可以规定,什么样的状态码,对应什么样的状态信息,前端就可以根据状态码,分析状态信息,作出处理了。

    我们的code(状态码),是这么设置的:

    code=-1,errMsg="用户未登录"

    code=-2,errMsg="后端出错了"

    code=200,errMsg="后端正常响应"

  4. 我们在 ResponseResult类,定义 状态码属性code ,状态信息属性 errMsg

    ResponseResult类,表示的是 分页的图书数据(所有图书的信息数据)

  5. 但是,每个接口,可能都需要强制登录功能,每个接口,返回值不同,不一定都是ResponseResult类,也有的是 String

  6. 我们对于每个接口的返回类型再次封装成另一个类(Result),定义 code 和 errMsg 这两个属性,还有数据 data,后续作为接口返回类型,返回 状态码code ,状态信息errMsg ,后端数据data。

    每个接口,返回的数据,有的是 所有图书信息,有的是一个String(错误信息),有的是一个 Integer(图书修改之后,影响的行数)......

    不同的接口,返回的数据,都是不同的,使用 Result<T>,需要注意到每个接口,还有数据需要返回的。

  7. code,有三种不同的状态码,我们可以使用枚举类型,对 code,进行封装ResultCodeEnum类,设置 get方法,能够获取枚举变

    量中的 code 的就行

  8. 根据不同的情况,设置不同的 Result对象:创建 Result对象,设置属性,作为接口的返回值,返回 Result。

    这几步操纵,不同的情况,代码都是这么些代码,我们可以将这些重复的代码,在 Result 类中,封装成静态方法

    我们只需要传值,在 Result<T> 类中,就可以帮我们设置状态码,状态信息,返回的后端数据了。

  9. 最后,我们只需要调用 Result 类中封装好的 静态方法,就能一键 设置状态码code ,状态信息errMsg ,后端数据data

这就是我们 代码封装 的一个整体思路了。

大家也可以把这个当作是一个 代码封装 的一个基础练习。

代码封装 的优点:

后续,我们进行项目开发,都是会大量使用 代码封装 的思想,有这么几个优点:

  1. 减少代码的重复性,增强代码的简洁程度
  2. 封装之后的代码,也比较方便进行维护
  3. 减少编写代码时,修改的难度

2. 后端其他接口的强制登陆:

那么,其他接口的强制登陆功能,我们应该一个一个代码的粘贴复制到其他接口吗?

目前来说:是这样的

但是,我们之后,会学习 统一功能处理,这些重复性的工作,Spring 会提供一个功能,简化这些操作。

所以,目前,我们不对其他接口,实现强制登录功能了。

学完 统一功能 之后,我们会在图书管理系统(7)完善强制登录功能

进一步完善图书管理系统,目前,图书管理系统,暂且告一段落了。

3. 总结:

这里主要学习了两点:

  1. 首次接触,如何实现强制登录功能,掌握其实现思路
  2. 第一次在小型项目中,多次对代码,进行封装,认识到代码封装的好处。

最后,如果这篇博客能帮到你的,请你点点赞,有写错了,写的不好的,欢迎评论指出,谢谢!

下一篇博客:图书管理系统(6)强制登陆前端

相关推荐
丹牛Daniel1 小时前
Java解决HV000183: Unable to initialize ‘javax.el.ExpressionFactory‘
java·开发语言·spring boot·tomcat·intellij-idea·个人开发
PHP源码4 小时前
SpringBoot实验室管理系统
spring boot·springboot实验室设备·java实验室预约设备管理·vue实验室预约设备管理系统·前后端分离实验室管理系统
rfidunion5 小时前
springboot+VUE+部署(13。创建多表查询)
vue.js·spring boot·后端
Coder_Boy_7 小时前
Java高级_资深_架构岗 核心知识点(模块三:高并发)
java·spring boot·分布式·面试·架构
Coder_Boy_7 小时前
Java高级_资深_架构岗 核心知识点全解析(模块二:Spring生态 架构岗必备)
java·spring boot·spring·架构
暮色妖娆丶8 小时前
Spring 源码分析 Lifecycle Bean
spring boot·spring·源码
倚肆8 小时前
WebSocket连接教程示例(Spring Boot + STOMP + SockJS + Vue)
vue.js·spring boot·websocket
程序猿零零漆8 小时前
【Spring Boot开发实战手册】掌握Springboot开发技巧和窍门(六)创建菜单和游戏界面(下)
java·spring boot·游戏
GEM的左耳返8 小时前
Java面试深度剖析:从JVM到云原生的技术演进
jvm·spring boot·云原生·中间件·java面试·分布式架构·ai技术