feat: update baishun game mode rollout

This commit is contained in:
hy001 2026-04-21 20:21:28 +08:00
parent df9a779b62
commit 0701bd0795
8 changed files with 939 additions and 23 deletions

View File

@ -4,20 +4,23 @@ import "time"
// SysGameListConfig 是系统游戏列表主表,描述平台可展示的游戏。
type SysGameListConfig struct {
ID int64 `gorm:"column:id;primaryKey"` // 主键
SysOrigin string `gorm:"column:sys_origin;size:32"` // 系统标识
GameOrigin string `gorm:"column:game_origin;size:32"` // 游戏来源
GameID string `gorm:"column:game_id;size:64"` // 内部游戏 ID
Name string `gorm:"column:name;size:128"` // 游戏名称
Category string `gorm:"column:category;size:64"` // 分类
GameCode string `gorm:"column:game_code;size:512"` // 游戏码或入口标识
Cover string `gorm:"column:cover;size:1024"` // 封面图
Showcase bool `gorm:"column:is_showcase"` // 是否首页推荐
Sort int64 `gorm:"column:sort"` // 排序值
FullScreen bool `gorm:"column:full_screen"` // 是否全屏展示
ClientOrigin string `gorm:"column:client_origin;size:32"` // 客户端来源
Regions string `gorm:"column:regions;size:255"` // 可投放地区
GameMode string `gorm:"column:game_mode;size:255"` // 游戏模式配置
ID int64 `gorm:"column:id;primaryKey"` // 主键
SysOrigin string `gorm:"column:sys_origin;size:32"` // 系统标识
GameOrigin string `gorm:"column:game_origin;size:32"` // 游戏来源
GameID string `gorm:"column:game_id;size:64"` // 内部游戏 ID
Name string `gorm:"column:name;size:128"` // 游戏名称
Category string `gorm:"column:category;size:64"` // 分类
GameCode string `gorm:"column:game_code;size:512"` // 游戏码或入口标识
Cover string `gorm:"column:cover;size:1024"` // 封面图
Amounts string `gorm:"column:amounts;size:512"` // 金额配置
Showcase bool `gorm:"column:is_showcase"` // 是否首页推荐
Sort int64 `gorm:"column:sort"` // 排序值
Height float64 `gorm:"column:height"` // 高度
Width float64 `gorm:"column:width"` // 宽度
FullScreen bool `gorm:"column:full_screen"` // 是否全屏展示
ClientOrigin string `gorm:"column:client_origin;size:32"` // 客户端来源
Regions string `gorm:"column:regions;size:255"` // 可投放地区
GameMode string `gorm:"column:game_mode;size:255"` // 游戏模式配置
}
// TableName 返回系统游戏列表表名。

View File

