fix(activity-mutual): close Phase 16 KEEP-VERIFY/EXTEND — 7 defects + 1 hidden compile defect (reopenEpisode) + 1 test-fidelity; unify dual-impl to confirmActivityInterest; INV-006 fix; +7 tests (481 green)
This commit is contained in:
+5
-1
@@ -23,6 +23,10 @@ public class ActivityMutualConsent extends BaseEntity {
|
||||
private String userAStatus;
|
||||
/** B的同意状态:pending / accepted / declined */
|
||||
private String userBStatus;
|
||||
/** 双方都同意时的时间戳 */
|
||||
/** 双方都同意时的时间戳(非 NULL 即代表已双确认,exactly-once 的判定依据) */
|
||||
private Instant bothConfirmedAt;
|
||||
/** 二阶段同意截止时间(V041);NULL 表示不限制。超时后不能再推动成双确认 */
|
||||
private Instant expiresAt;
|
||||
/** 双确认形成的 Match Episode id(V041),仅用于溯源 */
|
||||
private String matchId;
|
||||
}
|
||||
|
||||
+59
-2
@@ -5,6 +5,7 @@ import com.danyang.match.platform.activity_mutual.entity.ActivityMutualConsent;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -12,20 +13,76 @@ import java.util.Optional;
|
||||
@Mapper
|
||||
public interface ActivityMutualConsentMapper extends BaseMapper<ActivityMutualConsent> {
|
||||
|
||||
// Phase 16 修复:以下查询此前全部缺少 deleted_at IS NULL。
|
||||
// V037 已为该表补上 deleted_at,软删后的同意记录仍会被检出,
|
||||
// 导致已作废的互选关系继续参与判断。
|
||||
|
||||
@Select("SELECT * FROM activity_mutual_connection_consents " +
|
||||
"WHERE activity_id = #{activityId} " +
|
||||
"AND ((user_a_id = #{userId} AND user_b_id = #{partnerId}) " +
|
||||
"OR (user_a_id = #{partnerId} AND user_b_id = #{userId})) " +
|
||||
"AND deleted_at IS NULL " +
|
||||
"LIMIT 1")
|
||||
Optional<ActivityMutualConsent> findByActivityPair(@Param("activityId") String activityId,
|
||||
@Param("userId") String userId,
|
||||
@Param("partnerId") String partnerId);
|
||||
|
||||
@Select("SELECT * FROM activity_mutual_connection_consents " +
|
||||
"WHERE activity_id = #{activityId} AND user_a_status = 'accepted' AND user_b_status = 'accepted'")
|
||||
"WHERE activity_id = #{activityId} AND user_a_status = 'accepted' AND user_b_status = 'accepted' " +
|
||||
"AND deleted_at IS NULL")
|
||||
List<ActivityMutualConsent> findBothConfirmedByActivity(String activityId);
|
||||
|
||||
@Select("SELECT * FROM activity_mutual_connection_consents " +
|
||||
"WHERE (user_a_id = #{userId} OR user_b_id = #{userId}) AND user_a_status = 'accepted' AND user_b_status = 'accepted'")
|
||||
"WHERE (user_a_id = #{userId} OR user_b_id = #{userId}) " +
|
||||
"AND user_a_status = 'accepted' AND user_b_status = 'accepted' " +
|
||||
"AND deleted_at IS NULL")
|
||||
List<ActivityMutualConsent> findBothConfirmedByUser(String userId);
|
||||
|
||||
/**
|
||||
* 原子确认本方(Phase 16 核心)。
|
||||
*
|
||||
* <p>单条 SQL 内完成三件事,避免「先读后写」在并发下丢更新:
|
||||
* <ol>
|
||||
* <li>只把「调用方所在侧」置为 accepted(CASE WHEN 定位本侧);</li>
|
||||
* <li>若两侧因此都变成 accepted,则同一语句写入 both_confirmed_at = NOW();</li>
|
||||
* <li>WHERE 同时要求:未软删、尚未双确认、未过期、本侧当前为 pending。</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p><b>exactly-once 依据</b>:把 both_confirmed_at 从 NULL 改成非 NULL 只能有一个事务成功
|
||||
* (其它并发事务的 WHERE both_confirmed_at IS NULL 不再成立),
|
||||
* 因此只有 affectedRows == 1 且回读 both_confirmed_at != null 的那一方负责创建 Match。
|
||||
*
|
||||
* @return 受影响行数:1 = 本次写入生效;0 = 已双确认 / 本方已表态 / 已过期 / 已软删
|
||||
*/
|
||||
@Update("UPDATE activity_mutual_connection_consents SET " +
|
||||
" user_a_status = CASE WHEN user_a_id = #{userId} THEN 'accepted' ELSE user_a_status END, " +
|
||||
" user_b_status = CASE WHEN user_b_id = #{userId} THEN 'accepted' ELSE user_b_status END, " +
|
||||
" both_confirmed_at = CASE WHEN " +
|
||||
" (CASE WHEN user_a_id = #{userId} THEN 'accepted' ELSE user_a_status END) = 'accepted' " +
|
||||
" AND (CASE WHEN user_b_id = #{userId} THEN 'accepted' ELSE user_b_status END) = 'accepted' " +
|
||||
" THEN NOW() ELSE NULL END, " +
|
||||
" updated_at = NOW() " +
|
||||
"WHERE id = #{id} AND deleted_at IS NULL " +
|
||||
" AND both_confirmed_at IS NULL " +
|
||||
" AND (expires_at IS NULL OR expires_at > NOW()) " +
|
||||
" AND CASE WHEN user_a_id = #{userId} THEN user_a_status ELSE user_b_status END = 'pending'")
|
||||
int confirmSideIfPending(@Param("id") String id, @Param("userId") String userId);
|
||||
|
||||
/**
|
||||
* 原子拒绝本方。declined 是终态:本侧一旦 declined 就不再接受后续 confirm
|
||||
* (WHERE 里的 pending 条件保证),如需重新开启必须走运营重置或重新互选。
|
||||
*/
|
||||
@Update("UPDATE activity_mutual_connection_consents SET " +
|
||||
" user_a_status = CASE WHEN user_a_id = #{userId} THEN 'declined' ELSE user_a_status END, " +
|
||||
" user_b_status = CASE WHEN user_b_id = #{userId} THEN 'declined' ELSE user_b_status END, " +
|
||||
" updated_at = NOW() " +
|
||||
"WHERE id = #{id} AND deleted_at IS NULL " +
|
||||
" AND both_confirmed_at IS NULL " +
|
||||
" AND CASE WHEN user_a_id = #{userId} THEN user_a_status ELSE user_b_status END = 'pending'")
|
||||
int declineSideIfPending(@Param("id") String id, @Param("userId") String userId);
|
||||
|
||||
/** 记录双确认后形成的 Match,便于溯源与幂等回查 */
|
||||
@Update("UPDATE activity_mutual_connection_consents SET match_id = #{matchId}, updated_at = NOW() " +
|
||||
"WHERE id = #{id} AND deleted_at IS NULL")
|
||||
int bindMatch(@Param("id") String id, @Param("matchId") String matchId);
|
||||
}
|
||||
|
||||
+47
-18
@@ -3,10 +3,12 @@ package com.danyang.match.platform.activity_mutual.service;
|
||||
import com.danyang.match.platform.activity_mutual.dto.MutualInterestResultDTO;
|
||||
import com.danyang.match.platform.activity_mutual.entity.ActivityMutualConsent;
|
||||
import com.danyang.match.platform.activity_mutual.mapper.ActivityMutualConsentMapper;
|
||||
import com.danyang.match.platform.relationship.service.RelationshipService;
|
||||
import com.danyang.match.platform.shared.constant.Constants;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
@@ -17,7 +19,7 @@ import java.util.*;
|
||||
public class ActivityMutualConsentService {
|
||||
|
||||
private final ActivityMutualConsentMapper consentMapper;
|
||||
private final com.danyang.match.platform.match.mapper.MatchMapper matchMapper;
|
||||
private final RelationshipService relationshipService;
|
||||
|
||||
public List<MutualInterestResultDTO> detectMutualResults(String activityId) {
|
||||
List<ActivityMutualConsent> bothConfirmed = consentMapper.findBothConfirmedByActivity(activityId);
|
||||
@@ -47,32 +49,60 @@ public class ActivityMutualConsentService {
|
||||
return consent;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Map<String, Object> confirmConnection(String userId, String activityId, String partnerId) {
|
||||
ActivityMutualConsent consent = consentMapper.findByActivityPair(activityId, userId, partnerId)
|
||||
.orElseThrow(() -> new RuntimeException("没有找到该活动的互选记录"));
|
||||
if (userId.equals(consent.getUserAId())) {
|
||||
consent.setUserAStatus(Constants.MUTUAL_CONSENT_STATUS_ACCEPTED);
|
||||
} else {
|
||||
consent.setUserBStatus(Constants.MUTUAL_CONSENT_STATUS_ACCEPTED);
|
||||
}
|
||||
consent.setUpdatedAt(Instant.now());
|
||||
consentMapper.updateById(consent);
|
||||
|
||||
// 原子确认本方(Phase 16 核心):仅当本方 pending、未过期、未软删时写入 accepted,
|
||||
// 并借助 both_confirmed_at 从 NULL→非NULL 的原子 UPDATE 实现 exactly-once。
|
||||
// return 1 表示该事务是「把双确认落定」的那一方。
|
||||
int affected = consentMapper.confirmSideIfPending(consent.getId(), userId);
|
||||
|
||||
// 回读权威状态(可能受并发另一方提交影响)
|
||||
consent = consentMapper.findByActivityPair(activityId, userId, partnerId)
|
||||
.orElseThrow(() -> new RuntimeException("互选记录已不存在"));
|
||||
|
||||
boolean aAccepted = Constants.MUTUAL_CONSENT_STATUS_ACCEPTED.equals(consent.getUserAStatus());
|
||||
boolean bAccepted = Constants.MUTUAL_CONSENT_STATUS_ACCEPTED.equals(consent.getUserBStatus());
|
||||
boolean amA = userId.equals(consent.getUserAId());
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("myStatus", userId.equals(consent.getUserAId()) ? (aAccepted ? "accepted" : "pending") : (bAccepted ? "accepted" : "pending"));
|
||||
result.put("partnerStatus", userId.equals(consent.getUserAId()) ? (bAccepted ? "accepted" : "pending") : (aAccepted ? "accepted" : "pending"));
|
||||
result.put("myStatus", amA ? (aAccepted ? "accepted" : "pending") : (bAccepted ? "accepted" : "pending"));
|
||||
result.put("partnerStatus", amA ? (bAccepted ? "accepted" : "pending") : (aAccepted ? "accepted" : "pending"));
|
||||
|
||||
if (aAccepted && bAccepted) {
|
||||
consent.setBothConfirmedAt(Instant.now());
|
||||
consentMapper.updateById(consent);
|
||||
result.put("bothConfirmed", true);
|
||||
log.info("双方确认活动互选: activityId={}, userA={}, userB={}", activityId, consent.getUserAId(), consent.getUserBId());
|
||||
// exactly-once 赢家(本次 confirmSideIfPending 翻转 both_confirmed_at 的一方)负责建 Match + 开启会话。
|
||||
// 另一方即使随后也进入该分支,affected==0,不会重复建 Match(confirmActivityInterest 本身亦幂等复用)。
|
||||
if (affected == 1 && consent.getBothConfirmedAt() != null) {
|
||||
createMatchAndConversation(consent);
|
||||
}
|
||||
result.put("matchCreated", true);
|
||||
} else {
|
||||
result.put("bothConfirmed", false);
|
||||
result.put("matchCreated", false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 双确认后形成 Match 并开启 Conversation(Gate:互选确认后双方可沟通)。
|
||||
* 通过统一的 {@code RelationshipService.confirmActivityInterest} 完成,避免「双实现」:
|
||||
* 该方法负责 active Match 复用 / 历史 Unmatch 开新 Episode / source=activity_mutual /
|
||||
* 显式开启会话,全部集中在一处。
|
||||
*/
|
||||
private void createMatchAndConversation(ActivityMutualConsent consent) {
|
||||
Map<String, Object> matchResult = relationshipService.confirmActivityInterest(
|
||||
consent.getUserAId(), consent.getUserBId());
|
||||
Object matchIdObj = matchResult.get("matchId");
|
||||
if (matchIdObj != null) {
|
||||
consentMapper.bindMatch(consent.getId(), String.valueOf(matchIdObj));
|
||||
}
|
||||
log.info("活动互选双确认后形成Match并开启会话: activityId={}, userA={}, userB={}, matchId={}",
|
||||
consent.getActivityId(), consent.getUserAId(), consent.getUserBId(), matchIdObj);
|
||||
}
|
||||
|
||||
public Map<String, Object> declineConnection(String userId, String activityId, String partnerId) {
|
||||
ActivityMutualConsent consent = consentMapper.findByActivityPair(activityId, userId, partnerId)
|
||||
.orElseThrow(() -> new RuntimeException("没有找到该活动的互选记录"));
|
||||
@@ -89,10 +119,6 @@ public class ActivityMutualConsentService {
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean hasActiveMatch(String userId, String partnerId) {
|
||||
return matchMapper.findActiveMatch(userId, partnerId).isPresent();
|
||||
}
|
||||
|
||||
public List<MutualInterestResultDTO> getMyMutualResults(String userId) {
|
||||
List<ActivityMutualConsent> consents = consentMapper.findBothConfirmedByUser(userId);
|
||||
return consents.stream().map(c -> toResultDTO(c, c.getActivityId())).toList();
|
||||
@@ -108,8 +134,11 @@ public class ActivityMutualConsentService {
|
||||
ActivityMutualConsent consent = consentOpt.get();
|
||||
boolean myAccepted = (userId.equals(consent.getUserAId()) && Constants.MUTUAL_CONSENT_STATUS_ACCEPTED.equals(consent.getUserAStatus()))
|
||||
|| (userId.equals(consent.getUserBId()) && Constants.MUTUAL_CONSENT_STATUS_ACCEPTED.equals(consent.getUserBStatus()));
|
||||
boolean partnerAccepted = (userId.equals(consent.getUserAId()) && Constants.MUTUAL_CONSENT_STATUS_ACCEPTED.equals(consent.getUserBStatus()))
|
||||
boolean partnerSideAccepted = (userId.equals(consent.getUserAId()) && Constants.MUTUAL_CONSENT_STATUS_ACCEPTED.equals(consent.getUserBStatus()))
|
||||
|| (userId.equals(consent.getUserBId()) && Constants.MUTUAL_CONSENT_STATUS_ACCEPTED.equals(consent.getUserAStatus()));
|
||||
// INV-006 单向意向保密:只有本侧也已确认,才允许回显对方是否已确认。
|
||||
// 否则 A 已确认、B 未确认时,B 能通过此接口探知 A 的意向,违反「单向意向不暴露」。
|
||||
boolean partnerAccepted = myAccepted && partnerSideAccepted;
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("exists", true);
|
||||
result.put("myStatus", myAccepted ? "accepted" : "pending");
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.danyang.match.platform.match.entity.Match;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -53,4 +54,19 @@ public interface MatchMapper extends BaseMapper<Match> {
|
||||
/** 活跃 Match 数(带软删过滤) */
|
||||
@Select("SELECT COUNT(*) FROM matches WHERE (user_a_id = #{userId} OR user_b_id = #{userId}) AND status IN ('active', 'getting_to_know') AND deleted_at IS NULL")
|
||||
int countActiveMatches(String userId);
|
||||
|
||||
/**
|
||||
* 历史 Unmatch 复用行开启新 Episode(Phase 16 修复 D4 必崩路径)。
|
||||
*
|
||||
* <p>生产 {@code uk_match_pair UNIQUE(user_a_id, user_b_id)} 决定同一对用户只能存在一行。
|
||||
* 此前该场景走 INSERT → 抛 DuplicateKeyException(500),AGENTS.md #20「历史 Unmatch 则创建新 Episode」
|
||||
* 在生产上根本不可用。改为复用同一行、递增 episode、状态回到 active。
|
||||
*
|
||||
* <p>乐观锁:仅当该行仍为 {@code unmatched} 时才更新;若并发下已被改(如另一方已抢先 reopen),
|
||||
* 命中 0 行 → 调用方抛出重试。
|
||||
*/
|
||||
@Update("UPDATE matches SET status = 'active', source = #{source}, episode = #{episode}, " +
|
||||
"matched_at = NOW(), last_activity_at = NOW(), unmatched_at = NULL, updated_at = NOW() " +
|
||||
"WHERE id = #{id} AND status = 'unmatched' AND deleted_at IS NULL")
|
||||
int reopenEpisode(@Param("id") String id, @Param("episode") int episode, @Param("source") String source);
|
||||
}
|
||||
|
||||
+62
-31
@@ -192,9 +192,10 @@ public class RelationshipService {
|
||||
|
||||
/**
|
||||
* 创建双向匹配(事务)
|
||||
* @param autoCreateConversation 是否自动创建会话(V1.1: 活动互选不自动创建)
|
||||
* @param autoCreateConversation 是否自动创建会话(V1.1: 活动互选不自动创建,由调用方在双确认后显式开启)
|
||||
* @return 新建或复用的 Match(V1.1 Phase 16: 返回以便调用方开启会话/溯源)
|
||||
*/
|
||||
private void createMatch(String userId, String targetUserId, String source, boolean autoCreateConversation) {
|
||||
private Match createMatch(String userId, String targetUserId, String source, boolean autoCreateConversation) {
|
||||
// 标准化user_a/user_b顺序
|
||||
String userA = userId.compareTo(targetUserId) < 0 ? userId : targetUserId;
|
||||
String userB = userId.compareTo(targetUserId) < 0 ? targetUserId : userId;
|
||||
@@ -207,10 +208,7 @@ public class RelationshipService {
|
||||
}
|
||||
|
||||
// Phase 12 复验修复(D5):本方法此前不设置 episode / lastActivityAt,
|
||||
// 而另一条创建路径 MatchService.createOrReuseMatch 会设置 —— 两条路径字段填充不一致:
|
||||
// · episode 为 null → 后续 MatchService.createOrReuseMatch 里
|
||||
// `m.getEpisode() + 1` 会对 null 自动拆箱抛 NPE;
|
||||
// · lastActivityAt 为 null → 沉默期服务建议(RelationshipStageService)失效。
|
||||
// 而另一条创建路径 MatchService.createOrReuseMatch 会设置 —— 两条路径字段填充不一致。
|
||||
// 统一按 AGENTS.md #20 推导 episode:prior Unmatch → prior.episode + 1,否则 1。
|
||||
int episode = 1;
|
||||
Match prior = matchMapper.findLatestMatch(userId, targetUserId).orElse(null);
|
||||
@@ -219,33 +217,55 @@ public class RelationshipService {
|
||||
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处理
|
||||
if (autoCreateConversation) {
|
||||
try {
|
||||
chatService.getOrCreateConversation(match.getId(), userId, targetUserId);
|
||||
log.info("Match后自动创建会话: matchId={}, users={}", match.getId(), userId + "," + targetUserId);
|
||||
} catch (Exception e) {
|
||||
log.warn("创建会话失败,Match已创建但聊天可能不可用: matchId={}, error={}",
|
||||
match.getId(), e.toString(), e);
|
||||
Match match;
|
||||
if (prior != null && Constants.MATCH_STATUS_UNMATCHED.equals(prior.getStatus())) {
|
||||
// Phase 16 修复(D1,必崩):生产 uk_match_pair UNIQUE(user_a_id, user_b_id)
|
||||
// 决定同一对用户只能存在一行。此前这里走 INSERT → prior Unmatch 场景必定
|
||||
// 抛 DuplicateKeyException(500),AGENTS.md #20「历史 Unmatch 则创建新 Episode」
|
||||
// 在生产上根本不可用。测试库此前也缺该 UNIQUE,所以零覆盖。
|
||||
// 正确语义:复用同一行、递增 episode、状态回到 active。
|
||||
int reopened = matchMapper.reopenEpisode(prior.getId(), episode, source);
|
||||
if (reopened == 0) {
|
||||
throw new IllegalStateException("匹配状态已变更,请重试");
|
||||
}
|
||||
prior.setStatus(Constants.MATCH_STATUS_ACTIVE);
|
||||
prior.setSource(source);
|
||||
prior.setEpisode(episode);
|
||||
prior.setMatchedAt(Instant.now());
|
||||
prior.setLastActivityAt(Instant.now());
|
||||
prior.setUnmatchedAt(null);
|
||||
match = prior;
|
||||
log.info("历史 Unmatch 复用行开启新 Episode: matchId={}, episode={}", match.getId(), episode);
|
||||
} else {
|
||||
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处理
|
||||
if (autoCreateConversation) {
|
||||
try {
|
||||
chatService.getOrCreateConversation(match.getId(), userId, targetUserId);
|
||||
log.info("Match后自动创建会话: matchId={}, users={}", match.getId(), userId + "," + targetUserId);
|
||||
} catch (Exception e) {
|
||||
log.warn("创建会话失败,Match已创建但聊天可能不可用: matchId={}, error={}",
|
||||
match.getId(), e.toString(), e);
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重载:默认自动创建Conversation(保持原有行为)
|
||||
*/
|
||||
private void createMatch(String userId, String targetUserId, String source) {
|
||||
createMatch(userId, targetUserId, source, true);
|
||||
private Match createMatch(String userId, String targetUserId, String source) {
|
||||
return createMatch(userId, targetUserId, source, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,6 +283,7 @@ public class RelationshipService {
|
||||
result.put("matchReused", true);
|
||||
result.put("matchId", existingMatch.get().getId());
|
||||
result.put("status", existingMatch.get().getStatus());
|
||||
result.put("conversationAutoCreated", true);
|
||||
log.info("活动互选复用已有Match: userId={}, partnerId={}, matchId={}", userId, partnerId, existingMatch.get().getId());
|
||||
return result;
|
||||
}
|
||||
@@ -273,14 +294,24 @@ public class RelationshipService {
|
||||
&& Constants.MATCH_STATUS_UNMATCHED.equals(priorUnmatch.get().getStatus());
|
||||
|
||||
// 3. 创建新 Match Episode(source=activity_mutual,不自动创建 Conversation)
|
||||
createMatch(userId, partnerId, Constants.MATCH_SOURCE_ACTIVITY_MUTUAL, false);
|
||||
Match match = createMatch(userId, partnerId, Constants.MATCH_SOURCE_ACTIVITY_MUTUAL, false);
|
||||
|
||||
// V1.1 Phase 16 修复(Gate:互选确认后双方可沟通):双确认后必须开启会话。
|
||||
// 此前此处未建会话,仅靠首条消息惰性创建,导致「双方可沟通」的入口不可见。
|
||||
// 现显式开启,保证互选成功后聊天入口立即可见。
|
||||
try {
|
||||
chatService.getOrCreateConversation(match.getId(), match.getUserAId(), match.getUserBId());
|
||||
} catch (Exception e) {
|
||||
log.warn("活动互选开启会话失败(Match 已创建): matchId={}, error={}", match.getId(), e.toString(), e);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("matchCreated", true);
|
||||
result.put("matchId", match.getId());
|
||||
result.put("priorUnmatch", priorUnmatchExists);
|
||||
result.put("conversationAutoCreated", false); // V1.1: 不自动创建Conversation
|
||||
log.info("活动互选创建新Match Episode: userId={}, partnerId={}, priorUnmatch={}",
|
||||
userId, partnerId, priorUnmatchExists);
|
||||
result.put("conversationAutoCreated", true);
|
||||
log.info("活动互选创建新Match Episode: userId={}, partnerId={}, priorUnmatch={}, matchId={}",
|
||||
userId, partnerId, priorUnmatchExists, match.getId());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user