Merge remote-tracking branch 'origin/master'
# Conflicts: # internal/controller/task/task_v1_ranking.go
This commit is contained in:
@ -2,14 +2,17 @@ package v1
|
|||||||
|
|
||||||
import "github.com/gogf/gf/v2/frame/g"
|
import "github.com/gogf/gf/v2/frame/g"
|
||||||
|
|
||||||
type TaskListReq struct {
|
type RankingReq struct {
|
||||||
}
|
|
||||||
type TaskRankingReq struct {
|
|
||||||
g.Meta `path:"/task/ranking" method:"get" tags:"Task" summary:"任务排行榜"`
|
g.Meta `path:"/task/ranking" method:"get" tags:"Task" summary:"任务排行榜"`
|
||||||
StoreId int `json:"storeId" v:"required#请选择店铺" dc:"门店id"`
|
StoreId int `json:"storeId" v:"required#请选择店铺" dc:"门店id"`
|
||||||
Page int `json:"page" dc:"页数"`
|
Page int `json:"page" dc:"页数"`
|
||||||
Size int `json:"size" dc:"条数"`
|
Size int `json:"size" dc:"条数"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskRankingRes struct {
|
type RankingRes struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListReq struct {
|
||||||
|
}
|
||||||
|
type ListRes struct {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,4 +16,7 @@ type IUserV1 interface {
|
|||||||
Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error)
|
Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error)
|
||||||
BindPhone(ctx context.Context, req *v1.BindPhoneReq) (res *v1.BindPhoneRes, err error)
|
BindPhone(ctx context.Context, req *v1.BindPhoneReq) (res *v1.BindPhoneRes, err error)
|
||||||
GetPhoneCode(ctx context.Context, req *v1.GetPhoneCodeReq) (res *v1.GetPhoneCodeRes, err error)
|
GetPhoneCode(ctx context.Context, req *v1.GetPhoneCodeReq) (res *v1.GetPhoneCodeRes, err error)
|
||||||
|
GetUserBoundInfo(ctx context.Context, req *v1.GetUserBoundInfoReq) (res *v1.GetUserBoundInfoRes, err error)
|
||||||
|
GetBoundUrl(ctx context.Context, req *v1.GetBoundUrlReq) (res *v1.GetBoundUrlRes, err error)
|
||||||
|
GetUnboundUrl(ctx context.Context, req *v1.GetUnboundUrlReq) (res *v1.GetUnboundUrlRes, err error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,20 +58,31 @@ type GetPhoneCodeReq struct {
|
|||||||
type GetPhoneCodeRes struct {
|
type GetPhoneCodeRes struct {
|
||||||
Success bool `json:"success" dc:"是否成功"`
|
Success bool `json:"success" dc:"是否成功"`
|
||||||
}
|
}
|
||||||
type GetGameLifeBondReq struct {
|
type GetUserBoundInfoReq struct {
|
||||||
g.Meta `path:"/user/getGameLifeBond" method:"post" tags:"User" summary:"获取GameLife绑定情况"`
|
g.Meta `path:"/user/boundInfo" method:"get" tags:"User" summary:"获取用户绑定信息"`
|
||||||
|
PopenId string `json:"popenId" v:"required#popenId不能为空" dc:"用户详情接口返回的 wxPopenId 或者是 qqPopenId"`
|
||||||
}
|
}
|
||||||
type GetGameLifeBondRes struct {
|
type GetUserBoundInfoRes struct {
|
||||||
|
IsBound bool `json:"isBound" dc:"是否已绑定"`
|
||||||
}
|
}
|
||||||
type BundleGameLifeReq struct {
|
|
||||||
g.Meta `path:"/user/bindGameLife" method:"post" tags:"User" summary:"绑定GameLife账号"`
|
type GetBoundUrlReq struct {
|
||||||
|
g.Meta `path:"/user/boundUrl" method:"get" tags:"User" summary:"获取用户绑定url"`
|
||||||
|
PopenId string `json:"popenId" v:"required#popenId不能为空" dc:"用户详情接口返回的 wxPopenId 或者是 qqPopenId"`
|
||||||
|
BindType int `json:"bindType" v:"required|in:1,2#请选择绑定方式,只能为1或2" dc:"绑定方式,默认值1,1: qq; 2: wx" default:"1"`
|
||||||
|
AppName string `json:"appName" v:"required#请选择游戏" dc:"游戏名称"`
|
||||||
}
|
}
|
||||||
type BundleGameLifeRes struct {
|
type GetBoundUrlRes struct {
|
||||||
Url string `json:"url" dc:"绑定游戏人生的 h5 页面 url"`
|
Url string `json:"url" dc:"绑定的 h5 页面 url"`
|
||||||
}
|
}
|
||||||
type UnbundleGameLifeReq struct {
|
|
||||||
g.Meta `path:"/user/unbindGameLife" method:"post" tags:"User" summary:"解绑GameLife账号"`
|
type GetUnboundUrlReq struct {
|
||||||
|
g.Meta `path:"/user/unBoundUrl" method:"get" tags:"User" summary:"获取用户解绑url"`
|
||||||
|
PopenId string `json:"popenId" v:"required#popenId不能为空" dc:"用户详情接口返回的 wxPopenId 或者是 qqPopenId"`
|
||||||
|
BindType int `json:"bindType" v:"required|in:1,2#请选择绑定方式,只能为1或2" dc:"绑定方式,默认值1,1: qq; 2: wx" default:"1"`
|
||||||
|
AppName string `json:"appName" v:"required#请选择游戏" dc:"游戏名称"`
|
||||||
|
Nickname string `json:"nickname" v:"required#昵称不能为空" dc:"昵称"`
|
||||||
}
|
}
|
||||||
type UnbundleGameLifeRes struct {
|
type GetUnboundUrlRes struct {
|
||||||
Url string `json:"url" dc:"解绑游戏人生的 h5 页面 url"`
|
Url string `json:"url" dc:"解绑的 h5 页面 url"`
|
||||||
}
|
}
|
||||||
|
|||||||
10
internal/consts/gamelife.go
Normal file
10
internal/consts/gamelife.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package consts
|
||||||
|
|
||||||
|
const (
|
||||||
|
GamelifeExtplatType = "bindcode"
|
||||||
|
GamelifeExtplatExtraMobile = "mobile"
|
||||||
|
GamelifeExtplatExtraPc = "pc"
|
||||||
|
GamelifeExtplatBoundTypeQQ = "qq"
|
||||||
|
GamelifeExtplatBoundTypeWX = "wx"
|
||||||
|
GamelifeMiniProgramBand = "1"
|
||||||
|
)
|
||||||
@ -5,3 +5,8 @@ const (
|
|||||||
UserBindPhoneKey = "user:bindPhone:%d"
|
UserBindPhoneKey = "user:bindPhone:%d"
|
||||||
UserCodeExpire = 5 * 60
|
UserCodeExpire = 5 * 60
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GameLifeUserKey = "gamelife:user:%s"
|
||||||
|
GameLifeUserExpire = 2 * 60 * 60
|
||||||
|
)
|
||||||
|
|||||||
17
internal/controller/user/user_v1_get_bound_url.go
Normal file
17
internal/controller/user/user_v1_get_bound_url.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"server/internal/model"
|
||||||
|
"server/internal/service"
|
||||||
|
|
||||||
|
"server/api/user/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ControllerV1) GetBoundUrl(ctx context.Context, req *v1.GetBoundUrlReq) (res *v1.GetBoundUrlRes, err error) {
|
||||||
|
out, err := service.User().BoundUrl(ctx, &model.UserBoundUrlIn{PopenId: req.PopenId, AppName: req.AppName, BindType: req.BindType, IsBound: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &v1.GetBoundUrlRes{Url: out.Url}, nil
|
||||||
|
}
|
||||||
17
internal/controller/user/user_v1_get_unbound_url.go
Normal file
17
internal/controller/user/user_v1_get_unbound_url.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"server/internal/model"
|
||||||
|
"server/internal/service"
|
||||||
|
|
||||||
|
"server/api/user/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ControllerV1) GetUnboundUrl(ctx context.Context, req *v1.GetUnboundUrlReq) (res *v1.GetUnboundUrlRes, err error) {
|
||||||
|
out, err := service.User().UnBoundUrl(ctx, &model.UserBoundUrlIn{PopenId: req.PopenId, AppName: req.AppName, BindType: req.BindType, IsBound: false, Nickname: req.Nickname})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &v1.GetUnboundUrlRes{Url: out.Url}, nil
|
||||||
|
}
|
||||||
17
internal/controller/user/user_v1_get_user_bound_info.go
Normal file
17
internal/controller/user/user_v1_get_user_bound_info.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"server/internal/model"
|
||||||
|
"server/internal/service"
|
||||||
|
|
||||||
|
"server/api/user/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ControllerV1) GetUserBoundInfo(ctx context.Context, req *v1.GetUserBoundInfoReq) (res *v1.GetUserBoundInfoRes, err error) {
|
||||||
|
out, err := service.User().BoundInfo(ctx, &model.UserBoundInfoIn{PopenId: req.PopenId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &v1.GetUserBoundInfoRes{IsBound: out.IsBound}, nil
|
||||||
|
}
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
"server/internal/model/entity"
|
"server/internal/model/entity"
|
||||||
"server/internal/service"
|
"server/internal/service"
|
||||||
"server/utility/ecode"
|
"server/utility/ecode"
|
||||||
utility "server/utility/encrypt"
|
"server/utility/encrypt"
|
||||||
"server/utility/jwt"
|
"server/utility/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ func checkAdmin() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !exist {
|
if !exist {
|
||||||
password, err := utility.EncryptPassword("Aa123456")
|
password, err := encrypt.EncryptPassword("Aa123456")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func (s *sAdmin) Login(ctx context.Context, in *model.AdminLoginIn) (out *model.
|
|||||||
if err := dao.Admins.Ctx(ctx).Where(do.Admins{Username: in.Username}).Scan(&admin); err != nil {
|
if err := dao.Admins.Ctx(ctx).Where(do.Admins{Username: in.Username}).Scan(&admin); err != nil {
|
||||||
return nil, ecode.Fail.Sub("查询管理员失败")
|
return nil, ecode.Fail.Sub("查询管理员失败")
|
||||||
}
|
}
|
||||||
if !utility.ComparePassword(admin.PasswordHash, in.Password) {
|
if !encrypt.ComparePassword(admin.PasswordHash, in.Password) {
|
||||||
return nil, ecode.Auth
|
return nil, ecode.Auth
|
||||||
}
|
}
|
||||||
value, err := dao.Roles.Ctx(ctx).WherePri(admin.RoleId).Fields(dao.Roles.Columns().Code).Value()
|
value, err := dao.Roles.Ctx(ctx).WherePri(admin.RoleId).Fields(dao.Roles.Columns().Code).Value()
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import (
|
|||||||
"server/internal/model/entity"
|
"server/internal/model/entity"
|
||||||
"server/internal/service"
|
"server/internal/service"
|
||||||
"server/utility/ecode"
|
"server/utility/ecode"
|
||||||
utility "server/utility/encrypt"
|
"server/utility/encrypt"
|
||||||
"server/utility/jwt"
|
"server/utility/jwt"
|
||||||
"server/utility/snowid"
|
"server/utility/snowid"
|
||||||
)
|
)
|
||||||
@ -65,7 +65,7 @@ func (s *sMerchantAdmin) Login(ctx context.Context, in *model.MerchantLoginIn) (
|
|||||||
if mAdmin[dao.MerchantAdmins.Columns().Status].Int() == consts.MerchantAdministratorDisable {
|
if mAdmin[dao.MerchantAdmins.Columns().Status].Int() == consts.MerchantAdministratorDisable {
|
||||||
return nil, ecode.Params.Sub("该用户已被禁用")
|
return nil, ecode.Params.Sub("该用户已被禁用")
|
||||||
}
|
}
|
||||||
if !utility.ComparePassword(mAdmin[dao.MerchantAdmins.Columns().PasswordHash].String(), in.Password) {
|
if !encrypt.ComparePassword(mAdmin[dao.MerchantAdmins.Columns().PasswordHash].String(), in.Password) {
|
||||||
return nil, ecode.Params.Sub("密码错误")
|
return nil, ecode.Params.Sub("密码错误")
|
||||||
}
|
}
|
||||||
value, err := dao.Roles.Ctx(ctx).WherePri(mAdmin[dao.MerchantAdmins.Columns().RoleId].Int()).Fields(dao.Roles.Columns().Code).Value()
|
value, err := dao.Roles.Ctx(ctx).WherePri(mAdmin[dao.MerchantAdmins.Columns().RoleId].Int()).Fields(dao.Roles.Columns().Code).Value()
|
||||||
@ -145,7 +145,7 @@ func (s *sMerchantAdmin) Register(ctx context.Context, in *model.MerchantAdminRe
|
|||||||
if code.String() != in.Code {
|
if code.String() != in.Code {
|
||||||
return nil, ecode.Fail.Sub("验证码错误")
|
return nil, ecode.Fail.Sub("验证码错误")
|
||||||
}
|
}
|
||||||
hashPass, err := utility.EncryptPassword(in.Password)
|
hashPass, err := encrypt.EncryptPassword(in.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ecode.Fail.Sub("密码加密失败")
|
return nil, ecode.Fail.Sub("密码加密失败")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,8 +8,9 @@ import (
|
|||||||
"server/internal/model/do"
|
"server/internal/model/do"
|
||||||
"server/internal/model/entity"
|
"server/internal/model/entity"
|
||||||
"server/internal/service"
|
"server/internal/service"
|
||||||
|
"server/utility/encrypt"
|
||||||
|
|
||||||
"server/utility/ecode"
|
"server/utility/ecode"
|
||||||
utility "server/utility/encrypt"
|
|
||||||
"server/utility/jwt"
|
"server/utility/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ func (s *sStoreAdmin) Login(ctx context.Context, in *model.StoreAdminLoginIn) (o
|
|||||||
if one[dao.StoreAdmins.Columns().Status].Int() == consts.StoreAdminDisable {
|
if one[dao.StoreAdmins.Columns().Status].Int() == consts.StoreAdminDisable {
|
||||||
return nil, ecode.Params.Sub("该用户已被禁用")
|
return nil, ecode.Params.Sub("该用户已被禁用")
|
||||||
}
|
}
|
||||||
if !utility.ComparePassword(one[dao.StoreAdmins.Columns().PasswordHash].String(), in.Password) {
|
if !encrypt.ComparePassword(one[dao.StoreAdmins.Columns().PasswordHash].String(), in.Password) {
|
||||||
return nil, ecode.Params.Sub("密码错误")
|
return nil, ecode.Params.Sub("密码错误")
|
||||||
}
|
}
|
||||||
value, err := dao.Roles.Ctx(ctx).WherePri(one[dao.StoreAdmins.Columns().RoleId].Int()).Fields(dao.Roles.Columns().Code).Value()
|
value, err := dao.Roles.Ctx(ctx).WherePri(one[dao.StoreAdmins.Columns().RoleId].Int()).Fields(dao.Roles.Columns().Code).Value()
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"server/internal/consts"
|
"server/internal/consts"
|
||||||
"server/internal/dao"
|
"server/internal/dao"
|
||||||
@ -11,6 +12,7 @@ import (
|
|||||||
"server/internal/model/entity"
|
"server/internal/model/entity"
|
||||||
"server/internal/service"
|
"server/internal/service"
|
||||||
"server/utility/ecode"
|
"server/utility/ecode"
|
||||||
|
"server/utility/gamelife"
|
||||||
"server/utility/jwt"
|
"server/utility/jwt"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
@ -200,3 +202,41 @@ func (s *sUser) List(ctx context.Context, in *model.UserListIn) (out *model.User
|
|||||||
// 用于系统管理员、商户、门店查看用户列表, 展示用户最近的相关信息
|
// 用于系统管理员、商户、门店查看用户列表, 展示用户最近的相关信息
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *sUser) BoundUrl(ctx context.Context, in *model.UserBoundUrlIn) (out *model.UserBoundUrlOut, err error) {
|
||||||
|
url, err := gamelife.GetGamelifeClient(ctx).GetUrl(ctx, in.PopenId, in.AppName, "", in.BindType, in.IsBound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ecode.Fail.Sub("获取绑定链接失败")
|
||||||
|
}
|
||||||
|
return &model.UserBoundUrlOut{
|
||||||
|
Url: url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *sUser) UnBoundUrl(ctx context.Context, in *model.UserBoundUrlIn) (out *model.UserUnBoundUrlOut, err error) {
|
||||||
|
url, err := gamelife.GetGamelifeClient(ctx).GetUrl(ctx, in.PopenId, in.AppName, in.Nickname, in.BindType, in.IsBound)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ecode.Fail.Sub("获取绑定链接失败")
|
||||||
|
}
|
||||||
|
return &model.UserUnBoundUrlOut{
|
||||||
|
Url: url,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (s *sUser) BoundInfo(ctx context.Context, in *model.UserBoundInfoIn) (out *model.UserBoundInfoOut, err error) {
|
||||||
|
url, err := gamelife.GetGamelifeClient(ctx).GetBound(ctx, in.PopenId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ecode.Fail.Sub("获取绑定信息失败")
|
||||||
|
}
|
||||||
|
var result model.UserBoundResult
|
||||||
|
resp, err := resty.New().R().SetResult(&result).Post(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ecode.Fail.Sub("获取绑定信息失败")
|
||||||
|
}
|
||||||
|
if resp.StatusCode() != 200 {
|
||||||
|
return nil, ecode.Fail.Sub("获取绑定信息失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.UserBoundInfoOut{
|
||||||
|
IsBound: result.Result,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -133,3 +133,38 @@ type GetPhoneCodeIn struct {
|
|||||||
type GetPhoneCodeOut struct {
|
type GetPhoneCodeOut struct {
|
||||||
Success bool
|
Success bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserGamelifeCache struct {
|
||||||
|
Aes string `json:"aes"`
|
||||||
|
IV string `json:"iv"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserBoundResult struct {
|
||||||
|
Result bool `json:"result"`
|
||||||
|
Nick string `json:"nick"`
|
||||||
|
Ctime uint32 `json:"ctime"`
|
||||||
|
Utype int8 `json:"utype"`
|
||||||
|
AppName []string `json:"app_name"`
|
||||||
|
}
|
||||||
|
type UserBoundUrlIn struct {
|
||||||
|
PopenId string
|
||||||
|
BindType int
|
||||||
|
AppName string
|
||||||
|
Nickname string
|
||||||
|
IsBound bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserBoundUrlOut struct {
|
||||||
|
Url string
|
||||||
|
}
|
||||||
|
type UserUnBoundUrlOut struct {
|
||||||
|
Url string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserBoundInfoIn struct {
|
||||||
|
PopenId string
|
||||||
|
}
|
||||||
|
type UserBoundInfoOut struct {
|
||||||
|
IsBound bool
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||||
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
|
||||||
_ "server/utility/myCasbin"
|
_ "server/utility/myCasbin"
|
||||||
|
_ "server/utility/rsa"
|
||||||
_ "server/utility/snowid"
|
_ "server/utility/snowid"
|
||||||
_ "server/utility/wechat"
|
_ "server/utility/wechat"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -19,6 +19,9 @@ type (
|
|||||||
Update(ctx context.Context, in *model.UserUpdateIn) (out *model.UpdateOut, err error)
|
Update(ctx context.Context, in *model.UserUpdateIn) (out *model.UpdateOut, err error)
|
||||||
BindPhone(ctx context.Context, in *model.UserBindPhoneIn) (out *model.UserBindPhoneOut, err error)
|
BindPhone(ctx context.Context, in *model.UserBindPhoneIn) (out *model.UserBindPhoneOut, err error)
|
||||||
List(ctx context.Context, in *model.UserListIn) (out *model.UserListOut, err error)
|
List(ctx context.Context, in *model.UserListIn) (out *model.UserListOut, err error)
|
||||||
|
BoundUrl(ctx context.Context, in *model.UserBoundUrlIn) (out *model.UserBoundUrlOut, err error)
|
||||||
|
UnBoundUrl(ctx context.Context, in *model.UserBoundUrlIn) (out *model.UserUnBoundUrlOut, err error)
|
||||||
|
BoundInfo(ctx context.Context, in *model.UserBoundInfoIn) (out *model.UserBoundInfoOut, err error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
28
manifest/config/private.pem
Normal file
28
manifest/config/private.pem
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCHXJazSOFka3q+
|
||||||
|
xyBqVuLtK+OElsgRQyQCRakBvcBkq5lIl1G+6ivuju+q3bemiYzOWah/H/+7848c
|
||||||
|
yu505CpJtRUvPiSaXqhwIAWAHvD1ckLo9xw3eh9uYdmnZx92+d+oKVcjZWz/M6qQ
|
||||||
|
g6GYf8kE0x8zDKw1Xd89y6xzo7DqFNnE9BHSxeFNSAgp+MOBPn7X2mdDOWl6eGLs
|
||||||
|
qXK6BTTFyeS0JaH1t0ra6JyQ5wau3WKpB22G3pK9+u8MQPKjM+JXdIZFA0eT2SeI
|
||||||
|
qbB0xRfU4fOmhn/eXVyNYws1s92hPq1Lu8/QHf6m6QNbmualgaSiEo/ogKGN1urj
|
||||||
|
/ARsTdbpAgMBAAECggEAF2DnakmhHA3wIjkUISmmgd39qq28GjclZfsQCIv0Sa7V
|
||||||
|
h5MS/E1HjylCvZkTmgDRv+X+Uw58xcJ4KjnmW2v43cgXw5QREFRe9RaivJEbftjg
|
||||||
|
M4pSZkaCXTcrN115MrxPY6TYNvXSkHUk9Va2tzcCygGItvFuYL04zFx8CXDxIkx7
|
||||||
|
4NP3F3AGbQ4GACoIUsKNuQ0WIZO6WrxJP0sGH1iz+oxDIlEkEZAYHAKIIna3gaY5
|
||||||
|
YBvOmRScGDO2K05fLxDJUK0x4nxkBRQ1NRu9Ud1Fo4zwUFxiwM4ZUjaLclgIgv8v
|
||||||
|
5h0urZY/j4994YtC4UvheC53UEAqDKjMWXnjH5u4nQKBgQDlwqSk6St7pAW1lpA+
|
||||||
|
0OMKQgdDe+W+O2a0LPjaSevCX3Q9ml/ToRH+tmVUGOr2S301GqSRaPdauXBDvE29
|
||||||
|
HSoD6gCEsT+TocSxyhn0Gg25fMDz3+H7XVOAj+tPg/IWJG8+KxO5gEwucb6Axl3w
|
||||||
|
NQ29l77SKFgMYitq8N1jjt6LMwKBgQCW0hJzx2dgaYmKrxctWOxfibTaCGBEn4+F
|
||||||
|
6YwmfGJg+qZ+FU+aBkwaocutIudHogIAOXp6e4J086a6cUwx/Eex1tvTztVa8A/W
|
||||||
|
8cRmk+DWIIYN4c/wePe3wDluddtRIgh54ukpFsgVsOEEmzp3GAa8fyUA7dVvuJlQ
|
||||||
|
z1torG11cwKBgQCImppzZiKxR0sRtOwcPOvQLIPPDroAyaZ9l4N5nZurnD8rZT52
|
||||||
|
P/zH+T/zqUEBoM5XpXiU79ipOznRPALoXo+ddiJKwmuvZe3hWuzlYhwo3VCHbuQY
|
||||||
|
JFvCQ08/no5vtcfiKZB3qR0iPARs4gP2DkUWJUOSBeSbsD5qPb0TNV2BWwKBgAz+
|
||||||
|
QBS1YxSNQwotl2OSu5pndKsr+Y8v599zhV1zbc5JCbrm/xqX3EqXEcLytNYZAO8g
|
||||||
|
BIs0xMJqkzyQsi3EPDD3/6w5r2vMLrEn1vG3X7FSz/m2MIHZCg5MgyYfBSvyMKS/
|
||||||
|
hbLCga5MtLX+4YSND1eB5KA13SNo1dx+YLOd1zg9AoGAczXhM+n0beuXDhLkiAm3
|
||||||
|
7Av8lJazrpZg2Oh79IrFJANjVyZZeOVaSfNXPumoDXMZvEMjkIaZZ50IzQfpVdLF
|
||||||
|
7J0Y/hFCTsX6b3JdkiluniiEh1a2I3tC3+Qa4sSPqKNdHcBJSmmcBcpzRLL5n2zc
|
||||||
|
bjt4KNf3ZQ3nHvPHnwrd7pg=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
9
manifest/config/public.pem
Normal file
9
manifest/config/public.pem
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh1yWs0jhZGt6vscgalbi
|
||||||
|
7SvjhJbIEUMkAkWpAb3AZKuZSJdRvuor7o7vqt23pomMzlmofx//u/OPHMrudOQq
|
||||||
|
SbUVLz4kml6ocCAFgB7w9XJC6PccN3ofbmHZp2cfdvnfqClXI2Vs/zOqkIOhmH/J
|
||||||
|
BNMfMwysNV3fPcusc6Ow6hTZxPQR0sXhTUgIKfjDgT5+19pnQzlpenhi7KlyugU0
|
||||||
|
xcnktCWh9bdK2uickOcGrt1iqQdtht6SvfrvDEDyozPiV3SGRQNHk9kniKmwdMUX
|
||||||
|
1OHzpoZ/3l1cjWMLNbPdoT6tS7vP0B3+pukDW5rmpYGkohKP6IChjdbq4/wEbE3W
|
||||||
|
6QIDAQAB
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
58
utility/encrypt/aes.go
Normal file
58
utility/encrypt/aes.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package encrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AesEncrypt 使用 AES-CBC 模式对数据进行加密。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - plainText: 原始明文数据(必须是任意长度)
|
||||||
|
// - key: 加密密钥(长度必须是 16、24 或 32 字节)
|
||||||
|
// - iv: 初始化向量(必须是 16 字节)
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - 加密后的密文
|
||||||
|
// - 错误信息(如果加密失败)
|
||||||
|
func AesEncrypt(plainText, key, iv []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(iv) != aes.BlockSize {
|
||||||
|
return nil, errors.New("IV 长度必须为 16 字节")
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText = pkcs7Padding(plainText, aes.BlockSize)
|
||||||
|
cipherText := make([]byte, len(plainText))
|
||||||
|
|
||||||
|
mode := cipher.NewCBCEncrypter(block, iv)
|
||||||
|
mode.CryptBlocks(cipherText, plainText)
|
||||||
|
|
||||||
|
return cipherText, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// pkcs7Padding 对数据进行 PKCS7 填充。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - data: 原始数据
|
||||||
|
// - blockSize: 块大小(通常为 16)
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - 填充后的数据
|
||||||
|
func pkcs7Padding(data []byte, blockSize int) []byte {
|
||||||
|
padding := blockSize - len(data)%blockSize
|
||||||
|
padText := bytesRepeat(byte(padding), padding)
|
||||||
|
return append(data, padText...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bytesRepeat 返回一个重复 count 次的字节切片。
|
||||||
|
func bytesRepeat(b byte, count int) []byte {
|
||||||
|
buf := make([]byte, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
buf[i] = b
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
19
utility/encrypt/bs4.go
Normal file
19
utility/encrypt/bs4.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package encrypt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Base64Encode 对字符串进行 base64 编码
|
||||||
|
func Base64Encode(data []byte) string {
|
||||||
|
return base64.StdEncoding.EncodeToString(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64Decode 对 base64 编码的字符串进行解码
|
||||||
|
func Base64Decode(encoded string) ([]byte, error) {
|
||||||
|
decodedBytes, err := base64.StdEncoding.DecodeString(encoded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return decodedBytes, nil
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package utility
|
package encrypt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package utility
|
package encrypt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|||||||
287
utility/gamelife/gamelife.go
Normal file
287
utility/gamelife/gamelife.go
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
package gamelife
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
"github.com/gogf/gf/v2/os/glog"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"github.com/gogf/gf/v2/util/grand"
|
||||||
|
"net/url"
|
||||||
|
"server/internal/consts"
|
||||||
|
"server/internal/model"
|
||||||
|
"server/utility/ecode"
|
||||||
|
"server/utility/encrypt"
|
||||||
|
"server/utility/rsa"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gamelifeClient struct {
|
||||||
|
PlatId string `json:"platId" `
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
instance *gamelifeClient
|
||||||
|
)
|
||||||
|
|
||||||
|
func newgamelifeClient(ctx context.Context) *gamelifeClient {
|
||||||
|
instance = &gamelifeClient{
|
||||||
|
PlatId: g.Config().MustGet(ctx, "gamelife.platId").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.authinfoo_cgi.authinfo_cgi/GetPlatUserInfo",
|
||||||
|
"prod": "https://api.cafe.qq.com/tipmp.user.authinfoo_cgi.authinfo_cgi/GetPlatUserInfo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
glog.Infof(ctx, "初始化 gamelifeClient 成功")
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ctx := context.Background()
|
||||||
|
newgamelifeClient(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGamelifeClient(ctx context.Context) *gamelifeClient {
|
||||||
|
if instance == nil {
|
||||||
|
instance = newgamelifeClient(ctx)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
err = ecode.Fail.Sub("序列化 json 数据出现异常")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
key, err := rsa.GetRsaClient().EncryptWithRsaPublicKey(marshal)
|
||||||
|
if err != nil {
|
||||||
|
err = ecode.Fail.Sub("序列化 json 数据出现异常")
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
// base64编码
|
||||||
|
base64Encode := encrypt.Base64Encode(key)
|
||||||
|
// 向游戏人生发送请求
|
||||||
|
result := struct {
|
||||||
|
secret string `json:"secret"`
|
||||||
|
key string `json:"key"` // 该用户的token,后续的账号绑定、登录态携带都需要此参数。
|
||||||
|
}{}
|
||||||
|
resp, err := resty.New().R().SetBody(map[string]string{
|
||||||
|
"plat_id": s.PlatId,
|
||||||
|
"key": base64Encode,
|
||||||
|
}).SetResult(result).Post(s.keyivUrlMap[s.Mode])
|
||||||
|
if err != nil {
|
||||||
|
err = ecode.Fail.Sub("获取用户信息失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
if resp.StatusCode() != 200 {
|
||||||
|
err = ecode.Fail.Sub("获取用户信息失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
decode, err := encrypt.Base64Decode(result.secret)
|
||||||
|
if err != nil {
|
||||||
|
err = ecode.Fail.Sub("解密用户信息失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
plain, err := rsa.GetRsaClient().DecryptWithRsaPrivateKey(decode)
|
||||||
|
if err != nil {
|
||||||
|
err = ecode.Fail.Sub("解密用户信息失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
aesResult := struct {
|
||||||
|
key string `json:"key"`
|
||||||
|
iv string `json:"iv"`
|
||||||
|
}{}
|
||||||
|
if err = json.Unmarshal(plain, &aesResult); err != nil {
|
||||||
|
err = ecode.Fail.Sub("解密用户信息失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
if !isBound {
|
||||||
|
rooturl = s.unBoundUrlMap[s.Mode]
|
||||||
|
}
|
||||||
|
baseUrl, err := url.Parse(rooturl)
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("解析基础 URL 失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheData, err := g.Redis().Get(ctx, fmt.Sprintf(consts.GameLifeUserKey, popenid))
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("从缓存中获取用户信息失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
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("解析用户信息失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化原始数据
|
||||||
|
oriData := g.Map{
|
||||||
|
"PopenId": popenid,
|
||||||
|
"TimeStamp": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
marshal, err := json.Marshal(oriData)
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("序列化用户信息失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密用户信息
|
||||||
|
aesEncrypt, err := encrypt.AesEncrypt(marshal, []byte(gamelifeCache.Aes), []byte(gamelifeCache.IV))
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("加密用户信息失败")
|
||||||
|
}
|
||||||
|
platUserInfoStr := encrypt.Base64Encode(aesEncrypt)
|
||||||
|
explatData := g.MapStrStr{"Token": gamelifeCache.Token, "PlatUserInfoStr": platUserInfoStr}
|
||||||
|
|
||||||
|
// 构建查询参数
|
||||||
|
queryParams := baseUrl.Query()
|
||||||
|
queryParams.Add("app_name", appname)
|
||||||
|
queryParams.Add("mini_program_band", consts.GamelifeMiniProgramBand)
|
||||||
|
queryParams.Add("extplat_plat", s.PlatId)
|
||||||
|
queryParams.Add("extplat_type", consts.GamelifeExtplatType)
|
||||||
|
queryParams.Add("extplat_extra", consts.GamelifeExtplatExtraPc)
|
||||||
|
queryParams.Add("extplat_data", url.QueryEscape(gconv.String(explatData)))
|
||||||
|
|
||||||
|
// 根据 isBound 设置 bind_type 和 nickname
|
||||||
|
if bindType == 1 {
|
||||||
|
queryParams.Add("bind_type", consts.GamelifeExtplatBoundTypeQQ)
|
||||||
|
} else {
|
||||||
|
queryParams.Add("bind_type", consts.GamelifeExtplatBoundTypeWX)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 仅在解绑时添加 nickname
|
||||||
|
if !isBound {
|
||||||
|
queryParams.Add("nickname", nickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseUrl.RawQuery = queryParams.Encode()
|
||||||
|
return baseUrl.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBound 获取用户绑定情况
|
||||||
|
func (s *gamelifeClient) GetBound(ctx context.Context, popenid string) (string, error) {
|
||||||
|
baseUrl, err := url.Parse(s.getBoundUrl[s.Mode])
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("解析基础 URL 失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheData, err := g.Redis().Get(ctx, fmt.Sprintf(consts.GameLifeUserKey, popenid))
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("从缓存中获取用户信息失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
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("解析用户信息失败")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 序列化原始数据
|
||||||
|
oriData := g.Map{
|
||||||
|
"PopenId": popenid,
|
||||||
|
"TimeStamp": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
marshal, err := json.Marshal(oriData)
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("序列化用户信息失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密用户信息
|
||||||
|
aesEncrypt, err := encrypt.AesEncrypt(marshal, []byte(gamelifeCache.Aes), []byte(gamelifeCache.IV))
|
||||||
|
if err != nil {
|
||||||
|
return "", ecode.Fail.Sub("加密用户信息失败")
|
||||||
|
}
|
||||||
|
platUserInfoStr := encrypt.Base64Encode(aesEncrypt)
|
||||||
|
explatData := g.MapStrStr{"Token": gamelifeCache.Token, "PlatUserInfoStr": platUserInfoStr}
|
||||||
|
|
||||||
|
queryParams := baseUrl.Query()
|
||||||
|
queryParams.Add("plat_id", s.PlatId)
|
||||||
|
queryParams.Add("plat_user_info", popenid)
|
||||||
|
queryParams.Add("plat_user_str", url.QueryEscape(gconv.String(explatData)))
|
||||||
|
baseUrl.RawQuery = queryParams.Encode()
|
||||||
|
|
||||||
|
return baseUrl.String(), nil
|
||||||
|
}
|
||||||
126
utility/rsa/rsa.go
Normal file
126
utility/rsa/rsa.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package rsa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rsaClient struct {
|
||||||
|
publicKey *rsa.PublicKey
|
||||||
|
privateKey *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
instance *rsaClient
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// init 会在包初始化时自动调用,用于加载默认的 RSA 公钥和私钥。
|
||||||
|
func init() {
|
||||||
|
ctx := context.Background()
|
||||||
|
once.Do(func() {
|
||||||
|
instance = &rsaClient{}
|
||||||
|
err := instance.loadKeys(g.Config().MustGet(ctx, "rsa.publickey").String(), g.Config().MustGet(ctx, "rsa.privatekey").String())
|
||||||
|
if err != nil {
|
||||||
|
panic("加载 RSA 密钥失败: " + err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRsaClient 返回 RSA 客户端的单例实例。
|
||||||
|
//
|
||||||
|
// 通常用于执行加解密操作。
|
||||||
|
func GetRsaClient() *rsaClient {
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptWithRsaPublicKey 使用加载的 RSA 公钥对原始数据进行加密。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - plain: 待加密的明文数据。
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - 加密后的密文数据。
|
||||||
|
// - 如果加密失败,则返回错误。
|
||||||
|
func (c *rsaClient) EncryptWithRsaPublicKey(plain []byte) ([]byte, error) {
|
||||||
|
if c.publicKey == nil {
|
||||||
|
return nil, errors.New("公钥未加载")
|
||||||
|
}
|
||||||
|
return rsa.EncryptPKCS1v15(rand.Reader, c.publicKey, plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecryptWithRsaPrivateKey 使用加载的 RSA 私钥对密文数据进行解密。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - cipher: 加密后的密文数据。
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - 解密后的明文数据。
|
||||||
|
// - 如果解密失败,则返回错误。
|
||||||
|
func (c *rsaClient) DecryptWithRsaPrivateKey(cipher []byte) ([]byte, error) {
|
||||||
|
if c.privateKey == nil {
|
||||||
|
return nil, errors.New("私钥未加载")
|
||||||
|
}
|
||||||
|
return rsa.DecryptPKCS1v15(rand.Reader, c.privateKey, cipher)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadKeys 从指定文件中加载 RSA 公钥和私钥。
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// - publicKeyPath: 公钥 PEM 文件路径。
|
||||||
|
// - privateKeyPath: 私钥 PEM 文件路径。
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// - 成功返回 nil,否则返回错误信息。
|
||||||
|
func (c *rsaClient) loadKeys(publicKeyPath, privateKeyPath string) error {
|
||||||
|
// 加载公钥
|
||||||
|
pubBytes, err := os.ReadFile(publicKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pubBlock, _ := pem.Decode(pubBytes)
|
||||||
|
if pubBlock == nil {
|
||||||
|
return errors.New("无法解析公钥 PEM 文件")
|
||||||
|
}
|
||||||
|
pubKey, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
if c.publicKey, ok = pubKey.(*rsa.PublicKey); !ok {
|
||||||
|
return errors.New("公钥不是 RSA 公钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载私钥
|
||||||
|
privBytes, err := os.ReadFile(privateKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
privBlock, _ := pem.Decode(privBytes)
|
||||||
|
if privBlock == nil {
|
||||||
|
return errors.New("无法解析私钥 PEM 文件")
|
||||||
|
}
|
||||||
|
// 尝试解析 PKCS#8 格式
|
||||||
|
privKey, err := x509.ParsePKCS8PrivateKey(privBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
// 回退尝试 PKCS#1 格式
|
||||||
|
privKey, err = x509.ParsePKCS1PrivateKey(privBlock.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("解析私钥失败: 既不是 PKCS#8 也不是 PKCS#1 格式")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var ok2 bool
|
||||||
|
if c.privateKey, ok2 = privKey.(*rsa.PrivateKey); !ok2 {
|
||||||
|
return errors.New("私钥不是 RSA 私钥")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user