灵仙游戏的key
This commit is contained in:
parent
171ba88a2e
commit
2bba927458
@ -2,6 +2,7 @@ package gameopen
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -71,6 +72,27 @@ func normalizeCallbackToken(token string) string {
|
|||||||
return parts[0] + "." + parts[1]
|
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 {
|
func receiptTypeFromType(typed int) string {
|
||||||
if typed == 2 {
|
if typed == 2 {
|
||||||
return "INCOME"
|
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)
|
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
||||||
return resp
|
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")
|
resp := failResponse(gameOpenCodeSignatureError, "signature error")
|
||||||
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
s.saveCallbackLog(ctx, "query-user", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
||||||
return resp
|
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)
|
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
||||||
return resp
|
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")
|
resp := failResponse(gameOpenCodeSignatureError, "signature error")
|
||||||
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
s.saveCallbackLog(ctx, "update-coin", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
||||||
return resp
|
return resp
|
||||||
@ -147,7 +159,13 @@ func (s *GameOpenService) HandleSupplement(ctx context.Context, req SupplementRe
|
|||||||
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
||||||
return resp
|
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")
|
resp := failResponse(gameOpenCodeSignatureError, "signature error")
|
||||||
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
s.saveCallbackLog(ctx, "supplement", rawJSON, resp, "", req, nil, gameOpenLogStatusFailed)
|
||||||
return resp
|
return resp
|
||||||
@ -187,6 +205,7 @@ func (s *GameOpenService) GetIntegrationInfo(ctx context.Context, publicBaseURL
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
appKey := s.resolveCallbackAppKey(ctx, "", token)
|
||||||
return &IntegrationInfoResponse{
|
return &IntegrationInfoResponse{
|
||||||
Domain: baseURL,
|
Domain: baseURL,
|
||||||
QueryUser: baseURL + "/game/open/user-info",
|
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)),
|
UID: defaultIfBlank(s.cfg.GameOpen.TestAccount, strconv.FormatInt(s.cfg.GameOpen.TestUserID, 10)),
|
||||||
Password: s.cfg.GameOpen.TestPassword,
|
Password: s.cfg.GameOpen.TestPassword,
|
||||||
Token: strings.TrimSpace(token),
|
Token: strings.TrimSpace(token),
|
||||||
Key: s.cfg.GameOpen.AppKey,
|
Key: appKey,
|
||||||
}, nil
|
}, 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) {
|
func (s *GameOpenService) readUserBalance(ctx context.Context, userID int64) (int64, error) {
|
||||||
balances, err := s.java.MapGoldBalance(ctx, []int64{userID})
|
balances, err := s.java.MapGoldBalance(ctx, []int64{userID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -327,8 +402,6 @@ func (s *GameOpenService) validateQueryUserRequest(req QueryUserRequest) error {
|
|||||||
return newBadRequest("token is required")
|
return newBadRequest("token is required")
|
||||||
case strings.TrimSpace(req.Sign) == "":
|
case strings.TrimSpace(req.Sign) == "":
|
||||||
return newBadRequest("sign is required")
|
return newBadRequest("sign is required")
|
||||||
case strings.TrimSpace(s.cfg.GameOpen.AppKey) == "":
|
|
||||||
return newBadRequest("app key is blank")
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -352,8 +425,6 @@ func (s *GameOpenService) validateUpdateCoinRequest(req UpdateCoinRequest) error
|
|||||||
return newBadRequest("token is required")
|
return newBadRequest("token is required")
|
||||||
case strings.TrimSpace(req.Sign) == "":
|
case strings.TrimSpace(req.Sign) == "":
|
||||||
return newBadRequest("sign is required")
|
return newBadRequest("sign is required")
|
||||||
case strings.TrimSpace(s.cfg.GameOpen.AppKey) == "":
|
|
||||||
return newBadRequest("app key is blank")
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -373,8 +444,6 @@ func (s *GameOpenService) validateSupplementRequest(req SupplementRequest) error
|
|||||||
return newBadRequest("coin is required")
|
return newBadRequest("coin is required")
|
||||||
case strings.TrimSpace(req.Sign) == "":
|
case strings.TrimSpace(req.Sign) == "":
|
||||||
return newBadRequest("sign is required")
|
return newBadRequest("sign is required")
|
||||||
case strings.TrimSpace(s.cfg.GameOpen.AppKey) == "":
|
|
||||||
return newBadRequest("app key is blank")
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,13 @@ package gameopen
|
|||||||
import (
|
import (
|
||||||
"chatapp3-golang/internal/config"
|
"chatapp3-golang/internal/config"
|
||||||
"chatapp3-golang/internal/integration"
|
"chatapp3-golang/internal/integration"
|
||||||
|
"chatapp3-golang/internal/model"
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stubGateway struct {
|
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) {
|
func TestHandleQueryUserAcceptsAccountUID(t *testing.T) {
|
||||||
gateway := &stubGateway{
|
gateway := &stubGateway{
|
||||||
credential: integration.UserCredential{UserID: 4569421795454615552, SysOrigin: "LIKEI"},
|
credential: integration.UserCredential{UserID: 4569421795454615552, SysOrigin: "LIKEI"},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user