轻松聘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.
 
 
 

23 KiB

R01-A6 独立代码复验报告(最终批:5 个 Controller 请求身份与 requestId 收束)

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


0. 结论

R01-A6独立代码复验通过,可以进入精确暂存与独立提交检查点;A系列到此结束,不再设置A7/A8。FairnessMonitoringControllerTest的单跑夹具缺陷须在A6提交后以独立纯测试提交修复。

业务 Controller 身份解析残留 = 0;业务 Controller requestId 重复解析残留 = 0。 剩余命中全部落在基础设施职责内(身份 3 文件、requestId 5 文件)。

未发现 A6 引入的生产缺陷。 FairnessMonitoringControllerTest 的单跑失败经证据链确认为 测试夹具缺陷(TestAuthTokens 对"已存在但被禁用"的用户行不做激活), 与 A6 生产改动无关;其准确根因见 §9。


1. 开始前状态

项 结果
git branch --show-current refactor/r01-structure-performance
git rev-parse HEAD 534afe686b966f175bcaf4af0726cec061092050
git diff --cached --name-only 空(暂存区为空)
git diff --check 仅 docs/backend-development-roadmap.md 的 LF/CRLF 警告,exit 0,无空白错误
Java/Maven 残留进程 无

工作区改动共 6 个文件:5 个生产 Controller + docs/backend-development-roadmap.md。


2. 五个 Controller 静态审查

git diff --stat:

docs/backend-development-roadmap.md                |  2 +-
.../com/mydreamjob/controller/ApiController.java   | 12 +++++---
.../com/mydreamjob/controller/AuthController.java  | 33 +++++++++++-----------
.../mydreamjob/controller/SystemController.java    | 14 +++++----
.../java/com/mydreamjob/job/JobController.java     | 21 +++++++-------
.../FairnessMonitoringController.java              | 25 +++++++---------
6 files changed, 54 insertions(+), 53 deletions(-)
项 结果
修改范围是否仅限 A6 清单内 5 个 Controller 是 ✔
是否新增接口 / ErrorCode / Flyway / 表 / 索引 / 依赖 否 ✔
是否触碰 SecurityConfig / Filter / Accessor / GlobalExceptionHandler 否 ✔
是否修改既有测试文件 否 ✔(git status 中无任何 src/test 既有文件被修改)

2.1 禁止模式扫描(静态门禁)

对 5 个 Controller 逐一扫描 X-Request-Id 直读、SecurityContextHolder、 getAuthentication()、Long.valueOf(auth.getName()):

文件 命中
controller/ApiController.java 0 ✔
controller/AuthController.java 0 ✔
controller/SystemController.java 0 ✔
job/JobController.java 0 ✔
scoringmonitoring/FairnessMonitoringController.java 0 ✔

全仓 *Controller.java 扫描同一组模式:0 命中 ✔

2.2 构造器注入与无回退

ApiController、SystemController 新增 @RequiredArgsConstructor 并声明 private final CurrentUserAccessor / private final RequestIdAccessor; AuthController、JobController、FairnessMonitoringController 复用既有 @RequiredArgsConstructor。全部为 final 构造器注入,无字段默认值、无 null 回退、 无 new 兜底实现 ✔

2.3 GlobalExceptionHandler.java 三方 blob 比对

git hash-object      <file>  → 1fdc341ebe3653393b3ec04f0c2d0bb9dfbb6d21
git rev-parse HEAD:<file>    → 1fdc341ebe3653393b3ec04f0c2d0bb9dfbb6d21
git ls-files -s      <file>  → 100644 1fdc341ebe3653393b3ec04f0c2d0bb9dfbb6d21 0 <file>
git diff --quiet     <file>  → exit 0

工作区 blob 与 HEAD blob 完全一致,该文件在 git status 中的 M 标记是 Windows stat 缓存假阳性,未修改、未触碰 ✔


3. 删除的私有方法(真实数量:6)

