user-center 是一个基于 Spring Boot 的用户中心项目,当前聚焦于用户注册、登录、登录态维护、个人资料维护、注册码准入、管理员查询用户和管理员删除用户等基础能力。项目使用 MyBatis-Plus 访问 MySQL,通过 Session 保存登录态,适合作为用户体系、权限控制和后续业务系统登录模块的起点。
- 用户注册:校验账号、密码、确认密码和注册码,检查账号唯一性,并写入数据库。
- 注册码准入:用户注册前必须持有可用注册码;注册成功后注册码会被标记为已使用。
- 管理员生成注册码:管理员可生成 12 位大小写字母和数字混合的唯一注册码。
- 注册码校验:支持检查注册码格式是否正确、是否存在且未使用。
- 用户登录:校验账号密码,登录成功后返回脱敏用户信息,并把用户登录态写入 Session。
- 当前用户恢复:支持根据浏览器携带的
JSESSIONID从 Session 中读取当前登录用户,用于页面刷新后恢复前端登录态。 - 修改个人资料:普通用户和管理员都可以修改自己的昵称、头像、手机号、邮箱和性别,保存后会刷新 Session 中的当前用户信息。
- 用户退出登录:移除当前 Session 中的用户登录态。
- 用户脱敏:对外返回用户信息时移除密码、逻辑删除标记、更新时间等敏感或内部字段。
- 管理员查询用户:管理员可按用户名模糊查询用户列表。
- 管理员删除用户:管理员可根据用户 ID 删除用户,删除行为由 MyBatis-Plus 逻辑删除配置接管。
- 登录态与权限:使用服务端 Session 保存
userLoginState,并通过userRole区分普通用户和管理员。 - 统一响应与异常处理:接口统一返回
BaseResponse<T>,业务失败通过BusinessException抛出,并由全局异常处理器统一包装。
| 技术 | 版本 / 说明 |
|---|---|
| Java | 21 |
| Spring Boot | 4.0.6 |
| Spring Web MVC | HTTP 接口层 |
| MyBatis Spring Boot Starter | 4.0.1 |
| MyBatis-Plus | 3.5.15,提供通用 CRUD、逻辑删除能力 |
| MySQL Connector/J | MySQL 数据库连接 |
| Lombok | 简化实体类 getter、setter 等样板代码 |
| Apache Commons Lang | 字符串校验工具 |
| JUnit 5 / Spring Boot Test | 单元测试与 Spring 上下文测试 |
| Maven Wrapper | 3.3.4,自动使用 Maven 3.9.15 |
user-center
|-- .mvn/wrapper/ # Maven Wrapper 配置
|-- src
| |-- main
| | |-- java/com/zhiyuan/usercenter
| | | |-- UserCenterApplication.java # Spring Boot 启动类
| | | |-- common
| | | | |-- BaseResponse.java # 通用接口响应对象
| | | | |-- BusinessException.java # 业务异常
| | | | |-- ErrorCode.java # 业务错误码
| | | | |-- GlobalExceptionHandler.java # 全局异常处理器
| | | | |-- ResultUtils.java # 响应构造工具
| | | | `-- UserUtils.java # 用户权限工具
| | | |-- constant
| | | | `-- UserConstant.java # 用户登录态、角色常量
| | | |-- controller
| | | | |-- RegisterCodeController.java # 注册码相关 HTTP 接口
| | | | `-- UserController.java # 用户相关 HTTP 接口
| | | |-- mapper
| | | | |-- RegisterCodeMapper.java # 注册码 Mapper
| | | | `-- UserMapper.java # 用户 Mapper
| | | |-- model/domain
| | | | |-- RegisterCode.java # 注册码实体,对应 register_code 表
| | | | |-- User.java # 用户实体,对应 user 表
| | | | `-- request
| | | | |-- UserLoginRequest.java
| | | | `-- UserRegisterRequest.java
| | | `-- service
| | | |-- RegisterCodeService.java # 注册码服务接口
| | | |-- UserService.java # 用户服务接口
| | | `-- impl
| | | |-- RegisterCodeServiceImpl.java
| | | `-- UserServiceImpl.java
| | `-- resources
| | |-- application.yaml # 应用、数据库、MyBatis-Plus 配置
| | `-- mapper/UserMapper.xml # User 字段映射
| `-- test
| `-- java/com/zhiyuan/usercenter
| |-- UserCenterApplicationTests.java
| `-- service/UserServiceTest.java
|-- pom.xml
|-- mvnw
|-- mvnw.cmd
|-- HELP.md
`-- README.md
说明:frontend/ 是独立的 Vue 前端目录,不参与后端 Maven 构建;如果需要单独管理前端,可以整体移动该目录。
启动项目前请准备:
- JDK 21
- MySQL 8.x 或兼容版本
- 可用的
user_center数据库 - Windows 推荐使用
mvnw.cmd,macOS / Linux 使用./mvnw
项目已经带有 Maven Wrapper,不强制要求本机提前安装 Maven。
当前配置连接本机 MySQL:
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: "1234"
url: jdbc:mysql://localhost:3306/user_center?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8如本机账号、密码、端口或数据库名不同,请修改 src/main/resources/application.yaml。
可以使用下面的 SQL 初始化数据库和用户表:
CREATE DATABASE IF NOT EXISTS user_center
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_unicode_ci;
USE user_center;
CREATE TABLE IF NOT EXISTS `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'id',
`username` VARCHAR(256) NULL COMMENT '用户昵称',
`userAccount` VARCHAR(256) NOT NULL COMMENT '账号',
`userPassword` VARCHAR(512) NOT NULL COMMENT '密码',
`avatarUrl` VARCHAR(1024) NULL COMMENT '用户头像',
`phone` VARCHAR(128) NULL COMMENT '电话',
`email` VARCHAR(512) NULL COMMENT '邮箱',
`gender` TINYINT NULL COMMENT '性别',
`userStatus` INT NOT NULL DEFAULT 0 COMMENT '用户状态',
`createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`isDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除:0-未删除,1-已删除',
`userRole` INT NOT NULL DEFAULT 0 COMMENT '用户角色:0-普通用户,1-管理员',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_userAccount` (`userAccount`)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COMMENT='用户';
CREATE TABLE IF NOT EXISTS `register_code` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'id',
`code` VARCHAR(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '注册码',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0-未使用,1-已使用',
`usedBy` BIGINT NULL COMMENT '使用该注册码注册的用户 id',
`createTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updateTime` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`isDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '是否删除:0-未删除,1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COMMENT='注册码';注册码说明:
code使用utf8mb4_bin排序规则,确保大小写敏感。status = 0表示未使用,status = 1表示已使用。usedBy保存使用该注册码注册成功的用户 ID,目前是逻辑关联,不强制使用数据库外键。
如果需要测试管理员接口,可以先注册一个用户,然后将其设置为管理员:
UPDATE `user`
SET `userRole` = 1
WHERE `userAccount` = '你的账号';核心配置位于 src/main/resources/application.yaml:
| 配置项 | 当前值 | 说明 |
|---|---|---|
spring.application.name |
user-center |
Spring 应用名 |
spring.datasource.url |
jdbc:mysql://localhost:3306/user_center... |
MySQL 连接地址 |
spring.datasource.username |
root |
数据库用户名 |
spring.datasource.password |
1234 |
数据库密码 |
server.port |
8080 |
HTTP 服务端口 |
server.servlet.session.timeout |
1d |
Session 有效期 |
mybatis-plus.mapper-locations |
classpath*:/mapper/**/*.xml |
Mapper XML 扫描路径 |
mybatis-plus.configuration.map-underscore-to-camel-case |
false |
不启用下划线转驼峰,数据库字段需与实体属性名一致 |
mybatis-plus.global-config.db-config.logic-delete-field |
isDeleted |
逻辑删除字段 |
mybatis-plus.global-config.db-config.logic-delete-value |
1 |
已删除值 |
mybatis-plus.global-config.db-config.logic-not-delete-value |
0 |
未删除值 |
Windows:
.\mvnw.cmd spring-boot:runmacOS / Linux:
./mvnw spring-boot:run启动成功后,服务默认监听:
http://localhost:8080
打包:
.\mvnw.cmd clean package运行测试:
.\mvnw.cmd test注意:当前测试依赖真实 MySQL 数据库和现有数据状态。例如 UserServiceTest#userRegister 中会校验账号重复场景,期望数据库中已经存在 qq1234 账号;同时成功注册场景会使用固定注册码并写入 testAccount1。如果数据库为空、缺少可用注册码或重复执行测试,可能需要先清理或准备测试数据。
当前主要接口前缀:
/user
/register-code
当前接口统一返回 BaseResponse<T>:
{
"code": 0,
"data": {},
"message": "ok"
}字段说明:
| 字段 | 说明 |
|---|---|
code |
业务状态码,0 表示成功,非 0 表示失败 |
data |
真正的业务数据,类型由具体接口决定 |
message |
返回给前端或用户看的提示信息 |
常用错误码:
| 错误码 | 枚举 | 说明 |
|---|---|---|
0 |
SUCCESS |
请求成功 |
40000 |
PARAMS_ERROR |
请求参数错误 |
40001 |
REGISTER_CODE_ERROR |
注册码格式错误、不存在、已使用或不可用 |
40100 |
NOT_LOGIN_ERROR |
用户未登录 |
40101 |
NO_AUTH_ERROR |
当前用户无权限 |
40400 |
NOT_FOUND_ERROR |
请求数据不存在 |
50000 |
SYSTEM_ERROR |
系统内部异常 |
业务失败会抛出 BusinessException,由 GlobalExceptionHandler 统一转换成 BaseResponse。Controller 不再返回 null 或负数错误码。
POST /user/register
Content-Type: application/json请求体:
{
"userAccount": "testAccount1",
"userPassword": "testPassword1",
"confirmPassword": "testPassword1",
"registerCode": "AbC123xYz789"
}成功响应:
{
"code": 0,
"data": 1,
"message": "ok"
}返回值说明:
| 字段 | 含义 |
|---|---|
data |
注册成功后的新用户 ID |
code != 0 |
注册失败,具体原因看 message |
注意:用户创建和注册码消费处于同一个事务中;如果注册码消费失败,会抛出业务异常并回滚用户创建。
示例:
curl -X POST http://localhost:8080/user/register \
-H "Content-Type: application/json" \
-d '{"userAccount":"testAccount1","userPassword":"testPassword1","confirmPassword":"testPassword1","registerCode":"AbC123xYz789"}'POST /register-code/generate权限要求:
- 必须已登录。
- 当前 Session 中的用户
userRole必须为1。
响应:
{
"code": 0,
"data": "AbC123xYz789",
"message": "ok"
}返回值说明:
| 字段 | 含义 |
|---|---|
data |
生成成功后的 12 位注册码 |
40101 |
当前用户不是管理员 |
50000 |
生成注册码失败 |
示例:
curl -X POST http://localhost:8080/register-code/generate \
-H "Cookie: JSESSIONID=你的会话ID"GET /register-code/check?code=AbC123xYz789行为:
- 校验
code是否为 12 位大小写字母或数字。 - 校验数据库中是否存在该注册码,且
status = 0。 - 只返回是否可用,不区分“不存在”和“已使用”。
响应:
{
"code": 0,
"data": true,
"message": "ok"
}示例:
curl "http://localhost:8080/register-code/check?code=AbC123xYz789"POST /user/login
Content-Type: application/json请求体:
{
"userAccount": "testAccount1",
"userPassword": "testPassword1"
}成功响应示例:
{
"code": 0,
"data": {
"id": 1,
"username": null,
"userAccount": "testAccount1",
"userPassword": null,
"avatarUrl": null,
"phone": null,
"email": null,
"gender": null,
"userStatus": 0,
"createTime": "2026-05-21T00:00:00.000+00:00",
"updateTime": null,
"isDeleted": null,
"userRole": 0
},
"message": "ok"
}登录成功后,后端会把脱敏后的用户对象写入当前 Session:
Session key: userLoginState
失败时返回统一错误响应。常见失败原因包括账号或密码为空、账号长度不合法、密码长度不合法、账号包含特殊字符、账号密码不匹配。
示例:
curl -i -X POST http://localhost:8080/user/login \
-H "Content-Type: application/json" \
-d '{"userAccount":"testAccount1","userPassword":"testPassword1"}'如果后续要访问当前用户或管理员接口,需要保留登录响应中的 JSESSIONID Cookie。
GET /user/current行为:
- 浏览器请求时会自动携带
JSESSIONIDCookie。 - 后端根据
JSESSIONID找到对应 Session,再读取userLoginState。 - 如果 Session 中存在登录用户,返回
BaseResponse<User>。 - 如果未登录或 Session 已失效,返回
NOT_LOGIN_ERROR。
成功响应:
{
"code": 0,
"data": {
"id": 1,
"username": "otaku",
"userAccount": "testAccount1",
"avatarUrl": "/user/avatar/example.png",
"phone": "13812345678",
"email": "123@qq.com",
"gender": 0,
"userStatus": 0,
"createTime": "2026-05-21T00:00:00.000+00:00",
"userRole": 1
},
"message": "ok"
}前端会在应用启动时调用该接口。如果返回当前用户,就重新写入 Pinia 的 currentUser;如果未登录,则保持前端未登录状态。
POST /user/profile/update
Content-Type: application/json行为:
- 必须已登录,后端通过当前请求的
JSESSIONID找到 Session,再从userLoginState读取当前用户 ID。 - 只允许修改当前登录用户自己的资料,不接受前端传入的用户 ID。
- 允许修改字段:
username、avatarUrl、phone、email、gender。 - 不允许通过该接口修改
userAccount、userPassword、userRole、userStatus、createTime、updateTime、isDeleted。 - 保存成功后重新查询用户、脱敏返回,并同步刷新 Session 中的当前用户信息。
请求体:
{
"username": "鱼鸢",
"avatarUrl": "/user/avatar/example.png",
"phone": "13812345678",
"email": "99@qq.com",
"gender": 1
}字段校验:
| 字段 | 规则 |
|---|---|
username |
可为空;非空时长度不超过 256 |
avatarUrl |
可为空;非空时长度不超过 1024,且必须是 /user/avatar/... 站内头像路径 |
phone |
可为空;非空时需符合 ^1[3-9]\d{9}$ |
email |
可为空;非空时长度不超过 256,并符合基础邮箱格式 |
gender |
可为空;非空时只能是 0 或 1 |
成功响应:
{
"code": 0,
"data": {
"id": 1,
"username": "鱼鸢",
"userAccount": "testAccount1",
"avatarUrl": "/user/avatar/example.png",
"phone": "13812345678",
"email": "99@qq.com",
"gender": 1,
"userStatus": 0,
"createTime": "2026-05-21T00:00:00.000+00:00",
"userRole": 0
},
"message": "ok"
}POST /user/logout行为:
- 移除当前 Session 中的
userLoginState。 - 成功返回
BaseResponse<Boolean>,其中data = true。 - 如果请求对象异常,抛出业务异常并返回统一错误响应。
示例:
curl -X POST http://localhost:8080/user/logout \
-H "Cookie: JSESSIONID=你的会话ID"GET /user/search?username=otaku权限要求:
- 必须已登录。
- 当前 Session 中的用户
userRole必须为1。
请求参数:
| 参数 | 必填 | 说明 |
|---|---|---|
username |
否 | 用户昵称,传入时进行模糊查询;不传则查询全部未删除用户 |
成功响应:
{
"code": 0,
"data": [
{
"id": 1,
"username": "otaku",
"userAccount": "testAccount1",
"userPassword": null,
"avatarUrl": "/user/avatar/example.png",
"phone": "13812345678",
"email": "123@qq.com",
"gender": 0,
"userStatus": 0,
"createTime": "2026-05-21T00:00:00.000+00:00",
"updateTime": null,
"isDeleted": null,
"userRole": 1
}
],
"message": "ok"
}非管理员或未登录时返回统一错误响应。
示例:
curl "http://localhost:8080/user/search?username=otaku" \
-H "Cookie: JSESSIONID=你的会话ID"POST /user/delete
Content-Type: application/json权限要求:
- 必须已登录。
- 当前 Session 中的用户
userRole必须为1。
请求体是用户 ID 数字本身:
1响应:
{
"code": 0,
"data": true,
"message": "ok"
}返回值说明:
| 字段 | 含义 |
|---|---|
data = true |
删除成功 |
40101 |
非管理员或无权限 |
40000 |
ID 非法 |
40400 |
用户不存在或删除失败 |
示例:
curl -X POST http://localhost:8080/user/delete \
-H "Content-Type: application/json" \
-H "Cookie: JSESSIONID=你的会话ID" \
-d '1'注册逻辑位于 UserServiceImpl#userRegister:
userAccount、userPassword、confirmPassword、registerCode不能为空。- 账号长度不能小于 6。
- 密码和确认密码长度不能小于 8。
- 账号只能包含英文字母、数字和下划线,正则为
^[a-zA-Z0-9_]+$。 - 密码和确认密码必须完全一致。
- 账号不能重复。
- 注册码必须存在、未使用,且格式为 12 位大小写字母或数字。
- 密码存储前会使用固定盐值
kawaii拼接原始密码,再进行 MD5 摘要。 - 用户保存成功后会消费注册码,将
register_code.status更新为1,并把usedBy写为新用户 ID。 - 用户创建和注册码消费处于同一个事务中;如果注册码消费失败,会抛出异常并回滚用户创建。
注册码逻辑位于 RegisterCodeServiceImpl:
- 注册码长度固定为 12。
- 字符集为
a-zA-Z0-9,区分大小写。 - 使用
SecureRandom随机生成。 code字段有唯一索引,极小概率重复时会重新生成。checkCode只判断格式正确、存在且未使用。useCode通过WHERE code = ? AND status = 0更新状态,避免同一个注册码被并发重复消费。
登录逻辑位于 UserServiceImpl#userLogin:
- 账号和密码不能为空。
- 账号长度不能小于 6。
- 密码长度不能小于 8。
- 账号只能包含英文字母、数字和下划线。
- 输入密码经过同样的盐值和 MD5 处理后,与数据库中的
userPassword匹配。 - 登录成功后写入 Session。
脱敏逻辑位于 UserServiceImpl#getSafeUser。当前对外保留字段:
idusernameuserAccountavatarUrlphoneemailgenderuserRoleuserStatuscreateTime
不会主动返回:
userPasswordupdateTimeisDeleted
个人资料修改逻辑位于 UserServiceImpl#updateCurrentUserProfile:
- 使用
request.getSession(false)获取已有 Session;没有 Session 时直接返回未登录错误,不为未登录请求创建空 Session。 - Session 存在但没有
userLoginState,或登录用户 ID 不合法时,也返回未登录错误。 - 字符串字段会先去掉前后空格,空字符串会转换为
null,方便用户主动清空可选资料。 - 更新条件固定为当前登录用户 ID,避免用户通过请求体修改其他账号资料。
- 更新成功后复用
getSafeUser返回脱敏用户,并把最新用户信息写回 Session。
角色常量定义在 UserConstant:
| 常量 | 值 | 说明 |
|---|---|---|
DEFAULT_ROLE |
0 |
普通用户 |
ADMIN_ROLE |
1 |
管理员 |
USER_LOGIN_STATE |
userLoginState |
Session 中保存登录用户的 key |
管理员判断逻辑位于 UserUtils#isAdmin:从 Session 中读取 userLoginState,再判断 userRole == 1。
User 实体对应数据库中的 user 表:
| 字段 | 类型建议 | 说明 |
|---|---|---|
id |
BIGINT |
主键,自增 |
username |
VARCHAR |
用户昵称 |
userAccount |
VARCHAR |
账号 |
userPassword |
VARCHAR |
加密后的密码 |
avatarUrl |
VARCHAR |
用户头像 |
phone |
VARCHAR |
电话 |
email |
VARCHAR |
邮箱 |
gender |
TINYINT |
性别 |
userStatus |
INT |
用户状态 |
createTime |
DATETIME |
创建时间 |
updateTime |
DATETIME |
更新时间 |
isDeleted |
TINYINT |
逻辑删除标记 |
userRole |
INT |
用户角色,0-普通用户,1-管理员 |
RegisterCode 实体对应数据库中的 register_code 表:
| 字段 | 类型建议 | 说明 |
|---|---|---|
id |
BIGINT |
主键,自增 |
code |
VARCHAR |
注册码,12 位大小写字母和数字 |
status |
TINYINT |
状态:0-未使用,1-已使用 |
usedBy |
BIGINT |
使用该注册码注册成功的用户 ID |
createTime |
DATETIME |
创建时间 |
updateTime |
DATETIME |
更新时间 |
isDeleted |
TINYINT |
逻辑删除标记 |
由于 map-underscore-to-camel-case 当前设置为 false,数据库字段名需要保持 userAccount、userPassword、avatarUrl 这类驼峰命名,否则需要同步调整配置或 Mapper 映射。
UserMapper和RegisterCodeMapper继承BaseMapper,基础 CRUD 由 MyBatis-Plus 提供。UserService和RegisterCodeService继承IService,可以直接使用save、list、count、removeById等通用方法。- Controller 主要负责接收请求、调用 Service 和返回
ResultUtils.success(...)。 - Service 负责业务校验;业务失败时抛出
BusinessException。 - 全局异常处理器负责把业务异常和系统异常统一转换为
BaseResponse。 removeById在当前 MyBatis-Plus 逻辑删除配置下会更新isDeleted,而不是物理删除记录。- 接口调用方只需要统一判断
code是否为0,再读取data。
检查:
- MySQL 是否已启动。
user_center数据库是否已创建。application.yaml中账号、密码、端口是否正确。- MySQL 是否允许当前用户从本机连接。
检查:
- 登录时使用的明文密码是否与注册时一致。
- 数据库中
userPassword是否是通过当前代码生成的 MD5 值。 - 账号是否只包含英文字母、数字和下划线。
- 账号长度是否不少于 6,密码长度是否不少于 8。
检查:
- 注册码是否为 12 位大小写字母或数字。
register_code表中是否存在该注册码。register_code.status是否为0。- 该注册码是否已经被其他用户注册消费。
- 如果要测试大小写敏感,请确认
code字段使用了utf8mb4_bin这类大小写敏感排序规则。
检查:
- 是否先调用
/user/login完成登录。 - 后续请求是否携带同一个
JSESSIONIDCookie。 - 当前登录用户的
userRole是否为1。
当前测试不是纯内存测试,会依赖本地 MySQL 和测试数据。建议在运行测试前准备独立测试库,或者在后续改造中引入测试容器、H2、测试 Profile 和数据初始化脚本。
- 将数据库密码、密码盐值等敏感配置移动到环境变量或独立配置文件。
- 使用 BCrypt、Argon2 等更适合密码存储的哈希算法替代 MD5。
- 优化当前用户接口的测试覆盖和前端刷新恢复体验。
- 完善权限控制,避免在 Controller 中手写管理员判断。
- 增加参数校验注解,例如
@Valid、@NotBlank、@Size。 - 增加测试隔离能力,避免测试依赖固定账号和本地数据库状态。
- 增加接口文档生成能力,例如 OpenAPI / Swagger。