fix: preserve baishun provider response fields

This commit is contained in:
hy001 2026-04-22 20:39:41 +08:00
parent 6e379d62ac
commit 8f8ae40947
7 changed files with 384 additions and 27 deletions

View File

@ -23,7 +23,7 @@ func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, regi
writeError(c, err)
return
}
writeOK(c, stripProvidersFromRoomGameItems(resp))
writeOK(c, publicRoomGameItems(resp))
})
appGroup.GET("/room/list", func(c *gin.Context) {
resp, err := registry.ListRoomGames(c.Request.Context(), mustAuthUser(c), c.Query("roomId"), c.Query("category"))
@ -31,7 +31,7 @@ func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, regi
writeError(c, err)
return
}
writeOK(c, stripProvidersFromRoomGameListResponse(resp))
writeOK(c, publicRoomGameListResponse(resp))
})
appGroup.GET("/state", func(c *gin.Context) {
resp, err := registry.GetRoomState(c.Request.Context(), mustAuthUser(c), c.Query("roomId"))
@ -39,7 +39,7 @@ func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, regi
writeError(c, err)
return
}
writeOK(c, stripProviderFromRoomState(resp))
writeOK(c, publicRoomState(resp))
})
appGroup.POST("/launch", func(c *gin.Context) {
var req gameprovider.LaunchRequest
@ -52,7 +52,7 @@ func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, regi
writeError(c, err)
return
}
writeOK(c, stripProviderFromLaunch(resp))
writeOK(c, publicLaunch(resp))
})
appGroup.POST("/close", func(c *gin.Context) {
var req gameprovider.CloseRequest
@ -65,7 +65,7 @@ func registerGameProviderRoutes(engine *gin.Engine, javaClient authGateway, regi
writeError(c, err)
return
}
writeOK(c, stripProviderFromRoomState(resp))
writeOK(c, publicRoomState(resp))
})
providerGroup := engine.Group("/app/game/providers")
@ -154,42 +154,138 @@ func resolveGameProvider(c *gin.Context, registry *gameprovider.Registry) (gamep
return provider, true
}
func stripProvidersFromRoomGameItems(items []gameprovider.RoomGameListItem) []gameprovider.RoomGameListItem {
type publicRoomGameItem struct {
ID int64 `json:"id"`
GameID string `json:"gameId"`
GameType string `json:"gameType,omitempty"`
Provider string `json:"provider,omitempty"`
VendorType string `json:"vendorType,omitempty"`
ProviderGameID string `json:"providerGameId,omitempty"`
VendorGameID string `json:"vendorGameId,omitempty"`
Name string `json:"name"`
Cover string `json:"cover"`
Category string `json:"category,omitempty"`
Sort int64 `json:"sort,omitempty"`
LaunchMode string `json:"launchMode,omitempty"`
FullScreen bool `json:"fullScreen"`
GameMode int `json:"gameMode,omitempty"`
SafeHeight int `json:"safeHeight,omitempty"`
Orientation int `json:"orientation,omitempty"`
PackageVersion string `json:"packageVersion,omitempty"`
Status string `json:"status,omitempty"`
LaunchParams map[string]any `json:"launchParams,omitempty"`
}
type publicRoomGameList struct {
Items []publicRoomGameItem `json:"items"`
}
type publicRoomStateResponse struct {
RoomID string `json:"roomId"`
State string `json:"state"`
Provider string `json:"provider,omitempty"`
VendorType string `json:"vendorType,omitempty"`
GameSessionID string `json:"gameSessionId,omitempty"`
CurrentGameID string `json:"currentGameId,omitempty"`
CurrentProviderGameID string `json:"currentProviderGameId,omitempty"`
CurrentVendorGameID string `json:"currentVendorGameId,omitempty"`
CurrentGameName string `json:"currentGameName,omitempty"`
CurrentGameCover string `json:"currentGameCover,omitempty"`
HostUserID int64 `json:"hostUserId,omitempty"`
}
type publicLaunchResponse struct {
ID int64 `json:"id,omitempty"`
GameSessionID string `json:"gameSessionId"`
Provider string `json:"provider,omitempty"`
VendorType string `json:"vendorType,omitempty"`
GameID string `json:"gameId"`
ProviderGameID string `json:"providerGameId,omitempty"`
VendorGameID string `json:"vendorGameId,omitempty"`
Entry gameprovider.LaunchEntry `json:"entry"`
LaunchConfig any `json:"launchConfig,omitempty"`
BridgeConfig any `json:"bridgeConfig,omitempty"`
RoomState publicRoomStateResponse `json:"roomState"`
}
func publicRoomGameItems(items []gameprovider.RoomGameListItem) []publicRoomGameItem {
if len(items) == 0 {
return []gameprovider.RoomGameListItem{}
return []publicRoomGameItem{}
}
result := make([]gameprovider.RoomGameListItem, 0, len(items))
result := make([]publicRoomGameItem, 0, len(items))
for _, item := range items {
item.Provider = ""
result = append(result, item)
result = append(result, publicRoomGameItem{
ID: item.ID,
GameID: item.GameID,
GameType: item.GameType,
Provider: item.Provider,
VendorType: item.Provider,
ProviderGameID: item.ProviderGameID,
VendorGameID: item.ProviderGameID,
Name: item.Name,
Cover: item.Cover,
Category: item.Category,
Sort: item.Sort,
LaunchMode: item.LaunchMode,
FullScreen: item.FullScreen,
GameMode: item.GameMode,
SafeHeight: item.SafeHeight,
Orientation: item.Orientation,
PackageVersion: item.PackageVersion,
Status: item.Status,
LaunchParams: item.LaunchParams,
})
}
return result
}
func stripProvidersFromRoomGameListResponse(resp *gameprovider.RoomGameListResponse) *gameprovider.RoomGameListResponse {
func publicRoomGameListResponse(resp *gameprovider.RoomGameListResponse) *publicRoomGameList {
if resp == nil {
return &gameprovider.RoomGameListResponse{Items: []gameprovider.RoomGameListItem{}}
return &publicRoomGameList{Items: []publicRoomGameItem{}}
}
return &gameprovider.RoomGameListResponse{
Items: stripProvidersFromRoomGameItems(resp.Items),
return &publicRoomGameList{
Items: publicRoomGameItems(resp.Items),
}
}
func stripProviderFromRoomState(resp *gameprovider.RoomStateResponse) *gameprovider.RoomStateResponse {
func publicRoomState(resp *gameprovider.RoomStateResponse) *publicRoomStateResponse {
if resp == nil {
return nil
}
copied := *resp
copied.Provider = ""
return &copied
return &publicRoomStateResponse{
RoomID: resp.RoomID,
State: resp.State,
Provider: resp.Provider,
VendorType: resp.Provider,
GameSessionID: resp.GameSessionID,
CurrentGameID: resp.CurrentGameID,
CurrentProviderGameID: resp.CurrentProviderGameID,
CurrentVendorGameID: resp.CurrentProviderGameID,
CurrentGameName: resp.CurrentGameName,
CurrentGameCover: resp.CurrentGameCover,
HostUserID: resp.HostUserID,
}
}
func stripProviderFromLaunch(resp *gameprovider.LaunchResponse) *gameprovider.LaunchResponse {
func publicLaunch(resp *gameprovider.LaunchResponse) *publicLaunchResponse {
if resp == nil {
return nil
}
copied := *resp
copied.Provider = ""
copied.RoomState = *stripProviderFromRoomState(&resp.RoomState)
return &copied
roomState := publicRoomState(&resp.RoomState)
if roomState == nil {
roomState = &publicRoomStateResponse{}
}
return &publicLaunchResponse{
ID: resp.ID,
GameSessionID: resp.GameSessionID,
Provider: resp.Provider,
VendorType: resp.Provider,
GameID: resp.GameID,
ProviderGameID: resp.ProviderGameID,
VendorGameID: resp.ProviderGameID,
Entry: resp.Entry,
LaunchConfig: resp.LaunchConfig,
BridgeConfig: resp.LaunchConfig,
RoomState: *roomState,
}
}

View File

@ -0,0 +1,80 @@
package router
import (
"testing"
"chatapp3-golang/internal/service/gameprovider"
)
func TestPublicRoomGameListResponseKeepsLegacyProviderFields(t *testing.T) {
resp := publicRoomGameListResponse(&gameprovider.RoomGameListResponse{
Items: []gameprovider.RoomGameListItem{
{
ID: 9527,
GameID: "bs_1046",
GameType: "BAISHUN",
Provider: "BAISHUN",
ProviderGameID: "1046",
Name: "Lucky77Pro",
},
},
})
if len(resp.Items) != 1 {
t.Fatalf("items = %d, want 1", len(resp.Items))
}
item := resp.Items[0]
if item.Provider != "BAISHUN" {
t.Fatalf("provider = %q, want BAISHUN", item.Provider)
}
if item.VendorType != "BAISHUN" {
t.Fatalf("vendorType = %q, want BAISHUN", item.VendorType)
}
if item.ProviderGameID != "1046" {
t.Fatalf("providerGameId = %q, want 1046", item.ProviderGameID)
}
if item.VendorGameID != "1046" {
t.Fatalf("vendorGameId = %q, want 1046", item.VendorGameID)
}
}
func TestPublicLaunchKeepsLegacyBridgeAndProviderFields(t *testing.T) {
launchConfig := map[string]any{"code": "launch-code"}
resp := publicLaunch(&gameprovider.LaunchResponse{
ID: 9527,
GameSessionID: "bs_room_1",
Provider: "BAISHUN",
GameID: "bs_1046",
ProviderGameID: "1046",
LaunchConfig: launchConfig,
RoomState: gameprovider.RoomStateResponse{
RoomID: "room-1",
State: "PLAYING",
Provider: "BAISHUN",
CurrentProviderGameID: "1046",
},
})
if resp.Provider != "BAISHUN" {
t.Fatalf("provider = %q, want BAISHUN", resp.Provider)
}
if resp.VendorType != "BAISHUN" {
t.Fatalf("vendorType = %q, want BAISHUN", resp.VendorType)
}
if resp.VendorGameID != "1046" {
t.Fatalf("vendorGameId = %q, want 1046", resp.VendorGameID)
}
bridgeConfig, ok := resp.BridgeConfig.(map[string]any)
if !ok {
t.Fatalf("bridgeConfig type = %T, want map[string]any", resp.BridgeConfig)
}
if got := bridgeConfig["code"]; got != "launch-code" {
t.Fatalf("bridgeConfig[code] = %v, want launch-code", got)
}
if resp.RoomState.Provider != "BAISHUN" {
t.Fatalf("roomState.provider = %q, want BAISHUN", resp.RoomState.Provider)
}
if resp.RoomState.CurrentVendorGameID != "1046" {
t.Fatalf("roomState.currentVendorGameId = %q, want 1046", resp.RoomState.CurrentVendorGameID)
}
}

View File

@ -202,6 +202,7 @@ func (s *BaishunService) SaveAdminGame(ctx context.Context, req SaveAdminBaishun
tx.Rollback()
return nil, err
}
showcase := resolveAdminShowcase(req.Showcase, cfg.Showcase, cfg.ID == 0)
cfgValues := map[string]any{
"sys_origin": sysOrigin,
@ -212,7 +213,7 @@ func (s *BaishunService) SaveAdminGame(ctx context.Context, req SaveAdminBaishun
"game_code": gameCode,
"cover": cover,
"amounts": strings.TrimSpace(req.Amounts),
"is_showcase": req.Showcase,
"is_showcase": showcase,
"sort": req.Sort,
"height": req.Height,
"width": req.Width,
@ -238,7 +239,7 @@ func (s *BaishunService) SaveAdminGame(ctx context.Context, req SaveAdminBaishun
GameCode: gameCode,
Cover: cover,
Amounts: strings.TrimSpace(req.Amounts),
Showcase: req.Showcase,
Showcase: showcase,
Sort: req.Sort,
Height: req.Height,
Width: req.Width,
@ -585,7 +586,7 @@ func (s *BaishunService) importCatalogGamesTx(tx *gorm.DB, sysOrigin string, fil
Category: "OTHER",
GameCode: vendorGameID,
Cover: defaultIfBlank(catalog.Cover, catalog.PreviewURL),
Showcase: false,
Showcase: true,
Sort: int64(catalog.VendorGameID),
FullScreen: true,
ClientOrigin: "COMMON",
@ -687,6 +688,16 @@ func normalizePageLimit(limit int) int {
return limit
}
func resolveAdminShowcase(showcase *bool, current bool, isCreate bool) bool {
if showcase != nil {
return *showcase
}
if isCreate {
return true
}
return current
}
func defaultAdminGSP(catalogGSP string, fallback int) string {
if strings.TrimSpace(catalogGSP) != "" {
return strings.TrimSpace(catalogGSP)

View File

@ -0,0 +1,24 @@
package baishun
import "testing"
func TestResolveAdminShowcase(t *testing.T) {
t.Run("create defaults to enabled", func(t *testing.T) {
if got := resolveAdminShowcase(nil, false, true); !got {
t.Fatalf("resolveAdminShowcase(nil, false, true) = %v, want true", got)
}
})
t.Run("update keeps current when omitted", func(t *testing.T) {
if got := resolveAdminShowcase(nil, false, false); got {
t.Fatalf("resolveAdminShowcase(nil, false, false) = %v, want false", got)
}
})
t.Run("explicit false remains false", func(t *testing.T) {
showcase := false
if got := resolveAdminShowcase(&showcase, true, true); got {
t.Fatalf("resolveAdminShowcase(false, true, true) = %v, want false", got)
}
})
}

View File

@ -0,0 +1,118 @@
package baishun
import (
"context"
"testing"
"chatapp3-golang/internal/config"
"chatapp3-golang/internal/integration"
"chatapp3-golang/internal/model"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type stubGateway struct{}
func (stubGateway) GetUserProfile(context.Context, int64) (integration.UserProfile, error) {
return integration.UserProfile{}, nil
}
func (stubGateway) MapGoldBalance(context.Context, []int64) (map[int64]int64, error) {
return map[int64]int64{}, nil
}
func (stubGateway) GetUserLanguage(context.Context, int64) (string, error) {
return "en", nil
}
func (stubGateway) ExistsGoldEvent(context.Context, string) (bool, error) {
return false, nil
}
func (stubGateway) ChangeGoldBalance(context.Context, integration.GoldReceiptCommand) error {
return nil
}
func newTestBaishunService(t *testing.T) (*BaishunService, *gorm.DB) {
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
if err := db.AutoMigrate(
&model.SysGameListConfig{},
&model.SysGameListVendorExt{},
&model.BaishunProviderConfig{},
&model.BaishunGameCatalog{},
&model.BaishunLaunchSession{},
&model.BaishunRoomState{},
); err != nil {
t.Fatalf("auto migrate: %v", err)
}
service := NewBaishunService(config.Config{
Baishun: config.BaishunConfig{
PlatformBaseURL: "https://mesh-channels-online.jieyou.shop",
AppID: 7485318192,
AppName: "yumiparty_app",
AppChannel: "yumiparty",
AppKey: "test-app-key",
GSP: 111,
LaunchCodeTTLSeconds: 300,
SSTokenTTLSeconds: 86400,
},
}, db, nil, stubGateway{})
return service, db
}
func TestLaunchGameFallsBackToCatalogWhenConfigMissing(t *testing.T) {
service, db := newTestBaishunService(t)
if err := db.Create(&model.BaishunGameCatalog{
ID: 1,
SysOrigin: "LIKEI",
InternalGameID: "bs_1146",
VendorGameID: 1146,
Name: "LordOfOlympus",
Cover: "https://cdn.example.com/cover.png",
PreviewURL: "https://cdn.example.com/preview.png",
DownloadURL: "https://cdn.example.com/game/index.html",
PackageVersion: "1.0.0",
SafeHeight: 24,
GSP: "222",
Status: baishunCatalogEnabled,
}).Error; err != nil {
t.Fatalf("create catalog: %v", err)
}
resp, err := service.LaunchGame(context.Background(), AuthUser{
UserID: 1234567,
SysOrigin: "LIKEI",
}, BaishunLaunchAppRequest{
RoomID: "room-1",
GameID: "bs_1146",
SceneMode: 1,
ClientOrigin: "COMMON",
}, "127.0.0.1")
if err != nil {
t.Fatalf("LaunchGame() error = %v", err)
}
if resp.GameID != "bs_1146" {
t.Fatalf("gameId = %q, want bs_1146", resp.GameID)
}
if resp.VendorGameID != 1146 {
t.Fatalf("vendorGameId = %d, want 1146", resp.VendorGameID)
}
if resp.Entry.EntryURL != "https://cdn.example.com/game/index.html" {
t.Fatalf("entryUrl = %q, want catalog download url", resp.Entry.EntryURL)
}
if resp.BridgeConfig.GSP != 222 {
t.Fatalf("gsp = %d, want 222", resp.BridgeConfig.GSP)
}
if resp.RoomState.State != baishunRoomStatePlaying {
t.Fatalf("roomState.state = %q, want %q", resp.RoomState.State, baishunRoomStatePlaying)
}
}

View File

@ -3,6 +3,7 @@ package baishun
import (
"chatapp3-golang/internal/model"
"context"
"fmt"
"net/http"
"strconv"
"strings"
@ -155,6 +156,33 @@ func (s *BaishunService) findGameRow(ctx context.Context, sysOrigin, gameID stri
if strings.TrimSpace(row.InternalGameID) != "" {
return row, nil
}
var catalog model.BaishunGameCatalog
if err := s.repo.DB.WithContext(ctx).
Where("sys_origin = ? AND internal_game_id = ?", sysOrigin, gameID).
First(&catalog).Error; err == nil {
return gameListRow{
SysOrigin: sysOrigin,
InternalGameID: defaultIfBlank(catalog.InternalGameID, fmt.Sprintf("bs_%d", catalog.VendorGameID)),
Name: catalog.Name,
Cover: catalog.Cover,
VendorType: baishunVendorType,
VendorGameID: strconv.Itoa(catalog.VendorGameID),
LaunchMode: baishunLaunchModeRemote,
ExtGSP: catalog.GSP,
CatalogCover: catalog.Cover,
CatalogPreviewURL: catalog.PreviewURL,
CatalogDownloadURL: catalog.DownloadURL,
CatalogPackageVersion: catalog.PackageVersion,
CatalogSafeHeight: catalog.SafeHeight,
CatalogOrientation: catalog.Orientation,
CatalogStatus: catalog.Status,
GameModeRaw: formatAdminGameModeRaw(baishunFixedGameMode),
}, nil
} else if err != gorm.ErrRecordNotFound {
return gameListRow{}, err
}
return gameListRow{}, NewAppError(http.StatusNotFound, "game_not_found", "room game config not found")
}

View File

@ -239,7 +239,7 @@ type SaveAdminBaishunGameRequest struct {
GameCode string `json:"gameCode"`
Cover string `json:"cover"`
Amounts string `json:"amounts"`
Showcase bool `json:"showcase"`
Showcase *bool `json:"showcase"`
Sort int64 `json:"sort"`
Height float64 `json:"height"`
Width float64 `json:"width"`