逐文件 git diff 核对(- 行为真实删除):

# 文件 删除的方法签名
1 controller/ApiController private String getRequestId(HttpServletRequest)
2 controller/AuthController private String getRequestId(HttpServletRequest)
3 controller/SystemController private String getRequestId(HttpServletRequest)
4 job/JobController private String getRequestId(HttpServletRequest)
5 scoringmonitoring/FairnessMonitoringController private Long userId()
6 scoringmonitoring/FairnessMonitoringController private String requestId(HttpServletRequest)

计数澄清:getRequestId 名称的方法实际为 4 个; FairnessMonitoringController 的 requestId 方法签名为 requestId(HttpServletRequest) (无 get 前缀),不能被计入 getRequestId。 正确分解为 4 × getRequestId + 1 × userId + 1 × requestId = 6, 与上表逐文件清单一致 ✔

未为迎合任何数字修改生产代码 ✔


4. 独立接线变异证明

新增独立测试 A6AccessorWiringIndependentTest 将 CurrentUserAccessor、 RequestIdAccessor 全部替换为 mock,覆盖 5 个 Controller 的代表端点 (/、/health、/auth/me、GET /jobs、/admin/scoring/fairness/drift):

断言 结果
5 个端点响应体 requestId 均等于 mock 返回值 a6-indep-request-id 通过 ✔
Accessor 返回 4242 时 service.drift(4242L) 被调用 通过 ✔
Accessor 返回 77L 时 overview/dimensions/rankingStability 收到 77L 通过 ✔
Accessor 抛 InsufficientAuthenticationException → 401 且 service 零调用 通过 ✔
POST /jobs 企业侧身份经 CurrentUserAccessor.requireUserId() 通过 ✔

变异检测含义:若任一 Controller 把 requestIdAccessor.get(request) 换回直读 request.getHeader("X-Request-Id"),响应体将不再出现 mock 值 → 该断言必然失败; 若 FairnessMonitoringController 换回直读 SecurityContextHolder, 4242/77 将不会传到 Service → 断言失败。接线真实生效,非空转 ✔


5. 独立测试质量审查

复验对象: A6AccessorWiringIndependentTest(6 个用例)、A6IndependentVerificationTest(7 个用例)。

核对项 结果
公开端点未被错误要求 401 ✔ 无 Token 断言仅覆盖 /auth/me 与 /admin/scoring/fairness/drift,/、/health、GET /jobs 明确排除在断言之外
JobController 身份接线使用真正需要企业身份的端点 ✔ 改用 POST /jobs(resolveCompanyId() 路径),非公开的 GET /jobs
Service 参数验证不是空转 ✔ 断言具体数值(4242 / 77),而非仅 any()
requestId 断言未错误声称响应头来自 Accessor ✔ 只断言「响应头非空且已规范化」+「响应体 requestId 与响应头同源」;响应头由 RequestIdFilter 产出,未作错误归属
副作用快照列真实存在 ✔ 9 张表列名逐一对齐迁移文件(SCORING_RISK_EVENTS、SCORING_RISK_ACTIONS、JOBS、USERS、USER_ROLES、AUDIT_LOGS、NOTIFICATIONS、MESSAGES、CANDIDATE_DIMENSION_SCORES)
未修改或放宽既有测试 ✔ 既有测试文件零改动
未通过过度 mock 绕过目标 Controller ✔ 目标 Controller 全部真实(@SpringBootTest + MockMvc),只 mock 下游 Service 与两个 Accessor

5.1 独立测试首次运行的两处自身错误及修正

# 原断言 问题性质 修正
1 把公开端点路径 /、/health、GET /jobs 也断言为无 Token 401 测试自身错误(公开端点本不应 401),非生产缺陷 断言范围收窄为 /auth/me、/admin/scoring/fairness/drift 两个需认证端点
2 用公开的 GET /jobs 验证 JobController 的身份 Accessor 接线 测试自身错误(该端点不解析身份,断言无意义) 改用 POST /jobs 企业侧端点

