fix(relationship): Phase 12 复验 —— 修复实体↔生产迁移系统性漂移(生产故障族)/ 双路径 episode 与 NPE / 测试库缺表致首页零覆盖 / 计划项 2 未实现
KEEP-VERIFY 复验发现并修复 4 处真实缺陷: - D1(Critical,生产故障·同一族系统性缺陷)实体与 db/migration 系统性漂移:23 表 50 列在生产 迁移中不存在,而测试库 schema-h2.sql 手写补齐 → 全量绿=假绿。其中 11 表属"INSERT 必崩"级 (缺 id/created_at/updated_at,被 AutoFillHandler 强制填充),含 matches.episode、user_preferences 权重列、permissions/roles、consent_records、risk_events 等;真实 PG 上 Match 创建、择偶偏好保存、 权限/角色写入、同意记录、风险事件全部失败。修复:新增 V037(幂等 ADD COLUMN IF NOT EXISTS + 存量 episode 回填)。 - D2(High)RelationshipService.createMatch 不写 episode/lastActivityAt,与 MatchService 双路径 填充不一致;且 episode 为 null 时 m.getEpisode()+1 对 Integer 自动拆箱抛 NPE。修复:统一按 AGENTS.md #20 推导 + null 兜底。 - D3(High)测试库缺 user_identities 等 7 表 → GET /api/v1/home 在测试环境 BadSqlGrammar, 首页接口零回归覆盖。修复:schema-h2.sql 补齐 7 表。 - D4(Medium)Phase 12 计划项 2 实际未实现:getting_to_know 分支只产出 counseling。修复: HomeApplicationService 补 premarital 引导(与 counseling 并列)。 测试保真度: - 新增永久守卫 EntityMigrationParityTest(5 用例):把"实体字段 ⊆ 生产迁移累积列"与"测试库覆盖 关键业务表"固化为自动不变量,区分"必崩级/潜伏级";经两轮负向验证证明会变红(第一轮分级过窄 已修正:episode 由"潜伏级"纠正为"必崩级")。 - 新增 RelationshipProgressPhase12Test(14 用例,含 8 线程并发确认)与 HomePremaritalGuidancePhase12Test(5 用例)。 - 删除 2 个假测试类中的 assertTrue(true) 占位(AGENTS.md 明令禁止),改走真实服务方法验证。 Gate:单方确认不改变最终关系 ✅ / 双确认后才 successful ✅ / 历史 Episode 可追踪 ✅ / 新 Episode 不恢复旧聊天权限(conversations.match_id UNIQUE)✅ / 非成员不可代确认 ✅ 全量回归:371 tests / 0 failures / 0 errors / 3 skipped / BUILD SUCCESS(71 类,5m07s)
This commit is contained in:
+6
@@ -214,6 +214,12 @@ public class HomeApplicationService {
|
||||
list.add(serviceRec("counseling", "刚开始认识,不知道聊什么?",
|
||||
"这里有一些第一次聊天的小建议。",
|
||||
"看看沟通建议", "/pages/counseling-intro/counseling-intro"));
|
||||
// Phase 12(计划项 2):getting_to_know 阶段补充婚前辅导入口引导。
|
||||
// 历史缺陷:该分支此前只给 counseling,婚前辅导仅在 matched_successfully 才出现,
|
||||
// 导致「正在了解」阶段用户完全看不到婚前辅导入口。
|
||||
list.add(serviceRec("premarital", "想更认真地了解彼此?",
|
||||
"善缘丹阳公益婚前辅导帮助你们在决定前更好地了解彼此。",
|
||||
"了解婚前辅导", "/pages/premarital-intro/premarital-intro"));
|
||||
} else if (Constants.RELATIONSHIP_STATUS_MATCHED_SUCCESSFULLY.equals(status)) {
|
||||
list.add(serviceRec("premarital", "正在认真考虑未来?",
|
||||
"可以了解善缘丹阳公益婚前辅导。",
|
||||
|
||||
@@ -55,10 +55,16 @@ public final class MatchService {
|
||||
}
|
||||
|
||||
// 规则 2 & 3:新建
|
||||
// Phase 12 复验修复:prior.episode 可能为 null(历史数据 / 早期由 RelationshipService
|
||||
// 创建的 Match 未写该列),直接 `getEpisode() + 1` 会对 null 自动拆箱抛 NPE。
|
||||
Optional<Match> latest = matchMapper.findLatestBetween(userA, userB);
|
||||
int episode = latest.map(m ->
|
||||
Constants.MATCH_STATUS_UNMATCHED.equals(m.getStatus()) ? m.getEpisode() + 1 : 1
|
||||
).orElse(1);
|
||||
int episode = latest.map(m -> {
|
||||
if (!Constants.MATCH_STATUS_UNMATCHED.equals(m.getStatus())) {
|
||||
return 1;
|
||||
}
|
||||
Integer priorEpisode = m.getEpisode();
|
||||
return (priorEpisode == null ? 1 : priorEpisode) + 1;
|
||||
}).orElse(1);
|
||||
|
||||
Match m = new Match();
|
||||
m.setId(UUID.randomUUID().toString());
|
||||
|
||||
+15
@@ -206,12 +206,27 @@ public class RelationshipService {
|
||||
throw new RuntimeException("匹配已存在");
|
||||
}
|
||||
|
||||
// Phase 12 复验修复(D5):本方法此前不设置 episode / lastActivityAt,
|
||||
// 而另一条创建路径 MatchService.createOrReuseMatch 会设置 —— 两条路径字段填充不一致:
|
||||
// · episode 为 null → 后续 MatchService.createOrReuseMatch 里
|
||||
// `m.getEpisode() + 1` 会对 null 自动拆箱抛 NPE;
|
||||
// · lastActivityAt 为 null → 沉默期服务建议(RelationshipStageService)失效。
|
||||
// 统一按 AGENTS.md #20 推导 episode:prior Unmatch → prior.episode + 1,否则 1。
|
||||
int episode = 1;
|
||||
Match prior = matchMapper.findLatestMatch(userId, targetUserId).orElse(null);
|
||||
if (prior != null && Constants.MATCH_STATUS_UNMATCHED.equals(prior.getStatus())) {
|
||||
Integer priorEpisode = prior.getEpisode();
|
||||
episode = (priorEpisode == null ? 1 : priorEpisode) + 1;
|
||||
}
|
||||
|
||||
Match match = new Match();
|
||||
match.setUserAId(userA);
|
||||
match.setUserBId(userB);
|
||||
match.setStatus(Constants.MATCH_STATUS_ACTIVE);
|
||||
match.setSource(source);
|
||||
match.setMatchedAt(Instant.now());
|
||||
match.setLastActivityAt(Instant.now());
|
||||
match.setEpisode(episode);
|
||||
matchMapper.insert(match);
|
||||
|
||||
// V1.1: 活动互选不自动创建Conversation,需双方确认后由confirmActivityInterest处理
|
||||
|
||||
Reference in New Issue
Block a user