package highwin import ( "context" "fmt" "net/http" "strconv" "strings" "time" "chatapp3-golang/internal/common" "chatapp3-golang/internal/service/regionimgroup" ) const ( SimulationKindLuckyGift = "LUCKY_GIFT" SimulationKindGameWin = "GAME_WIN" ) type BroadcastSimulationRequest struct { SysOrigin string `json:"sysOrigin"` RegionCode string `json:"regionCode"` Kind string `json:"kind"` SenderUserID string `json:"senderUserId"` RoomID string `json:"roomId"` Multiple string `json:"multiple"` WinAmount string `json:"winAmount"` AcceptUserID string `json:"acceptUserId"` GiftID string `json:"giftId"` GiftCandy string `json:"giftCandy"` GiftQuantity string `json:"giftQuantity"` GameID string `json:"gameId"` GameRoundID string `json:"gameRoundId"` GameURL string `json:"gameUrl"` BetAmount string `json:"betAmount"` ProviderOrder string `json:"providerOrderId"` } type BroadcastSimulationResponse struct { SysOrigin string `json:"sysOrigin"` RegionCode string `json:"regionCode"` GroupID string `json:"groupId"` Type string `json:"type"` Data map[string]any `json:"data"` } func SendSimulationBroadcast( ctx context.Context, broadcaster RegionBroadcaster, req BroadcastSimulationRequest, ) (*BroadcastSimulationResponse, error) { if broadcaster == nil { return nil, common.NewAppError(http.StatusInternalServerError, "region_broadcaster_missing", "region broadcaster is missing") } messageType, err := simulationMessageType(req.Kind) if err != nil { return nil, err } userID, err := parseRequiredInt64(req.SenderUserID, "senderUserId") if err != nil { return nil, err } roomID := strings.TrimSpace(req.RoomID) if roomID == "" { return nil, common.NewAppError(http.StatusBadRequest, "missing_room_id", "roomId is required") } amount, err := parseRequiredInt64(req.WinAmount, "winAmount") if err != nil { return nil, err } multiple, err := parseRequiredFloat64(req.Multiple, "multiple") if err != nil { return nil, err } if !ShouldBroadcast(multiple, amount) { return nil, common.NewAppError(http.StatusBadRequest, "broadcast_threshold_not_met", "multiple must be >= 10 and winAmount must be > 0") } data := simulationData(messageType, req, amount) event := BroadcastEvent{ SysOrigin: req.SysOrigin, RegionCode: req.RegionCode, Type: messageType, UserID: userID, RoomID: roomID, Multiple: multiple, Amount: amount, Data: data, } if err := SendRegionBroadcast(ctx, broadcaster, event); err != nil { return nil, err } previewData := cloneData(data) setRequired(previewData, "userId", strconv.FormatInt(userID, 10)) setRequired(previewData, "sendUserId", strconv.FormatInt(userID, 10)) setRequired(previewData, "senderUserId", strconv.FormatInt(userID, 10)) setRequired(previewData, "roomId", roomID) setRequired(previewData, "multiple", normalizeMultiple(multiple)) setRequired(previewData, "winMultiple", normalizeMultiple(multiple)) setRequired(previewData, "winAmount", amount) setRequired(previewData, "awardAmount", amount) setRequired(previewData, "msg", highWinMessage(previewData, messageType, strconv.FormatInt(userID, 10), multiple, amount)) return &BroadcastSimulationResponse{ SysOrigin: strings.ToUpper(strings.TrimSpace(req.SysOrigin)), RegionCode: strings.TrimSpace(req.RegionCode), Type: messageType, Data: previewData, }, nil } func simulationMessageType(kind string) (string, error) { switch strings.ToUpper(strings.TrimSpace(kind)) { case SimulationKindLuckyGift: return MessageTypeLuckyGift, nil case SimulationKindGameWin: return MessageTypeBaishunWin, nil default: return "", common.NewAppError(http.StatusBadRequest, "invalid_broadcast_kind", "kind must be LUCKY_GIFT or GAME_WIN") } } func simulationData(messageType string, req BroadcastSimulationRequest, amount int64) map[string]any { now := strconv.FormatInt(time.Now().UnixMilli(), 10) switch messageType { case MessageTypeLuckyGift: data := map[string]any{ "businessId": firstNonBlank(req.ProviderOrder, "admin-sim-lucky-gift-"+now), "orderId": firstNonBlank(req.ProviderOrder, "admin-sim-order-"+now), "globalNews": true, "multipleType": "WIN", } setOptional(data, "acceptUserId", req.AcceptUserID) setOptional(data, "giftId", req.GiftID) setOptionalInt64(data, "giftCandy", req.GiftCandy) setOptionalInt64(data, "giftQuantity", req.GiftQuantity) return data default: data := map[string]any{ "gameRoundId": firstNonBlank(req.GameRoundID, "admin-sim-game-round-"+now), "currencyDiff": amount, } setOptional(data, "gameId", req.GameID) setOptional(data, "gameUrl", req.GameURL) setOptionalInt64(data, "betAmount", req.BetAmount) return data } } func setOptional(data map[string]any, key string, value string) { if value = strings.TrimSpace(value); value != "" { data[key] = value } } func setOptionalInt64(data map[string]any, key string, value string) { parsed, err := parseOptionalInt64(value) if err == nil && parsed > 0 { data[key] = parsed } } func firstNonBlank(values ...string) string { for _, value := range values { if trimmed := strings.TrimSpace(value); trimmed != "" { return trimmed } } return "" } func parseRequiredInt64(value string, field string) (int64, error) { parsed, err := parseOptionalInt64(value) if err != nil || parsed <= 0 { return 0, common.NewAppError(http.StatusBadRequest, "invalid_"+field, fmt.Sprintf("%s must be a positive integer", field)) } return parsed, nil } func parseOptionalInt64(value string) (int64, error) { raw := strings.TrimSpace(value) if raw == "" { return 0, nil } return strconv.ParseInt(raw, 10, 64) } func parseRequiredFloat64(value string, field string) (float64, error) { raw := strings.TrimSpace(value) if raw == "" { return 0, common.NewAppError(http.StatusBadRequest, "invalid_"+field, fmt.Sprintf("%s must be a positive number", field)) } parsed, err := strconv.ParseFloat(raw, 64) if err != nil || parsed <= 0 { return 0, common.NewAppError(http.StatusBadRequest, "invalid_"+field, fmt.Sprintf("%s must be a positive number", field)) } return parsed, nil } var _ RegionBroadcaster = (*regionimgroup.Service)(nil)