diff --git a/backend/src/main/java/com/danyang/match/platform/chat/config/ChatDisabledFilter.java b/backend/src/main/java/com/danyang/match/platform/chat/config/ChatDisabledFilter.java new file mode 100644 index 0000000..61fe83c --- /dev/null +++ b/backend/src/main/java/com/danyang/match/platform/chat/config/ChatDisabledFilter.java @@ -0,0 +1,44 @@ +package com.danyang.match.platform.chat.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * 产品当前不提供用户之间的即时聊天。 + * + *

在 HTTP 和 WebSocket 握手进入 Controller/Handler 前统一拒绝, + * 防止只隐藏小程序入口却留下可调用的通信通道。 + */ +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class ChatDisabledFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + String path = request.getRequestURI(); + String context = request.getContextPath(); + if (context != null && !context.isEmpty() && path.startsWith(context)) { + path = path.substring(context.length()); + } + if (path.equals("/conversations") || path.startsWith("/conversations/") + || path.equals("/ws/chat") || path.startsWith("/ws/chat/")) { + response.setStatus(HttpServletResponse.SC_GONE); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write( + "{\"success\":false,\"code\":\"CHAT_DISABLED\"," + + "\"message\":\"平台未提供用户之间的即时聊天功能\",\"data\":null}"); + return; + } + filterChain.doFilter(request, response); + } +} diff --git a/backend/src/main/java/com/danyang/match/platform/match/controller/LikeController.java b/backend/src/main/java/com/danyang/match/platform/match/controller/LikeController.java index 75189ed..8e02969 100644 --- a/backend/src/main/java/com/danyang/match/platform/match/controller/LikeController.java +++ b/backend/src/main/java/com/danyang/match/platform/match/controller/LikeController.java @@ -1,7 +1,5 @@ package com.danyang.match.platform.match.controller; -import com.danyang.match.platform.chat.entity.Conversation; -import com.danyang.match.platform.chat.service.ChatService; import com.danyang.match.platform.match.service.RelationService; import com.danyang.match.platform.match.service.RelationService.LikeResult; import com.danyang.match.platform.shared.constant.Constants; @@ -11,7 +9,6 @@ import com.danyang.match.platform.shared.idempotency.Idempotent; import com.danyang.match.platform.shared.security.principal.UserPrincipal; import lombok.Data; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -22,18 +19,16 @@ import org.springframework.web.bind.annotation.*; * DELETE /api/v1/likes/{targetUserId} — A 取消 Like B * * 核心规则:INV-005 未 Mutual 不可聊天 - * 响应契约对齐 miniapp/types/index.d.ts 的 LikeResult:{ relationStatus, mutualMatch, matchedConversationId } + * 响应返回关系状态和 Match 结果,不创建用户会话。 * * 说明:原 /relations/{targetUserId}/like(2026-09-10 版)已被本控制器取代,调用方统一走 /likes。 */ -@Slf4j @RestController @RequestMapping("/likes") @RequiredArgsConstructor public class LikeController { private final RelationService relationService; - private final ChatService chatService; @Idempotent @PostMapping @@ -44,7 +39,7 @@ public class LikeController { return ApiResponse.error(ApiResponseCode.BAD_REQUEST, "targetUserId不能为空"); } LikeResult result = relationService.like(principal.id(), req.targetUserId); - return ApiResponse.ok(LikeResponse.from(result, ensureConversation(result, principal.id(), req.targetUserId))); + return ApiResponse.ok(LikeResponse.from(result)); } /** @@ -62,20 +57,8 @@ public class LikeController { /** * Mutual 达成时确保会话存在,并返回会话 id —— 前端据此直接跳转聊天页。 * - * - getOrCreateConversation 幂等,不会重复建会话 - * - 建会话失败不应让 Like 整体失败(Like 已落库),故降级为 null,前端仅提示不跳转 */ - private String ensureConversation(LikeResult result, String userId, String targetUserId) { - if (!result.mutual || result.match == null) return null; - try { - Conversation conv = chatService.getOrCreateConversation(result.match.getId(), userId, targetUserId); - return conv.getId(); - } catch (RuntimeException e) { - log.warn("Mutual 达成但会话创建失败,已降级:user={}, target={}, matchId={}, reason={}", - userId, targetUserId, result.match.getId(), e.getMessage()); - return null; - } - } + // 当前产品不提供用户间会话;Match 成功后仅展示结果和公益服务入口。 // ==================== DTO ==================== @@ -89,16 +72,14 @@ public class LikeController { public static class LikeResponse { private String relationStatus; private boolean mutualMatch; - private String matchedConversationId; private String matchId; - public static LikeResponse from(LikeResult r, String matchedConversationId) { + public static LikeResponse from(LikeResult r) { LikeResponse resp = new LikeResponse(); resp.relationStatus = r.mutual ? Constants.RELATION_STATUS_MUTUAL_MATCH : Constants.RELATION_STATUS_LIKED; resp.mutualMatch = r.mutual; - resp.matchedConversationId = matchedConversationId; resp.matchId = r.match != null ? r.match.getId() : null; return resp; } diff --git a/backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipService.java b/backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipService.java index 0d7ba5d..b2a8f13 100644 --- a/backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipService.java +++ b/backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipService.java @@ -251,16 +251,7 @@ public class RelationshipService { 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 仅用于结果判定和公益服务引导。 return match; } @@ -286,7 +277,6 @@ 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; } @@ -299,20 +289,10 @@ public class RelationshipService { // 3. 创建新 Match Episode(source=activity_mutual,不自动创建 Conversation) 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 result = new HashMap<>(); result.put("matchCreated", true); result.put("matchId", match.getId()); result.put("priorUnmatch", priorUnmatchExists); - result.put("conversationAutoCreated", true); log.info("活动互选创建新Match Episode: userId={}, partnerId={}, priorUnmatch={}, matchId={}", userId, partnerId, priorUnmatchExists, match.getId()); return result; diff --git a/backend/src/test/java/com/danyang/match/platform/activity_mutual/ActivityMutualConsentServiceTest.java b/backend/src/test/java/com/danyang/match/platform/activity_mutual/ActivityMutualConsentServiceTest.java index 7c657cd..ffb0eef 100644 --- a/backend/src/test/java/com/danyang/match/platform/activity_mutual/ActivityMutualConsentServiceTest.java +++ b/backend/src/test/java/com/danyang/match/platform/activity_mutual/ActivityMutualConsentServiceTest.java @@ -73,7 +73,7 @@ class ActivityMutualConsentServiceTest { @Test @DisplayName("Gate: 双确认后形成 Match 并开启 Conversation(此前只打日志,Gate 失败)") - void doubleConfirmCreatesMatchAndConversation() { + void doubleConfirmCreatesMatchWithoutConversation() { // 第一阶段:建立互选同意记录(二阶段同意) consentService.createMutualConsent(activityId, userA, userB); @@ -95,8 +95,8 @@ class ActivityMutualConsentServiceTest { assertEquals(Constants.MATCH_SOURCE_ACTIVITY_MUTUAL, match.getSource()); // 会话必须已开启(Gate:互选确认后双方可沟通) - assertTrue(conversationMapper.findByMatchId(match.getId()).isPresent(), - "双确认后必须已开启 Conversation"); + assertTrue(conversationMapper.findByMatchId(match.getId()).isEmpty(), + "双确认后不得创建用户聊天 Conversation"); // 溯源字段:consent 记录应绑定 match_id ActivityMutualConsent consent = consentMapper.findByActivityPair(activityId, userA, userB).orElseThrow(); diff --git a/backend/src/test/java/com/danyang/match/platform/chat/ChatPhase09IntegrationTest.java b/backend/src/test/java/com/danyang/match/platform/chat/ChatPhase09IntegrationTest.java index 8528bc0..cb5ebff 100644 --- a/backend/src/test/java/com/danyang/match/platform/chat/ChatPhase09IntegrationTest.java +++ b/backend/src/test/java/com/danyang/match/platform/chat/ChatPhase09IntegrationTest.java @@ -71,6 +71,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @SpringBootTest @ActiveProfiles("test") @AutoConfigureMockMvc +@Disabled("用户端即时聊天已按产品方案下线") class ChatPhase09IntegrationTest { @Autowired private MockMvc mockMvc; diff --git a/backend/src/test/java/com/danyang/match/platform/invariants/P0FixVerificationTest.java b/backend/src/test/java/com/danyang/match/platform/invariants/P0FixVerificationTest.java index 8061fcd..50ef95a 100644 --- a/backend/src/test/java/com/danyang/match/platform/invariants/P0FixVerificationTest.java +++ b/backend/src/test/java/com/danyang/match/platform/invariants/P0FixVerificationTest.java @@ -94,6 +94,7 @@ class P0FixVerificationTest { // ==================== P0-1: Block不变量验证 ==================== @Test + @Disabled("用户端即时聊天已按产品方案下线") @DisplayName("INV-003: Block后A不可向B发送消息") void testBlockPreventsChat() { relationshipService.like(userA, userB); @@ -213,7 +214,7 @@ class P0FixVerificationTest { var convs = conversationMapper.selectList( new LambdaQueryWrapper() .eq(com.danyang.match.platform.chat.entity.Conversation::getMatchId, match.getId())); - assertFalse(convs.isEmpty(), "Match后应自动创建Conversation"); + assertTrue(convs.isEmpty(), "Match 后不得自动创建 Conversation"); } // ==================== P1-1: Required全字段过滤验证 ==================== diff --git a/backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerActionGuardTest.java b/backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerActionGuardTest.java index de5b354..09b5402 100644 --- a/backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerActionGuardTest.java +++ b/backend/src/test/java/com/danyang/match/platform/matchmaker/MatchmakerActionGuardTest.java @@ -258,12 +258,12 @@ class MatchmakerActionGuardTest { // ==================== MM-ACTION-002:会话/关系开启无 operator 触达面 ==================== @Test - @DisplayName("MM-ACTION-002(负向): operator token 打会话开启端点 → 403(match/conversation 仅用户路径)") + @DisplayName("MM-ACTION-002(负向): operator token 打已下线会话端点 → 410") void operatorCannotOpenConversationFromMatch() throws Exception { - // 认证层即拒绝(hasRole("USER")),无需真实 match 存在 + // 用户间会话已全局下线,先于角色鉴权返回 CHAT_DISABLED。 mockMvc.perform(post("/conversations/from-match/{matchId}", "no-such-match") .header("Authorization", "Bearer " + operatorToken)) - .andExpect(status().isForbidden()); + .andExpect(status().isGone()); } // ==================== MM-ACTION-003:must-confirm 语义锁定 ==================== diff --git a/backend/src/test/java/com/danyang/match/platform/relationship/RelationshipProgressPhase12Test.java b/backend/src/test/java/com/danyang/match/platform/relationship/RelationshipProgressPhase12Test.java index 24e4c35..19b51ae 100644 --- a/backend/src/test/java/com/danyang/match/platform/relationship/RelationshipProgressPhase12Test.java +++ b/backend/src/test/java/com/danyang/match/platform/relationship/RelationshipProgressPhase12Test.java @@ -237,7 +237,7 @@ class RelationshipProgressPhase12Test { // 旧 Match 有会话 Long oldConvCount = conversationMapper.selectCount( new LambdaQueryWrapper().eq(Conversation::getMatchId, first.getId())); - assertEquals(1L, oldConvCount, "首个 Match 应有 1 个会话"); + assertEquals(0L, oldConvCount, "首个 Match 不应有用户聊天会话"); // Unmatch Match m1 = matchMapper.findById(first.getId()).orElseThrow(); @@ -255,10 +255,10 @@ class RelationshipProgressPhase12Test { new LambdaQueryWrapper().eq(Conversation::getMatchId, second.getId())), "新 Match 不应自动继承旧会话(旧聊天权限不恢复)"); - // 且旧会话仍挂在旧 Match 上,不会被新 Match 复用 - assertEquals(1L, conversationMapper.selectCount( + // 历史数据为空:本产品不再创建用户聊天会话 + assertEquals(0L, conversationMapper.selectCount( new LambdaQueryWrapper().eq(Conversation::getMatchId, first.getId())), - "旧会话应仍属于旧 Match"); + "旧 Match 也不应存在用户聊天会话"); } // ==================== 拒绝 / 恢复 / 退出 ==================== diff --git a/docs/NO-USER-IM-SCOPE-2026-10-15.md b/docs/NO-USER-IM-SCOPE-2026-10-15.md new file mode 100644 index 0000000..5c54e4d --- /dev/null +++ b/docs/NO-USER-IM-SCOPE-2026-10-15.md @@ -0,0 +1,20 @@ +# 用户端无即时通讯边界(2026-10-15) + +## 已确认范围 + +- 小程序全流程不展示微信群二维码,也不展示或交换手机号、微信号、QQ 等个人联系方式。 +- 小程序不提供用户之间的私信、聊天、会话列表或 WebSocket 双向通讯。 +- 双向意向匹配只产生平台系统通知,并引导用户查看公益活动、公益服务站和公益红娘服务。 +- 活动签到二维码属于活动现场核验,不属于群二维码,继续保留。 + +## 实施边界 + +- Like 和活动互选不再创建新 Conversation,不返回 `matchedConversationId`。 +- 后端 `/conversations`、`/conversations/**`、`/ws/chat` 返回 `410 CHAT_DISABLED`;历史聊天数据保留,不再新增或向用户端开放。 +- Nginx `/ws/` 直接返回 `410 CHAT_DISABLED`,避免网关继续代理聊天连接。 +- 小程序不注册消息 Tab、消息页和聊天页;历史源码暂保留用于数据迁移和回归兼容,但不在运行路由中暴露。 + +## 验收 + +- `tests/miniapp/no_user_im_contract_test.py`:通过。 +- 仍需在后端容器完成完整 `mvn test -B`,并验证运行中的 REST 聊天接口返回 410。 diff --git a/docs/STATUS.md b/docs/STATUS.md index 301888d..2d12ae4 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -899,3 +899,8 @@ docker run --rm --dns 223.5.5.5 --dns 114.114.114.114 ` - 后端镜像已按当前源码重建成功;健康检查通过,未认证通知接口返回 401。 - 活栈 E2E 最新结果:55 passed;剩余 8 errors 为测试容器缺少 Docker CLI,另有活动互选测试依赖容器内 `docker exec`,不能计为完整 E2E 通过。 - 证据:`.mimosa/core-e2e-postgres-test.log`。 + +## 2026-10-15 无用户 IM 改造 +- 已按确认方案关闭小程序用户私信/聊天入口,不展示微信群二维码。 +- 匹配成功保留平台系统通知,并引导公益活动、服务站、红娘服务。 +- 后端与 Nginx 聊天入口返回 CHAT_DISABLED/410;完整 Maven 回归与运行态验收待本轮完成。 diff --git a/infrastructure/nginx-production.conf b/infrastructure/nginx-production.conf index dc48f32..6e59896 100644 --- a/infrastructure/nginx-production.conf +++ b/infrastructure/nginx-production.conf @@ -147,16 +147,10 @@ http { proxy_read_timeout 60s; } - # WebSocket proxy + # 用户间即时聊天已下线,避免网关继续暴露 WebSocket 入口。 location /ws/ { - limit_req zone=ws burst=20 nodelay; - proxy_pass http://backend; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_read_timeout 3600s; + default_type application/json; + return 410 '{"success":false,"code":"CHAT_DISABLED","message":"平台未提供用户之间的即时聊天功能","data":null}'; } # Auth endpoints (strict rate limiting) diff --git a/infrastructure/nginx.conf b/infrastructure/nginx.conf index a9997fb..547141e 100644 --- a/infrastructure/nginx.conf +++ b/infrastructure/nginx.conf @@ -68,17 +68,10 @@ http { add_header Pragma "no-cache"; } - # WebSocket proxy + # 用户间即时聊天已下线,避免网关继续暴露 WebSocket 入口。 location /ws/ { - proxy_pass $backend_upstream; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_read_timeout 3600s; + default_type application/json; + return 410 '{"success":false,"code":"CHAT_DISABLED","message":"平台未提供用户之间的即时聊天功能","data":null}'; } # Auth endpoints - stricter rate limit diff --git a/miniapp/app.json b/miniapp/app.json index 257bd57..77f2cff 100644 --- a/miniapp/app.json +++ b/miniapp/app.json @@ -5,7 +5,6 @@ "pages": [ "pages/index/index", "pages/discover/discover", - "pages/messages/messages", "pages/activity/activity", "pages/mine/mine", "pages/login/login", @@ -23,7 +22,6 @@ "pages/review-rejected/review-rejected", "pages/profile-detail/profile-detail", "pages/fate-center/fate-center", - "pages/chat/chat", "pages/activity-detail/activity-detail", "pages/activity-register/activity-register", "pages/my-activities/my-activities", @@ -97,12 +95,6 @@ "iconPath": "assets/tabbar/discover.png", "selectedIconPath": "assets/tabbar/discover-active.png" }, - { - "pagePath": "pages/messages/messages", - "text": "消息", - "iconPath": "assets/tabbar/message.png", - "selectedIconPath": "assets/tabbar/message-active.png" - }, { "pagePath": "pages/activity/activity", "text": "活动", diff --git a/miniapp/constants/statusEnums.js b/miniapp/constants/statusEnums.js index 3ffada8..cadc95c 100644 --- a/miniapp/constants/statusEnums.js +++ b/miniapp/constants/statusEnums.js @@ -173,7 +173,7 @@ exports.ERROR_USER_MESSAGE = { [exports.BUSINESS_ERROR_CODE.PROFILE_NOT_APPROVED]: '当前资料尚未通过审核', [exports.BUSINESS_ERROR_CODE.ACCOUNT_LIMITED]: '账号已被限制,详情请查看账号状态', [exports.BUSINESS_ERROR_CODE.TARGET_UNAVAILABLE]: '该用户当前不可查看或联系', - [exports.BUSINESS_ERROR_CODE.MATCH_REQUIRED]: '双方互相喜欢后才能开始聊天', + [exports.BUSINESS_ERROR_CODE.MATCH_REQUIRED]: '当前平台未提供用户间即时聊天功能', [exports.BUSINESS_ERROR_CODE.ACTIVITY_FULL]: '本次活动报名人数已满', [exports.BUSINESS_ERROR_CODE.ACTIVITY_CLOSED]: '本次活动报名已结束', [exports.BUSINESS_ERROR_CODE.ALREADY_APPLIED]: '你已经提交过申请', diff --git a/miniapp/pages.json b/miniapp/pages.json index 0702f1c..998c630 100644 --- a/miniapp/pages.json +++ b/miniapp/pages.json @@ -2,7 +2,6 @@ "pages": [ "pages/index/index", "pages/discover/discover", - "pages/messages/messages", "pages/activity/activity", "pages/mine/mine", "pages/login/login", @@ -20,7 +19,6 @@ "pages/review-rejected/review-rejected", "pages/profile-detail/profile-detail", "pages/fate-center/fate-center", - "pages/chat/chat", "pages/activity-detail/activity-detail", "pages/activity-register/activity-register", "pages/my-activities/my-activities", @@ -68,12 +66,6 @@ "iconPath": "assets/tabbar/discover.png", "selectedIconPath": "assets/tabbar/discover-active.png" }, - { - "pagePath": "pages/messages/messages", - "text": "消息", - "iconPath": "assets/tabbar/message.png", - "selectedIconPath": "assets/tabbar/message-active.png" - }, { "pagePath": "pages/activity/activity", "text": "活动", @@ -101,4 +93,4 @@ } }, "sitemapLocation": "sitemap.json" -} \ No newline at end of file +} diff --git a/miniapp/pages/activity-matches/activity-matches.js b/miniapp/pages/activity-matches/activity-matches.js index 99b6764..1bcc573 100644 --- a/miniapp/pages/activity-matches/activity-matches.js +++ b/miniapp/pages/activity-matches/activity-matches.js @@ -50,9 +50,4 @@ Page({ }); }, - startChat(userId) { - wx.navigateTo({ - url: `/pages/chat/chat?userId=${userId}&activityId=${this.data.activityId}` - }); - } }); diff --git a/miniapp/pages/activity-matches/activity-matches.wxml b/miniapp/pages/activity-matches/activity-matches.wxml index 1d98917..7b41379 100644 --- a/miniapp/pages/activity-matches/activity-matches.wxml +++ b/miniapp/pages/activity-matches/activity-matches.wxml @@ -24,7 +24,7 @@ {{item.age}}岁 · {{item.locationName || ''}} - + 可查看公益服务 diff --git a/miniapp/pages/activity-mutual-result/activity-mutual-result.ts b/miniapp/pages/activity-mutual-result/activity-mutual-result.ts index 6f87b72..5f4ef7e 100644 --- a/miniapp/pages/activity-mutual-result/activity-mutual-result.ts +++ b/miniapp/pages/activity-mutual-result/activity-mutual-result.ts @@ -18,8 +18,4 @@ Page({ } catch (e) { console.error(e); this.setData({ loading: false }); } }, - openChat(e: any) { - const partnerId = e.currentTarget.dataset.partnerid; - wx.navigateTo({ url: `/pages/chat/chat?partnerId=${partnerId}` }); - } }); diff --git a/miniapp/pages/activity-mutual-result/activity-mutual-result.wxml b/miniapp/pages/activity-mutual-result/activity-mutual-result.wxml index 6dbc4a5..e886af2 100644 --- a/miniapp/pages/activity-mutual-result/activity-mutual-result.wxml +++ b/miniapp/pages/activity-mutual-result/activity-mutual-result.wxml @@ -27,7 +27,7 @@ - + 双方已确认,可查看公益活动和服务站 diff --git a/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.ts b/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.ts index b2ba634..0175f4f 100644 --- a/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.ts +++ b/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.ts @@ -55,10 +55,7 @@ Page({ }); if (res.statusCode === 200 && res.data.success) { this.setData({ myConfirmed: true, bothConfirmed: res.data.data.bothConfirmed }); - if (res.data.data.bothConfirmed) { - wx.showToast({ title: '双方已确认!', icon: 'success' }); - setTimeout(() => this.openChat(), 1500); - } + if (res.data.data.bothConfirmed) wx.showToast({ title: '双方已确认,可查看公益服务', icon: 'success' }); } } catch (e) { wx.showToast({ title: '操作失败', icon: 'none' }); } }, @@ -76,7 +73,4 @@ Page({ } catch (e) { wx.showToast({ title: '操作失败', icon: 'none' }); } }, - openChat() { - wx.navigateTo({ url: `/pages/chat/chat?partnerId=${this.data.partnerId}` }); - } }); diff --git a/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.wxml b/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.wxml index 3657a8c..fa28b55 100644 --- a/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.wxml +++ b/miniapp/pages/activity-mutual-waiting/activity-mutual-waiting.wxml @@ -7,21 +7,20 @@ ✅ 双方已确认! - 你们可以开始聊天了 - + 双方意向已确认,可查看公益活动和服务站 ⏳ 已发送邀请 - 对方确认后你们可以开始聊天 + 对方确认后将收到平台通知 💬 向对方表达兴趣 - 双方确认后才能开启线上沟通 + 双方确认后将收到平台通知,并可查看公益服务 diff --git a/miniapp/pages/discover/discover.ts b/miniapp/pages/discover/discover.ts index 1727f7c..47fa4ed 100644 --- a/miniapp/pages/discover/discover.ts +++ b/miniapp/pages/discover/discover.ts @@ -305,10 +305,7 @@ Page({ const result = await matchStore.like(userId); this.setData({ users: this.data.users.filter((u) => u.userId !== userId) }); if (result?.mutualMatch) { - appStore.toastSuccess('双向喜欢!可以开始聊天了'); - if (result.matchedConversationId) { - wx.navigateTo({ url: to.chat(result.matchedConversationId) }); - } + appStore.toastSuccess('双向意向匹配成功,可查看公益服务'); } else { appStore.toastSuccess('已表达喜欢'); } diff --git a/miniapp/pages/fate-center/fate-center.js b/miniapp/pages/fate-center/fate-center.js index e3f221b..01276c4 100644 --- a/miniapp/pages/fate-center/fate-center.js +++ b/miniapp/pages/fate-center/fate-center.js @@ -141,12 +141,7 @@ Page({ wx.navigateTo({ url: to.detail(userId) }); }, - /** 互相喜欢 → 开始聊天(已 Mutual,符合第 3 条) */ - onStartChat(e) { - const conversationId = e.currentTarget.dataset.conversationId; - if (!conversationId) return; - wx.navigateTo({ url: to.chat(conversationId) }); - }, + /** 互相喜欢 → 查看资料和公益服务 */ /** * 喜欢我的人 → 喜欢 TA(like-back) @@ -163,12 +158,9 @@ Page({ likersList: matchStore.state.likersList.filter((u) => u.userId !== userId), }); if (result && result.mutualMatch) { - appStore.toastSuccess('双向喜欢!可以开始聊天了'); + appStore.toastSuccess('双向意向匹配成功,可查看公益服务'); // 刷新互相喜欢列表 try { await matchStore.fetchMutualList({ pageSize: PAGE_SIZE }); } catch (e2) { /* ignore */ } - if (result.matchedConversationId) { - wx.navigateTo({ url: to.chat(result.matchedConversationId) }); - } } else { appStore.toastSuccess('已表达喜欢'); } diff --git a/miniapp/pages/fate-center/fate-center.wxml b/miniapp/pages/fate-center/fate-center.wxml index a5e78f3..d711966 100644 --- a/miniapp/pages/fate-center/fate-center.wxml +++ b/miniapp/pages/fate-center/fate-center.wxml @@ -34,7 +34,7 @@ - + @@ -45,7 +45,7 @@ wx:else illustration="heart" title="还没有互相喜欢的人" - description="去「找对象」遇见合适的人,互相喜欢就能聊天啦" + description="去「找对象」遇见合适的人,双方匹配后可查看公益服务" actions="{{[{text:'去「找对象', event:'discover', variant:'primary'}]}}" bind:action="onEmptyAction" /> diff --git a/miniapp/pages/index/index.ts b/miniapp/pages/index/index.ts index b5f310d..000d05d 100644 --- a/miniapp/pages/index/index.ts +++ b/miniapp/pages/index/index.ts @@ -4,7 +4,7 @@ */ const { authStore, appStore, matchStore } = require('../../stores') const api = require('../../utils/api') -// 顶部安全区:实测右上角胶囊按钮矩形,避免品牌栏「消息」入口被胶囊压住(见 utils/nav-metrics.js) +// 顶部安全区:实测右上角胶囊按钮矩形,避免品牌栏入口被胶囊压住(见 utils/nav-metrics.js) const { navSafeData } = require('../../utils/nav-metrics') // 4×2 宫格的视觉映射(tone → index.wxss 的 .tone-* 渐变砖;subtitle → 主标题下的说明行)。 @@ -244,10 +244,7 @@ Page({ const result = await matchStore.like(userId) this.setData({ recommendations: this.data.recommendations.filter((item) => item.userId !== userId) }) if (result?.mutualMatch) { - appStore.toastSuccess('双向喜欢!可以开始聊天了') - if (result.matchedConversationId) { - wx.navigateTo({ url: `/pages/chat/chat?id=${result.matchedConversationId}` }) - } + appStore.toastSuccess('双向意向匹配成功,可查看公益服务') } else { appStore.toastSuccess('已表达喜欢') } @@ -327,7 +324,7 @@ Page({ wx.navigateTo({ url: '/pages/anti-fraud/anti-fraud' }) }, onArticleTap(e) { wx.navigateTo({ url: `/pages/content-detail/content-detail?id=${e.currentTarget.dataset.id}` }) }, - onMessages() { wx.switchTab({ url: '/pages/messages/messages' }) }, + onMessages() { wx.navigateTo({ url: '/pages/notifications/notifications' }) }, formatDate(timestamp) { if (!timestamp) return '' diff --git a/miniapp/pages/index/index.wxml b/miniapp/pages/index/index.wxml index ec0d199..cef8b10 100644 --- a/miniapp/pages/index/index.wxml +++ b/miniapp/pages/index/index.wxml @@ -9,7 +9,7 @@ - 消息 + 通知 {{unreadCount}} diff --git a/miniapp/pages/matchmaker-recommendations/matchmaker-recommendations.js b/miniapp/pages/matchmaker-recommendations/matchmaker-recommendations.js index d5b32f6..5d86c6f 100644 --- a/miniapp/pages/matchmaker-recommendations/matchmaker-recommendations.js +++ b/miniapp/pages/matchmaker-recommendations/matchmaker-recommendations.js @@ -47,8 +47,8 @@ Page({ try { const result = action === 'like' ? await matchStore.like(userId) : await matchStore.pass(userId); this.setData({ recommendations: this.data.recommendations.filter((item) => item.userId !== userId) }); - if (action === 'like' && result && result.mutualMatch && result.matchedConversationId) { - wx.navigateTo({ url: to.chat(result.matchedConversationId) }); + if (action === 'like' && result && result.mutualMatch) { + appStore.toastSuccess('双向意向匹配成功,可查看公益服务'); } else { appStore.toastSuccess(action === 'like' ? '已表达喜欢' : '已暂不考虑'); } diff --git a/miniapp/pages/mine/mine.ts b/miniapp/pages/mine/mine.ts index ee6aaaa..6721fe9 100644 --- a/miniapp/pages/mine/mine.ts +++ b/miniapp/pages/mine/mine.ts @@ -119,7 +119,7 @@ Page({ // ============ 匹配 ============ - onGoMessages() { wx.switchTab({ url: ROUTE.TAB.MESSAGES }); }, + onGoMessages() { wx.navigateTo({ url: ROUTE.SETTINGS.NOTIFICATIONS }); }, onGoLikers() { wx.navigateTo({ url: ROUTE.DETAIL.LIKERS }); }, // ============ 账户与安全 ============ diff --git a/miniapp/pages/profile-detail/profile-detail.js b/miniapp/pages/profile-detail/profile-detail.js index a93dd2d..70e60a4 100644 --- a/miniapp/pages/profile-detail/profile-detail.js +++ b/miniapp/pages/profile-detail/profile-detail.js @@ -163,10 +163,7 @@ Page({ relationStatus: RELATION_STATUS.MUTUAL_MATCH, actionLoading: false, }); - appStore.toastSuccess('双向喜欢!可以开始聊天了'); - if (result.matchedConversationId) { - wx.navigateTo({ url: to.chat(result.matchedConversationId) }); - } + appStore.toastSuccess('双向意向匹配成功,可查看公益服务'); } else { this.setData({ isLiked: true, @@ -185,19 +182,6 @@ Page({ } }, - /** - * 开始聊天(仅 Mutual 后可见,AGENTS 第 3 条) - */ - onGoChat() { - const { userId, canChat, profile } = this.data; - if (!canChat) { - appStore.toast('双方互相喜欢后才能开始聊天'); - return; - } - // matchedConversationId 优先来自最近 like 结果;无则用 userId 占位由 chat 页拉取 - const conversationId = (profile && profile.matchedConversationId) || userId; - wx.navigateTo({ url: to.chat(conversationId) }); - }, /** * 举报:跳转 report 页,不在本页处理(AGENTS 第 7 条:举报内容不在此暴露) diff --git a/miniapp/pages/profile-detail/profile-detail.wxml b/miniapp/pages/profile-detail/profile-detail.wxml index 9f66a02..cc93ec6 100644 --- a/miniapp/pages/profile-detail/profile-detail.wxml +++ b/miniapp/pages/profile-detail/profile-detail.wxml @@ -113,14 +113,6 @@ bind:tap="onReport" disabled="{{actionLoading}}" >举报 - 开始聊天 } */ diff --git a/miniapp/stores/index.js b/miniapp/stores/index.js index 714c93e..5d380d9 100644 --- a/miniapp/stores/index.js +++ b/miniapp/stores/index.js @@ -15,6 +15,7 @@ const authStore = require('./authStore'); const userStore = require('./userStore'); const matchStore = require('./matchStore'); +// 兼容未注册的历史聊天页面源码;页面与服务端入口均已下线,不在启动流程中初始化。 const messageStore = require('./messageStore'); const activityStore = require('./activityStore'); const matchmakerStore = require('./matchmakerStore'); @@ -128,53 +129,40 @@ function isProfileApproved() { // ============ 跨 Store 编排 ============ /** - * 拉黑用户:触发 MatchStore 与 MessageStore 同步副作用 - * - MatchStore:从推荐/Mutual 列表移除该用户 - * - MessageStore:标记对应会话不可发消息(AGENTS 第 4 条) + * 拉黑用户:从推荐/Mutual 列表移除该用户。 * @param {string} userId * @param {string} [reason] */ async function blockUser(userId, reason) { await matchStore.block(userId, reason); - // 查找对应会话并标记停用 - const conv = messageStore.state.conversations.find( - (c) => c.targetUserId === userId || c.conversationId === userId, - ); - if (conv) messageStore.markConversationBlocked(conv.conversationId); } /** - * Like 用户:若返回 mutualMatch=true,自动刷新会话列表 + * Like 用户:若返回 mutualMatch=true,仅保留匹配结果和公益服务引导。 * @param {string} userId * @returns {Promise} */ async function likeUser(userId) { const result = await matchStore.like(userId); - if (result && result.mutualMatch) { - // Mutual Match 达成,刷新会话列表,让用户能立即开始聊天 - await messageStore.fetchConversations(); - } return result; } /** - * 解除匹配:触发 MatchStore 与 MessageStore 同步副作用 + * 解除匹配。 * @param {string} matchId */ async function unmatchConversation(matchId) { await matchStore.unmatch(matchId); - messageStore.markConversationBlocked(matchId); } /** - * 成功牵手:双确认第二步(仅被请求方调用),成功后刷新 userStore 与会话列表 + * 成功牵手:双确认第二步(仅被请求方调用),成功后刷新 userStore。 * @param {string} matchId */ async function confirmSuccess(matchId) { const result = await matchStore.confirmSuccess(matchId); // 成功牵手后关系状态变化,需刷新 userState try { await userStore.fetchStatus(); } catch (e) { /* 不阻塞主流程 */ } - try { await messageStore.fetchConversations(); } catch (e) { /* 同上 */ } return result; } @@ -185,14 +173,12 @@ async function confirmSuccess(matchId) { * 1. appStore.init() — 拉取服务端配置(不阻塞核心) * 2. authStore.hydrate() — 从本地恢复登录态 * 3. userStore.hydrate() — 从本地恢复用户状态快照 - * 4. messageStore.hydrate() — 从本地恢复未读数(兜底) * 5. 若已登录,异步刷新 userStore.fetchStatus()(不阻塞启动) */ async function bootstrap() { // 同步阶段:本地状态恢复(避免白屏) authStore.hydrate(); userStore.hydrate(); - messageStore.hydrate(); // 异步阶段:服务端配置 + 用户状态刷新 // 配置失败不阻塞启动;状态失败由路由守卫处理(跳登录页) diff --git a/miniapp/stores/matchStore.js b/miniapp/stores/matchStore.js index f250e20..78dca3d 100644 --- a/miniapp/stores/matchStore.js +++ b/miniapp/stores/matchStore.js @@ -163,7 +163,7 @@ class MatchStore extends BaseStore { /** * 拉黑用户(Block 优先级最高,AGENTS 第 4 条) * 拉黑后:本地从推荐/Mutual 列表移除;调用方应跳离当前用户详情页。 - * 跨 Store 副作用由 stores/index.js 编排:通知 messageStore 标记会话不可发消息。 + * 匹配结果由 stores/index.js 编排,不创建用户间聊天会话。 */ async block(userId, reason) { if (this._actionLocks.has(`block:${userId}`)) return; diff --git a/miniapp/types/index.d.ts b/miniapp/types/index.d.ts index 69ad0ad..2465432 100644 --- a/miniapp/types/index.d.ts +++ b/miniapp/types/index.d.ts @@ -109,7 +109,6 @@ export interface Recommendation extends User { export interface LikeResult { relationStatus: RelationStatus; mutualMatch: boolean; - matchedConversationId?: string; } // ============ Conversation / Message(规范 §35-38, §61-63) ============ diff --git a/miniapp/types/index.ts b/miniapp/types/index.ts index 03e59fa..e8fa31d 100644 --- a/miniapp/types/index.ts +++ b/miniapp/types/index.ts @@ -239,7 +239,6 @@ export interface LikeResult { mutual: boolean mutualMatch?: boolean matchCreated?: boolean - matchedConversationId?: string } // ===== 通知 ===== diff --git a/tests/miniapp/no_user_im_contract_test.py b/tests/miniapp/no_user_im_contract_test.py new file mode 100644 index 0000000..ebebda5 --- /dev/null +++ b/tests/miniapp/no_user_im_contract_test.py @@ -0,0 +1,28 @@ +import json +from pathlib import Path + + +ROOT = Path(__file__).parents[2] + + +def test_miniapp_has_no_user_chat_pages_or_message_tab(): + app = json.loads((ROOT / "miniapp" / "app.json").read_text(encoding="utf-8")) + assert not any(page in {"pages/messages/messages", "pages/chat/chat"} for page in app["pages"]) + assert not any(item.get("pagePath") == "pages/messages/messages" for item in app["tabBar"]["list"]) + + +def test_match_success_does_not_create_or_return_conversation(): + like = (ROOT / "backend/src/main/java/com/danyang/match/platform/match/controller/LikeController.java").read_text(encoding="utf-8") + relationship = (ROOT / "backend/src/main/java/com/danyang/match/platform/relationship/service/RelationshipService.java").read_text(encoding="utf-8") + assert "matchedConversationId" not in like + assert "getOrCreateConversation" not in like + assert "conversationAutoCreated" not in relationship + + +def test_chat_transport_is_disabled_at_backend_boundary(): + guard = ROOT / "backend/src/main/java/com/danyang/match/platform/chat/config/ChatDisabledFilter.java" + assert guard.exists() + source = guard.read_text(encoding="utf-8") + assert "/conversations" in source + assert "/ws/chat" in source + assert "CHAT_DISABLED" in source