灵仙gateway

This commit is contained in:
hy001 2026-04-23 16:03:13 +08:00
parent 3e2719f682
commit 1d2bc6eafd
5 changed files with 252 additions and 8 deletions

View File

@ -88,6 +88,16 @@ func (g *Gateways) GetUserProfile(ctx context.Context, userID int64) (UserProfil
return g.Profile.GetUserProfile(ctx, userID) return g.Profile.GetUserProfile(ctx, userID)
} }
// GetUserProfileByAccount 透传到资料网关。
func (g *Gateways) GetUserProfileByAccount(ctx context.Context, sysOrigin, account string) (UserProfile, error) {
return g.Profile.GetUserProfileByAccount(ctx, sysOrigin, account)
}
// GetUserProfileByAccountOne 透传到资料网关。
func (g *Gateways) GetUserProfileByAccountOne(ctx context.Context, account string) (UserProfile, error) {
return g.Profile.GetUserProfileByAccountOne(ctx, account)
}
// GetUserLanguage 透传到资料网关。 // GetUserLanguage 透传到资料网关。
func (g *Gateways) GetUserLanguage(ctx context.Context, userID int64) (string, error) { func (g *Gateways) GetUserLanguage(ctx context.Context, userID int64) (string, error) {
return g.Profile.GetUserLanguage(ctx, userID) return g.Profile.GetUserLanguage(ctx, userID)
@ -198,6 +208,16 @@ func (g *ProfileGateway) GetUserProfile(ctx context.Context, userID int64) (User
return g.client.GetUserProfile(ctx, userID) return g.client.GetUserProfile(ctx, userID)
} }
// GetUserProfileByAccount 根据账号查询用户资料。
func (g *ProfileGateway) GetUserProfileByAccount(ctx context.Context, sysOrigin, account string) (UserProfile, error) {
return g.client.GetUserProfileByAccount(ctx, sysOrigin, account)
}
// GetUserProfileByAccountOne 根据账号查询单个用户资料。
func (g *ProfileGateway) GetUserProfileByAccountOne(ctx context.Context, account string) (UserProfile, error) {
return g.client.GetUserProfileByAccountOne(ctx, account)
}
// GetUserLanguage 查询用户语言。 // GetUserLanguage 查询用户语言。
func (g *ProfileGateway) GetUserLanguage(ctx context.Context, userID int64) (string, error) { func (g *ProfileGateway) GetUserLanguage(ctx context.Context, userID int64) (string, error) {
return g.client.GetUserLanguage(ctx, userID) return g.client.GetUserLanguage(ctx, userID)

View File

@ -38,6 +38,7 @@ type ConsoleAccount struct {
type UserProfile struct { type UserProfile struct {
ID Int64Value `json:"id"` ID Int64Value `json:"id"`
Account string `json:"account"`
UserAvatar string `json:"userAvatar"` UserAvatar string `json:"userAvatar"`
UserNickname string `json:"userNickname"` UserNickname string `json:"userNickname"`
} }
@ -349,6 +350,32 @@ func (c *Client) GetUserProfile(ctx context.Context, userID int64) (UserProfile,
return resp.Body, nil return resp.Body, nil
} }
func (c *Client) GetUserProfileByAccount(ctx context.Context, sysOrigin, account string) (UserProfile, error) {
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getByAccount?sysOrigin=" +
url.QueryEscape(strings.TrimSpace(sysOrigin)) + "&account=" + url.QueryEscape(strings.TrimSpace(account))
var resp resultResponse[UserProfile]
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
return UserProfile{}, err
}
if int64(resp.Body.ID) == 0 {
return UserProfile{}, fmt.Errorf("empty user profile response")
}
return resp.Body, nil
}
func (c *Client) GetUserProfileByAccountOne(ctx context.Context, account string) (UserProfile, error) {
endpoint := c.cfg.Java.OtherBaseURL + "/user-profile/client/getByAccountOne?account=" +
url.QueryEscape(strings.TrimSpace(account))
var resp resultResponse[UserProfile]
if err := c.getJSON(ctx, endpoint, nil, &resp); err != nil {
return UserProfile{}, err
}
if int64(resp.Body.ID) == 0 {
return UserProfile{}, fmt.Errorf("empty user profile response")
}
return resp.Body, nil
}
func (c *Client) rewardBridgeBaseURL() string { func (c *Client) rewardBridgeBaseURL() string {
if baseURL := strings.TrimRight(strings.TrimSpace(c.cfg.Java.ConsoleBaseURL), "/"); baseURL != "" { if baseURL := strings.TrimRight(strings.TrimSpace(c.cfg.Java.ConsoleBaseURL), "/"); baseURL != "" {
return baseURL return baseURL

View File

@ -31,14 +31,19 @@ func (s *GameOpenService) HandleQueryUser(ctx context.Context, req QueryUserRequ
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed) s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp return resp
} }
userID := int64(credential.UserID) resolved, err := s.resolveUserWithToken(ctx, credential, req.UID)
if req.UID != strconv.FormatInt(userID, 10) { if err != nil {
resp := failResponse(gameOpenCodeTokenInvalid, "uid token mismatch") resp := failResponse(gameOpenCodeTokenInvalid, "uid token mismatch")
userID := int64(credential.UserID)
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, credential.SysOrigin, req, &userID, gameOpenLogStatusFailed) s.saveCallbackLog(ctx, "query-user", rawJSON, resp, credential.SysOrigin, req, &userID, gameOpenLogStatusFailed)
return resp return resp
} }
userID := resolved.userID
profile, err := s.java.GetUserProfile(ctx, userID) profile := resolved.profile
if int64(profile.ID) == 0 {
profile, err = s.java.GetUserProfile(ctx, userID)
}
if err != nil { if err != nil {
resp := failResponse(gameOpenCodeServerError, "profile load failed") resp := failResponse(gameOpenCodeServerError, "profile load failed")
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, credential.SysOrigin, req, &userID, gameOpenLogStatusFailed) s.saveCallbackLog(ctx, "query-user", rawJSON, resp, credential.SysOrigin, req, &userID, gameOpenLogStatusFailed)
@ -80,12 +85,14 @@ func (s *GameOpenService) HandleUpdateCoin(ctx context.Context, req UpdateCoinRe
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed) s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp return resp
} }
userID := int64(credential.UserID) resolved, err := s.resolveUserWithToken(ctx, credential, req.UID)
if req.UID != strconv.FormatInt(userID, 10) { if err != nil {
resp := failResponse(gameOpenCodeTokenInvalid, "uid token mismatch") resp := failResponse(gameOpenCodeTokenInvalid, "uid token mismatch")
userID := int64(credential.UserID)
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, credential.SysOrigin, req, &userID, gameOpenLogStatusFailed) s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, credential.SysOrigin, req, &userID, gameOpenLogStatusFailed)
return resp return resp
} }
userID := resolved.userID
eventID := "GAME_OPEN:" + strings.TrimSpace(req.OrderID) eventID := "GAME_OPEN:" + strings.TrimSpace(req.OrderID)
exists, err := s.java.ExistsGoldEvent(ctx, eventID) exists, err := s.java.ExistsGoldEvent(ctx, eventID)
@ -138,7 +145,12 @@ func (s *GameOpenService) HandleSupplement(ctx context.Context, req SupplementRe
return resp return resp
} }
userID := parseInt64Default(req.UID) userID, err := s.resolveUserID(ctx, req.UID)
if err != nil {
resp := failResponse(gameOpenCodeTokenInvalid, "uid invalid")
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
}
balance, err := s.readUserBalance(ctx, userID) balance, err := s.readUserBalance(ctx, userID)
if err != nil { if err != nil {
resp := failResponse(gameOpenCodeServerError, "balance load failed") resp := failResponse(gameOpenCodeServerError, "balance load failed")
@ -187,6 +199,49 @@ func (s *GameOpenService) readUserBalance(ctx context.Context, userID int64) (in
return balances[userID], nil return balances[userID], nil
} }
type resolvedGameOpenUser struct {
userID int64
profile integration.UserProfile
}
func (s *GameOpenService) resolveUserWithToken(ctx context.Context, credential integration.UserCredential, uid string) (resolvedGameOpenUser, error) {
userID := int64(credential.UserID)
uid = strings.TrimSpace(uid)
if uid == strconv.FormatInt(userID, 10) {
return resolvedGameOpenUser{userID: userID}, nil
}
profile, err := s.java.GetUserProfileByAccount(ctx, credential.SysOrigin, uid)
if err == nil {
if int64(profile.ID) == userID {
return resolvedGameOpenUser{userID: userID, profile: profile}, nil
}
return resolvedGameOpenUser{}, fmt.Errorf("uid token mismatch")
}
profile, err = s.java.GetUserProfile(ctx, userID)
if err != nil {
return resolvedGameOpenUser{}, err
}
if strings.EqualFold(strings.TrimSpace(profile.Account), uid) {
return resolvedGameOpenUser{userID: userID, profile: profile}, nil
}
return resolvedGameOpenUser{}, fmt.Errorf("uid token mismatch")
}
func (s *GameOpenService) resolveUserID(ctx context.Context, uid string) (int64, error) {
uid = strings.TrimSpace(uid)
profile, err := s.java.GetUserProfileByAccountOne(ctx, uid)
if err == nil && int64(profile.ID) > 0 {
return int64(profile.ID), nil
}
if userID := parseInt64Default(uid); userID > 0 {
return userID, nil
}
return 0, fmt.Errorf("uid invalid")
}
func (s *GameOpenService) saveCallbackLog(ctx context.Context, requestType, rawJSON string, resp CallbackResponse, sysOrigin string, req any, userID *int64, status string) { func (s *GameOpenService) saveCallbackLog(ctx context.Context, requestType, rawJSON string, resp CallbackResponse, sysOrigin string, req any, userID *int64, status string) {
if s == nil || s.repo.DB == nil { if s == nil || s.repo.DB == nil {
return return

View File

@ -11,6 +11,7 @@ type stubGateway struct {
credential integration.UserCredential credential integration.UserCredential
token string token string
profile integration.UserProfile profile integration.UserProfile
accountMap map[string]integration.UserProfile
balance int64 balance int64
exists bool exists bool
cmd integration.GoldReceiptCommand cmd integration.GoldReceiptCommand
@ -28,8 +29,34 @@ func (s *stubGateway) GetUserProfile(context.Context, int64) (integration.UserPr
return s.profile, nil return s.profile, nil
} }
func (s *stubGateway) GetUserProfileByAccount(_ context.Context, _ string, account string) (integration.UserProfile, error) {
if profile, ok := s.accountMap[account]; ok {
return profile, nil
}
return integration.UserProfile{}, context.Canceled
}
func (s *stubGateway) GetUserProfileByAccountOne(_ context.Context, account string) (integration.UserProfile, error) {
if profile, ok := s.accountMap[account]; ok {
return profile, nil
}
return integration.UserProfile{}, context.Canceled
}
func (s *stubGateway) MapGoldBalance(context.Context, []int64) (map[int64]int64, error) { func (s *stubGateway) MapGoldBalance(context.Context, []int64) (map[int64]int64, error) {
return map[int64]int64{int64(s.credential.UserID): s.balance}, nil result := map[int64]int64{}
if int64(s.credential.UserID) > 0 {
result[int64(s.credential.UserID)] = s.balance
}
if int64(s.profile.ID) > 0 {
result[int64(s.profile.ID)] = s.balance
}
for _, profile := range s.accountMap {
if int64(profile.ID) > 0 {
result[int64(profile.ID)] = s.balance
}
}
return result, nil
} }
func (s *stubGateway) ExistsGoldEvent(context.Context, string) (bool, error) { func (s *stubGateway) ExistsGoldEvent(context.Context, string) (bool, error) {
@ -46,6 +73,7 @@ func TestHandleQueryUser(t *testing.T) {
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"}, credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
profile: integration.UserProfile{ profile: integration.UserProfile{
ID: 1234567, ID: 1234567,
Account: "1234567",
UserNickname: "tester", UserNickname: "tester",
UserAvatar: "https://example.com/avatar.png", UserAvatar: "https://example.com/avatar.png",
}, },
@ -78,7 +106,11 @@ func TestHandleQueryUser(t *testing.T) {
func TestHandleUpdateCoin(t *testing.T) { func TestHandleUpdateCoin(t *testing.T) {
gateway := &stubGateway{ gateway := &stubGateway{
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"}, credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
balance: 99, profile: integration.UserProfile{
ID: 1234567,
Account: "1234567",
},
balance: 99,
} }
service := NewGameOpenService(config.Config{ service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"}, GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
@ -109,6 +141,114 @@ func TestHandleUpdateCoin(t *testing.T) {
} }
} }
func TestHandleQueryUserAcceptsAccountUID(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 4569421795454615552, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 4569421795454615552,
Account: "1234567",
UserNickname: "tester",
UserAvatar: "https://example.com/avatar.png",
},
accountMap: map[string]integration.UserProfile{
"1234567": {
ID: 4569421795454615552,
Account: "1234567",
UserNickname: "tester",
UserAvatar: "https://example.com/avatar.png",
},
},
balance: 88,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := QueryUserRequest{
GameID: "101",
UID: "1234567",
Token: "token",
RoomID: "room-1",
}
req.Sign = buildQueryUserSign(req, "game-open-test-key")
resp := service.HandleQueryUser(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleQueryUser() error = %+v", resp)
}
}
func TestHandleUpdateCoinAcceptsAccountUID(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 4569421795454615552, SysOrigin: "LIKEI"},
accountMap: map[string]integration.UserProfile{
"1234567": {
ID: 4569421795454615552,
Account: "1234567",
},
},
balance: 99,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := UpdateCoinRequest{
OrderID: "order-1",
GameID: "101",
RoundID: "round-1",
UID: "1234567",
Coin: 15,
Type: 2,
RewardType: 2,
Token: "token",
WinID: "win-1",
RoomID: "room-1",
}
req.Sign = buildUpdateCoinSign(req, "game-open-test-key")
resp := service.HandleUpdateCoin(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleUpdateCoin() error = %+v", resp)
}
}
func TestHandleSupplementAcceptsAccountUID(t *testing.T) {
gateway := &stubGateway{
accountMap: map[string]integration.UserProfile{
"1234567": {
ID: 4569421795454615552,
Account: "1234567",
},
},
balance: 66,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "game-open-test-key"},
}, nil, gateway)
req := SupplementRequest{
OrderID: "order-1",
GameID: "101",
RoundID: "round-1",
UID: "1234567",
Coin: 1,
RewardType: 1,
WinID: "win-1",
RoomID: "room-1",
}
req.Sign = buildSupplementSign(req, "game-open-test-key")
resp := service.HandleSupplement(context.Background(), req, "{}")
if resp.ErrorCode != 0 {
t.Fatalf("HandleSupplement() error = %+v", resp)
}
data, ok := resp.Data.(map[string]any)
if !ok {
t.Fatalf("HandleSupplement() data type = %T", resp.Data)
}
if got := data["coin"]; got != int64(66) {
t.Fatalf("HandleSupplement() coin = %v, want 66", got)
}
}
func TestGetIntegrationInfo(t *testing.T) { func TestGetIntegrationInfo(t *testing.T) {
gateway := &stubGateway{token: "token-123"} gateway := &stubGateway{token: "token-123"}
service := NewGameOpenService(config.Config{ service := NewGameOpenService(config.Config{

View File

@ -29,6 +29,8 @@ type gameOpenGateway interface {
AuthenticateToken(ctx context.Context, token string) (integration.UserCredential, error) AuthenticateToken(ctx context.Context, token string) (integration.UserCredential, error)
CreateIfAbsentUserCredentialToken(ctx context.Context, userID int64) (string, error) CreateIfAbsentUserCredentialToken(ctx context.Context, userID int64) (string, error)
GetUserProfile(ctx context.Context, userID int64) (integration.UserProfile, error) GetUserProfile(ctx context.Context, userID int64) (integration.UserProfile, error)
GetUserProfileByAccount(ctx context.Context, sysOrigin, account string) (integration.UserProfile, error)
GetUserProfileByAccountOne(ctx context.Context, account string) (integration.UserProfile, error)
MapGoldBalance(ctx context.Context, userIDs []int64) (map[int64]int64, error) MapGoldBalance(ctx context.Context, userIDs []int64) (map[int64]int64, error)
ExistsGoldEvent(ctx context.Context, eventID string) (bool, error) ExistsGoldEvent(ctx context.Context, eventID string) (bool, error)
ChangeGoldBalance(ctx context.Context, cmd integration.GoldReceiptCommand) error ChangeGoldBalance(ctx context.Context, cmd integration.GoldReceiptCommand) error