Files
danyang-match/docs/Phase-17-V11-Recovery-Completion-Report-2026-09-17.md
T

95 lines
7.5 KiB
Markdown
Raw Normal View History

# Phase 17 — 举报与风控 KEEP-VERIFY/EXTEND 复验完成报告
**日期**:2026-09-17
**范围**:举报与风控(risk / report / referral 模块 + 后台风险页)
**纪律**:KEEP-VERIFY/EXTEND。历史 ✅ 不可信;测试覆盖 ≠ 运行时路径覆盖;逐条核对运行时路径与生产约束;发现真实缺陷即修;补测试保真度;守卫必须解析真实 DDL;离线容器真实编译+执行;每阶段出报告后停止等确认。
---
## 一、结论
**两项 Gate 全部达成,复验关闭。**
| 项 | 结果 |
|---|---|
| 风险来源可追踪 | ✅ counseling / service_station / offline_activity 三来源均接入统一 `RiskEvent`,且已确认运行时真实调用(非定义未调用) |
| 正文最小可见 | ✅ 后台风险画像只带必要来源上下文,不暴露无关咨询正文(INV-006 同族约束延续) |
| 低权限人员不能扩大读取范围 | ✅ 禁止公开黑名单;敏感访问全部审计(AGENTS.md #8 敏感数据默认拒绝 + 查看需审计) |
**本轮修复真实缺陷 6 处(含 1 处必崩级测试盲区、1 处隐藏编译缺陷)**,新增集成测试 7 例。
- 全量回归:**488 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS**(约 4 分)
- 基线 481 → 488,**净 +7**(Phase 17 集成测试 7 方法)
- 负向验证:临时注释 `getReportDetail` 敏感访问审计 → `operatorRiskEndpointsRecordSensitiveAccessAudit` 精准变红,已还原
---
## 二、本轮最关键发现(必记)
### F1:风控/举报真实 DB 路径「零覆盖」盲区(必崩级)
`EntityMigrationParityTest` 的白名单 `TEST_SCHEMA_OPTIONAL_TABLES` 中列有 `reports` / `risk_actions` / `risk_events` 三张表,但 **H2 测试库 `schema-h2.sql` 中根本没有这三张表的定义** → 整条 `ReportService` / `RiskReferralService` 真实 DB 路径**零测试覆盖**;而 `ReportServiceTest` 全是 `assertTrue(true)` 假绿。这与 STATUS.md 反复警示的"测试库缺表 → 零覆盖 → 必崩"模式完全一致,是本轮 KEEP-VERIFY 的最高风险点。
**修复**:在 `schema-h2.sql` 补齐三表(对齐 V012 生产 DDL,去外键、保留 CHECK);将三表从 `TEST_SCHEMA_OPTIONAL_TABLES` 移除,加入 `REQUIRED_IN_TEST_SCHEMA`(带 Phase 17 注释);新建真实 DB 集成测试。
### F2:隐藏编译缺陷(javax → jakarta)
为补审计在 `ReportService` / `RiskReferralService` / `AuditLogService` 中引入 `HttpServletRequest` 取 ip/userAgent 时,误用 `javax.servlet.http`(Spring Boot 3.x 已迁移到 **Jakarta EE** `jakarta.servlet.http`)→ `test-compile` 直接失败。修复为 `jakarta.servlet`。**印证 Phase 15 铁律:每次复验结束前必须真实编译+执行,不能以文档声明替代实证。**
### F3:补表时的列缺失
`schema-h2.sql` 的 `risk_events` 表起初漏了 `updated_at` 列(实体 `RiskEvent extends BaseEntity` 含 `updatedAt`,MyBatis-Plus 注入该列)→ 全量回归 6 个 `BadSqlGrammar` 错误。修复补齐 `updated_at`(与 `reports` / `risk_actions` 一致)。
---
## 三、真实缺陷清单(对应 Gate)
| 编号 | 类别 | 缺陷 | 修复 | Gate |
|---|---|---|---|---|
| D1 | 测试保真度/必崩级盲区 | `reports`/`risk_events`/`risk_actions` 在 H2 缺失 → 风控/举报真实 DB 路径零覆盖;`ReportServiceTest` 假绿 | 补三表 + 移出白名单 + 入必需集合 + 真实集成测试 `ReportAndRiskPhase17IntegrationTest`(7 例) | 风险来源可追踪 |
| D2 | 审计缺失 | `manualFlagRisk`(人工风险标记)不写审计 | 注入 `AuditLogMapper`,写 `MANUAL_FLAG_RISK` | 低权限不扩大读取 |
| D3 | 敏感访问无审计 | 查看他人风险画像/举报详情/风险事件列表不记录审计(AGENTS.md #8) | 新增 `AuditLogService.recordSensitiveAccess`;`getReportDetail`/`getUserRiskProfile`/`RiskController.getRiskEvents` 三端点接线 | 低权限不扩大读取 |
| D4 | 审计完整性 | `ReportService.saveAuditLog` 未填 `ip`/`userAgent` | 增强补全(与 `AuthorizationAuditListener` 同模式取 `RequestContextHolder`) | 低权限不扩大读取 |
| D5 | 数据一致性 | `RiskEventMapper.findByUserId` 未过滤 `deleted_at`,与 `sumScoreByUserId`(已过滤)不一致 | 补 `deleted_at IS NULL` | 风险来源可追踪 |
| D6 | 编译/列缺失 | 见 F2、F3 | jakarta 修正 + `risk_events` 补 `updated_at` | — |
> 来源接入代码层(`detectCounselingRisk` / `detectAppointmentRisk` / `detectActivityRisk`)**在运行时已被 `CounselingService`/`ActivityService`/`ServiceAppointmentService` 真实调用**,常量齐全,不是"假绿",本轮核心在修复覆盖盲区与审计缺口,而非重写来源逻辑。
---
## 四、负向验证(守卫有牙)
- 临时注释 `ReportOperatorController.getReportDetail` 中的 `auditLogService.recordSensitiveAccess(...)` → 定向运行 `ReportAndRiskPhase17IntegrationTest` → **BUILD FAILURE**(`operatorRiskEndpointsRecordSensitiveAccessAudit` 断言 `before+3 == after` 失败,实际只 +2)。已还原,代码回到 488 全绿状态。
---
## 五、证据(实测,非声明)
- **全量回归**:`488 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS`(离线容器 `danyang-match-backend-test` `mvn -B -o test`,约 4 分)。
- **定向编译验证**:`mvn -B -o test-compile` 通过(jakarta 修正后)。
- **新增测试**:`backend/src/test/java/com/danyang/match/platform/risk/ReportAndRiskPhase17IntegrationTest.java`(7 例:submitReport 写 reports+abuse_report 风险事件、三来源接入统一 RiskEvent、处置封禁写 risk_actions(ban)、manualFlagRisk 审计、敏感访问审计、findByUserId 软删过滤、最小可见)。
---
## 六、改动文件清单
**主代码(src/main)**
- `backend/src/test/resources/schema-h2.sql` — 补齐 `reports` / `risk_events` / `risk_actions` 三表(对齐 V012,去外键、保留 CHECK;`risk_events` 含 `updated_at`)。
- `backend/src/main/java/com/danyang/match/platform/risk/mapper/RiskEventMapper.java` — `findByUserId` 补 `deleted_at IS NULL`。
- `backend/src/main/java/com/danyang/match/platform/risk/service/ReportService.java` — `saveAuditLog` 补全 `ip`/`userAgent`(jakarta 修正)。
- `backend/src/main/java/com/danyang/match/platform/risk/service/RiskReferralService.java` — 注入 `AuditLogMapper`,`manualFlagRisk` 写审计(jakarta 修正)。
- `backend/src/main/java/com/danyang/match/platform/audit/service/AuditLogService.java` — 新增 `recordSensitiveAccess`(jakarta 修正)。
- `backend/src/main/java/com/danyang/match/platform/risk/controller/ReportOperatorController.java` — `getReportDetail`/`getUserRiskProfile` 接线敏感访问审计。
- `backend/src/main/java/com/danyang/match/platform/risk/controller/RiskController.java` — `getRiskEvents` 接线敏感访问审计。
**测试(src/test)**
- `backend/src/test/java/com/danyang/match/platform/risk/ReportAndRiskPhase17IntegrationTest.java` — 新增 7 例。
- `backend/src/test/java/com/danyang/match/platform/deployment/EntityMigrationParityTest.java` — 三表移出白名单、入 `REQUIRED_IN_TEST_SCHEMA`(Phase 17 注释)。
**文档**
- `docs/STATUS.md`、`docs/JIRA_BACKLOG.md`、`docs/善缘丹阳 V1.1 客户需求基线增量开发与修复计划.md` 的 Phase 17 段。
- `docs/Phase-17-V11-Recovery-Completion-Report-2026-09-17.md`(本报告)。
- 技能 `danyang-platform-verification` 同步更新。
---
## 七、下一步
**Phase 18(申诉 — KEEP-VERIFY)未自动进入,等待用户确认后启动。** 是否继续?