docs(plan): 用户聚合查询实施计划

- 20个详细任务分解
- 包含依赖关系图
- 数据库变更、代码修改、缓存、测试全覆盖
- 风险评估和回滚计划
This commit is contained in:
2026-06-06 13:32:44 +08:00
parent de78c21799
commit 3c2fa877a6
@@ -0,0 +1,725 @@
# 用户聚合查询实施计划
> **日期**: 2026-06-06
> **状态**: 已批准
> **关联 Spec**: `docs/superpowers/specs/2026-06-06-user-aggregate-query-design.md`
---
## 1. 任务总览
### 1.1 任务清单
| 编号 | 任务名称 | 优先级 | 预估时间 | 依赖 |
|------|---------|--------|---------|------|
| T1 | 数据库变更:uc_user 添加 phone 字段 | 高 | 20分钟 | 无 |
| T2 | 数据库变更:uc_user_detail 移除 phone 字段 | 高 | 15分钟 | T1 |
| T3 | 修改 User 实体:添加 phone 字段 | 高 | 15分钟 | T1 |
| T4 | 修改 UserDetail 实体:移除 phone 字段 | 高 | 10分钟 | T2 |
| T5 | 新增 VO 对象:UserAggregateVO, UserDeptVO, UserPostVO | 高 | 20分钟 | 无 |
| T6 | 新增枚举:AccountType | 高 | 10分钟 | 无 |
| T7 | 新增 DTOLoginAccountDTO | 高 | 10分钟 | T6 |
| T8 | 修改 UserDeptMapper:添加批量查询方法 | 高 | 20分钟 | 无 |
| T9 | 修改 UserPostMapper:添加批量查询方法 | 高 | 20分钟 | 无 |
| T10 | 修改 UserService:添加聚合查询方法 | 高 | 30分钟 | T3, T5, T8, T9 |
| T11 | 修改 UserController:添加聚合接口 | 高 | 20分钟 | T10 |
| T12 | 修改 UserInnerController:添加统一认证接口 | 高 | 25分钟 | T3, T7 |
| T13 | 修改 UserAuthFeign:添加统一认证方法 | 高 | 15分钟 | T12 |
| T14 | 修改 RemoteUserDetailsService:支持新接口 | 高 | 20分钟 | T13 |
| T15 | 添加缓存:Redis 缓存用户聚合数据 | 中 | 25分钟 | T10 |
| T16 | 缓存失效:数据变更时清除缓存 | 中 | 20分钟 | T15 |
| T17 | 编写 SQL 升级脚本 | 高 | 15分钟 | T1, T2 |
| T18 | 单元测试 | 中 | 40分钟 | T10, T12 |
| T19 | 集成测试 | 中 | 30分钟 | T18 |
| T20 | 文档更新 | 低 | 15分钟 | 全部 |
### 1.2 依赖关系图
```
T1 (数据库添加phone)
├── T3 (User实体添加phone)
│ ├── T10 (UserService聚合查询)
│ │ ├── T11 (UserController聚合接口)
│ │ ├── T15 (Redis缓存)
│ │ │ └── T16 (缓存失效)
│ │ └── T18 (单元测试)
│ └── T12 (统一认证接口)
│ ├── T13 (UserAuthFeign)
│ │ └── T14 (RemoteUserDetailsService)
│ └── T18 (单元测试)
├── T7 (LoginAccountDTO)
│ └── T12 (统一认证接口)
└── T17 (SQL脚本)
T2 (数据库移除phone)
└── T4 (UserDetail实体移除phone)
T5 (VO对象)
└── T10 (UserService聚合查询)
T6 (AccountType枚举)
├── T7 (LoginAccountDTO)
└── T12 (统一认证接口)
T8 (UserDeptMapper批量查询)
└── T10 (UserService聚合查询)
T9 (UserPostMapper批量查询)
└── T10 (UserService聚合查询)
T18 (单元测试)
└── T19 (集成测试)
T20 (文档更新)
```
---
## 2. 详细任务
### T1: 数据库变更 - uc_user 添加 phone 字段
**目标**: 在 `uc_user` 表添加 `phone` 字段并创建索引
**步骤**:
1. [ ] 编写 SQL
```sql
ALTER TABLE rui_uc_user
ADD COLUMN phone VARCHAR(20) DEFAULT NULL COMMENT '手机号' AFTER username;
ALTER TABLE rui_uc_user
ADD UNIQUE KEY uk_phone (tenant_id, phone);
ALTER TABLE rui_uc_user
ADD INDEX idx_phone (phone);
```
2. [ ] 在开发环境执行 SQL
3. [ ] 验证表结构:`DESCRIBE rui_uc_user;`
4. [ ] 验证索引:`SHOW INDEX FROM rui_uc_user;`
**验证标准**:
- `phone` 字段存在
- `uk_phone` 唯一索引存在
- `idx_phone` 普通索引存在
**风险**: 生产环境需要谨慎,建议在低峰期执行
---
### T2: 数据库变更 - uc_user_detail 移除 phone 字段
**目标**: 从 `uc_user_detail` 表移除 `phone` 字段
**步骤**:
1. [ ] 备份数据(可选)
2. [ ] 编写 SQL
```sql
-- 先迁移数据(如果有)
-- UPDATE rui_uc_user u
-- JOIN rui_uc_user_detail d ON u.id = d.user_id
-- SET u.phone = d.phone
-- WHERE u.phone IS NULL AND d.phone IS NOT NULL;
ALTER TABLE rui_uc_user_detail
DROP COLUMN phone;
```
3. [ ] 在开发环境执行 SQL
4. [ ] 验证表结构
**验证标准**:
- `phone` 字段已移除
- 其他字段不受影响
**风险**: 确保数据已迁移或不再使用
---
### T3: 修改 User 实体 - 添加 phone 字段
**目标**: 在 `User.java` 实体中添加 `phone` 字段
**文件**: `rui-service/rui-service-user/src/main/java/com/rui/service/user/entity/User.java`
**步骤**:
1. [ ] 添加字段:
```java
@Schema(description = "手机号")
@SearchField(alias = "phone")
private String phone;
```
2. [ ] 确保字段位置在 `username` 之后
3. [ ] 编译验证
**验证标准**:
- `User` 实体可以正常编译
- `phone` 字段有 getter/setter@Data 自动生成)
---
### T4: 修改 UserDetail 实体 - 移除 phone 字段
**目标**: 从 `UserDetail.java` 实体中移除 `phone` 字段
**文件**: `rui-service/rui-service-user/src/main/java/com/rui/service/user/entity/UserDetail.java`
**步骤**:
1. [ ] 移除字段:
```java
// 移除以下代码
@Schema(description = "手机号")
@SearchField(alias = "phone")
private String phone;
```
2. [ ] 编译验证
**验证标准**:
- `UserDetail` 实体可以正常编译
- `phone` 字段已移除
---
### T5: 新增 VO 对象
**目标**: 创建用户聚合查询的 VO 对象
**文件**:
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/vo/UserAggregateVO.java`
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/vo/UserDeptVO.java`
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/vo/UserPostVO.java`
**步骤**:
1. [ ] 创建 `vo` 包
2. [ ] 创建 `UserAggregateVO.java`
```java
package com.rui.service.user.vo;
import com.rui.service.user.entity.User;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户聚合信息")
public class UserAggregateVO extends User {
@Schema(description = "部门列表")
private List<UserDeptVO> depts;
@Schema(description = "岗位列表")
private List<UserPostVO> posts;
@Schema(description = "主部门ID")
private Long mainDeptId;
@Schema(description = "主部门名称")
private String mainDeptName;
@Schema(description = "部门编码")
private String deptCode;
@Schema(description = "岗位编码")
private String postCode;
}
```
3. [ ] 创建 `UserDeptVO.java`
4. [ ] 创建 `UserPostVO.java`
5. [ ] 编译验证
**验证标准**:
- 所有 VO 类可以正常编译
- 字段和类型正确
---
### T6: 新增枚举 - AccountType
**目标**: 创建账号类型枚举
**文件**: `rui-service/rui-service-user/src/main/java/com/rui/service/user/enums/AccountType.java`
**步骤**:
1. [ ] 创建 `enums` 包
2. [ ] 创建 `AccountType.java`
```java
package com.rui.service.user.enums;
import lombok.Getter;
@Getter
public enum AccountType {
USERNAME("用户名"),
PHONE("手机号"),
EMAIL("邮箱");
private final String description;
AccountType(String description) {
this.description = description;
}
}
```
3. [ ] 编译验证
**验证标准**:
- 枚举可以正常编译
- 包含 USERNAME, PHONE, EMAIL 三个值
---
### T7: 新增 DTO - LoginAccountDTO
**目标**: 创建登录账号 DTO
**文件**: `rui-service/rui-service-user/src/main/java/com/rui/service/user/dto/LoginAccountDTO.java`
**步骤**:
1. [ ] 创建 `dto` 包
2. [ ] 创建 `LoginAccountDTO.java`
```java
package com.rui.service.user.dto;
import com.rui.service.user.enums.AccountType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(description = "登录账号信息")
public class LoginAccountDTO {
@Schema(description = "账号")
private String account;
@Schema(description = "账号类型")
private AccountType accountType;
}
```
3. [ ] 编译验证
**验证标准**:
- DTO 可以正常编译
- 包含 account 和 accountType 字段
---
### T8: 修改 UserDeptMapper - 添加批量查询方法
**目标**: 添加根据用户ID列表批量查询部门的方法
**文件**:
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/mapper/UserDeptMapper.java`
- `rui-service/rui-service-user/src/main/resources/mapper/UserDeptMapper.xml`
**步骤**:
1. [ ] 在 `UserDeptMapper.java` 添加方法:
```java
List<UserDeptVO> selectDeptListByUserIds(@Param("userIds") List<Long> userIds);
```
2. [ ] 在 `UserDeptMapper.xml` 添加 SQL
```xml
<select id="selectDeptListByUserIds" resultType="com.rui.service.user.vo.UserDeptVO">
SELECT
ud.user_id as userId,
ud.dept_id as deptId,
d.dept_code as deptCode,
d.name as deptName,
ud.is_main as main
FROM uc_user_dept ud
INNER JOIN uc_dept d ON ud.dept_id = d.id
WHERE ud.user_id IN
<foreach collection="userIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND ud.deleted = 0
AND d.deleted = 0
</select>
```
3. [ ] 编译验证
**验证标准**:
- Mapper 接口可以正常编译
- XML 语法正确
---
### T9: 修改 UserPostMapper - 添加批量查询方法
**目标**: 添加根据用户ID列表批量查询岗位的方法
**文件**:
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/mapper/UserPostMapper.java`
- `rui-service/rui-service-user/src/main/resources/mapper/UserPostMapper.xml`
**步骤**:
1. [ ] 在 `UserPostMapper.java` 添加方法:
```java
List<UserPostVO> selectPostListByUserIds(@Param("userIds") List<Long> userIds);
```
2. [ ] 在 `UserPostMapper.xml` 添加 SQL
3. [ ] 编译验证
**验证标准**:
- Mapper 接口可以正常编译
- XML 语法正确
---
### T10: 修改 UserService - 添加聚合查询方法
**目标**: 在 UserService 中添加聚合查询逻辑
**文件**:
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/service/IUserService.java`
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserServiceImpl.java`
**步骤**:
1. [ ] 在 `IUserService.java` 添加方法:
```java
UserAggregateVO getUserAggregate(Long userId);
Page<UserAggregateVO> listUserAggregate(Page<User> page, QueryWrapper<User> query);
```
2. [ ] 在 `UserServiceImpl.java` 实现方法
3. [ ] 注入 `UserDeptMapper` 和 `UserPostMapper`
4. [ ] 实现单用户聚合查询
5. [ ] 实现批量列表查询(使用 IN 批量查询)
6. [ ] 编译验证
**验证标准**:
- Service 接口可以正常编译
- 实现类可以正常编译
- 聚合查询逻辑正确
---
### T11: 修改 UserController - 添加聚合接口
**目标**: 添加用户聚合查询接口
**文件**: `rui-service/rui-service-user/src/main/java/com/rui/service/user/controller/UserController.java`
**步骤**:
1. [ ] 添加方法:
```java
@Operation(summary = "获取用户聚合信息")
@GetMapping("/{id}/aggregate")
public Result<UserAggregateVO> getUserAggregate(@PathVariable Long id) {
return Result.ok(service.getUserAggregate(id));
}
```
2. [ ] 编译验证
**验证标准**:
- Controller 可以正常编译
- 接口路径正确
---
### T12: 修改 UserInnerController - 添加统一认证接口
**目标**: 添加统一认证查询接口
**文件**: `rui-service/rui-service-user/src/main/java/com/rui/service/user/controller/inner/UserInnerController.java`
**步骤**:
1. [ ] 添加新方法:
```java
@PostMapping("/auth/load")
public Result<JSONObject> loadUser(@RequestBody LoginAccountDTO loginAccount) {
User user;
switch (loginAccount.getAccountType()) {
case PHONE:
user = userService.lambdaQuery()
.eq(User::getPhone, loginAccount.getAccount())
.one();
break;
case EMAIL:
// 如果 User 实体有 email 字段
user = userService.lambdaQuery()
.eq(User::getEmail, loginAccount.getAccount())
.one();
break;
case USERNAME:
default:
user = userService.lambdaQuery()
.eq(User::getUsername, loginAccount.getAccount())
.one();
break;
}
if (user == null) {
return Result.ok(null);
}
// 组装认证信息(复用现有逻辑)
JSONObject info = buildAuthInfo(user);
return Result.ok(info);
}
```
2. [ ] 将原有 `loadByUsername` 标记为 `@Deprecated`
3. [ ] 编译验证
**验证标准**:
- Controller 可以正常编译
- 新接口可以处理不同账号类型
- 旧接口仍然可用但标记为弃用
---
### T13: 修改 UserAuthFeign - 添加统一认证方法
**目标**: 在 Feign 客户端添加新方法
**文件**: `rui-common/rui-common-oauth2/src/main/java/com/rui/common/oauth2/feign/UserAuthFeign.java`
**步骤**:
1. [ ] 添加新方法:
```java
@PostMapping("/auth/load")
Result<JSONObject> loadUser(@RequestBody LoginAccountDTO loginAccount);
```
2. [ ] 将原有 `loadUser` 方法(基于 username)标记为 `@Deprecated`
3. [ ] 编译验证
**验证标准**:
- Feign 接口可以正常编译
- 新方法参数正确
---
### T14: 修改 RemoteUserDetailsService - 支持新接口
**目标**: 修改认证服务以支持新的统一认证接口
**文件**: `rui-common/rui-common-oauth2/src/main/java/com/rui/common/oauth2/service/RemoteUserDetailsService.java`
**步骤**:
1. [ ] 修改 `loadUserByUsername` 方法,改为调用新的 `loadUser` 方法
2. [ ] 或者新增方法 `loadUserByAccount`
3. [ ] 确保兼容性
4. [ ] 编译验证
**验证标准**:
- 认证服务可以正常编译
- 支持用户名和手机号登录
---
### T15: 添加 Redis 缓存
**目标**: 为用户聚合数据添加 Redis 缓存
**文件**: `rui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserServiceImpl.java`
**步骤**:
1. [ ] 注入 `RedisUtil`
2. [ ] 在 `getUserAggregate` 方法中添加缓存逻辑:
```java
public UserAggregateVO getUserAggregate(Long userId) {
String cacheKey = String.format("user:agg:%s:%s", tenantId, userId);
// 尝试从缓存获取
UserAggregateVO cached = redisUtil.getObj(cacheKey, UserAggregateVO.class);
if (cached != null) {
return cached;
}
// 查询数据库...
UserAggregateVO vo = // ... 查询逻辑
// 写入缓存(10分钟)
redisUtil.set(cacheKey, vo, Duration.ofMinutes(10));
return vo;
}
```
3. [ ] 编译验证
**验证标准**:
- 缓存可以正常读写
- TTL 设置正确
---
### T16: 缓存失效
**目标**: 在用户数据变更时清除缓存
**文件**:
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserDeptServiceImpl.java`
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserPostServiceImpl.java`
- `rui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserServiceImpl.java`
**步骤**:
1. [ ] 在 `UserDeptServiceImpl` 的 `assignDepts` 和 `setMainDept` 方法中添加缓存清除
2. [ ] 在 `UserPostServiceImpl` 的 `assignPosts` 方法中添加缓存清除
3. [ ] 在 `UserServiceImpl` 的 `update` 方法中添加缓存清除
4. [ ] 编写通用的缓存清除方法
**验证标准**:
- 数据变更后缓存被清除
- 下次查询会重新加载数据
---
### T17: 编写 SQL 升级脚本
**目标**: 创建数据库升级脚本
**文件**: `sql/upgrade-v2.x-add-phone-to-user.sql`
**步骤**:
1. [ ] 创建 SQL 文件
2. [ ] 编写升级脚本:
```sql
-- 升级脚本:将 phone 从 uc_user_detail 迁移到 uc_user
-- 1. 在 uc_user 表添加 phone 字段
ALTER TABLE rui_uc_user
ADD COLUMN phone VARCHAR(20) DEFAULT NULL COMMENT '手机号' AFTER username;
-- 2. 添加唯一索引
ALTER TABLE rui_uc_user
ADD UNIQUE KEY uk_phone (tenant_id, phone);
-- 3. 添加普通索引
ALTER TABLE rui_uc_user
ADD INDEX idx_phone (phone);
-- 4. 迁移数据(如果有)
-- UPDATE rui_uc_user u
-- JOIN rui_uc_user_detail d ON u.id = d.user_id
-- SET u.phone = d.phone
-- WHERE u.phone IS NULL AND d.phone IS NOT NULL;
-- 5. 从 uc_user_detail 移除 phone 字段
ALTER TABLE rui_uc_user_detail
DROP COLUMN phone;
```
3. [ ] 验证 SQL 语法
**验证标准**:
- SQL 语法正确
- 可以在开发环境正常执行
---
### T18: 单元测试
**目标**: 编写单元测试
**文件**:
- `rui-service/rui-service-user/src/test/java/com/rui/service/user/service/UserServiceTest.java`
- `rui-service/rui-service-user/src/test/java/com/rui/service/user/controller/UserControllerTest.java`
**步骤**:
1. [ ] 测试 `getUserAggregate` 方法
2. [ ] 测试 `listUserAggregate` 方法
3. [ ] 测试缓存命中和失效
4. [ ] 测试统一认证接口
5. [ ] 运行测试
**验证标准**:
- 所有测试通过
- 覆盖主要业务场景
---
### T19: 集成测试
**目标**: 进行集成测试
**步骤**:
1. [ ] 启动服务
2. [ ] 测试聚合接口
3. [ ] 测试统一认证接口
4. [ ] 测试缓存
5. [ ] 验证旧接口仍然可用
**验证标准**:
- 所有接口正常响应
- 数据正确
- 缓存有效
---
### T20: 文档更新
**目标**: 更新相关文档
**步骤**:
1. [ ] 更新 API 文档(Swagger
2. [ ] 更新数据库文档
3. [ ] 更新接口文档
**验证标准**:
- 文档与实际代码一致
---
## 3. 实施顺序建议
### 阶段 1:数据库变更(T1, T2, T17
先执行数据库变更,为后续代码修改做准备。
### 阶段 2:基础代码(T3, T4, T5, T6, T7, T8, T9
修改实体、新增 VO/DTO/枚举、修改 Mapper。
### 阶段 3:核心业务(T10, T11, T12, T13, T14
实现聚合查询和统一认证接口。
### 阶段 4:缓存优化(T15, T16
添加缓存和失效机制。
### 阶段 5:测试(T18, T19
编写和运行测试。
### 阶段 6:文档(T20
更新文档。
---
## 4. 风险评估
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|----------|
| 数据库变更失败 | 低 | 高 | 备份数据,先在开发环境测试 |
| 缓存数据不一致 | 中 | 中 | 完善缓存失效机制 |
| 旧接口不兼容 | 低 | 高 | 保留旧接口,标记为弃用 |
| 手机号唯一性冲突 | 中 | 中 | 数据迁移时处理重复数据 |
| 性能问题 | 低 | 中 | 批量查询优化,添加索引 |
---
## 5. 回滚计划
如果实施过程中出现问题,可以按以下顺序回滚:
1. **代码回滚**:使用 git 回滚到上一个版本
2. **数据库回滚**
```sql
-- 移除 uc_user 的 phone 字段
ALTER TABLE rui_uc_user DROP COLUMN phone;
ALTER TABLE rui_uc_user DROP INDEX uk_phone;
ALTER TABLE rui_uc_user DROP INDEX idx_phone;
-- 在 uc_user_detail 添加 phone 字段
ALTER TABLE rui_uc_user_detail
ADD COLUMN phone VARCHAR(20) DEFAULT NULL COMMENT '手机号' AFTER email;
```
---
## 6. 验收标准
- [ ] 所有任务完成
- [ ] 单元测试通过率 100%
- [ ] 集成测试通过
- [ ] 代码审查通过
- [ ] 文档更新完成
- [ ] 数据库变更成功
- [ ] 缓存正常工作
- [ ] 旧接口仍然可用