调整游戏人生封装
This commit is contained in:
@ -5,12 +5,16 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"server/internal/dao"
|
||||
"server/internal/model/do"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"server/internal/consts"
|
||||
"server/internal/model"
|
||||
"server/utility/ecode"
|
||||
"server/utility/encrypt"
|
||||
"server/utility/rsa"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
@ -19,341 +23,349 @@ import (
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
// Config holds gamelife client configuration.
|
||||
type Config struct {
|
||||
PlatID string
|
||||
BrandID string
|
||||
Secret string
|
||||
Mode string
|
||||
}
|
||||
|
||||
// gamelifeClient manages interactions with the GameLife system.
|
||||
type gamelifeClient struct {
|
||||
PlatId string `json:"platId" `
|
||||
BrandId string `json:"brand_id"`
|
||||
Secret string `json:"secret"`
|
||||
Mode string `json:"mode" `
|
||||
keyivUrlMap map[string]string `json:"-"` // 存储获取用户 aes key 和 iv 的 url
|
||||
boundUrlMap map[string]string `json:"-"` // 存储用户绑定状态的 url
|
||||
unBoundUrlMap map[string]string `json:"-"` // 存储用户解绑状态的 url
|
||||
getBoundUrl map[string]string `json:"-"` // 存储用户绑定状态的 url
|
||||
taskUrlMap map[string]string `json:"-"`
|
||||
config Config
|
||||
keyIVURLMap map[string]string
|
||||
boundURLMap map[string]string
|
||||
unBoundURLMap map[string]string
|
||||
getBoundURL map[string]string
|
||||
taskURLMap map[string]string
|
||||
}
|
||||
|
||||
// URLMaps defines the URL mappings for different environments.
|
||||
var URLMaps = struct {
|
||||
KeyIV map[string]string
|
||||
Bound map[string]string
|
||||
UnBound map[string]string
|
||||
GetBound map[string]string
|
||||
Task map[string]string
|
||||
}{
|
||||
KeyIV: map[string]string{
|
||||
"test": "https://api-test.nes.smoba.qq.com/pvpesport.sgamenes.commcgi.commcgi/GetExplatSecret",
|
||||
"prod": "https://api.nes.smoba.qq.com/pvpesport.sgamenes.commcgi.commcgi/GetExplatSecret",
|
||||
},
|
||||
Bound: map[string]string{
|
||||
"test": "https://h5-test.cafe.qq.com/pmd-mobile.cafe.bind-account.pc.feature.bind-account/#/index",
|
||||
"prod": "https://h5.cafe.qq.com/pmd-mobile.cafe.bind-account.pc/#/index",
|
||||
},
|
||||
UnBound: map[string]string{
|
||||
"test": "https://h5-test.cafe.qq.com/pmd-mobile.cafe.bind-account.pc.feature.bind-account/#/bind-manage",
|
||||
"prod": "https://h5.cafe.qq.com/pmd-mobile.cafe.bind-account.pc/#/bind-manage",
|
||||
},
|
||||
GetBound: map[string]string{
|
||||
"test": "https://api-test.cafe.qq.com/tipmp.user.authinfo_cgi.authinfo_cgi/GetPlatUserInfo",
|
||||
"prod": "https://api.cafe.qq.com/tipmp.user.authinfo_cgi.authinfo_cgi/GetPlatUserInfo",
|
||||
},
|
||||
Task: map[string]string{
|
||||
"test": "https://api-test.cafe.qq.com/netbar.cafe.open_api.open_api/",
|
||||
"prod": "https://api.cafe.qq.com/netbar.cafe.open_api.open_api/",
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
instance *gamelifeClient
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
func newgamelifeClient(ctx context.Context) *gamelifeClient {
|
||||
instance = &gamelifeClient{
|
||||
PlatId: g.Config().MustGet(ctx, "gamelife.platId").String(),
|
||||
BrandId: g.Config().MustGet(ctx, "gamelife.brandId").String(),
|
||||
// GetGamelifeClient returns the singleton gamelifeClient instance, initializing it if necessary.
|
||||
func GetGamelifeClient(ctx context.Context) *gamelifeClient {
|
||||
once.Do(func() {
|
||||
instance = newGamelifeClient(ctx)
|
||||
})
|
||||
return instance
|
||||
}
|
||||
|
||||
// newGamelifeClient initializes a new gamelifeClient with configuration and URL mappings.
|
||||
func newGamelifeClient(ctx context.Context) *gamelifeClient {
|
||||
cfg := Config{
|
||||
PlatID: g.Config().MustGet(ctx, "gamelife.platId").String(),
|
||||
BrandID: g.Config().MustGet(ctx, "gamelife.brandId").String(),
|
||||
Secret: g.Config().MustGet(ctx, "gamelife.secret").String(),
|
||||
Mode: g.Config().MustGet(ctx, "gamelife.mode").String(),
|
||||
keyivUrlMap: map[string]string{
|
||||
"test": "https://api-test.nes.smoba.qq.com/pvpesport.sgamenes.commcgi.commcgi/GetExplatSecret",
|
||||
"prod": "https://api.nes.smoba.qq.com/pvpesport.sgamenes.commcgi.commcgi/GetExplatSecret",
|
||||
},
|
||||
boundUrlMap: map[string]string{
|
||||
"test": "https://h5-test.cafe.qq.com/pmd-mobile.cafe.bind-account.pc.feature.bind-account/#/index",
|
||||
"prod": "https://h5.cafe.qq.com/pmd-mobile.cafe.bind-account.pc/#/index",
|
||||
},
|
||||
unBoundUrlMap: map[string]string{
|
||||
"test": "https://h5-test.cafe.qq.com/pmd-mobile.cafe.bind-account.pc.feature.bind-account/#/bind-manage",
|
||||
"prod": "https://h5.cafe.qq.com/pmd-mobile.cafe.bind-account.pc/#/bind-manage",
|
||||
},
|
||||
getBoundUrl: map[string]string{
|
||||
"test": "https://api-test.cafe.qq.com/tipmp.user.authinfo_cgi.authinfo_cgi/GetPlatUserInfo",
|
||||
"prod": "https://api.cafe.qq.com/tipmp.user.authinfo_cgi.authinfo_cgi/GetPlatUserInfo",
|
||||
},
|
||||
taskUrlMap: map[string]string{
|
||||
"test": "https://api-test.cafe.qq.com/netbar.cafe.open_api.open_api/",
|
||||
"prod": "https://api.cafe.qq.com/netbar.cafe.open_api.open_api/",
|
||||
},
|
||||
}
|
||||
glog.Infof(ctx, "初始化 gamelifeClient 成功")
|
||||
return instance
|
||||
client := &gamelifeClient{
|
||||
config: cfg,
|
||||
keyIVURLMap: URLMaps.KeyIV,
|
||||
boundURLMap: URLMaps.Bound,
|
||||
unBoundURLMap: URLMaps.UnBound,
|
||||
getBoundURL: URLMaps.GetBound,
|
||||
taskURLMap: URLMaps.Task,
|
||||
}
|
||||
glog.Info(ctx, "Initialized gamelifeClient successfully", map[string]interface{}{
|
||||
"plat_id": cfg.PlatID,
|
||||
"mode": cfg.Mode,
|
||||
})
|
||||
return client
|
||||
}
|
||||
|
||||
func init() {
|
||||
ctx := context.Background()
|
||||
newgamelifeClient(ctx)
|
||||
}
|
||||
|
||||
func GetGamelifeClient(ctx context.Context) *gamelifeClient {
|
||||
if instance == nil {
|
||||
instance = newgamelifeClient(ctx)
|
||||
// GetUserKeyIV retrieves or refreshes the AES key, IV, and token for a user, storing them in cache.
|
||||
func (s *gamelifeClient) GetUserKeyIV(ctx context.Context, popenID string) (model.UserGamelifeCache, error) {
|
||||
oriData := map[string]string{
|
||||
"PlatId": s.config.PlatID,
|
||||
"PopenId": popenID,
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
func (s *gamelifeClient) GetUserKeyIV(ctx context.Context, popenId string) (cache model.UserGamelifeCache, err error) {
|
||||
// 创建加密原数据
|
||||
oriData := g.MapStrStr{
|
||||
"PlatId": s.PlatId,
|
||||
"PopenId": popenId,
|
||||
}
|
||||
marshal, err := json.Marshal(oriData)
|
||||
marshaled, err := json.Marshal(oriData)
|
||||
if err != nil {
|
||||
err = ecode.Fail.Sub("序列化 json 数据出现异常")
|
||||
return
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("序列化 json 数据出现异常")
|
||||
}
|
||||
key, err := rsa.GetRsaClient().EncryptWithRsaPublicKey(marshal)
|
||||
if err != nil {
|
||||
err = ecode.Fail.Sub("序列化 json 数据出现异常")
|
||||
return
|
||||
|
||||
encrypted, err := rsa.GetRsaClient().EncryptWithRsaPublicKey(marshaled)
|
||||
if err != nil {
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("序列化 json 数据出现异常")
|
||||
}
|
||||
// base64编码
|
||||
base64Encode := encrypt.Base64Encode(key)
|
||||
// 向游戏人生发送请求
|
||||
|
||||
type httpResult struct {
|
||||
Secret string `json:"secret"`
|
||||
Key string `json:"key"` // 该用户的token,后续的账号绑定、登录态携带都需要此参数。
|
||||
Key string `json:"key"`
|
||||
}
|
||||
var result httpResult
|
||||
resp, err := resty.New().R().SetBody(map[string]string{
|
||||
"plat_id": s.PlatId,
|
||||
"key": base64Encode,
|
||||
}).SetResult(&result).Post(s.keyivUrlMap[s.Mode])
|
||||
resp, err := resty.New().R().
|
||||
SetBody(map[string]string{
|
||||
"plat_id": s.config.PlatID,
|
||||
"key": encrypt.Base64Encode(encrypted),
|
||||
}).
|
||||
SetResult(&result).
|
||||
Post(s.keyIVURLMap[s.config.Mode])
|
||||
if err != nil || resp.StatusCode() != 200 {
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("获取用户信息失败")
|
||||
}
|
||||
|
||||
decoded, err := encrypt.Base64Decode(result.Secret)
|
||||
if err != nil {
|
||||
err = ecode.Fail.Sub("获取用户信息失败")
|
||||
return
|
||||
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
err = ecode.Fail.Sub("获取用户信息失败")
|
||||
return
|
||||
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("解密用户信息失败")
|
||||
}
|
||||
|
||||
decode, err := encrypt.Base64Decode(result.Secret)
|
||||
plain, err := rsa.GetRsaClient().DecryptWithRsaPrivateKey(decoded)
|
||||
if err != nil {
|
||||
err = ecode.Fail.Sub("解密用户信息失败")
|
||||
return
|
||||
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("解密用户信息失败")
|
||||
}
|
||||
|
||||
plain, err := rsa.GetRsaClient().DecryptWithRsaPrivateKey(decode)
|
||||
if err != nil {
|
||||
err = ecode.Fail.Sub("解密用户信息失败")
|
||||
return
|
||||
|
||||
}
|
||||
aesResult := struct {
|
||||
var aesResult struct {
|
||||
Key string `json:"key"`
|
||||
IV string `json:"iv"`
|
||||
}{}
|
||||
if err = json.Unmarshal(plain, &aesResult); err != nil {
|
||||
err = ecode.Fail.Sub("解密用户信息失败")
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(plain, &aesResult); err != nil {
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("解密用户信息失败")
|
||||
}
|
||||
|
||||
gamelifeCache := model.UserGamelifeCache{Aes: aesResult.Key, IV: aesResult.IV, Token: result.Key}
|
||||
// 将用户的 aeskey 和 iv 存储到缓存当中,用于后续请求数据加密, 固定时间 2 小时同时不同用户加上一个随机时间
|
||||
if err = g.Redis().SetEX(ctx, fmt.Sprintf(consts.GameLifeUserKey, popenId), gamelifeCache, int64(consts.GameLifeUserExpire+grand.Intn(1000))); err != nil {
|
||||
err = ecode.Fail.Sub("设置用户信息失败")
|
||||
return
|
||||
cache := model.UserGamelifeCache{
|
||||
Aes: aesResult.Key,
|
||||
IV: aesResult.IV,
|
||||
Token: result.Key,
|
||||
}
|
||||
return
|
||||
cacheKey := fmt.Sprintf(consts.GameLifeUserKey, popenID)
|
||||
if err := g.Redis().SetEX(ctx, cacheKey, cache, int64(consts.GameLifeUserExpire+grand.Intn(1000))); err != nil {
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("设置用户信息失败")
|
||||
}
|
||||
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
// GetUrl 为 GameLife 系统生成绑定或解绑定的 URL。
|
||||
// 它从缓存中获取用户加密密钥(若缓存过期则重新获取),加密用户数据,
|
||||
// 并根据操作类型(绑定或解绑)构造包含查询参数的 URL。
|
||||
//
|
||||
// 参数:
|
||||
// - ctx: 用于控制请求生命周期和取消的上下文。
|
||||
// - popenid: 用户的唯一标识符。
|
||||
// - appname: 应用程序名称。
|
||||
// - nickname: 用户昵称,仅在解绑操作时需要(绑定操作时忽略)。
|
||||
// - bindType: 绑定类型(1 表示 QQ,其他值表示微信)。
|
||||
// - isBound: 布尔值,指示操作类型(true 表示绑定,false 表示解绑)。
|
||||
//
|
||||
// 返回值:
|
||||
// - string: 构造的绑定或解绑操作的 URL。
|
||||
// - error: 如果任何步骤(缓存获取、密钥获取、JSON 序列化、加密或 URL 构造)失败,则返回错误。
|
||||
//
|
||||
// 错误:
|
||||
// - 如果发生以下情况,将返回带有具体错误信息的错误:
|
||||
// - 无法解析基础 URL。
|
||||
// - 无法从缓存或通过 GetUserKeyIV 获取用户信息。
|
||||
// - JSON 序列化或反序列化失败。
|
||||
// - AES 加密或 Base64 编码失败。
|
||||
func (s *gamelifeClient) GetUrl(ctx context.Context, popenid, appname, nickname string, bindType int, isBound bool) (string, error) {
|
||||
// 从缓存中获取用户的 aes 和 iv
|
||||
rooturl := s.boundUrlMap[s.Mode]
|
||||
// GetUrl generates a URL for binding or unbinding a user in the GameLife system.
|
||||
// It retrieves or refreshes user encryption keys from cache and constructs a URL
|
||||
// with query parameters using buildQueryParams.
|
||||
func (s *gamelifeClient) GetUrl(ctx context.Context, popenID, appName, nickname string, bindType int, isBound bool) (string, error) {
|
||||
rootURL := s.boundURLMap[s.config.Mode]
|
||||
if !isBound {
|
||||
rooturl = s.unBoundUrlMap[s.Mode]
|
||||
rootURL = s.unBoundURLMap[s.config.Mode]
|
||||
}
|
||||
|
||||
gamelifeCacheKey := fmt.Sprintf(consts.GameLifeUserKey, popenid)
|
||||
cacheData, err := g.Redis().Get(ctx, gamelifeCacheKey)
|
||||
cache, err := s.getOrRefreshCache(ctx, popenID)
|
||||
if err != nil {
|
||||
return "", ecode.Fail.Sub("从缓存中获取用户信息失败")
|
||||
return "", err
|
||||
}
|
||||
|
||||
var gamelifeCache model.UserGamelifeCache
|
||||
if cacheData.IsEmpty() {
|
||||
// 如果缓存不存在或已过期,重新调用 GetUserKeyIV
|
||||
data, err := s.GetUserKeyIV(ctx, popenid)
|
||||
if err != nil {
|
||||
return "", ecode.Fail.Sub("获取用户信息失败")
|
||||
}
|
||||
gamelifeCache = model.UserGamelifeCache{Aes: data.Aes, IV: data.IV, Token: data.Token}
|
||||
} else {
|
||||
// 缓存存在,直接解析
|
||||
if err = json.Unmarshal(cacheData.Bytes(), &gamelifeCache); err != nil {
|
||||
return "", ecode.Fail.Sub("解析用户信息失败")
|
||||
}
|
||||
cache.Params, err = s.buildQueryParams(ctx, popenID, cache, appName, nickname, bindType, isBound)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 序列化原始数据
|
||||
oriData := g.Map{
|
||||
"PopenId": popenid,
|
||||
cacheKey := fmt.Sprintf(consts.GameLifeUserKey, popenID)
|
||||
ttl, err := g.Redis().TTL(ctx, cacheKey)
|
||||
if err != nil {
|
||||
return "", ecode.Fail.Sub("获取缓存过期时间失败")
|
||||
}
|
||||
if err := g.Redis().SetEX(ctx, cacheKey, cache, ttl); err != nil {
|
||||
return "", ecode.Fail.Sub("更新缓存失败")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s?%s", rootURL, cache.Params), nil
|
||||
}
|
||||
|
||||
// GetBound retrieves the binding status of a user from the GameLife system.
|
||||
// It uses buildQueryParams to construct the encrypted user data for the POST body.
|
||||
func (s *gamelifeClient) GetBound(ctx context.Context, popenID string) (*model.UserBoundResult, error) {
|
||||
cache, err := s.getOrRefreshCache(ctx, popenID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use default values for appName, nickname, bindType, and isBound
|
||||
platUserStr, err := s.buildQueryParams(ctx, popenID, cache, s.config.PlatID, "", 1, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse platUserStr to extract Token and PlatUserInfoStr
|
||||
queryParams, err := url.ParseQuery(platUserStr)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("解析查询参数失败")
|
||||
}
|
||||
extplatDataStr := queryParams.Get("extplat_data")
|
||||
if extplatDataStr == "" {
|
||||
return nil, ecode.Fail.Sub("查询参数中缺少 extplat_data")
|
||||
}
|
||||
var extplatData map[string]string
|
||||
if err := json.Unmarshal([]byte(extplatDataStr), &extplatData); err != nil {
|
||||
return nil, ecode.Fail.Sub("解析 extplat_data 失败")
|
||||
}
|
||||
|
||||
postBody := map[string]string{
|
||||
"plat_id": s.config.PlatID,
|
||||
"plat_user_info": popenID,
|
||||
"plat_user_str": gconv.String(extplatData),
|
||||
}
|
||||
var result model.UserBoundResult
|
||||
resp, err := resty.New().R().
|
||||
SetBody(postBody).
|
||||
SetResult(&result).
|
||||
Post(s.getBoundURL[s.config.Mode])
|
||||
if err != nil || resp.StatusCode() != 200 {
|
||||
return nil, ecode.Fail.Sub("向游戏人生获取绑定信息出现异常")
|
||||
}
|
||||
|
||||
glog.Info(ctx, "Fetched user binding info", map[string]interface{}{
|
||||
"popen_id": popenID,
|
||||
"result": result,
|
||||
})
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// buildQueryParams constructs URL query parameters for GameLife requests using cached AES, IV, and token.
|
||||
// It encrypts user data and encodes it into a URL query string.
|
||||
func (s *gamelifeClient) buildQueryParams(ctx context.Context, popenID string, cache model.UserGamelifeCache, appName, nickname string, bindType int, isBound bool) (string, error) {
|
||||
oriData := map[string]interface{}{
|
||||
"PopenId": popenID,
|
||||
"TimeStamp": time.Now().Unix(),
|
||||
}
|
||||
marshal, err := json.Marshal(oriData)
|
||||
marshaled, err := json.Marshal(oriData)
|
||||
if err != nil {
|
||||
return "", ecode.Fail.Sub("序列化用户信息失败")
|
||||
}
|
||||
|
||||
// 加密用户信息
|
||||
aesEncrypt, err := encrypt.AesEncryptCBCPKCS5(marshal, []byte(gamelifeCache.Aes), []byte(gamelifeCache.IV))
|
||||
encrypted, err := encrypt.AesEncryptCBCPKCS5(marshaled, []byte(cache.Aes), []byte(cache.IV))
|
||||
if err != nil {
|
||||
return "", ecode.Fail.Sub("加密用户信息失败")
|
||||
}
|
||||
platUserInfoStr := encrypt.Base64Encode(aesEncrypt)
|
||||
explatData := g.MapStrStr{"Token": gamelifeCache.Token, "PlatUserInfoStr": platUserInfoStr}
|
||||
queryParams := url.Values{}
|
||||
queryParams.Add("extplat_plat", s.PlatId)
|
||||
queryParams.Add("app_name", appname)
|
||||
platUserInfoStr := encrypt.Base64Encode(encrypted)
|
||||
|
||||
if bindType == 1 {
|
||||
queryParams.Add("bind_type", consts.GamelifeExtplatBoundTypeQQ)
|
||||
} else {
|
||||
queryParams.Add("bind_type", consts.GamelifeExtplatBoundTypeWX)
|
||||
queryParams := url.Values{
|
||||
"extplat_plat": {s.config.PlatID},
|
||||
"app_name": {appName},
|
||||
"bind_type": {consts.GamelifeExtplatBoundTypeQQ},
|
||||
"extplat_type": {consts.GamelifeExtplatType},
|
||||
"mini_program_band": {consts.GamelifeMiniProgramBand},
|
||||
"extplat_extra": {consts.GamelifeExtplatExtraPc},
|
||||
"extplat_data": {gconv.String(map[string]string{"Token": cache.Token, "PlatUserInfoStr": platUserInfoStr})},
|
||||
}
|
||||
if bindType != 1 {
|
||||
queryParams.Set("bind_type", consts.GamelifeExtplatBoundTypeWX)
|
||||
}
|
||||
|
||||
queryParams.Add("extplat_type", consts.GamelifeExtplatType)
|
||||
queryParams.Add("mini_program_band", consts.GamelifeMiniProgramBand)
|
||||
queryParams.Add("extplat_extra", consts.GamelifeExtplatExtraPc)
|
||||
queryParams.Add("extplat_data", gconv.String(explatData))
|
||||
|
||||
// 解绑时加 nickname
|
||||
if !isBound {
|
||||
queryParams.Add("nickname", nickname)
|
||||
}
|
||||
// 将请求参数更新到缓存中
|
||||
gamelifeCache.Params = queryParams.Encode()
|
||||
// 获取原缓存值、过期时间
|
||||
ttl, err := g.Redis().TTL(ctx, gamelifeCacheKey)
|
||||
if err != nil {
|
||||
return "", ecode.Fail.Sub("获取缓存过期时间失败")
|
||||
}
|
||||
// 更新数据
|
||||
if err = g.Redis().SetEX(ctx, gamelifeCacheKey, gamelifeCache, ttl); err != nil {
|
||||
return "", ecode.Fail.Sub("更新缓存失败")
|
||||
}
|
||||
// 拼接最终 URL
|
||||
url := fmt.Sprintf("%s?%s", rooturl, queryParams.Encode())
|
||||
return url, nil
|
||||
}
|
||||
|
||||
// GetBound 获取用户绑定情况
|
||||
func (s *gamelifeClient) GetBound(ctx context.Context, popenid string) (*model.UserBoundResult, error) {
|
||||
// 获取基础 URL
|
||||
rooturl := s.getBoundUrl[s.Mode]
|
||||
|
||||
// 获取缓存
|
||||
cacheData, err := g.Redis().Get(ctx, fmt.Sprintf(consts.GameLifeUserKey, popenid))
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("从缓存中获取用户信息失败")
|
||||
}
|
||||
|
||||
var gamelifeCache model.UserGamelifeCache
|
||||
if cacheData.IsEmpty() {
|
||||
// 如果缓存不存在或已过期,重新调用 GetUserKeyIV
|
||||
data, err := s.GetUserKeyIV(ctx, popenid)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取用户信息失败")
|
||||
}
|
||||
gamelifeCache = model.UserGamelifeCache{Aes: data.Aes, IV: data.IV, Token: data.Token}
|
||||
} else {
|
||||
if err = json.Unmarshal(cacheData.Bytes(), &gamelifeCache); err != nil {
|
||||
return nil, ecode.Fail.Sub("解析用户信息失败")
|
||||
}
|
||||
}
|
||||
|
||||
// 加密原始数据
|
||||
oriData := g.Map{
|
||||
"PopenId": popenid,
|
||||
"TimeStamp": time.Now().Unix(),
|
||||
}
|
||||
marshal, err := json.Marshal(oriData)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("序列化用户信息失败")
|
||||
}
|
||||
aesEncrypt, err := encrypt.AesEncryptCBCPKCS5(marshal, []byte(gamelifeCache.Aes), []byte(gamelifeCache.IV))
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("加密用户信息失败")
|
||||
}
|
||||
platUserInfoStr := encrypt.Base64Encode(aesEncrypt)
|
||||
|
||||
postBody := g.MapStrStr{
|
||||
"plat_id": s.PlatId,
|
||||
"plat_user_info": popenid,
|
||||
"plat_user_str": gconv.String(g.MapStrStr{"Token": gamelifeCache.Token, "PlatUserInfoStr": platUserInfoStr}),
|
||||
}
|
||||
var result model.UserBoundResult
|
||||
resp, err := resty.New().R().SetBody(postBody).SetResult(&result).Post(rooturl)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("向游戏人生获取绑定信息出现异常")
|
||||
}
|
||||
if resp.StatusCode() != 200 {
|
||||
return nil, ecode.Fail.Sub("向游戏人生获取绑定信息失败")
|
||||
}
|
||||
glog.Infof(ctx, "获取用户游戏人生绑定信息: %v", result)
|
||||
|
||||
return &result, nil
|
||||
|
||||
return queryParams.Encode(), nil
|
||||
}
|
||||
|
||||
// RequestActivity handles activity requests for the GameLife system based on the service name.
|
||||
// It uses the cached UserGamelifeCache.Params for authenticated task list requests, reconstructing
|
||||
// Params if empty and updating the cache.
|
||||
func (s *gamelifeClient) RequestActivity(ctx context.Context, in *model.QQNetbarActivityIn) (interface{}, error) {
|
||||
client := resty.New()
|
||||
taskURL := s.taskURLMap[s.config.Mode]
|
||||
|
||||
switch in.ServiceName {
|
||||
case consts.GetNonLoginTaskList:
|
||||
result := model.GameTaskResponse{}
|
||||
in.TaskParam.Source = s.PlatId
|
||||
in.TaskParam.BrandId = s.BrandId
|
||||
in.TaskParam.Source = s.config.PlatID
|
||||
in.TaskParam.BrandId = s.config.BrandID
|
||||
var result model.GameTaskResponse
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetBody(in.TaskParam).
|
||||
SetResult(&result).
|
||||
Post(s.taskUrlMap[s.Mode] + consts.GetNonLoginTaskList)
|
||||
if err != nil {
|
||||
Post(taskURL + consts.GetNonLoginTaskList)
|
||||
if err != nil || resp.IsError() {
|
||||
return nil, ecode.Fail.Sub("请求出现异常")
|
||||
}
|
||||
|
||||
if resp.IsError() {
|
||||
return nil, ecode.Fail.Sub("请求失败")
|
||||
}
|
||||
return &result, nil
|
||||
|
||||
case consts.GetTaskList:
|
||||
result := model.GameTaskResponse{}
|
||||
in.TaskParam.BrandId = s.BrandId
|
||||
get, err := g.Redis().Get(ctx, fmt.Sprintf(consts.GameLifeUserKey, in.PopenId))
|
||||
cache, err := s.getOrRefreshCache(ctx, in.PopenId)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("从缓存中获取用户信息失败")
|
||||
return nil, err
|
||||
}
|
||||
var gamelifeCache model.UserGamelifeCache
|
||||
if err = json.Unmarshal(get.Bytes(), &gamelifeCache); err != nil {
|
||||
return nil, ecode.Fail.Sub("解析用户信息失败")
|
||||
cacheKey := fmt.Sprintf(consts.GameLifeUserKey, in.PopenId)
|
||||
if cache.Params == "" {
|
||||
// Reconstruct Params with default values
|
||||
value, err := dao.Games.Ctx(ctx).Where(do.Games{GameId: in.TaskParam.Gid}).Fields(dao.Games.Columns().GameCode).Value()
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取游戏名称失败")
|
||||
}
|
||||
|
||||
cache.Params, err = s.buildQueryParams(ctx, in.PopenId, cache, value.String(), "", in.BindType, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Update cache with new Params
|
||||
ttl, err := g.Redis().TTL(ctx, cacheKey)
|
||||
if err != nil {
|
||||
return nil, ecode.Fail.Sub("获取缓存过期时间失败")
|
||||
}
|
||||
if err := g.Redis().SetEX(ctx, cacheKey, cache, ttl); err != nil {
|
||||
return nil, ecode.Fail.Sub("更新缓存失败")
|
||||
}
|
||||
}
|
||||
in.TaskParam.BrandId = s.config.BrandID
|
||||
var result model.GameTaskResponse
|
||||
resp, err := client.R().
|
||||
SetContext(ctx).
|
||||
SetBody(in.TaskParam).
|
||||
SetResult(&result).
|
||||
Post(fmt.Sprintf("%s%s?%s", s.taskUrlMap[s.Mode], consts.GetTaskList, gamelifeCache.Params))
|
||||
if err != nil {
|
||||
Post(fmt.Sprintf("%s%s?%s", taskURL, consts.GetTaskList, cache.Params))
|
||||
if err != nil || resp.IsError() {
|
||||
return nil, ecode.Fail.Sub("请求出现异常")
|
||||
}
|
||||
if resp.IsError() {
|
||||
return nil, ecode.Fail.Sub("请求失败")
|
||||
}
|
||||
return &result, nil
|
||||
|
||||
default:
|
||||
return nil, ecode.Fail.Sub("不支持的任务")
|
||||
return nil, ecode.Fail.Sub(fmt.Sprintf("不支持的任务: %s", in.ServiceName))
|
||||
}
|
||||
}
|
||||
|
||||
// getOrRefreshCache retrieves user cache or refreshes it if expired.
|
||||
func (s *gamelifeClient) getOrRefreshCache(ctx context.Context, popenID string) (model.UserGamelifeCache, error) {
|
||||
cacheKey := fmt.Sprintf(consts.GameLifeUserKey, popenID)
|
||||
cacheData, err := g.Redis().Get(ctx, cacheKey)
|
||||
if err != nil {
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("从缓存中获取用户信息失败")
|
||||
}
|
||||
|
||||
var cache model.UserGamelifeCache
|
||||
if cacheData.IsEmpty() {
|
||||
return s.GetUserKeyIV(ctx, popenID)
|
||||
}
|
||||
if err := json.Unmarshal(cacheData.Bytes(), &cache); err != nil {
|
||||
return model.UserGamelifeCache{}, ecode.Fail.Sub("解析用户信息失败")
|
||||
}
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user