两处均为新测试自身缺陷,已修正;未修改生产代码,未修改任何既有测试。 修正后两个类合计 13/13 通过。


6. 身份边界与 401 / 403 / 404 分层

6.1 非法主体一律 401(真实 Accessor + 真实 SecurityFilterChain)

对主体 anonymousUser、not-a-number、99999999999999999999、0、-1, 以及 AnonymousAuthenticationToken 和完全无 Token:

断言 结果
状态码一律 401(不得 400/500) ✔
响应体 code = 401 ✔
message ∈ {authentication required, not authenticated} ✔
message 不回显异常类名、java.、principal、anonymousUser、com.mydreamjob、NumberFormat ✔
响应体无 stackTrace / trace ✔
业务 Service 零调用 ✔
合法数字主体(7)不被身份层拒绝 ✔

6.2 401 / 403 / 404 分层未漂移

静态审查确认:A6 diff 未删除、未新增任何角色校验或资源归属校验; JobController.resolveCompanyId() 与 jobService.assertOwnerOrThrow(companyId, jobId) 原样保留, AuthController 的「已注销手机号 → HTTP 409 / code=9303」分支原样保留。

既有契约由既有测试继续守护且全部通过(例:AuthenticationBoundaryIntegrationTest 对 POST /jobs 断言 403 + ErrorCode.FORBIDDEN)。403 / 404 契约未漂移 ✔


7. requestId 语义

对 5 个 Controller 的代表端点,注入 X-Request-Id ∈ {a6v-legal-1, 全空白, 90 字符超长, <b>x</b>, a b}:

断言 结果
响应头 X-Request-Id 非空 ✔
不含 <、>、空格、CR、LF ✔
长度 ≤ 32 ✔
响应体 "requestId":"<响应头同值>" ✔
请求属性优先于原始 Header ✔
CR/LF 不进入规范化结果(6 组恶意输入) ✔

8. 失败请求的业务副作用

illegalIdentity_producesNoBusinessWrite:对 9 张核心表按显式列 + 稳定主键排序做全量快照, 执行 5 组非法主体请求 + 1 组无 Token 请求后再次快照。

结果
9 张表逐字段零变化 ✔

9. FairnessMonitoringControllerTest 单跑:真实结果与准确根因

9.1 单跑命令与真实结果(不写成通过)

mvn -Dtest="FairnessMonitoringControllerTest" test

日志:docs/acceptance/r01-d4-2-20260916/a6v-fairness-repeat.log

Tests run: 7, Failures: 6, Errors: 0, Skipped: 0
BUILD FAILURE

6 个失败全部是 Status expected:<200|403|400> but was:<401>; 唯一通过的是 anonymous_isUnauthorized(不发 Token 的那个)。

9.2 逐项查明(任务书第 2 步的 6 个问题)

# 问题 结论 证据
1 HTTP 实际状态 401,响应体 {"code":401,"message":"not authenticated","timestamp":""} MockHttpServletResponse 输出;Handler: Type = null(请求未进入任何 Controller)
2 Filter 为什么拒绝 不是验签失败,而是服务端会话校验的用户检查失败 日志:AuthSessionService - session validation rejected: user not usable → JwtAuthenticationFilter - JWT authentication rejected: session not active
3 测试用户是否存在且 is_active = true 行存在,但 is_active = 0(不是"不存在",也不是"active") 见 §9.3 推理链
4 auth_tokens 会话记录是否存在/未过期/未消费/未撤销 存在且有效;拒绝发生在读取会话记录之前 TestAuthTokens.writeSession 自身执行 SELECT COUNT(*) FROM auth_tokens WHERE user_id=? AND session_id=? AND revoked=0 AND consumed_at IS NULL AND expired_at > ?,不可见即抛 IllegalStateException;日志中无该异常,说明会话行有效。而 isSessionActive() 先判用户、后判会话,拒绝文本是 "user not usable" 而非 "no active session"
5 JWT 的 sid/jti/sub/type/aud/iss 是否与库一致 一致;JWT 本身完全合法 parseAndValidateAccessToken 未抛异常(否则拒绝路径不同)。日志中 Token 载荷:sub=1、type=access、sid=0073abf7-…、role=ADMIN、iss=mydreamjob、aud=["mydreamjob-api"]、iat=1577836800(2020-01-01)、exp=2523916800(2050-01-01),与夹具 FIXTURE_BASE/30 年 TTL 完全对应;sid 即写入 auth_tokens.session_id 的同值
6 是否真的与 user_roles 有关 无关 AuthSessionService.isUsable(User) 只读 user.getActive();UserRepository.findById 无角色过滤;该路径从不查询 user_roles

