Files
tyc-server-v2/app/main/api/internal/logic/user/wxh5authlogic.go

185 lines
6.5 KiB
Go
Raw Normal View History

2026-01-22 16:04:12 +08:00
package user
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
2026-05-11 20:21:56 +08:00
"strings"
2026-01-22 16:04:12 +08:00
"time"
"tyc-server/app/main/model"
2026-05-11 20:21:56 +08:00
"tyc-server/common/ctxdata"
2026-01-22 16:04:12 +08:00
"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)
}
2026-05-11 20:21:56 +08:00
// 已登录正式用户 + 静默授权 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)
}
2026-01-22 16:04:12 +08:00
// 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,
2026-05-11 20:21:56 +08:00
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,
2026-01-22 16:04:12 +08:00
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
}