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

S01 最终真实 MySQL / Docker 增量复验报告(脱敏版)

  • 验收日期:2026-09-14
  • 分支:fix/resume-upload-safety
  • 范围:只验证 S01 第四轮整改(F6:expires_at → expired_at)后的真实运行路径;本轮未修改任何代码
  • 结论:PASS
  • 本报告不含:验证码值、完整手机号、JWT、Authorization 头、数据库口令、.env 内容

本轮为此前因 Docker Linux Engine 未运行而 BLOCKED 的复验。本次 Docker Engine 可用,全部真实黑盒已执行完成。


1. 基线与边界

项 实测
分支 fix/resume-upload-safety
暂存区 空(git diff --cached --name-only 无输出)
git diff --check 无空白错误(仅 CRLF 提示)
迁移改动 无(git diff --name-only -- db/migration 无输出;41 个文件,最大 V41__message_recall.sql)
已跟踪改动 24 个(全部来自此前几轮:7 个 docs + 11 个 java main + 6 个 java test),本轮 0 新增
删除文件 0
历史未跟踪文件 未删除、未移动、未覆盖

本轮未修改:生产代码、现有测试、docs/smoke-test.sh、V1–V41;未新增 Flyway/表/索引/ErrorCode/接口;未恢复验证码日志。

2. Docker 环境

项 值
Docker Engine 29.6.2(Docker Desktop 4.84.0,linux/amd64)
mydreamjob-mysql healthy(mysql:8.4,127.0.0.1:3307→3306)
mydreamjob-redis healthy(redis:7.4-alpine)
mydreamjob-rabbitmq healthy(rabbitmq:3.13-management-alpine)

进入本轮时三个容器均为 Exited(2 天前停止)。经负责人明确授权后,对已存在的具名容器执行 docker start:未重建容器、未改动配置、未触碰 volume(mysql_data/redis_data/rabbitmq_data 均保留)。验收结束后容器保持运行,由负责人决定是否停止。

3. 真实 Schema 对账

真实 MySQL 执行 SHOW COLUMNS FROM sms_verification_codes(应用账号 + MYSQL_PWD,非 root):

id  phone  code  purpose  attempts  max_attempts  expired_at  used  created_at

三方对账完全一致:

来源 列集合
真实 MySQL attempts code created_at expired_at id max_attempts phone purpose used
实体 @Column 同上
V1__base_schema.sql 同上
  • 包含 expired_at ✅
  • 不包含 expires_at ✅

反证:SELECT expires_at FROM sms_verification_codes LIMIT 1; → ERROR 1054 (42S22) Unknown column 'expires_at' in 'field list'(退出码 1)。

4. 真实取码 SQL

修复后的等价只读查询在真实 MySQL 执行:

SELECT code FROM sms_verification_codes
 WHERE phone = ? AND purpose = ? AND used = 0
   AND expired_at > NOW()
 ORDER BY created_at DESC, id DESC LIMIT 1;

退出码 = 0,无 ERROR 1054。(查询返回行数按参数而定,本报告不展示任何取回的 code。)

5. 最新 JAR 启动

项 值
JAR target/mydreamjob-server-0.0.1-SNAPSHOT.jar(mvn clean verify 产物)
启动参数 --spring.jpa.hibernate.ddl-auto=validate、--app.sms.mock.enabled=true、--server.port=8080
启动结果 Started MyDreamjobApplication(14.9 s)
Flyway 无错误(仅一条 MySQL 8.4 版本提示 WARN)
Schema validation 无错误
GET /api/v1/health HTTP 200,status=UP
运行时 OpenAPI 173 paths / 211 operations

端口 8080 在启动前空闲,无未知进程被停止。日志写入本轮新目录 docs/acceptance/s01-20260914/,未覆盖历史日志。

6. 真实完整冒烟链

以唯一虚构前缀 1990914xxxx 与隔离 requestId 执行:

SMS_MOCK_ENABLED=true ALLOW_TEST_OTP_DB_READ=true bash docs/smoke-test.sh

结果:PASS=10 FAIL=0 SKIP=1,exit=0

