From de78c21799332ac0534cf3dc8baa54d411b2680a Mon Sep 17 00:00:00 2001 From: pigeon Date: Sat, 6 Jun 2026 13:30:23 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E6=9B=B4=E6=96=B0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=81=9A=E5=90=88=E6=9F=A5=E8=AF=A2=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E8=A7=84=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 phone 字段迁移到 uc_user 表的设计 - 新增统一认证接口 /user/inner/auth/load(POST) - 支持 AccountType 枚举:USERNAME/PHONE/EMAIL - 废弃旧的 loadByUsername 接口 - 添加数据库变更 SQL 脚本 --- .../2026-06-06-user-aggregate-query-design.md | 219 ++++++++++++++---- 1 file changed, 168 insertions(+), 51 deletions(-) diff --git a/superpowers/specs/2026-06-06-user-aggregate-query-design.md b/superpowers/specs/2026-06-06-user-aggregate-query-design.md index 36b3ab5..4a3e54a 100644 --- a/superpowers/specs/2026-06-06-user-aggregate-query-design.md +++ b/superpowers/specs/2026-06-06-user-aggregate-query-design.md @@ -25,6 +25,8 @@ - **前端请求过多**:获取完整用户信息需要3个独立请求 - **列表页性能差**:100条用户数据需要 1 + 100 + 100 = 201 个请求 - **数据一致性难保证**:多个请求可能部分失败 +- **手机号位置不合理**:手机号在 `uc_user_detail` 表,但短信登录需要频繁查询,应该提升到 `uc_user` 表 +- **认证接口不灵活**:当前 `loadByUsername` 只支持用户名,无法扩展支持手机号、邮箱等多种登录方式 ### 1.3 约束条件 @@ -32,6 +34,7 @@ - 需要支持 **多租户**(tenantId) - 必须 **保持向后兼容** - 需要 **缓存优化** +- 手机号需要 **唯一约束**(按租户) --- @@ -40,7 +43,9 @@ 1. **减少前端请求**:从3个减少到1个 2. **优化列表页性能**:批量查询,避免 N+1 3. **引入缓存**:Redis 缓存用户聚合数据 -4. **保持兼容性**:现有接口不受影响 +4. **手机号迁移**:将 `phone` 从 `uc_user_detail` 迁移到 `uc_user` 表 +5. **统一认证接口**:支持多种登录方式(用户名、手机号、邮箱等) +6. **保持兼容性**:现有接口不受影响 --- @@ -95,9 +100,6 @@ import java.util.List; @Schema(description = "用户聚合信息") public class UserAggregateVO extends User { - @Schema(description = "用户详情") - private UserDetailVO detail; - @Schema(description = "部门列表") private List depts; @@ -109,6 +111,12 @@ public class UserAggregateVO extends User { @Schema(description = "主部门名称") private String mainDeptName; + + @Schema(description = "部门编码") + private String deptCode; + + @Schema(description = "岗位编码") + private String postCode; } ``` @@ -118,9 +126,15 @@ public class UserAggregateVO extends User { @Data @Schema(description = "用户部门信息") public class UserDeptVO { + @Schema(description = "用户ID") + private Long userId; + @Schema(description = "部门ID") private Long deptId; + @Schema(description = "部门编码") + private String deptCode; + @Schema(description = "部门名称") private String deptName; @@ -131,9 +145,15 @@ public class UserDeptVO { @Data @Schema(description = "用户岗位信息") public class UserPostVO { + @Schema(description = "用户ID") + private Long userId; + @Schema(description = "岗位ID") private Long postId; + @Schema(description = "岗位编码") + private String postCode; + @Schema(description = "岗位名称") private String postName; } @@ -160,23 +180,20 @@ GET /user/admin/user/{id}/aggregate "data": { "id": 1, "username": "admin", + "phone": "13800138000", "userNo": "U001", "userType": 2, "status": 1, - "detail": { - "nickname": "管理员", - "realName": "系统管理员", - "email": "admin@example.com", - "phone": "13800138000" - }, "depts": [ { "deptId": 1, + "deptCode": "TECH", "deptName": "技术部", "main": true }, { "deptId": 2, + "deptCode": "PROD", "deptName": "产品部", "main": false } @@ -184,11 +201,14 @@ GET /user/admin/user/{id}/aggregate "posts": [ { "postId": 1, + "postCode": "JAVA_DEV", "postName": "Java开发工程师" } ], "mainDeptId": 1, - "mainDeptName": "技术部" + "mainDeptName": "技术部", + "deptCode": "TECH", + "postCode": "JAVA_DEV" } } ``` @@ -202,14 +222,87 @@ GET /user/admin/user/{id}/aggregate - 批量查询所有用户的部门和岗位 - 组装到响应中 -### 5.2 现有接口保持不变 +#### 5.1.3 统一用户认证查询(内部接口) -以下接口继续保留,不受影响: +``` +POST /user/inner/auth/load +``` + +**请求体:** + +```json +{ + "account": "13800138000", + "loginType": "PHONE" +} +``` + +**AccountType 枚举:** + +```java +public enum AccountType { + USERNAME("用户名"), + PHONE("手机号"), + EMAIL("邮箱"); + + private final String description; + + AccountType(String description) { + this.description = description; + } +} +``` + +**响应:** 与现有 `loadByUsername` 保持一致,返回用户认证信息 + +**实现逻辑:** + +```java +@PostMapping("/auth/load") +public Result loadUser(@RequestBody LoginAccountDTO loginAccount) { + User user; + + switch (loginAccount.getLoginType()) { + case PHONE: + user = userService.lambdaQuery() + .eq(User::getPhone, loginAccount.getAccount()) + .one(); + break; + case 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); +} +``` + +### 5.2 现有接口处理 + +**保留但标记为弃用:** + +- `GET /user/inner/auth/loadByUsername/{username}` — **@Deprecated**,建议迁移到新的 `/auth/load` + +**继续保留的接口:** - `GET /user/admin/user/{id}` — 用户基础信息 - `GET /user/admin/user-dept/user/{userId}` — 用户部门列表 - `GET /user/admin/user-post/user/{userId}` — 用户岗位列表 -- `GET /user/admin/detail` — 用户详情(uc_user_detail) +- `GET /user/admin/detail` — 用户详情(uc_user_detail,phone 字段将移除) --- @@ -262,22 +355,15 @@ public UserAggregateVO getUserAggregate(Long userId, Long tenantId) { return null; } - // 2. 查询用户详情 - UserDetail detail = userDetailMapper.selectOne( - new LambdaQueryWrapper() - .eq(UserDetail::getUserId, userId) - ); - - // 3. 查询部门(带名称) + // 2. 查询部门(带名称) List depts = userDeptMapper.selectDeptListByUserId(userId); - // 4. 查询岗位(带名称) + // 3. 查询岗位(带名称) List posts = userPostMapper.selectPostListByUserId(userId); - // 5. 组装 + // 4. 组装 UserAggregateVO vo = new UserAggregateVO(); BeanUtils.copyProperties(user, vo); - vo.setDetail(convertDetail(detail)); vo.setDepts(depts); vo.setPosts(posts); @@ -327,22 +413,12 @@ public Page listUserAggregate(Page page, QueryWrapper detailMap = userDetailMapper - .selectList( - new LambdaQueryWrapper() - .in(UserDetail::getUserId, userIds) - ) - .stream() - .collect(Collectors.toMap(UserDetail::getUserId, Function.identity())); - - // 6. 组装 + // 5. 组装 List records = users.stream().map(user -> { UserAggregateVO vo = new UserAggregateVO(); BeanUtils.copyProperties(user, vo); vo.setDepts(deptMap.getOrDefault(user.getId(), Collections.emptyList())); vo.setPosts(postMap.getOrDefault(user.getId(), Collections.emptyList())); - vo.setDetail(convertDetail(detailMap.get(user.getId()))); // 提取主部门 vo.getDepts().stream() @@ -481,20 +557,62 @@ public Page listUserAggregate(Page page, QueryWrapper