sync: 对齐小程序 v1.0.2 生产代码并脱敏

This commit is contained in:
MiFaZhan
2026-10-29 15:15:12 +08:00
parent a7abd476f0
commit 5fcf74deb0
423 changed files with 15001 additions and 6382 deletions
+195 -31
View File
@@ -7,7 +7,7 @@ import (
"encoding/json"
"fmt"
"io"
"math/rand"
"math/big"
"net/http"
"net/url"
"strconv"
@@ -39,10 +39,20 @@ import (
const appSideCaptchaCachePrefix = "app:side:captcha:"
const appSmsCachePrefix = "app:sms:"
const appSmsCooldownPrefix = "app:sms:cooldown:"
const appSmsDailyPhonePrefix = "app:sms:daily:phone:"
const appSmsDailyIPPrefix = "app:sms:daily:ip:"
const appWechatBindTicketCachePrefix = "app:wechat:bind:"
const appWechatMiniAppSecretPlaceholder = "CHANGE_ME_WECHAT_MINI_APP_SECRET"
const appWechatBindTicketExpire = 10 * time.Minute
const (
appSmsCodeTTL = 5 * time.Minute
appSmsCooldownTTL = 60 * time.Second
appSmsDailyLimit = 10
appSmsDailyExpire = 24 * time.Hour
)
var appSlideCaptcha slide.Captcha
type appWechatCode2SessionRes struct {
@@ -188,16 +198,32 @@ func (s *sAppAuth) SendSms(ctx context.Context, param *model.AppSendSmsParam) (r
param.TenantId = strings.TrimSpace(param.TenantId)
param.Phone = strings.TrimSpace(param.Phone)
param.Scene = strings.TrimSpace(param.Scene)
if err = s.validateAppSmsScene(ctx, param); err != nil {
return nil, err
}
if err = s.reserveAppSmsCooldown(ctx, param.TenantId, param.Phone); err != nil {
return nil, err
}
if err = s.checkAppSmsRateLimit(ctx, param.TenantId, param.Phone); err != nil {
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
return nil, err
}
code := fmt.Sprintf("%04d", rand.Intn(10000))
code, err := appRandomDigits(6)
if err != nil {
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
return nil, gerror.Wrap(err, "生成短信验证码失败")
}
cacheKey := appSmsCacheKey(param.TenantId, param.Phone, param.Scene)
if err = mcache.Set(ctx, cacheKey, code, 5*time.Minute); err != nil {
if err = setAppSmsCode(ctx, cacheKey, code); err != nil {
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
return nil, gerror.New("短信验证码发送失败")
}
sentByHTTP, err := sendAppPhoneCode(ctx, param.Phone, code)
sentByHTTP, err := sendAppPhoneCode(ctx, param.Phone, code, appSmsServiceScene(param.Scene))
if err != nil {
_ = mcache.Remove(ctx, cacheKey)
_ = deleteAppSmsCode(ctx, cacheKey)
_ = releaseAppSmsCooldown(ctx, param.TenantId, param.Phone)
g.Log().Errorf(ctx, "APP短信验证码发送失败 scene=%s phone=%s err=%+v", param.Scene, param.Phone, err)
return nil, gerror.New("短信验证码发送失败,请稍后重试")
}
@@ -209,19 +235,110 @@ func (s *sAppAuth) SendSms(ctx context.Context, param *model.AppSendSmsParam) (r
return res, nil
}
func sendAppPhoneCode(ctx context.Context, phone string, code string) (sentByHTTP bool, err error) {
func (s *sAppAuth) validateAppSmsScene(ctx context.Context, param *model.AppSendSmsParam) error {
if err := service.AppAuth().CheckSideCaptcha(ctx, &model.AppCheckSideCaptchaParam{
CaptchaKey: strings.TrimSpace(param.CaptchaKey),
Point: strings.TrimSpace(param.CaptchaPoint),
}); err != nil {
return err
}
users, err := s.findAppLoginUsersByAccount(ctx, param.TenantId, param.Phone)
if err != nil {
return err
}
switch param.Scene {
case "register":
if len(users) > 0 {
return gerror.New("手机号已注册")
}
case "login", "forgotPassword", "wechatBindPhone":
if len(users) == 0 {
return gerror.New("手机号未注册")
}
}
return nil
}
func (s *sAppAuth) checkAppSmsRateLimit(ctx context.Context, tenantId string, phone string) error {
tenantId = strings.TrimSpace(tenantId)
if tenantId == "" {
tenantId = consts.DefaultSystemTenantCode
}
clientIP := strings.TrimSpace(utility.GetClientIp(ctx))
for _, item := range []struct {
key string
name string
skip bool
}{
{appSmsDailyPhonePrefix + strings.TrimSpace(tenantId) + ":" + phone, "手机号", false},
{appSmsDailyIPPrefix + clientIP, "当前网络", clientIP == ""},
} {
if item.skip {
continue
}
value, err := g.Redis().Do(ctx, "INCR", item.key)
if err != nil {
return gerror.Wrap(err, "短信服务暂不可用")
}
if value.Int() == 1 {
_, _ = g.Redis().Do(ctx, "EXPIRE", item.key, int(appSmsDailyExpire.Seconds()))
}
if value.Int() > appSmsDailyLimit {
return gerror.Newf("%s今日短信发送次数已达上限", item.name)
}
}
return nil
}
func (s *sAppAuth) reserveAppSmsCooldown(ctx context.Context, tenantId string, phone string) error {
key := appSmsCooldownCacheKey(tenantId, phone)
value, err := g.Redis().Do(ctx, "SET", key, "1", "NX", "EX", int(appSmsCooldownTTL.Seconds()))
if err != nil {
return gerror.Wrap(err, "短信服务暂不可用")
}
if value.IsNil() || strings.EqualFold(value.String(), "") {
return gerror.New("短信验证码发送过于频繁,请稍后再试")
}
return nil
}
func releaseAppSmsCooldown(ctx context.Context, tenantId string, phone string) error {
_, err := g.Redis().Do(ctx, "DEL", appSmsCooldownCacheKey(tenantId, phone))
return err
}
func appSmsCooldownCacheKey(tenantId string, phone string) string {
if tenantId == "" {
tenantId = consts.DefaultSystemTenantCode
}
return fmt.Sprintf("%s%s:%s", appSmsCooldownPrefix, tenantId, phone)
}
func appSmsServiceScene(scene string) string {
switch scene {
case "wechatBindPhone":
return "bind_phone"
default:
return scene
}
}
func sendAppPhoneCode(ctx context.Context, phone string, code string, scene string) (sentByHTTP bool, err error) {
var cfg appCodeHttpSenderConfig
if v, cfgErr := g.Cfg().Get(ctx, "register.sms"); cfgErr == nil && !v.IsNil() {
_ = v.Scan(&cfg)
}
if strings.TrimSpace(cfg.Url) != "" {
return true, callAppCodeHTTP(ctx, cfg, phone, code)
return true, callAppCodeHTTP(ctx, cfg, phone, code, scene)
}
if !strings.EqualFold(strings.TrimSpace(g.Cfg().MustGet(ctx, "system.gfmode", "prod").String()), "dev") {
return false, gerror.New("短信服务未配置")
}
g.Log().Infof(ctx, "APP短信验证码已生成 phone=%s code=%s", phone, code)
return false, nil
}
func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact string, code string) error {
func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact string, code string, scene string) error {
method := strings.ToUpper(strings.TrimSpace(cfg.Method))
if method == "" {
method = http.MethodPost
@@ -231,7 +348,7 @@ func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact s
successStatus = http.StatusOK
}
body := renderAppCodeTemplate(cfg.BodyTemplate, contact, code)
body := renderAppCodeTemplate(cfg.BodyTemplate, contact, code, scene)
if strings.TrimSpace(body) == "" {
bodyBytes, _ := json.Marshal(g.Map{
"contact": contact,
@@ -280,7 +397,7 @@ func callAppCodeHTTP(ctx context.Context, cfg appCodeHttpSenderConfig, contact s
return nil
}
func renderAppCodeTemplate(template string, contact string, code string) string {
func renderAppCodeTemplate(template string, contact string, code string, scene string) string {
template = strings.TrimSpace(template)
if template == "" {
return ""
@@ -295,6 +412,9 @@ func renderAppCodeTemplate(template string, contact string, code string) string
"{phone}", contact,
"{{phone}}", contact,
"${phone}", contact,
"{scene}", scene,
"{{scene}}", scene,
"${scene}", scene,
"{code}", code,
"{{code}}", code,
"${code}", code,
@@ -309,23 +429,37 @@ func appSmsCacheKey(tenantId string, phone string, scene string) string {
return fmt.Sprintf("%s%s:%s:%s", appSmsCachePrefix, tenantId, scene, phone)
}
func setAppSmsCode(ctx context.Context, key string, code string) error {
_, err := g.Redis().Do(ctx, "SETEX", key, int(appSmsCodeTTL.Seconds()), code)
return err
}
func deleteAppSmsCode(ctx context.Context, key string) error {
_, err := g.Redis().Do(ctx, "DEL", key)
return err
}
func verifyAppSmsCode(ctx context.Context, tenantId string, phone string, scene string, code string) error {
value, err := mcache.Get(ctx, appSmsCacheKey(tenantId, phone, scene))
value, err := g.Redis().Get(ctx, appSmsCacheKey(tenantId, phone, scene))
if err != nil || value == nil {
return gerror.NewCode(consts.CodeCaptchaError, "短信验证码已失效")
}
if fmt.Sprintf("%v", value) != code {
if strings.TrimSpace(value.String()) != strings.TrimSpace(code) {
return gerror.NewCode(consts.CodeCaptchaError, "短信验证码错误")
}
return nil
}
func verifyOptionalAppSmsCode(ctx context.Context, tenantId string, phone string, scene string, code string) error {
code = strings.TrimSpace(code)
if code == "" {
return nil
func appRandomDigits(length int) (string, error) {
if length <= 0 {
length = 6
}
return verifyAppSmsCode(ctx, tenantId, phone, scene, code)
limit := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(length)), nil)
value, err := cryptorand.Int(cryptorand.Reader, limit)
if err != nil {
return "", err
}
return fmt.Sprintf("%0*d", length, value.Int64()), nil
}
func (s *sAppAuth) PhoneRegister(ctx context.Context, param *model.AppPhoneRegisterParam) (res *model.AppAuthTokenModel, err error) {
@@ -335,7 +469,7 @@ func (s *sAppAuth) PhoneRegister(ctx context.Context, param *model.AppPhoneRegis
param.TenantId = strings.TrimSpace(param.TenantId)
param.Phone = strings.TrimSpace(param.Phone)
param.SmsCode = strings.TrimSpace(param.SmsCode)
if err = verifyOptionalAppSmsCode(ctx, param.TenantId, param.Phone, "register", param.SmsCode); err != nil {
if err = verifyAppSmsCode(ctx, param.TenantId, param.Phone, "register", param.SmsCode); err != nil {
return nil, err
}
if param.TenantId == "" {
@@ -406,7 +540,7 @@ func (s *sAppAuth) PhoneRegister(ctx context.Context, param *model.AppPhoneRegis
}
return nil, err
}
_ = mcache.Remove(ctx, appSmsCacheKey(param.TenantId, param.Phone, "register"))
_ = deleteAppSmsCode(ctx, appSmsCacheKey(param.TenantId, param.Phone, "register"))
return s.PhoneLogin(ctx, &model.AppPhoneLoginParam{
TenantId: param.TenantId,
@@ -522,6 +656,43 @@ func (s *sAppAuth) PhoneLogin(ctx context.Context, param *model.AppPhoneLoginPar
return s.buildAppAuthToken(ctx, user)
}
func (s *sAppAuth) PhoneSmsLogin(ctx context.Context, param *model.AppPhoneSmsLoginParam) (res *model.AppAuthTokenModel, err error) {
if param == nil {
return nil, gerror.New("请求参数不能为空")
}
param.TenantId = strings.TrimSpace(param.TenantId)
param.Phone = strings.TrimSpace(param.Phone)
param.SmsCode = strings.TrimSpace(param.SmsCode)
if err = verifyAppSmsCode(ctx, param.TenantId, param.Phone, "login", param.SmsCode); err != nil {
return nil, err
}
users, err := s.findAppWechatBindUsersByPhone(ctx, param.TenantId, param.Phone)
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, gerror.New("手机号未注册")
}
if len(users) > 1 && param.SelectedUserId <= 0 {
return &model.AppAuthTokenModel{NeedSelect: true, Candidates: appLoginCandidateModels(ctx, users)}, nil
}
user := users[0]
if param.SelectedUserId > 0 {
user = nil
for _, item := range users {
if item != nil && item.UserId == param.SelectedUserId {
user = item
break
}
}
if user == nil {
return nil, gerror.New("选择的账号不属于当前手机号")
}
}
_ = deleteAppSmsCode(ctx, appSmsCacheKey(param.TenantId, param.Phone, "login"))
return s.buildAppAuthToken(ctx, user)
}
func (s *sAppAuth) WechatLogin(ctx context.Context, param *model.AppWechatLoginParam) (res *model.AppWechatLoginModel, err error) {
if param == nil {
return nil, gerror.New("请求参数不能为空")
@@ -619,6 +790,7 @@ func (s *sAppAuth) WechatBindPhone(ctx context.Context, param *model.AppWechatBi
if err = s.saveWechatOpenId(ctx, user, ticketData.OpenId, ticketData.UnionId); err != nil {
return nil, err
}
_ = deleteAppSmsCode(ctx, appSmsCacheKey(tenantId, phone, "wechatBindPhone"))
_ = mcache.Remove(ctx, appWechatBindTicketKey(param.BindTicket))
token, err := s.buildAppAuthToken(ctx, user)
@@ -819,20 +991,12 @@ func appEnsureWechatBindPhoneVerified(ctx context.Context, param *model.AppWecha
if ticketData != nil && strings.TrimSpace(ticketData.VerifiedPhone) == strings.TrimSpace(phone) {
return nil
}
captchaKey := strings.TrimSpace(param.CaptchaKey)
captchaPoint := strings.TrimSpace(param.CaptchaPoint)
if captchaKey == "" || captchaPoint == "" {
return gerror.New("请先完成图形验证")
}
if err := service.AppAuth().CheckSideCaptcha(ctx, &model.AppCheckSideCaptchaParam{
CaptchaKey: captchaKey,
Point: captchaPoint,
}); err != nil {
return err
}
if ticketData == nil || strings.TrimSpace(ticketData.OpenId) == "" || strings.TrimSpace(phone) == "" {
return gerror.New("微信绑定参数不完整")
}
if err := verifyAppSmsCode(ctx, ticketData.TenantId, phone, "wechatBindPhone", param.SmsCode); err != nil {
return err
}
ticketData.VerifiedPhone = strings.TrimSpace(phone)
if err := mcache.Set(ctx, appWechatBindTicketKey(param.BindTicket), ticketData, appWechatBindTicketExpire); err != nil {
return gerror.Wrap(err, "保存手机号验证状态失败")
@@ -1075,7 +1239,7 @@ func (s *sAppAuth) ForgotPassword(ctx context.Context, param *model.AppForgotPas
if err != nil {
return err
}
_ = mcache.Remove(ctx, appSmsCacheKey(param.TenantId, param.Phone, "forgotPassword"))
_ = deleteAppSmsCode(ctx, appSmsCacheKey(param.TenantId, param.Phone, "forgotPassword"))
salt := utility.RandomString(5)
password := appPasswordEncryptForStore(param.Password, salt)