灵仙游戏的key
This commit is contained in:
parent
171ba88a2e
commit
2bba927458
@ -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"
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user