# 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` 原压缩单行身份方法**已完整删除**: ```java - 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` 直读): ```bash 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 个文件」)。 身份解析: ```bash 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。**