Files
zac_server_v2/app/main/api/internal/logic/user/mobilecodeloginlogic.go
2026-02-28 18:01:59 +08:00

122 lines
4.6 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"
"database/sql"
stderrors "errors"
"fmt"
"os"
"qnc-server/app/main/api/internal/svc"
"qnc-server/app/main/api/internal/types"
"qnc-server/app/main/model"
"qnc-server/common/xerr"
"qnc-server/pkg/lzkit/crypto"
"strings"
"time"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/logx"
)
type MobileCodeLoginLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewMobileCodeLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *MobileCodeLoginLogic {
return &MobileCodeLoginLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *MobileCodeLoginLogic) MobileCodeLogin(req *types.MobileCodeLoginReq) (resp *types.MobileCodeLoginResp, err error) {
secretKey := l.svcCtx.Config.Encrypt.SecretKey
encryptedMobile, err := crypto.EncryptMobile(req.Mobile, secretKey)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 加密手机号失败: %+v", err)
}
// 开发环境下跳过验证码校验
if os.Getenv("ENV") != "development" {
// 检查手机号是否在一分钟内已发送过验证码
redisKey := fmt.Sprintf("%s:%s", "login", encryptedMobile)
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
if err != nil {
if stderrors.Is(err, redis.Nil) {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码已过期"), "手机登录, 验证码过期: %s", encryptedMobile)
}
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取验证码redis缓存失败, mobile: %s, err: %+v", encryptedMobile, err)
}
if cacheCode != req.Code {
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "手机登录, 验证码不正确: %s", encryptedMobile)
}
}
var userID string
// 最多重试3次每次间隔50ms
maxRetries := 3
for i := 0; i < maxRetries; i++ {
user, findUserErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
if findUserErr != nil && findUserErr != model.ErrNotFound {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 读取数据库获取用户失败, mobile: %s, err: %+v", encryptedMobile, err)
}
if user != nil {
// 用户存在
if user.Disable == 1 {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.USER_DISABLED), "用户已被封禁")
}
userID = user.Id
break
}
// 用户不存在,尝试注册
l.Infof("手机登录, 用户不存在,尝试注册新用户 (尝试 %d/%d): %s", i+1, maxRetries, encryptedMobile)
registeredUserID, registerErr := l.svcCtx.UserService.RegisterUser(l.ctx, encryptedMobile)
if registerErr != nil {
// 检查是否是并发注册导致的唯一键冲突
errStr := registerErr.Error()
if strings.Contains(errStr, "Duplicate entry") && strings.Contains(errStr, "user_auth.unique_type_key") {
// 并发冲突,等待一小段时间后重试
l.Infof("手机登录, 检测到并发注册冲突,等待后重试: %s", encryptedMobile)
if i < maxRetries-1 {
time.Sleep(50 * time.Millisecond)
continue
}
// 最后一次重试仍然失败,尝试最后一次查询
user, lastRetryErr := l.svcCtx.UserModel.FindOneByMobile(l.ctx, sql.NullString{String: encryptedMobile, Valid: true})
if lastRetryErr != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "手机登录, 并发冲突后重试查询用户失败, mobile: %s, err: %+v", encryptedMobile, lastRetryErr)
}
if user == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 重试多次后仍未找到用户, mobile: %s", encryptedMobile)
}
userID = user.Id
l.Infof("手机登录, 并发冲突重试后获取到已注册用户, userId: %s, mobile: %s", userID, encryptedMobile)
} else {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 自动注册用户失败, mobile: %s, err: %+v", encryptedMobile, registerErr)
}
} else {
userID = registeredUserID
l.Infof("手机登录, 自动注册用户成功, userId: %s, mobile: %s", userID, encryptedMobile)
break
}
}
token, err := l.svcCtx.UserService.GeneralUserToken(l.ctx, userID)
if err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "手机登录, 生成token失败 : %s", userID)
}
// 获取当前时间戳
now := time.Now().Unix()
return &types.MobileCodeLoginResp{
AccessToken: token,
AccessExpire: now + l.svcCtx.Config.JwtAuth.AccessExpire,
RefreshAfter: now + l.svcCtx.Config.JwtAuth.RefreshAfter,
}, nil
}