type AliyunSmsService struct { client *dypnsapi.Client rdb *redis.Client signName string templateCode string codeExpireTime time.Duration sendInterval time.Duration dailyLimit int ipDailyLimit int }
func NewAliyunSmsService(accessKeyID, accessKeySecret, signName, templateCode string, rdb *redis.Client) (*AliyunSmsService, error) { config := &openapi.Config{ AccessKeyId: tea.String(accessKeyID), AccessKeySecret: tea.String(accessKeySecret), Endpoint: tea.String("dypnsapi.aliyuncs.com"), } client, err := dypnsapi.NewClient(config) if err != nil { return nil, fmt.Errorf("failed to create aliyun sms client: %w", err) } return &AliyunSmsService{ client: client, rdb: rdb, signName: signName, templateCode: templateCode, codeExpireTime: 15 * time.Minute, sendInterval: 60 * time.Second, dailyLimit: 10, ipDailyLimit: 20, }, nil }
func (s *AliyunSmsService) SendVerifyCode(ctx context.Context, phone, codeType, ip string) (bizID, requestID string, err error) { if err := s.checkRateLimit(ctx, phone, ip); err != nil { return "", "", err } sendCodeRequest := &dypnsapi.SendSmsVerifyCodeRequest{ PhoneNumber: tea.String(phone), SignName: tea.String(s.signName), TemplateCode: tea.String(s.templateCode), TemplateParam: tea.String(`{"code":"##code##","min":"15"}`), CodeType: tea.Int64(1), CodeLength: tea.Int64(6), Interval: tea.Int64(60), ValidTime: tea.Int64(15), } sendCodeResponse, err := s.client.SendSmsVerifyCode(sendCodeRequest) if err != nil { return "", "", fmt.Errorf("failed to send sms: %w", err) } if sendCodeResponse.Body == nil || *sendCodeResponse.Body.Code != "OK" { errMsg := "unknown error" if sendCodeResponse.Body != nil && sendCodeResponse.Body.Message != nil { errMsg = *sendCodeResponse.Body.Message } return "", "", fmt.Errorf("sms send failed: %s", errMsg) } s.recordSend(ctx, phone, ip) requestID = "" if sendCodeResponse.Body.RequestId != nil { requestID = *sendCodeResponse.Body.RequestId } return "", requestID, nil }
func (s *AliyunSmsService) VerifyCode(ctx context.Context, phone, codeType, code string) (bool, error) { checkRequest := &dypnsapi.CheckSmsVerifyCodeRequest{ PhoneNumber: tea.String(phone), VerifyCode: tea.String(code), } checkResponse, err := s.client.CheckSmsVerifyCode(checkRequest) if err != nil { return false, fmt.Errorf("failed to verify code: %w", err) } if checkResponse.Body == nil || checkResponse.Body.Code == nil || *checkResponse.Body.Code != "OK" { return false, nil } return true, nil }
func (s *AliyunSmsService) checkRateLimit(ctx context.Context, phone, ip string) error { intervalKey := fmt.Sprintf("sms:interval:%s", phone) exists, err := s.rdb.Exists(ctx, intervalKey).Result() if err != nil { return fmt.Errorf("failed to check interval: %w", err) } if exists > 0 { return fmt.Errorf("发送过于频繁,请60秒后再试") } dailyKey := fmt.Sprintf("sms:daily:%s:%s", time.Now().Format("20060102"), phone) count, err := s.rdb.Get(ctx, dailyKey).Int() if err != nil && err != redis.Nil { return fmt.Errorf("failed to check daily limit: %w", err) } if count >= s.dailyLimit { return fmt.Errorf("今日发送次数已达上限") } return nil }
|
评论
0 条评论