用户中心后端完整业务流程
1. 文档说明
本文档以当前仓库中的后端源码为准,完整梳理用户中心项目从请求进入、业务处理、数据持久化到响应返回的全过程。
项目当前实现了以下功能:
- 用户注册
- 用户登录
- 获取当前用户
- 用户注销
- 管理员搜索用户
- 管理员删除用户
- 统一响应和异常处理
本文档描述的是当前代码已经实现的行为,同时会把源码中值得继续完善的地方单独标出来。
2. 项目整体业务链路
所有用户接口大体都遵循下面的调用链:
客户端
│
│ HTTP 请求
▼
UserController
│
│ 请求绑定、基础校验、部分权限判断
▼
UserService / UserServiceImpl
│
│ 业务校验、密码摘要、Session 操作
├──────────────────────► HttpSession
▼
UserMapper / MyBatis-Plus
│
│ 条件查询、插入、更新、逻辑删除
▼
MySQL.user
│
▼
User / BaseResponse
│
▼
客户端 JSON 响应
异常路径如下:
Controller / Service 抛出 BusinessException
│
▼
GlobalExceptionHandler
│
▼
ResultUtils.error(...)
│
▼
BaseResponse 错误响应
当前项目存在少量例外:部分失败分支直接返回 null 或 -1,没有完全走统一异常响应。
3. 核心角色和状态
3.1 用户角色
| Value | Role | Permission |
|---|---|---|
| 0 | 普通用户 | 使用普通用户功能 |
| 1 | 管理员 | 搜索和删除用户 |
角色常量定义在 UserConstant.java:
- DEFAULT_ROLE = 0
- ADMIN_ROLE = 1
管理员判断依赖登录后写入 Session 的用户对象,而不是依赖客户端自行提交的角色字段。
3.2 登录状态
登录成功后,系统将脱敏用户对象写入 Session:
Session["userLoginState"] = safetyUser
后续需要登录的接口会从 Session 中读取该对象。客户端必须保存并携带相同的 Session Cookie。
3.3 用户数据状态
| Field | Meaning |
|---|---|
| userStatus | 用户状态,数据库脚本中默认 0 |
| userRole | 用户角色,0 为普通用户,1 为管理员 |
| isDelete | 逻辑删除标记,0 为未删除,1 为已删除 |
当前登录流程会读取账号和密码,但源码中尚未明确校验 userStatus 是否允许登录。
4. 用户注册流程
4.1 接口信息
POST /api/user/register
其中 /api 来自 server.servlet.context-path,/user/register 来自 UserController 的请求映射。
4.2 请求参数
{
"userAccount": "testuser",
"userPassword": "12345678",
"checkPassword": "12345678",
"planetCode": "1001"
}
请求对象是 UserRegisterRequest,包含:
| Field | Purpose |
|---|---|
| userAccount | 用户登录账号 |
| userPassword | 原始密码 |
| checkPassword | 确认密码,只用于校验 |
| planetCode | 星球编号 |
4.3 详细执行过程
客户端提交注册请求
│
▼
UserController.userRegister
│
├─ 请求对象为空?──────► BusinessException(PARAMS_ERROR)
├─ 必填字段为空?──────► 当前代码 return null
│
▼
UserServiceImpl.userRegister
│
├─ 参数为空?──────────► BusinessException
├─ 账号长度小于 4?────► BusinessException
├─ 密码长度小于 8?────► BusinessException
├─ planetCode 大于 5?─► BusinessException
├─ 账号含特殊字符?────► 返回 -1
├─ 两次密码不一致?────► 返回 -1
├─ 账号已存在?────────► BusinessException
├─ 编号已存在?────────► BusinessException
│
▼
密码摘要:MD5(SALT + password)
│
▼
创建 User 对象
│
├─ 设置 userAccount
├─ 设置摘要后的 userPassword
└─ 设置 planetCode
│
▼
this.save(user)
│
├─ 保存失败 ───────────► 返回 -1
└─ 保存成功 ───────────► 返回新用户 id
4.4 注册校验规则
| Rule | Current behavior |
|---|---|
| 参数为空 | 抛出 PARAMS_ERROR |
| 账号长度不足 | 小于 4 时抛出 PARAMS_ERROR |
| 密码长度不足 | 密码或确认密码小于 8 时抛出 PARAMS_ERROR |
| 星球编号过长 | 长度大于 5 时抛出 PARAMS_ERROR |
| 账号含特殊字符 | 返回 -1 |
| 两次密码不一致 | 返回 -1 |
| 账号重复 | 查询数量大于 0 时抛出业务异常 |
| 星球编号重复 | 查询数量大于 0 时抛出业务异常 |
| 数据库保存失败 | 返回 -1 |
4.5 数据库写入
注册成功时,Service 创建新的 User 对象,主要设置:
- userAccount
- 摘要后的 userPassword
- planetCode
数据库中的 id 通过自增策略生成。其他字段使用数据库默认值或保持为空。
4.6 注册响应
成功响应:
{
"code": 0,
"data": 123,
"message": "ok",
"description": ""
}
data 是新用户 id。
4.7 注册后的状态
注册成功后,当前代码不会自动登录,也不会向 Session 写入登录态。用户还需要单独调用登录接口。
5. 用户登录流程
5.1 接口信息
POST /api/user/login
5.2 请求参数
{
"userAccount": "testuser",
"userPassword": "12345678"
}
请求对象是 UserLoginRequest,包含账号和密码两个字段。
5.3 详细执行过程
客户端提交账号和密码
│
▼
UserController.userLogin
│
├─ 请求对象为空?──────► PARAMS_ERROR
└─ 账号或密码为空?────► PARAMS_ERROR
│
▼
UserServiceImpl.userLogin
│
├─ 账号长度小于 4?────► 返回 null
├─ 密码长度小于 8?────► 返回 null
├─ 账号含特殊字符?────► 返回 null
│
▼
计算 MD5(SALT + password)
│
▼
按账号和密码摘要查询数据库
│
├─ 查不到用户 ─────────► 记录日志,返回 null
└─ 查到用户
│
▼
getSafetyUser(user)
│
▼
Session.setAttribute(...)
│
▼
返回脱敏用户对象
5.4 密码验证
注册和登录使用相同的摘要逻辑:
storedPassword = MD5(SALT + rawPassword)
登录时,系统用用户输入的原始密码计算摘要,再根据账号和摘要密码查询用户:
WHERE userAccount = ?
AND userPassword = ?
当前源码中的盐值是 Service 内部的固定字符串。固定盐值加 MD5 适合用于理解流程,但不适合作为生产级密码存储方案。
5.5 用户脱敏
查询到原始用户后,Service 不直接返回数据库对象,而是调用 getSafetyUser 创建新对象。
会复制的字段:
- id
- username
- userAccount
- avatarUrl
- gender
- phone
- planetCode
- userRole
- userStatus
- createTime
不会复制的字段:
- userPassword
- updateTime
- isDelete
5.6 Session 写入
登录成功后,Service 执行:
request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
USER_LOGIN_STATE 的实际值是 userLoginState。
后续请求必须携带同一个 Session Cookie,否则系统无法识别当前用户。
5.7 登录响应
登录成功时,Controller 返回脱敏后的 User 对象:
{
"code": 0,
"data": {
"id": 123,
"userAccount": "testuser",
"userRole": 0
},
"message": "ok",
"description": ""
}
实际返回字段以脱敏对象和 JSON 序列化结果为准。
当前登录失败时,Service 可能返回 null,Controller 随后仍调用 ResultUtils.success(user)。因此密码错误目前不一定得到明确的登录失败错误码。
6. 获取当前用户流程
6.1 接口信息
GET /api/user/current
该接口需要携带登录成功后的 Session Cookie。
6.2 详细执行过程
客户端请求 /api/user/current
│
▼
从 Session 读取 userLoginState
│
├─ 没有登录态 ───────► BusinessException(NOT_LOGIN)
└─ 存在登录态
│
▼
读取 Session 用户 id
│
▼
userService.getById(userId)
│
▼
getSafetyUser(user)
│
▼
返回当前脱敏用户
6.3 为什么还要查询数据库
Session 中保存的是登录时的脱敏对象,但用户信息可能已经发生变化。因此 current 接口会根据 Session 中的 id 再查一次数据库,然后重新脱敏。
6.4 当前待完善点
源码中保留了“校验用户是否合法”的 TODO,目前还没有完整处理:
- Session 中的用户已经被删除
- 用户状态被禁用
- 用户角色或资料发生变化后的边界行为
- Session 中的用户对象已经过期
7. 用户注销流程
7.1 接口信息
POST /api/user/logout
7.2 详细执行过程
客户端提交注销请求
│
▼
UserController.userLogout
│
├─ request 为 null?────► PARAMS_ERROR
│
▼
UserServiceImpl.userLogout
│
▼
移除 Session["userLoginState"]
│
▼
返回 1
注销的核心操作是移除 Session 中的 userLoginState 属性,而不是销毁整个 Session。
成功响应:
{
"code": 0,
"data": 1,
"message": "ok",
"description": ""
}
注销之后再次访问 current,由于 Session 中已经没有 userLoginState,会被视为未登录。
8. 管理员搜索用户流程
8.1 接口信息
GET /api/user/search?username=demo
这是管理员接口。
8.2 权限判断
Controller 调用 isAdmin(request):
Session 中读取 userLoginState
│
├─ 用户为空 ───────► false
└─ 用户存在
│
▼
user.getUserRole() == ADMIN_ROLE
│
├─ 等于 1 ─────► true
└─ 其他值 ────► false
权限不通过时,当前搜索接口抛出 PARAMS_ERROR;从语义上说更适合使用 NO_AUTH。
8.3 查询流程
管理员请求搜索接口
│
▼
检查 Session 中的 userRole
│
├─ 非管理员 ─────────► 拒绝请求
└─ 管理员
│
▼
创建 QueryWrapper<User>
│
├─ username 非空
│ ▼
│ 按 username 模糊查询
│
▼
userService.list(queryWrapper)
│
▼
每个 User 调用 getSafetyUser
│
▼
返回脱敏用户列表
8.4 返回结果
查询结果会逐个经过用户脱敏:
{
"code": 0,
"data": [
{
"id": 1,
"username": "demo",
"userAccount": "demo-user",
"userRole": 0
}
],
"message": "ok",
"description": ""
}
如果不传 username,当前代码会查询全部用户,再逐个脱敏。
9. 管理员删除用户流程
9.1 接口信息
POST /api/user/delete
当前方法参数是 long 类型的 id,因此请求体是 JSON 数字:
1
而不是:
{
"id": 1
}
9.2 详细执行过程
管理员提交用户 id
│
▼
UserController.deleteUser
│
├─ 不是管理员?──────► NO_AUTH
├─ id <= 0?─────────► PARAMS_ERROR
│
▼
userService.removeById(id)
│
▼
MyBatis-Plus 根据逻辑删除配置处理
│
▼
返回 Boolean 结果
9.3 逻辑删除
User 实体中的 isDelete 使用逻辑删除标记,配置文件中定义:
| Config | Value |
|---|---|
| 逻辑删除字段 | isDelete |
| 已删除值 | 1 |
| 未删除值 | 0 |
因此删除通常表现为:
isDelete: 0 → 1
数据库记录可能仍然存在,但 MyBatis-Plus 的普通查询会自动排除已删除数据。
9.4 删除响应
成功时:
{
"code": 0,
"data": true,
"message": "ok",
"description": ""
}
data 表示底层删除操作是否成功。
10. 统一响应流程
10.1 成功响应
Controller 通常调用 ResultUtils.success(data),生成:
code = 0
data = 业务数据
message = "ok"
description = ""
返回类型是泛型 BaseResponse
10.2 错误码
| ErrorCode | Code | Meaning |
|---|---|---|
| SUCCESS | 0 | 成功 |
| PARAMS_ERROR | 40000 | 请求参数错误 |
| NULL_ERROR | 40001 | 请求数据为空 |
| NOT_LOGIN | 40100 | 未登录 |
| NO_AUTH | 40101 | 无权限 |
| SYSTEM_ERROR | 50000 | 系统内部异常 |
10.3 业务异常流程
业务代码发现问题
│
▼
throw new BusinessException(...)
│
▼
GlobalExceptionHandler.businessExceptionHandler
│
▼
ResultUtils.error(code, message, description)
│
▼
BaseResponse 错误 JSON
10.4 未知运行时异常流程
未被业务代码处理的 RuntimeException
│
▼
GlobalExceptionHandler.runtimeExceptionHandler
│
▼
记录日志
│
▼
返回 SYSTEM_ERROR
当前实现会把部分运行时异常消息放入响应描述。生产环境应避免把 SQL、路径、主机等内部细节直接返回给客户端。
11. 数据流总览
11.1 注册数据流
注册请求
│ 原始账号、原始密码、确认密码、星球编号
▼
Controller 参数接收
▼
Service 业务校验
▼
密码摘要
▼
User 实体
▼
MySQL.user
▼
新用户 id
11.2 登录数据流
登录请求
│ 账号 + 原始密码
▼
密码摘要
▼
按账号和摘要密码查询
▼
原始 User
▼
getSafetyUser
├────────► Session[userLoginState]
└────────► BaseResponse<User>
11.3 管理员搜索数据流
管理员请求
▼
Session 角色校验
▼
QueryWrapper 条件
▼
MySQL.user 查询
▼
用户列表逐个脱敏
▼
BaseResponse<List<User>>
11.4 管理员删除数据流
管理员请求 id
▼
角色和 id 校验
▼
removeById
▼
isDelete: 0 → 1
▼
Boolean 结果
12. 功能之间的关系
注册
│ 创建账号
▼
登录
│ 建立 Session 登录态
├────────► 当前用户查询
├────────► 注销
└────────► 管理员搜索 / 管理员删除
│
▼
依赖 userRole = 1
业务关系可以概括为:
- 注册负责创建用户数据。
- 登录负责验证身份并建立登录态。
- 当前用户接口负责读取并刷新用户信息。
- 注销负责移除登录态。
- 管理员搜索和删除依赖登录态中的管理员角色。
- 正常结果通过 BaseResponse 返回,异常尽量由全局异常处理器收敛。
13. 当前实现中的主要问题
以下问题都可以从当前源码直接观察到。
13.1 失败返回不统一
注册和登录存在三种失败表达:
- 抛出 BusinessException
- 返回 -1
- 返回 null
建议统一为明确的业务异常或统一的错误响应,避免前端处理隐式协议。
13.2 密码安全性不足
当前使用固定盐值加 MD5。正式项目应改用专门的密码哈希算法,例如 BCrypt、SCrypt 或 Argon2,并为每个用户生成随机盐。
13.3 查重不能完全防止并发重复
当前流程是:
selectCount
▼
判断没有重复
▼
save
并发请求可能同时通过查重,因此数据库仍应为 userAccount 和 planetCode 增加唯一约束。
13.4 管理员权限判断位置较简单
当前权限判断写在 Controller 的 isAdmin 中。随着功能增多,可以抽取统一鉴权组件,避免每个接口重复判断。
13.5 Session 扩容问题
当前使用 Servlet Session。单实例运行比较直观,多实例部署时需要考虑共享 Session、会话粘滞或改用无状态认证。
13.6 生产配置安全
生产配置文件不应直接保存数据库真实密码。应使用环境变量、配置中心或密钥管理服务,并及时轮换已经暴露过的凭据。
13.7 测试覆盖不完整
目前已有 CRUD 和部分注册测试,但登录成功、Session、脱敏、权限、HTTP JSON 响应等场景还需要补充。
14. 推荐阅读顺序
- src/main/java/com/yupi/usercenter/controller/UserController.java
- src/main/java/com/yupi/usercenter/service/UserService.java
- src/main/java/com/yupi/usercenter/service/impl/UserServiceImpl.java
- src/main/java/com/yupi/usercenter/model/domain/User.java
- src/main/java/com/yupi/usercenter/mapper/UserMapper.java
- src/main/resources/mapper/UserMapper.xml
- src/main/java/com/yupi/usercenter/common/
- src/main/java/com/yupi/usercenter/exception/
- src/main/resources/application.yml
- sql/create_table.sql
- src/test/java/com/yupi/usercenter/service/UserServiceTest.java