Merge branch 'test'
This commit is contained in:
commit
c52bc9d026
@ -10,21 +10,25 @@ import (
|
|||||||
"hyapp-admin-server/internal/integration/userclient"
|
"hyapp-admin-server/internal/integration/userclient"
|
||||||
"hyapp-admin-server/internal/integration/walletclient"
|
"hyapp-admin-server/internal/integration/walletclient"
|
||||||
walletv1 "hyapp.local/api/proto/wallet/v1"
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
coinSellerMerchantAssetType = "COIN_SELLER_COIN"
|
coinSellerMerchantAssetType = "COIN_SELLER_COIN"
|
||||||
coinSellerStockTypePurchase = "usdt_purchase"
|
coinSellerStockTypePurchase = "usdt_purchase"
|
||||||
coinSellerStockTypeDeduction = "usdt_deduction"
|
coinSellerStockTypeDeduction = "usdt_deduction"
|
||||||
coinSellerStockTypeCompensate = "coin_compensation"
|
coinSellerStockTypeCompensate = "coin_compensation"
|
||||||
agencyStatusDeleted = "deleted"
|
agencyStatusDeleted = "deleted"
|
||||||
membershipStatusActive = "active"
|
membershipStatusActive = "active"
|
||||||
paidCurrencyUSDT = "USDT"
|
paidCurrencyUSDT = "USDT"
|
||||||
sortByDiamond = "diamond"
|
sortByDiamond = "diamond"
|
||||||
sortByMerchantBalance = "merchant_balance"
|
sortByMerchantBalance = "merchant_balance"
|
||||||
sortBySalaryWallet = "salary_wallet"
|
sortBySalaryWallet = "salary_wallet"
|
||||||
sortByTotalRechargeUSDT = "total_recharge_usdt"
|
sortByTotalRechargeUSDT = "total_recharge_usdt"
|
||||||
bdLeaderPositionAliasMaxRunes = 64
|
bdLeaderPositionAliasMaxRunes = 64
|
||||||
|
hostOrgInternalUserIDMinDigits = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
@ -481,18 +485,62 @@ func (s *Service) resolveDisplayUserID(ctx context.Context, requestID string, di
|
|||||||
if displayUserID <= 0 {
|
if displayUserID <= 0 {
|
||||||
return 0, fmt.Errorf("display_user_id is required")
|
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{
|
identity, err := s.userClient.ResolveDisplayUserID(ctx, userclient.ResolveDisplayUserIDRequest{
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
Caller: "hyapp-admin-server",
|
Caller: "hyapp-admin-server",
|
||||||
DisplayUserID: strconv.FormatInt(displayUserID, 10),
|
DisplayUserID: rawID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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) {
|
func normalizeBDLeaderPositionAlias(value string) (string, error) {
|
||||||
|
|||||||
@ -1,13 +1,86 @@
|
|||||||
package hostorg
|
package hostorg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hyapp-admin-server/internal/integration/userclient"
|
"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 锁定后台列表支持的计算排序字段不会在查询归一化时被清掉。
|
// TestNormalizeListQueryAllowsComputedSorts 锁定后台列表支持的计算排序字段不会在查询归一化时被清掉。
|
||||||
func TestNormalizeListQueryAllowsComputedSorts(t *testing.T) {
|
func TestNormalizeListQueryAllowsComputedSorts(t *testing.T) {
|
||||||
for _, sortBy := range []string{sortByMerchantBalance, sortBySalaryWallet, sortByTotalRechargeUSDT} {
|
for _, sortBy := range []string{sortByMerchantBalance, sortBySalaryWallet, sortByTotalRechargeUSDT} {
|
||||||
|
|||||||
@ -1007,8 +1007,9 @@ func buildLaunchURL(game gamedomain.LaunchableGame, session gamedomain.LaunchSes
|
|||||||
query.Set("session", token)
|
query.Set("session", token)
|
||||||
query.Set("gameId", strings.TrimSpace(session.ProviderGameID))
|
query.Set("gameId", strings.TrimSpace(session.ProviderGameID))
|
||||||
query.Set("game_id", strings.TrimSpace(session.ProviderGameID))
|
query.Set("game_id", strings.TrimSpace(session.ProviderGameID))
|
||||||
query.Set("callbackBase", "/api/v1/zgame/callback")
|
callbackBase := config.CallbackBaseValue()
|
||||||
query.Set("callback_base", "/api/v1/zgame/callback")
|
query.Set("callbackBase", callbackBase)
|
||||||
|
query.Set("callback_base", callbackBase)
|
||||||
if config.DefaultLang != "" {
|
if config.DefaultLang != "" {
|
||||||
query.Set("lang", config.DefaultLang)
|
query.Set("lang", config.DefaultLang)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -567,6 +567,7 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) {
|
|||||||
"token_ttl_seconds":7200,
|
"token_ttl_seconds":7200,
|
||||||
"uid_mode":"display_user_id",
|
"uid_mode":"display_user_id",
|
||||||
"merchant_payload":"room_game",
|
"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_url":"https://cdn.example/zgame-bridge.js",
|
||||||
"bridge_script_version":"20260629.1",
|
"bridge_script_version":"20260629.1",
|
||||||
"game_urls":{"1001":"https://zgame.example/h5/1001"}
|
"game_urls":{"1001":"https://zgame.example/h5/1001"}
|
||||||
@ -607,8 +608,8 @@ func TestLaunchGameBuildsZGameURL(t *testing.T) {
|
|||||||
"session": token,
|
"session": token,
|
||||||
"gameId": "1001",
|
"gameId": "1001",
|
||||||
"game_id": "1001",
|
"game_id": "1001",
|
||||||
"callbackBase": "/api/v1/zgame/callback",
|
"callbackBase": "https://api.example.test/api/v1/zgame/callback",
|
||||||
"callback_base": "/api/v1/zgame/callback",
|
"callback_base": "https://api.example.test/api/v1/zgame/callback",
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"roomId": "room_1",
|
"roomId": "room_1",
|
||||||
"room_id": "room_1",
|
"room_id": "room_1",
|
||||||
|
|||||||
@ -38,6 +38,9 @@ type zgameAdapterConfig struct {
|
|||||||
// game_urls 支持 ZGame 每款游戏独立 H5 地址;launch_url_template 用于批量上新时按 provider_game_id 生成地址。
|
// game_urls 支持 ZGame 每款游戏独立 H5 地址;launch_url_template 用于批量上新时按 provider_game_id 生成地址。
|
||||||
GameURLs map[string]string `json:"game_urls"`
|
GameURLs map[string]string `json:"game_urls"`
|
||||||
LaunchURLTemplate string `json:"launch_url_template"`
|
LaunchURLTemplate string `json:"launch_url_template"`
|
||||||
|
// callback_base 可按环境覆盖为完整公网 HTTPS 地址,避免 H5 在第三方域名下把相对路径解析到厂商域名。
|
||||||
|
CallbackBase string `json:"callback_base"`
|
||||||
|
CallbackBaseAlias string `json:"callbackBase"`
|
||||||
// merchant_payload 是 ZGame consume 回调的透传字段来源;只参与厂商回传,不进入内部业务判断。
|
// merchant_payload 是 ZGame consume 回调的透传字段来源;只参与厂商回传,不进入内部业务判断。
|
||||||
MerchantPayload string `json:"merchant_payload"`
|
MerchantPayload string `json:"merchant_payload"`
|
||||||
// level/gender 是 ZGame 用户资料响应里的展示字段;真实等级接入前保持显式默认值,避免从其它体系误推。
|
// level/gender 是 ZGame 用户资料响应里的展示字段;真实等级接入前保持显式默认值,避免从其它体系误推。
|
||||||
@ -58,6 +61,7 @@ func zgameConfigFromPlatform(value any) zgameAdapterConfig {
|
|||||||
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.LaunchURLTemplate = strings.TrimSpace(config.LaunchURLTemplate)
|
config.LaunchURLTemplate = strings.TrimSpace(config.LaunchURLTemplate)
|
||||||
|
config.CallbackBase = strings.TrimSpace(firstNonEmpty(config.CallbackBase, config.CallbackBaseAlias))
|
||||||
config.MerchantPayload = strings.TrimSpace(config.MerchantPayload)
|
config.MerchantPayload = strings.TrimSpace(config.MerchantPayload)
|
||||||
config.GameURLs = normalizeStringMap(config.GameURLs)
|
config.GameURLs = normalizeStringMap(config.GameURLs)
|
||||||
return config
|
return config
|
||||||
@ -70,6 +74,13 @@ func (c zgameAdapterConfig) TokenTTL() time.Duration {
|
|||||||
return zgameDefaultTokenTTL
|
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 {
|
func zgameLaunchBaseURL(game gamedomain.LaunchableGame, config zgameAdapterConfig) string {
|
||||||
for _, key := range []string{game.ProviderGameID, game.GameID, strings.ToLower(game.GameID)} {
|
for _, key := range []string{game.ProviderGameID, game.GameID, strings.ToLower(game.GameID)} {
|
||||||
if raw := strings.TrimSpace(config.GameURLs[strings.TrimSpace(key)]); raw != "" {
|
if raw := strings.TrimSpace(config.GameURLs[strings.TrimSpace(key)]); raw != "" {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user