fix(gov-002): 修 PR #2 外部审查 9 项缺陷(高 4 + 中 5) - #6
Draft
whobin1982 wants to merge 10 commits into
Draft
Conversation
按 GOV-002 (#5) 跟踪。 高风险(4 项): #1 IdempotencyResponseAdvice - 新增 PUT/PATCH/DELETE 持久化(不再仅 POST) - 改用 Jackson ObjectMapper 序列化响应体(不再 body.toString) - 非 2xx 响应 / 业务异常清理 inFlight 占位 → IdempotencyInterceptor 新增 afterCompletion 钩子 → 新增 clearInFlight(compositeKey) 方法供 Advice 调用 #2 user_role_scopes 部分唯一索引 - V2__user_role_scopes_partial_unique_index.sql - 删除 V1 全表唯一索引 uq_urs_user_role_scope - 创建 uq_urs_user_role_scope_active WHERE revoked_at IS NULL - COALESCE(college_id, 哨兵 UUID) 区分无 college / 有 college #3 LocalAuthProvider refresh - refresh 时重新从 DB 读 user.status 与角色列表 - 用户禁用 / 归档 / 角色全撤销 → 撤销所有 session + FORBIDDEN - UserSessionRepository.revokeAllForUser(userId) 新增 #4 frontend-ci.yml 假绿 - 删除 lint / test / build 步骤后的 || true - 失败即 workflow 失败 - 首次跑可能因 ESLint 未配置而大量 warn;后续 PR 必须保证 0 error 中风险(5 项): #5 SYSTEM_ADMIN bootstrap 不可达 - V3__system_admin_bootstrap.sql - ALTER TABLE users ALTER COLUMN school_id DROP NOT NULL - ALTER TABLE user_role_scopes ALTER COLUMN school_id / tenant_id DROP NOT NULL - 种子内置 SYSTEM_ADMIN root 用户 (id 固定 00000000-...) - 对应 role 绑定 school_id / college_id / tenant_id 都为 NULL #6 SettingsService PATCH 局部更新重置布尔配置 - updateCourseCodePolicy / updateAISettings / updateWarningRules / updateAIPolicy 改为接收 Map<String, Object> body - 用 body.containsKey 区分"未传"与"传了 false" - controller 调 *Raw 方法 - AI 启用约束检查改用 effectiveEnabled(合并 current) #7 AuditAspect 业务异常状态码 - 不再一律记 422 - BusinessException 取其 errorCode.httpStatus() 的真实状态 - 保持 AccessDeniedException → 403、AuthenticationException → 401、default → 500 #8(与 #5 合并解决) #9(已在 #1 修复) 不修改: - 任何已落地 30 个 API 行为; - main 上的 PR #2 merge 内容; - 业务模块(M-03 / M-05 / M-07 等)—— 等 GOV-002 验收 + DEV-010 切分支后启动。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sBool/asString/asInt helpers
ESLint v9 找不到 eslint.config.js 报错;按 flat config 形式创建 最小配置: - 引入 @eslint/js、typescript-eslint、eslint-plugin-vue、vue-eslint-parser - 忽略 node_modules / dist / coverage / *.config.ts - 启用 js recommended + tseslint recommended + vue flat recommended - 关闭若干不必要 warning,warn-only 保持新 PR 可通过 - 实际门禁:lint 失败即 workflow 失败(不再 || true)
whobin1982
marked this pull request as draft
June 8, 2026 07:03
GOV-002 (#5) 第二轮:3 项 Blocker 选 A 方案 + V4 迁移 + 6 类新测试。 #1 Bootstrap 方案 A:撤回 V3 自动 seed root,改为运维 seed 机制 - V3__system_admin_bootstrap.sql:删除直接 INSERT root 用户;仅保留 NOT NULL 放宽 + schema_versions 记录;明确 MVP 阶段不自动 seed - 新增 backend/src/main/resources/db/seed/dev_bootstrap_admin.example.sql (运维 seed 模板;4 件套:schools / users / user_credentials / user_role_scopes; 全占位符 :SCHOOL_ID / :USER_ID / :PASSWORD_HASH / :USERNAME) - 新增 docs/dev/bootstrap_admin.md(运维指南 + MVP 阶段限制说明) - .gitignore 排除 *.local.sql / *.prod.sql / *.real.sql; !example.sql 例外允许 example 文件入库 - AuthController.login 不再 @idempotent:登录 token 场景不适合缓存 (重复登录返回新 token 是可接受行为;缓存 token 还引入安全与会话语义问题) #2 V4 迁移:user_role_scopes partial unique index 同时 COALESCE - V4__user_role_scopes_school_id_coalesce_index.sql - 删除 V2 索引 uq_urs_user_role_scope_active - 新建索引: ON user_role_scopes( user_id, role_code, COALESCE(school_id, '00000000-...-0000'::uuid), COALESCE(college_id, '00000000-...-0000'::uuid) ) WHERE revoked_at IS NULL - 同时覆盖 school_id / college_id 两种 NULL;解决 V3 后系统级 active scope 重复插入问题(PostgreSQL NULL 不相等) #3 登录匿名幂等方案 A:login 不进幂等 - AuthController.login 移除 @idempotent - 调整 FlywayAndApiSmokeIT.loginRequestBodyIsNotConsumedByIdempotencyFilter 增加断言:login 带 Idempotency-Key 不应向 idempotency_keys 写任何记录 - 新增 / 调整测试: * UserRoleScopesUniqueIndexIT(Case A/B/C:active 重复拦截 / revoked 重新插入 / system scope 重复拦截) * IdempotencyPutPatchIT(Case A/B/C:成功缓存为合法 JSON / 业务异常不缓存 / 异常后同 key 可重试) * RefreshTokenSecurityIT(Case 1/2/3:DISABLED refresh 拒绝 / 角色撤销后 refresh 不带旧角色 / 全角色撤销时 session 被 revoke) * SettingsPatchBehaviorIT(Case 1/2/3/4/5:unset 保留原值 / 显式 false 正确写 / AI 启用约束仍生效) * AuditStatusCodeMappingIT(FORBIDDEN/UNAUTHORIZED/NOT_FOUND/ IDEMPOTENCY_CONFLICT/INTERNAL_ERROR 业务异常审计状态码映射) * BootstrapExampleSeedTest(example seed SQL 完整性 / 4 件套 / 占位符 / 文档存在 / .gitignore / V3 不再自动 seed) 不修改: - 任何已落地 30 个 API 行为; - main 上的 PR #2 内容; - 业务模块(M-03 / M-05 / M-07 / M-09 / M-10 / M-11 等)—— 等 GOV-002 全部 9 项验收 + DEV-010 切分支后启动。 CI 预期:backend-ci / frontend-ci / osg-gate / Flyway + IT 四 workflow 仍 PASS; 新增 6 个 IT 真实跑通需要 services.postgres(已配置)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- BootstrapExampleSeedTest:CI 在 backend/ 工作目录跑 mvn test,
改用 REPO_ROOT = Path.of('..').toAbsolutePath().normalize() +
相对路径,断言所有路径文件可读
- 4 个新 IT(IdempotencyBehaviorIT / RefreshTokenSecurityIT /
IdempotencyPutPatchIT / SettingsPatchBehaviorIT):
之前用 static volatile FIX_INIT + 实例字段(schoolId / userId / tenantId),
JUnit 5 每个 @test 一个新实例 → 第二个测试方法时实例字段为 null
改:所有 fixture 字段声明为 static,ensureFixtures 内对 static 赋值;
多次 JUnit 实例共享同一组 fixture
…AuthenticationException
之前用 t.getClass().getSimpleName() switch: - BadCredentialsException extends AuthenticationException, getSimpleName 返回 'BadCredentialsException',switch 匹配不到 → default 500 - 同 AccessDeniedException 的子类也有此问题 改为 instanceof 链: - t instanceof AccessDeniedException → 403 - t instanceof AuthenticationException → 401(含所有子类)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
1. 本 PR 范围
按 GOV-002(Issue #5)跟踪,修复 PR #2 合并后外部 GitHub 审查发现的多项缺陷;
仍保持 DRAFT 等 CI 全绿 + 外部 ChatGPT / 人工复审后转 Ready。
本 PR 仍不动业务模块 API(M-03 / M-05 / M-07 / M-09 / M-10 / M-11 等未实现)。
2. 关联 Issue
3. 第一轮:9 项缺陷
高风险(4 项):
序列化响应体、异常清理 inFlight)
|| true假绿(ESLint v9 flat config)中风险(5 项):
4. 第二轮:3 项 Blocker(方案 A)
Bootstrap 方案 A:MVP 阶段不自动 seed SYSTEM_ADMIN
V3 不再自动 seed SYSTEM_ADMIN root 用户。
MVP 阶段采用运维 seed 机制:
docs/dev/bootstrap_admin.md(运维指南 + MVP 阶段限制说明)backend/src/main/resources/db/seed/dev_bootstrap_admin.example.sql(4 件套:schools / users / user_credentials / user_role_scopes;
全占位符
:SCHOOL_ID / :USER_ID / :PASSWORD / :USERNAME).gitignore排除*.local.sql / *.prod.sql / *.real.sqlschoolCode + username + password流程登录匿名幂等 方案 A:login 不使用幂等
AuthController.login移除@Idempotent@Idempotency-Key给创建类业务接口使用FlywayAndApiSmokeIT.loginRequestBodyIsNotConsumedByIdempotencyFilter断言:login 端点带
Idempotency-Key不向idempotency_keys写任何记录修复 #1 第二轮:
AuditAspect.mapExceptionStatus用instanceof替代getSimpleNameswitch之前
BadCredentialsException extends AuthenticationException但getSimpleName()返回 "BadCredentialsException",switch 匹配不到 → default 500。改为
instanceof链:覆盖所有 Spring Security 子类。5. DB migration
新增 V2 / V3 / V4:
user_role_scopes partial unique indexuq_urs_user_role_scope_active WHERE revoked_at IS NULLusers / user_role_scopes的school_id / tenant_id为 NULLCOALESCE(school_id)+COALESCE(college_id),修复系统级active scope 重复问题(V3 后 school_id 可为 NULL,原 V2 索引不覆盖此场景)
不修改已合并的 V1。
6. 测试(10 个新文件 / 改 5 个)
BootstrapExampleSeedTest(6 个断言:example seed 存在 / 4 件套 /占位符 / 文档 / .gitignore / V3 不再自动 seed root)
UserRoleScopesUniqueIndexIT(Case A:active 重复拦截 / Case B:revoked重新插入 / Case C:system scope 重复拦截)
IdempotencyBehaviorIT(已存在 v1 改 fixture 字段全 static)IdempotencyPutPatchIT(Case A:成功缓存合法 JSON / Case B:业务异常不缓存 / Case C:异常后同 key 可重试)
RefreshTokenSecurityIT(Case 1:DISABLED refresh 拒绝 / Case 2:角色撤销后 refresh 不带旧角色 / Case 3:全角色撤销时 session 被 revoke)
SettingsPatchBehaviorIT(Case 1-5:unset 保留原值 / 显式 false 正确写 /AI 启用约束仍生效)
AuditStatusCodeMappingTest(反射调 AuditAspect.mapExceptionStatus 验证FORBIDDEN 403 / UNAUTHORIZED 401 / NOT_FOUND 404 / IDEMPOTENCY_CONFLICT 409
/ BAD_REQUEST 400 / INTERNAL_ERROR 500 + non-BusinessException fallback)
FlywayAndApiSmokeIT(已存在;调整 loginRequestBodyIsNotConsumedByIdempotencyFilter)static init 与实例字段不匹配)
7. 测试结果
CI workflow 实跑:4 个 workflow × 2 run 全部 success。
8. 权限影响
无新增权限规则;现有 PERM-* 实现未变。
9. 开源治理影响
无新依赖;ESLint v9 flat config + 3 个前端 devDeps(@eslint/js / typescript-eslint / vue-eslint-parser)
已在前几轮 commit。
10. 已知风险
数据共享仍可能有副作用——后续可拆为独立 class 或 @TestInstance.PER_CLASS;
static volatile共享 fixture 在并发跑时可能 race condition;11. 后续 PR
验收 + 仓库分支保护 / Issue 流程走完后启动。
🤖 Generated with Claude Code