通用im 中奖消息
This commit is contained in:
parent
a02e22ac74
commit
8e8878a038
@ -69,6 +69,8 @@ func main() {
|
|||||||
wheelService := wheel.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
wheelService := wheel.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
smashEggService := smashegg.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
smashEggService := smashegg.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
regionIMGroupService := regionimgroup.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
regionIMGroupService := regionimgroup.NewService(app.Config, app.Repository.DB, &app.Gateways)
|
||||||
|
baishunService.SetHighWinBroadcaster(regionIMGroupService)
|
||||||
|
luckyGiftService.SetHighWinBroadcaster(regionIMGroupService)
|
||||||
voiceRoomRedPacketService := voiceroomredpacket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet, regionIMGroupService, app.Gateways.Room)
|
voiceRoomRedPacketService := voiceroomredpacket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, app.Gateways.Wallet, regionIMGroupService, app.Gateways.Room)
|
||||||
voiceRoomRedPacketService.SetRegionResolver(&app.Gateways)
|
voiceRoomRedPacketService.SetRegionResolver(&app.Gateways)
|
||||||
voiceRoomRocketService := voiceroomrocket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
voiceRoomRocketService := voiceroomrocket.NewService(app.Config, app.Repository.DB, app.Repository.Redis, &app.Gateways)
|
||||||
|
|||||||
@ -248,7 +248,7 @@ func registerBaishunRoutes(
|
|||||||
})
|
})
|
||||||
callbackGroup.POST("/report", func(c *gin.Context) {
|
callbackGroup.POST("/report", func(c *gin.Context) {
|
||||||
raw, payload := readRawPayload(c)
|
raw, payload := readRawPayload(c)
|
||||||
writeBaishunJSON(c, http.StatusOK, baishunService.HandleReport(c.Request.Context(), payload, raw))
|
writeBaishunJSON(c, http.StatusOK, baishunService.HandleReport(c.Request.Context(), mapToReportRequest(payload), raw))
|
||||||
})
|
})
|
||||||
callbackGroup.POST("/balance-info", func(c *gin.Context) {
|
callbackGroup.POST("/balance-info", func(c *gin.Context) {
|
||||||
raw, payload := readRawPayload(c)
|
raw, payload := readRawPayload(c)
|
||||||
@ -338,6 +338,16 @@ func mapToChangeBalanceRequest(payload map[string]any) baishun.BaishunChangeBala
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mapToReportRequest 把百顺 report 回调报文映射成服务层 DTO。
|
||||||
|
func mapToReportRequest(payload map[string]any) baishun.BaishunReportRequest {
|
||||||
|
return baishun.BaishunReportRequest{
|
||||||
|
ReportType: asString(payload["report_type"]),
|
||||||
|
ReportMsg: asMap(payload["report_msg"]),
|
||||||
|
UserID: asString(payload["user_id"]),
|
||||||
|
SSToken: asString(payload["ss_token"]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mapToBalanceInfoRequest 把百顺 balance-info 回调报文映射成服务层 DTO。
|
// mapToBalanceInfoRequest 把百顺 balance-info 回调报文映射成服务层 DTO。
|
||||||
func mapToBalanceInfoRequest(payload map[string]any) baishun.BaishunBalanceInfoRequest {
|
func mapToBalanceInfoRequest(payload map[string]any) baishun.BaishunBalanceInfoRequest {
|
||||||
return baishun.BaishunBalanceInfoRequest{
|
return baishun.BaishunBalanceInfoRequest{
|
||||||
@ -350,6 +360,26 @@ func mapToBalanceInfoRequest(payload map[string]any) baishun.BaishunBalanceInfoR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// asMap 宽松读取对象字段。
|
||||||
|
func asMap(value any) map[string]any {
|
||||||
|
switch typed := value.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
return typed
|
||||||
|
case string:
|
||||||
|
result := map[string]any{}
|
||||||
|
_ = json.Unmarshal([]byte(typed), &result)
|
||||||
|
return result
|
||||||
|
default:
|
||||||
|
body, err := json.Marshal(typed)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]any{}
|
||||||
|
}
|
||||||
|
result := map[string]any{}
|
||||||
|
_ = json.Unmarshal(body, &result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// asString 宽松读取字符串字段。
|
// asString 宽松读取字符串字段。
|
||||||
func asString(value any) string {
|
func asString(value any) string {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
|
|||||||
@ -243,21 +243,6 @@ func (s *BaishunService) HandleChangeBalance(ctx context.Context, req BaishunCha
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleReport 处理百顺上报类回调,目前仅记录日志并返回成功。
|
|
||||||
func (s *BaishunService) HandleReport(ctx context.Context, payload map[string]any, rawJSON string) baishunStandardResponse {
|
|
||||||
userID := toString(payload["user_id"])
|
|
||||||
orderID := toString(payload["order_id"])
|
|
||||||
gameRoundID := toString(payload["game_round_id"])
|
|
||||||
resp := baishunStandardResponse{
|
|
||||||
Code: 0,
|
|
||||||
Message: "succeed",
|
|
||||||
UniqueID: s.uniqueID(),
|
|
||||||
Data: map[string]any{},
|
|
||||||
}
|
|
||||||
s.saveCallbackLog(ctx, "report", rawJSON, utils.MustJSONString(resp, ""), "", userID, stringPtr(orderID), stringPtr(gameRoundID), baishunCallbackStatusSuccess, 0, resp.Message)
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaishunService) reportGameConsumeTaskEvent(ctx context.Context, session model.BaishunLaunchSession, req BaishunChangeBalanceRequest) {
|
func (s *BaishunService) reportGameConsumeTaskEvent(ctx context.Context, session model.BaishunLaunchSession, req BaishunChangeBalanceRequest) {
|
||||||
if s.taskReporter == nil || req.CurrencyDiff >= 0 {
|
if s.taskReporter == nil || req.CurrencyDiff >= 0 {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -49,6 +49,7 @@ func newTestBaishunService(t *testing.T) (*BaishunService, *gorm.DB) {
|
|||||||
&model.BaishunProviderConfig{},
|
&model.BaishunProviderConfig{},
|
||||||
&model.BaishunGameCatalog{},
|
&model.BaishunGameCatalog{},
|
||||||
&model.BaishunLaunchSession{},
|
&model.BaishunLaunchSession{},
|
||||||
|
&model.BaishunCallbackLog{},
|
||||||
&model.BaishunRoomState{},
|
&model.BaishunRoomState{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("auto migrate: %v", err)
|
t.Fatalf("auto migrate: %v", err)
|
||||||
|
|||||||
291
internal/service/baishun/report.go
Normal file
291
internal/service/baishun/report.go
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
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),
|
||||||
|
}, ":")
|
||||||
|
}
|
||||||
115
internal/service/baishun/report_test.go
Normal file
115
internal/service/baishun/report_test.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package baishun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/model"
|
||||||
|
"chatapp3-golang/internal/service/regionimgroup"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHandleReportPublishesRegionHighWinAfterStartAndSettle(t *testing.T) {
|
||||||
|
service, db := newTestBaishunService(t)
|
||||||
|
mr := miniredis.RunT(t)
|
||||||
|
redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
||||||
|
t.Cleanup(func() { _ = redisClient.Close() })
|
||||||
|
service.repo.Redis = redisClient
|
||||||
|
broadcaster := &fakeBaishunRegionBroadcaster{}
|
||||||
|
service.SetHighWinBroadcaster(broadcaster)
|
||||||
|
|
||||||
|
expireAt := time.Now().Add(time.Hour)
|
||||||
|
token := "ss-token-1001"
|
||||||
|
if err := db.Create(&model.BaishunLaunchSession{
|
||||||
|
ID: 1,
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
RoomID: "9001",
|
||||||
|
UserID: 1001,
|
||||||
|
InternalGameID: "bs_1146",
|
||||||
|
VendorGameID: 1146,
|
||||||
|
GameSessionID: "session-1",
|
||||||
|
SSToken: &token,
|
||||||
|
SSTokenExpireTime: &expireAt,
|
||||||
|
Status: baishunSessionActive,
|
||||||
|
CreateTime: time.Now(),
|
||||||
|
UpdateTime: time.Now(),
|
||||||
|
}).Error; err != nil {
|
||||||
|
t.Fatalf("seed session: %v", err)
|
||||||
|
}
|
||||||
|
if err := db.Create(&model.BaishunRoomState{
|
||||||
|
ID: 2,
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
RoomID: "9001",
|
||||||
|
HostUserID: 1001,
|
||||||
|
CurrentGameID: "bs_1146",
|
||||||
|
CurrentVendorGameID: intPtr(1146),
|
||||||
|
CurrentGameName: "LordOfOlympus",
|
||||||
|
CurrentGameCover: "https://cdn.example.com/game.png",
|
||||||
|
GameSessionID: "session-1",
|
||||||
|
State: baishunRoomStatePlaying,
|
||||||
|
CreateTime: time.Now(),
|
||||||
|
UpdateTime: time.Now(),
|
||||||
|
}).Error; err != nil {
|
||||||
|
t.Fatalf("seed room state: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
startResp := service.HandleReport(context.Background(), BaishunReportRequest{
|
||||||
|
ReportType: baishunReportTypeStart,
|
||||||
|
UserID: "1001",
|
||||||
|
SSToken: token,
|
||||||
|
ReportMsg: map[string]any{
|
||||||
|
"game_id": 1146,
|
||||||
|
"room_id": "9001",
|
||||||
|
"game_round_id": "round-1",
|
||||||
|
"players": []any{
|
||||||
|
map[string]any{"user_id": "1001", "is_ai": 0, "bet": 100},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, `{"report_type":"game_start"}`)
|
||||||
|
if startResp.Code != 0 {
|
||||||
|
t.Fatalf("start response = %+v", startResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
settleResp := service.HandleReport(context.Background(), BaishunReportRequest{
|
||||||
|
ReportType: baishunReportTypeSettle,
|
||||||
|
UserID: "1001",
|
||||||
|
SSToken: token,
|
||||||
|
ReportMsg: map[string]any{
|
||||||
|
"game_id": 1146,
|
||||||
|
"room_id": "9001",
|
||||||
|
"game_round_id": "round-1",
|
||||||
|
"players": []any{
|
||||||
|
map[string]any{"user_id": "1001", "is_ai": 0, "reward": 1000, "rank": 1, "score": 88},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, `{"report_type":"game_settle"}`)
|
||||||
|
if settleResp.Code != 0 {
|
||||||
|
t.Fatalf("settle response = %+v", settleResp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(broadcaster.requests) != 1 {
|
||||||
|
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
||||||
|
}
|
||||||
|
req := broadcaster.requests[0]
|
||||||
|
if req.Type != "GAME_BAISHUN_WIN" {
|
||||||
|
t.Fatalf("type = %q, want GAME_BAISHUN_WIN", req.Type)
|
||||||
|
}
|
||||||
|
if req.Data["userId"] != "1001" || req.Data["roomId"] != "9001" ||
|
||||||
|
req.Data["multiple"] != int64(10) || req.Data["winAmount"] != int64(1000) ||
|
||||||
|
req.Data["currencyDiff"] != int64(1000) ||
|
||||||
|
req.Data["gameUrl"] != "https://cdn.example.com/game.png" {
|
||||||
|
t.Fatalf("payload = %+v", req.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeBaishunRegionBroadcaster struct {
|
||||||
|
requests []regionimgroup.RegionBroadcastRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeBaishunRegionBroadcaster) SendRegionBroadcast(_ context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
||||||
|
f.requests = append(f.requests, req)
|
||||||
|
return ®ionimgroup.RegionBroadcastResponse{Type: req.Type}, nil
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package baishun
|
|||||||
import (
|
import (
|
||||||
"chatapp3-golang/internal/config"
|
"chatapp3-golang/internal/config"
|
||||||
"chatapp3-golang/internal/integration"
|
"chatapp3-golang/internal/integration"
|
||||||
|
"chatapp3-golang/internal/service/highwin"
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -61,11 +62,12 @@ type baishunPorts struct {
|
|||||||
|
|
||||||
// BaishunService 负责百顺目录同步、游戏启动、房间状态以及回调处理。
|
// BaishunService 负责百顺目录同步、游戏启动、房间状态以及回调处理。
|
||||||
type BaishunService struct {
|
type BaishunService struct {
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
repo baishunPorts
|
repo baishunPorts
|
||||||
java baishunGateway
|
java baishunGateway
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
taskReporter taskEventReporter
|
taskReporter taskEventReporter
|
||||||
|
highWinBroadcaster highwin.RegionBroadcaster
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBaishunService 创建百顺接入服务实例。
|
// NewBaishunService 创建百顺接入服务实例。
|
||||||
@ -84,6 +86,10 @@ func (s *BaishunService) SetTaskEventReporter(reporter taskEventReporter) {
|
|||||||
s.taskReporter = reporter
|
s.taskReporter = reporter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *BaishunService) SetHighWinBroadcaster(broadcaster highwin.RegionBroadcaster) {
|
||||||
|
s.highWinBroadcaster = broadcaster
|
||||||
|
}
|
||||||
|
|
||||||
// RoomGameListItem 是房间游戏列表中的单个游戏项。
|
// RoomGameListItem 是房间游戏列表中的单个游戏项。
|
||||||
type RoomGameListItem struct {
|
type RoomGameListItem struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
@ -418,6 +424,13 @@ type BaishunBalanceInfoRequest struct {
|
|||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BaishunReportRequest struct {
|
||||||
|
ReportType string `json:"report_type"`
|
||||||
|
ReportMsg map[string]any `json:"report_msg"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
SSToken string `json:"ss_token"`
|
||||||
|
}
|
||||||
|
|
||||||
// baishunStandardResponse 是百顺标准回包结构。
|
// baishunStandardResponse 是百顺标准回包结构。
|
||||||
type baishunStandardResponse struct {
|
type baishunStandardResponse struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
|
|||||||
148
internal/service/highwin/broadcast.go
Normal file
148
internal/service/highwin/broadcast.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package highwin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/service/regionimgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ThresholdMultiple = 10
|
||||||
|
|
||||||
|
MessageTypeLuckyGift = "GAME_LUCKY_GIFT"
|
||||||
|
MessageTypeBaishunWin = "GAME_BAISHUN_WIN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegionBroadcaster interface {
|
||||||
|
SendRegionBroadcast(ctx context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BroadcastEvent struct {
|
||||||
|
SysOrigin string
|
||||||
|
Type string
|
||||||
|
UserID int64
|
||||||
|
RoomID string
|
||||||
|
Multiple float64
|
||||||
|
Amount int64
|
||||||
|
Data map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func IntegralMultiple(amount, base int64) int64 {
|
||||||
|
if amount <= 0 || base <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return amount / base
|
||||||
|
}
|
||||||
|
|
||||||
|
func Multiple(amount, base int64) float64 {
|
||||||
|
if amount <= 0 || base <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return float64(amount) / float64(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShouldBroadcast(multiple float64, amount int64) bool {
|
||||||
|
return amount > 0 && multiple >= ThresholdMultiple
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendRegionBroadcast(ctx context.Context, broadcaster RegionBroadcaster, event BroadcastEvent) error {
|
||||||
|
if broadcaster == nil || !ShouldBroadcast(event.Multiple, event.Amount) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
messageType := strings.TrimSpace(event.Type)
|
||||||
|
if messageType == "" {
|
||||||
|
return fmt.Errorf("high win broadcast type is required")
|
||||||
|
}
|
||||||
|
if event.UserID <= 0 {
|
||||||
|
return fmt.Errorf("high win broadcast user id is required")
|
||||||
|
}
|
||||||
|
roomID := strings.TrimSpace(event.RoomID)
|
||||||
|
if roomID == "" {
|
||||||
|
return fmt.Errorf("high win broadcast room id is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := cloneData(event.Data)
|
||||||
|
userIDText := strconv.FormatInt(event.UserID, 10)
|
||||||
|
setRequired(data, "userId", userIDText)
|
||||||
|
setRequired(data, "sendUserId", userIDText)
|
||||||
|
setRequired(data, "senderUserId", userIDText)
|
||||||
|
setRequired(data, "roomId", roomID)
|
||||||
|
setRequired(data, "multiple", normalizeMultiple(event.Multiple))
|
||||||
|
setRequired(data, "winMultiple", normalizeMultiple(event.Multiple))
|
||||||
|
setRequired(data, "winAmount", event.Amount)
|
||||||
|
setRequired(data, "awardAmount", event.Amount)
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneData(data map[string]any) map[string]any {
|
||||||
|
clone := map[string]any{}
|
||||||
|
for key, value := range data {
|
||||||
|
clone[key] = value
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRequired(data map[string]any, key string, value any) {
|
||||||
|
data[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeMultiple(value float64) any {
|
||||||
|
if value <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if math.Trunc(value) == value {
|
||||||
|
return int64(value)
|
||||||
|
}
|
||||||
|
return math.Round(value*100) / 100
|
||||||
|
}
|
||||||
|
|
||||||
|
func highWinMessage(data map[string]any, messageType, fallbackUser string, multiple float64, amount int64) string {
|
||||||
|
user := firstTextValue(data, "userNickname", "nickname", "account", "actualAccount", "sendUserId", "senderUserId", "userId")
|
||||||
|
if user == "" {
|
||||||
|
user = fallbackUser
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s play %s get x %s win %d coins!!!", user, highWinPlayTarget(messageType), formatMultipleText(multiple), amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func highWinPlayTarget(messageType string) string {
|
||||||
|
switch strings.TrimSpace(messageType) {
|
||||||
|
case MessageTypeLuckyGift:
|
||||||
|
return "lucky_gift"
|
||||||
|
case MessageTypeBaishunWin:
|
||||||
|
return "game"
|
||||||
|
default:
|
||||||
|
return "game"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstTextValue(data map[string]any, keys ...string) string {
|
||||||
|
for _, key := range keys {
|
||||||
|
if value := strings.TrimSpace(fmt.Sprint(data[key])); value != "" && value != "<nil>" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatMultipleText(value float64) string {
|
||||||
|
normalized := normalizeMultiple(value)
|
||||||
|
switch typed := normalized.(type) {
|
||||||
|
case int64:
|
||||||
|
return strconv.FormatInt(typed, 10)
|
||||||
|
case float64:
|
||||||
|
return strconv.FormatFloat(typed, 'f', -1, 64)
|
||||||
|
default:
|
||||||
|
return strings.TrimSpace(fmt.Sprint(typed))
|
||||||
|
}
|
||||||
|
}
|
||||||
71
internal/service/highwin/broadcast_test.go
Normal file
71
internal/service/highwin/broadcast_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package highwin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/service/regionimgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSendRegionBroadcastBuildsRequiredPayload(t *testing.T) {
|
||||||
|
broadcaster := &fakeRegionBroadcaster{}
|
||||||
|
|
||||||
|
err := SendRegionBroadcast(context.Background(), broadcaster, BroadcastEvent{
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
Type: MessageTypeBaishunWin,
|
||||||
|
UserID: 1001,
|
||||||
|
RoomID: "9001",
|
||||||
|
Multiple: 10.5,
|
||||||
|
Amount: 2100,
|
||||||
|
Data: map[string]any{
|
||||||
|
"gameId": 1146,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRegionBroadcast() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(broadcaster.requests) != 1 {
|
||||||
|
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
||||||
|
}
|
||||||
|
req := broadcaster.requests[0]
|
||||||
|
if req.Type != MessageTypeBaishunWin {
|
||||||
|
t.Fatalf("type = %q, want %q", req.Type, MessageTypeBaishunWin)
|
||||||
|
}
|
||||||
|
if req.Data["userId"] != "1001" || req.Data["roomId"] != "9001" ||
|
||||||
|
req.Data["winAmount"] != int64(2100) || req.Data["multiple"] != 10.5 ||
|
||||||
|
req.Data["msg"] != "1001 play game get x 10.5 win 2100 coins!!!" {
|
||||||
|
t.Fatalf("payload = %+v", req.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendRegionBroadcastSkipsBelowThreshold(t *testing.T) {
|
||||||
|
broadcaster := &fakeRegionBroadcaster{}
|
||||||
|
|
||||||
|
err := SendRegionBroadcast(context.Background(), broadcaster, BroadcastEvent{
|
||||||
|
Type: MessageTypeLuckyGift,
|
||||||
|
UserID: 1001,
|
||||||
|
RoomID: "9001",
|
||||||
|
Multiple: 9,
|
||||||
|
Amount: 900,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRegionBroadcast() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(broadcaster.requests) != 0 {
|
||||||
|
t.Fatalf("requests = %d, want 0", len(broadcaster.requests))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeRegionBroadcaster struct {
|
||||||
|
requests []regionimgroup.RegionBroadcastRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeRegionBroadcaster) SendRegionBroadcast(_ context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
||||||
|
f.requests = append(f.requests, req)
|
||||||
|
return ®ionimgroup.RegionBroadcastResponse{
|
||||||
|
SysOrigin: req.SysOrigin,
|
||||||
|
RegionCode: "AR",
|
||||||
|
GroupID: "@region-ar",
|
||||||
|
Type: req.Type,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@ -140,6 +140,8 @@ func (s *LuckyGiftService) Draw(ctx context.Context, req LuckyGiftDrawRequest) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.publishHighWinRegionBroadcasts(ctx, req, results, balanceAfter)
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
internal/service/luckygift/high_win.go
Normal file
51
internal/service/luckygift/high_win.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package luckygift
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/service/highwin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *LuckyGiftService) publishHighWinRegionBroadcasts(ctx context.Context, req LuckyGiftDrawRequest, results []LuckyGiftDrawResult, balanceAfter int64) {
|
||||||
|
if s == nil || s.highWinBroadcaster == nil || req.GiftPrice <= 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, result := range results {
|
||||||
|
if result.RewardNum <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
multiple := highwin.IntegralMultiple(result.RewardNum, req.GiftPrice)
|
||||||
|
if !highwin.ShouldBroadcast(float64(multiple), result.RewardNum) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data := map[string]any{
|
||||||
|
"businessId": req.BusinessID,
|
||||||
|
"orderId": result.ID,
|
||||||
|
"providerOrderId": result.OrderID,
|
||||||
|
"acceptUserId": strconv.FormatInt(result.AcceptUserID, 10),
|
||||||
|
"giftId": strconv.FormatInt(req.GiftID, 10),
|
||||||
|
"giftCandy": req.GiftPrice,
|
||||||
|
"giftQuantity": req.GiftNum,
|
||||||
|
"multipleType": luckyGiftMultipleType(multiple),
|
||||||
|
"balance": balanceAfter,
|
||||||
|
"globalNews": true,
|
||||||
|
}
|
||||||
|
if err := highwin.SendRegionBroadcast(ctx, s.highWinBroadcaster, highwin.BroadcastEvent{
|
||||||
|
SysOrigin: req.SysOrigin,
|
||||||
|
Type: highwin.MessageTypeLuckyGift,
|
||||||
|
UserID: req.SendUserID,
|
||||||
|
RoomID: strconv.FormatInt(req.RoomID, 10),
|
||||||
|
Multiple: float64(multiple),
|
||||||
|
Amount: result.RewardNum,
|
||||||
|
Data: data,
|
||||||
|
}); err != nil {
|
||||||
|
log.Printf("send lucky gift high win region broadcast failed businessId=%s orderId=%s: %v", req.BusinessID, result.ID, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func luckyGiftMultipleType(_ int64) string {
|
||||||
|
return "WIN"
|
||||||
|
}
|
||||||
48
internal/service/luckygift/high_win_test.go
Normal file
48
internal/service/luckygift/high_win_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package luckygift
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"chatapp3-golang/internal/service/regionimgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPublishHighWinRegionBroadcastsSendsOnlyTenTimesAndAbove(t *testing.T) {
|
||||||
|
broadcaster := &fakeLuckyGiftRegionBroadcaster{}
|
||||||
|
service := &LuckyGiftService{highWinBroadcaster: broadcaster}
|
||||||
|
|
||||||
|
service.publishHighWinRegionBroadcasts(context.Background(), LuckyGiftDrawRequest{
|
||||||
|
BusinessID: "biz-1",
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
RoomID: 9001,
|
||||||
|
SendUserID: 1001,
|
||||||
|
GiftID: 5001,
|
||||||
|
GiftPrice: 100,
|
||||||
|
GiftNum: 1,
|
||||||
|
}, []LuckyGiftDrawResult{
|
||||||
|
{ID: "low", AcceptUserID: 2001, RewardNum: 900},
|
||||||
|
{ID: "high", OrderID: "LG_high", AcceptUserID: 2002, RewardNum: 1000},
|
||||||
|
}, 90000)
|
||||||
|
|
||||||
|
if len(broadcaster.requests) != 1 {
|
||||||
|
t.Fatalf("requests = %d, want 1", len(broadcaster.requests))
|
||||||
|
}
|
||||||
|
req := broadcaster.requests[0]
|
||||||
|
if req.Type != "GAME_LUCKY_GIFT" {
|
||||||
|
t.Fatalf("type = %q, want GAME_LUCKY_GIFT", req.Type)
|
||||||
|
}
|
||||||
|
if req.Data["sendUserId"] != "1001" || req.Data["roomId"] != "9001" ||
|
||||||
|
req.Data["multiple"] != int64(10) || req.Data["awardAmount"] != int64(1000) ||
|
||||||
|
req.Data["acceptUserId"] != "2002" {
|
||||||
|
t.Fatalf("payload = %+v", req.Data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeLuckyGiftRegionBroadcaster struct {
|
||||||
|
requests []regionimgroup.RegionBroadcastRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeLuckyGiftRegionBroadcaster) SendRegionBroadcast(_ context.Context, req regionimgroup.RegionBroadcastRequest) (*regionimgroup.RegionBroadcastResponse, error) {
|
||||||
|
f.requests = append(f.requests, req)
|
||||||
|
return ®ionimgroup.RegionBroadcastResponse{Type: req.Type}, nil
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package luckygift
|
|||||||
import (
|
import (
|
||||||
"chatapp3-golang/internal/config"
|
"chatapp3-golang/internal/config"
|
||||||
"chatapp3-golang/internal/integration"
|
"chatapp3-golang/internal/integration"
|
||||||
|
"chatapp3-golang/internal/service/highwin"
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@ -39,10 +40,11 @@ type luckyGiftPorts struct {
|
|||||||
|
|
||||||
// LuckyGiftService 负责幸运礼物抽奖、三方调用和钱包返奖。
|
// LuckyGiftService 负责幸运礼物抽奖、三方调用和钱包返奖。
|
||||||
type LuckyGiftService struct {
|
type LuckyGiftService struct {
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
repo luckyGiftPorts
|
repo luckyGiftPorts
|
||||||
java luckyGiftGateway
|
java luckyGiftGateway
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
|
highWinBroadcaster highwin.RegionBroadcaster
|
||||||
}
|
}
|
||||||
|
|
||||||
// LuckyGiftDrawRequest 是幸运礼物抽奖入参。
|
// LuckyGiftDrawRequest 是幸运礼物抽奖入参。
|
||||||
@ -60,6 +62,10 @@ type LuckyGiftDrawRequest struct {
|
|||||||
Orders []LuckyGiftDrawOrderInput `json:"orders"`
|
Orders []LuckyGiftDrawOrderInput `json:"orders"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LuckyGiftService) SetHighWinBroadcaster(broadcaster highwin.RegionBroadcaster) {
|
||||||
|
s.highWinBroadcaster = broadcaster
|
||||||
|
}
|
||||||
|
|
||||||
// LuckyGiftDrawOrderInput 表示单个收礼人的抽奖子单。
|
// LuckyGiftDrawOrderInput 表示单个收礼人的抽奖子单。
|
||||||
type LuckyGiftDrawOrderInput struct {
|
type LuckyGiftDrawOrderInput struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|||||||
@ -66,6 +66,7 @@ func (s *Service) SendRegionBroadcast(ctx context.Context, req RegionBroadcastRe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
refreshHighWinMessage(data, messageType)
|
||||||
|
|
||||||
body := map[string]any{
|
body := map[string]any{
|
||||||
"type": messageType,
|
"type": messageType,
|
||||||
@ -182,6 +183,41 @@ func firstInt64Value(values ...any) int64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refreshHighWinMessage(data map[string]any, messageType string) {
|
||||||
|
target := highWinPlayTarget(messageType)
|
||||||
|
if target == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user := firstTextValue(data["userNickname"], data["nickname"], data["account"], data["actualAccount"], data["userId"])
|
||||||
|
multiple := firstTextValue(data["multiple"], data["winMultiple"])
|
||||||
|
amount := firstTextValue(data["winAmount"], data["awardAmount"])
|
||||||
|
if user == "" || multiple == "" || amount == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data["msg"] = fmt.Sprintf("%s play %s get x %s win %s coins!!!", user, target, multiple, amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func highWinPlayTarget(messageType string) string {
|
||||||
|
switch strings.TrimSpace(messageType) {
|
||||||
|
case "GAME_LUCKY_GIFT":
|
||||||
|
return "lucky_gift"
|
||||||
|
case "GAME_BAISHUN_WIN":
|
||||||
|
return "game"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstTextValue(values ...any) string {
|
||||||
|
for _, value := range values {
|
||||||
|
text := strings.TrimSpace(fmt.Sprint(value))
|
||||||
|
if text != "" && text != "<nil>" {
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func firstInt64FromList(value any) int64 {
|
func firstInt64FromList(value any) int64 {
|
||||||
switch typed := value.(type) {
|
switch typed := value.(type) {
|
||||||
case []any:
|
case []any:
|
||||||
|
|||||||
@ -133,6 +133,52 @@ func TestSendRegionBroadcastResolvesRegionAndEnrichesGiftPayload(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSendRegionBroadcastRefreshesHighWinMessageWithProfile(t *testing.T) {
|
||||||
|
service, db := newRegionIMGroupTestService(t)
|
||||||
|
fakeIM := &fakeIMGateway{}
|
||||||
|
service.im = fakeIM
|
||||||
|
now := time.Now()
|
||||||
|
if err := db.Create(&model.VoiceRoomRegionIMGroup{
|
||||||
|
ID: 3010,
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
RegionCode: "AR",
|
||||||
|
RegionName: "Arab",
|
||||||
|
GroupID: "@region-ar",
|
||||||
|
GroupName: "region-ar",
|
||||||
|
Status: imGroupStatusActive,
|
||||||
|
CreateTime: now,
|
||||||
|
UpdateTime: now,
|
||||||
|
}).Error; err != nil {
|
||||||
|
t.Fatalf("seed im group: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := service.SendRegionBroadcast(context.Background(), RegionBroadcastRequest{
|
||||||
|
SysOrigin: "LIKEI",
|
||||||
|
Type: "GAME_BAISHUN_WIN",
|
||||||
|
Data: map[string]any{
|
||||||
|
"userId": "1001",
|
||||||
|
"roomId": "9001",
|
||||||
|
"multiple": 10,
|
||||||
|
"winAmount": 1000,
|
||||||
|
"msg": "1001 play game get x 10 win 1000 coins!!!",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("SendRegionBroadcast() error = %v", err)
|
||||||
|
}
|
||||||
|
body, ok := fakeIM.sent[0].body.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("sent body type = %T", fakeIM.sent[0].body)
|
||||||
|
}
|
||||||
|
data, ok := body["data"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("sent data type = %T", body["data"])
|
||||||
|
}
|
||||||
|
if data["msg"] != "sender play game get x 10 win 1000 coins!!!" {
|
||||||
|
t.Fatalf("msg = %v", data["msg"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSendRegionBroadcastPrefersJavaRegionResolver(t *testing.T) {
|
func TestSendRegionBroadcastPrefersJavaRegionResolver(t *testing.T) {
|
||||||
service, db := newRegionIMGroupTestService(t)
|
service, db := newRegionIMGroupTestService(t)
|
||||||
service.regionResolver = fakeRegionResolver{
|
service.regionResolver = fakeRegionResolver{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user