fix game zeeone expired session lookup

This commit is contained in:
zhx 2026-07-01 20:11:58 +08:00
parent 2a7fff405d
commit 2f3af25c0c
2 changed files with 75 additions and 7 deletions

View File

@ -1260,6 +1260,68 @@ func TestHandleZeeOneAccessTokenIgnoresStaleLaunchSessionGameID(t *testing.T) {
}
}
func TestHandleZeeOneExpiredAccessTokenUsesScopedLaunchSession(t *testing.T) {
secret := "zeeone-test-secret"
userID := int64(328141720820654080)
token := leaderccTestAccessToken(t, "lalu", userID, "1010", 1700000300)
repo := &fakeRepository{
platform: gamedomain.Platform{
PlatformCode: "zeeone",
AdapterType: gamedomain.AdapterZeeOneV1,
CallbackSecretCiphertext: secret,
AdapterConfigJSON: `{"merchant_id":307715,"platform_id":2032,"uid_mode":"display_user_id"}`,
},
sessions: []gamedomain.LaunchSession{
{
AppCode: "lalu",
SessionID: "launch_session_1035",
UserID: userID,
DisplayUserID: "1010",
PlatformCode: "zeeone",
GameID: "zeeone_1035",
ProviderGameID: "1035",
LaunchTokenHash: stableHash(token),
Status: gamedomain.SessionActive,
ExpiresAtMS: 1700086800000,
CreatedAtMS: 1700000000000,
},
{
AppCode: "lalu",
SessionID: "newer_wrong_game_session",
UserID: userID,
DisplayUserID: "1010",
PlatformCode: "zeeone",
GameID: "zeeone_1021",
ProviderGameID: "1021",
LaunchTokenHash: stableHash(token),
Status: gamedomain.SessionActive,
ExpiresAtMS: 1700086800000,
CreatedAtMS: 1700000100000,
},
},
}
wallet := &fakeWallet{balanceAfter: 777}
svc := New(Config{}, repo, wallet, &fakeUser{})
svc.now = func() time.Time { return time.UnixMilli(1700000400000) }
changeBody := `{"appId":"M307715_P2032","userId":"1010","sessionId":"` + token + `","currency_diff":-50000,"diff_msg":"bet","game_id":1035,"room_id":"3255","change_time_at":1700000400,"order_id":"zee_order_expired_token_scoped"}`
raw, _, err := svc.HandleCallback(context.Background(), &gamev1.CallbackRequest{
Meta: &gamev1.RequestMeta{RequestId: "req-zeeone-expired-scoped", AppCode: "lalu"},
PlatformCode: "zeeone",
Operation: "change_balance",
RawBody: zeeoneTestEnvelope(t, secret, changeBody, 1700000400000),
})
if err != nil {
t.Fatalf("HandleCallback change failed: %v", err)
}
if !strings.Contains(string(raw), `"code":0`) || strings.Contains(string(raw), "sessionId invalid") {
t.Fatalf("zeeone expired JWT must fall back to scoped launch session: %s", raw)
}
if wallet.lastApply.GetUserId() != userID || wallet.lastApply.GetGameId() != "zeeone_1035" || wallet.lastApply.GetCoinAmount() != 50000 {
t.Fatalf("zeeone scoped launch session must drive wallet command, got %+v", wallet.lastApply)
}
}
func TestHandleZeeOneAllowsDebitAndCreditWithSameProviderOrderID(t *testing.T) {
secret := "zeeone-test-secret"
token := leaderccTestAccessToken(t, "lalu", 123456, "123456", 1700003600)

View File

@ -143,7 +143,7 @@ func (s *Service) handleZeeOneOperation(ctx context.Context, app string, platfor
case "change_balance", "change_coin", "update_coin":
return s.handleZeeOneChangeBalance(ctx, app, config, req, body, requestHash)
case "update_session", "refresh_session":
return s.handleZeeOneUpdateSession(ctx, app, config, body)
return s.handleZeeOneUpdateSession(ctx, app, req.GetPlatformCode(), config, body)
case "repair_order", "repair":
return s.handleZeeOneRepairOrder(ctx, app, platform, body)
default:
@ -232,7 +232,7 @@ func validateZeeOneAppID(config zeeoneAdapterConfig, appID string) error {
}
func (s *Service) handleZeeOneUserInfo(ctx context.Context, app string, config zeeoneAdapterConfig, req *gamev1.CallbackRequest, body zeeoneBusinessBody) ([]byte, string, string, bool, string, error) {
session, err := s.validZeeOneSession(ctx, app, body.SessionID)
session, err := s.validZeeOneSession(ctx, app, req.GetPlatformCode(), body.SessionID, body.GameID)
if err != nil {
return zeeoneAdapterError(zeeoneCodeFromError(err), zeeoneMessageFromError(err), true, "")
}
@ -262,7 +262,7 @@ func (s *Service) handleZeeOneChangeBalance(ctx context.Context, app string, con
if body.OrderID == "" || body.UserID == "" || body.SessionID == "" || body.GameID <= 0 || body.DiffMsg == "" {
return zeeoneAdapterError(zeeoneCodeInvalidArgument, "invalid argument", true, body.OrderID)
}
session, err := s.validZeeOneSession(ctx, app, body.SessionID)
session, err := s.validZeeOneSession(ctx, app, req.GetPlatformCode(), body.SessionID, body.GameID)
if err != nil {
return zeeoneAdapterError(zeeoneCodeFromError(err), zeeoneMessageFromError(err), true, body.OrderID)
}
@ -288,8 +288,8 @@ func (s *Service) handleZeeOneChangeBalance(ctx context.Context, app string, con
return raw, contentType, strconv.Itoa(zeeoneCodeOK), true, body.OrderID, nil
}
func (s *Service) handleZeeOneUpdateSession(ctx context.Context, app string, config zeeoneAdapterConfig, body zeeoneBusinessBody) ([]byte, string, string, bool, string, error) {
session, err := s.validZeeOneSession(ctx, app, body.SessionID)
func (s *Service) handleZeeOneUpdateSession(ctx context.Context, app string, platformCode string, config zeeoneAdapterConfig, body zeeoneBusinessBody) ([]byte, string, string, bool, string, error) {
session, err := s.validZeeOneSession(ctx, app, platformCode, body.SessionID, body.GameID)
if err != nil {
return zeeoneAdapterError(zeeoneCodeFromError(err), zeeoneMessageFromError(err), true, "")
}
@ -321,7 +321,7 @@ func (s *Service) handleZeeOneRepairOrder(ctx context.Context, app string, platf
return raw, contentType, strconv.Itoa(zeeoneCodeOK), true, body.OrderID, nil
}
func (s *Service) validZeeOneSession(ctx context.Context, app string, sessionID string) (gamedomain.LaunchSession, error) {
func (s *Service) validZeeOneSession(ctx context.Context, app string, platformCode string, sessionID string, gameID int64) (gamedomain.LaunchSession, error) {
sessionID = strings.TrimSpace(sessionID)
if sessionID == "" {
return gamedomain.LaunchSession{}, xerr.New(xerr.Unauthorized, "sessionId not found")
@ -333,7 +333,13 @@ func (s *Service) validZeeOneSession(ctx context.Context, app string, sessionID
return session, nil
}
}
if session, err := s.validYomiSession(ctx, app, sessionID); err == nil {
providerGameID := ""
if gameID > 0 {
providerGameID = strconv.FormatInt(gameID, 10)
}
// App access token 过期后仍可能处于三方游戏局中;启动链路已经把同一 token 的厂商、游戏和 24h TTL
// 落到 game_launch_sessions。这里必须按平台和游戏收窄避免同一 access token 打开多款 ZeeOne 游戏时拿到旧会话。
if session, err := s.validScopedYomiSession(ctx, app, platformCode, sessionID, providerGameID); err == nil {
return session, nil
}
if strings.Count(sessionID, ".") == 2 {