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 }