185 lines
6.5 KiB
Go
185 lines
6.5 KiB
Go
package user
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
"tyc-server/app/main/model"
|
||
"tyc-server/common/ctxdata"
|
||
"tyc-server/common/xerr"
|
||
|
||
"github.com/pkg/errors"
|
||
|
||
"tyc-server/app/main/api/internal/svc"
|
||
"tyc-server/app/main/api/internal/types"
|
||
|
||
"github.com/zeromicro/go-zero/core/logx"
|
||
)
|
||
|
||
type WxH5AuthLogic struct {
|
||
logx.Logger
|
||
ctx context.Context
|
||
svcCtx *svc.ServiceContext
|
||
}
|
||
|
||
func NewWxH5AuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxH5AuthLogic {
|
||
return &WxH5AuthLogic{
|
||
Logger: logx.WithContext(ctx),
|
||
ctx: ctx,
|
||
svcCtx: svcCtx,
|
||
}
|
||
}
|
||
|
||
func (l *WxH5AuthLogic) WxH5Auth(req *types.WXH5AuthReq) (resp *types.WXH5AuthResp, err error) {
|
||
// Step 1: 使用code获取access_token
|
||
accessTokenResp, err := l.GetAccessToken(req.Code)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取access_token失败: %v", err)
|
||
}
|
||
|
||
// 已登录正式用户 + 静默授权 code:把当前公众号 openid 写入 user_auth(wxh5_openid),供微信内 JSAPI 支付使用
|
||
if claims, claimsErr := ctxdata.GetClaimsFromCtx(l.ctx); claimsErr == nil && claims != nil && claims.UserType == model.UserTypeNormal {
|
||
return l.bindWxh5OpenidToNormalUser(claims.UserId, accessTokenResp.Openid)
|
||
}
|
||
|
||
// Step 2: 查找用户授权信息
|
||
userAuth, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid)
|
||
if findErr != nil && !errors.Is(findErr, model.ErrNotFound) {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", findErr)
|
||
}
|
||
|
||
// Step 3: 处理用户信息
|
||
var userID int64
|
||
var userType int64
|
||
if userAuth != nil {
|
||
// 已存在用户,直接登录
|
||
userID = userAuth.UserId
|
||
userType = model.UserTypeNormal
|
||
} else {
|
||
// 检查临时用户表
|
||
userTemp, err := l.svcCtx.UserTempModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, accessTokenResp.Openid)
|
||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户临时信息失败: %v", err)
|
||
}
|
||
|
||
if userTemp == nil {
|
||
// 创建临时用户记录
|
||
userTemp = &model.UserTemp{
|
||
AuthType: model.UserAuthTypeWxh5OpenID,
|
||
AuthKey: accessTokenResp.Openid,
|
||
}
|
||
result, err := l.svcCtx.UserTempModel.Insert(l.ctx, nil, userTemp)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建临时用户信息失败: %v", err)
|
||
}
|
||
userID, err = result.LastInsertId()
|
||
if err != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取新创建的临时用户ID失败: %v", err)
|
||
}
|
||
} else {
|
||
userID = userTemp.Id
|
||
}
|
||
userType = model.UserTypeTemp
|
||
}
|
||
|
||
// Step 4: 生成JWT Token
|
||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID, userType)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT token失败: %v", err)
|
||
}
|
||
|
||
// Step 5: 返回登录结果
|
||
now := time.Now().Unix()
|
||
return &types.WXH5AuthResp{
|
||
AccessToken: token,
|
||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||
}, nil
|
||
}
|
||
|
||
// bindWxh5OpenidToNormalUser 将 snsapi_base 换得的 openid 绑定到当前正式用户(插入或更新 user_auth)。
|
||
// 临时用户不能走此分支:需在绑定手机号时由 TempUserBindUser 写入 user_auth,避免与临时转正逻辑冲突。
|
||
func (l *WxH5AuthLogic) bindWxh5OpenidToNormalUser(normalUserID int64, openid string) (*types.WXH5AuthResp, error) {
|
||
openid = strings.TrimSpace(openid)
|
||
if openid == "" {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "微信 openid 为空")
|
||
}
|
||
existingByOpenid, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxh5OpenID, openid)
|
||
if err != nil && !errors.Is(err, model.ErrNotFound) {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询 wxh5_openid 失败: %v", err)
|
||
}
|
||
if existingByOpenid != nil && existingByOpenid.UserId != normalUserID {
|
||
return nil, errors.Wrapf(xerr.NewErrMsg("该微信已绑定其他账号"), "wxh5_openid 已被占用 user_id=%d", existingByOpenid.UserId)
|
||
}
|
||
rowByUser, err := l.svcCtx.UserAuthModel.FindOneByUserIdAuthType(l.ctx, normalUserID, model.UserAuthTypeWxh5OpenID)
|
||
if errors.Is(err, model.ErrNotFound) {
|
||
_, insErr := l.svcCtx.UserAuthModel.Insert(l.ctx, nil, &model.UserAuth{
|
||
UserId: normalUserID,
|
||
AuthType: model.UserAuthTypeWxh5OpenID,
|
||
AuthKey: openid,
|
||
})
|
||
if insErr != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "写入 wxh5_openid 失败: %v", insErr)
|
||
}
|
||
logx.WithContext(l.ctx).Infof("[WxH5Auth] 已写入 user_auth wxh5_openid user_id=%d", normalUserID)
|
||
} else if err != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户 wxh5_openid 失败: %v", err)
|
||
} else if rowByUser.AuthKey != openid {
|
||
rowByUser.AuthKey = openid
|
||
if _, updErr := l.svcCtx.UserAuthModel.Update(l.ctx, nil, rowByUser); updErr != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新 wxh5_openid 失败: %v", updErr)
|
||
}
|
||
logx.WithContext(l.ctx).Infof("[WxH5Auth] 已更新 user_auth wxh5_openid user_id=%d", normalUserID)
|
||
}
|
||
|
||
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, normalUserID, model.UserTypeNormal)
|
||
if err != nil {
|
||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT token失败: %v", err)
|
||
}
|
||
now := time.Now().Unix()
|
||
return &types.WXH5AuthResp{
|
||
AccessToken: token,
|
||
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
|
||
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
|
||
}, nil
|
||
}
|
||
|
||
type AccessTokenResp struct {
|
||
AccessToken string `json:"access_token"`
|
||
Openid string `json:"openid"`
|
||
}
|
||
|
||
// GetAccessToken 通过code获取access_token
|
||
func (l *WxH5AuthLogic) GetAccessToken(code string) (*AccessTokenResp, error) {
|
||
appID := l.svcCtx.Config.WechatH5.AppID
|
||
appSecret := l.svcCtx.Config.WechatH5.AppSecret
|
||
|
||
url := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appID, appSecret, code)
|
||
|
||
resp, err := http.Get(url)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var accessTokenResp AccessTokenResp
|
||
if err = json.Unmarshal(body, &accessTokenResp); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
if accessTokenResp.AccessToken == "" || accessTokenResp.Openid == "" {
|
||
return nil, errors.New("accessTokenResp.AccessToken为空")
|
||
}
|
||
|
||
return &accessTokenResp, nil
|
||
}
|