Files
rui-docs/superpowers/plans/2026-06-06-user-aggregate-query-plan.md
T
vifo 3c2fa877a6 docs(plan): 用户聚合查询实施计划
- 20个详细任务分解
- 包含依赖关系图
- 数据库变更、代码修改、缓存、测试全覆盖
- 风险评估和回滚计划
2026-06-06 13:32:44 +08:00

726 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 用户聚合查询实施计划
> **日期**: 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%
- [ ] 集成测试通过
- [ ] 代码审查通过
- [ ] 文档更新完成
- [ ] 数据库变更成功
- [ ] 缓存正常工作
- [ ] 旧接口仍然可用