轻松聘AI开发
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

12 KiB

R01-A3 独立代码复验报告(第三批:8 个 Controller 请求身份与 requestId 收束)

日期:2026-09-17 分支:refactor/r01-structure-performance HEAD:52c8026b2e56dae7dbfae6a828a9efe23e180445(复验全程未变) 复验对象:docs/r01-a3-request-context-migration-report.md 性质:仅复验 —— 未修改生产代码、未修改既有测试、未暂存、未提交、未推送


0. 结论

R01-A3独立代码复验通过;A2与A3当前仍共存于未提交工作区,下一步必须按文件和hunk拆分为两个独立提交,不得合并提交。

FAIL = 0(A3 范围内);SKIP = 0。 另发现基线已存在的非 A3 阻断缺陷 1 项(§7),已用基线 worktree 证伪归因。


1. 边界确认

检查项 结果
分支 refactor/r01-structure-performance ✔
HEAD 52c8026b2e56dae7dbfae6a828a9efe23e180445 ✔
暂存区 空 ✔
git diff --check 无空白错误 ✔
工作区构成 A2(8 个 Controller)+ A3(8 个 Controller) + 路线图 + GlobalExceptionHandler(stat 缓存,内容零差异)✔
归因纪律 本报告的 A3 证据仅取自 A3 的 8 个文件路径;A2 的 8 个文件未计入 A3 证据 ✔
未进入 A4 ✔
未修改既有污染测试 ✔

2. A3 文件静态审查

git diff -- <8 个 A3 Controller 精确路径> 的改动逐行分类:

改动类型 数量
Long userId = resolveUserId(request); → currentUserAccessor.requireUserId() 21
新增 private final CurrentUserAccessor currentUserAccessor; 8
新增 private final RequestIdAccessor requestIdAccessor; 8
删除 import ...SecurityContextHolder; 8
删除 import ...Authentication; 7
删除 return request.getHeader("X-Request-Id"); 7
删除 return Long.valueOf(auth.getName()); 7
getRequestId(request) / requestId(request) → requestIdAccessor.get(request) 其余全部调用点
riskAuditContext 内 header 直读 → requestIdAccessor.get(request) 1

2.1 私有方法删除清单 —— 实际为 16 个(实施报告写 17,属笔误)

逐条核对(git diff | grep '^-.*private .*(resolveUserId\|getUserId\|userId(\|getRequestId\|resolveRequestId\|requestId('):

7× resolveUserId(HttpServletRequest)   AccountDeletion / CompanyVerification / AdminRisk /
                                        AdminReport / Report / Block / Notification
3× getRequestId(HttpServletRequest)    AccountDeletion / CompanyVerification / Notification
4× requestId(HttpServletRequest)       AdminRisk / AdminReport / Report / Block
1× userId()                            JobRecommendation(紧凑单行)
1× requestId(HttpServletRequest r)     JobRecommendation(紧凑单行)
────────────────────────────────────────
= 16

这是本次复验发现的报告数字错误:实施报告 §2.1 称「共删除私有解析方法 17 个」,实际为 16 个(8 个 Controller × 2)。行为本身正确(8 个文件门禁命中均为 0),仅报告计数有误。

JobRecommendationController 原压缩单行身份方法已完整删除:

-    private Long userId(){ Authentication a=SecurityContextHolder.getContext().getAuthentication(); ... }
-    private String requestId(HttpServletRequest r){return r.getHeader("X-Request-Id");}

其 9 处调用点全部改为 currentUserAccessor.requireUserId() / requestIdAccessor.get(request) ✔

2.2 注入方式

8 个 Controller 均为 private final 构造器注入;无 @Autowired(required=false)、@Nullable、ObjectProvider、new CurrentUserAccessor()、new RequestIdAccessor() ✔

2.3 未漂移项

