# 用户聚合查询实施计划 > **日期**: 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% - [ ] 集成测试通过 - [ ] 代码审查通过 - [ ] 文档更新完成 - [ ] 数据库变更成功 - [ ] 缓存正常工作 - [ ] 旧接口仍然可用