灵仙游戏的key

This commit is contained in:
hy001 2026-05-12 15:33:32 +08:00
parent 171ba88a2e
commit 2bba927458
3 changed files with 209 additions and 10 deletions

View File

@ -2,6 +2,7 @@ package gameopen
import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"net/url"
@ -71,6 +72,27 @@ func normalizeCallbackToken(token string) string {
return parts[0] + "." + parts[1]
}
func callbackTokenSysOrigin(token string) string {
token = normalizeCallbackToken(token)
parts := strings.SplitN(token, ".", 2)
if len(parts) != 2 || strings.TrimSpace(parts[1]) == "" {
return ""
}
decoded, err := base64.StdEncoding.DecodeString(parts[1])
if err != nil {
return ""
}
payload := strings.TrimSpace(string(decoded))
if unescaped, err := url.QueryUnescape(payload); err == nil {
payload = strings.TrimSpace(unescaped)
}
fields := strings.Split(payload, ":")
if len(fields) < 3 {
return ""
}
return strings.ToUpper(strings.TrimSpace(fields[2]))
}
func receiptTypeFromType(typed int) string {
if typed == 2 {
return "INCOME"

View File

@ -21,7 +21,13 @@ func (s *GameOpenService) HandleQueryUser(ctx context.Context, req QueryUserRequ
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
}
if !verifySign(req.Sign, buildQueryUserSign(req, s.cfg.GameOpen.AppKey)) {
appKey := s.resolveCallbackAppKey(ctx, req.GameID, req.Token)
if strings.TrimSpace(appKey) == "" {
resp := failResponse(gameOpenCodeBadRequest, "app key is blank")
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
}
if !verifySign(req.Sign, buildQueryUserSign(req, appKey)) {
resp := failResponse(gameOpenCodeSignatureError, "signature error")
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
@ -76,7 +82,13 @@ func (s *GameOpenService) HandleUpdateCoin(ctx context.Context, req UpdateCoinRe
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
}
if !verifySign(req.Sign, buildUpdateCoinSign(req, s.cfg.GameOpen.AppKey)) {
appKey := s.resolveCallbackAppKey(ctx, req.GameID, req.Token)
if strings.TrimSpace(appKey) == "" {
resp := failResponse(gameOpenCodeBadRequest, "app key is blank")
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
}
if !verifySign(req.Sign, buildUpdateCoinSign(req, appKey)) {
resp := failResponse(gameOpenCodeSignatureError, "signature error")
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
@ -147,7 +159,13 @@ func (s *GameOpenService) HandleSupplement(ctx context.Context, req SupplementRe
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
}
if !verifySign(req.Sign, buildSupplementSign(req, s.cfg.GameOpen.AppKey)) {
appKey := s.resolveCallbackAppKey(ctx, req.GameID, "")
if strings.TrimSpace(appKey) == "" {
resp := failResponse(gameOpenCodeBadRequest, "app key is blank")
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
}
if !verifySign(req.Sign, buildSupplementSign(req, appKey)) {
resp := failResponse(gameOpenCodeSignatureError, "signature error")
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
return resp
@ -187,6 +205,7 @@ func (s *GameOpenService) GetIntegrationInfo(ctx context.Context, publicBaseURL
if err != nil {
return nil, err
}
appKey := s.resolveCallbackAppKey(ctx, "", token)
return &IntegrationInfoResponse{
Domain: baseURL,
QueryUser: baseURL + "/game/open/user-info",
@ -195,10 +214,66 @@ func (s *GameOpenService) GetIntegrationInfo(ctx context.Context, publicBaseURL
UID: defaultIfBlank(s.cfg.GameOpen.TestAccount, strconv.FormatInt(s.cfg.GameOpen.TestUserID, 10)),
Password: s.cfg.GameOpen.TestPassword,
Token: strings.TrimSpace(token),
Key: s.cfg.GameOpen.AppKey,
Key: appKey,
}, nil
}
func (s *GameOpenService) resolveCallbackAppKey(ctx context.Context, gameID, token string) string {
if s != nil && s.repo.DB != nil {
if sysOrigin := callbackTokenSysOrigin(token); sysOrigin != "" {
if appKey := s.resolveActiveLingxianAppKey(ctx, sysOrigin); appKey != "" {
return appKey
}
}
if appKey := s.resolveLingxianAppKeyByGame(ctx, gameID); appKey != "" {
return appKey
}
}
if s == nil {
return ""
}
return strings.TrimSpace(s.cfg.GameOpen.AppKey)
}
func (s *GameOpenService) resolveLingxianAppKeyByGame(ctx context.Context, gameID string) string {
gameID = strings.TrimSpace(gameID)
if gameID == "" {
return ""
}
var row struct {
AppKey string
}
err := s.repo.DB.WithContext(ctx).
Table("lingxian_provider_config AS cfg").
Select("cfg.app_key AS app_key").
Joins("JOIN sys_game_list_vendor_ext ext ON ext.sys_origin = cfg.sys_origin AND ext.profile = cfg.profile AND ext.vendor_type = ? AND ext.enabled = ?", "LINGXIAN", true).
Where("cfg.active = ? AND ext.vendor_game_id = ?", true, gameID).
Order("cfg.update_time DESC").
Limit(1).
Scan(&row).Error
if err != nil {
return ""
}
return strings.TrimSpace(row.AppKey)
}
func (s *GameOpenService) resolveActiveLingxianAppKey(ctx context.Context, sysOrigin string) string {
sysOrigin = strings.ToUpper(strings.TrimSpace(sysOrigin))
if sysOrigin == "" {
return ""
}
var row model.LingxianProviderConfig
err := s.repo.DB.WithContext(ctx).
Where("sys_origin = ? AND active = ?", sysOrigin, true).
Order("update_time DESC").
Limit(1).
First(&row).Error
if err != nil {
return ""
}
return strings.TrimSpace(row.AppKey)
}
func (s *GameOpenService) readUserBalance(ctx context.Context, userID int64) (int64, error) {
balances, err := s.java.MapGoldBalance(ctx, []int64{userID})
if err != nil {
@ -327,8 +402,6 @@ func (s *GameOpenService) validateQueryUserRequest(req QueryUserRequest) error {
return newBadRequest("token is required")
case strings.TrimSpace(req.Sign) == "":
return newBadRequest("sign is required")
case strings.TrimSpace(s.cfg.GameOpen.AppKey) == "":
return newBadRequest("app key is blank")
default:
return nil
}
@ -352,8 +425,6 @@ func (s *GameOpenService) validateUpdateCoinRequest(req UpdateCoinRequest) error
return newBadRequest("token is required")
case strings.TrimSpace(req.Sign) == "":
return newBadRequest("sign is required")
case strings.TrimSpace(s.cfg.GameOpen.AppKey) == "":
return newBadRequest("app key is blank")
default:
return nil
}
@ -373,8 +444,6 @@ func (s *GameOpenService) validateSupplementRequest(req SupplementRequest) error
return newBadRequest("coin is required")
case strings.TrimSpace(req.Sign) == "":
return newBadRequest("sign is required")
case strings.TrimSpace(s.cfg.GameOpen.AppKey) == "":
return newBadRequest("app key is blank")
default:
return nil
}

View File

@ -3,8 +3,13 @@ package gameopen
import (
"chatapp3-golang/internal/config"
"chatapp3-golang/internal/integration"
"chatapp3-golang/internal/model"
"context"
"testing"
"time"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type stubGateway struct {
@ -259,6 +264,109 @@ func TestHandleUpdateCoinDuplicateOrderReturns10003(t *testing.T) {
}
}
func TestCallbacksUseLingxianProviderConfigAppKey(t *testing.T) {
db := newGameOpenTestDB(t)
now := time.Now()
if err := db.Create(&model.LingxianProviderConfig{
ID: 1,
SysOrigin: "LIKEI",
Profile: "PROD",
Active: true,
AppKey: "backend-lingxian-key",
CreateTime: now,
UpdateTime: now,
}).Error; err != nil {
t.Fatalf("create lingxian config: %v", err)
}
if err := db.Create(&model.SysGameListVendorExt{
ID: 2,
GameListConfigID: 3,
SysOrigin: "LIKEI",
Profile: "PROD",
VendorType: "LINGXIAN",
VendorGameID: "101",
Enabled: true,
CreateTime: now,
UpdateTime: now,
}).Error; err != nil {
t.Fatalf("create vendor ext: %v", err)
}
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 1234567, SysOrigin: "LIKEI"},
profile: integration.UserProfile{
ID: 1234567,
Account: "1234567",
},
accountMap: map[string]integration.UserProfile{
"1234567": {ID: 1234567, Account: "1234567"},
},
balance: 99,
}
service := NewGameOpenService(config.Config{
GameOpen: config.GameOpenConfig{AppKey: "wrong-env-key"},
}, db, gateway)
queryReq := QueryUserRequest{
GameID: "101",
UID: "1234567",
Token: "token",
RoomID: "room-1",
}
queryReq.Sign = buildQueryUserSign(queryReq, "backend-lingxian-key")
if resp := service.HandleQueryUser(context.Background(), queryReq, "{}"); resp.ErrorCode != 0 {
t.Fatalf("HandleQueryUser() error = %+v", resp)
}
updateReq := 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",
}
updateReq.Sign = buildUpdateCoinSign(updateReq, "backend-lingxian-key")
if resp := service.HandleUpdateCoin(context.Background(), updateReq, "{}"); resp.ErrorCode != 0 {
t.Fatalf("HandleUpdateCoin() error = %+v", resp)
}
supplementReq := SupplementRequest{
OrderID: "order-2",
GameID: "101",
RoundID: "round-2",
UID: "1234567",
Coin: 1,
RewardType: 1,
WinID: "win-2",
RoomID: "room-1",
}
supplementReq.Sign = buildSupplementSign(supplementReq, "backend-lingxian-key")
if resp := service.HandleSupplement(context.Background(), supplementReq, "{}"); resp.ErrorCode != 0 {
t.Fatalf("HandleSupplement() error = %+v", resp)
}
}
func newGameOpenTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
if err != nil {
t.Fatalf("open sqlite: %v", err)
}
if err := db.AutoMigrate(
&model.LingxianProviderConfig{},
&model.SysGameListVendorExt{},
&model.GameOpenCallbackLog{},
); err != nil {
t.Fatalf("migrate sqlite: %v", err)
}
return db
}
func TestHandleQueryUserAcceptsAccountUID(t *testing.T) {
gateway := &stubGateway{
credential: integration.UserCredential{UserID: 4569421795454615552, SysOrigin: "LIKEI"},