From 4cf4eba5ff430e189da32e25935e45a26d5cf764 Mon Sep 17 00:00:00 2001 From: hy001 Date: Mon, 25 May 2026 21:02:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E6=8B=9F=E9=A3=98=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/router/region_im_group_routes.go | 19 ++ internal/service/highwin/admin.go | 202 ++++++++++++++++++++++ internal/service/highwin/broadcast.go | 22 +-- 3 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 internal/service/highwin/admin.go diff --git a/internal/router/region_im_group_routes.go b/internal/router/region_im_group_routes.go index 873ab12..8eb8fd5 100644 --- a/internal/router/region_im_group_routes.go +++ b/internal/router/region_im_group_routes.go @@ -4,6 +4,7 @@ import ( "net/http" "chatapp3-golang/internal/config" + "chatapp3-golang/internal/service/highwin" "chatapp3-golang/internal/service/regionimgroup" "github.com/gin-gonic/gin" @@ -65,6 +66,7 @@ func registerRegionIMGroupInternalRoutes(engine *gin.Engine, cfg config.Config, func registerRegionIMGroupAdminRoutes(engine *gin.Engine, javaClient authGateway, service *regionimgroup.Service) { registerRegionIMGroupAdminGroup(engine.Group("/region/config"), javaClient, service) registerRegionIMGroupAdminGroup(engine.Group("/resident-activity/voice-room-red-packet"), javaClient, service) + registerRegionBroadcastSimulationAdminGroup(engine.Group("/operate/region-broadcast"), javaClient, service) } func registerRegionIMGroupAdminGroup(group *gin.RouterGroup, javaClient authGateway, service *regionimgroup.Service) { @@ -104,3 +106,20 @@ func registerRegionIMGroupAdminGroup(group *gin.RouterGroup, javaClient authGate writeOK(c, resp) }) } + +func registerRegionBroadcastSimulationAdminGroup(group *gin.RouterGroup, javaClient authGateway, service *regionimgroup.Service) { + group.Use(consoleAuthMiddleware(javaClient)) + group.POST("/simulate", func(c *gin.Context) { + var req highwin.BroadcastSimulationRequest + if err := c.ShouldBindJSON(&req); err != nil { + writeError(c, regionimgroup.NewAppError(http.StatusBadRequest, "bad_request", err.Error())) + return + } + resp, err := highwin.SendSimulationBroadcast(c.Request.Context(), service, req) + if err != nil { + writeError(c, err) + return + } + writeOK(c, resp) + }) +} diff --git a/internal/service/highwin/admin.go b/internal/service/highwin/admin.go new file mode 100644 index 0000000..af0cda4 --- /dev/null +++ b/internal/service/highwin/admin.go @@ -0,0 +1,202 @@ +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) diff --git a/internal/service/highwin/broadcast.go b/internal/service/highwin/broadcast.go index dd88c3a..f6dabd3 100644 --- a/internal/service/highwin/broadcast.go +++ b/internal/service/highwin/broadcast.go @@ -22,13 +22,14 @@ type RegionBroadcaster interface { } type BroadcastEvent struct { - SysOrigin string - Type string - UserID int64 - RoomID string - Multiple float64 - Amount int64 - Data map[string]any + SysOrigin string + RegionCode string + Type string + UserID int64 + RoomID string + Multiple float64 + Amount int64 + Data map[string]any } func IntegralMultiple(amount, base int64) int64 { @@ -78,9 +79,10 @@ func SendRegionBroadcast(ctx context.Context, broadcaster RegionBroadcaster, eve setRequired(data, "msg", highWinMessage(data, messageType, userIDText, event.Multiple, event.Amount)) _, err := broadcaster.SendRegionBroadcast(ctx, regionimgroup.RegionBroadcastRequest{ - SysOrigin: strings.TrimSpace(event.SysOrigin), - Type: messageType, - Data: data, + SysOrigin: strings.TrimSpace(event.SysOrigin), + RegionCode: strings.TrimSpace(event.RegionCode), + Type: messageType, + Data: data, }) return err }