f
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
|
"qnc-server/common/reviewphone"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
|
|
||||||
@@ -53,8 +54,12 @@ func (l *ApplyForAgentLogic) ApplyForAgent(req *types.AgentApplyReq) (resp *type
|
|||||||
if req.Referrer == "" {
|
if req.Referrer == "" {
|
||||||
return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
|
return nil, errors.Wrapf(xerr.NewErrMsg("请填写邀请信息"), "")
|
||||||
}
|
}
|
||||||
// 2. 校验验证码(开发环境下跳过验证码校验)
|
// 2. 校验验证码:开发环境跳过;审核预留号段 + 固定码;其余 Redis
|
||||||
if os.Getenv("ENV") != "development" {
|
if os.Getenv("ENV") == "development" {
|
||||||
|
// skip
|
||||||
|
} else if reviewphone.IsAppReviewDemoMobile(req.Mobile) && req.Code == reviewphone.DemoVerifyCode {
|
||||||
|
l.Infof("[ApplyForAgent] 审核体验号段固定验证码通过, mobile: %s", req.Mobile)
|
||||||
|
} else {
|
||||||
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import (
|
|||||||
"qnc-server/app/main/model"
|
"qnc-server/app/main/model"
|
||||||
"qnc-server/common/ctxdata"
|
"qnc-server/common/ctxdata"
|
||||||
"qnc-server/common/globalkey"
|
"qnc-server/common/globalkey"
|
||||||
|
"qnc-server/common/reviewphone"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-sql-driver/mysql"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/zeromicro/go-zero/core/stores/redis"
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
||||||
@@ -24,6 +26,36 @@ import (
|
|||||||
"github.com/zeromicro/go-zero/core/logx"
|
"github.com/zeromicro/go-zero/core/logx"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func isMySQLDuplicateEntry(err error) bool {
|
||||||
|
var me *mysql.MySQLError
|
||||||
|
return errors.As(err, &me) && me.Number == 1062
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertMobileUserAuthOrSkip 写入 mobile 认证;若并发重复插入触发唯一键冲突,且该手机号已绑定同一用户则视为成功(幂等)。
|
||||||
|
func (l *RegisterByInviteCodeLogic) insertMobileUserAuthOrSkip(ctx context.Context, session sqlx.Session, userID string, encryptedMobile string) error {
|
||||||
|
_, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
UserId: userID,
|
||||||
|
AuthType: model.UserAuthTypeMobile,
|
||||||
|
AuthKey: encryptedMobile,
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !isMySQLDuplicateEntry(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exist, findErr := l.svcCtx.UserAuthModel.FindOneByAuthTypeAuthKey(ctx, model.UserAuthTypeMobile, encryptedMobile)
|
||||||
|
if findErr != nil {
|
||||||
|
return findErr
|
||||||
|
}
|
||||||
|
if exist != nil && exist.UserId == userID {
|
||||||
|
l.Infof("[insertMobileUserAuthOrSkip] mobile 认证已由并发请求创建,跳过: userId=%s", userID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Wrapf(xerr.NewErrMsg("该手机号已被占用"), "")
|
||||||
|
}
|
||||||
|
|
||||||
type RegisterByInviteCodeLogic struct {
|
type RegisterByInviteCodeLogic struct {
|
||||||
logx.Logger
|
logx.Logger
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -48,8 +80,12 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
|
|||||||
}
|
}
|
||||||
l.Infof("[RegisterByInviteCode] 手机号加密完成, encryptedMobile: %s", encryptedMobile)
|
l.Infof("[RegisterByInviteCode] 手机号加密完成, encryptedMobile: %s", encryptedMobile)
|
||||||
|
|
||||||
// 校验验证码(开发环境下跳过验证码校验)
|
// 校验验证码:开发环境跳过;审核预留号段 + 固定码走专用通道;其余走 Redis
|
||||||
if os.Getenv("ENV") != "development" && req.Code != "143838" {
|
if os.Getenv("ENV") == "development" {
|
||||||
|
l.Infof("[RegisterByInviteCode] 开发环境跳过验证码校验")
|
||||||
|
} else if reviewphone.IsAppReviewDemoMobile(req.Mobile) && req.Code == reviewphone.DemoVerifyCode {
|
||||||
|
l.Infof("[RegisterByInviteCode] 审核体验号段固定验证码通过, mobile: %s", req.Mobile)
|
||||||
|
} else {
|
||||||
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
redisKey := fmt.Sprintf("%s:%s", "agentApply", encryptedMobile)
|
||||||
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
cacheCode, err := l.svcCtx.Redis.Get(redisKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -64,8 +100,6 @@ func (l *RegisterByInviteCodeLogic) RegisterByInviteCode(req *types.RegisterByIn
|
|||||||
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
|
return nil, errors.Wrapf(xerr.NewErrMsg("验证码不正确"), "")
|
||||||
}
|
}
|
||||||
l.Infof("[RegisterByInviteCode] 验证码校验通过, mobile: %s", req.Mobile)
|
l.Infof("[RegisterByInviteCode] 验证码校验通过, mobile: %s", req.Mobile)
|
||||||
} else {
|
|
||||||
l.Infof("[RegisterByInviteCode] 开发环境跳过验证码校验")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前登录态(可能为空)
|
// 获取当前登录态(可能为空)
|
||||||
@@ -471,14 +505,8 @@ func (l *RegisterByInviteCodeLogic) handleMobileNotExists(ctx context.Context, s
|
|||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建用户失败: %v", err)
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 用户创建成功, userId: %s", newUser.Id)
|
l.Infof("[handleMobileNotExists] 用户创建成功, userId: %s", newUser.Id)
|
||||||
// 创建 mobile 认证
|
if err := l.insertMobileUserAuthOrSkip(ctx, session, newUser.Id, encryptedMobile); err != nil {
|
||||||
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
|
return "", errors.Wrap(err, "创建手机号认证失败")
|
||||||
Id: uuid.NewString(),
|
|
||||||
UserId: newUser.Id,
|
|
||||||
AuthType: model.UserAuthTypeMobile,
|
|
||||||
AuthKey: encryptedMobile,
|
|
||||||
}); err != nil {
|
|
||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", newUser.Id)
|
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", newUser.Id)
|
||||||
return newUser.Id, nil
|
return newUser.Id, nil
|
||||||
@@ -496,14 +524,8 @@ func (l *RegisterByInviteCodeLogic) handleMobileNotExists(ctx context.Context, s
|
|||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新手机号失败: %v", err)
|
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "更新手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 用户升级为正式用户成功, userId: %s", currentUserID)
|
l.Infof("[handleMobileNotExists] 用户升级为正式用户成功, userId: %s", currentUserID)
|
||||||
// 创建 mobile 认证
|
if err := l.insertMobileUserAuthOrSkip(ctx, session, currentUserID, encryptedMobile); err != nil {
|
||||||
if _, err := l.svcCtx.UserAuthModel.Insert(ctx, session, &model.UserAuth{
|
return "", errors.Wrap(err, "创建手机号认证失败")
|
||||||
Id: uuid.NewString(),
|
|
||||||
UserId: currentUserID,
|
|
||||||
AuthType: model.UserAuthTypeMobile,
|
|
||||||
AuthKey: encryptedMobile,
|
|
||||||
}); err != nil {
|
|
||||||
return "", errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "创建手机号认证失败: %v", err)
|
|
||||||
}
|
}
|
||||||
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", currentUserID)
|
l.Infof("[handleMobileNotExists] 手机号认证创建成功, userId: %s", currentUserID)
|
||||||
return currentUserID, nil
|
return currentUserID, nil
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"qnc-server/common/reviewphone"
|
||||||
"qnc-server/common/xerr"
|
"qnc-server/common/xerr"
|
||||||
"qnc-server/pkg/captcha"
|
"qnc-server/pkg/captcha"
|
||||||
"qnc-server/pkg/lzkit/crypto"
|
"qnc-server/pkg/lzkit/crypto"
|
||||||
@@ -42,6 +43,20 @@ func (l *SendSmsLogic) SendSms(req *types.SendSmsReq, clientIP string, userAgent
|
|||||||
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err)
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 加密手机号失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 审核体验:预留号段申请代理时不发短信、不校验滑块,仅写入与注册校验一致的固定验证码
|
||||||
|
if req.ActionType == "agentApply" && reviewphone.IsAppReviewDemoMobile(req.Mobile) {
|
||||||
|
codeKey := fmt.Sprintf("%s:%s", req.ActionType, encryptedMobile)
|
||||||
|
limitCodeKey := fmt.Sprintf("limit:%s:%s", req.ActionType, encryptedMobile)
|
||||||
|
if err := l.svcCtx.Redis.Setex(codeKey, reviewphone.DemoVerifyCode, l.svcCtx.Config.VerifyCode.ValidTime); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 审核号写入验证码失败: %v", err)
|
||||||
|
}
|
||||||
|
if err := l.svcCtx.Redis.Setex(limitCodeKey, "1", 60); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "短信发送, 审核号限流标记失败: %v", err)
|
||||||
|
}
|
||||||
|
l.Infof("短信发送, 审核体验号段跳过真实短信, mobile=%s", req.Mobile)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 滑块验证码校验(可选,支持微信环境跳过验证)
|
// 1. 滑块验证码校验(可选,支持微信环境跳过验证)
|
||||||
cfg := l.svcCtx.Config.Captcha
|
cfg := l.svcCtx.Config.Captcha
|
||||||
captchaResult := captcha.VerifyOptionalWithUserAgent(captcha.Config{
|
captchaResult := captcha.VerifyOptionalWithUserAgent(captcha.Config{
|
||||||
|
|||||||
22
common/reviewphone/reviewphone.go
Normal file
22
common/reviewphone/reviewphone.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Package reviewphone 定义微信小程序等平台审核时使用的固定体验手机号段与验证码,
|
||||||
|
// 仅用于审核人员完整体验代理注册流程,勿用于开放环境绕过真实校验。
|
||||||
|
package reviewphone
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DemoMobileMin / DemoMobileMax 为 inclusive 区间,共 101 个号码。
|
||||||
|
DemoMobileMin int64 = 13100009999
|
||||||
|
DemoMobileMax int64 = 13100010099
|
||||||
|
// DemoVerifyCode 与 DemoMobile 区间配合使用,通过服务端校验。
|
||||||
|
DemoVerifyCode = "143838"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsAppReviewDemoMobile 判断是否为审核预留手机号(13100009999–13100010099)。
|
||||||
|
func IsAppReviewDemoMobile(mobile string) bool {
|
||||||
|
n, err := strconv.ParseInt(mobile, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return n >= DemoMobileMin && n <= DemoMobileMax
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user