后端配置和调试

mysql

下载和配置

常用命令

登入: mysql -u root -p

image-f66e9fec

查看数据库: show databases;

image-ca93a9cc

创建数据库: create database dbname;

image-39d06838

删除数据库:drop database dbname;

image-a05bfa73

使用数据库: use dbname;

image-90d84210

创建表 create table tbname(setname typename , setname typename);

image-31a8fde8

(查询当初创建表的sql语句: show create table tbname;

列出当前数据库的所有表: show tables;

image-b55a5c82

在表中插入数据: insert into tbname values( , , ……);

image-30eb2bf7

查询表中所有数据 select * from tbname;

image-26572f74

删除某行数据 delete from tbname where ?;

image-96761df4

删除表 drop table tbname;

image-17b474fd

更多命令

详见 mysql笔记

pom.xml (配置Spring Boot)

Maven仓库地址 Mybatis-Plus官网

在pom.xml文件中添加依赖:

创建初加

  • Spring Boot Starter JDBC
  • Project Lombok
  • MySQL Connector/J
  • mybatis-plus-boot-starter
  • mybatis-plus-generator

Maven仓库地址中一个一个搜上述依赖

将最新版本的代码拷贝入pom.xml 的标签下

image-b99d1f7e image-8a49ad18

点击右方maven 重新加载

image-7301ebf0

等待下载(挂梯子都要很久 要么就考虑换源 反正人麻了)

作用解释:

  • 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到底是怎么个事

关于pom.xml

这是当前阶段的pom.xml代码 (后面MyBatis有兼容问题 把springboot版本改成了3.11 然后又新引入mybatis

3.5.16 才得以解决 代码没敲两行 全他妈改环境去了 恶心

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.zwnsyw</groupId>
	<artifactId>backend</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>backend</name>
	<description>backend</description>
	<url/>
	<licenses>
		<license/>
	</licenses>
	<developers>
		<developer/>
	</developers>
	<scm>
		<connection/>
		<developerConnection/>
		<tag/>
		<url/>
	</scm>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
			<version>3.3.5</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.34</version>
			<scope>provided</scope>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<version>9.1.0</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.9</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-generator</artifactId>
			<version>3.5.9</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>3.0.3</version>
		</dependency>

		<!-- MyBatis Core Dependency -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.16</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

用到再加

还有一些依赖包 后面用到了再导入 不然会引起不必要的麻烦

  • 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-apijjwt-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 version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.4</version> <!-- Spring Boot 父项目版本 -->
        <relativePath/>
    </parent>

    <groupId>com.example</groupId> <!-- 项目组织 ID -->
    <artifactId>your-project-name</artifactId> <!-- 项目名称 -->
    <version>0.0.1-SNAPSHOT</version> <!-- 项目版本 -->
    <name>Your Project Name</name> <!-- 项目描述 -->
    <description>Your Project Description</description> <!-- 项目描述 -->

    <properties>
        <java.version>17</java.version> <!-- 指定 Java 版本 -->
    </properties>

    <dependencies>
        <!-- Spring Boot Web 启动器,支持创建 Web 应用 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Spring Boot JDBC 启动器,简化数据库访问 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!-- Spring Boot Thymeleaf 启动器,支持 Thymeleaf 模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!-- Spring Boot 安全启动器,提供基本的安全配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- MySQL 数据库的 JDBC 驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>9.1.0</version>
        </dependency>

        <!-- Project Lombok,减少样板代码 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.34</version>
            <scope>provided</scope> <!-- 编译时提供 -->
        </dependency>

        <!-- MyBatis Plus 启动器,增强 MyBatis 功能 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.9</version>
        </dependency>

        <!-- MyBatis Plus 代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.9</version>
        </dependency>

        <!-- JJWT API,处理 JSON Web Tokens (JWT) -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>

        <!-- JJWT 实现,提供实际的 JWT 生成和解析功能 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
        </dependency>

        <!-- JJWT Jackson 支持,将 JWT 的 JSON 数据与 Jackson 结合 -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
        </dependency>

        <!-- Spring Boot 测试启动器,提供测试支持 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope> <!-- 仅在测试时使用 -->
        </dependency>

        <!-- JUnit 测试库 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
            <scope>test</scope> <!-- 仅在测试时使用 -->
        </dependency>

        <!-- Springfox,用于生成 Swagger 文档 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

        <!-- Logback 日志库 -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>

        <!-- Redis 启动器,支持 Redis 数据库 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- Kafka 启动器,支持 Kafka 消息队列 -->
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- Spring Boot Maven 插件 -->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

在application.properties中添加数据库配置:

springboot要访问服务器其实也是要root+密码登入的 所以要进行一些配置

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

包如果成了xxx.xx导致逻辑不清晰 可以如上图设置 取消勾选压缩空的中间软件包

另外了解几个概念

SpringBoot中的常用模块

  • pojo层:将数据库中的表对应成Java中的Class
  • mapper层(也叫Dao层):将pojo层的class中的操作,映射成sql语句
  • service层:写具体的业务逻辑,组合使用mapper中的操作
  • controller层:负责请求转发,接受页面过来的参数,传给Service处理,接到返回值,再传给页面

在我们的项目中 一共分为四个页面

做成四个模块 pk、record、ranklist、user

这里我们主要先实现用户的登入功能 所以操作user包

不过可以把其他包也先建好

image-be984d65

调试(以user为例)

先实现一下pojo层 将数据表转变成类

这个转变可以是用工具一键生成(MyBatisPlus) 也可以自己定义类后用Lombok注解 半自动的生成

pojo层

这里先实现一下半自动的方法

backend包下新建pojo包

image-467f9b2b

pojo下新建一个user类 用于将kob中user表转变为class

image-93c7c1d0

既然是翻译 (将表翻译为class) 我们就来查看一下表的结构

image-8138c0c5

由此得到这样一个类的结构

package com.zwnsyw.backend.pojo;

public class User {
    private Integer id;
    private String username;
    private String password;
}

接下来就是要实现一些基本的操作 get、set 构造函数等等

但这种机械化的东西无需自己写 利用好lombok注解就能迅速解决

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

可以发现 什么构造 初始化一堆乱七八糟的接口 就这样有模有样的写好了 就只加了三个注解 神奇。。。

image-8adcdc9b

mapper 层

将类中的操作 映射成sql语句 写入mysql

同样 backend包下创一个mapper包 里面建一个UserMapper 接口 (注意是接口)

image-acdccd2f

这里我们仅需加上一个@Mapper注解 标志这是一个Mapper接口

因为使用了MyBatis-Plus 本来增删改查需要写很多sql语句 但MyBatis-Plus帮我们做了这些事情 怎么把它做好的东西拿过来呢

很简单——继承

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<User> {//继承 MyBatis-Plus 的 BaseMapper 接口 自动获得 CRUD 操作的实现
}
  • BaseMapper<User>: 通过继承 MyBatis-Plus 的 BaseMapper 接口,我们可以自动获得 CRUD 操作的实现,而不需要手动编写 SQL 语句。
  • @Mapper: 注解标识这个接口是 MyBatis 的 Mapper,使得 Spring 能够自动扫描并实现这个接口。

如果不加@Mapper的话 可以在BackendApplication中加上一个MapperScan

image-2189a6d4

MyBatis-MyBatis-plus

controller层和service层

原本是还要写层service的 但现在只是调试 就把这俩功能都写在controller里面

在backend、controller、user下创建UserController类

加上注解@RestController

RestController和Controller

package com.zwnsyw.backend.controller.user;

import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

}

再加上ReqestMapping注解(或具体的 GetMapping PostMapping)

处理http请求的注解

这里做个测试 处理get请求 在user目录下返回所有用户

具体的实现逻辑原本是要在service中实现 这里为了方便调试 直接写在controller层中 因为要获取用户 自然要调用mapper中实现的增删改查接口 要调用mapper 就得先定义出来 同时 配套的加上Autowired注解 另外 mapper中的增删改查接口是由MyBatis-plus生成的 所以一些api我们可能不熟悉参数 可以具体去MyBatis-plus官网

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<User> getAll(){
        return userMapper.selectList(null);//查询所有数据
    }
}
e424a9fe80cf22193952d5d0619aff57-e424a9fe
测试查询

