--- title: "00-后端配置和调试" created: 2025-12-02 tags: - 项目 aliases: - 后端配置和调试 --- # 后端配置和调试 ## mysql 下载和配置 ### 常用命令 登入: `mysql -u root -p` ![[image-f66e9fec.png]] 查看数据库: `show databases;` ![[image-ca93a9cc.png]] 创建数据库: `create database dbname;` ![[image-39d06838.png]] 删除数据库:`drop database dbname;` ![[image-a05bfa73.png]] 使用数据库: `use dbname;` ![[image-90d84210.png]] 创建表 `create table tbname(setname typename , setname typename);` ![[image-31a8fde8.png]] (查询当初创建表的sql语句: `show create table tbname;` ) 列出当前数据库的所有表: `show tables;` ![[image-b55a5c82.png]] 在表中插入数据: `insert into tbname values( , , ……);` ![[image-30eb2bf7.png]] 查询表中所有数据 `select * from tbname;` ![[image-26572f74.png]] 删除某行数据 `delete from tbname where ?;` ![[image-96761df4.png]] 删除表 `drop table tbname;` ![[image-17b474fd.png]] #### 更多命令 详见 mysql笔记 ## pom.xml (配置Spring Boot) [Maven仓库地址](https://mvnrepository.com/) [Mybatis-Plus官网](https://baomidou.com/) ### 在pom.xml文件中添加依赖: #### 创建初加 - Spring Boot Starter JDBC - Project Lombok - MySQL Connector/J - mybatis-plus-boot-starter - mybatis-plus-generator 在[Maven仓库地址](https://mvnrepository.com/)中一个一个搜上述依赖 将最新版本的代码拷贝入pom.xml 的标签下 ![[image-b99d1f7e.png]] ![[image-8a49ad18.png]] 点击右方maven 重新加载 ![[image-7301ebf0.png]] 等待下载(挂梯子都要很久 要么就考虑换源 反正人麻了) 作用解释: - `spring-boot-starter-jdbc`: 提供 JDBC 支持,简化数据库访问。 - `lombok`: 用于减少样板代码,自动生成 getters、setters、toString 等方法,简化代码书写。 - `mysql-connector-j`: MySQL 数据库的 JDBC 驱动,允许 Java 应用连接 MySQL 数据库。 - `mybatis-plus-boot-starter`: 为 MyBatis 提供增强功能,简化数据库操作。 - `mybatis-plus-generator`: 代码生成器,自动生成 MyBatis 的 Mapper、Entity 等类。 - `spring-boot-starter-thymeleaf`: 支持 Thymeleaf 模板引擎,用于生成动态网页。 - `spring-boot-starter-web`: 支持创建 RESTful API 和 Web 应用的基础组件。 - `spring-boot-starter-test`: 测试支持库,用于编写和运行测试。 因为被折磨了很久 又是换电脑开发后 jdk环境不支持 又是下载依赖时网络不佳 所以来了解一下这个.xml到底是怎么个事 [[00-关于pom.xml|关于pom.xml]] 这是当前阶段的pom.xml代码 (后面MyBatis有兼容问题 把springboot版本改成了3.11 然后又新引入mybatis 3.5.16 才得以解决 代码没敲两行 全他妈改环境去了 恶心 ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 3.1.1 com.zwnsyw backend 0.0.1-SNAPSHOT backend backend 17 org.springframework.boot spring-boot-starter-jdbc 3.3.5 org.projectlombok lombok 1.18.34 provided com.mysql mysql-connector-j 9.1.0 com.baomidou mybatis-plus-boot-starter 3.5.9 com.baomidou mybatis-plus-generator 3.5.9 org.mybatis.spring.boot mybatis-spring-boot-starter 3.0.3 org.mybatis mybatis 3.5.16 org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin ``` #### 用到再加 还有一些依赖包 后面用到了再导入 不然会引起不必要的麻烦 - spring-boot-starter-security - jjwt-api - jjwt-impl - jjwt-jackson 作用解释: `spring-boot-starter-security` - **作用**: 这是 Spring Boot 的安全启动器,提供了安全功能的基本配置。它集成了 Spring Security,可以帮助你快速实现安全认证和授权。 - **功能**: - 提供基本的身份验证和授权功能。 - 支持用户的角色管理和权限控制。 - 可配置各种安全策略,例如基于表单的登录、HTTP 基本认证等。 - **使用场景**: 当你需要为 Spring Boot 应用添加安全机制时,使用这个依赖可以快速上手。 `jjwt-api` - **作用**: 这是 JJWT(Java JWT)库的 API 部分,专门用于处理 JSON Web Tokens (JWT) 的创建、解析和验证。 - **功能**: - 提供了一套简单的 API 来生成和验证 JWT。 - 支持设置自定义声明、签名算法等。 - **使用场景**: 当你需要在应用中使用 JWT 进行身份认证和信息传递时,使用这个依赖可以简化实现。 `jjwt-impl` - **作用**: 这是 JJWT 的实现部分,提供实际的 JWT 生成和解析功能。 - **功能**: - 实现了 `jjwt-api` 中定义的接口和功能。 - 处理 JWT 的签名、加密和解析。 - **使用场景**: 需要使用 JWT 的完整功能时,`jjwt-impl` 是必需的。 `jjwt-jackson` - **作用**: 这是 JJWT 的 Jackson 支持库,允许你将 JWT 的 JSON 数据与 Jackson 序列化和反序列化结合使用。 - **功能**: - 提供了将 JWT 中的声明(claims)与 Java 对象之间的转换功能。 - 允许你使用 Jackson 的特性(如 JSON 处理)与 JWT 集成。 - **使用场景**: 如果你的应用使用 Jackson 作为 JSON 处理库,并且需要在 JWT 中使用自定义对象,这个依赖会非常有用。 即: - `spring-boot-starter-security` 用于添加安全功能。 - `jjwt-api` 和 `jjwt-impl` 用于处理 JWT。 - `jjwt-jackson` 用于将 JWT 与 Jackson 集成。 这些依赖库通常一起使用,帮助你在 Spring Boot 应用中实现安全认证机制,特别是使用 JWT 进行用户身份验证时。 #### 思考与拓展 貌似有些包基本什么项目都会用得到 可以将常用的依赖包整合在一起 以后创建项目时直接把他们一起写入.xml中 而不用一个一个去官网复制 **整合常用依赖** 可以创建一个“基础模板”项目 包含所有常用依赖 未来新项目可以从这个模板复制 `pom.xml` 还有些推荐的常用依赖 - **JUnit**: `org.junit.jupiter:junit-jupiter` - **Springfox**: 用于生成 Swagger 文档 - **Logback**: 日志处理库 - **Redis**: `spring-boot-starter-data-redis` - **Kafka**: `spring-kafka` ##### pom.xml 模板 包含了常用的 Spring Boot 项目依赖 这些依赖涵盖了数据库访问、模板引擎、安全、测试等功能 后续可以根据需要进行调整和扩展 ```xml 4.0.0 org.springframework.boot spring-boot-starter-parent 3.3.4 com.example your-project-name 0.0.1-SNAPSHOT Your Project Name Your Project Description 17 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-security mysql mysql-connector-j 9.1.0 org.projectlombok lombok 1.18.34 provided com.baomidou mybatis-plus-boot-starter 3.5.9 com.baomidou mybatis-plus-generator 3.5.9 io.jsonwebtoken jjwt-api 0.11.5 io.jsonwebtoken jjwt-impl 0.11.5 io.jsonwebtoken jjwt-jackson 0.11.5 org.springframework.boot spring-boot-starter-test test org.junit.jupiter junit-jupiter 5.8.2 test io.springfox springfox-boot-starter 3.0.0 ch.qos.logback logback-classic org.springframework.boot spring-boot-starter-data-redis org.springframework.kafka spring-kafka org.springframework.boot spring-boot-maven-plugin ``` ### 在application.properties中添加数据库配置: springboot要访问服务器其实也是要root+密码登入的 所以要进行一些配置 ```properties spring.datasource.username=root spring.datasource.password=zw200495 spring.datasource.url=jdbc:mysql://localhost:3306/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ``` ## 四层模块 tips: ![[image-9c5b943b.png]] 包如果成了xxx.xx导致逻辑不清晰 可以如上图设置 取消勾选压缩空的中间软件包 另外了解几个概念 SpringBoot中的常用模块 - pojo层:将数据库中的表对应成Java中的Class - mapper层(也叫Dao层):将pojo层的class中的操作,映射成sql语句 - service层:写具体的业务逻辑,组合使用mapper中的操作 - controller层:负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面 在我们的项目中 一共分为四个页面 做成四个模块 pk、record、ranklist、user 这里我们主要先实现用户的登入功能 所以操作user包 不过可以把其他包也先建好 ![[image-be984d65.png]] ### 调试(以user为例) 先实现一下pojo层 将数据表转变成类 这个转变可以是用工具一键生成(MyBatisPlus) 也可以自己定义类后用Lombok注解 半自动的生成 #### pojo层 这里先实现一下半自动的方法 backend包下新建pojo包 ![[image-467f9b2b.png]] pojo下新建一个user类 用于将kob中user表转变为class ![[image-93c7c1d0.png]] 既然是翻译 (将表翻译为class) 我们就来查看一下表的结构 ![[image-8138c0c5.png]] 由此得到这样一个类的结构 ```java package com.zwnsyw.backend.pojo; public class User { private Integer id; private String username; private String password; } ``` 接下来就是要实现一些基本的操作 get、set 构造函数等等 但这种机械化的东西无需自己写 利用好[[02-lombok注解|lombok注解]]就能迅速解决 ```java package com.zwnsyw.backend.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data //自动生成 getters, setters, toString, equals, hashcode @NoArgsConstructor // 无参构造函数 @AllArgsConstructor // 有参构造函数 public class User { private Integer id; private String username; private String password; } ``` 在target中存放着这几行代码编译后的结果 然后又通过反编译的形式展示给我们看 ![[image-1881d79c.png]] 可以发现 什么构造 初始化一堆乱七八糟的接口 就这样有模有样的写好了 就只加了三个注解 神奇。。。 ![[image-8adcdc9b.png]] #### mapper 层 将类中的操作 映射成sql语句 写入mysql 同样 backend包下创一个mapper包 里面建一个UserMapper **接口 (注意是接口)** ![[image-acdccd2f.png]] 这里我们仅需加上一个@Mapper注解 标志这是一个Mapper接口 因为使用了MyBatis-Plus 本来增删改查需要写很多sql语句 但MyBatis-Plus帮我们做了这些事情 怎么把它做好的东西拿过来呢 很简单——继承 ```java package com.zwnsyw.backend.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.zwnsyw.backend.pojo.User; import org.apache.ibatis.annotations.Mapper; @Mapper //标识这个接口是 MyBatis 的 Mapper public interface UserMapper extends BaseMapper {//继承 MyBatis-Plus 的 BaseMapper 接口 自动获得 CRUD 操作的实现 } ``` - `BaseMapper`: 通过*继承 MyBatis-Plus 的* `BaseMapper` *接口,我们可以自动获得 CRUD 操作的实现*,而不需要手动编写 SQL 语句。 - `@Mapper`: 注解标识这个接口是 MyBatis 的 Mapper,使得 Spring 能够自动扫描并实现这个接口。 如果不加@Mapper的话 可以在BackendApplication中加上一个MapperScan ![[image-2189a6d4.png]] [[03-MyBatis-MyBatis-plus|MyBatis-MyBatis-plus]] #### controller层和service层 原本是还要写层service的 但现在只是调试 就把这俩功能都写在controller里面 在backend、controller、user下创建UserController类 加上注解@RestController [[04-RestController和Controller|RestController和Controller]] ```java package com.zwnsyw.backend.controller.user; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { } ``` 再加上ReqestMapping注解(或具体的 GetMapping PostMapping) [[06-处理http请求的注解|处理http请求的注解]] 这里做个测试 处理get请求 在user目录下返回所有用户 具体的实现逻辑原本是要在service中实现 这里为了方便调试 直接写在controller层中 因为要获取用户 自然要调用mapper中实现的增删改查接口 要调用mapper 就得先定义出来 同时 配套的加上Autowired注解 另外 mapper中的增删改查接口是由MyBatis-plus生成的 所以一些api我们可能不熟悉参数 可以具体去[MyBatis-plus官网](https://baomidou.com/)查 ```java package com.zwnsyw.backend.controller.user; import com.zwnsyw.backend.mapper.UserMapper; import com.zwnsyw.backend.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class UserController { @Autowired UserMapper userMapper; @GetMapping("/user/all/")//在user目录下返回所有用户 public List getAll(){ return userMapper.selectList(null);//查询所有数据 } } ``` ![[e424a9fe80cf22193952d5d0619aff57-e424a9fe.png]] ##### 测试查询 查询某个用户的数据 需要加上注解@PathVariable ```java package com.zwnsyw.backend.controller.user; import com.zwnsyw.backend.mapper.UserMapper; import com.zwnsyw.backend.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController public class UserController { @Autowired UserMapper userMapper; @GetMapping("/user/all/") public List getAll(){ return userMapper.selectList(null); } @GetMapping("/user/{userId}/") public User getUser(@PathVariable int userId){ return userMapper.selectById(userId); } } ``` ![[df53c01f0a1c9fccb192a88e30a97f03-df53c01f.png]] ![[19c1a8ef8f8dfe1dd13735f02c328727-19c1a8ef.png]] 查询某个id的另一种写法 ![[b56e7037ca2d96e40687d1847c49a0bc-b56e7037.png]] 查询某个区间的数据 ![[6c5ccf63662ac65d6cb453d559ed2886_720-8b197597.png]] 这些用法全可以在官网查api 可以发现全离不开[[01-@PathVariable|@PathVariable]] 就好比vue中的占位符 等待传入替换 ##### 测试插入 其实真实的插入不是这样 简单利用PathVariable的特性 来实现一个插入数据 ```java @GetMapping("/user/add/{userId}/{username}/{password}") public String addUser(@PathVariable int userId, @PathVariable String username, @PathVariable String password){ User user = new User(userId, username, password); userMapper.insert(user); return "Add User Success"; } ``` ![[image-157e86c3.png]] ##### 测试删除 同样 利用PathVariable ```java //测试删除 @GetMapping("/user/delete/{userId}") public String deleteUser(@PathVariable int userId){ userMapper.deleteById(userId); return "delete User Success"; } ``` ![[image-d7ef58be.png]] ![[image-d4e6dea1.png]] #### 对四层的理解 首先pojo层将表转换为类 使得java能够进行操作 为一切工作做准备 然后对于前端传来的请求 交给controller进行处理 它只是接口 不做具体逻辑 把接受的请求传给service service中实现具体业务逻辑 配合mapper层实现的对数据库的增删改查操作 把查询的数据返回给controller再传给前端展示 把前端传来的修改 传给mapper 再进一步持久化到数据库 ##### 流程 - **POJO 层**: - 将数据库的表映射为 Java 类,使得数据库中的数据可以被 Java 代码操作。这一层为其他所有层的工作提供了基础数据结构。 - **Controller 层**: - 接收前端发送的请求,并作为接口处理。Controller 层的职责是接收请求参数,判断调用哪个 Service 层的方法,然后将结果返回给前端。 - **不处理具体的业务逻辑**,只是作为请求的入口和响应的出口。 - **Service 层**: - 处理具体的业务逻辑。接收 Controller 层传递过来的请求参数,根据业务需求调用 Mapper 层进行数据的查询、修改、删除或保存。 - 在 Service 层中实现业务逻辑,使得代码结构清晰且具有可维护性。 - **Mapper 层(DAO 层)**: - 负责与数据库进行交互。通过持久化框架(如 MyBatis 或 MyBatis-Plus),执行对数据库的 CRUD 操作。 - 这个层次只关心数据的存储和访问,不涉及业务逻辑。 - **请求与响应的流程**: - 前端请求被发送到 Controller。 - Controller 根据请求调用 Service 层的对应方法。 - Service 层结合业务逻辑进行操作,可能会调用 Mapper 层访问数据库。 - Mapper 层执行数据库的操作,将数据返回给 Service 层。 - Service 层将结果返回给 Controller。 - Controller 将结果数据返回给前端。 ##### 实现 在pojo层 主要是用lombok注解 来实现表到class的转换(构造、get\set)也可以直接用MyBatis-plus配合插件直接生成实体类 在mapper 主要是使用MyBatis和MyBatis-plus 来实现 类和表的交互 增删改查 在controller层使用这一系列http请求的注解 来完成和前端的交互 service层实现具体业务逻辑 调用mapper 对增删改查进行操作 POJO 层 - **作用**: 表到 Java 类(实体类)的映射。POJO 层中的类与数据库中的表相对应,用于存储数据。 - **常用工具**: Lombok 和 MyBatis-Plus。 - **实现方式**: - 使用 Lombok 注解(如 `@Data`、`@NoArgsConstructor`、`@AllArgsConstructor`)简化实体类的构建。 - 或者使用 MyBatis-Plus 代码生成器自动生成实体类。 Mapper 层(DAO 层) - **作用**: 负责将 Java 对象与数据库的表进行交互,实现数据的持久化操作(增删改查)。 - **常用工具**: MyBatis 和 MyBatis-Plus。 - **实现方式**: - 通过继承 MyBatis-Plus 的 `BaseMapper`,自动获取基本的 CRUD 操作。 - 可以根据业务需求,定义自定义的 SQL 查询、复杂条件查询、分页查询等。 Service 层 - **作用**: 负责处理业务逻辑,调用 Mapper 层进行数据的增删改查,并将结果返回给 Controller 层。 - **常用工具**: Spring 提供的服务注解。 - **实现方式**: - 通过在 Service 层注入 Mapper 对象,使用 Mapper 对象的方法处理数据的增删改查。 - 编写业务逻辑代码,处理业务规则和流程。 - 使用 `@Service` 注解标识服务类。 - 对于需要事务支持的业务操作,可以使用 `@Transactional` 注解。 Controller 层 - **作用**: 负责接收和处理来自前端(如浏览器、移动端)发来的 HTTP 请求,调用 Service 层执行具体的业务逻辑,并将结果返回给前端。 - **常用工具**: Spring MVC 提供的注解(如 `@RestController`、`@GetMapping`、`@PostMapping` 等)。 - **实现方式**: - 使用 `@RestController` 定义控制器类,处理 RESTful API 请求。 - 使用 `@GetMapping`、`@PostMapping`、`@PutMapping`、`@DeleteMapping` 等注解处理不同类型的 HTTP 请求。 - 使用 `@RequestBody` 解析请求体中的 JSON 数据,将其转换为 Java 对象。 - 使用 `@RequestParam` 和 `@PathVariable` 处理 URL 中的参数和路径变量。 小结 - **POJO 层** 主要用于定义数据结构,使用 Lombok 注解简化类的创建,或者使用 MyBatis-Plus 自动生成。 - **Mapper 层** 负责数据的持久化,通过 MyBatis-Plus 简化数据库操作。 - **Service 层** 实现业务逻辑,负责与 Mapper 层交互,并且可以处理事务。 - **Controller 层** 负责处理 HTTP 请求,调用 Service 层完成业务逻辑,并返回结果。 进一步的拓展 - 如果有更复杂的需求,可以在 Service 层编写更复杂的业务逻辑。 - 在 Controller 层,可以使用 `@ExceptionHandler` 等注解来处理全局异常。 - 可以在 Mapper 层使用自定义 SQL 语句来完成复杂的数据库操作。 ## 认证操作 很多操作需要登入成功之后才能访问 所以得加上一个授权机制 springboot中也有实现的 我们给他引入 继承下来 ### 引入依赖和调试 引入依赖包 spring-boot-starter-security [Maven仓库地址](https://mvnrepository.com/) ```xml org.springframework.boot spring-boot-starter-security ``` 建议去掉所有 Spring Boot 依赖的 `` 标签,这样所有的依赖版本都会自动继承父项目中的版本,保持一致性 如果父版本较低 而依赖版本较高 就会出现非常卡顿的情况(前面的jdbc也可以去掉版本号) 去版本号之前 进入login卡十多秒 退出login卡十多秒 且渲染都不完全 ![[image-b0cfc05d.png]] ![[image-eb541976.png]] 去版本号之后 飞快 ![[image-af9de82c.png]] ![[image-d757e289.png]] 言归正传 加上这个依赖后 刷新页面发现 之前的所有页面都进不去了(只有login和logout是可以访问的) 看上去是全都重定向在了/login 要求登入 默认的账号是user 密码在每次构建时都会重新生成 ![[image-14e87c78.png]] 验证完了 才能正常访问其他页面 如果登出的话 这些页面就又不能访问了 实现它的基本原理:[[00-后端配置和调试|后端配置和调试]] ### 与数据库对接 现在登入只能用默认的用户名 和 随机生成的密码 怎么让它对接我们的数据库 需要配置一下security #### 实现`service.impl.UserDetailsServiceImpl`类 在backend包下创建service包 service包下创建impl包 在impl包里创建UserDetailsServiceImpl类 ![[image-81ad7f48.png]] 继承自`UserDetailsService`接口,用来接入数据库信息 (tips: Alt+insert 快速查找该接口的所有实现方法) 使用快捷方式实现loadUserByUsername方法 ![[image-783968c6.png]] ![[image-a3fe0c55.png]] 得到以下代码 加上一个@Service标明 ```java package com.zwnsyw.backend.service.impl; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return null; } } ``` 这个api会传入一个username 我们需要通过这个username去返回一个接口`UserDetails` 包含对应的用户名和用户密码 首先就得实现 对数据库的查询 得用到mapper层 前面说过用mapper得先定义出来(最好加个private 防止出错) 然后加个@Autowired注解 然后调用MyBatis-plus的查询sql接口 进行查询 如果查询结果不存在 就抛出异常 ```java package com.zwnsyw.backend.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zwnsyw.backend.mapper.UserMapper; import com.zwnsyw.backend.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.eq("username",username); User user=userMapper.selectOne(queryWrapper); if(user==null){ throw new RuntimeException("用户不存在"); } return null; } } ``` 如果结果存在 就需要 ##### 实现 UserDetails 接口 新建软件包 com.zwnsyw.backend.service.impl.utils 创建类 UserDetailsImpl ![[image-8a447263.png]] 它是实现UserDetails的接口 方法照旧 alt+insert ![[image-a9d2c3df.png]] 直接确定 全部插入 ![[image-f01fad1a.png]] 对得到的代码进行解读: ```java package com.zwnsyw.backend.service.impl.utils; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; public class UserDetailsImpl implements UserDetails { @Override public Collection getAuthorities() { // 返回用户的权限集合,可以根据实际的角色和权限需求进行修改 return List.of(); } @Override public String getPassword() { // 返回用户的密码,可以根据实际的用户数据进行修改 return ""; } @Override public String getUsername() { // 返回用户的用户名,可以根据实际的用户数据进行修改 return ""; } @Override public boolean isAccountNonExpired() { // 表示账号是否未过期,返回 true 代表账号有效 return false; } @Override public boolean isAccountNonLocked() { // 表示账号是否未被锁定,返回 true 代表账号未被锁定 return false; } @Override public boolean isCredentialsNonExpired() { // 表示用户的凭据(如密码)是否未过期,返回 true 代表凭据未过期 return false; } @Override public boolean isEnabled() { // 表示用户是否启用,返回 true 代表用户被启用 return false; } } ``` 说明: 1. `getAuthorities`:根据你的权限管理逻辑,返回用户的权限列表。这里用 `List.of()` 代表一个空权限列表,你可以根据实际情况返回包含用户权限的集合。 2. `getPassword` 和 `getUsername`:这两个方法应该根据你的用户数据返回正确的用户名和密码。 3. `isAccountNonExpired`:表示用户账号是否未过期。现在返回 `true`,即表示账号有效。 4. `isAccountNonLocked`:表示用户账号是否未被锁定。现在返回 `true`,即表示账号未被锁定。 5. `isCredentialsNonExpired`:表示用户凭据(例如密码)是否未过期。现在返回 `true`,即表示凭据未过期。 6. `isEnabled`:表示用户是否被启用。现在返回 `true`,即表示用户处于启用状态。 这个用户名用户密码需要由我们实现的pojo中的User中得到 调用get方法就行了 另外加上实现构造函数get、set方法的注解 得到如下: ```java package com.zwnsyw.backend.service.impl.utils; import com.zwnsyw.backend.pojo.User; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class UserDetailsImpl implements UserDetails { private User user; @Override public Collection getAuthorities() { return List.of(); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } ``` 回到UserDetailsServiceImpl.java 如果成功查询 返回UserDetailsImpl类的用user初始化的对象即可 ```java package com.zwnsyw.backend.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zwnsyw.backend.mapper.UserMapper; import com.zwnsyw.backend.pojo.User; import com.zwnsyw.backend.service.impl.utils.UserDetailsImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { QueryWrapper queryWrapper=new QueryWrapper<>(); queryWrapper.eq("username",username); User user=userMapper.selectOne(queryWrapper); if(user==null){ throw new RuntimeException("用户不存在"); } return new UserDetailsImpl(user); } } ``` [[07-新涉及到的注解 @Override @Autowired|新涉及到的注解 @Override @Autowired]] ##### 测试 现在的逻辑是 从数据库里查用户和密码 如果匹配就算验证成功 直接登入发现报错 ![[image-ac3d722b.png]] 是因为 `Spring Security` 默认期望密码经过编码和加密 而不是直接用数据库里的明文进行比对 但这里只是测试用户名密码登入 没用到加密 有两个解决方法 一个是修改Spring Security配置来支持NoOpPasswordEncoder 另一个是直接在数据表中password字段加上{noop} 说明该字段是明文 没用到加密 如果数据库中的密码是 `password123`,那么将其更新为 `{noop}password123` 因为后面肯定都要加密 所以没必要配置明文登入 改一下数据库就行了 ![[image-54ab08a7.png]] ![[用户名密码登入-9d7d02f3.gif]] #### 实现加密 类似于哈希 给一个字符串可以很容易变成密文 也类似于密钥对 从私到公很容易 从公到私 很难推出来 这样做 可以保证 哪怕数据库泄露了 也不会泄露用户的密码 那既然存的是密文 怎么判断输入的密码是否正确呢 回忆一下哈希就知道了 把第二串密码也加密一遍 如果一模一样就说明正确 不一样的密码哈希后成一样的结果的可能微乎其微(我可能不知道p1是什么 但我一定知道p2的加密结果是不是与p1的加密结果匹配) 这就是加密逻辑 在backend/config下加一个SecurityConfig类 ![[image-46eeeddb.png]] ```java package com.zwnsyw.backend.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` ##### 分析 `@Configuration` 注解 - `@Configuration` 表示这是一个配置类,类似于传统的 Spring XML 配置文件。这类类被 Spring 框架标识为包含一个或多个 `@Bean` 方法的类,Spring 容器将管理这个类中的 `@Bean` 方法生成的 Bean 对象。 `@EnableWebSecurity` 注解 - `@EnableWebSecurity` 是 Spring Security 提供的注解,用于开启 Web 安全支持。这个注解会启用 Spring Security 的 Web 安全特性,使得该配置类能够进行安全性相关的设置。 `passwordEncoder` 方法 - 该方法使用 `@Bean` 注解标注,表示这个方法会返回一个由 Spring 容器管理的 Bean 实例。在这种情况下,返回的 `PasswordEncoder` 是一个 `BCryptPasswordEncoder` 实例。 - `@Bean` 的作用是告诉 Spring 容器,这个方法返回的对象需要被注册为 Spring 容器中的一个 Bean。 `BCryptPasswordEncoder` 类 - `BCryptPasswordEncoder` 是 Spring Security 提供的一种密码加密方式。BCrypt 是一种非常流行且安全的哈希算法,能够有效地抵抗暴力破解和字典攻击。 - 在 Spring Security 中,使用 `BCryptPasswordEncoder` 可以对用户的密码进行加密,并在用户登录时对输入的密码进行匹配验证。 代码的整体功能 该配置类的主要目的是定义一个密码编码器 `PasswordEncoder`。这段代码告诉 Spring,当需要 `PasswordEncoder` 时,会使用 `BCryptPasswordEncoder` 作为加密器。 为什么要这样做? 在 Spring Security 中,密码通常需要进行加密存储,以防止密码泄露。使用 `BCryptPasswordEncoder` 可以确保密码的安全性,而不是将明文密码直接存储在数据库中。BCrypt 使用了自带的盐值机制,使得每次加密相同的密码都会产生不同的哈希值,这大大增强了安全性。 示例解释: - 当用户注册或更改密码时,系统会使用 `passwordEncoder().encode(plainPassword)` 对明文密码进行加密,并存储到数据库中。 - 当用户登录时,系统会用用户输入的密码通过 `passwordEncoder().matches(rawPassword, encodedPassword)` 方法来验证输入的密码是否与存储的加密密码匹配。 总结: 这段代码的目的是配置一个 `BCryptPasswordEncoder` 作为应用的密码加密器,以提高用户密码的安全性。这是一个推荐的安全实践,用于防止密码明文泄露,并增强密码存储的安全性。 其他地方也可以套用这个模板 基本不会变: ```java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` ##### 测试(使用test) ![[image-9d0f2cb8.png]] ```java package com.zwnsyw.backend; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @SpringBootTest class BackendApplicationTests { @Test void contextLoads() { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); System.out.println(passwordEncoder.encode("Zwww")); } } ``` 得到加密结果: ![[image-794111d9.png]] 如果把同样的明文加密三次 会发现其实结果并不一样 ![[image-bb72ea61.png]] ![[image-9694fcb3.png]] 所以其底层的原理实际要比简单的哈希要复杂得多 应该是有加上一个关于时间戳的概念 反正它内部有方法会帮我们做好匹配 具体原理可以忽略 也可以做个了解 [[05-加密原理|加密原理]] 把上次生成的密文与明文进行比对 使用passwordEncoder.matches方法 发现是true的 ![[image-a893f88c.png]] ![[image-bb612f9b.png]] ```java package com.zwnsyw.backend; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @SpringBootTest class BackendApplicationTests { @Test void contextLoads() { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); System.out.println(passwordEncoder.encode("Zwww")); System.out.println(passwordEncoder.matches("Zwww", "$2a$10$S6o7Hdy3HhWnDMVYjBkh5.fPFxkAzaUeeD6k232/llslSAdLalGFK")); } } ``` ##### 运用加密后的密码 现在已经实现了加密 再和前面一样用明文密码就会显示不匹配 因为修改了配置文件 所以即使加了{noop}也没用 ![[image-3e8e1db2.png]] 需要把数据库里的密码更新成加密后的密码 已经存在的我们就直接在test里面生成 然后更改到数据库(简单粗暴) ![[image-5bdadc25.png]] ![[image-fb4d8ed4.png]] ![[image-e1cfae27.png]] 现在就可以用密码登入了 后面会自动帮我们把输入的密码加密 然后与数据库中的密文对比 那么注册的代码得改一下 刚才是已经存在的密码这样手动改一下 新加的就让它默认走这套逻辑 直接存加密后的密码 编辑backend/controller/user/UserController.java ![[image-677fd7bb.png]] 修改addUser: ```java //测试添加 @GetMapping("/user/add/{userId}/{username}/{password}") public String addUser(@PathVariable int userId, @PathVariable String username, @PathVariable String password){ PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encodePassword = passwordEncoder.encode(password); User user = new User(userId, username, encodePassword); userMapper.insert(user); return "Add User Success"; } ``` ![[image-a676f64c.png]] ![[image-1eefc566.png]] ![[image-e74ee783.png]] 成功了 --- **项目分区导航**: [[01-js-import 和 export 的使用|js-import 和 export 的使用]] ⬅️ | 00-后端配置和调试 | ➡️ [[01-@PathVariable|@PathVariable]]