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

173 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 常量:
```java
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 方法
```java
@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 方法
```java
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
- [x] createMatch 重载方法签名正确,默认行为不变
- [x] confirmActivityInterest 正确调用 createMatch(..., false)
- [x] ActivityInterestController 新增接口语法正确
- [x] hasMutualInterest 方法逻辑正确(双方均为 MUTUAL 状态)
- [x] INV-002(未Mutual不可聊天)不受影响(chat 仍检查 active Match)
- [x] INV-003(Block优先级最高)不受影响
- [x] 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,等待下一步指令。