步骤 结果
GET /health PASS(200)
POST /auth/sms/send + POST /auth/login PASS(200 / 200)
GET /jobs?size=5 PASS
GET /jobs/public/{id} PASS
GET /jobs/{id}/match PASS
POST /applications PASS
PATCH /applications/{id}/status SKIP(未提供 COMPANY_TOKEN,属脚本既有语义)
GET /notifications PASS
GET /notifications/unread-count PASS
POST /notifications/{id}/read PASS
POST /applications/{id}/withdraw PASS

逐项确认:

  1. 发码成功 ✅
  2. smoke-test.sh 经真实 docker exec 连真实 MySQL ✅
  3. 使用应用账号 MYSQL_USER,未使用 root ✅
  4. mysql argv 不含密码 ✅(见 §7)
  5. SQL 使用 expired_at ✅
  6. 成功取得可用验证码,未显示其值 ✅
  7. 真实 POST /auth/login 成功 ✅
  8. 获得 Access Token,未输出 ✅
  9. 后续岗位/投递/通知步骤按夹具条件执行 ✅
  10. 未出现 ERROR 1054 ✅
  11. 未出现空验证码 ✅
  12. 未从日志读取验证码 ✅
  13. stdout/stderr 不含验证码、完整手机号、JWT、Authorization、数据库口令 ✅

核心链路无 SKIP:唯一 SKIP 是企业状态步骤,取码与登录核心链路均实际执行并通过。

真实库侧证据:该手机号在 sms_verification_codes 中产生 1 行、used=1、code 长度 6 —— 真实 MySQL 中确实生成了 6 位验证码并被真实登录消费。

关键推论:登录成功即证明真实执行的 SQL 使用了有效列。若仍是 expires_at,查询会报 ERROR 1054、取不到码,登录必然失败。

bash -x 复验

以 bash -x 执行(新手机号):exit=0,PASS=10 FAIL=0 SKIP=1,捕获 stdout+stderr 后扫描,敏感命中数:

标记 命中
实际验证码值 0
完整手机号 0
Authorization: Bearer 0
LOGIN_BODY= 0
JWT 形态(eyJ…) 0
ERROR 1054 0

7. F5 真实环境 argv 证据

在真实查询执行期间,读取容器内 mysql 进程的实际 argv:

mysql -umydreamjob -N -B mydreamjob
  • 使用应用账号(非 root)✅
  • 不含 -p<密码> ✅
  • 不含 --password ✅
  • 密码经 MYSQL_PWD 环境变量传递 ✅

8. mock=false 真实 MySQL 回滚

以 --app.sms.mock.enabled=false 重启后调用 POST /auth/sms/send:

项 结果
响应 HTTP 400 + {"code":400,"message":"sms sender unavailable"}
是否虚假成功 否 ✅
验证码表前后状态 完全一致(该手机号 0 行;全局 max_id 994→994;总数 918→918) ✅
残留 used=0 验证码 无 ✅
新增虚假发送成功审计 0 行 ✅
日志含手机号/验证码 0 命中 ✅

响应与上一轮(docs/acceptance/s01-20260911/d2-sms-response.txt)逐字一致。

9. 配置缺失 / 空值 / 非法值启动

用例 启动 Bean 缺失错误 发码响应 是否落库
配置缺失 Started ×1 0 400 sms sender unavailable 未落库(994→994) ✅
SMS_MOCK_ENABLED=(空值) Started ×1 0 400 sms sender unavailable 未落库(994→994) ✅
SMS_MOCK_ENABLED=invalid-value(非法值) Started ×1 0 400 sms sender unavailable 未落库(994→994) ✅

「恰好一个 SmsCodeSender 且为 UnavailableSmsCodeSender」在真实环境按排除法成立:

  • 0 个 Bean → 启动会以 NoSuchBeanDefinition 失败;≥2 个 → 会以 NoUniqueBeanDefinition 失败;Mock 生效 → 发码成功且落库。
  • 实测:启动成功 + 发码不可用 + 未落库,三者互斥地锁定「恰好一个 UnavailableSmsCodeSender」。

(Bean 数量与类型的严格断言另由 SmsSenderConfigurationTest(真实 ApplicationContextRunner)在 §10 覆盖。)

每次只停止本轮明确记录 PID 的 JAR,未触碰任何未知进程。

