mirror of https://github.com/veypi/OneAuth.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
package sms
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/veypi/OneAuth/cfg"
|
|
"github.com/veypi/OneAuth/libs/sms_providers"
|
|
"github.com/veypi/OneAuth/libs/utils"
|
|
"github.com/veypi/OneAuth/models"
|
|
"github.com/vyes-ai/vigo"
|
|
"github.com/vyes-ai/vigo/contrib/limiter"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// SendCodeRequest 发送验证码请求
|
|
type SendCodeRequest struct {
|
|
Phone string `json:"phone"`
|
|
Region string `json:"region"`
|
|
Purpose string `json:"purpose"`
|
|
TemplateID string `json:"template_id"`
|
|
}
|
|
|
|
// SendCodeResponse 发送验证码响应
|
|
type SendCodeResponse struct {
|
|
ID uint `json:"id"`
|
|
Phone string `json:"phone"`
|
|
ExpiresAt time.Time `json:"expires_at"`
|
|
Interval int `json:"interval"` // 下次可发送间隔(秒)
|
|
}
|
|
|
|
var _ = Router.Post("/", vigo.SkipBefore, limiter.NewAdvancedRequestLimiter(time.Minute*3, 5, time.Second*30), sendCode)
|
|
|
|
// SendCode 发送验证码
|
|
func sendCode(x *vigo.X) (any, error) {
|
|
req := &SendCodeRequest{}
|
|
err := x.Parse(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// 1. 验证手机号
|
|
normalizedPhone := utils.NormalizePhoneNumber(req.Phone)
|
|
if !utils.ValidatePhoneNumber(normalizedPhone) {
|
|
return nil, fmt.Errorf("invalid phone number: %s", req.Phone)
|
|
}
|
|
|
|
// 2. 检查区域配置
|
|
provider, err := sms_providers.GetSMSProvider(req.Region)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 3. 检查发送频率限制
|
|
if err := CheckSendInterval(normalizedPhone, req.Region, req.Purpose); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 4. 检查每日发送次数限制
|
|
if err := CheckDailyLimit(normalizedPhone, req.Region); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 5. 生成验证码
|
|
code, err := utils.GenerateCode(cfg.Config.SMS.Global.CodeLength)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to generate code: %w", err)
|
|
}
|
|
|
|
// 6. 创建验证码记录
|
|
smsCode := &models.SMSCode{
|
|
Phone: normalizedPhone,
|
|
Code: code,
|
|
Region: req.Region,
|
|
Purpose: req.Purpose,
|
|
Status: models.CodeStatusPending,
|
|
ExpiresAt: time.Now().Add(cfg.Config.SMS.Global.CodeExpiry),
|
|
}
|
|
|
|
// 7. 发送短信
|
|
sendReq := &sms_providers.SendSMSRequest{
|
|
Phone: normalizedPhone,
|
|
TemplateID: req.TemplateID,
|
|
Region: req.Region,
|
|
Purpose: req.Purpose,
|
|
Params: map[string]string{
|
|
"code": code,
|
|
},
|
|
}
|
|
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
|
err := tx.Create(smsCode).Error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
smsID, err := provider.SendSMS(x.Context(), sendReq)
|
|
LogSMS(normalizedPhone, req.Region, provider.GetName(), smsID, err)
|
|
if err != nil || smsID == "" {
|
|
return fmt.Errorf("failed to send sms: %v", err)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &SendCodeResponse{
|
|
ID: smsCode.ID,
|
|
Phone: normalizedPhone,
|
|
ExpiresAt: smsCode.ExpiresAt,
|
|
Interval: int(cfg.Config.SMS.Global.SendInterval.Seconds()),
|
|
}, nil
|
|
}
|