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 |
逐项确认:
- 发码成功 ✅
smoke-test.sh经真实docker exec连真实 MySQL ✅- 使用应用账号
MYSQL_USER,未使用 root ✅ - mysql argv 不含密码 ✅(见 §7)
- SQL 使用
expired_at✅ - 成功取得可用验证码,未显示其值 ✅
- 真实
POST /auth/login成功 ✅ - 获得 Access Token,未输出 ✅
- 后续岗位/投递/通知步骤按夹具条件执行 ✅
- 未出现
ERROR 1054✅ - 未出现空验证码 ✅
- 未从日志读取验证码 ✅
- 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 |
决策:保留本轮数据,不执行删除。 理由:
audit_logs有 8 行user_id指向这两个用户,且该表无外键约束;删除用户会使审计行成为孤儿。- 本轮任务边界明确禁止删除
audit_logs行。 - 保留可维持审计链一致;边界亦明确允许「无法安全确认时保留数据并记录」。
未执行任何模糊 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黑盒全部通过,可以进入精确暂存检查点。
遗留与观察(不阻断本轮结论)
- 验证码仍为数据库明文存储(未改表结构,属已知遗留)。
- 真实短信供应商未接入;生产
mock=false下发码一律安全失败,需产品确认上线策略。 - 本轮测试账号保留在库(见 §12),与其审计记录一致;如需清理应由人工按精确 ID 处置。
/api/v1/api-docs返回 500(/api/v1/v3/api-docs正常 200)。该行为与 S01 无关,非本轮引入,记录待查。- 三个容器在验收结束后保持运行(本轮仅
docker start,未重建、未删除 volume),是否停止由负责人决定。