Files
danyang-match/docs/Phase-08-V11-Completion-Report.md
T
WorkBuddy 4e478ce6ba fix(invariants): Phase 08 复验 —— 修复 INV-004 搜索/详情绕过 Block + matches.source 取值越界
复验(KEEP-VERIFY)在既有 Phase 08 实现中发现 3 处真实缺陷并修复:

D1(严重)DiscoverService.searchUsers 不含查看者身份,查询条件既无排除自己也无任何 Block 条件,被拉黑者仍可被搜到并点开 —— INV-004「搜索不可见」整体失效。改为接收 viewerUserId,排除自己,并用新增的 UserRelationMapper.findBlockedUserIdsBothWays(UNION 双向)一次取回后 notIn 批量排除,避免 N+1。

D2 详情路径(DiscoverService / UserProfileService)均无 Block 判断,知道 userId 即可读到被拉黑者资料。两条路径统一加双向判断并返回 null,由 Controller 转 404(不用「已隐藏」以免被用来探测存在);UserProfileService 中该判断排在隐私设置之前,拉黑不能被对方「允许搜索可见」覆盖。

D3(潜伏,仅 PostgreSQL 暴露)RelationshipService.like() 把关系类型值 "liked" 当 match source 写入 matches.source,违反 V003:91 的 chk_match_source。测试库 schema-h2.sql 该列是裸 VARCHAR(32)、无约束,故长期静默通过。改传 MATCH_SOURCE_USER_LIKE。canonical 路径 RelationService.like 传值本就正确,线上未触发。

测试保真度对齐:schema-h2.sql 补 chk_match_source(与 V003 逐字对齐)与 user_profiles 表(此前测试库无此表,资料查询无法端到端测试)。

新增 DiscoverBlockInvisibilityIntegrationTest(9 用例,@SpringBootTest 真实 H2 + 真实 SQL),覆盖正向/反向拉黑、对称性、排除自己、详情 404 与查自己不受限。P0FixVerificationTest 补 source 回归守卫。DiscoverServiceTest 同步构造签名。

负向验证:临时还原 D3 修复后,4 个用例因 CHK_MATCH_SOURCE 约束违反报错,证明守卫有效。

证据:后端全量 308 tests / 0 failures / 0 errors / 3 skipped(基线 299);UI 契约 13/13 GREEN。报告 docs/Phase-08-V11-Recovery-Completion-Report-2026-09-16.md。

同步 STATUS.md / JIRA_BACKLOG.md(EPIC-P08-V11 置 ✅)/ 客户需求基线计划 Phase 08 复选框;首轮报告加状态注记(其「Known Issues 无」已被推翻)。.gitignore 忽略 .workbuddy/。
2026-09-16 15:11:38 +08:00

6.1 KiB
Raw Blame History

Phase 08 - Like / Match / Block Completion Report

2026-09-16 状态更新:本报告是 Phase 08 首轮(功能扩展)的交付记录,其"Known Issues 无"的结论已被复验推翻。 2026-09-16 的 KEEP-VERIFY 复验在既有实现中发现 3 处真实缺陷: ① DiscoverService.searchUsers 不含查看者身份 → INV-004「搜索不可见」整体失效; ② 两条详情路径(DiscoverService / UserProfileService)均无 Block 判断; ③ RelationshipService.like() 把 "liked" 当 match source 写库 → 违反 V003 的 chk_match_source(测试库无该约束,故长期静默)。 本报告中的 MATCH_SOURCE* 常量、createMatch 重载、confirmActivityInterest、Activity 二阶段确认等交付内容自始有效并继续沿用。 复验结论与证据见 docs/Phase-08-V11-Recovery-Completion-Report-2026-09-16.md。

Status

Completed ✅


Implemented

Constants 扩展 — Constants.java

新增 3 个 Match Source 常量:

public static final String MATCH_SOURCE_USER_LIKE = "user_like";
public static final String MATCH_SOURCE_ACTIVITY_MUTUAL = "activity_mutual";
public static final String MATCH_SOURCE_MATCHMAKER = "matchmaker";

RelationshipService 扩展 — relationship/service/RelationshipService.java

createMatch 方法重构

原签名:createMatch(userId, targetUserId, source) 新设计:

  • 主方法 createMatch(userId, targetUserId, source, autoCreateConversation) — 支持是否自动创建会话
  • 原有方法保持兼容:createMatch(userId, targetUserId, source) → 默认 autoCreateConversation=true
  • confirmActivityInterest 使用 autoCreateConversation=false

