Files
tyc-server-v2/app/main/api/internal/logic/user/wxh5authlogic.go
2026-05-11 20:21:56 +08:00

185 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}