9.3 准确根因(证据链,取代此前未经证实的 user_roles 说法)

根因:TestAuthTokens 夹具只对"不存在的" users.id 执行 INSERT,对"已存在但 is_active=0"的行不做激活,而 V1_1 种子账号恰好占用 id=1/2/3 并被 V43 置为禁用。

证据链(逐步闭合):

  1. V1_1__seed_data.sql 按自增插入三名演示账号 → 占据 users.id = 1 / 2 / 3 (13800000001 ADMIN、13800000002 STUDENT、13800000003 COMPANY)。
  2. V43__disable_legacy_seed_accounts.sql 按原始指纹把这三行 is_active 置为 0。
  3. FairnessMonitoringControllerTest 使用 token(1L, "ADMIN") / token(2L, "STUDENT") → 命中 id=1/2 这两个已被禁用的种子行。
  4. TestAuthTokens.writeSession() 的行为是:
    SELECT COUNT(*) FROM users WHERE id = ?      // id=1 已存在 → count = 1
    if (count == null || count == 0) { INSERT ... is_active=1 }   // → 被跳过,永不激活
    

    即夹具只补建缺失行,不修复既有行的启用状态。

  5. users.created_at / updated_at 在 V1__base_schema.sql 中均有 DEFAULT CURRENT_TIMESTAMP,因此"若行不存在则夹具 INSERT 必然成功且 is_active=1"成立。 既然实际观察到 401(而非 500/ERROR),只可能是行已存在且 is_active=0 —— 推理闭合。
  6. AuthSessionService.isSessionActive() 第一步 isUserUsable(userId) 因此返回 false, 直接短路返回,从未走到会话记录检查,与日志文本完全吻合。

9.4 与 A6 生产改动的归因判定:无关

判据 内容
代码路径 401 由 JwtAuthenticationFilter / AuthSessionService 在请求进入 Controller 之前产生(Handler: Type = null)。A6 只改了 5 个 Controller 内部取 requestId / 取身份的写法,该路径上一行 A6 代码都未执行
A6 diff 范围 仅 5 个 Controller + 文档;SecurityConfig、Filter、AuthSessionService、UserRepository、TestAuthTokens、Flyway 迁移零改动
失败面 7 个用例中,唯一不携带 Token 的用例通过;其余全部 401 —— 与「Token 认证链路在用户检查处失败」完全一致,与「Controller 被改写」不一致

9.5 为何全量顺序下不触发(机制已定位,非"偶然")

com.mydreamjob.controller.AuthIntegrationTest 的 @BeforeEach(源码第 97 行)执行 userRepository.deleteAll();,在共享的 integrationdb(无 @TestPropertySource 覆盖)上 清空全部用户,包括 V43 已禁用的 V1_1 种子行。

全量日志 docs/acceptance/r01-d4-2-20260916/a3-commit-cv.log 的执行顺序印证:

第 151767 行  [INFO] Running com.mydreamjob.controller.AuthIntegrationTest
第 247038 行  [INFO] Running com.mydreamjob.scoringmonitoring.FairnessMonitoringControllerTest

