feat: persist game safe height

This commit is contained in:
zhx 2026-06-02 17:42:50 +08:00
parent e1a5457ee2
commit 8d25c99361
12 changed files with 123 additions and 18 deletions

View File

@ -265,6 +265,7 @@ type GameCatalogItem struct {
Tags []string `protobuf:"bytes,14,rep,name=tags,proto3" json:"tags,omitempty"`
CreatedAtMs int64 `protobuf:"varint,15,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"`
UpdatedAtMs int64 `protobuf:"varint,16,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"`
SafeHeight int32 `protobuf:"varint,17,opt,name=safe_height,json=safeHeight,proto3" json:"safe_height,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -411,6 +412,13 @@ func (x *GameCatalogItem) GetUpdatedAtMs() int64 {
return 0
}
func (x *GameCatalogItem) GetSafeHeight() int32 {
if x != nil {
return x.SafeHeight
}
return 0
}
type AppGame struct {
state protoimpl.MessageState `protogen:"open.v1"`
GameId string `protobuf:"bytes,1,opt,name=game_id,json=gameId,proto3" json:"game_id,omitempty"`
@ -426,6 +434,7 @@ type AppGame struct {
Enabled bool `protobuf:"varint,11,opt,name=enabled,proto3" json:"enabled,omitempty"`
Maintenance bool `protobuf:"varint,12,opt,name=maintenance,proto3" json:"maintenance,omitempty"`
SortOrder int32 `protobuf:"varint,13,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"`
SafeHeight int32 `protobuf:"varint,14,opt,name=safe_height,json=safeHeight,proto3" json:"safe_height,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -551,6 +560,13 @@ func (x *AppGame) GetSortOrder() int32 {
return 0
}
func (x *AppGame) GetSafeHeight() int32 {
if x != nil {
return x.SafeHeight
}
return 0
}
type ListGamesRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
@ -796,6 +812,7 @@ type LaunchGameResponse struct {
ExpiresAtMs int64 `protobuf:"varint,3,opt,name=expires_at_ms,json=expiresAtMs,proto3" json:"expires_at_ms,omitempty"`
Orientation string `protobuf:"bytes,4,opt,name=orientation,proto3" json:"orientation,omitempty"`
ServerTimeMs int64 `protobuf:"varint,5,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"`
SafeHeight int32 `protobuf:"varint,6,opt,name=safe_height,json=safeHeight,proto3" json:"safe_height,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -865,6 +882,13 @@ func (x *LaunchGameResponse) GetServerTimeMs() int64 {
return 0
}
func (x *LaunchGameResponse) GetSafeHeight() int32 {
if x != nil {
return x.SafeHeight
}
return 0
}
type CallbackRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"`
@ -1795,7 +1819,7 @@ const file_proto_game_v1_game_proto_rawDesc = "" +
" \x01(\tR\x0ecallbackSecret\x12.\n" +
"\x13callback_secret_set\x18\v \x01(\bR\x11callbackSecretSet\x122\n" +
"\x15callback_ip_whitelist\x18\f \x03(\tR\x13callbackIpWhitelist\x12.\n" +
"\x13adapter_config_json\x18\r \x01(\tR\x11adapterConfigJson\"\xf6\x03\n" +
"\x13adapter_config_json\x18\r \x01(\tR\x11adapterConfigJson\"\x97\x04\n" +
"\x0fGameCatalogItem\x12\x19\n" +
"\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x17\n" +
"\agame_id\x18\x02 \x01(\tR\x06gameId\x12#\n" +
@ -1815,7 +1839,9 @@ const file_proto_game_v1_game_proto_rawDesc = "" +
"sort_order\x18\r \x01(\x05R\tsortOrder\x12\x12\n" +
"\x04tags\x18\x0e \x03(\tR\x04tags\x12\"\n" +
"\rcreated_at_ms\x18\x0f \x01(\x03R\vcreatedAtMs\x12\"\n" +
"\rupdated_at_ms\x18\x10 \x01(\x03R\vupdatedAtMs\"\x83\x03\n" +
"\rupdated_at_ms\x18\x10 \x01(\x03R\vupdatedAtMs\x12\x1f\n" +
"\vsafe_height\x18\x11 \x01(\x05R\n" +
"safeHeight\"\xa4\x03\n" +
"\aAppGame\x12\x17\n" +
"\agame_id\x18\x01 \x01(\tR\x06gameId\x12#\n" +
"\rplatform_code\x18\x02 \x01(\tR\fplatformCode\x12\x19\n" +
@ -1832,7 +1858,9 @@ const file_proto_game_v1_game_proto_rawDesc = "" +
"\aenabled\x18\v \x01(\bR\aenabled\x12 \n" +
"\vmaintenance\x18\f \x01(\bR\vmaintenance\x12\x1d\n" +
"\n" +
"sort_order\x18\r \x01(\x05R\tsortOrder\"\xec\x01\n" +
"sort_order\x18\r \x01(\x05R\tsortOrder\x12\x1f\n" +
"\vsafe_height\x18\x0e \x01(\x05R\n" +
"safeHeight\"\xec\x01\n" +
"\x10ListGamesRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12\x17\n" +
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x14\n" +
@ -1851,7 +1879,7 @@ const file_proto_game_v1_game_proto_rawDesc = "" +
"\agame_id\x18\x04 \x01(\tR\x06gameId\x12\x14\n" +
"\x05scene\x18\x05 \x01(\tR\x05scene\x12\x17\n" +
"\aroom_id\x18\x06 \x01(\tR\x06roomId\x12!\n" +
"\faccess_token\x18\a \x01(\tR\vaccessToken\"\xbe\x01\n" +
"\faccess_token\x18\a \x01(\tR\vaccessToken\"\xdf\x01\n" +
"\x12LaunchGameResponse\x12\x1d\n" +
"\n" +
"session_id\x18\x01 \x01(\tR\tsessionId\x12\x1d\n" +
@ -1859,7 +1887,9 @@ const file_proto_game_v1_game_proto_rawDesc = "" +
"launch_url\x18\x02 \x01(\tR\tlaunchUrl\x12\"\n" +
"\rexpires_at_ms\x18\x03 \x01(\x03R\vexpiresAtMs\x12 \n" +
"\vorientation\x18\x04 \x01(\tR\vorientation\x12$\n" +
"\x0eserver_time_ms\x18\x05 \x01(\x03R\fserverTimeMs\"\xbe\x03\n" +
"\x0eserver_time_ms\x18\x05 \x01(\x03R\fserverTimeMs\x12\x1f\n" +
"\vsafe_height\x18\x06 \x01(\x05R\n" +
"safeHeight\"\xbe\x03\n" +
"\x0fCallbackRequest\x12.\n" +
"\x04meta\x18\x01 \x01(\v2\x1a.hyapp.game.v1.RequestMetaR\x04meta\x12#\n" +
"\rplatform_code\x18\x02 \x01(\tR\fplatformCode\x12\x1c\n" +

View File

@ -48,6 +48,7 @@ message GameCatalogItem {
repeated string tags = 14;
int64 created_at_ms = 15;
int64 updated_at_ms = 16;
int32 safe_height = 17;
}
message AppGame {
@ -64,6 +65,7 @@ message AppGame {
bool enabled = 11;
bool maintenance = 12;
int32 sort_order = 13;
int32 safe_height = 14;
}
message ListGamesRequest {
@ -99,6 +101,7 @@ message LaunchGameResponse {
int64 expires_at_ms = 3;
string orientation = 4;
int64 server_time_ms = 5;
int32 safe_height = 6;
}
message CallbackRequest {

View File

@ -34,6 +34,7 @@ type catalogDTO struct {
LaunchURL string `json:"launchUrl,omitempty"`
LaunchMode string `json:"launchMode"`
Orientation string `json:"orientation"`
SafeHeight int32 `json:"safeHeight"`
MinCoin int64 `json:"minCoin"`
Status string `json:"status"`
SortOrder int32 `json:"sortOrder"`
@ -78,6 +79,7 @@ func catalogFromProto(item *gamev1.GameCatalogItem) catalogDTO {
CoverURL: item.GetCoverUrl(),
LaunchMode: item.GetLaunchMode(),
Orientation: item.GetOrientation(),
SafeHeight: item.GetSafeHeight(),
MinCoin: item.GetMinCoin(),
Status: item.GetStatus(),
SortOrder: item.GetSortOrder(),

View File

@ -36,6 +36,7 @@ type catalogRequest struct {
CoverURL string `json:"coverUrl"`
LaunchMode string `json:"launchMode"`
Orientation string `json:"orientation"`
SafeHeight int32 `json:"safeHeight"`
MinCoin int64 `json:"minCoin"`
Status string `json:"status"`
SortOrder int32 `json:"sortOrder"`
@ -77,6 +78,7 @@ func (r catalogRequest) toProto(gameID string) *gamev1.GameCatalogItem {
CoverUrl: strings.TrimSpace(r.CoverURL),
LaunchMode: strings.TrimSpace(r.LaunchMode),
Orientation: strings.TrimSpace(r.Orientation),
SafeHeight: r.SafeHeight,
MinCoin: r.MinCoin,
Status: strings.TrimSpace(r.Status),
SortOrder: r.SortOrder,

View File

@ -478,6 +478,7 @@ type baishunGameListItem struct {
CoverURL string `json:"cover_url"`
DownloadURL string `json:"download_url"`
GameOrientation any `json:"game_orientation"`
SafeHeight any `json:"safe_height"`
}
func baishunCatalogItems(platformCode string, providerItems []baishunGameListItem, req syncGamesRequest) ([]catalogRequest, map[string]string) {
@ -502,6 +503,7 @@ func baishunCatalogItems(platformCode string, providerItems []baishunGameListIte
CoverURL: coverURL,
LaunchMode: defaulted(req.LaunchMode, defaultLaunchMode),
Orientation: baishunOrientation(providerItem.GameOrientation),
SafeHeight: int32FromJSONValue(providerItem.SafeHeight),
MinCoin: req.MinCoin,
Status: defaulted(req.Status, defaultGameStatus),
SortOrder: int32((index + 1) * 10),
@ -626,6 +628,45 @@ func stringFromJSONValue(value any) string {
}
}
func int32FromJSONValue(value any) int32 {
switch typed := value.(type) {
case nil:
return 0
case int:
if typed > 0 {
return int32(typed)
}
case int32:
if typed > 0 {
return typed
}
case int64:
if typed > 0 {
return int32(typed)
}
case float64:
if typed > 0 {
return int32(typed)
}
case json.Number:
parsed, err := strconv.ParseInt(typed.String(), 10, 32)
if err == nil && parsed > 0 {
return int32(parsed)
}
case string:
parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 32)
if err == nil && parsed > 0 {
return int32(parsed)
}
default:
parsed, err := strconv.ParseInt(strings.TrimSpace(fmt.Sprint(typed)), 10, 32)
if err == nil && parsed > 0 {
return int32(parsed)
}
}
return 0
}
func baishunMD5(value string) string {
sum := md5.Sum([]byte(value))
return hex.EncodeToString(sum[:])

View File

@ -28,7 +28,7 @@ func TestFetchBaishunGameSyncPlanFetchesSignsAndFormatsCatalog(t *testing.T) {
t.Fatalf("request body mismatch: %+v", body)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"code":0,"message":"ok","data":[{"game_id":1006,"name":"Gold of Test","preview_url":"https://cdn.test/1006.png","download_url":"https://h5.test/1006/index.html","game_orientation":2}]}`))
_, _ = w.Write([]byte(`{"code":0,"message":"ok","data":[{"game_id":1006,"name":"Gold of Test","preview_url":"https://cdn.test/1006.png","download_url":"https://h5.test/1006/index.html","game_orientation":2,"safe_height":1024}]}`))
}))
defer server.Close()
@ -58,6 +58,9 @@ func TestFetchBaishunGameSyncPlanFetchesSignsAndFormatsCatalog(t *testing.T) {
if game.IconURL != "https://cdn.test/1006.png" || game.CoverURL != "https://cdn.test/1006.png" || game.Orientation != "landscape" || game.Status != "disabled" || game.LaunchMode != "full_screen" {
t.Fatalf("catalog presentation mismatch: %+v", game)
}
if game.SafeHeight != 1024 {
t.Fatalf("safeHeight = %d, want 1024", game.SafeHeight)
}
var merged struct {
GameURLs map[string]string `json:"game_urls"`
}

View File

@ -33,6 +33,7 @@ CREATE TABLE IF NOT EXISTS game_catalog (
cover_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '封面 URL',
launch_mode VARCHAR(32) NOT NULL DEFAULT 'h5_popup' COMMENT '启动模式',
orientation VARCHAR(32) NOT NULL DEFAULT 'portrait' COMMENT '屏幕方向',
safe_height INT NOT NULL DEFAULT 0 COMMENT '游戏安全高',
min_coin BIGINT NOT NULL DEFAULT 0 COMMENT '最小金币',
status VARCHAR(32) NOT NULL COMMENT '业务状态',
sort_order INT NOT NULL DEFAULT 0 COMMENT '排序权重,数值越小越靠前',

View File

@ -66,6 +66,7 @@ type CatalogItem struct {
CoverURL string
LaunchMode string
Orientation string
SafeHeight int32
MinCoin int64
Status string
SortOrder int32
@ -85,6 +86,7 @@ type AppGame struct {
Category string
LaunchMode string
Orientation string
SafeHeight int32
MinCoin int64
Enabled bool
Maintenance bool

View File

@ -116,6 +116,7 @@ type LaunchResult struct {
LaunchURL string
ExpiresAtMS int64
Orientation string
SafeHeight int32
ServerTimeMS int64
}
@ -250,6 +251,7 @@ func (s *Service) LaunchGame(ctx context.Context, command LaunchCommand) (Launch
LaunchURL: launchURL,
ExpiresAtMS: expiresAt,
Orientation: game.Orientation,
SafeHeight: game.SafeHeight,
ServerTimeMS: now.UnixMilli(),
}, nil
}

View File

@ -86,6 +86,7 @@ func (r *Repository) Migrate(ctx context.Context) error {
cover_url VARCHAR(512) NOT NULL DEFAULT '',
launch_mode VARCHAR(32) NOT NULL DEFAULT 'h5_popup',
orientation VARCHAR(32) NOT NULL DEFAULT 'portrait',
safe_height INT NOT NULL DEFAULT 0,
min_coin BIGINT NOT NULL DEFAULT 0,
status VARCHAR(32) NOT NULL,
sort_order INT NOT NULL DEFAULT 0,
@ -252,6 +253,9 @@ func (r *Repository) Migrate(ctx context.Context) error {
if err := r.ensureColumn(ctx, "game_platforms", "adapter_type", "adapter_type VARCHAR(64) NOT NULL DEFAULT 'demo' AFTER api_base_url"); err != nil {
return err
}
if err := r.ensureColumn(ctx, "game_catalog", "safe_height", "safe_height INT NOT NULL DEFAULT 0 AFTER orientation"); err != nil {
return err
}
if err := r.ensureColumn(ctx, "game_launch_sessions", "region_id", "region_id BIGINT NOT NULL DEFAULT 0 AFTER room_id"); err != nil {
return err
}
@ -382,7 +386,7 @@ func (r *Repository) seedDefaults(ctx context.Context) error {
func (r *Repository) ListGames(ctx context.Context, query gameservice.ListGamesQuery) ([]gamedomain.AppGame, error) {
rows, err := r.db.QueryContext(ctx,
`SELECT c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
c.launch_mode, c.orientation, c.min_coin, c.status, p.status,
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status,
MIN(CASE WHEN r.sort_order = 0 THEN c.sort_order ELSE r.sort_order END) AS display_sort
FROM game_catalog c
JOIN game_platforms p ON p.app_code = c.app_code AND p.platform_code = c.platform_code
@ -397,7 +401,7 @@ func (r *Repository) ListGames(ctx context.Context, query gameservice.ListGamesQ
AND (r.language = '' OR r.language = ?)
AND (r.platform = '' OR r.platform = ?)
GROUP BY c.game_id, c.platform_code, c.game_name, c.icon_url, c.cover_url, c.category,
c.launch_mode, c.orientation, c.min_coin, c.status, p.status, c.sort_order, p.sort_order
c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status, p.status, c.sort_order, p.sort_order
ORDER BY display_sort ASC, p.sort_order ASC, c.sort_order ASC, c.game_id ASC`,
appcode.Normalize(query.AppCode), query.Scene, query.RegionID, query.Language, query.ClientPlatform,
)
@ -409,7 +413,7 @@ func (r *Repository) ListGames(ctx context.Context, query gameservice.ListGamesQ
for rows.Next() {
var item gamedomain.AppGame
var gameStatus, platformStatus string
if err := rows.Scan(&item.GameID, &item.PlatformCode, &item.Name, &item.IconURL, &item.CoverURL, &item.Category, &item.LaunchMode, &item.Orientation, &item.MinCoin, &gameStatus, &platformStatus, &item.SortOrder); err != nil {
if err := rows.Scan(&item.GameID, &item.PlatformCode, &item.Name, &item.IconURL, &item.CoverURL, &item.Category, &item.LaunchMode, &item.Orientation, &item.SafeHeight, &item.MinCoin, &gameStatus, &platformStatus, &item.SortOrder); err != nil {
return nil, err
}
item.NameKey = "game." + item.GameID + ".name"
@ -424,7 +428,7 @@ func (r *Repository) GetLaunchableGame(ctx context.Context, appCode string, game
// 启动游戏时一次性取出 catalog + platform 配置,避免拿到游戏后再查平台导致配置不一致。
row := r.db.QueryRowContext(ctx,
`SELECT c.app_code, c.game_id, c.platform_code, c.provider_game_id, c.game_name, c.category,
c.icon_url, c.cover_url, c.launch_mode, c.orientation, c.min_coin, c.status,
c.icon_url, c.cover_url, c.launch_mode, c.orientation, c.safe_height, c.min_coin, c.status,
c.sort_order, COALESCE(CAST(c.tags AS CHAR), '[]'), c.created_at_ms, c.updated_at_ms,
p.platform_name, p.status, p.api_base_url, COALESCE(p.adapter_type, 'demo'),
p.callback_secret_ciphertext, COALESCE(CAST(p.callback_ip_whitelist AS CHAR), '[]'),
@ -437,7 +441,7 @@ func (r *Repository) GetLaunchableGame(ctx context.Context, appCode string, game
var item gamedomain.LaunchableGame
var tagsJSON string
var whitelistJSON string
if err := row.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS, &item.PlatformName, &item.PlatformStatus, &item.APIBaseURL, &item.AdapterType, &item.CallbackSecretCiphertext, &whitelistJSON, &item.AdapterConfigJSON); err != nil {
if err := row.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.SafeHeight, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS, &item.PlatformName, &item.PlatformStatus, &item.APIBaseURL, &item.AdapterType, &item.CallbackSecretCiphertext, &whitelistJSON, &item.AdapterConfigJSON); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return gamedomain.LaunchableGame{}, xerr.New(xerr.NotFound, "game not found")
}
@ -1033,7 +1037,7 @@ func (r *Repository) UpsertPlatform(ctx context.Context, platform gamedomain.Pla
func (r *Repository) ListCatalog(ctx context.Context, query gameservice.ListCatalogQuery) ([]gamedomain.CatalogItem, string, error) {
sqlText := `SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
launch_mode, orientation, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms
launch_mode, orientation, safe_height, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms
FROM game_catalog WHERE app_code = ?`
args := []any{appcode.Normalize(query.AppCode)}
if strings.TrimSpace(query.PlatformCode) != "" {
@ -1081,14 +1085,14 @@ func (r *Repository) UpsertCatalog(ctx context.Context, item gamedomain.CatalogI
if _, err := tx.ExecContext(ctx,
`INSERT INTO game_catalog (
app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
launch_mode, orientation, min_coin, status, sort_order, tags, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
launch_mode, orientation, safe_height, min_coin, status, sort_order, tags, created_at_ms, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE platform_code = VALUES(platform_code), provider_game_id = VALUES(provider_game_id),
game_name = VALUES(game_name), category = VALUES(category), icon_url = VALUES(icon_url), cover_url = VALUES(cover_url),
launch_mode = VALUES(launch_mode), orientation = VALUES(orientation), min_coin = VALUES(min_coin),
launch_mode = VALUES(launch_mode), orientation = VALUES(orientation), safe_height = VALUES(safe_height), min_coin = VALUES(min_coin),
status = VALUES(status), sort_order = VALUES(sort_order), tags = VALUES(tags), updated_at_ms = VALUES(updated_at_ms)`,
item.AppCode, item.GameID, item.PlatformCode, item.ProviderGameID, item.GameName, item.Category, item.IconURL, item.CoverURL,
item.LaunchMode, item.Orientation, item.MinCoin, item.Status, item.SortOrder, string(tagsJSON), item.CreatedAtMS, item.UpdatedAtMS,
item.LaunchMode, item.Orientation, item.SafeHeight, item.MinCoin, item.Status, item.SortOrder, string(tagsJSON), item.CreatedAtMS, item.UpdatedAtMS,
); err != nil {
return gamedomain.CatalogItem{}, err
}
@ -1156,7 +1160,7 @@ func (r *Repository) DeleteCatalog(ctx context.Context, appCode string, gameID s
func (r *Repository) getCatalog(ctx context.Context, appCode string, gameID string) (gamedomain.CatalogItem, error) {
row := r.db.QueryRowContext(ctx,
`SELECT app_code, game_id, platform_code, provider_game_id, game_name, category, icon_url, cover_url,
launch_mode, orientation, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms
launch_mode, orientation, safe_height, min_coin, status, sort_order, COALESCE(CAST(tags AS CHAR), '[]'), created_at_ms, updated_at_ms
FROM game_catalog WHERE app_code = ? AND game_id = ?`,
appcode.Normalize(appCode), strings.TrimSpace(gameID),
)
@ -1221,7 +1225,7 @@ func levelEventOutboxSelectSQL() string {
func scanCatalog(scanner catalogScanner) (gamedomain.CatalogItem, error) {
var item gamedomain.CatalogItem
var tagsJSON string
if err := scanner.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil {
if err := scanner.Scan(&item.AppCode, &item.GameID, &item.PlatformCode, &item.ProviderGameID, &item.GameName, &item.Category, &item.IconURL, &item.CoverURL, &item.LaunchMode, &item.Orientation, &item.SafeHeight, &item.MinCoin, &item.Status, &item.SortOrder, &tagsJSON, &item.CreatedAtMS, &item.UpdatedAtMS); err != nil {
return gamedomain.CatalogItem{}, err
}
_ = json.Unmarshal([]byte(tagsJSON), &item.Tags)

View File

@ -67,6 +67,7 @@ func (s *Server) LaunchGame(ctx context.Context, req *gamev1.LaunchGameRequest)
LaunchUrl: result.LaunchURL,
ExpiresAtMs: result.ExpiresAtMS,
Orientation: result.Orientation,
SafeHeight: result.SafeHeight,
ServerTimeMs: result.ServerTimeMS,
}, nil
}
@ -260,6 +261,7 @@ func appGameToProto(item gamedomain.AppGame) *gamev1.AppGame {
Category: item.Category,
LaunchMode: item.LaunchMode,
Orientation: item.Orientation,
SafeHeight: item.SafeHeight,
MinCoin: item.MinCoin,
Enabled: item.Enabled,
Maintenance: item.Maintenance,
@ -318,6 +320,7 @@ func catalogToProto(item gamedomain.CatalogItem) *gamev1.GameCatalogItem {
CoverUrl: item.CoverURL,
LaunchMode: item.LaunchMode,
Orientation: item.Orientation,
SafeHeight: item.SafeHeight,
MinCoin: item.MinCoin,
Status: item.Status,
SortOrder: item.SortOrder,
@ -342,6 +345,7 @@ func catalogFromProto(item *gamev1.GameCatalogItem) gamedomain.CatalogItem {
CoverURL: item.GetCoverUrl(),
LaunchMode: item.GetLaunchMode(),
Orientation: item.GetOrientation(),
SafeHeight: item.GetSafeHeight(),
MinCoin: item.GetMinCoin(),
Status: item.GetStatus(),
SortOrder: item.GetSortOrder(),
@ -383,6 +387,9 @@ func normalizeCatalog(item gamedomain.CatalogItem) (gamedomain.CatalogItem, erro
if item.Orientation == "" {
item.Orientation = "portrait"
}
if item.SafeHeight < 0 {
item.SafeHeight = 0
}
if item.GameID == "" || item.PlatformCode == "" || item.ProviderGameID == "" || item.GameName == "" || item.Status == "" {
return gamedomain.CatalogItem{}, xerr.New(xerr.InvalidArgument, "game is incomplete")
}

View File

@ -17,6 +17,8 @@ type gameItemData struct {
Category string `json:"category"`
LaunchMode string `json:"launch_mode"`
Orientation string `json:"orientation"`
SafeHeight int32 `json:"safe_height"`
SafeHeightV2 int32 `json:"safeHeight"`
MinCoin int64 `json:"min_coin"`
Enabled bool `json:"enabled"`
Maintenance bool `json:"maintenance"`
@ -28,6 +30,8 @@ type gameLaunchData struct {
LaunchURL string `json:"launch_url"`
ExpiresAtMS int64 `json:"expires_at_ms"`
Orientation string `json:"orientation"`
SafeHeight int32 `json:"safe_height"`
SafeHeightV2 int32 `json:"safeHeight"`
ServerTimeMS int64 `json:"server_time_ms"`
}
@ -47,6 +51,8 @@ func gameListDataFromProto(resp *gamev1.ListGamesResponse) gameListData {
Category: item.GetCategory(),
LaunchMode: item.GetLaunchMode(),
Orientation: item.GetOrientation(),
SafeHeight: item.GetSafeHeight(),
SafeHeightV2: item.GetSafeHeight(),
MinCoin: item.GetMinCoin(),
Enabled: item.GetEnabled(),
Maintenance: item.GetMaintenance(),
@ -65,6 +71,8 @@ func gameLaunchDataFromProto(resp *gamev1.LaunchGameResponse) gameLaunchData {
LaunchURL: resp.GetLaunchUrl(),
ExpiresAtMS: resp.GetExpiresAtMs(),
Orientation: resp.GetOrientation(),
SafeHeight: resp.GetSafeHeight(),
SafeHeightV2: resp.GetSafeHeight(),
ServerTimeMS: resp.GetServerTimeMs(),
}
}