10. 日志脱敏扫描

扫描对象:本轮全部新日志(6 个应用日志 + stdout)+ 冒烟 stdout/stderr。

禁止项 命中
实际验证码(本轮真实产生的 2 个,逐个比对) 0 / 0
完整手机号(6 个测试号逐个比对) 0
Authorization: Bearer 0
JWT 形态(eyJ…) 0
MYSQL_PASSWORD 实际值 / 数据库口令 0
LOGIN_BODY 0
MOCK SMS CODE 0
NumberFormatException 0
ERROR 1054 0

敏感标记命中合计 = 0。

扫描器有效性对照:注入探针(MyDreamJob backend smoke test)命中 2,证明扫描器在工作,而非空转的 0。

11. OpenAPI 与回归

项 结果
python docs/verify_openapi.py All checks passed!
运行时 /v3/api-docs 173 paths / 211 operations
静态 docs/openapi-rc.json 173 paths / 211 operations
静/运行时一致 YES(路径差集:静态独有 = 无,运行独有 = 无)
是否新增取码接口 无(含 "code" 的唯一路径是 /admin/scoring/risk/rules/{ruleCode},为风控规则码,与验证码无关)
接口结构变化 无

Maven 安全专项:

mvn -Dtest="SmsSenderConfigurationTest,SmsVerificationSecurityTest,SmsVerificationServiceTest,CurrentUserAccessorTest,AuthenticationBoundaryIntegrationTest,SmsEmptyPropertyStartupIntegrationTest,SmsUnavailableSenderRollbackIntegrationTest" test

→ Tests run: 61, Failures: 0, Errors: 0, Skipped: 0 — BUILD SUCCESS

12. 数据清理

本轮产生的数据可按唯一虚构前缀 1990914 精确圈定(时间戳均为 2026-09-14):

表 行数
users 2(验收虚构号码:199****0101、199****0102)
sms_verification_codes 2
applications 2(均为 WITHDRAWN)
notifications 4
user_roles 2
audit_logs 8

决策:保留本轮数据,不执行删除。 理由:

  1. audit_logs 有 8 行 user_id 指向这两个用户,且该表无外键约束;删除用户会使审计行成为孤儿。
  2. 本轮任务边界明确禁止删除 audit_logs 行。
  3. 保留可维持审计链一致;边界亦明确允许「无法安全确认时保留数据并记录」。

未执行任何模糊 DELETE、未清空表、未重置自增、未修改真实账号。上述足迹为精确记录,供后续人工处置。

13. 最终 Git 检查

项 结果
git status --short 24 个已跟踪改动(与本轮开始时同一批)+ 98 个未跟踪(既有残骸 + 本轮验收产物)
git diff --cached --name-only 空(未暂存)
git diff --check 无空白错误
git diff --name-only -- db/migration 空(V1–V41 未修改)
删除文件 0
commit / push 未执行
S02 未进入

14. 验收结论

# 判据 结果
1 Docker/MySQL 可用 ✅
2 真实表包含 expired_at ✅
3 真实取码 SQL 执行成功 ✅
4 完整学生登录链成功 ✅
5 bash -x 无敏感泄露 ✅
6 mock=false 真实 MySQL 完整回滚 ✅
7 缺失/空/非法配置安全回退 ✅
8 运行时 OpenAPI 为 173/211 ✅
9 无日志泄露 ✅
10 核心项无 SKIP ✅
11 FAIL = 0 ✅

S01独立代码复验和真实MySQL/Docker黑盒全部通过,可以进入精确暂存检查点。


遗留与观察(不阻断本轮结论)

  1. 验证码仍为数据库明文存储(未改表结构,属已知遗留)。
  2. 真实短信供应商未接入;生产 mock=false 下发码一律安全失败,需产品确认上线策略。
  3. 本轮测试账号保留在库(见 §12),与其审计记录一致;如需清理应由人工按精确 ID 处置。
  4. /api/v1/api-docs 返回 500(/api/v1/v3/api-docs 正常 200)。该行为与 S01 无关,非本轮引入,记录待查。
  5. 三个容器在验收结束后保持运行(本轮仅 docker start,未重建、未删除 volume),是否停止由负责人决定。