# Phase 21(账户生命周期)KEEP-VERIFY 复验完成报告 — 2026-09-18 > **结论:CLOSED(Gate 全项通过)** —— 但这是**复验后**的结论,不是 `STATUS.md` 原有「✅」所声称的结论。 > 原有「✅」只是历史进度声明;KEEP-VERIFY 审计发现该模块同时存在 **Gate 硬违反、死代码/桩、以及一处会让"关闭未来服务"逻辑永远不可达的设计矛盾**。 --- ## 一、复验起点:为什么「已完成」不可信 `STATUS.md` 早已把 Phase 21 标为「✅ 已完成」,P21-T01~T05 全部打勾。但 KEEP-VERIFY 实测发现: - **Gate「注销后不能登录」被违反** —— `AuthService.checkAccountStatus` 只拒绝 `BANNED`/`SUSPENDED`,**不拒绝 `CLOSED`**,注销后仍能登录。 - **Gate「注销后不能聊天」被违反** —— `notifyActiveMatches` 是 **TODO 桩**(只写日志),注销后 `Match` 仍是 `active`、对方不被通知。 - **Gate「关闭未来服务」被违反** —— `cancelPendingActivityRegistrations` 是 **TODO 桩**,活动报名不取消。 - **Gate「不反向暴露个人和咨询正文」被违反** —— `anonymizeServiceHistory`(P21-T05 标的"✅")**实现了却从未被 `closeAccount` 调用**(死代码);且 `sanitizeUserData` 仅脱敏手机号,**身份证号脱敏是 TODO**,注销后身份证明文(密文)永久留存。 - **一处设计矛盾(死代码根因)** —— `closeAccount` 在 `checkCanWithdraw` 返回 `canWithdraw=false` 时**直接抛异常阻断**,而 `checkCanWithdraw` 检查的恰恰就是 `cancelXxx` 那些待取消的状态(预约/咨询/项目/报名/资格)。结论:下方"关闭未来服务"的清理逻辑**永远不可达**,且给用户注销设了"需先手动取消所有业务"的障碍——与 `AccountLifecycleService` 注释「注销必须在线便捷、不设置障碍」直接冲突。 - **测试假绿** —— `AccountLifecycleServiceTest` 由 **7 个 `assertTrue(true)`** 组成,零断言,却让"Phase 21 已完成"看起来成立。 **代码存在 ≠ 功能正确,更 ≠ Gate 通过。** --- ## 二、修复的真实缺陷(高价值项) | 编号 | 严重度 | 缺陷 | 修复 | |------|--------|------|------| | **D-21-A** | 🔴 CRITICAL(Gate) | `AuthService.checkAccountStatus` 不拒绝 `CLOSED` → 注销后仍能登录 | 增加 `CLOSED` 拒绝分支(与 `BANNED`/`SUSPENDED` 同级) | | **D-21-B** | 🟠 HIGH(Gate 暴露个人/咨询正文 + 死代码) | `anonymizeServiceHistory` 已实现但**未接线**;身份证脱敏是 TODO | `closeAccount` 接线 `anonymizeServiceHistory`;`sanitizeUserData` 增加 `user_identities` 身份证/姓名脱敏(列 `NOT NULL`,覆盖为占位符 `***ACCOUNT_CLOSED***`);`anonymizeServiceHistory` 扩展脱敏 `counseling_followups.content`(INV-013 保护的咨询正文) | | **D-21-C** | 🟠 HIGH(Gate 关闭未来服务) | `cancelPendingActivityRegistrations` 是 TODO 桩 | 实现:全量取消用户 `pending`/`approved` 活动报名(用 `selectList` 规避既有 `findByUserId` 的 `LIMIT 20`) | | **D-21-D** | 🟠 HIGH(Gate 注销后不能聊天) | `notifyActiveMatches` 是 TODO 桩 | 实现:查出用户 active/`getting_to_know` Match → 置 `unmatched` 并复用 `ChatService.closeConversationForMatch` 触发 `conversation.closed` 通知对方 | | **D-21-E** | 🟡 MEDIUM(死代码矛盾) | `closeAccount` 在 blockers 处硬抛异常,使 `cancelXxx`/`invalidateLocalEligibility` 永远不可达;且给用户注销设障碍 | 改为"信息性日志 + 继续清理"(尊重「注销不设置障碍」+ 计划 520「关闭未来服务」);`checkCanWithdraw` 仍作为 GET 端点向用户/运营展示待处理业务 | | **D-21-F** | 🟡 MEDIUM(假绿测试) | 7×`assertTrue(true)` 零断言 | 删除,替换为 11 用例真实 DB 集成测试 + 3 用例登录守卫 | ### 设计修正说明(D-21-E) 原 `closeAccount` 在 `checkCanWithdraw` 失败即 `throw`。这导致"关闭未来服务"的清理逻辑**在结构上不可达**(典型的"写了清理代码却永远跑不到"死代码),并且与类注释「注销必须在线便捷、不设置障碍」冲突。本轮改为:先调 `checkCanWithdraw` 仅作**信息性日志**,随后**继续**执行取消/退出/失效/关闭 Match/脱敏。这是一次有意为之的行为修正,理由: 1. AGENTS「注销必须在线便捷、不设置障碍」——不能要求用户先手动取消所有业务才能注销; 2. 计划 520「注销事务关闭未来服务」——清理必须由注销流程自己完成; 3. 让原已写好却不可达的 `cancelXxx` 逻辑真正生效。 > ⚠️ 此修正改变了"注销在有进行中业务时是否阻断"的行为。**原有行为是硬阻断**,现已改为"提示 + 自动清理"。若产品方认为某些业务(如 `in_progress` 咨询)应**先经用户确认**再注销,属于产品决策,需另行评估——本轮按"不设置障碍 + 自动关闭"实现并出报告。 --- ## 三、跨模块验证(既有,确认无误) - **推荐引擎** `RecommendationEngineService:329`:非 `accountStatus = NORMAL` 用户(含 `CLOSED`)被排除 → 注销后不再进入推荐池。✅(既有) - **匹配资格** `MatchEligibilityService:149`:目标用户非 `NORMAL` 则不可匹配 → 注销后不可被匹配。✅(既有) - **Capability** `CapabilityService:46,67`:`NORMAL` 才可展示能力入口。✅(既有) 这三处与 D-21-A/D-21-D 共同构成 Gate「注销后不能登录/推荐/聊天」的完整防线。 --- ## 四、验证证据 | 项 | 结果 | |---|---| | 后端全量回归 | **650 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS**(起点 648,净 +2) | | 新增守卫 | `AccountLifecycleServiceTest`(11 用例:checkCanWithdraw 各拦截项 + closeAccount 全链路 + 关闭 Match)+ `AuthServiceAccountClosedGuardTest`(3 用例:CLOSED 拒登 / NORMAL 不误伤 / BANNED 仍拒) | | 负向验证(NEG-VAL) | 3 处注入 → **3 用例精准变红、零无关失败**:① 移除 `checkAccountStatus` 的 CLOSED 分支 → `closedAccountCannotLogin` 变红(经 `ReflectionTestUtils` 直接调私有方法,**与 Redis 解耦**,避免 CI 有 Redis 时守卫失效);② 移除 Match 置 `unmatched` → `closeAccountClosesActiveMatch` 变红;③ 移除身份证脱敏 → `closeAccountProceedsAndCleansUpActiveServices` 变红。还原后 `md5sum` 逐字节一致、残留 0 | | Git | commit + tag `phase21-recovery-2026-09-18` + bundle(回读通过),工作区干净 | --- ## 五、遗留 / 待确认(未静默扩权) 1. **红娘服务进行中拦截** —— `checkCanWithdraw` 的 `activeMatchmakerServices` 仍硬编码 `0`。根因:当前数据模型**没有 `matchmaker_services` 表**(全仓搜索为空),该拦截项在设计上无法落地。本轮**未**凭空建表(属扩权),标注为遗留 TODO,建议产品确认红娘服务的存储模型后再补。 2. **数据保留"配置 + 人工审核"删除流程** —— 计划 521 要求"数据保留期限未确认的敏感材料采用配置和人工审核,不擅自永久保存或删除"。本轮仅做到"脱敏保留"(PII 已脱敏),**可配置保留期限 + 人工审核删除流程**属产品决策,未实现,建议单列任务。 3. **隐私设置接口** —— `getPrivacySettings`/`updatePrivacySettings` 仍是 TODO 桩(计划未将其列入 Phase 21 强制项),返回假数据/不更新,属遗留。 --- ## 六、改动清单 **核心修复(生产)** - `auth/service/AuthService.java` —— `checkAccountStatus` 增加 `CLOSED` 拒绝。 - `account/service/AccountLifecycleService.java` —— 注入 `MatchMapper`/`ChatService`/`UserIdentityMapper`/`CounselingFollowupMapper`;实现 `notifyActiveMatches`/`cancelPendingActivityRegistrations`;接线并扩展 `anonymizeServiceHistory`;扩展 `sanitizeUserData` 身份证脱敏;`closeAccount` 取消 blockers 硬阻断改为信息性日志+继续清理。 **测试(保真度)** - `account/AccountLifecycleServiceTest.java` —— 由 7×`assertTrue(true)` 重写为 11 用例真实集成测试。 - `auth/AuthServiceAccountClosedGuardTest.java` —— 新增 3 用例(Redis 解耦)。 **文档** - `docs/Phase-21-V11-Recovery-Completion-Report-2026-09-18.md`(本文件) - `docs/STATUS.md` / `docs/JIRA_BACKLOG.md` —— 同步复验结论与真实缺陷。