- 所有20个任务已完成 - 编译验证通过 - 代码已提交
20 KiB
用户聚合查询实施计划
日期: 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 字段并创建索引
步骤:
- 编写 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); - 在开发环境执行 SQL
- 验证表结构:
DESCRIBE rui_uc_user; - 验证索引:
SHOW INDEX FROM rui_uc_user;
验证标准:
phone字段存在uk_phone唯一索引存在idx_phone普通索引存在
风险: 生产环境需要谨慎,建议在低峰期执行
T2: 数据库变更 - uc_user_detail 移除 phone 字段
目标: 从 uc_user_detail 表移除 phone 字段
步骤:
- 备份数据(可选)
- 编写 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; - 在开发环境执行 SQL
- 验证表结构
验证标准:
phone字段已移除- 其他字段不受影响
风险: 确保数据已迁移或不再使用
T3: 修改 User 实体 - 添加 phone 字段
目标: 在 User.java 实体中添加 phone 字段
文件: rui-service/rui-service-user/src/main/java/com/rui/service/user/entity/User.java
步骤:
- 添加字段:
@Schema(description = "手机号") @SearchField(alias = "phone") private String phone; - 确保字段位置在
username之后 - 编译验证
验证标准:
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
步骤:
- 移除字段:
// 移除以下代码 @Schema(description = "手机号") @SearchField(alias = "phone") private String phone; - 编译验证
验证标准:
UserDetail实体可以正常编译phone字段已移除
T5: 新增 VO 对象
目标: 创建用户聚合查询的 VO 对象
文件:
rui-service/rui-service-user/src/main/java/com/rui/service/user/vo/UserAggregateVO.javarui-service/rui-service-user/src/main/java/com/rui/service/user/vo/UserDeptVO.javarui-service/rui-service-user/src/main/java/com/rui/service/user/vo/UserPostVO.java
步骤:
- 创建
vo包 - 创建
UserAggregateVO.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; } - 创建
UserDeptVO.java - 创建
UserPostVO.java - 编译验证
验证标准:
- 所有 VO 类可以正常编译
- 字段和类型正确
T6: 新增枚举 - AccountType
目标: 创建账号类型枚举
文件: rui-service/rui-service-user/src/main/java/com/rui/service/user/enums/AccountType.java
步骤:
- 创建
enums包 - 创建
AccountType.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; } } - 编译验证
验证标准:
- 枚举可以正常编译
- 包含 USERNAME, PHONE, EMAIL 三个值
T7: 新增 DTO - LoginAccountDTO
目标: 创建登录账号 DTO
文件: rui-service/rui-service-user/src/main/java/com/rui/service/user/dto/LoginAccountDTO.java
步骤:
- 创建
dto包 - 创建
LoginAccountDTO.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; } - 编译验证
验证标准:
- DTO 可以正常编译
- 包含 account 和 accountType 字段
T8: 修改 UserDeptMapper - 添加批量查询方法
目标: 添加根据用户ID列表批量查询部门的方法
文件:
rui-service/rui-service-user/src/main/java/com/rui/service/user/mapper/UserDeptMapper.javarui-service/rui-service-user/src/main/resources/mapper/UserDeptMapper.xml
步骤:
- 在
UserDeptMapper.java添加方法:List<UserDeptVO> selectDeptListByUserIds(@Param("userIds") List<Long> userIds); - 在
UserDeptMapper.xml添加 SQL:<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> - 编译验证
验证标准:
- Mapper 接口可以正常编译
- XML 语法正确
T9: 修改 UserPostMapper - 添加批量查询方法
目标: 添加根据用户ID列表批量查询岗位的方法
文件:
rui-service/rui-service-user/src/main/java/com/rui/service/user/mapper/UserPostMapper.javarui-service/rui-service-user/src/main/resources/mapper/UserPostMapper.xml
步骤:
- 在
UserPostMapper.java添加方法:List<UserPostVO> selectPostListByUserIds(@Param("userIds") List<Long> userIds); - 在
UserPostMapper.xml添加 SQL - 编译验证
验证标准:
- Mapper 接口可以正常编译
- XML 语法正确
T10: 修改 UserService - 添加聚合查询方法
目标: 在 UserService 中添加聚合查询逻辑
文件:
rui-service/rui-service-user/src/main/java/com/rui/service/user/service/IUserService.javarui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserServiceImpl.java
步骤:
- 在
IUserService.java添加方法:UserAggregateVO getUserAggregate(Long userId); Page<UserAggregateVO> listUserAggregate(Page<User> page, QueryWrapper<User> query); - 在
UserServiceImpl.java实现方法 - 注入
UserDeptMapper和UserPostMapper - 实现单用户聚合查询
- 实现批量列表查询(使用 IN 批量查询)
- 编译验证
验证标准:
- Service 接口可以正常编译
- 实现类可以正常编译
- 聚合查询逻辑正确
T11: 修改 UserController - 添加聚合接口
目标: 添加用户聚合查询接口
文件: rui-service/rui-service-user/src/main/java/com/rui/service/user/controller/UserController.java
步骤:
- 添加方法:
@Operation(summary = "获取用户聚合信息") @GetMapping("/{id}/aggregate") public Result<UserAggregateVO> getUserAggregate(@PathVariable Long id) { return Result.ok(service.getUserAggregate(id)); } - 编译验证
验证标准:
- Controller 可以正常编译
- 接口路径正确
T12: 修改 UserInnerController - 添加统一认证接口
目标: 添加统一认证查询接口
文件: rui-service/rui-service-user/src/main/java/com/rui/service/user/controller/inner/UserInnerController.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); } - 将原有
loadByUsername标记为@Deprecated - 编译验证
验证标准:
- Controller 可以正常编译
- 新接口可以处理不同账号类型
- 旧接口仍然可用但标记为弃用
T13: 修改 UserAuthFeign - 添加统一认证方法
目标: 在 Feign 客户端添加新方法
文件: rui-common/rui-common-oauth2/src/main/java/com/rui/common/oauth2/feign/UserAuthFeign.java
步骤:
- 添加新方法:
@PostMapping("/auth/load") Result<JSONObject> loadUser(@RequestBody LoginAccountDTO loginAccount); - 将原有
loadUser方法(基于 username)标记为@Deprecated - 编译验证
验证标准:
- Feign 接口可以正常编译
- 新方法参数正确
T14: 修改 RemoteUserDetailsService - 支持新接口
目标: 修改认证服务以支持新的统一认证接口
文件: rui-common/rui-common-oauth2/src/main/java/com/rui/common/oauth2/service/RemoteUserDetailsService.java
步骤:
- 修改
loadUserByUsername方法,改为调用新的loadUser方法 - 或者新增方法
loadUserByAccount - 确保兼容性
- 编译验证
验证标准:
- 认证服务可以正常编译
- 支持用户名和手机号登录
T15: 添加 Redis 缓存
目标: 为用户聚合数据添加 Redis 缓存
文件: rui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserServiceImpl.java
步骤:
- 注入
RedisUtil - 在
getUserAggregate方法中添加缓存逻辑: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; } - 编译验证
验证标准:
- 缓存可以正常读写
- TTL 设置正确
T16: 缓存失效
目标: 在用户数据变更时清除缓存
文件:
rui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserDeptServiceImpl.javarui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserPostServiceImpl.javarui-service/rui-service-user/src/main/java/com/rui/service/user/service/impl/UserServiceImpl.java
步骤:
- 在
UserDeptServiceImpl的assignDepts和setMainDept方法中添加缓存清除 - 在
UserPostServiceImpl的assignPosts方法中添加缓存清除 - 在
UserServiceImpl的update方法中添加缓存清除 - 编写通用的缓存清除方法
验证标准:
- 数据变更后缓存被清除
- 下次查询会重新加载数据
T17: 编写 SQL 升级脚本
目标: 创建数据库升级脚本
文件: sql/upgrade-v2.x-add-phone-to-user.sql
步骤:
- 创建 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; - 验证 SQL 语法
验证标准:
- SQL 语法正确
- 可以在开发环境正常执行
T18: 单元测试
目标: 编写单元测试
文件:
rui-service/rui-service-user/src/test/java/com/rui/service/user/service/UserServiceTest.javarui-service/rui-service-user/src/test/java/com/rui/service/user/controller/UserControllerTest.java
步骤:
- 测试
getUserAggregate方法 - 测试
listUserAggregate方法 - 测试缓存命中和失效
- 测试统一认证接口
- 运行测试
验证标准:
- 所有测试通过
- 覆盖主要业务场景
T19: 集成测试
目标: 进行集成测试
步骤:
- 启动服务
- 测试聚合接口
- 测试统一认证接口
- 测试缓存
- 验证旧接口仍然可用
验证标准:
- 所有接口正常响应
- 数据正确
- 缓存有效
T20: 文档更新
目标: 更新相关文档
步骤:
- 更新 API 文档(Swagger)
- 更新数据库文档
- 更新接口文档
验证标准:
- 文档与实际代码一致
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. 回滚计划
如果实施过程中出现问题,可以按以下顺序回滚:
- 代码回滚:使用 git 回滚到上一个版本
- 数据库回滚:
-- 移除 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%
- 集成测试通过
- 代码审查通过
- 文档更新完成
- 数据库变更成功
- 缓存正常工作
- 旧接口仍然可用