diff --git a/superpowers/plans/2026-06-06-user-aggregate-query-plan.md b/superpowers/plans/2026-06-06-user-aggregate-query-plan.md new file mode 100644 index 0000000..7680056 --- /dev/null +++ b/superpowers/plans/2026-06-06-user-aggregate-query-plan.md @@ -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 | 新增 DTO:LoginAccountDTO | 高 | 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 depts; + + @Schema(description = "岗位列表") + private List 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 selectDeptListByUserIds(@Param("userIds") List userIds); + ``` +2. [ ] 在 `UserDeptMapper.xml` 添加 SQL: + ```xml + + ``` +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 selectPostListByUserIds(@Param("userIds") List 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 listUserAggregate(Page page, QueryWrapper 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 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 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 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% +- [ ] 集成测试通过 +- [ ] 代码审查通过 +- [ ] 文档更新完成 +- [ ] 数据库变更成功 +- [ ] 缓存正常工作 +- [ ] 旧接口仍然可用