package baishun import ( "context" "encoding/json" "errors" "fmt" "log" "strconv" "strings" "time" "chatapp3-golang/internal/model" "chatapp3-golang/internal/service/highwin" "chatapp3-golang/internal/utils" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) const ( baishunReportTypeStart = "game_start" baishunReportTypeSettle = "game_settle" baishunReportBetTTL = 24 * time.Hour baishunReportBetRedisPrefix = "baishun:report:bet" ) type baishunGameBetRecord struct { SysOrigin string `json:"sysOrigin"` RoomID string `json:"roomId"` GameID int64 `json:"gameId"` GameRoundID string `json:"gameRoundId"` UserID int64 `json:"userId"` Bet int64 `json:"bet"` } func (s *BaishunService) HandleReport(ctx context.Context, req BaishunReportRequest, rawJSON string) baishunStandardResponse { resp := baishunStandardResponse{ Code: 0, Message: "succeed", UniqueID: s.uniqueID(), Data: map[string]any{}, } sysOrigin := "" if session, ok := s.findSessionByToken(ctx, req.SSToken, req.UserID); ok { sysOrigin = session.SysOrigin if err := s.handleReportHighWins(ctx, session.SysOrigin, req); err != nil { log.Printf("handle baishun report high win failed userId=%s reportType=%s: %v", req.UserID, req.ReportType, err) } } s.saveCallbackLog( ctx, "report", rawJSON, utils.MustJSONString(resp, ""), sysOrigin, req.UserID, nil, stringPtr(reportGameRoundID(req.ReportMsg)), baishunCallbackStatusSuccess, 0, resp.Message, ) return resp } func (s *BaishunService) handleReportHighWins(ctx context.Context, sysOrigin string, req BaishunReportRequest) error { switch strings.ToLower(strings.TrimSpace(req.ReportType)) { case baishunReportTypeStart: return s.rememberBaishunGameBets(ctx, sysOrigin, req.ReportMsg) case baishunReportTypeSettle: return s.publishBaishunGameHighWins(ctx, sysOrigin, req.ReportMsg) default: return nil } } func (s *BaishunService) rememberBaishunGameBets(ctx context.Context, sysOrigin string, payload map[string]any) error { if s == nil || s.repo.Redis == nil { return nil } roomID := reportText(payload, "room_id", "roomId") gameRoundID := reportGameRoundID(payload) if strings.TrimSpace(roomID) == "" || strings.TrimSpace(gameRoundID) == "" { return nil } gameID := reportInt64(payload, "game_id", "gameId") for _, player := range reportPlayers(payload) { if reportInt64(player, "is_ai", "isAi") != 0 { continue } userID := reportInt64(player, "user_id", "userId") bet := reportInt64(player, "bet") if userID <= 0 || bet <= 0 { continue } record := baishunGameBetRecord{ SysOrigin: normalizeAdminSysOrigin(sysOrigin), RoomID: roomID, GameID: gameID, GameRoundID: gameRoundID, UserID: userID, Bet: bet, } body, err := json.Marshal(record) if err != nil { return err } if err := s.repo.Redis.Set(ctx, baishunGameBetKey(record.SysOrigin, gameRoundID, userID), string(body), baishunReportBetTTL).Err(); err != nil { return err } } return nil } func (s *BaishunService) publishBaishunGameHighWins(ctx context.Context, sysOrigin string, payload map[string]any) error { if s == nil || s.highWinBroadcaster == nil || s.repo.Redis == nil { return nil } sysOrigin = normalizeAdminSysOrigin(sysOrigin) roomID := reportText(payload, "room_id", "roomId") gameRoundID := reportGameRoundID(payload) if strings.TrimSpace(roomID) == "" || strings.TrimSpace(gameRoundID) == "" { return nil } gameID := reportInt64(payload, "game_id", "gameId") gameCover := s.resolveBaishunReportGameCover(ctx, sysOrigin, roomID, gameID) for _, player := range reportPlayers(payload) { if reportInt64(player, "is_ai", "isAi") != 0 { continue } userID := reportInt64(player, "user_id", "userId") reward := reportInt64(player, "reward") if userID <= 0 || reward <= 0 { continue } betRecord, ok, err := s.loadBaishunGameBet(ctx, sysOrigin, gameRoundID, userID) if err != nil { return err } if !ok || betRecord.Bet <= 0 { continue } multiple := highwin.Multiple(reward, betRecord.Bet) if !highwin.ShouldBroadcast(multiple, reward) { continue } data := map[string]any{ "gameId": gameID, "gameRoundId": gameRoundID, "gameUrl": gameCover, "betAmount": betRecord.Bet, "currencyDiff": reward, "rank": reportInt64(player, "rank"), "score": reportInt64(player, "score"), } if err := highwin.SendRegionBroadcast(ctx, s.highWinBroadcaster, highwin.BroadcastEvent{ SysOrigin: sysOrigin, Type: highwin.MessageTypeBaishunWin, UserID: userID, RoomID: roomID, Multiple: multiple, Amount: reward, Data: data, }); err != nil { log.Printf("send baishun game high win region broadcast failed roundId=%s userId=%d: %v", gameRoundID, userID, err) } _ = s.repo.Redis.Del(ctx, baishunGameBetKey(sysOrigin, gameRoundID, userID)).Err() } return nil } func (s *BaishunService) loadBaishunGameBet(ctx context.Context, sysOrigin, gameRoundID string, userID int64) (baishunGameBetRecord, bool, error) { raw, err := s.repo.Redis.Get(ctx, baishunGameBetKey(sysOrigin, gameRoundID, userID)).Result() if errors.Is(err, redis.Nil) { return baishunGameBetRecord{}, false, nil } if err != nil { return baishunGameBetRecord{}, false, err } var record baishunGameBetRecord if err := json.Unmarshal([]byte(raw), &record); err != nil { return baishunGameBetRecord{}, false, err } return record, true, nil } func (s *BaishunService) resolveBaishunReportGameCover(ctx context.Context, sysOrigin, roomID string, gameID int64) string { var state model.BaishunRoomState if strings.TrimSpace(roomID) != "" { err := s.repo.DB.WithContext(ctx). Where("sys_origin = ? AND room_id = ?", normalizeAdminSysOrigin(sysOrigin), roomID). First(&state).Error if err == nil && strings.TrimSpace(state.CurrentGameCover) != "" { return strings.TrimSpace(state.CurrentGameCover) } if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return "" } } if gameID <= 0 { return "" } profile, err := s.activeProfile(ctx, sysOrigin) if err != nil { return "" } var catalog model.BaishunGameCatalog if err := s.repo.DB.WithContext(ctx). Where("sys_origin = ? AND profile = ? AND vendor_game_id = ?", normalizeAdminSysOrigin(sysOrigin), profile, gameID). First(&catalog).Error; err != nil { return "" } return strings.TrimSpace(catalog.Cover) } func reportGameRoundID(payload map[string]any) string { return reportText(payload, "game_round_id", "gameRoundId") } func reportPlayers(payload map[string]any) []map[string]any { raw, exists := payload["players"] if !exists { return nil } switch typed := raw.(type) { case []map[string]any: return typed case []any: players := make([]map[string]any, 0, len(typed)) for _, item := range typed { if player, ok := item.(map[string]any); ok { players = append(players, player) } } return players default: return nil } } func reportText(payload map[string]any, keys ...string) string { for _, key := range keys { if value, exists := payload[key]; exists { text := strings.TrimSpace(toString(value)) if text != "" { return text } } } return "" } func reportInt64(payload map[string]any, keys ...string) int64 { for _, key := range keys { value, exists := payload[key] if !exists { continue } switch typed := value.(type) { case int: return int64(typed) case int64: return typed case float64: return int64(typed) case json.Number: parsed, _ := typed.Int64() return parsed case string: parsed, _ := strconv.ParseInt(strings.TrimSpace(typed), 10, 64) return parsed default: parsed, _ := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(typed)), 10, 64) return parsed } } return 0 } func baishunGameBetKey(sysOrigin, gameRoundID string, userID int64) string { return strings.Join([]string{ baishunReportBetRedisPrefix, normalizeAdminSysOrigin(sysOrigin), strings.TrimSpace(gameRoundID), strconv.FormatInt(userID, 10), }, ":") }