From f1f4f46ee9a1c979fc84f406776ad1bacf4a376a Mon Sep 17 00:00:00 2001 From: zhx Date: Wed, 22 Jul 2026 19:14:39 +0800 Subject: [PATCH] vip im --- .../transport/http/vip_handler_test.go | 14 ++++++++----- .../transport/http/walletapi/vip_handler.go | 21 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/services/gateway-service/internal/transport/http/vip_handler_test.go b/services/gateway-service/internal/transport/http/vip_handler_test.go index 9294d388..d573a035 100644 --- a/services/gateway-service/internal/transport/http/vip_handler_test.go +++ b/services/gateway-service/internal/transport/http/vip_handler_test.go @@ -97,11 +97,12 @@ func TestTriggerVIPOnlineNoticeChecksBenefitAndPublishesTrustedGlobalPayload(t * var envelope struct { Code string `json:"code"` Data struct { - EventID string `json:"event_id"` - GroupID string `json:"group_id"` - Status string `json:"status"` - Created bool `json:"created"` - EvaluatedAtMS int64 `json:"evaluated_at_ms"` + EventID string `json:"event_id"` + GroupID string `json:"group_id"` + Status string `json:"status"` + Created bool `json:"created"` + EvaluatedAtMS int64 `json:"evaluated_at_ms"` + Notice map[string]any `json:"notice"` } `json:"data"` } if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil { @@ -110,6 +111,9 @@ func TestTriggerVIPOnlineNoticeChecksBenefitAndPublishesTrustedGlobalPayload(t * if envelope.Code != httpkit.CodeOK || envelope.Data.EventID != publish.GetEventId() || envelope.Data.GroupID != "hy_fami_bc_g_v2" || envelope.Data.Status != "pending" || !envelope.Data.Created || envelope.Data.EvaluatedAtMS != 1_800_000_000_000 { t.Fatalf("response mismatch: %+v", envelope) } + if envelope.Data.Notice["event_id"] != publish.GetEventId() || envelope.Data.Notice["broadcast_type"] != "vip_online_notice" || envelope.Data.Notice["app_code"] != "fami" || envelope.Data.Notice["scope"] != "global" || envelope.Data.Notice["user_id"] != "42" || envelope.Data.Notice["vip_level"] != float64(9) { + t.Fatalf("local notice snapshot mismatch: %+v", envelope.Data.Notice) + } } func TestTriggerVIPOnlineNoticeWithoutEquippedFramePublishesExplicitEmptyFields(t *testing.T) { diff --git a/services/gateway-service/internal/transport/http/walletapi/vip_handler.go b/services/gateway-service/internal/transport/http/walletapi/vip_handler.go index f8f73259..c33fa371 100644 --- a/services/gateway-service/internal/transport/http/walletapi/vip_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/vip_handler.go @@ -274,7 +274,16 @@ func (h *Handler) triggerVIPOnlineNotice(writer http.ResponseWriter, request *ht if displayID == "" { displayID = strings.TrimSpace(profile.GetDisplayUserId()) } - payload, err := json.Marshal(map[string]any{ + // event_id 同时进入腾讯 IM payload 和 HTTP 成功响应;发送端用它把本地即时回显 + // 与稍后可能回环的 AVChatRoom 消息收敛成一次展示,不能让两条链路各自产生身份。 + commandDigest := sha256.Sum256([]byte(commandID)) + eventID := fmt.Sprintf("vip_online_notice:%d:%x", userID, commandDigest) + notice := map[string]any{ + "event_id": eventID, + "broadcast_type": vipOnlineNoticeBroadcastType, + "scope": "global", + "app_code": appCode, + "sent_at_ms": benefitResp.GetEvaluatedAtMs(), // VIP 全服播报进入腾讯 IM,长用户 ID 必须在首次 JSON 编码时就是字符串。 "user_id": tencentim.FormatOptionalUserID(userID), "sender_user_id": tencentim.FormatOptionalUserID(userID), @@ -296,15 +305,12 @@ func (h *Handler) triggerVIPOnlineNotice(writer http.ResponseWriter, request *ht "vip_name": effectiveVIP.GetName(), "message": vipOnlineNoticeMessage(benefitResp.GetBenefit().GetMetadataJson()), "action": map[string]string{"type": "none"}, - }) + } + payload, err := json.Marshal(notice) if err != nil { httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error") return } - // event_id 使用 user_id + command_id 摘要,既隔离不同用户,又避免客户端长命令超过 - // activity outbox 的 VARCHAR(128) 主键;相同 command_id 的 HTTP 重试会命中同一条 outbox。 - commandDigest := sha256.Sum256([]byte(commandID)) - eventID := fmt.Sprintf("vip_online_notice:%d:%x", userID, commandDigest) broadcastResp, err := h.broadcastClient.PublishGlobalBroadcast(ctx, &activityv1.PublishGlobalBroadcastRequest{ Meta: httpkit.ActivityMeta(request), EventId: eventID, @@ -321,6 +327,9 @@ func (h *Handler) triggerVIPOnlineNotice(writer http.ResponseWriter, request *ht "status": broadcastResp.GetStatus(), "created": broadcastResp.GetCreated(), "evaluated_at_ms": benefitResp.GetEvaluatedAtMs(), + // notice 是已经通过权益、用户资料和当前佩戴资源校验的可信快照;客户端只可 + // 在本机即时回显,其他用户仍必须消费 activity-service 投递的腾讯 IM 消息。 + "notice": notice, }) }