查询某个用户的数据

需要加上注解@PathVariable

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<User> getAll(){
        return userMapper.selectList(null);
    }

    @GetMapping("/user/{userId}/")
    public User getUser(@PathVariable int userId){
        return userMapper.selectById(userId);
    }
}
df53c01f0a1c9fccb192a88e30a97f03-df53c01f 19c1a8ef8f8dfe1dd13735f02c328727-19c1a8ef

查询某个id的另一种写法

b56e7037ca2d96e40687d1847c49a0bc-b56e7037

查询某个区间的数据

6c5ccf63662ac65d6cb453d559ed2886_720-8b197597

这些用法全可以在官网查api

可以发现全离不开@PathVariable 就好比vue中的占位符 等待传入替换

测试插入

其实真实的插入不是这样 简单利用PathVariable的特性 来实现一个插入数据

@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
测试删除

同样 利用PathVariable

//测试删除
    @GetMapping("/user/delete/{userId}")
    public String deleteUser(@PathVariable int userId){
        userMapper.deleteById(userId);
        return "delete User Success";
    }
image-d7ef58be image-d4e6dea1

对四层的理解

首先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/artifact/org.springframework.boot/spring-boot-starter-security -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
			<!-- <version>3.3.5</version>-->
		</dependency>

建议去掉所有 Spring Boot 依赖的 <version> 标签,这样所有的依赖版本都会自动继承父项目中的版本,保持一致性

如果父版本较低 而依赖版本较高 就会出现非常卡顿的情况(前面的jdbc也可以去掉版本号)

去版本号之前 进入login卡十多秒 退出login卡十多秒 且渲染都不完全

image-b0cfc05d image-eb541976

去版本号之后 飞快

image-af9de82c image-d757e289

