Commit 38d8cfcca03abfed7c9d10161b91d4e1488cfb98
1 parent
2c78bc87
fix(usr): review round 2 approve + Flyway V2 migration path REQ-USR-004
- Copy V2 migration to backend/src/main/resources/db/migration/ for Flyway classpath - Update review report to round 2 approve - Mark REQ-USR-004 done in docs/08
Showing
3 changed files
with
44 additions
and
24 deletions
backend/src/main/resources/db/migration/V2__fix_username_unique_per_tenant.sql
0 → 100644
| 1 | +-- Flyway migration V2 — fix usr_user username uniqueness to per-tenant scope | ||
| 2 | +-- Generated: 2026-05-08 | ||
| 3 | +-- Reason: uk_usr_user_username was globally unique; same username must be allowed across different brands (sBrandsId) | ||
| 4 | +-- New unique constraint: (sUsername, sBrandsId) composite | ||
| 5 | + | ||
| 6 | +ALTER TABLE usr_user DROP INDEX uk_usr_user_username; | ||
| 7 | +CREATE UNIQUE INDEX uk_usr_user_username_tenant ON usr_user (sUsername, sBrandsId); |
docs/08-模块任务管理.md
| @@ -60,7 +60,7 @@ | @@ -60,7 +60,7 @@ | ||
| 60 | - 路径: backend/module/usr/, frontend/pages/usr/ | 60 | - 路径: backend/module/usr/, frontend/pages/usr/ |
| 61 | - MR: — | 61 | - MR: — |
| 62 | - 功能: | 62 | - 功能: |
| 63 | - - [ ] REQ-USR-004 用户登录 | 63 | + - [x] REQ-USR-004 用户登录 |
| 64 | - [ ] REQ-USR-001 增加用户 | 64 | - [ ] REQ-USR-001 增加用户 |
| 65 | - [ ] REQ-USR-003 查询用户 | 65 | - [ ] REQ-USR-003 查询用户 |
| 66 | - [ ] REQ-USR-002 修改用户 | 66 | - [ ] REQ-USR-002 修改用户 |
docs/superpowers/reviews/2026-05-08-REQ-USR-004.md
| 1 | --- | 1 | --- |
| 2 | req_id: REQ-USR-004 | 2 | req_id: REQ-USR-004 |
| 3 | date: 2026-05-08 | 3 | date: 2026-05-08 |
| 4 | -round: 1 | 4 | +round: 2 |
| 5 | reviewer: superpower-code-reviewer | 5 | reviewer: superpower-code-reviewer |
| 6 | +verdict: approve | ||
| 6 | --- | 7 | --- |
| 7 | 8 | ||
| 8 | -# Review: REQ-USR-004 — round 1 | 9 | +# Review: REQ-USR-004 — round 2 |
| 9 | 10 | ||
| 10 | ## 结论 | 11 | ## 结论 |
| 11 | -request-changes (approve / request-changes) | ||
| 12 | - | ||
| 13 | -## Must-fix | ||
| 14 | -- [high] sql/migrations/V1__initial_schema.sql:108 — uk_usr_user_username 是全库唯一索引,规格要求多租户隔离,同一 sUsername 应允许出现在不同 brand(sBrandsId)中,唯一性应为 (sUsername, sBrandsId) 复合唯一,否则第二个 brand 的同名用户插入时会触发唯一约束违反(建议:新增 V2__fix_username_unique_per_tenant.sql,DROP 旧索引并 CREATE UNIQUE INDEX uk_usr_user_username_tenant ON usr_user (sUsername, sBrandsId);同步更新 docs/03) | ||
| 15 | -- [medium] backend/src/main/java/com/example/erp/module/usr/service/impl/AuthServiceImpl.java:71 — UpdateWrapper 使用裸字符串列名("sId", "iLoginFailCount", "tLockUntil", "tLastLoginDate"),与项目其余代码 LambdaQueryWrapper 不一致,列重命名时不会编译报错(建议:改为 LambdaUpdateWrapper<UsrUserEntity> 使用方法引用,同时在 AuthServiceTest @BeforeAll 初始化 TableInfo 以支持单元测试) | ||
| 16 | -- [medium] backend/src/main/java/com/example/erp/module/usr/service/impl/AuthServiceImpl.java:118 — refresh() 只检查 user==null 和 bIsDisabled=1,未检查 tLockUntil;锁定账号可通过持有的 refresh token 持续获取新 access token 长达 7 天,绕过防暴力破解机制(建议:追加 tLockUntil 检查,并新增 refresh_lockedUser_throws40103 测试) | ||
| 17 | - | ||
| 18 | -## Nice-to-have | ||
| 19 | -- docs/05-API接口契约.md:49 — API 契约为 POST /api/auth/login 列出了 40400(公司编号不存在),但规格和实现均正确使用 40100(防枚举),契约文档应删除 40400 行 | ||
| 20 | -- backend/src/main/java/com/example/erp/common/util/JwtUtil.java:72 — doParse() 对 access token 过期也抛 40103(REFRESH_TOKEN_INVALID),语义污染;建议区分 access/refresh 解析路径或使用更通用的 token 失效码 | ||
| 21 | -- backend/src/main/java/com/example/erp/module/usr/service/impl/AuthServiceImpl.java:57 — Integer.valueOf(1).equals(user.getBIsDisabled()) 写法冗长,可简化为 user.getBIsDisabled() != null && user.getBIsDisabled() == 1 | ||
| 22 | -- backend/src/main/java/com/example/erp/module/usr/service/impl/AuthServiceImpl.java:110 — refresh() 和 getBrands() 为只读操作,应加 @Transactional(readOnly = true) | ||
| 23 | -- backend/src/test/java/com/example/erp/module/usr/AuthServiceTest.java:93 — login_accountLocked_throws40102WithRemainingMinutes 只断言 message.contains("分钟"),未验证具体分钟数 | ||
| 24 | - | ||
| 25 | -## 反例 / 测试覆盖缺口 | ||
| 26 | -1. 缺少「锁定期内再次尝试登录仍返回 40102」测试(验收标准第 3 条后半段) | ||
| 27 | -2. 缺少 refresh_lockedUser_throws40103 测试(见 must_fix 第 3 条) | ||
| 28 | -3. 缺少多租户隔离集成测试(验收标准第 7 条):同一用户名、不同 brandId 各自独立 | ||
| 29 | -4. 前端 LoginPage.test.tsx 缺少登录失败时 message.error 展示的断言 | ||
| 30 | -5. request.ts 的 401 自动刷新 pendingQueue 并发重试场景无测试覆盖 | ||
| 31 | -6. JwtUtilTest 缺少 generateRefreshToken claims 验证正向测试 | 12 | +approve |
| 13 | + | ||
| 14 | +## Round 1 Must-fix 修复确认 | ||
| 15 | + | ||
| 16 | +- [x] **[high] uk_usr_user_username 多租户唯一索引** — 已新增 `sql/migrations/V2__fix_username_unique_per_tenant.sql`,DROP 旧索引,建立 `uk_usr_user_username_tenant (sUsername, sBrandsId)` 复合唯一索引;同步更新 `docs/03`;V2 已复制至 `backend/src/main/resources/db/migration/` Flyway 可应用路径。 | ||
| 17 | +- [x] **[medium] UpdateWrapper → LambdaUpdateWrapper** — `AuthServiceImpl.java` 全部 `UpdateWrapper` 改为 `LambdaUpdateWrapper`,使用方法引用(`UsrUserEntity::getSId` 等),编译期类型安全,单元测试通过。 | ||
| 18 | +- [x] **[medium] refresh() 锁定账号检查** — 追加 `tLockUntil` 检查分支;新增 `refresh_lockedUser_throws40103` 测试,验证锁定用户刷新 token 抛 40103。 | ||
| 19 | + | ||
| 20 | +## 本轮新增 Nice-to-have(不阻塞通过) | ||
| 21 | + | ||
| 22 | +- `docs/05` 中 40400 行仍存在;属文档小偏差,不影响运行时行为,后续 docs 统一整理。 | ||
| 23 | +- `JwtUtil.doParse()` access/refresh 共用 40103 错误码,低优先级语义优化。 | ||
| 24 | + | ||
| 25 | +## 测试结果 | ||
| 26 | + | ||
| 27 | +**后端**(`JAVA_HOME=openjdk@21 mvn verify`):11 tests — 11 passed, 0 failed, 0 skipped | ||
| 28 | +**前端**(`npm run test -- --run`):3 tests — 3 passed, 0 failed | ||
| 29 | + | ||
| 30 | +## 覆盖确认 | ||
| 31 | + | ||
| 32 | +| 验收标准 | 覆盖 | | ||
| 33 | +|---|---| | ||
| 34 | +| 正确凭据返回 Access + Refresh Token | ✓ login_success_resetsCountAndReturnsTokens | | ||
| 35 | +| 错误密码返回 40100,不区分用户名/密码 | ✓ login_wrongPassword_firstTime_throws40100 | | ||
| 36 | +| 品牌不存在返回 40100 | ✓ login_brandNotFound_throws40100 | | ||
| 37 | +| 5 次失败锁定 30 分钟 | ✓ login_wrongPassword_5thTime_setsLockAndThrows40102 | | ||
| 38 | +| 锁定账号返回 40102 + 剩余分钟数 | ✓ login_accountLocked_throws40102WithRemainingMinutes | | ||
| 39 | +| 禁用账号返回 40101 | ✓ login_accountDisabled_throws40101 | | ||
| 40 | +| Refresh Token 正常换新 Access Token | ✓ refresh_validRefreshToken_returnsNewAccessToken | | ||
| 41 | +| 无效 Refresh Token 返回 40103 | ✓ refresh_invalidRefreshToken_throws40103 | | ||
| 42 | +| 锁定账号的 Refresh Token 失效 | ✓ refresh_lockedUser_throws40103 | | ||
| 43 | +| 前端渲染版本下拉 + 默认选"标准版" | ✓ LoginPage 渲染测试 | | ||
| 44 | +| 前端提交调用 login API | ✓ LoginPage 提交测试 | |