SpringBoot3 登录注册基础篇(一) - 多模块架构与统一响应封装

SpringBoot3 登录注册基础篇(一) - 多模块架构与统一响应封装

第一章. 为什么需要多模块架构

在开始构建认证系统之前,我们先解决一个基础问题:为什么不能把所有代码都塞在一个模块里?

假设我们现在有一个单模块项目,所有代码都在 src/main/java/com/example/auth 下。这种结构在项目初期看起来很简洁,但随着业务增长,会遇到三个致命问题。

1.1. 单模块的三大困境

困境一:职责不清

JwtUtil 是通用的 JWT 工具类,理论上可以被其他项目复用。但现在它和业务代码混在一起,如果其他项目想用,只能复制粘贴代码。

困境二:依赖混乱

AuthController 依赖了 TokenServiceTokenService 依赖了 JwtUtilJwtUtil 依赖了 RedisUtil
。这些依赖关系全部隐藏在代码里,新人接手项目时很难理解架构。

困境三:测试困难

如果我想单独测试 JwtUtil 的功能,必须启动整个 Spring Boot 应用,因为它和 Web 层耦合在一起。

1.2. 多模块架构设计

我们将项目拆分成多个模块,每个模块有明确的职责:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
auth/                          # 根聚合器
├── pom.xml # 父POM(统一管理依赖版本)
├── auth-common/ # 公共基础模块
│ └── src/main/java/.../common/
│ ├── model/Result.java
│ └── util/SnowflakeIdGenerator.java
├── auth-core/ # 认证核心模块
│ └── src/main/java/.../core/
│ ├── util/JwtUtil.java
│ └── service/TokenService.java
└── auth-web/ # Web应用模块
└── src/main/java/.../web/
├── AuthApplication.java
└── controller/AuthController.java

依赖关系非常清晰:auth-webauth-coreauth-common

核心设计:聚合器和父 POM 分离,根目录只声明模块,auth-parent 只管理依赖版本。


第二章. 核心配置文件

2.1. 根 POM 配置(聚合器 + 父工程)

根目录的 pom.xml 同时负责两个职责:

  1. 聚合器(Aggregator):通过 <modules> 声明包含哪些子模块
  2. 父工程(Parent):通过 <dependencyManagement> 统一管理依赖版本

📄 文件路径pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>auth</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<name>auth</name>
<description>Auth System Multi-Module Project</description>

<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.4.2</spring-boot.version>
<jjwt.version>0.12.5</jjwt.version>
<hutool.version>5.8.34</hutool.version>
<lombok.version>1.18.30</lombok.version>
</properties>

<modules>
<module>auth-common</module>
<module>auth-core</module>
<module>auth-web</module>
</modules>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
</dependency>

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

标准实践:在 99% 的 Maven 多模块项目中,根目录既是聚合器也是父工程。这样可以:

  • 减少目录层级,结构更清晰
  • 避免维护多余的 POM 文件
  • 简化子模块的 <relativePath> 配置

2.2. 子模块配置

每个子模块都需要指定父 POM 为根目录的 auth,并声明自己的依赖。

auth-common 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>auth</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>auth-common</artifactId>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
</dependencies>
</project>

子模块的 <relativePath> 指向 ../pom.xml,即父目录的 POM 文件。这样 Maven 可以正确找到父工程。

auth-core 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>auth</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>auth-core</artifactId>

<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>auth-common</artifactId>
<version>1.0.0</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>

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

<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

auth-web 模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>auth</artifactId>
<version>1.0.0</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>auth-web</artifactId>

<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>auth-core</artifactId>
<version>1.0.0</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>


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

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


第三章. 统一响应封装

3.1. 为什么需要统一响应

假设我们有两个接口,一个返回 Token 对象,一个返回 User 对象。前端收到的响应格式完全不同:

1
2
3
4
5
6
7
8
9
10
11
// 接口1的响应
{
"accessToken": "token123",
"refreshToken": "refresh456"
}

// 接口2的响应
{
"id": 1001,
"username": "admin"
}

前端开发者会抱怨:“为什么每个接口的格式都不一样?我怎么知道请求成功还是失败?”

我们需要定义一个统一的响应格式,所有接口都返回这个结构:

1
2
3
4
5
6
7
{
"code": 200,
"message": "操作成功",
"data": {
/* 实际数据 */
}
}

3.2. Result 泛型类设计

我们使用泛型 Result<T> 来封装响应,其中 T 可以是任何类型的数据。

📄 文件路径auth-common/src/main/java/com/example/auth/common/model/Result.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.example.auth.common.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
// 响应码(200 表示成功)
private Integer code;
// 响应消息
private String message;
// 响应数据(泛型)
private T data;

// 成功响应(带数据)
public static <T> Result<T> ok(T data) {
return new Result<>(200, "操作成功", data);
}

// 成功响应(不带数据)
public static <T> Result<T> ok() {
return new Result<>(200, "操作成功", null);
}

// 失败响应
public static <T> Result<T> fail(Integer code, String message) {
return new Result<>(code, message, null);
}

// 失败响应(默认 500 错误码)
public static <T> Result<T> fail(String message) {
return new Result<>(500, message, null);
}
}

现在,我们的接口可以统一返回格式:

1
2
3
4
5
6
7
8
9
10
11
12

@PostMapping("/login")
public Result<AuthToken> login() {
AuthToken token = new AuthToken("token123", "refresh456");
return Result.ok(token);
}

@GetMapping("/user")
public Result<User> getUser() {
User user = new User(1001L, "admin");
return Result.ok(user);
}

前端收到的响应格式完全一致:

1
2
3
4
5
6
7
8
{
"code": 200,
"message": "操作成功",
"data": {
"accessToken": "token123",
"refreshToken": "refresh456"
}
}

3.3. 雪花算法工具类

在后续章节中,我们需要生成全局唯一的用户 ID。我们使用 Hutool 提供的雪花算法生成 19 位 Long 类型的趋势递增 ID来实现。

📄 文件路径auth-common/src/main/java/com/example/auth/common/util/SnowflakeIdGenerator.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.example.auth.common.util;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;

public class SnowflakeIdGenerator {

// workerId = 1, datacenterId = 1
private static final Snowflake SNOWFLAKE = IdUtil.getSnowflake(1, 1);

public static Long nextId() {
return SNOWFLAKE.nextId();
}
}

第四章. 本章小结

我们完成了标准 Maven 多模块架构的搭建,并封装了统一响应格式。

核心成果

模块职责依赖关系
auth (根 POM)聚合器 + 父工程
auth-common公共基础模块继承 auth
auth-core认证核心模块继承 auth,依赖 auth-common
auth-webWeb 应用模块继承 auth,依赖 auth-core

架构优势

  1. 职责分离:每个模块有明确的职责,易于维护
  2. 可复用性:auth-common 可以被其他项目复用
  3. 易于测试:auth-core 可以独立测试,不依赖 Web 层
  4. 版本统一:所有依赖版本在根 POM 中统一管理
  5. 结构清晰:采用标准的 Maven 多模块结构,减少了多余的目录层级

核心类速查

类名方法作用
Resultok(T data)成功响应(带数据)
Resultok()成功响应(不带数据)
Resultfail(Integer code, String message)失败响应
SnowflakeIdGeneratornextId()生成全局唯一 ID