Files
qnc-server-v3/app/main/api/internal/logic/user/wxminiauthlogic.go
2026-02-06 16:00:04 +08:00

165 lines
5.4 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"
"time"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/api/internal/types"
"qnc-server/app/main/model"
"qnc-server/common/xerr"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
)
type WxMiniAuthLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewWxMiniAuthLogic(ctx context.Context, svcCtx *svc.ServiceContext) *WxMiniAuthLogic {
return &WxMiniAuthLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *WxMiniAuthLogic) WxMiniAuth(req *types.WXMiniAuthReq) (resp *types.WXMiniAuthResp, err error) {
// 1. 获取session_key和openid
sessionKeyResp, err := l.GetSessionKey(req.Code)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
}
// 2. 查找用户授权信息
userAuth, err := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(l.ctx, model.UserAuthTypeWxMiniOpenID, sessionKeyResp.Openid)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "查询用户授权失败: %v", err)
}
// 3. 处理用户信息
var userID string
if userAuth != nil {
// 已存在用户,直接登录
userID = userAuth.UserId
} else {
// 新用户创建为临时用户没有mobile
user := &model.User{Id: uuid.NewString()}
_, err := l.svcCtx.UserModel.Insert(l.ctx, nil, user)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
}
ua := &model.UserAuth{Id: uuid.NewString(), UserId: user.Id, AuthType: model.UserAuthTypeWxMiniOpenID, AuthKey: sessionKeyResp.Openid}
_, err = l.svcCtx.UserAuthModel.Insert(l.ctx, nil, ua)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户授权失败: %v", err)
}
userID = user.Id
}
// 4. 生成JWT Token动态计算userType
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "生成JWT Token失败: %v", err)
}
// 5. 返回登录结果
now := time.Now().Unix()
return &types.WXMiniAuthResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
}, nil
}
// SessionKeyResp 小程序登录返回结构
type SessionKeyResp struct {
Openid string `json:"openid"`
SessionKey string `json:"session_key"`
Unionid string `json:"unionid,omitempty"`
ErrCode int `json:"errcode,omitempty"`
ErrMsg string `json:"errmsg,omitempty"`
}
// GetSessionKey 通过code获取小程序的session_key和openid
func (l *WxMiniAuthLogic) GetSessionKey(code string) (*SessionKeyResp, error) {
var appID string
var appSecret string
appID = l.svcCtx.Config.WechatMini.AppID
appSecret = l.svcCtx.Config.WechatMini.AppSecret
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
appID, appSecret, code)
resp, err := http.Get(url)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取session_key失败: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "读取响应失败: %v", err)
}
var sessionKeyResp SessionKeyResp
if err = json.Unmarshal(body, &sessionKeyResp); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "解析响应失败: %v", err)
}
// 检查微信返回的错误码
if sessionKeyResp.ErrCode != 0 {
// 针对不同的微信错误码返回更明确的错误信息
var errMsg string
switch sessionKeyResp.ErrCode {
case 40029:
// code 无效(已使用、已过期或格式错误)
errMsg = "微信授权码无效或已过期,请重新打开小程序"
l.Errorf("微信code无效: errcode=%d, errmsg=%s, code前6位=%s",
sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg,
func() string {
if len(code) > 6 {
return code[:6] + "..."
}
return code
}())
case 40013:
// 无效的 AppID
errMsg = "小程序配置错误,请联系管理员"
l.Errorf("微信AppID无效: errcode=%d, errmsg=%s", sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg)
case 40125:
// 无效的 AppSecret
errMsg = "小程序配置错误,请联系管理员"
l.Errorf("微信AppSecret无效: errcode=%d, errmsg=%s", sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg)
case 45011:
// 频率限制
errMsg = "请求过于频繁,请稍后再试"
l.Errorf("微信API频率限制: errcode=%d, errmsg=%s", sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg)
default:
errMsg = fmt.Sprintf("微信授权失败: %s", sessionKeyResp.ErrMsg)
l.Errorf("微信接口返回错误: errcode=%d, errmsg=%s", sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg)
}
return nil, errors.Wrapf(xerr.NewErrMsg(errMsg),
"微信接口返回错误: errcode=%d, errmsg=%s",
sessionKeyResp.ErrCode, sessionKeyResp.ErrMsg)
}
// 验证必要字段
if sessionKeyResp.Openid == "" || sessionKeyResp.SessionKey == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR),
"微信接口返回数据不完整: openid=%s, session_key=%s",
sessionKeyResp.Openid, sessionKeyResp.SessionKey)
}
return &sessionKeyResp, nil
}