言归正传 加上这个依赖后

刷新页面发现 之前的所有页面都进不去了(只有login和logout是可以访问的) 看上去是全都重定向在了/login 要求登入

默认的账号是user 密码在每次构建时都会重新生成

image-14e87c78

验证完了 才能正常访问其他页面

如果登出的话 这些页面就又不能访问了

实现它的基本原理:后端配置和调试

与数据库对接

现在登入只能用默认的用户名 和 随机生成的密码

怎么让它对接我们的数据库

需要配置一下security

实现service.impl.UserDetailsServiceImpl

在backend包下创建service包 service包下创建impl包 在impl包里创建UserDetailsServiceImpl类

image-81ad7f48

继承自UserDetailsService接口,用来接入数据库信息

(tips: Alt+insert 快速查找该接口的所有实现方法)

使用快捷方式实现loadUserByUsername方法

image-783968c6 image-a3fe0c55

得到以下代码 加上一个@Service标明

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接口 进行查询

如果查询结果不存在 就抛出异常

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<User> 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

它是实现UserDetails的接口 方法照旧 alt+insert

image-a9d2c3df

直接确定 全部插入

image-f01fad1a

对得到的代码进行解读:

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<? extends GrantedAuthority> 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. getPasswordgetUsername:这两个方法应该根据你的用户数据返回正确的用户名和密码。
  3. isAccountNonExpired:表示用户账号是否未过期。现在返回 true,即表示账号有效。
  4. isAccountNonLocked:表示用户账号是否未被锁定。现在返回 true,即表示账号未被锁定。
  5. isCredentialsNonExpired:表示用户凭据(例如密码)是否未过期。现在返回 true,即表示凭据未过期。
  6. isEnabled:表示用户是否被启用。现在返回 true,即表示用户处于启用状态。

这个用户名用户密码需要由我们实现的pojo中的User中得到 调用get方法就行了

另外加上实现构造函数get、set方法的注解

得到如下:

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<? extends GrantedAuthority> 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初始化的对象即可

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<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",username);

        User user=userMapper.selectOne(queryWrapper);

        if(user==null){
            throw new RuntimeException("用户不存在");
        }

        return new UserDetailsImpl(user);
    }
}

新涉及到的注解 @Override @Autowired

测试

现在的逻辑是 从数据库里查用户和密码 如果匹配就算验证成功

直接登入发现报错

image-ac3d722b

是因为 Spring Security 默认期望密码经过编码和加密 而不是直接用数据库里的明文进行比对 但这里只是测试用户名密码登入 没用到加密

有两个解决方法

一个是修改Spring Security配置来支持NoOpPasswordEncoder

另一个是直接在数据表中password字段加上{noop} 说明该字段是明文 没用到加密 如果数据库中的密码是 password123,那么将其更新为 {noop}password123

因为后面肯定都要加密 所以没必要配置明文登入 改一下数据库就行了

image-54ab08a7 用户名密码登入-9d7d02f3

实现加密

类似于哈希 给一个字符串可以很容易变成密文 也类似于密钥对 从私到公很容易 从公到私 很难推出来

这样做 可以保证 哪怕数据库泄露了 也不会泄露用户的密码

那既然存的是密文 怎么判断输入的密码是否正确呢 回忆一下哈希就知道了 把第二串密码也加密一遍 如果一模一样就说明正确 不一样的密码哈希后成一样的结果的可能微乎其微(我可能不知道p1是什么 但我一定知道p2的加密结果是不是与p1的加密结果匹配) 这就是加密逻辑

在backend/config下加一个SecurityConfig类

image-46eeeddb
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 作为应用的密码加密器,以提高用户密码的安全性。这是一个推荐的安全实践,用于防止密码明文泄露,并增强密码存储的安全性。

其他地方也可以套用这个模板 基本不会变:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
测试(使用test)
image-9d0f2cb8
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

如果把同样的明文加密三次 会发现其实结果并不一样

image-bb72ea61 image-9694fcb3

所以其底层的原理实际要比简单的哈希要复杂得多 应该是有加上一个关于时间戳的概念 反正它内部有方法会帮我们做好匹配

具体原理可以忽略 也可以做个了解 加密原理

把上次生成的密文与明文进行比对 使用passwordEncoder.matches方法 发现是true的

image-a893f88c image-bb612f9b
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

需要把数据库里的密码更新成加密后的密码

已经存在的我们就直接在test里面生成 然后更改到数据库(简单粗暴)

image-5bdadc25 image-fb4d8ed4 image-e1cfae27

现在就可以用密码登入了 后面会自动帮我们把输入的密码加密 然后与数据库中的密文对比

那么注册的代码得改一下 刚才是已经存在的密码这样手动改一下 新加的就让它默认走这套逻辑 直接存加密后的密码

编辑backend/controller/user/UserController.java

image-677fd7bb

修改addUser:

//测试添加

    @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 image-1eefc566 image-e74ee783

成功了


项目分区导航js-import 和 export 的使用 ⬅️ | 00-后端配置和调试 | ➡️ @PathVariable