小游
This commit is contained in:
parent
4af7844d1b
commit
a5a53b390f
@ -564,6 +564,9 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if base == "" {
|
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) {
|
if strings.EqualFold(game.AdapterType, gamedomain.AdapterZeeOneV1) {
|
||||||
return "", xerr.New(xerr.InvalidArgument, "zeeone launch url is required")
|
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("platUserId", externalUserID(session, config.UIDMode))
|
||||||
query.Set("lang", lang)
|
query.Set("lang", lang)
|
||||||
// Yomi gameLevelUid 语义类似房间/场次,没有后台配置时必须显式传 0。
|
// Yomi gameLevelUid 语义类似房间/场次,没有后台配置时必须显式传 0。
|
||||||
if config.GameLevelUID > 0 {
|
if gameLevelUID := config.GameLevelUIDFor(game); gameLevelUID > 0 {
|
||||||
query.Set("gameLevelUid", strconv.FormatInt(config.GameLevelUID, 10))
|
query.Set("gameLevelUid", strconv.FormatInt(gameLevelUID, 10))
|
||||||
} else {
|
} else {
|
||||||
query.Set("gameLevelUid", "0")
|
query.Set("gameLevelUid", "0")
|
||||||
}
|
}
|
||||||
|
if config.PlatPayload != "" {
|
||||||
|
query.Set("platPayload", config.PlatPayload)
|
||||||
|
}
|
||||||
if session.RoomID != "" {
|
if session.RoomID != "" {
|
||||||
query.Set("platRoomId", session.RoomID)
|
query.Set("platRoomId", session.RoomID)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -82,7 +83,7 @@ func TestLaunchGameBuildsYomiAuthURL(t *testing.T) {
|
|||||||
PlatformStatus: gamedomain.StatusActive,
|
PlatformStatus: gamedomain.StatusActive,
|
||||||
APIBaseURL: "https://api-testv2.yomigame.games/gate/gateway/auth",
|
APIBaseURL: "https://api-testv2.yomigame.games/gate/gateway/auth",
|
||||||
AdapterType: gamedomain.AdapterYomiV4,
|
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{})
|
svc := New(Config{LaunchSessionTTL: 15 * time.Minute}, repo, &fakeWallet{}, &fakeUser{})
|
||||||
@ -102,13 +103,57 @@ func TestLaunchGameBuildsYomiAuthURL(t *testing.T) {
|
|||||||
if result.ExpiresAtMS != 1700086400000 {
|
if result.ExpiresAtMS != 1700086400000 {
|
||||||
t.Fatalf("yomi launch must use adapter token ttl, got %+v", result)
|
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"} {
|
parsed, err := url.Parse(result.LaunchURL)
|
||||||
if !strings.Contains(result.LaunchURL, part) {
|
if err != nil {
|
||||||
t.Fatalf("yomi launch url missing %s: %s", part, result.LaunchURL)
|
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) {
|
func TestLaunchGameBuildsLeaderCCURL(t *testing.T) {
|
||||||
repo := &fakeRepository{
|
repo := &fakeRepository{
|
||||||
launchable: gamedomain.LaunchableGame{
|
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) {
|
func TestHandleYomiChangeBalanceDecryptsAndAppliesWallet(t *testing.T) {
|
||||||
secret := "12345678901234567890123456789012"
|
secret := "12345678901234567890123456789012"
|
||||||
token := "gt_test_token"
|
token := "gt_test_token"
|
||||||
|
|||||||
@ -43,6 +43,10 @@ type yomiAdapterConfig struct {
|
|||||||
TokenTTLSeconds int64 `json:"token_ttl_seconds"`
|
TokenTTLSeconds int64 `json:"token_ttl_seconds"`
|
||||||
// game_level_uid 对应 Yomi 房间/场次,没有配置时按文档传 0。
|
// game_level_uid 对应 Yomi 房间/场次,没有配置时按文档传 0。
|
||||||
GameLevelUID int64 `json:"game_level_uid"`
|
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 {
|
func yomiConfigFromPlatform(value any) yomiAdapterConfig {
|
||||||
@ -58,6 +62,8 @@ func yomiConfigFromPlatform(value any) yomiAdapterConfig {
|
|||||||
_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
|
_ = json.Unmarshal([]byte(strings.TrimSpace(raw)), &config)
|
||||||
config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode))
|
config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode))
|
||||||
config.DefaultLang = strings.TrimSpace(config.DefaultLang)
|
config.DefaultLang = strings.TrimSpace(config.DefaultLang)
|
||||||
|
config.PlatPayload = strings.TrimSpace(config.PlatPayload)
|
||||||
|
config.GameLevelUIDs = normalizeInt64Map(config.GameLevelUIDs)
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +75,32 @@ func (c yomiAdapterConfig) TokenTTL() time.Duration {
|
|||||||
return yomiDefaultTokenTTL
|
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 {
|
func externalUserID(session gamedomain.LaunchSession, uidMode string) string {
|
||||||
// 三方通常会把 uid 展示给玩家或写入订单,切换 uid_mode 后必须保持启动和回调校验一致。
|
// 三方通常会把 uid 展示给玩家或写入订单,切换 uid_mode 后必须保持启动和回调校验一致。
|
||||||
if strings.EqualFold(uidMode, "display_user_id") && strings.TrimSpace(session.DisplayUserID) != "" {
|
if strings.EqualFold(uidMode, "display_user_id") && strings.TrimSpace(session.DisplayUserID) != "" {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user