核查项 结论
角色检查位置 未移动 —— verificationService.assertAdminRole(userId)×4、AdminRisk/AdminReport/Report 内的 userRepository.findById → getRoles → FORBIDDEN、JobRecommendation 的 Service 层裁决,全部原样保留 ✔
资源归属检查 未移动 ✔
Service 调用顺序 未变 ✔
DTO / 错误码 / 响应结构 未变(ErrorCode、SecurityConfig、Flyway 均零修改)✔
是否混入 UserRoleGuard 或其他重构 否 ✔

3. 独立测试(不复用实施者断言)

新增两个独立测试类:A3IndependentVerificationTest(9 例)、A3AccessorWiringProbeTest(4 例)。

覆盖 结果
8 端点 × 8 非法主体(anonymousUser/空/空白/非数字/abc123/20 位溢出/0/-1) 全部 401,无 500 ✔
匿名 token 与无 Token 401 ✔(无 Token 由 Security 链提前返回,按要求只断言 401、不要求补 requestId)
错误体安全 code=401;message ∈ {authentication required, not authenticated};无类名/堆栈/principal;非数字标记不回显(纯数字主体不参与该断言,避免与状态码/时间戳碰撞)✔
溢出主体 无 NumberFormatException;解析 JSON 后仅检查业务字段,未做整响应数字搜索 ✔
合法主体 不被身份层拒绝 ✔
角色语义未漂移 显式 stub 角色层抛 FORBIDDEN → 403(非 401);AdminRiskController 桩造非 ADMIN 用户 → 403 ✔
request attribute 优先于 header ✔
响应 body 使用规范化 requestId(与响应头同源) 5 种输入 × 8 端点 ✔
CR/LF 不进入规范化结果(直接经 Accessor 验证)✔
Accessor 接线变异检测 mock 抛未登录 → 8 端点必须 401;mock requestId 必须出现在响应体;verify(notificationService).list(42L, …) —— 删除 Accessor 调用会使该测试失败 ✔

4. 统计口径重新核对(任务书第四节)

使用更完整的模式(含 requestId(...) 方法名与 X-Request-Id 直读):

rg -l 'request\.getHeader\("X-Request-Id"\)|String\s+(getRequestId|resolveRequestId|requestId)\s*\(' \
   mydreamjob-server/src/main/java -g "*.java"