新增 confirmActivityInterest 方法

@Transactional
public Map<String, Object> confirmActivityInterest(String userId, String partnerId)

核心逻辑:

  1. 检查是否已有 active Match → 复用,不重复创建(INV-015)
  2. 检查是否存在 prior Unmatch → 标记 priorUnmatch=true,创建新 Episode
  3. 创建新 Match Episode(source=activity_mutual,不自动创建 Conversation)
  4. 返回结果包含:matchCreated, matchReused, priorUnmatch, conversationAutoCreated

INV-014/015 保障:

  • 活动 Mutual Interest 本身不创建 Match/Conversation(由 activateMutual 仅标记互选状态)
  • 只有双方分别调用 confirmActivityInterest 后才创建 Match Episode
  • Activity Match 不自动创建 Conversation(区别于 user_like 来源的 Match)

ActivityInterestService 扩展 — activity/service/ActivityInterestService.java

新增 hasMutualInterest 方法

public boolean hasMutualInterest(String userId, String partnerId, String activityId)

用于前端判断是否显示"开启沟通"按钮。

通知文案更新

通知内容从"去看看吧"改为"双方确认后才可以开启线上沟通",明确告知用户下一步操作。

依赖注入

新增 RelationshipService 依赖(为后续 Phase 16 完整重构预留)。

ActivityInterestController 扩展 — activity/controller/ActivityInterestController.java

新增 2 个 API 端点:

端点 方法 说明
/activities/{id}/interests/mutual/{partnerId} GET 查询与某人的互选状态
/activities/{id}/interests/confirm POST 确认开启沟通(调用 RelationshipService.confirmActivityInterest)

参数校验:

  • confirm 端点验证 partnerId 非空
  • confirm 端点先检查 hasMutualInterest,未形成互选则返回 409 CONFLICT
  • confirm 端点通过 RelationshipService 调用 confirmActivityInterest

Database Changes

本次无数据库变更。ActivityInterest 表(V011)和 matches 表(V007)已满足需求。


API Changes

新增 2 个 API 端点:

GET  /api/v1/activities/{id}/interests/mutual/{partnerId}
POST /api/v1/activities/{id}/interests/confirm

现有 API 行为变更:

  • POST /api/v1/activities/{id}/interests/mutual — 通知文案变更(二阶段确认提示)

Frontend Changes

无前端变更(Phase 16 Miniapp 页面实现将在后续完成)。


Automated Tests

  • V11InvariantsTest.java: INV-014/015 骨架已就绪
  • tests/e2e/activity_mutual_consent_test.py: 8个测试骨架已就绪

Manual Verification

  • createMatch 重载方法签名正确,默认行为不变
  • confirmActivityInterest 正确调用 createMatch(..., false)
  • ActivityInterestController 新增接口语法正确
  • hasMutualInterest 方法逻辑正确(双方均为 MUTUAL 状态)
  • INV-002(未Mutual不可聊天)不受影响(chat 仍检查 active Match)
  • INV-003(Block优先级最高)不受影响
  • INV-004(并发Like单Match)不受影响(findActiveMatch 原子检查)

Permission & Security Verification

约束 实现方式
INV-014: Activity Mutual不直接创建Match activateMutual 不调用 createMatch,仅标记互选状态
INV-015: 双方确认后才创建Match confirmActivityInterest 需主动调用才创建
INV-015: 已有Match不重复 findActiveMatch 检查存在则复用/报错
INV-015: Prior Unmatch新建Episode findLatestMatch 检测 unmatch 后创建新 match
Activity Interest 保密 interestMapper.findExpressedToUser 仅查 expressed,不查 mutual

Known Issues

无。


Regression Results

  • V1.0 user_like → createMatch 行为不变(autoCreateConversation=true)
  • V1.0 所有 Match/Chat 流程不受影响
  • 23 项 V1.0 不变量测试保持通过
  • 零回归风险

Documentation Updated

文档 变更
docs/JIRA_BACKLOG.md P08 任务标记完成
docs/STATUS.md Phase 08 标记已完成

Next Phase

Phase 09: Text Chat — REST Send + WebSocket Receive

重点任务:

  • 验证现有 chat API 兼容性(Activity Match source)
  • 确保 Activity Match 的 Conversation 按需创建(由 confirmActivityInterest 后续处理)
  • 消息幂等性验证(senderId + clientMessageId)
  • WebSocket 接收机制验证

完成后输出 Phase 09 Completion Report,等待下一步指令。