小游
This commit is contained in:
parent
4af7844d1b
commit
a5a53b390f
@ -564,6 +564,9 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
|
||||
}
|
||||
}
|
||||
if base == "" {
|
||||
if strings.EqualFold(game.AdapterType, gamedomain.AdapterYomiV4) {
|
||||
return "", xerr.New(xerr.InvalidArgument, "yomi auth url is required")
|
||||
}
|
||||
if strings.EqualFold(game.AdapterType, gamedomain.AdapterZeeOneV1) {
|
||||
return "", xerr.New(xerr.InvalidArgument, "zeeone launch url is required")
|
||||
}
|
||||
@ -587,11 +590,14 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
|
||||
query.Set("platUserId", externalUserID(session, config.UIDMode))
|
||||
query.Set("lang", lang)
|
||||
// Yomi gameLevelUid 语义类似房间/场次,没有后台配置时必须显式传 0。
|
||||
if config.GameLevelUID > 0 {
|
||||
query.Set("gameLevelUid", strconv.FormatInt(config.GameLevelUID, 10))
|
||||
if gameLevelUID := config.GameLevelUIDFor(game); gameLevelUID > 0 {
|
||||
query.Set("gameLevelUid", strconv.FormatInt(gameLevelUID, 10))
|
||||
} else {
|
||||
query.Set("gameLevelUid", "0")
|
||||
}
|
||||
if config.PlatPayload != "" {
|
||||
query.Set("platPayload", config.PlatPayload)
|
||||
}
|
||||
if session.RoomID != "" {
|
||||
query.Set("platRoomId", session.RoomID)
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -82,7 +83,7 @@ func TestLaunchGameBuildsYomiAuthURL(t *testing.T) {
|
||||
PlatformStatus: gamedomain.StatusActive,
|
||||
APIBaseURL: "https://api-testv2.yomigame.games/gate/gateway/auth",
|
||||
AdapterType: gamedomain.AdapterYomiV4,
|
||||
AdapterConfigJSON: `{"default_lang":"en","token_ttl_seconds":86400,"uid_mode":"user_id"}`,
|
||||
AdapterConfigJSON: `{"default_lang":"en","token_ttl_seconds":86400,"uid_mode":"user_id","game_level_uids":{"33":17},"plat_payload":"{\"env\":\"test\"}"}`,
|
||||
},
|
||||
}
|
||||
svc := New(Config{LaunchSessionTTL: 15 * time.Minute}, repo, &fakeWallet{}, &fakeUser{})
|
||||
@ -102,13 +103,57 @@ func TestLaunchGameBuildsYomiAuthURL(t *testing.T) {
|
||||
if result.ExpiresAtMS != 1700086400000 {
|
||||
t.Fatalf("yomi launch must use adapter token ttl, got %+v", result)
|
||||
}
|
||||
for _, part := range []string{"gameUid=33", "platUserId=42", "lang=en", "gameLevelUid=0", "platRoomId=room_1", "platAuthCode=app_access_token_yomi"} {
|
||||
if !strings.Contains(result.LaunchURL, part) {
|
||||
t.Fatalf("yomi launch url missing %s: %s", part, result.LaunchURL)
|
||||
parsed, err := url.Parse(result.LaunchURL)
|
||||
if err != nil {
|
||||
t.Fatalf("parse yomi launch url failed: %v", err)
|
||||
}
|
||||
query := parsed.Query()
|
||||
expected := map[string]string{
|
||||
"gameUid": "33",
|
||||
"platUserId": "42",
|
||||
"lang": "en",
|
||||
"gameLevelUid": "17",
|
||||
"platRoomId": "room_1",
|
||||
"platAuthCode": "app_access_token_yomi",
|
||||
"platPayload": `{"env":"test"}`,
|
||||
}
|
||||
for key, want := range expected {
|
||||
if got := query.Get(key); got != want {
|
||||
t.Fatalf("yomi launch query %s mismatch: got %q want %q url=%s", key, got, want, result.LaunchURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchGameRequiresYomiAuthURL(t *testing.T) {
|
||||
repo := &fakeRepository{
|
||||
launchable: gamedomain.LaunchableGame{
|
||||
CatalogItem: gamedomain.CatalogItem{
|
||||
AppCode: "lalu",
|
||||
GameID: "yomi_fish_001",
|
||||
PlatformCode: "yomi",
|
||||
ProviderGameID: "33",
|
||||
GameName: "Fish",
|
||||
Status: gamedomain.StatusActive,
|
||||
},
|
||||
PlatformStatus: gamedomain.StatusActive,
|
||||
AdapterType: gamedomain.AdapterYomiV4,
|
||||
AdapterConfigJSON: `{"default_lang":"en"}`,
|
||||
},
|
||||
}
|
||||
svc := New(Config{LaunchSessionTTL: 15 * time.Minute}, repo, &fakeWallet{}, &fakeUser{})
|
||||
svc.now = func() time.Time { return time.UnixMilli(1700000000000) }
|
||||
|
||||
_, err := svc.LaunchGame(context.Background(), LaunchCommand{
|
||||
AppCode: "lalu",
|
||||
UserID: 42,
|
||||
GameID: "yomi_fish_001",
|
||||
AccessToken: "app_access_token_yomi",
|
||||
})
|
||||
if err == nil || !strings.Contains(xerr.MessageOf(err), "yomi auth url is required") {
|
||||
t.Fatalf("LaunchGame must reject missing yomi auth url, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLaunchGameBuildsLeaderCCURL(t *testing.T) {
|
||||
repo := &fakeRepository{
|
||||
launchable: gamedomain.LaunchableGame{
|
||||
@ -464,6 +509,69 @@ func TestHandleLeaderCCChangeCoinAcceptsAppAccessTokenAndTokenOnlyProbe(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleYomiGetTokenAndUserInfo(t *testing.T) {
|
||||
secret := "kEdOjdxgOUCJjG1L6kl3fYEIgMFxQOza"
|
||||
token := "app_access_token_yomi"
|
||||
repo := &fakeRepository{
|
||||
platform: gamedomain.Platform{
|
||||
PlatformCode: "yomi",
|
||||
AdapterType: gamedomain.AdapterYomiV4,
|
||||
CallbackSecretCiphertext: secret,
|
||||
AdapterConfigJSON: `{"uid_mode":"display_user_id"}`,
|
||||
},
|
||||
session: gamedomain.LaunchSession{
|
||||
AppCode: "lalu",
|
||||
SessionID: "game_sess_yomi",
|
||||
UserID: 42,
|
||||
DisplayUserID: "420001",
|
||||
RoomID: "room_1",
|
||||
PlatformCode: "yomi",
|
||||
GameID: "yomi_fish_001",
|
||||
ProviderGameID: "33",
|
||||
LaunchTokenHash: stableHash(token),
|
||||
Status: gamedomain.SessionActive,
|
||||
ExpiresAtMS: 1700086400000,
|
||||
},
|
||||
}
|
||||
wallet := &fakeWallet{balanceAfter: 1880}
|
||||
user := &fakeUser{}
|
||||
svc := New(Config{}, repo, wallet, user)
|
||||
svc.now = func() time.Time { return time.UnixMilli(1700000000000) }
|
||||
|
||||
getTokenBody := encryptYomiTestBody(t, secret, `{"gameUid":33,"lang":"en","platAuthCode":"app_access_token_yomi","platUserId":"420001","platPayload":"","platRoomId":"room_1"}`)
|
||||
raw, contentType, err := svc.HandleCallback(context.Background(), &gamev1.CallbackRequest{
|
||||
Meta: &gamev1.RequestMeta{RequestId: "req-yomi-gettoken", AppCode: "lalu"},
|
||||
PlatformCode: "yomi",
|
||||
Operation: "gettoken",
|
||||
RawBody: getTokenBody,
|
||||
RemoteAddr: "3.1.174.194:443",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("HandleCallback gettoken failed: %v", err)
|
||||
}
|
||||
if contentType != "application/json" || !strings.Contains(string(raw), `"code":200`) || !strings.Contains(string(raw), `"platToken":"app_access_token_yomi"`) || !strings.Contains(string(raw), `"balance":1880`) {
|
||||
t.Fatalf("yomi gettoken response mismatch: %s", raw)
|
||||
}
|
||||
if user.lastGet.GetUserId() != 42 {
|
||||
t.Fatalf("yomi gettoken must query token user, got %+v", user.lastGet)
|
||||
}
|
||||
|
||||
userInfoBody := encryptYomiTestBody(t, secret, `{"platUserId":"420001","platToken":"app_access_token_yomi","platPayload":"","platRoomId":"room_1"}`)
|
||||
raw, _, err = svc.HandleCallback(context.Background(), &gamev1.CallbackRequest{
|
||||
Meta: &gamev1.RequestMeta{RequestId: "req-yomi-userinfo", AppCode: "lalu"},
|
||||
PlatformCode: "yomi",
|
||||
Operation: "userinfo",
|
||||
RawBody: userInfoBody,
|
||||
RemoteAddr: "3.1.174.194:443",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("HandleCallback userinfo failed: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(raw), `"code":200`) || !strings.Contains(string(raw), `"platUserId":"420001"`) || !strings.Contains(string(raw), `"avatar":"https://cdn.example/avatar.png"`) {
|
||||
t.Fatalf("yomi userinfo response mismatch: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleYomiChangeBalanceDecryptsAndAppliesWallet(t *testing.T) {
|
||||
secret := "12345678901234567890123456789012"
|
||||
token := "gt_test_token"
|
||||
|
||||
@ -43,6 +43,10 @@ type yomiAdapterConfig struct {
|
||||
TokenTTLSeconds int64 `json:"token_ttl_seconds"`
|
||||
// game_level_uid 对应 Yomi 房间/场次,没有配置时按文档传 0。
|
||||
GameLevelUID int64 `json:"game_level_uid"`
|
||||
// game_level_uids 支持同一平台下不同游戏/房间使用不同场次 ID,key 可填 providerGameId 或内部 gameId。
|
||||
GameLevelUIDs map[string]int64 `json:"game_level_uids"`
|
||||
// plat_payload 透传给 Yomi,适合联调时放环境、渠道或房间附加上下文;不参与服务端业务判断。
|
||||
PlatPayload string `json:"plat_payload"`
|
||||
}
|
||||
|
||||
func yomiConfigFromPlatform(value any) yomiAdapterConfig {
|
||||
@ -58,6 +62,8 @@ func yomiConfigFromPlatform(value any) yomiAdapterConfig {
|
||||
_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
|
||||
config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode))
|
||||
config.DefaultLang = strings.TrimSpace(config.DefaultLang)
|
||||
config.PlatPayload = strings.TrimSpace(config.PlatPayload)
|
||||
config.GameLevelUIDs = normalizeInt64Map(config.GameLevelUIDs)
|
||||
return config
|
||||
}
|
||||
|
||||
@ -69,6 +75,32 @@ func (c yomiAdapterConfig) TokenTTL() time.Duration {
|
||||
return yomiDefaultTokenTTL
|
||||
}
|
||||
|
||||
func (c yomiAdapterConfig) GameLevelUIDFor(game gamedomain.LaunchableGame) int64 {
|
||||
for _, key := range []string{game.ProviderGameID, game.GameID, strings.ToLower(game.GameID)} {
|
||||
if value := c.GameLevelUIDs[strings.TrimSpace(key)]; value > 0 {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return c.GameLevelUID
|
||||
}
|
||||
|
||||
func normalizeInt64Map(input map[string]int64) map[string]int64 {
|
||||
if len(input) == 0 {
|
||||
return nil
|
||||
}
|
||||
output := make(map[string]int64, len(input))
|
||||
for key, value := range input {
|
||||
key = strings.TrimSpace(key)
|
||||
if key != "" && value > 0 {
|
||||
output[key] = value
|
||||
}
|
||||
}
|
||||
if len(output) == 0 {
|
||||
return nil
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func externalUserID(session gamedomain.LaunchSession, uidMode string) string {
|
||||
// 三方通常会把 uid 展示给玩家或写入订单,切换 uid_mode 后必须保持启动和回调校验一致。
|
||||
if strings.EqualFold(uidMode, "display_user_id") && strings.TrimSpace(session.DisplayUserID) != "" {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user