# R01-A5 独立代码复验报告(第五批:8 个 Controller 请求身份与 requestId 收束) 日期:2026-09-17 分支:`refactor/r01-structure-performance` HEAD:`0226977063243fce101e3f6c8dcf66492d306ef7`(A4 后最新提交,复验全程未变) 复验对象:`docs/r01-a5-request-context-migration-report.md` 性质:**仅复验** —— 未修改生产代码、未修改既有测试断言、未暂存、未提交、未推送 --- ## 0. 结论 > **R01-A5独立代码复验通过,可以进入精确暂存、本地提交与推送检查点。** **FAIL = 0;SKIP = 0。未发现生产缺陷。** --- ## 1. 开始前状态 | 检查项 | 结果 | |---|---| | 分支 | `refactor/r01-structure-performance` ✔ | | HEAD | `0226977063243fce101e3f6c8dcf66492d306ef7`(A4 后最新)✔ | | 与上游同步 | `0 / 0` ✔ | | 暂存区 | 空 ✔ | | `git diff --check` | 无空白错误 ✔ | | 工作区改动 | **仅 8 个 A5 Controller + 路线图**,无 A5 之外的生产代码改动 ✔ | | 历史未跟踪文件 | 全部保留 ✔ | | reset/restore/checkout/clean、`git add -A`、Docker | 均未执行 ✔ | ### `GlobalExceptionHandler.java` 三方 blob 比对 | 来源 | blob | |---|---| | worktree(`git hash-object`) | `1fdc341ebe3653393b3ec04f0c2d0bb9dfbb6d21` | | index(`git ls-files -s`) | `1fdc341ebe3653393b3ec04f0c2d0bb9dfbb6d21` | | HEAD(`git rev-parse HEAD:`) | `1fdc341ebe3653393b3ec04f0c2d0bb9dfbb6d21` | **三者完全一致 → 纯 Windows stat 缓存现象,未触碰该文件** ✔ --- ## 2. 8 个 Controller 静态审查 | Controller | 代表端点 | Accessor 调用(auth / reqId) | |---|---|---| | CandidateScoringGovernanceController | `GET /profile/me/candidate-scoring/appeals` | 9 / 9 | | InterviewScoreController | `GET /interviews/{id}/score` | 4 / 4 | | FavoriteController | `GET /jobs/favorites` | 3 / 3 | | AdminReviewController | `GET /admin/reviews/pending` | 3 / 3 | | AdminReviewReplyController | `GET /admin/review-replies/pending` | 2 / 2 | | CompanyReviewManagementController | `GET /companies/me/job-reviews` | 4 / 4 | | JobReviewController | `DELETE /jobs/{jobId}/reviews/{reviewId}` | 3 / 7 | | StudentReviewWeightController | `GET /profile/me/review-weights` | 3 / 4 | (`JobReviewController` / `StudentReviewWeightController` 的 reqId 调用数高于 auth 属正常 —— 部分只读端点只需 requestId。) **改动性质**:`8 files changed, 101 insertions(+), 198 deletions(-)`,删除的 `ErrorCode.UNAUTHORIZED` 共 10 处,**全部来自被移除的私有解析方法**;**角色检查(`assertAdminRole`/`assertCompanyOwner`/`assertStudentRole`)、资源归属、`FORBIDDEN`/`NOT_FOUND` 及 Service 调用顺序未删未增、未移动** ✔ **`JobReviewController.parseIds` 完整保留** ✔ —— 它解析 `ids` 查询参数(业务参数,非认证主体),并以 `catch (NumberFormatException) → BAD_REQUEST` 处理,属正确的业务参数语义,未被误删或误判。 --- ## 3. 禁止模式扫描 任务书指定模式在 8 个文件中的命中:**全部为 0** ✔ 覆盖:`SecurityContextHolder`、`AnonymousAuthenticationToken`、`anonymousUser`、`auth.getName()`、`authentication.getName()`、`Long.(valueOf|parseLong)(...(auth|authentication)`、`private (resolveUserId|getRequestId|requestId)(`、`getHeader(...X-Request-Id|REQUEST_ID_HEADER)`、`@Autowired(required=false)`、`@Nullable`、`ObjectProvider`、`new CurrentUserAccessor`、`new RequestIdAccessor` --- ## 4. 构造器注入与无回退证明 8 个 Controller 均声明: ```java private final CurrentUserAccessor currentUserAccessor; private final RequestIdAccessor requestIdAccessor; ``` - 全部为 **`private final` 构造器注入**(Lombok `@RequiredArgsConstructor`)✔ - **无** `@Autowired(required = false)` / `@Nullable` / `ObjectProvider` / `new CurrentUserAccessor()` / `new RequestIdAccessor()` 回退 ✔ - 身份统一为 `currentUserAccessor.requireUserId()`;requestId 统一为 `requestIdAccessor.get(request)` ✔ --- ## 5. 独立接线变异测试(Accessor 真实生效的证明) 新增 `A5AccessorWiringIndependentTest`(4 例),**不复用实施者断言**,使用 `@MockBean` 替换 `CurrentUserAccessor`、`RequestIdAccessor` 与 8 个 Service: | 用例 | 证明 | |---|---| | `allEightEndpoints_useAccessorUserId` | mock 返回 `4242L` → 逐一验证 **8 个 Service 均收到 `4242L`**(`listMyAppeals` / `getScore` / `listFavorites` / `listPendingReviews` / `listPending` / `listMine` / `deleteReview` / `getMyWeights`),并 `verify(currentUserAccessor, times(8)).requireUserId()` | | `illegalIdentity_rejectedBeforeService` | mock 抛 `InsufficientAuthenticationException` → 8 端点全部 401、无 500,且各 Service **零调用** | | `responseBodyRequestId_comesFromAccessor` | mock 返回 `a5-independent-request` → 8 个端点响应体 `requestId` 均为该值,`verify(requestIdAccessor, times(8)).get(...)` | | `noToken_is401` | 无 Token → Security 链 401 | **变异有效性**:若删除任一 Controller 中的 `currentUserAccessor.requireUserId()` 或 `requestIdAccessor.get(request)` 调用,对应 `verify` 与断言必然失败 —— 即**删除 Accessor 调用会使本测试失败** ✔ > 说明:响应头 `X-Request-Id` 由 `RequestIdFilter` 设置,**不来自 Controller**;本测试只断言**响应体**的 `requestId` 来源于 Accessor,未错误声称响应头来源。 --- ## 6. 八端点非法主体结果(独立身份边界复验) `A5IndependentVerificationTest` 使用**真实 `CurrentUserAccessor` 与真实 `SecurityFilterChain`**(不 mock Accessor): | 主体 | 结果 | |---|---| | `anonymousUser` | **401** ✔ | | `not-a-number` | **401** ✔ | | `99999999999999999999`(20 位溢出) | **401** ✔ | | `0` | **401** ✔ | | `-1` | **401** ✔ | | `AnonymousAuthenticationToken` / 无 Token | **401** ✔ | 逐端点断言(7 个 GET 端点 + `DELETE /jobs/1/reviews/1`): - **HTTP 401,无 500** ✔ - **业务 Service 零调用** ✔(`verify(..., never())`) - **不回显非法主体** —— 仅解析 JSON 后检查 `code`/`message` 稳定字段,**未对整段 JSON 做数字搜索** ✔ - 响应不包含 `Exception` / `java.` / `principal` / `anonymousUser` / `com.mydreamjob` / `NumberFormat` ✔ **未使用**任何捕获 `NumberFormatException` 的方式"修复"测试。 --- ## 7. 合法主体传入 Service 的 userId 证据 §5 的 `verify` 系列即为证据:mock Accessor 返回 `4242L` 时,**8 个 Service 各自收到 `4242L`**(逐一 `eq(4242L)` 断言)✔ 同时 `A5IndependentVerificationTest#legalPrincipal_reachesService` 使用**真实 Accessor**(数字主体 `"7"`)验证 8 端点**不被身份层判为未登录**(非 401)✔ --- ## 8. requestId Accessor 接线证据 见 §5 第 3 项:mock `RequestIdAccessor#get` 返回固定值 → 8 个端点(7 个 GET + 1 个 DELETE,均返回 `ApiResponse`)响应体 `requestId` 均为 `a5-independent-request`,并 `verify(requestIdAccessor, times(8)).get(any(HttpServletRequest.class))` ✔ --- ## 9. 401 / 403 / 404 分层保持 | 检查项 | 结果 | |---|---| | 静态审查:角色/归属检查是否被删改或移动 | **否**(`git diff` 确认未删未增)✔ | | `FORBIDDEN` / `NOT_FOUND` 错误码 | **未修改** ✔ | | `ErrorCode.java` | **零改动** ✔ | | 合法主体不被判为未登录 | ✔(§7) | **401 与 403/404 的分层未被统一**:身份失败为 401,角色/归属失败仍由既有 Service 逻辑产生 403/404,语义未漂移。 --- ## 10. 失败请求的业务副作用 新增 `A5SideEffectSnapshotTest`(2 例),使用 H2 **行级快照**(显式列 + 稳定 `ORDER BY`,**逐字段**比较,**非 COUNT/SUM**): 快照覆盖 9 张核心表:`JOB_FAVORITES`、`JOB_REVIEWS`、`COMPANY_REVIEW_REPLIES`、`INTERVIEW_SCORES`、`CANDIDATE_SCORING_APPEALS`、`STUDENT_REVIEW_WEIGHT_PREFERENCES`、`NOTIFICATIONS`、`MESSAGES`、`AUDIT_LOGS`。 | 用例 | 结果 | |---|---| | 非法身份请求(`anonymousUser` + `not-a-number` × 7 端点 + DELETE)前后 | **9 张表逐字段零变化** ✔ | | 无 Token 请求前后 | **9 张表逐字段零变化** ✔ | **结论**:非法身份请求不写收藏、评价、回复、面试评分、治理申诉、权重;不发通知;不发 RabbitMQ 消息;不写审计成功记录(`AUDIT_LOGS` 零变化)✔ > 本轮**未启动 Docker**,以上为 H2 独立复验结论;**不声称真实 MySQL 结论**。 --- ## 11. 测试数 | 命令 | 结果 | |---|---| | `mvn -Dtest="A5AccessorWiringIndependentTest,A5IndependentVerificationTest" test` | **8 / 8**,F=0 E=0 **S=0** | | `mvn -Dtest="A5SideEffectSnapshotTest" test` | **2 / 2**,F=0 E=0 **S=0** | | A5 定向(`R01A5…,A5*Test,` 8 个 Controller 测试 + 3 个 Accessor/Filter 测试) | **98 / 98**,F=0 E=0 **S=0**,BUILD SUCCESS | | `mvn clean verify` | **2255 / 2255**,Failures=0、Errors=0、**Skipped=0**,BUILD SUCCESS | - 实施基线 2245 → 复验后 **2255**(新增 **10** 个独立测试:4 + 4 + 2),**不低于基线** ✔ - 未见新的基线测试污染 ✔ --- ## 12. OpenAPI `python docs\verify_openapi.py` → **176 paths / 214 operations**,All checks passed ✔ **`docs/openapi-rc.json` 零修改** ✔ --- ## 13. Flyway / ErrorCode / DTO / 接口 | 项 | 结果 | |---|---| | V1–V45 迁移 | **零修改**(`git diff --name-only -- db/migration` 为空)✔ | | `ErrorCode.java` | **零改动** ✔ | | DTO | 零改动 ✔ | | 公网接口 / OpenAPI | 零新增零修改 ✔ | | 新增表 / 索引 | 无 ✔ | --- ## 14. 全仓剩余统计(逐文件分类) **身份解析**(`SecurityContextHolder.getContext().getAuthentication` \| `Long.(valueOf|parseLong)([^)]*getName()`)—— **4 文件** | 分类 | 文件 | |---|---| | 基础设施(3) | `security/CurrentUserAccessor`(Accessor 自身)、`auth/JwtAuthenticationFilter`(认证过滤器)、`deletion/DeletionGuardInterceptor`(拦截器) | | **业务 Controller(1)** | `scoringmonitoring/FairnessMonitoringController` | | 误报 | 0 | **requestId 解析**(含 `X-Request-Id` 直读、`RequestIdFilter.REQUEST_ID_HEADER`、`String (getRequestId|resolveRequestId|requestId)(`)—— **10 文件** | 分类 | 文件 | |---|---| | 基础设施 / 非业务入口(5) | `audit/AuditContext`、`common/exception/GlobalExceptionHandler`、`common/logging/RequestIdAccessor`、`common/logging/RequestLoggingFilter`、`deletion/DeletionGuardInterceptor` | | **业务 Controller(5)** | `controller/ApiController`、`controller/AuthController`、`controller/SystemController`、`job/JobController`、`scoringmonitoring/FairnessMonitoringController` | | 误报 | 0 | **与路线图记数的一处差异(如实记录)**:路线图 R01 行记「全仓 requestId 11 文件 = 基础设施/非业务入口 6 + 业务 5」。本次独立复验用任务书指定模式实测为 **10 文件 = 基础设施 5 + 业务 5**。**业务 Controller 数一致(5)**;差异在基础设施计数(5 vs 6),推测为路线图对"基础设施/非业务入口"的分类口径略有不同(如是否计入某调用点)。**不应据此修改代码或路线图**;建议后续批次统一口径。 --- ## 15. 精确新增文件 ``` 新增测试(3) mydreamjob-server/src/test/java/com/mydreamjob/common/r01a5/A5AccessorWiringIndependentTest.java mydreamjob-server/src/test/java/com/mydreamjob/common/r01a5/A5IndependentVerificationTest.java mydreamjob-server/src/test/java/com/mydreamjob/common/r01a5/A5SideEffectSnapshotTest.java 复验报告(1) docs/r01-a5-independent-verification-20260917.md ``` **未修改**:8 个 A5 Controller 生产代码、既有测试断言、`GlobalExceptionHandler`、`SecurityConfig`、Filter、Accessor、`ErrorCode`、DTO、OpenAPI、Flyway。 --- ## 16. Git 状态 ``` 分支:refactor/r01-structure-performance HEAD:0226977063243fce101e3f6c8dcf66492d306ef7(未变) 暂存区:空 git add -A / commit / push:均未执行 reset / restore / checkout / clean:均未执行 Docker:未操作 工作区:8 个 A5 Controller + 路线图(实施者改动,仍未暂存)+ 本报告与 3 个独立测试(未跟踪) 历史未跟踪文件:全部保留 ``` --- ## 17. 是否发现生产缺陷 **未发现生产缺陷。** 8 个 Controller 的迁移完整、正确: - 禁止模式 0 命中;构造器注入无回退;`parseIds` 业务解析正确保留 - 角色/归属/403/404 语义未漂移 - 独立接线变异测试证明 Accessor 真实生效(删除调用即失败) - 独立身份边界测试证明非法主体在业务调用前被拒(401、无 500、零 Service 调用)且不回显主体 - 失败请求 9 张核心表逐字段零变化 --- ## 18. 明确声明 - **未暂存、未提交、未推送。** - 未修改 8 个 Controller 或任何生产代码;未修改、删除或放宽既有测试断言;未修改 `GlobalExceptionHandler`、`SecurityConfig`、Filter、Accessor、`ErrorCode`、DTO、OpenAPI、Flyway。 - 未进入 R01-A6;未删除历史未跟踪文件。 - 本轮未启动 Docker,**未伪造真实 MySQL 结论**。 --- > **R01-A5独立代码复验通过,可以进入精确暂存、本地提交与推送检查点。**