@ -109,6 +109,73 @@ func registerBaishunRoutes(
writeOK(c, resp)
})
consoleGroup := engine.Group("/operate/baishun-game")
consoleGroup.Use(consoleAuthMiddleware(javaClient))
consoleGroup.GET("/page", func(c *gin.Context) {
cursor, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("cursor", "1")))
limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "20")))
resp, err := baishunService.PageAdminGames(
c.Request.Context(),
c.Query("sysOrigin"),
c.Query("keyword"),
parseOptionalBool(c.Query("showcase")),
cursor,
limit,
)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
consoleGroup.POST("/save", func(c *gin.Context) {
var req baishun.SaveAdminBaishunGameRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := baishunService.SaveAdminGame(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
consoleGroup.DELETE("", func(c *gin.Context) {
id, _ := strconv.ParseInt(strings.TrimSpace(c.Query("id")), 10, 64)
if err := baishunService.DeleteAdminGame(c.Request.Context(), c.Query("sysOrigin"), id); err != nil {
writeError(c, err)
return
}
writeOK(c, gin.H{"deleted": true})
})
consoleGroup.POST("/import-catalog", func(c *gin.Context) {
var req baishun.SyncCatalogRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := baishunService.ImportCatalogGames(c.Request.Context(), req.SysOrigin, req.VendorGameIDs)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
consoleGroup.POST("/sync", func(c *gin.Context) {
var req baishun.SyncCatalogRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, baishun.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := baishunService.SyncAdminGames(c.Request.Context(), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
callbackGroup := engine.Group("/game/baishun")
callbackGroup.POST("/token", func(c *gin.Context) {
raw, payload := readRawPayload(c)
@ -268,3 +335,16 @@ func asInt64(value any) int64 {
return parsed
}
}
func parseOptionalBool(raw string) *bool {
switch strings.ToLower(strings.TrimSpace(raw)) {
case "0", "false":
value := false
return &value
case "1", "true":
value := true
return &value
default:
return nil
}
}

View File

@ -0,0 +1,720 @@
package baishun
import (
"chatapp3-golang/internal/model"
"chatapp3-golang/internal/utils"
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
const adminTimeLayout = "2006-01-02 15:04:05"
type adminGameRow struct {
ID int64
SysOrigin string
InternalGameID string
Name string
Category string
GameCode string
Cover string
Amounts string
Showcase bool
Sort int64
Height float64
Width float64
FullScreen bool
ClientOrigin string
Regions string
GameModeRaw string
CreateTime time.Time
UpdateTime time.Time
VendorGameID string
LaunchMode string
ExtPackageVersion string
ExtPreviewURL string
PackageURL string
ExtOrientation *int
ExtSafeHeight int
ExtGSP string
CurrencyIcon string
CatalogStatus string
CatalogPreviewURL string
CatalogDownloadURL string
CatalogPackage string
CatalogOrientation *int
CatalogSafeHeight int
CatalogGameModeJSON string
}
// PageAdminGames 返回后台百顺游戏管理分页数据。
func (s *BaishunService) PageAdminGames(
ctx context.Context,
sysOrigin string,
keyword string,
showcase *bool,
cursor int,
limit int,
) (*AdminBaishunGamePageResponse, error) {
sysOrigin = normalizeAdminSysOrigin(sysOrigin)
cursor = normalizePageCursor(cursor)
limit = normalizePageLimit(limit)
countQuery := s.buildAdminGameQuery(ctx, sysOrigin, keyword, showcase)
var total int64
if err := countQuery.Count(&total).Error; err != nil {
return nil, err
}
var rows []adminGameRow
listQuery := s.buildAdminGameQuery(ctx, sysOrigin, keyword, showcase)
if err := listQuery.
Select(`
cfg.id AS id,
cfg.sys_origin AS sys_origin,
cfg.game_id AS internal_game_id,
cfg.name AS name,
cfg.category AS category,
cfg.game_code AS game_code,
cfg.cover AS cover,
cfg.amounts AS amounts,
cfg.is_showcase AS showcase,
cfg.sort AS sort,
cfg.height AS height,
cfg.width AS width,
cfg.full_screen AS full_screen,
cfg.client_origin AS client_origin,
cfg.regions AS regions,
cfg.game_mode AS game_mode_raw,
cfg.create_time AS create_time,
cfg.update_time AS update_time,
ext.vendor_game_id AS vendor_game_id,
ext.launch_mode AS launch_mode,
ext.package_version AS ext_package_version,
ext.preview_url AS ext_preview_url,
ext.package_url AS package_url,
ext.orientation AS ext_orientation,
ext.safe_height AS ext_safe_height,
ext.gsp AS ext_gsp,
ext.currency_icon AS currency_icon,
cat.status AS catalog_status,
cat.preview_url AS catalog_preview_url,
cat.download_url AS catalog_download_url,
cat.package_version AS catalog_package,
cat.orientation AS catalog_orientation,
cat.safe_height AS catalog_safe_height,
cat.game_mode_json AS catalog_game_mode_json
`).
Order("cfg.is_showcase DESC").
Order("cfg.sort DESC").
Order("cfg.update_time DESC").
Limit(limit).
Offset((cursor - 1) * limit).
Scan(&rows).Error; err != nil {
return nil, err
}
items := make([]AdminBaishunGameItem, 0, len(rows))
for _, row := range rows {
items = append(items, row.toAdminItem())
}
return &AdminBaishunGamePageResponse{
Cursor: cursor,
Limit: limit,
Records: items,
Total: total,
}, nil
}
// SaveAdminGame 保存后台百顺游戏配置。
func (s *BaishunService) SaveAdminGame(ctx context.Context, req SaveAdminBaishunGameRequest) (*AdminBaishunGameItem, error) {
sysOrigin := normalizeAdminSysOrigin(req.SysOrigin)
catalog, err := s.findCatalogForAdmin(ctx, sysOrigin, req.VendorGameID, req.GameID)
if err != nil {
return nil, err
}
if req.VendorGameID <= 0 {
req.VendorGameID = catalog.VendorGameID
}
if req.VendorGameID <= 0 {
return nil, NewAppError(http.StatusBadRequest, "vendor_game_id_required", "vendorGameId is required")
}
gameID := strings.TrimSpace(req.GameID)
if gameID == "" {
gameID = defaultIfBlank(catalog.InternalGameID, fmt.Sprintf("bs_%d", req.VendorGameID))
}
if gameID == "" {
return nil, NewAppError(http.StatusBadRequest, "game_id_required", "gameId is required")
}
name := defaultIfBlank(strings.TrimSpace(req.Name), defaultIfBlank(catalog.Name, gameID))
if name == "" {
return nil, NewAppError(http.StatusBadRequest, "name_required", "name is required")
}
category := defaultIfBlank(strings.TrimSpace(req.Category), "OTHER")
cover := defaultIfBlank(strings.TrimSpace(req.Cover), defaultIfBlank(catalog.Cover, catalog.PreviewURL))
clientOrigin := defaultIfBlank(strings.TrimSpace(req.ClientOrigin), "COMMON")
gameCode := defaultIfBlank(strings.TrimSpace(req.GameCode), strconv.Itoa(req.VendorGameID))
launchMode := defaultIfBlank(strings.TrimSpace(req.LaunchMode), baishunLaunchModeRemote)
packageVersion := defaultIfBlank(strings.TrimSpace(req.PackageVersion), catalog.PackageVersion)
previewURL := defaultIfBlank(strings.TrimSpace(req.PreviewURL), catalog.PreviewURL)
packageURL := defaultIfBlank(strings.TrimSpace(req.PackageURL), catalog.DownloadURL)
gsp := defaultIfBlank(strings.TrimSpace(req.GSP), defaultAdminGSP(catalog.GSP, s.cfg.Baishun.GSP))
safeHeight := req.SafeHeight
if safeHeight <= 0 {
safeHeight = catalog.SafeHeight
}
orientation := req.Orientation
if orientation == nil && catalog.Orientation != nil {
value := *catalog.Orientation
orientation = &value
}
gameMode := baishunFixedGameMode
tx := s.repo.DB.WithContext(ctx).Begin()
if tx.Error != nil {
return nil, tx.Error
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
panic(r)
}
}()
cfg, err := s.findAdminConfigForSave(tx, sysOrigin, req.ID, gameID, req.VendorGameID)
if err != nil {
tx.Rollback()
return nil, err
}
cfgValues := map[string]any{
"sys_origin": sysOrigin,
"game_origin": baishunVendorType,
"game_id": gameID,
"name": name,
"category": category,
"game_code": gameCode,
"cover": cover,
"amounts": strings.TrimSpace(req.Amounts),
"is_showcase": req.Showcase,
"sort": req.Sort,
"height": req.Height,
"width": req.Width,
"full_screen": req.FullScreen,
"client_origin": clientOrigin,
"regions": strings.TrimSpace(req.Regions),
"game_mode": formatAdminGameModeRaw(gameMode),
}
if cfg.ID == 0 {
nextID, idErr := utils.NextID()
if idErr != nil {
tx.Rollback()
return nil, idErr
}
cfg = model.SysGameListConfig{
ID: nextID,
SysOrigin: sysOrigin,
GameOrigin: baishunVendorType,
GameID: gameID,
Name: name,
Category: category,
GameCode: gameCode,
Cover: cover,
Amounts: strings.TrimSpace(req.Amounts),
Showcase: req.Showcase,
Sort: req.Sort,
Height: req.Height,
Width: req.Width,
FullScreen: req.FullScreen,
ClientOrigin: clientOrigin,
Regions: strings.TrimSpace(req.Regions),
GameMode: formatAdminGameModeRaw(gameMode),
}
if err := tx.Create(&cfg).Error; err != nil {
tx.Rollback()
return nil, err
}
} else {
if err := tx.Model(&model.SysGameListConfig{}).
Where("id = ?", cfg.ID).
Updates(cfgValues).Error; err != nil {
tx.Rollback()
return nil, err
}
}
var ext model.SysGameListVendorExt
err = tx.Where("game_list_config_id = ? AND vendor_type = ?", cfg.ID, baishunVendorType).First(&ext).Error
if err != nil && err != gorm.ErrRecordNotFound {
tx.Rollback()
return nil, err
}
if ext.ID == 0 {
nextID, idErr := utils.NextID()
if idErr != nil {
tx.Rollback()
return nil, idErr
}
ext = model.SysGameListVendorExt{
ID: nextID,
GameListConfigID: cfg.ID,
SysOrigin: sysOrigin,
VendorType: baishunVendorType,
VendorGameID: strconv.Itoa(req.VendorGameID),
LaunchMode: launchMode,
PackageVersion: packageVersion,
PackageURL: packageURL,
PreviewURL: previewURL,
Orientation: orientation,
SafeHeight: safeHeight,
GSP: gsp,
CurrencyIcon: strings.TrimSpace(req.CurrencyIcon),
Enabled: true,
}
if err := tx.Create(&ext).Error; err != nil {
tx.Rollback()
return nil, err
}
} else {
if err := tx.Model(&model.SysGameListVendorExt{}).
Where("id = ?", ext.ID).
Updates(map[string]any{
"sys_origin": sysOrigin,
"vendor_game_id": strconv.Itoa(req.VendorGameID),
"launch_mode": launchMode,
"package_version": packageVersion,
"package_url": packageURL,
"preview_url": previewURL,
"orientation": orientation,
"safe_height": safeHeight,
"gsp": gsp,
"currency_icon": strings.TrimSpace(req.CurrencyIcon),
"enabled": true,
"bridge_schema_version": defaultIfBlank(ext.BridgeSchemaVersion, "1.0"),
}).Error; err != nil {
tx.Rollback()
return nil, err
}
}
if err := tx.Commit().Error; err != nil {
return nil, err
}
return s.getAdminGame(ctx, sysOrigin, cfg.ID)
}
// DeleteAdminGame 删除后台百顺游戏配置,不影响目录表。
func (s *BaishunService) DeleteAdminGame(ctx context.Context, sysOrigin string, id int64) error {
if id <= 0 {
return NewAppError(http.StatusBadRequest, "id_required", "id is required")
}
sysOrigin = normalizeAdminSysOrigin(sysOrigin)
tx := s.repo.DB.WithContext(ctx).Begin()
if tx.Error != nil {
return tx.Error
}
if err := tx.Where("game_list_config_id = ? AND vendor_type = ?", id, baishunVendorType).
Delete(&model.SysGameListVendorExt{}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Where("id = ? AND sys_origin = ? AND game_origin = ?", id, sysOrigin, baishunVendorType).
Delete(&model.SysGameListConfig{}).Error; err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
// ImportCatalogGames 把本地目录中尚未进入后台列表的百顺游戏补齐成可配置记录。
func (s *BaishunService) ImportCatalogGames(ctx context.Context, sysOrigin string, vendorGameIDs []int) (*ImportCatalogResponse, error) {
tx := s.repo.DB.WithContext(ctx).Begin()
if tx.Error != nil {
return nil, tx.Error
}
resp, err := s.importCatalogGamesTx(tx, normalizeAdminSysOrigin(sysOrigin), buildVendorGameIDFilter(vendorGameIDs))
if err != nil {
tx.Rollback()
return nil, err
}
if err := tx.Commit().Error; err != nil {
return nil, err
}
return resp, nil
}
// SyncAdminGames 先同步百顺平台目录,再把缺失配置导入后台列表。
func (s *BaishunService) SyncAdminGames(ctx context.Context, req SyncCatalogRequest) (*SyncAdminCatalogResponse, error) {
syncResp, err := s.SyncCatalog(ctx, req)
if err != nil {
return nil, err
}
importResp, err := s.ImportCatalogGames(ctx, req.SysOrigin, req.VendorGameIDs)
if err != nil {
return nil, err
}
return &SyncAdminCatalogResponse{
Inserted: syncResp.Inserted,
Updated: syncResp.Updated,
Skipped: syncResp.Skipped,
Existing: importResp.Existing,
Imported: importResp.Imported,
}, nil
}
func (s *BaishunService) buildAdminGameQuery(ctx context.Context, sysOrigin, keyword string, showcase *bool) *gorm.DB {
query := s.repo.DB.WithContext(ctx).
Table("sys_game_list_config AS cfg").
Joins("LEFT JOIN sys_game_list_vendor_ext ext ON ext.game_list_config_id = cfg.id AND ext.vendor_type = ?", baishunVendorType).
Joins(`
LEFT JOIN baishun_game_catalog cat
ON cat.sys_origin = cfg.sys_origin
AND (
CAST(cat.vendor_game_id AS CHAR) = ext.vendor_game_id
OR cat.internal_game_id = cfg.game_id
)
`).
Where("cfg.sys_origin = ? AND cfg.game_origin = ?", sysOrigin, baishunVendorType)
if showcase != nil {
query = query.Where("cfg.is_showcase = ?", *showcase)
}
keyword = strings.TrimSpace(keyword)
if keyword != "" {
like := "%" + keyword + "%"
query = query.Where(`
cfg.name LIKE ?
OR cfg.game_id LIKE ?
OR cfg.game_code LIKE ?
OR ext.vendor_game_id LIKE ?
`, like, like, like, like)
}
return query
}
func (s *BaishunService) getAdminGame(ctx context.Context, sysOrigin string, id int64) (*AdminBaishunGameItem, error) {
var rows []adminGameRow
err := s.buildAdminGameQuery(ctx, sysOrigin, "", nil).
Where("cfg.id = ?", id).
Select(`
cfg.id AS id,
cfg.sys_origin AS sys_origin,
cfg.game_id AS internal_game_id,
cfg.name AS name,
cfg.category AS category,
cfg.game_code AS game_code,
cfg.cover AS cover,
cfg.amounts AS amounts,
cfg.is_showcase AS showcase,
cfg.sort AS sort,
cfg.height AS height,
cfg.width AS width,
cfg.full_screen AS full_screen,
cfg.client_origin AS client_origin,
cfg.regions AS regions,
cfg.game_mode AS game_mode_raw,
cfg.create_time AS create_time,
cfg.update_time AS update_time,
ext.vendor_game_id AS vendor_game_id,
ext.launch_mode AS launch_mode,
ext.package_version AS ext_package_version,
ext.preview_url AS ext_preview_url,
ext.package_url AS package_url,
ext.orientation AS ext_orientation,
ext.safe_height AS ext_safe_height,
ext.gsp AS ext_gsp,
ext.currency_icon AS currency_icon,
cat.status AS catalog_status,
cat.preview_url AS catalog_preview_url,
cat.download_url AS catalog_download_url,
cat.package_version AS catalog_package,
cat.orientation AS catalog_orientation,
cat.safe_height AS catalog_safe_height,
cat.game_mode_json AS catalog_game_mode_json
`).
Limit(1).
Scan(&rows).Error
if err != nil {
return nil, err
}
if len(rows) == 1 {
item := rows[0].toAdminItem()
return &item, nil
}
return nil, NewAppError(http.StatusNotFound, "game_not_found", "admin baishun game not found")
}
func (s *BaishunService) findCatalogForAdmin(ctx context.Context, sysOrigin string, vendorGameID int, gameID string) (model.BaishunGameCatalog, error) {
query := s.repo.DB.WithContext(ctx).Where("sys_origin = ?", sysOrigin)
switch {
case vendorGameID > 0:
query = query.Where("vendor_game_id = ?", vendorGameID)
case strings.TrimSpace(gameID) != "":
query = query.Where("internal_game_id = ?", strings.TrimSpace(gameID))
default:
return model.BaishunGameCatalog{}, nil
}
var catalog model.BaishunGameCatalog
if err := query.First(&catalog).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return model.BaishunGameCatalog{}, nil
}
return model.BaishunGameCatalog{}, err
}
return catalog, nil
}
func (s *BaishunService) findAdminConfigForSave(tx *gorm.DB, sysOrigin string, id int64, gameID string, vendorGameID int) (model.SysGameListConfig, error) {
var cfg model.SysGameListConfig
if id > 0 {
err := tx.Where("id = ? AND sys_origin = ? AND game_origin = ?", id, sysOrigin, baishunVendorType).First(&cfg).Error
if err == gorm.ErrRecordNotFound {
return model.SysGameListConfig{}, NewAppError(http.StatusNotFound, "game_not_found", "admin baishun game not found")
}
return cfg, err
}
if vendorGameID > 0 {
var ext model.SysGameListVendorExt
err := tx.Where("sys_origin = ? AND vendor_type = ? AND vendor_game_id = ?", sysOrigin, baishunVendorType, strconv.Itoa(vendorGameID)).First(&ext).Error
if err != nil && err != gorm.ErrRecordNotFound {
return model.SysGameListConfig{}, err
}
if ext.GameListConfigID > 0 {
err = tx.Where("id = ? AND sys_origin = ? AND game_origin = ?", ext.GameListConfigID, sysOrigin, baishunVendorType).First(&cfg).Error
if err == nil {
return cfg, nil
}
if err != gorm.ErrRecordNotFound {
return model.SysGameListConfig{}, err
}
}
}
if strings.TrimSpace(gameID) == "" {
return model.SysGameListConfig{}, nil
}
err := tx.Where("sys_origin = ? AND game_origin = ? AND game_id = ?", sysOrigin, baishunVendorType, strings.TrimSpace(gameID)).First(&cfg).Error
if err == gorm.ErrRecordNotFound {
return model.SysGameListConfig{}, nil
}
return cfg, err
}
func (s *BaishunService) importCatalogGamesTx(tx *gorm.DB, sysOrigin string, filter map[int]struct{}) (*ImportCatalogResponse, error) {
query := tx.Where("sys_origin = ? AND status = ?", sysOrigin, baishunCatalogEnabled)
if len(filter) > 0 {
ids := make([]int, 0, len(filter))
for id := range filter {
ids = append(ids, id)
}
query = query.Where("vendor_game_id IN ?", ids)
}
var catalogs []model.BaishunGameCatalog
if err := query.Order("vendor_game_id ASC").Find(&catalogs).Error; err != nil {
return nil, err
}
resp := &ImportCatalogResponse{}
for _, catalog := range catalogs {
vendorGameID := strconv.Itoa(catalog.VendorGameID)
var ext model.SysGameListVendorExt
err := tx.Where("sys_origin = ? AND vendor_type = ? AND vendor_game_id = ?", sysOrigin, baishunVendorType, vendorGameID).First(&ext).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
if err == nil && ext.GameListConfigID > 0 {
resp.Existing++
continue
}
var cfg model.SysGameListConfig
err = tx.Where("sys_origin = ? AND game_origin = ? AND game_id = ?", sysOrigin, baishunVendorType, catalog.InternalGameID).First(&cfg).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
if err == gorm.ErrRecordNotFound {
nextID, idErr := utils.NextID()
if idErr != nil {
return nil, idErr
}
cfg = model.SysGameListConfig{
ID: nextID,
SysOrigin: sysOrigin,
GameOrigin: baishunVendorType,
GameID: defaultIfBlank(catalog.InternalGameID, fmt.Sprintf("bs_%d", catalog.VendorGameID)),
Name: catalog.Name,
Category: "OTHER",
GameCode: vendorGameID,
Cover: defaultIfBlank(catalog.Cover, catalog.PreviewURL),
Showcase: false,
Sort: int64(catalog.VendorGameID),
FullScreen: true,
ClientOrigin: "COMMON",
GameMode: formatAdminGameModeRaw(baishunFixedGameMode),
}
if err := tx.Create(&cfg).Error; err != nil {
return nil, err
}
}
nextID, idErr := utils.NextID()
if idErr != nil {
return nil, idErr
}
newExt := model.SysGameListVendorExt{
ID: nextID,
GameListConfigID: cfg.ID,
SysOrigin: sysOrigin,
VendorType: baishunVendorType,
VendorGameID: vendorGameID,
LaunchMode: baishunLaunchModeRemote,
PackageVersion: catalog.PackageVersion,
PackageURL: catalog.DownloadURL,
PreviewURL: catalog.PreviewURL,
Orientation: catalog.Orientation,
SafeHeight: catalog.SafeHeight,
GSP: defaultAdminGSP(catalog.GSP, s.cfg.Baishun.GSP),
BridgeSchemaVersion: "1.0",
Enabled: true,
}
if err := tx.Create(&newExt).Error; err != nil {
return nil, err
}
resp.Imported++
}
return resp, nil
}
func (r adminGameRow) toAdminItem() AdminBaishunGameItem {
return AdminBaishunGameItem{
ID: r.ID,
SysOrigin: r.SysOrigin,
GameID: r.InternalGameID,
VendorGameID: parseAdminVendorGameID(r.VendorGameID, r.InternalGameID),
Name: r.Name,
Category: r.Category,
GameCode: r.GameCode,
Cover: r.Cover,
Amounts: r.Amounts,
Showcase: r.Showcase,
Sort: r.Sort,
Height: r.Height,
Width: r.Width,
FullScreen: r.FullScreen,
ClientOrigin: r.ClientOrigin,
Regions: r.Regions,
GameMode: baishunFixedGameMode,
LaunchMode: defaultIfBlank(r.LaunchMode, baishunLaunchModeRemote),
PackageVersion: defaultIfBlank(r.ExtPackageVersion, r.CatalogPackage),
PreviewURL: defaultIfBlank(r.ExtPreviewURL, r.CatalogPreviewURL),
PackageURL: defaultIfBlank(r.PackageURL, r.CatalogDownloadURL),
Orientation: chooseAdminOrientation(r.ExtOrientation, r.CatalogOrientation),
SafeHeight: chooseAdminSafeHeight(r.ExtSafeHeight, r.CatalogSafeHeight),
GSP: r.ExtGSP,
CurrencyIcon: r.CurrencyIcon,
CatalogStatus: r.CatalogStatus,
CreateTime: formatAdminTime(r.CreateTime),
UpdateTime: formatAdminTime(r.UpdateTime),
}
}
func normalizeAdminSysOrigin(sysOrigin string) string {
sysOrigin = strings.ToUpper(strings.TrimSpace(sysOrigin))
if sysOrigin == "" {
return "LIKEI"
}
return sysOrigin
}
func normalizePageCursor(cursor int) int {
if cursor <= 0 {
return 1
}
return cursor
}
func normalizePageLimit(limit int) int {
if limit <= 0 {
return 20
}
if limit > 200 {
return 200
}
return limit
}
func defaultAdminGSP(catalogGSP string, fallback int) string {
if strings.TrimSpace(catalogGSP) != "" {
return strings.TrimSpace(catalogGSP)
}
if fallback > 0 {
return strconv.Itoa(fallback)
}
return "101"
}
func formatAdminGameModeRaw(gameMode int) string {
return fmt.Sprintf("[%d]", baishunFixedGameMode)
}
func parseAdminVendorGameID(raw string, gameID string) int {
if parsed, err := strconv.Atoi(strings.TrimSpace(raw)); err == nil {
return parsed
}
if strings.HasPrefix(strings.TrimSpace(gameID), "bs_") {
if parsed, err := strconv.Atoi(strings.TrimPrefix(strings.TrimSpace(gameID), "bs_")); err == nil {
return parsed
}
}
return 0
}
func chooseAdminOrientation(extOrientation, catalogOrientation *int) int {
if extOrientation != nil {
return *extOrientation
}
if catalogOrientation != nil {
return *catalogOrientation
}
return 0
}
func chooseAdminSafeHeight(extSafeHeight, catalogSafeHeight int) int {
if extSafeHeight > 0 {
return extSafeHeight
}
return catalogSafeHeight
}
func formatAdminTime(value time.Time) string {
if value.IsZero() {
return ""
}
return value.Format(adminTimeLayout)
}
func buildVendorGameIDFilter(vendorGameIDs []int) map[int]struct{} {
filter := make(map[int]struct{}, len(vendorGameIDs))
for _, id := range vendorGameIDs {
if id > 0 {
filter[id] = struct{}{}
}
}
return filter
}

View File

@ -0,0 +1,3 @@
package baishun
const baishunFixedGameMode = 2

View File

@ -0,0 +1,32 @@
package baishun
import "testing"
func TestGameListRowGameModeAlwaysFixed(t *testing.T) {
testCases := []string{"", "[1]", "[2]", "[3]", "CHAT_ROOM"}
for _, raw := range testCases {
row := gameListRow{GameModeRaw: raw}
if got := row.gameMode(); got != baishunFixedGameMode {
t.Fatalf("gameMode() = %d, want %d for raw %q", got, baishunFixedGameMode, raw)
}
if got := row.toListItem().GameMode; got != baishunFixedGameMode {
t.Fatalf("toListItem().GameMode = %d, want %d for raw %q", got, baishunFixedGameMode, raw)
}
}
}
func TestAdminGameModeAlwaysFixed(t *testing.T) {
row := adminGameRow{
GameModeRaw: "[3]",
CatalogGameModeJSON: "[1]",
}
if got := row.toAdminItem().GameMode; got != baishunFixedGameMode {
t.Fatalf("toAdminItem().GameMode = %d, want %d", got, baishunFixedGameMode)
}
if got := formatAdminGameModeRaw(0); got != "[2]" {
t.Fatalf("formatAdminGameModeRaw(0) = %q, want [2]", got)
}
if got := formatAdminGameModeRaw(99); got != "[2]" {
t.Fatalf("formatAdminGameModeRaw(99) = %q, want [2]", got)
}
}

View File

@ -87,10 +87,7 @@ func (r gameListRow) launchMode() string {
// gameMode 返回游戏模式。
func (r gameListRow) gameMode() int {
if parsed := pickFirstInt(r.GameModeRaw, 3); parsed > 0 {
return parsed
}
return 3
return baishunFixedGameMode
}
// vendorGameIDInt 返回厂商游戏 ID 的整数值。

View File

@ -99,9 +99,6 @@ func (s *BaishunService) listRoomGames(ctx context.Context, sysOrigin, roomID, c
if strings.TrimSpace(category) != "" && !strings.EqualFold(strings.TrimSpace(category), "CHAT_ROOM") {
query = query.Where("cfg.category = ?", category)
}
if strings.TrimSpace(roomID) != "" {
query = query.Where("cfg.game_mode LIKE ?", "%CHAT_ROOM%")
}
if err := query.Order("cfg.sort DESC").Scan(&rows).Error; err != nil {
return nil, err
}
@ -133,7 +130,7 @@ func (s *BaishunService) listRoomGames(ctx context.Context, sysOrigin, roomID, c
Sort: int64(catalog.VendorGameID),
LaunchMode: baishunLaunchModeRemote,
FullScreen: true,
GameMode: pickFirstInt(catalog.GameModeJSON, 3),
GameMode: baishunFixedGameMode,
SafeHeight: catalog.SafeHeight,
Orientation: derefInt(catalog.Orientation),
PackageVersion: catalog.PackageVersion,
@ -214,7 +211,7 @@ func (s *BaishunService) findGameRow(ctx context.Context, sysOrigin, gameID stri
CatalogSafeHeight: catalog.SafeHeight,
CatalogOrientation: catalog.Orientation,
CatalogStatus: catalog.Status,
GameModeRaw: catalog.GameModeJSON,
GameModeRaw: formatAdminGameModeRaw(baishunFixedGameMode),
}, nil
}
return gameListRow{}, NewAppError(http.StatusNotFound, "game_not_found", "room game config not found")

View File

@ -181,6 +181,90 @@ type SyncCatalogResponse struct {
Skipped int `json:"skipped"`
}
// AdminBaishunGameItem 是后台管理页展示的百顺游戏配置行。
type AdminBaishunGameItem struct {
ID int64 `json:"id"`
SysOrigin string `json:"sysOrigin"`
GameID string `json:"gameId"`
VendorGameID int `json:"vendorGameId"`
Name string `json:"name"`
Category string `json:"category"`
GameCode string `json:"gameCode"`
Cover string `json:"cover"`
Amounts string `json:"amounts"`
Showcase bool `json:"showcase"`
Sort int64 `json:"sort"`
Height float64 `json:"height"`
Width float64 `json:"width"`
FullScreen bool `json:"fullScreen"`
ClientOrigin string `json:"clientOrigin"`
Regions string `json:"regions"`
GameMode int `json:"gameMode"`
LaunchMode string `json:"launchMode"`
PackageVersion string `json:"packageVersion"`
PreviewURL string `json:"previewUrl"`
PackageURL string `json:"packageUrl"`
Orientation int `json:"orientation"`
SafeHeight int `json:"safeHeight"`
GSP string `json:"gsp"`
CurrencyIcon string `json:"currencyIcon"`
CatalogStatus string `json:"catalogStatus"`
CreateTime string `json:"createTime"`
UpdateTime string `json:"updateTime"`
}
// AdminBaishunGamePageResponse 是后台页分页响应。
type AdminBaishunGamePageResponse struct {
Cursor int `json:"cursor"`
Limit int `json:"limit"`
Records []AdminBaishunGameItem `json:"records"`
Total int64 `json:"total"`
}
// SaveAdminBaishunGameRequest 是后台保存百顺游戏配置的入参。
type SaveAdminBaishunGameRequest struct {
ID int64 `json:"id"`
SysOrigin string `json:"sysOrigin"`
GameID string `json:"gameId"`
VendorGameID int `json:"vendorGameId"`
Name string `json:"name"`
Category string `json:"category"`
GameCode string `json:"gameCode"`
Cover string `json:"cover"`
Amounts string `json:"amounts"`
Showcase bool `json:"showcase"`
Sort int64 `json:"sort"`
Height float64 `json:"height"`
Width float64 `json:"width"`
FullScreen bool `json:"fullScreen"`
ClientOrigin string `json:"clientOrigin"`
Regions string `json:"regions"`
GameMode int `json:"gameMode"`
LaunchMode string `json:"launchMode"`
PackageVersion string `json:"packageVersion"`
PreviewURL string `json:"previewUrl"`
PackageURL string `json:"packageUrl"`
Orientation *int `json:"orientation"`
SafeHeight int `json:"safeHeight"`
GSP string `json:"gsp"`
CurrencyIcon string `json:"currencyIcon"`
}
// ImportCatalogResponse 描述从本地目录补齐后台配置的结果。
type ImportCatalogResponse struct {
Existing int `json:"existing"`
Imported int `json:"imported"`
}
// SyncAdminCatalogResponse 描述同步平台目录并导入后台配置的结果。
type SyncAdminCatalogResponse struct {
Inserted int `json:"inserted"`
Updated int `json:"updated"`
Skipped int `json:"skipped"`
Existing int `json:"existing"`
Imported int `json:"imported"`
}
// BaishunTokenRequest 是百顺拉取 token/code 回调入参。
type BaishunTokenRequest struct {
AppID int64 `json:"app_id"`