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" const baishunRoomCategory = "CHAT_ROOM" type adminGameRow struct { ID int64 SysOrigin string Profile 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 ExtExtraJSON 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, profile string, keyword string, showcase *bool, cursor int, limit int, ) (*AdminBaishunGamePageResponse, error) { sysOrigin = normalizeAdminSysOrigin(sysOrigin) profile, err := s.requestProfile(ctx, sysOrigin, profile) if err != nil { return nil, err } cursor = normalizePageCursor(cursor) limit = normalizePageLimit(limit) countQuery := s.buildAdminGameQuery(ctx, sysOrigin, profile, keyword, showcase) var total int64 if err := countQuery.Count(&total).Error; err != nil { return nil, err } var rows []adminGameRow listQuery := s.buildAdminGameQuery(ctx, sysOrigin, profile, keyword, showcase) if err := listQuery. Select(` cfg.id AS id, cfg.sys_origin AS sys_origin, ext.profile AS profile, 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, ext.extra_json AS ext_extra_json, 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) profile, err := s.requestProfile(ctx, sysOrigin, req.Profile) if err != nil { return nil, err } runtimeCfg, err := s.resolveRuntimeConfigForProfile(ctx, sysOrigin, profile) if err != nil { return nil, err } catalog, err := s.findCatalogForAdmin(ctx, sysOrigin, profile, 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), baishunRoomCategory) 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, runtimeCfg.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, profile, req.ID, gameID, req.VendorGameID) if err != nil { tx.Rollback() return nil, err } showcase := resolveAdminShowcase(req.Showcase, cfg.Showcase, cfg.ID == 0) 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": 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: 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 = ? AND profile = ?", cfg.ID, baishunVendorType, profile).First(&ext).Error if err != nil && err != gorm.ErrRecordNotFound { tx.Rollback() return nil, err } now := time.Now() extraJSON := updateExtExtraJSONWithGameType(ext.ExtraJSON, req.LaunchParams, baishunVendorType, ext.ID != 0) 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, Profile: profile, 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), ExtraJSON: extraJSON, Enabled: true, CreateTime: now, UpdateTime: now, } 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, "profile": profile, "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), "extra_json": extraJSON, "enabled": true, "bridge_schema_version": defaultIfBlank(ext.BridgeSchemaVersion, "1.0"), "update_time": now, }).Error; err != nil { tx.Rollback() return nil, err } } if err := tx.Commit().Error; err != nil { return nil, err } s.clearJavaGameListCache(ctx) return s.getAdminGame(ctx, sysOrigin, profile, cfg.ID) } // DeleteAdminGame 删除后台百顺游戏配置,不影响目录表。 func (s *BaishunService) DeleteAdminGame(ctx context.Context, sysOrigin string, profile string, id int64) error { if id <= 0 { return NewAppError(http.StatusBadRequest, "id_required", "id is required") } sysOrigin = normalizeAdminSysOrigin(sysOrigin) profile, err := s.requestProfile(ctx, sysOrigin, profile) if err != nil { return err } 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 = ? AND profile = ?", id, baishunVendorType, profile). Delete(&model.SysGameListVendorExt{}).Error; err != nil { tx.Rollback() return err } var remaining int64 if err := tx.Model(&model.SysGameListVendorExt{}). Where("game_list_config_id = ? AND vendor_type = ?", id, baishunVendorType). Count(&remaining).Error; err != nil { tx.Rollback() return err } if remaining == 0 { if err := tx.Where("id = ? AND sys_origin = ? AND game_origin = ?", id, sysOrigin, baishunVendorType). Delete(&model.SysGameListConfig{}).Error; err != nil { tx.Rollback() return err } } if err := tx.Commit().Error; err != nil { return err } s.clearJavaGameListCache(ctx) return nil } // ImportCatalogGames 把本地目录中尚未进入后台列表的百顺游戏补齐成可配置记录。 func (s *BaishunService) ImportCatalogGames(ctx context.Context, sysOrigin string, profile string, vendorGameIDs []int, defaultShowcase ...bool) (*ImportCatalogResponse, error) { sysOrigin = normalizeAdminSysOrigin(sysOrigin) profile, err := s.requestProfile(ctx, sysOrigin, profile) if err != nil { return nil, err } tx := s.repo.DB.WithContext(ctx).Begin() if tx.Error != nil { return nil, tx.Error } resp, err := s.importCatalogGamesTx(tx, sysOrigin, profile, buildVendorGameIDFilter(vendorGameIDs), resolveImportDefaultShowcase(defaultShowcase...)) if err != nil { tx.Rollback() return nil, err } if err := tx.Commit().Error; err != nil { return nil, err } s.clearJavaGameListCache(ctx) 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.Profile, req.VendorGameIDs, resolveBoolPtrDefault(req.DefaultShowcase, true)) 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, profile, keyword string, showcase *bool) *gorm.DB { query := s.repo.DB.WithContext(ctx). Table("sys_game_list_config AS cfg"). Joins("JOIN sys_game_list_vendor_ext ext ON ext.game_list_config_id = cfg.id AND ext.vendor_type = ? AND ext.profile = ?", baishunVendorType, profile). Joins(` LEFT JOIN baishun_game_catalog cat ON cat.sys_origin = cfg.sys_origin AND cat.profile = ext.profile 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, profile string, id int64) (*AdminBaishunGameItem, error) { var rows []adminGameRow err := s.buildAdminGameQuery(ctx, sysOrigin, profile, "", nil). Where("cfg.id = ?", id). Select(` cfg.id AS id, cfg.sys_origin AS sys_origin, ext.profile AS profile, 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, ext.extra_json AS ext_extra_json, 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, profile string, vendorGameID int, gameID string) (model.BaishunGameCatalog, error) { query := s.repo.DB.WithContext(ctx).Where("sys_origin = ? AND profile = ?", sysOrigin, profile) 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, profile 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") } if err == nil { var extCount int64 err = tx.Model(&model.SysGameListVendorExt{}). Where("game_list_config_id = ? AND vendor_type = ? AND profile = ?", cfg.ID, baishunVendorType, profile). Count(&extCount).Error if err != nil { return model.SysGameListConfig{}, err } if extCount == 0 { 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 profile = ? AND vendor_type = ? AND vendor_game_id = ?", sysOrigin, profile, 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.Table("sys_game_list_config AS cfg"). Joins("JOIN sys_game_list_vendor_ext ext ON ext.game_list_config_id = cfg.id AND ext.vendor_type = ? AND ext.profile = ?", baishunVendorType, profile). Where("cfg.sys_origin = ? AND cfg.game_origin = ? AND cfg.game_id = ?", sysOrigin, baishunVendorType, strings.TrimSpace(gameID)). Select("cfg.*"). Limit(1). Scan(&cfg).Error if err != nil { return model.SysGameListConfig{}, err } if cfg.ID == 0 { return model.SysGameListConfig{}, nil } return cfg, nil } func (s *BaishunService) importCatalogGamesTx(tx *gorm.DB, sysOrigin string, profile string, filter map[int]struct{}, defaultShowcase bool) (*ImportCatalogResponse, error) { ctx := tx.Statement.Context if ctx == nil { ctx = context.Background() } runtimeCfg, err := s.resolveRuntimeConfigForProfile(ctx, sysOrigin, profile) if err != nil { return nil, err } query := tx.Where("sys_origin = ? AND profile = ? AND status = ?", sysOrigin, profile, 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) now := time.Now() var ext model.SysGameListVendorExt err := tx.Where("sys_origin = ? AND profile = ? AND vendor_type = ? AND vendor_game_id = ?", sysOrigin, profile, baishunVendorType, vendorGameID).First(&ext).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } if err == nil && ext.GameListConfigID > 0 { var cfg model.SysGameListConfig cfgErr := tx.Where("id = ? AND sys_origin = ? AND game_origin = ?", ext.GameListConfigID, sysOrigin, baishunVendorType).First(&cfg).Error if cfgErr == nil { resp.Existing++ continue } if cfgErr != gorm.ErrRecordNotFound { return nil, cfgErr } } nextCfgID, idErr := utils.NextID() if idErr != nil { return nil, idErr } cfg := model.SysGameListConfig{ ID: nextCfgID, SysOrigin: sysOrigin, GameOrigin: baishunVendorType, GameID: defaultIfBlank(catalog.InternalGameID, fmt.Sprintf("bs_%d", catalog.VendorGameID)), Name: catalog.Name, Category: baishunRoomCategory, GameCode: vendorGameID, Cover: defaultIfBlank(catalog.Cover, catalog.PreviewURL), Showcase: defaultShowcase, 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 } extraJSON := updateExtExtraJSONWithGameType(ext.ExtraJSON, nil, baishunVendorType, ext.ID != 0) extValues := map[string]any{ "game_list_config_id": cfg.ID, "sys_origin": sysOrigin, "profile": profile, "vendor_type": baishunVendorType, "vendor_game_id": vendorGameID, "launch_mode": baishunLaunchModeRemote, "package_version": catalog.PackageVersion, "package_url": catalog.DownloadURL, "preview_url": catalog.PreviewURL, "orientation": catalog.Orientation, "safe_height": catalog.SafeHeight, "gsp": defaultAdminGSP(catalog.GSP, runtimeCfg.gsp()), "bridge_schema_version": "1.0", "extra_json": extraJSON, "enabled": true, "update_time": now, } if ext.ID > 0 { if err := tx.Model(&model.SysGameListVendorExt{}). Where("id = ?", ext.ID). Updates(extValues).Error; err != nil { return nil, err } } else { newExt := model.SysGameListVendorExt{ ID: nextID, GameListConfigID: cfg.ID, SysOrigin: sysOrigin, Profile: profile, 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, runtimeCfg.gsp()), BridgeSchemaVersion: "1.0", ExtraJSON: extraJSON, Enabled: true, CreateTime: now, UpdateTime: now, } if err := tx.Create(&newExt).Error; err != nil { return nil, err } } resp.Imported++ } return resp, nil } func resolveImportDefaultShowcase(values ...bool) bool { if len(values) == 0 { return true } return values[0] } func resolveBoolPtrDefault(value *bool, fallback bool) bool { if value == nil { return fallback } return *value } func (r adminGameRow) toAdminItem() AdminBaishunGameItem { return AdminBaishunGameItem{ ID: r.ID, SysOrigin: r.SysOrigin, Profile: normalizeBaishunProfile(r.Profile), GameID: r.InternalGameID, GameType: gameTypeFromExtraJSON(r.ExtExtraJSON, baishunVendorType), 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, LaunchParams: launchParamsWithGameType(r.ExtExtraJSON, baishunVendorType), 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 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) } 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 }