From 785058a18987b4ade4bf82db0ba6df1726b8c851 Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 29 Jun 2026 18:31:41 +0800 Subject: [PATCH 1/2] fix zgame callback base config --- .../game-service/internal/service/game/service.go | 5 +++-- .../internal/service/game/service_test.go | 5 +++-- .../internal/service/game/zgame_adapter.go | 11 +++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/services/game-service/internal/service/game/service.go b/services/game-service/internal/service/game/service.go index 81330832..b58c90dd 100644 --- a/services/game-service/internal/service/game/service.go +++ b/services/game-service/internal/service/game/service.go @@ -1007,8 +1007,9 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes query.Set("session", token) query.Set("gameId", strings.TrimSpace(session.ProviderGameID)) query.Set("game_id", strings.TrimSpace(session.ProviderGameID)) - query.Set("callbackBase", "/api/v1/zgame/callback") - query.Set("callback_base", "/api/v1/zgame/callback") + callbackBase := config.CallbackBaseValue() + query.Set("callbackBase", callbackBase) + query.Set("callback_base", callbackBase) if config.DefaultLang != "" { query.Set("lang", config.DefaultLang) } diff --git a/services/game-service/internal/service/game/service_test.go b/services/game-service/internal/service/game/service_test.go index 7db50a53..07d39846 100644 --- a/services/game-service/internal/service/game/service_test.go +++ b/services/game-service/internal/service/game/service_test.go @@ -567,6 +567,7 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) { "token_ttl_seconds":7200, "uid_mode":"display_user_id", "merchant_payload":"room_game", + "callback_base":"https://api.example.test/api/v1/zgame/callback", "bridge_script_url":"https://cdn.example/zgame-bridge.js", "bridge_script_version":"20260629.1", "game_urls":{"1001":"https://zgame.example/h5/1001"} @@ -607,8 +608,8 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) { "session": token, "gameId": "1001", "game_id": "1001", - "callbackBase": "/api/v1/zgame/callback", - "callback_base": "/api/v1/zgame/callback", + "callbackBase": "https://api.example.test/api/v1/zgame/callback", + "callback_base": "https://api.example.test/api/v1/zgame/callback", "lang": "en", "roomId": "room_1", "room_id": "room_1", diff --git a/services/game-service/internal/service/game/zgame_adapter.go b/services/game-service/internal/service/game/zgame_adapter.go index 3af181d6..541ae5ef 100644 --- a/services/game-service/internal/service/game/zgame_adapter.go +++ b/services/game-service/internal/service/game/zgame_adapter.go @@ -38,6 +38,9 @@ type zgameAdapterConfig struct { // game_urls 支持 ZGame 每款游戏独立 H5 地址;launch_url_template 用于批量上新时按 provider_game_id 生成地址。 GameURLs map[string]string `json:"game_urls"` LaunchURLTemplate string `json:"launch_url_template"` + // callback_base 可按环境覆盖为完整公网 HTTPS 地址,避免 H5 在第三方域名下把相对路径解析到厂商域名。 + CallbackBase string `json:"callback_base"` + CallbackBaseAlias string `json:"callbackBase"` // merchant_payload 是 ZGame consume 回调的透传字段来源;只参与厂商回传,不进入内部业务判断。 MerchantPayload string `json:"merchant_payload"` // level/gender 是 ZGame 用户资料响应里的展示字段;真实等级接入前保持显式默认值,避免从其它体系误推。 @@ -58,6 +61,7 @@ func zgameConfigFromPlatform(value any) zgameAdapterConfig { config.UIDMode = strings.ToLower(strings.TrimSpace(config.UIDMode)) config.DefaultLang = strings.TrimSpace(config.DefaultLang) config.LaunchURLTemplate = strings.TrimSpace(config.LaunchURLTemplate) + config.CallbackBase = strings.TrimSpace(firstNonEmpty(config.CallbackBase, config.CallbackBaseAlias)) config.MerchantPayload = strings.TrimSpace(config.MerchantPayload) config.GameURLs = normalizeStringMap(config.GameURLs) return config @@ -70,6 +74,13 @@ func (c zgameAdapterConfig) TokenTTL() time.Duration { return zgameDefaultTokenTTL } +func (c zgameAdapterConfig) CallbackBaseValue() string { + if c.CallbackBase != "" { + return c.CallbackBase + } + return "/api/v1/zgame/callback" +} + func zgameLaunchBaseURL(game gamedomain.LaunchableGame, config zgameAdapterConfig) string { for _, key := range []string{game.ProviderGameID, game.GameID, strings.ToLower(game.GameID)} { if raw := strings.TrimSpace(config.GameURLs[strings.TrimSpace(key)]); raw != "" { From 55bd0305857164a1f6aba55ae8dcc2048d115bb0 Mon Sep 17 00:00:00 2001 From: zhx Date: Mon, 29 Jun 2026 18:31:46 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=8D=B3=E6=9E=84=E6=B8=B8=E6=88=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/internal/modules/hostorg/service.go | 82 +++++++++++++++---- .../internal/modules/hostorg/service_test.go | 73 +++++++++++++++++ 2 files changed, 138 insertions(+), 17 deletions(-) diff --git a/server/admin/internal/modules/hostorg/service.go b/server/admin/internal/modules/hostorg/service.go index 4f8d7d06..3a0ff77a 100644 --- a/server/admin/internal/modules/hostorg/service.go +++ b/server/admin/internal/modules/hostorg/service.go @@ -10,21 +10,25 @@ import ( "hyapp-admin-server/internal/integration/userclient" "hyapp-admin-server/internal/integration/walletclient" walletv1 "hyapp.local/api/proto/wallet/v1" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) const ( - coinSellerMerchantAssetType = "COIN_SELLER_COIN" - coinSellerStockTypePurchase = "usdt_purchase" - coinSellerStockTypeDeduction = "usdt_deduction" - coinSellerStockTypeCompensate = "coin_compensation" - agencyStatusDeleted = "deleted" - membershipStatusActive = "active" - paidCurrencyUSDT = "USDT" - sortByDiamond = "diamond" - sortByMerchantBalance = "merchant_balance" - sortBySalaryWallet = "salary_wallet" - sortByTotalRechargeUSDT = "total_recharge_usdt" - bdLeaderPositionAliasMaxRunes = 64 + coinSellerMerchantAssetType = "COIN_SELLER_COIN" + coinSellerStockTypePurchase = "usdt_purchase" + coinSellerStockTypeDeduction = "usdt_deduction" + coinSellerStockTypeCompensate = "coin_compensation" + agencyStatusDeleted = "deleted" + membershipStatusActive = "active" + paidCurrencyUSDT = "USDT" + sortByDiamond = "diamond" + sortByMerchantBalance = "merchant_balance" + sortBySalaryWallet = "salary_wallet" + sortByTotalRechargeUSDT = "total_recharge_usdt" + bdLeaderPositionAliasMaxRunes = 64 + hostOrgInternalUserIDMinDigits = 15 ) type Service struct { @@ -481,18 +485,62 @@ func (s *Service) resolveDisplayUserID(ctx context.Context, requestID string, di if displayUserID <= 0 { return 0, fmt.Errorf("display_user_id is required") } + rawID := strconv.FormatInt(displayUserID, 10) + userIDChecked := false + if len(strings.TrimLeft(rawID, "0")) >= hostOrgInternalUserIDMinDigits { + // 团队后台新合约的 targetUserId 可能是内部雪花 user_id;长数字先按 user_id 确认,避免把内部 ID 误送到短号解析。 + userIDChecked = true + found, err := s.userIDExists(ctx, requestID, displayUserID) + if err != nil { + return 0, err + } + if found { + return displayUserID, nil + } + } identity, err := s.userClient.ResolveDisplayUserID(ctx, userclient.ResolveDisplayUserIDRequest{ RequestID: requestID, Caller: "hyapp-admin-server", - DisplayUserID: strconv.FormatInt(displayUserID, 10), + DisplayUserID: rawID, }) if err != nil { - return 0, err + if !isGRPCNotFound(err) { + return 0, err + } + } else if identity != nil && identity.UserID > 0 { + return identity.UserID, nil } - if identity == nil || identity.UserID <= 0 { - return 0, fmt.Errorf("display_user_id %d not found", displayUserID) + + if !userIDChecked { + // 当前 active 短号解析失败后,短数字仍可能是本地/旧后台直接填写的真实 user_id 或 held 默认短号;只读 GetUser 做存在性确认,不改变 user-service 的 active 短号协议。 + found, err := s.userIDExists(ctx, requestID, displayUserID) + if err != nil { + return 0, err + } + if found { + return displayUserID, nil + } } - return identity.UserID, nil + return 0, fmt.Errorf("display_user_id %d not found", displayUserID) +} + +func (s *Service) userIDExists(ctx context.Context, requestID string, userID int64) (bool, error) { + user, err := s.userClient.GetUser(ctx, userclient.GetUserRequest{ + RequestID: requestID, + Caller: "hyapp-admin-server", + UserID: userID, + }) + if err != nil { + if isGRPCNotFound(err) { + return false, nil + } + return false, err + } + return user != nil && user.UserID > 0, nil +} + +func isGRPCNotFound(err error) bool { + return status.Code(err) == codes.NotFound } func normalizeBDLeaderPositionAlias(value string) (string, error) { diff --git a/server/admin/internal/modules/hostorg/service_test.go b/server/admin/internal/modules/hostorg/service_test.go index aeef0e9e..79317def 100644 --- a/server/admin/internal/modules/hostorg/service_test.go +++ b/server/admin/internal/modules/hostorg/service_test.go @@ -1,13 +1,86 @@ package hostorg import ( + "context" "encoding/json" + "reflect" + "strconv" "strings" "testing" "hyapp-admin-server/internal/integration/userclient" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) +type fakeHostOrgUserClient struct { + userclient.Client + users map[int64]*userclient.User + displays map[string]*userclient.UserIdentity + calls []string +} + +func (f *fakeHostOrgUserClient) GetUser(_ context.Context, req userclient.GetUserRequest) (*userclient.User, error) { + f.calls = append(f.calls, "get:"+strconv.FormatInt(req.UserID, 10)) + if user := f.users[req.UserID]; user != nil { + return user, nil + } + return nil, status.Error(codes.NotFound, "user not found") +} + +func (f *fakeHostOrgUserClient) ResolveDisplayUserID(_ context.Context, req userclient.ResolveDisplayUserIDRequest) (*userclient.UserIdentity, error) { + f.calls = append(f.calls, "resolve:"+req.DisplayUserID) + if identity := f.displays[req.DisplayUserID]; identity != nil { + return identity, nil + } + return nil, status.Error(codes.NotFound, "display_user_id not found") +} + +func TestResolveDisplayUserIDFallsBackToNumericUserID(t *testing.T) { + client := &fakeHostOrgUserClient{ + users: map[int64]*userclient.User{ + 123456: {UserID: 123456, DisplayUserID: "9999", DefaultDisplayUserID: "123456"}, + }, + displays: map[string]*userclient.UserIdentity{}, + } + service := NewService(client, nil, nil) + + userID, err := service.resolveDisplayUserID(context.Background(), "req-held-default", 123456) + if err != nil { + t.Fatalf("resolveDisplayUserID returned error: %v", err) + } + if userID != 123456 { + t.Fatalf("fallback user id mismatch: got %d", userID) + } + if want := []string{"resolve:123456", "get:123456"}; !reflect.DeepEqual(client.calls, want) { + t.Fatalf("calls mismatch: got %v want %v", client.calls, want) + } +} + +func TestResolveDisplayUserIDKeepsActiveDisplayPriority(t *testing.T) { + client := &fakeHostOrgUserClient{ + users: map[int64]*userclient.User{ + 123456: {UserID: 123456, DisplayUserID: "9999", DefaultDisplayUserID: "123456"}, + }, + displays: map[string]*userclient.UserIdentity{ + "123456": {UserID: 88, DisplayUserID: "123456"}, + }, + } + service := NewService(client, nil, nil) + + userID, err := service.resolveDisplayUserID(context.Background(), "req-active-display", 123456) + if err != nil { + t.Fatalf("resolveDisplayUserID returned error: %v", err) + } + if userID != 88 { + t.Fatalf("active display user id mismatch: got %d", userID) + } + if want := []string{"resolve:123456"}; !reflect.DeepEqual(client.calls, want) { + t.Fatalf("calls mismatch: got %v want %v", client.calls, want) + } +} + // TestNormalizeListQueryAllowsComputedSorts 锁定后台列表支持的计算排序字段不会在查询归一化时被清掉。 func TestNormalizeListQueryAllowsComputedSorts(t *testing.T) { for _, sortBy := range []string{sortByMerchantBalance, sortBySalaryWallet, sortByTotalRechargeUSDT} {