种子行被清空后,夹具第 4 步的 COUNT(*) 变为 0 → INSERT 补建 is_active=1 的用户 → 用户检查通过 → 该类在全量顺序下 7/7 通过。

因此:同一份代码,只因子夹具前置数据不同而结果不同 —— 属顺序依赖的夹具脆弱性, 不是被测代码的缺陷。

决定性对照实验(不新增任何文件、不修改任何代码,只改变前置数据):

mvn -Dtest="com.mydreamjob.controller.AuthIntegrationTest,FairnessMonitoringControllerTest" test

日志:docs/acceptance/r01-d4-2-20260916/a6v-fairness-mechanism.log

[INFO] Running com.mydreamjob.controller.AuthIntegrationTest
[INFO] Tests run: 6, Failures: 0, Errors: 0, Skipped: 0 -- in ...AuthIntegrationTest
[INFO] Running com.mydreamjob.scoringmonitoring.FairnessMonitoringControllerTest
[INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0 -- in ...FairnessMonitoringControllerTest
[INFO] BUILD SUCCESS
运行方式 同一份含 A6 的代码 结果
单独运行 Fairness A6 在场 7 run / 6 failures
AuthIntegrationTest 先运行 A6 在场 7 run / 0 failures
全量 mvn clean verify A6 在场 7 run / 0 failures

A6 代码在三行中完全相同,唯一变量是前置数据 —— 归因判定为夹具脆弱性的直接实验证据。

9.6 处置

  • 不修改生产代码 ✔
  • 不修改既有 FairnessMonitoringControllerTest ✔
  • 该缺陷列为 A6 提交后的独立纯测试修复:让 TestAuthTokens 在行已存在时 显式确保 is_active = 1(或改用不与 V1_1 种子账号冲突的 userId)。
  • 未把该单跑命令写成通过 ✔

10. 全仓剩余统计(逐文件分类)

身份解析(SecurityContextHolder.getContext().getAuthentication / Long.(valueOf|parseLong)(...getName()))—— 3 文件

文件 分类
security/CurrentUserAccessor 基础设施(Accessor 自身)
auth/JwtAuthenticationFilter 基础设施(认证过滤器)
deletion/DeletionGuardInterceptor 基础设施(拦截器)

requestId 解析(X-Request-Id 直读)—— 5 文件

文件 分类
audit/AuditContext 基础设施
common/logging/RequestIdAccessor 基础设施(Accessor 自身)
common/logging/RequestIdFilter 基础设施(过滤器)
common/logging/RequestLoggingFilter 基础设施
deletion/DeletionGuardInterceptor 基础设施

业务 Controller 残留:身份解析 0、requestId 重复解析 0 ✔


11. 测试数

11.1 A6 定向

mvn -Dtest="R01A6RequestContextMigrationIntegrationTest,A6*Test,CurrentUserAccessorTest,RequestIdAccessorTest,RequestIdFilterTest" test

日志:docs/acceptance/r01-d4-2-20260916/a6v-v1-targeted.log

测试类 Tests
common.r01a6.R01A6RequestContextMigrationIntegrationTest 7
common.r01a6.A6AccessorWiringProbeTest 3
common.r01a6.A6AccessorWiringIndependentTest 6
common.r01a6.A6IndependentVerificationTest 7
security.CurrentUserAccessorTest 11
common.logging.RequestIdAccessorTest 16
common.logging.RequestIdFilterTest 3
合计 53 / 53,Failures=0、Errors=0、Skipped=0,BUILD SUCCESS ✔

11.2 A6 关联

mvn -Dtest="*ApiController*Test,*AuthController*Test,*SystemController*Test,*JobController*Test,*Security*Test,*RequestId*Test" test

日志:docs/acceptance/r01-d4-2-20260916/a6v-v2-related.log

测试类 Tests
auth.SmsVerificationSecurityTest 10
common.logging.RequestIdAccessorTest 16
common.logging.RequestIdFilterTest 3
合计 29 / 29,Failures=0、Errors=0、Skipped=0,BUILD SUCCESS ✔

说明:该命令的 *ApiController*Test、*AuthController*Test、*SystemController*Test、 *JobController*Test 四个模式在本仓库不存在同名测试类(find 全量核对: 无 ApiControllerTest / SystemControllerTest / JobControllerTest / AuthControllerTest), 故只命中上表 3 个类。这 5 个 Controller 的既有覆盖由 mvn clean verify 全量承担(含 AuthenticationBoundaryIntegrationTest 对 POST /jobs 的 403 断言、controller.AuthIntegrationTest 的登录/刷新/登出全链路等)。未因此放宽任何断言。

11.3 全量

mvn clean verify

日志:docs/acceptance/r01-d4-2-20260916/a6v-v3-verify.log

[INFO] Tests run: 2278, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS
项 数值
全量测试总数 2278
Failures 0 ✔
Errors 0 ✔
Skipped 0 ✔
原实施基线 2265
独立新增 13(A6AccessorWiringIndependentTest 6 + A6IndependentVerificationTest 7)
2265 + 13 2278 ✔ 与 Surefire 汇总完全一致

无需要按 Surefire XML 另行解释的差异 —— 汇总行与预期逐位吻合。

11.4 FairnessMonitoringControllerTest 单跑(不计入通过)

命令 结果
mvn -Dtest="FairnessMonitoringControllerTest" test 7 run / 6 failures / 0 errors / 0 skipped,BUILD FAILURE

按任务书要求:该单跑命令不写成通过,其失败与准确根因见 §9。


12. OpenAPI

python ..\docs\verify_openapi.py
→ paths: 176 / operations: 214
→ All checks passed!

git diff -- docs/openapi-rc.json → 空,openapi-rc.json 零修改 ✔


13. Flyway / ErrorCode / DTO / 接口

项 结果
git diff --name-only -- mydreamjob-server/src/main/resources/db/migration 空 —— V1–V45 未改动 ✔
ErrorCode 未修改 ✔
DTO / 响应体结构 未修改(仅取值来源改变) ✔
接口(路径 / 方法 / 参数) 未新增、未删除、未改签名 ✔
依赖(pom.xml) 未修改 ✔

14. Git 状态

项 结果
分支 refactor/r01-structure-performance
HEAD 534afe686b966f175bcaf4af0726cec061092050(未变)
git diff --cached --name-only 空
git diff --check 仅 LF/CRLF 警告,exit 0
已暂存 / 已提交 / 已推送 均无 ✔(git log -1 仍为 A5 提交 534afe6)
与远端关系 ## refactor/r01-structure-performance...origin/refactor/r01-structure-performance(无 ahead/behind)
历史未跟踪文件 未删除 ✔
本轮验收日志 位于 docs/acceptance/r01-d4-2-20260916/,该路径被 .gitignore:43 **/*.log 忽略(docs/acceptance 受版本管理的文件数为 0),不会进入任何提交 ✔
残留 Java/Maven 进程 无 ✔

15. 明确声明

  • 未暂存、未提交、未推送。
  • 未修改任何生产代码;未修改、删除或放宽任何既有测试断言; 未修改 GlobalExceptionHandler、SecurityConfig、Filter、Accessor、ErrorCode、DTO、 OpenAPI、Flyway 迁移与 V1–V45。
  • 本轮仅新增 docs/r01-a6-independent-verification-20260917.md 与验收日志; 独立测试自身的两处缺陷已就地修正(§5.1),未触碰生产代码与既有测试。
  • 未进入 R01-B;A 系列到此结束,不再设置 A7/A8。
  • 本轮未启动 Docker,未伪造真实 MySQL 结论。

R01-A6独立代码复验通过,可以进入精确暂存与独立提交检查点;A系列到此结束,不再设置A7/A8。FairnessMonitoringControllerTest的单跑夹具缺陷须在A6提交后以独立纯测试提交修复。