结果:25 个文件(此前报告的 36 为宽口径误计 —— 旧模式 getRequestId 还匹配到 AuditContext#getRequestId 等非请求入口调用)。

分类 数量 明细
合理基础设施 4 audit/AuditContext.java、common/exception/GlobalExceptionHandler.java、common/logging/RequestLoggingFilter.java、deletion/DeletionGuardInterceptor.java
尚未迁移的业务 Controller 21 bidirectionalmatch/*×3、candidatescore/governance/、careerprogress/、controller/*×3、feedback/、interviewscore/、job/*×2、match/、review/*×5、scoringmonitoring/、talentdiscovery/、talentinvitation/
Service 或非请求入口 0 —
误报 0 —

真实业务剩余数 = 21 个业务 Controller(非「36 个文件」)。

身份解析:

rg -l 'SecurityContextHolder\.getContext\(\)\.getAuthentication|Long\.(valueOf|parseLong)\([^)]*getName\(\)' \
   mydreamjob-server/src/main/java -g "*.java"

结果:20 个文件 = 基础设施 3(security/CurrentUserAccessor、auth/JwtAuthenticationFilter、deletion/DeletionGuardInterceptor)+ 业务 Controller 17 + 误报 0。

真实业务剩余数 = 17 个业务 Controller。


5. 验证结果

命令 结果
mvn -Dtest="R01A3…,A3IndependentVerificationTest,A3AccessorWiringProbeTest,CurrentUserAccessorTest,RequestIdAccessorTest,RequestIdFilterTest" test 51 / 51,F=0 E=0 S=0
关联回归(*Deletion*Test,*Verification*Test,*Risk*Test,*Report*Test,*Block*Test,*Notification*Test,*Recommendation*Test,*RequestContext*Test,*RequestId*Test) 260 / 260,F=0 E=1,BUILD FAILURE —— 全部为基线已存在的非 A3 缺陷(§7)
mvn clean verify 2220 / 2220,Failures=0、Errors=0、Skipped=0,BUILD SUCCESS
python docs\verify_openapi.py 176 paths / 214 operations,All checks passed
  • 较实施轮次(2207)新增 13 个测试,全部为本次独立复验新增的两个测试类
  • docs/openapi-rc.json 零修改;ErrorCode / Flyway / SecurityConfig 零修改 ✔

6. 独立真实 HTTP 验证

只读实例 PID 16604,验证后已停止。

场景 结果
无 token / 畸形 Bearer / 纯数字 Bearer × 8 端点 全部 401 ✔
STUDENT → ADMIN 风险概览 / 举报 / 认证审核 403 ✔
ADMIN → 风险概览(合法) 200 ✔
STUDENT → 通知列表(合法) 200 ✔
上述全部请求前后 22 张表逐字段比较 全部零变化 ✔

7. 基线已存在的非 A3 阻断缺陷(如实记录,未修复)

关联回归命令触发 1 个 Error:

AccountDeletionControllerTest.lazyTransition_firstGet_returnsCompleted_andConsistent:228->createUser:75
DataIntegrityViolation: USERS(PHONE) 唯一键冲突 '930000000'

隔离复现(任务书要求):在 git worktree add --detach HEAD(不含 A2/A3 改动)中用完全相同的命令运行:

测试数 失败签名
基线(HEAD) 223 AccountDeletionControllerTest...createUser:75,USERS(PHONE)='930000000'
本次(含 A2+A3) 260 完全相同

测试数差 37 = A2(20) + A3(8) + 本次独立新增(9),数字自洽。

判定:基线已存在的非 A3 阻断缺陷,与 A2/A3 无关。

  • 根因:AccountDeletionControllerTest 的 PHONE 为 static AtomicLong(930_000_000L),配合共享命名 H2 库(integrationdb),在特定测试子集顺序下与其他类残留数据冲突
  • 单跑该类:14/14 全绿
  • mvn clean verify(全量顺序):2220/2220,0 Error

未修复(超出本批授权范围),未修改该既有测试。建议后续单独处理。


8. 复验过程中的自暴露问题(非产品缺陷)

# 问题 处置
1 独立测试首轮 nonAdminOnAdminEndpoints_is403 失败:/admin/companies/verifications 因 Service 为 @MockBean 未 stub 而 500 改为显式 stub 角色层抛 FORBIDDEN 并断言 403,这才是验证"角色语义未漂移"的正确方式
2 接线探针 verify(notificationService).list(42L) 参数个数错误(实际为 3 参) 改为 list(eq(42L), anyInt(), anyInt())

9. 结论与下一步约束

R01-A3独立代码复验通过;A2与A3当前仍共存于未提交工作区,下一步必须按文件和hunk拆分为两个独立提交,不得合并提交。

必须遵守的下一步约束:

  1. 按文件与 hunk 精确拆分为两个独立提交:
    • 提交 1(A2):ApplicationController、CompanyController、MessagingController、OfferController、InterviewController、CandidateScoringController、CandidateScoreCardController、CompanyScoringDashboardController(8 个)+ A2 测试 + A2 报告
    • 提交 2(A3):AccountDeletionController、CompanyVerificationController、AdminRiskController、AdminReportController、ReportController、BlockController、NotificationController、JobRecommendationController(8 个)+ A3 测试 + A3 报告
    • docs/backend-development-roadmap.md 同时被两批修改,必须用 git add -p 拆分 hunk
  2. 不得合并提交。
  3. GlobalExceptionHandler.java 仅 stat 缓存标记(内容零差异),不得提交。
  4. 既有污染测试缺陷不属 A2/A3,若修复应作为独立提交,不得混入。

复验完成前,未暂存、未提交、未推送、未进入 A4。