Spring Boot 管理系统项目解读
目录
预备知识:
项目中很重要的注解:
什么是Token(JWT) ?:
mapper层的作用:
Service层的作用:
Controller 层的作用:
全局搜索文件:
项目整体概述
后端技术栈简介:
框架层面
工具类与配置
数据库连接池简介:
前端技术栈简介:
数据库技术:
Utils包(工具包)
utils 包中各个类简述:
utils 包中各个类详述:
CreateVerifiCodeImage创建图片验证码:
MD5加密:
Result 类:
状态码定义:
mapper包(数据映射器)
service 包(服务层):
AdminService接口定义:
AdminServiceImpl的实现:
Controller 层(控制层)
类前注解作用:
分页查询方法:
删除方法:
登录校验:
验证码:
整体逻辑:
后端如何生成验证码并传给前端:
补充知识 Httpsession会话:
后端如何校验验证码:
登录信息校验:
用户类型的定义:
具体登录校验:
上传头像:
修改用户密码:
Configure(配置):
前端单页面应用架构:
Vue Router 实现
Webpack:
菜单显示:
预备知识:
项目中很重要的注解:
@Service
:这是 Spring 框架中的一个注解,用于标记该类是一个服务层组件。Spring 会自动扫描带有@Service
注解的类,并将其注册为 Spring 容器中的一个 Bean,以便在其他组件中可以通过依赖注入的方式使用。@RestController
是一个组合注解,它等价于@Controller
和@ResponseBody
的组合。@Controller:
表明这个类是一个控制器,会处理 HTTP 请求;@ResponseBody
则意味着控制器方法返回的对象会自动被序列化为 JSON 格式的数据,然后作为 HTTP 响应的主体返回给客户端。-
@Resource
注解将 定义接口的实现类注入到 所要用到的类中。 -
@Override
:这是一个 Java 注解,用于标记该方法是重写父类或接口中的方法。在实现接口时,使用@Override
可以让编译器检查该方法是否确实是重写了接口中的方法,避免方法名拼写错误等问题。 - @Data:Lombok库提供的注解,用于自动为类生成
getter
、setter
、equals
、hashCode
、toString
等方法。 @Api:使用了
Swagger 注解进行一些说明(类似注释的功能),如@Api(tags = "管理员控制层")
:使用 ,用于在 API 文档中对该控制器进行分类,这里表示该控制器处理管理员相关的接口。
什么是Token(JWT) ?:
客户端在登录成功后,服务器生成并返回给客户端一个token,后续客户端每次请求时都会在请求头中携带这个 token,服务器通过解析 token 来获取用户的身份信息(如用户 ID 和用户名),进而进行权限验证和业务逻辑处理.Token 不仅包含身份信息,还可以包含关于用户角色、权限等信息。项目里使用的就是一种Token 的常见类型 —— JWT(JSON Web Token)。
对JWT的介绍:
JWT 是一种开放标准,用于在网络应用环境中传递声明。JWT 包含三部分:Header(头部)、Payload(载荷)、Signature(签名)。
头部中包含 Token 的类型(即 JWT)和签名算法(如 HMAC SHA256 或 RSA)。
(载荷):包含用户信息和其他元数据。JWT 中的 Payload 是 Base64Url 编码的,因此可以直接解码查看,但不应存储敏感信息。
Signature(签名):签名用于验证 Token 是否被篡改。服务器使用 Header 和 Payload 部分以及密钥生成签名。
mapper层的作用:
mapper意为数据映射器,BaseMapper
是 MyBatis-Plus 框架提供的一个基础接口,它已经封装了很多常用的数据库操作方法,例如插入、删除、更新和查询等。当你的 Mapper 接口继承 BaseMapper<T>
时(这里的 T
是对应的实体类,如 Grade
、Admin
等),就可以直接使用这些通用的方法,而无需在 Mapper 接口中重复定义。)
Service层的作用:
service
包下的文件主要定义了服务层接口和实现类,服务层负责处理业务逻辑,它依赖于 mapper
层进行数据库操作,同时为 controller
层提供服务。
Controller 层的作用:
Controller 层是 Web 应用程序与外部世界交互的入口,负责接收来自客户端(如浏览器、移动应用等)的 HTTP 请求。
全局搜索文件:
- 快捷键:
Ctrl + Shift + R
- 使用场景:用于快速查找项目中的文件。按下后在弹出的搜索框中输入文件名或部分文件名进行搜索。
项目整体概述
smart_campus
是一款功能全面的校园管理系统项目。该系统涵盖了用户认证、信息管理等核心功能,能够对校园内的管理员、学生、教师、班级、年级等重要信息进行集中管理和维护。
后端技术栈简介:
框架层面
- Spring Boot:项目的核心框架。
- MyBatis-Plus:简化数据库操作。
工具类与配置
- Swagger:API 文档生成工具,可以根据项目中的代码自动生成详细的 API 文档。
- Lombok:Lombok 是一个 Java 库,它通过注解的方式简化了 Java 代码的编写。
- JJWT:JJWT(Java JWT)是一个用于创建和验证 JWT 的 Java 库。
数据库连接池简介:
- HikariCP:在
application.yml
配置文件中,使用 HikariCP 作为数据库连接池。HikariCP 。
前端技术栈简介:
- Vue.js:渐进式 JavaScript 框架。结合 Vue Router 实现路由管理,Vuex 实现状态管理,可以构建出复杂而又易于维护的前端应用。
- Axios:基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。
数据库技术:
- MySQL:使用 MySQL 作为关系型数据库管理系统。
Utils包(工具包)
包文件如下图:
utils 包中各个类简述:
1. CreateVerifiCodeImage.java:用于创建验证码图片,为系统提供验证码生成功能。
2. AuthContextHolder.java:用于保存和获取认证相关的上下文信息,方便在系统中传递和使用认证数据。
3. JwtHelper.java:处理JWT 的工具类,提供生成、解析、验证和刷新 JWT 的功能。
4. MD5.java:用于对数据进行 MD5 加密,增强数据的安全性,例如在用户密码存储时使用。
5. ResultCodeEnum.java:统一返回结果状态信息类,定义系统中各种操作结果的状态码和对应的消息描述。
6. Result.java:全局统一返回结果类,封装返回码、返回消息和返回数据,方便前后端交互时统一数据格式。
utils 包中各个类详述:
CreateVerifiCodeImage创建图片验证码:
在getVerifiCodeImage创建并返回包含验证码的图片,先初始化图片,再生成验证码(调用generateCheckCode,随机生成 4 位包含数字、大小写字母的验证码)、绘制背景和验证码(调用drawRands,把验证码绘制到图片上,每个字符设置随机颜色。 ),drawBackground绘制白色背景并添加 200 个随机颜色的干扰点。
注意:图片应创为字符流形式的。
MD5加密:
MD5是对用户登录密码进行加密的类,当新增用户(如管理员、学生、教师)时,会使用 MD5
工具类对用户输入的密码进行加密,再将加密后的密码存储到数据库中,确保密码以密文形式保存。当id==null,即新注册的一个账号时,会对该账号进行MD5加密。
例如在控制端Admincontroller中的使用如下代码,在StudentController,TeacherController的使用也如此。
登录时,系统会将用户输入的密码进行 MD5
加密,然后与数据库中存储的加密密码进行比对,代码如下。
用户修改密码时,系统也会对用户输入的新密码进行 MD5
加密,然后更新到数据库中。
Result
类:
在项目中起到了统一接口响应格式、便于状态判断、
在Admincontroller中有多个接口方法运用了 Result
类来返回统一的结果。这些接口方法涵盖了查询管理员信息、删除管理员信息以及保存或更新管理员信息等操作。
每个方法操作完成后返回Result.ok()。有的操作会携带额外的数据,如查询操作携带页面。
(补充:为什么需要统一接口?:前端和后端是独立开发的。为了让前端能够清晰地理解后端接口的处理结果,需要有一个统一的规范来表示不同的情况。状态码就是这个规范的重要组成部分,它使得前端开发人员可以根据状态码快速判断请求的处理结果,而不需要去解析复杂的返回信息。)
状态码定义:
部分状态码借鉴了 HTTP 状态码的通用标准,例如 SUCCESS(200, "成功")
。
对于自定义状态码,你可以自行划分范围。例如,你可以把 200 - 299 范围用于通用业务状态,300 - 399 用于用户相关业务状态,400 - 499 用于系统错误状态等。
mapper包(数据映射器)
包文件如下图:
mapper
包下的接口文件和对应的 xml文件共同构成了 MyBatis-Plus 框架下的数据访问层,作用是与数据库进行交互,执行对数据库表的增删改查操作。
每个mapper接口都继承了BaseMapper,除了ClazzMapper提供额外的查询方法,其余mapper类只使用基类中已有的方法,如下代码。
在resources
目录下的 mapper
文件夹存放的是 MyBatis 的对应的 Mapper XML 文件,这些文件通过定义 SQL 语句和对象关系映射,让我们更方便地操作数据库。每个 XML 文件对应一个 上面提到的Mapper 接口,通过命名空间进行关联。
如下,在AdminMapper.xml通过命名空间进行关联AdminMapper。
column="name"
意味着从数据库表 tb_grade
里取出 name
列的值,然后将其赋值给 Grade
类的 name
属性(property="name")。 jdbcType
属性:该属性明确了数据库列的数据类型。
<sql id="Base_Column_List">
定义了一个名为 Base_Column_List
的 SQL 片段,该片段包含了数据库表 tb_grade
中常用的列名。在其他 SQL 语句里,可以通过引用这个片段来避免重复书写这些列名。
service
包(服务层):
包中文件结构如下:
AdminService接口定义:
service包下的接口都继承自IService<T> (MyBatis-Plus 提供的通用服务接口,它封装了许多常用的数据库操作方法,如增删改查等)。
在Service接口中,除了Clazz和Grade外,其他接口(即管理员,教师,学生)都定义了两个自定义方法,如:
selectAdminByNameAndPassword
:根据用户名和密码查询管理员信息,用于管理员登录验证。(将查询结果以Admin
对象的形式返回)selectAdminById
:根据管理员的 ID 查询管理员信息,用于展示管理员详情等场景。
教师学生接口里也是如此添加上述方法。
在上述的AdminService接口中定义了selectAdminByNameAndPassword方法,在下面的AdminServiceImpl实现类的方法中进行了具体实现。
AdminServiceImpl的实现:
详细讲讲这个类的实现:
AdminServiceImpl 继承 ServiceImpl,前面提到,mapper定义了一些数据库操作,<
AdminMapper,Admin>
这里 AdminMapper
是对应的 Mapper 接口,Admin
是实体类。
对selectAdminByNameAndPassword
方法的详细解释如下:
QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
:QueryWrapper
是 MyBatis-Plus 提供的一个查询条件构造器,用于构建 SQL 查询条件。这里创建了一个 QueryWrapper
对象,用于构造查询管理员的条件。
queryWrapper.eq("name", username).eq("password", password);:
eq
方法用于添加等于条件。这里添加了两个条件,分别是 name
字段等于传入的 username
,password
字段等于传入的 password。类似于在数据库中的WHERE查询条件,如下:
SELECT * FROM tb_clazz WHERE user_name = #{userName}
return adminMapper.selectOne(queryWrapper);
:调用 AdminMapper
接口的 selectOne
方法(在BaseMapper中),根据构造好的查询条件查询一条管理员记录。如果查询结果存在,则返回一个 Admin
对象;如果查询结果为空,则返回 null
。
在 SystemController
类的 login
方法中,就可以使用AdminService提供的selectAdminByNameAndPassword
方法进行管理员登录验证:
前面提到过,在Service实现类中还定义了按ID查询的方法,这里的ID具体来说生成如下:
在 Admin
类里,id
字段使用了 @TableId
注解,并且 type
属性被设置成 IdType.AUTO
:(IdType.AUTO
表示使用数据库的自增策略来生成 id
)。
pojo
文件夹下的这些类主要用于数据的封装和传递,在项目的不同层之间起到了数据载体的作用。它们与数据库表相对应。
Admin类前的这些注解的作用:
@TableName(value ="tb_admin")
:MyBatis-Plus 注解,指定该实体类对应的数据库表名为tb_admin
。@Data
:Lombok 注解,自动生成getter
、setter
、toString
、equals
和hashCode
方法,减少样板代码。@AllArgsConstructor
:Lombok 注解,自动生成包含所有字段的构造函数。@NoArgsConstructor
:Lombok 注解,自动生成无参构造函数。
implements Serializable
:实现Serializable
接口,表明该类的对象可以被序列化。(序列化简单来说就是将对象转变为一串字节流便于存储以及在网络上传输)。
@TableId(type = IdType.AUTO)
:MyBatis-Plus 注解,指定该字段为主键,IdType.AUTO
表示主键自增。
@TableField(exist = false)
:MyBatis-Plus 注解,表明该字段在数据库表中不存在,只是 Java 类中的一个属性。private static final long serialVersionUID = 1L;
:定义序列化版本号,用于在反序列化时验证对象的版本一致性。
注意: 我们将登录信息也封装到一个类 LoginForm
类中。
Controller 层(控制层)
类前注解作用:
以AdminController为例,讲讲:
AdminController类前注解的作用:
@RestController
:表明这是一个 RESTful 风格的控制器,会将方法的返回值自动转换为 JSON 格式。@RequestMapping("/sms/adminController")
:设置该控制器处理的请求的基础路径。
分页查询方法:
查询请求主要把参数附加到 URL 上,而不是放在请求体中。在这个方法里,pn
和 pageSize
是通过路径变量(@PathVariable
)传递的,adminName
是通过查询参数传递的。
@ApiOperation("查询管理员信息,分页带条件")
:使用 Swagger 注解,描述该方法的功能。@GetMapping("/getAllAdmin/{pn}/{pageSize}")
:表示该方法处理 HTTP GET 请求,请求路径为/sms/adminController/getAllAdmin/{pn}/{pageSize}
,其中{pn}
和{pageSize}
是路径变量。@PathVariable
:用于获取路径变量的值。@ApiParam
:使用 Swagger 注解,描述参数的含义。StrUtil.isNotBlank(adminName)
:判断adminName
是否不为空。如果不为空,则使用like
方法进行模糊查询。orderByDesc(Admin::getId)
:按照管理员的 ID 降序排序。adminService.page
:调用AdminService
的page
方法进行分页查询。(page
方法是IService
接口自带的,page
方法的作用是根据分页信息和查询条件进行分页查询,并返回一个Page
对象)Result.ok(page)
:将查询结果封装到Result
对象中,并返回成功响应。
删除方法:
和前面分页查询方法逻辑基本一致。客户端发起的 DELETE 请求的请求体中应该包含一个整数列表,表示要删除的管理员的 ID 集合。Spring 会将请求体中的 JSON 数组解析为 List<Integer>
类型的 ids
参数。
@ApiOperation("单条记录或者批量删除管理员信息")
:使用 Swagger 注解,描述该方法的功能。@DeleteMapping("/deleteAdmin")
:表示该方法处理 HTTP DELETE 请求,请求路径为/sms/adminController/deleteAdmin
。@RequestBody
:用于获取请求体中的数据,这里接收一个包含管理员 ID 的列表。ids.size() == 1
:判断列表中是否只有一个元素。如果是,则调用adminService.removeById
方法删除单条记录;否则,调用adminService.removeBatchByIds
方法批量删除(这些page
方法也是IService
接口自带的)Result.ok()
:返回成功响应。
登录校验:
验证码:
整体逻辑:
用户打开登录界面,前端发送一个获取验证码图片的请求,后端接受请求生成验证码图片并把验证码的值储存在HttpSession里面,将验证码图片传给前端显示在页面上,用户输入验证码及其他登录信息,点击登录,将这个登录信息封装到LoginForm对象中,返回给后端,后端会比较之前在HttpSession中储存的验证码值和LoginForm中用户输入值。如果验证成功,就会根据用户ID和对应的用户类型生成到JWT Token,并将其封装在 Result
对象中返回给前端,前端接收到服务器的响应后,根据登录结果进行相应的处理,如跳转到首页或显示错误信息。
后端如何生成验证码并传给前端:
在SystemController中通过调用在工具类中定义的生成验证码图片类(CreateVerifiCodeImage)中的方法,生成验证码后,再通过ImageIO.write
方法将生成的 BufferedImage
对象以 JPG 格式写入 HttpServletResponse
的输出流,以字节流的形式将图片传输给前端,代码如下。
补充知识 Httpsession会话:
session
:这是HttpSession
类的一个实例,它代表着服务器与客户端之间的一次会话。在用户发起请求时,服务器会为每个用户创建一个唯一的会话,通过这个会话,服务器能够在多个请求之间跟踪用户的状态。setAttribute
:这是HttpSession
类的一个方法,其作用是把一个对象存储到会话里。该方法接收两个参数,第一个参数是一个字符串类型的键,用于标识存储的对象;第二个参数是要存储的对象。
后端如何校验验证码:
后端(SystemController)从前端传递的LoginForm
对象中获取用户输入的验证码,再从HttpSession
中获取之前生成并存储的验证码。判断存储在会话中的验证码是否为空或已过期。比较用户输入的验证码和存储的验证码是否一致(忽略大小写),如果验证码校验通过,从会话中销毁存储的验证码,防止重复使用,验证码不通过直接封装在 Result
对象中返回给前端,代码如下。
(注:本项目中并没有设置验证码的过期时间,这里验证过期也是判断验证码是否为空,通过HttpSession
有默认的过期时间,当会话过期时,存储在会话中的所有属性包括验证码都会被清除。)
登录信息校验:
验证码校验成功后,获取用户类型(学生,教师,管理员),根据不同的用户类型校验密码等。
用户类型的定义:
用户类型在前端js中定义,userType == 1
代表 “管理员”。userType == 2
代表 “学生”。userType == 3
代表 “教师”。
代码如下:
具体登录校验:
以校验管理员为例:
当用户类型为管理员(userType == 1
)时,根据用户输入的用户名和加密后的密码查询管理员信息,若未查到则返回登录失败提示,若查到则生成包含管理员 ID 和用户类型的 JWT 令牌并放入结果映射中。
具体校验过程:
调用 adminService
的 selectAdminByNameAndPassword
方法,根据用户输入的用户名和经过 MD5 加密后的密码在数据库中查询对应的管理员信息。这里对密码进行 MD5 加密是为了与数据库中存储的加密密码进行匹配,确保数据安全。如果查询结果 admin
为 null
,说明数据库中不存在与用户输入的账号和密码匹配的管理员记录,此时返回一个失败结果给前端,提示 “账号或密码有误”。若查询到对应的管理员信息,说明登录成功。调用 JwtHelper
工具类的 createToken
方法,将管理员的 ID 和用户类型作为参数传入,生成一个 JWT令牌。这个令牌包含了用户的身份信息,后续前端可以使用这个令牌进行身份验证和权限控制。最后将生成的令牌存储在 map
中,以便将其作为响应数据返回给前端。
以后前端每次发起请求,后端会将请求头中的token解析成id和用户类型 并根据id和类型查询用户信息 将信息和用户类型数据一起返回。
解析成功,根据用户类型,调用相应的服务方法(如adminService.selectAdminById
)从数据库中查询用户信息,如下代码。
上传头像:
用于接收客户端上传的头像文件,为文件生成唯一的文件名,将文件保存到指定的本地路径,并返回文件的相对路径给客户端。
修改用户密码:
修改用户密码,先验证请求头中 token
是否过期,若未过期则解析出用户 ID 和用户类型,根据用户类型查询对应用户信息,校验原密码,若原密码正确则将新密码加密后更新到数据库,最后返回操作成功结果。
Configure(配置):
1. SpringMvcConfigure.java:配置 Spring MVC,将 /upload/路径映射到配置文件中指定的文件存储路径,解决附件上传后需重新部署才能访问文件的问题。
2. Swagger2Config.java:配置 Swagger 2,生成项目 API 文档,设置分组、文档信息、请求头参数,扫描 `com.atguigu.campus.controller` 包下的控制器接口。
3. MybatisPlusConfigure.java:配置 MyBatis-Plus,添加分页插件并指定数据库类型为 MySQL,同时扫描 com.atguigu.campus.mapper 包下的 Mapper 接口。
前端单页面应用架构:
单页面应用(SPA)技术实现不同用户类型展示不同内容,下面详细介绍其实现方式:
系统使用单页面应用架构,index.html
作为唯一的 HTML 页面入口,所有内容都在这个页面上动态加载和切换。这种架构下,用户在操作过程中不会进行整页刷新,而是通过 JavaScript 动态更新页面内容。
index.html
作为入口页面,设置了页面的基本信息、预加载并引入所需的 CSS 和 JavaScript 资源,提示用户启用 JavaScript,提供 Vue 应用挂载点,还包含 Webpack 运行时代码用于动态加载和管理代码块,确保系统能正常运行。
Vue Router 实现
动态加载组件:前端路由可以根据不同的路径动态加载相应的组件。不同用户类型登录后,会被导向不同的路由,从而加载不同的组件,展示不同的页面内容。
Webpack:
通过 Webpack 进行代码分割和动态加载。文件中的 Webpack 运行时代码定义了多个函数,用于处理模块加载和代码分割,如 i.e
函数用于动态加载 JavaScript 和 CSS 代码块。
这些功能模块可能被分割成不同的代码块。当用户访问某个需要特定功能的页面时,系统会通过 i.e
函数动态加载相应的代码块。
注意:系统会根据用户类型动态加载不同的功能模块,但并不意味着每个用户类型只对应一个 JS 文件。一个用户类型可能需要多个 JavaScript 文件来实现不同的功能,而不同用户类型之间也可能共享一些通用的 JavaScript 文件。
菜单显示:
前端界面动态生成菜单和按钮,根据用户的角色和权限,系统可以从该文件中筛选出用户有权限访问的菜单和操作按钮,从而实现权限控制。
在 permission.json
文件中定义了用户有权限访问的菜单和按钮。前端获取到菜单数据后,需要根据用户权限进行过滤,只显示用户有权限访问的菜单和按钮。