package cprelationbroadcast import ( "context" "encoding/json" "errors" "fmt" "net/http" "strconv" "strings" "chatapp3-golang/internal/common" "chatapp3-golang/internal/model" "chatapp3-golang/internal/service/regionimgroup" "gorm.io/gorm" ) // ProcessPayload 解码 Java MQ 消息并发送区域飘屏。 func (s *Service) ProcessPayload(ctx context.Context, payload string) (*regionimgroup.RegionBroadcastResponse, error) { req, err := decodeEventPayload(payload, s.cfg.CPRelationBroadcast.MQ.Tag) if err != nil { return nil, err } if req.isEmpty() { return nil, nil } return s.ProcessEvent(ctx, req) } // ProcessEvent 处理标准化后的 CP 关系事件。 func (s *Service) ProcessEvent(ctx context.Context, req EventRequest) (*regionimgroup.RegionBroadcastResponse, error) { if s == nil || s.broadcaster == nil { return nil, common.NewAppError(http.StatusInternalServerError, "cp_relation_broadcaster_missing", "cp relation broadcaster is missing") } if s.db == nil { return nil, common.NewAppError(http.StatusInternalServerError, "cp_relation_db_missing", "cp relation db is missing") } req = normalizeEventRequest(req) userID := req.UserID.Int64() cpUserID := req.CpUserID.Int64() if userID <= 0 || cpUserID <= 0 { return nil, common.NewAppError(http.StatusBadRequest, "invalid_cp_relation_users", "userId and cpUserId are required") } relationType, err := normalizeRelationType(req.RelationType) if err != nil { return nil, err } sysOrigin := normalizeSysOrigin(req.SysOrigin, s.cfg.CPRelationBroadcast.DefaultSysOrigin) user, err := s.loadEventUser(ctx, userID, req.User) if err != nil { return nil, err } cpUser, err := s.loadEventUser(ctx, cpUserID, req.CpUser) if err != nil { return nil, err } msg := relationMessage(relationType, user.displayName(), cpUser.displayName()) data := buildBroadcastData(req, sysOrigin, relationType, user, cpUser, msg) return s.broadcaster.SendRegionBroadcast(ctx, regionimgroup.RegionBroadcastRequest{ SysOrigin: sysOrigin, Type: MessageTypeCPRelationBroadcast, Data: data, }) } func (s *Service) loadEventUser(ctx context.Context, userID int64, provided *EventUser) (EventUser, error) { var user model.UserBaseInfo err := s.db.WithContext(ctx).Where("id = ?", userID).First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { account := strconv.FormatInt(userID, 10) user = model.UserBaseInfo{ ID: userID, Account: account, UserNickname: account, } } else if err != nil { return EventUser{}, err } if strings.TrimSpace(user.Account) == "" { user.Account = strconv.FormatInt(userID, 10) } if strings.TrimSpace(user.UserNickname) == "" { user.UserNickname = user.Account } return mergeEventUser(userID, user, provided), nil } func buildBroadcastData(req EventRequest, sysOrigin, relationType string, user EventUser, cpUser EventUser, msg string) map[string]any { userID := user.normalizedID() cpUserID := cpUser.normalizedID() userIDText := strconv.FormatInt(userID, 10) cpUserIDText := strconv.FormatInt(cpUserID, 10) data := map[string]any{ "sysOrigin": sysOrigin, "type": MessageTypeCPRelationBroadcast, "relationType": relationType, "relationLabel": relationLabel(relationType), "msg": msg, "userId": userIDText, "sendUserId": userIDText, "senderUserId": userIDText, "cpUserId": cpUserIDText, "acceptUserId": cpUserIDText, "toUserId": cpUserIDText, "user": user.toMap(), "cpUser": cpUser.toMap(), "account": user.Account, "actualAccount": user.Account, "userNickname": user.displayName(), "nickname": user.displayName(), "userAvatar": firstNonBlank(user.UserAvatar, user.Avatar), "countryCode": user.CountryCode, "countryName": user.CountryName, "cpUserAccount": cpUser.Account, "cpUserNickname": cpUser.displayName(), "cpUserAvatar": firstNonBlank(cpUser.UserAvatar, cpUser.Avatar), "cpUserCountryCode": cpUser.CountryCode, "cpUserCountryName": cpUser.CountryName, "acceptAccount": cpUser.Account, "acceptNickname": cpUser.displayName(), "acceptUserAvatar": firstNonBlank(cpUser.UserAvatar, cpUser.Avatar), "toUserName": cpUser.displayName(), "toUserAvatarUrl": firstNonBlank(cpUser.UserAvatar, cpUser.Avatar), } if eventID := strings.TrimSpace(req.EventID); eventID != "" { data["eventId"] = eventID } if applyID := req.ApplyID.Int64(); applyID > 0 { data["applyId"] = strconv.FormatInt(applyID, 10) } if occurredAt := strings.TrimSpace(req.OccurredAt); occurredAt != "" { data["occurredAt"] = occurredAt } if len(req.Payload) > 0 { data["payload"] = req.Payload } return data } func normalizeEventRequest(req EventRequest) EventRequest { if req.UserID.Int64() <= 0 { req.UserID = req.SendUserID } if req.CpUserID.Int64() <= 0 { req.CpUserID = req.AcceptUserID } if req.UserID.Int64() <= 0 && req.User != nil { req.UserID = flexibleInt64(req.User.normalizedID()) } if req.CpUserID.Int64() <= 0 && req.CpUser != nil { req.CpUserID = flexibleInt64(req.CpUser.normalizedID()) } return req } func (req EventRequest) isEmpty() bool { return strings.TrimSpace(req.EventID) == "" && strings.TrimSpace(req.RelationType) == "" && req.UserID.Int64() <= 0 && req.CpUserID.Int64() <= 0 && req.SendUserID.Int64() <= 0 && req.AcceptUserID.Int64() <= 0 } func normalizeRelationType(value string) (string, error) { normalized := strings.ToUpper(strings.TrimSpace(value)) if normalized == "" { return RelationTypeCP, nil } switch normalized { case RelationTypeCP: return RelationTypeCP, nil case RelationTypeBrother, "BROTHERS": return RelationTypeBrother, nil case RelationTypeSisters, "SISTER": return RelationTypeSisters, nil default: return "", common.NewAppError(http.StatusBadRequest, "invalid_cp_relation_type", fmt.Sprintf("invalid cp relationType: %s", value)) } } func relationLabel(relationType string) string { switch relationType { case RelationTypeBrother: return "brothers" case RelationTypeSisters: return "sisters" default: return "cp_relation" } } func relationMessage(relationType, userName, cpUserName string) string { switch relationType { case RelationTypeBrother: return fmt.Sprintf("%s与%s结为兄弟", userName, cpUserName) case RelationTypeSisters: return fmt.Sprintf("%s与%s结为姐妹", userName, cpUserName) default: return fmt.Sprintf("%s与%s get cp relation", userName, cpUserName) } } func normalizeSysOrigin(values ...string) string { for _, value := range values { if text := strings.ToUpper(strings.TrimSpace(value)); text != "" { return text } } return defaultSysOrigin } func decodeEventPayload(payload string, expectedTag string) (EventRequest, error) { payload = strings.TrimSpace(payload) if payload == "" { return EventRequest{}, nil } req, err := decodeEventObject([]byte(payload)) if err != nil { return EventRequest{}, err } if !req.isEmpty() { return req, nil } var envelope messageEventEnvelope if err := json.Unmarshal([]byte(payload), &envelope); err != nil { return EventRequest{}, err } if shouldSkipEnvelope(envelope.Tag, expectedTag) { return EventRequest{}, nil } bodyPayload, ok, err := envelope.bodyPayload() if err != nil || !ok { return EventRequest{}, err } return decodeEventObject([]byte(bodyPayload)) } type rawEventRequest struct { EventRequest SysOriginSnake string `json:"sys_origin"` EventIDSnake string `json:"event_id"` ApplyIDSnake flexibleInt64 `json:"apply_id"` RelationTypeSnake string `json:"relation_type"` UserIDSnake flexibleInt64 `json:"user_id"` CpUserIDSnake flexibleInt64 `json:"cp_user_id"` SendUserIDSnake flexibleInt64 `json:"send_user_id"` AcceptUserIDSnake flexibleInt64 `json:"accept_user_id"` OccurredAtSnake string `json:"occurred_at"` TargetUserID flexibleInt64 `json:"targetUserId"` TargetUserIDSnake flexibleInt64 `json:"target_user_id"` ToUserIDSnake flexibleInt64 `json:"to_user_id"` } func decodeEventObject(data []byte) (EventRequest, error) { var raw rawEventRequest if err := json.Unmarshal(data, &raw); err != nil { return EventRequest{}, err } req := raw.EventRequest req.SysOrigin = firstNonBlank(req.SysOrigin, raw.SysOriginSnake) req.EventID = firstNonBlank(req.EventID, raw.EventIDSnake) req.RelationType = firstNonBlank(req.RelationType, raw.RelationTypeSnake) req.OccurredAt = firstNonBlank(req.OccurredAt, raw.OccurredAtSnake) if req.ApplyID.Int64() <= 0 { req.ApplyID = raw.ApplyIDSnake } if req.UserID.Int64() <= 0 { req.UserID = firstFlexible(raw.UserIDSnake, req.SendUserID, raw.SendUserIDSnake) } if req.CpUserID.Int64() <= 0 { req.CpUserID = firstFlexible(raw.CpUserIDSnake, req.AcceptUserID, raw.AcceptUserIDSnake, raw.TargetUserID, raw.TargetUserIDSnake, raw.ToUserIDSnake) } if req.SendUserID.Int64() <= 0 { req.SendUserID = firstFlexible(req.UserID, raw.SendUserIDSnake) } if req.AcceptUserID.Int64() <= 0 { req.AcceptUserID = firstFlexible(req.CpUserID, raw.AcceptUserIDSnake, raw.TargetUserID, raw.TargetUserIDSnake, raw.ToUserIDSnake) } return req, nil } func firstFlexible(values ...flexibleInt64) flexibleInt64 { for _, value := range values { if value.Int64() > 0 { return value } } return 0 } type messageEventEnvelope struct { Tag string `json:"tag"` Body json.RawMessage `json:"body"` } func (e messageEventEnvelope) bodyPayload() (string, bool, error) { raw := strings.TrimSpace(string(e.Body)) if raw == "" || raw == "null" { return "", false, nil } if strings.HasPrefix(raw, "\"") { var body string if err := json.Unmarshal(e.Body, &body); err != nil { return "", false, err } body = strings.TrimSpace(body) return body, body != "", nil } return raw, true, nil } func shouldSkipEnvelope(actualTag, expectedTag string) bool { actualTag = strings.TrimSpace(actualTag) expectedTag = strings.TrimSpace(expectedTag) if actualTag == "" || expectedTag == "" || expectedTag == "*" { return false } return actualTag != expectedTag }