Files
rui-docs/superpowers/plans/2026-06-06-user-aggregate-query-plan.md
T
vifo a4767ee3d0 docs(plan): 更新实施计划状态为已完成
- 所有20个任务已完成
- 编译验证通过
- 代码已提交
2026-06-06 17:10:40 +08:00

20 KiB
Raw Blame History

用户聚合查询实施计划

日期: 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
    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
    -- 先迁移数据(如果有)
    -- 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. 添加字段:
    @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. 移除字段:
    // 移除以下代码
    @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
    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
    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
    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 添加方法:
    List<UserDeptVO> selectDeptListByUserIds(@Param("userIds") List<Long> userIds);
    
  2. 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>
    
  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 添加方法:
    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 添加方法:
    UserAggregateVO getUserAggregate(Long userId);
    
    Page<UserAggregateVO> listUserAggregate(Page<User> page, QueryWrapper<User> query);
    
  2. UserServiceImpl.java 实现方法
  3. 注入 UserDeptMapperUserPostMapper
  4. 实现单用户聚合查询
  5. 实现批量列表查询(使用 IN 批量查询)
  6. 编译验证

验证标准:

  • Service 接口可以正常编译
  • 实现类可以正常编译
  • 聚合查询逻辑正确

T11: 修改 UserController - 添加聚合接口

目标: 添加用户聚合查询接口

文件: rui-service/rui-service-user/src/main/java/com/rui/service/user/controller/UserController.java

步骤:

  1. 添加方法:
    @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. 添加新方法:
    @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. 添加新方法:
    @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 方法中添加缓存逻辑:
    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. UserDeptServiceImplassignDeptssetMainDept 方法中添加缓存清除
  2. UserPostServiceImplassignPosts 方法中添加缓存清除
  3. UserServiceImplupdate 方法中添加缓存清除
  4. 编写通用的缓存清除方法

验证标准:

  • 数据变更后缓存被清除
  • 下次查询会重新加载数据

T17: 编写 SQL 升级脚本

目标: 创建数据库升级脚本

文件: sql/upgrade-v2.x-add-phone-to-user.sql

步骤:

  1. 创建 SQL 文件
  2. 编写升级脚本:
    -- 升级脚本:将 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. 数据库回滚
    -- 移除 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%
  • 集成测试通过
  • 代码审查通过
  • 文档更新完成
  • 数据库变更成功
  • 缓存正常工作
  • 旧接口仍然可用