package services import ( "fmt" "math/rand" "sync" "time" ) // SMSCodeInfo 短信验证码信息 type SMSCodeInfo struct { Code string // 验证码 Phone string // 手机号 IP string // IP地址 CreatedAt time.Time // 创建时间 ExpiresAt time.Time // 过期时间 } // RateLimitInfo 频率限制信息 type RateLimitInfo struct { Count int // 请求次数 FirstTime time.Time // 首次请求时间 LastTime time.Time // 最后请求时间 } // SMSService 短信验证码服务 type SMSService struct { // 验证码存储:key为手机号,value为验证码信息 codes sync.Map // map[string]*SMSCodeInfo // 手机号频率限制:key为手机号,value为限制信息 phoneLimits sync.Map // map[string]*RateLimitInfo // IP频率限制:key为IP地址,value为限制信息 ipLimits sync.Map // map[string]*RateLimitInfo } var ( smsServiceInstance *SMSService smsServiceOnce sync.Once ) // GetSMSService 获取短信服务单例 func GetSMSService() *SMSService { smsServiceOnce.Do(func() { smsServiceInstance = &SMSService{} // 启动清理协程,定期清理过期数据 go smsServiceInstance.cleanup() }) return smsServiceInstance } // GenerateCode 生成6位数字验证码 func (s *SMSService) GenerateCode() string { // 使用时间戳作为随机种子,确保每次生成的验证码都不同 r := rand.New(rand.NewSource(time.Now().UnixNano())) return fmt.Sprintf("%06d", r.Intn(1000000)) } // SendSMS 发送短信验证码 // phone: 手机号 // ip: 客户端IP地址 // 返回:验证码、错误信息 func (s *SMSService) SendSMS(phone, ip string) (string, error) { // 1. 验证手机号格式 if !s.validatePhone(phone) { return "", fmt.Errorf("手机号格式不正确") } // 2. 检查手机号频率限制(同一手机号1分钟内只能发送1次) if err := s.checkPhoneLimit(phone); err != nil { return "", err } // 3. 检查IP频率限制(同一IP1分钟内最多发送5次) if err := s.checkIPLimit(ip); err != nil { return "", err } // 4. 生成验证码 code := s.GenerateCode() // 5. 存储验证码信息(有效期5分钟) codeInfo := &SMSCodeInfo{ Code: code, Phone: phone, IP: ip, CreatedAt: time.Now(), ExpiresAt: time.Now().Add(5 * time.Minute), } s.codes.Store(phone, codeInfo) // 6. 更新频率限制记录 s.updatePhoneLimit(phone) s.updateIPLimit(ip) // TODO: 这里应该调用真实的短信服务API发送验证码 // 目前只返回验证码,实际生产环境需要调用短信服务商API fmt.Printf("[SMS] 发送验证码到 %s: %s (IP: %s)\n", phone, code, ip) return code, nil } // VerifyCode 验证验证码 func (s *SMSService) VerifyCode(phone, code string) bool { value, ok := s.codes.Load(phone) if !ok { return false } codeInfo := value.(*SMSCodeInfo) // 检查是否过期 if time.Now().After(codeInfo.ExpiresAt) { s.codes.Delete(phone) return false } // 验证码匹配 if codeInfo.Code == code { // 验证成功后删除验证码(一次性使用) s.codes.Delete(phone) return true } return false } // validatePhone 验证手机号格式 func (s *SMSService) validatePhone(phone string) bool { if len(phone) != 11 { return false } // 简单验证:1开头,第二位3-9 if phone[0] != '1' || phone[1] < '3' || phone[1] > '9' { return false } return true } // checkPhoneLimit 检查手机号频率限制 // 同一手机号1分钟内只能发送1次 func (s *SMSService) checkPhoneLimit(phone string) error { value, ok := s.phoneLimits.Load(phone) if !ok { return nil // 没有限制记录,可以发送 } limitInfo := value.(*RateLimitInfo) // 检查是否在限制时间内(1分钟) if time.Since(limitInfo.LastTime) < 1*time.Minute { return fmt.Errorf("操作过于频繁,请1分钟后再试") } return nil } // checkIPLimit 检查IP频率限制 // 同一IP1分钟内最多发送5次 func (s *SMSService) checkIPLimit(ip string) error { value, ok := s.ipLimits.Load(ip) if !ok { return nil // 没有限制记录,可以发送 } limitInfo := value.(*RateLimitInfo) // 如果超过1分钟,重置计数 if time.Since(limitInfo.FirstTime) >= 1*time.Minute { limitInfo.Count = 0 limitInfo.FirstTime = time.Now() return nil } // 检查是否超过限制(1分钟内最多5次) if limitInfo.Count >= 5 { return fmt.Errorf("请求过于频繁,请稍后再试") } return nil } // updatePhoneLimit 更新手机号限制记录 func (s *SMSService) updatePhoneLimit(phone string) { value, ok := s.phoneLimits.Load(phone) if !ok { // 创建新记录 s.phoneLimits.Store(phone, &RateLimitInfo{ Count: 1, FirstTime: time.Now(), LastTime: time.Now(), }) return } limitInfo := value.(*RateLimitInfo) limitInfo.Count++ limitInfo.LastTime = time.Now() } // updateIPLimit 更新IP限制记录 func (s *SMSService) updateIPLimit(ip string) { value, ok := s.ipLimits.Load(ip) if !ok { // 创建新记录 s.ipLimits.Store(ip, &RateLimitInfo{ Count: 1, FirstTime: time.Now(), LastTime: time.Now(), }) return } limitInfo := value.(*RateLimitInfo) // 如果超过1分钟,重置计数 if time.Since(limitInfo.FirstTime) >= 1*time.Minute { limitInfo.Count = 1 limitInfo.FirstTime = time.Now() limitInfo.LastTime = time.Now() } else { limitInfo.Count++ limitInfo.LastTime = time.Now() } } // cleanup 定期清理过期数据 func (s *SMSService) cleanup() { ticker := time.NewTicker(2 * time.Minute) // 每10分钟清理一次 defer ticker.Stop() for range ticker.C { now := time.Now() // 清理过期的验证码 s.codes.Range(func(key, value interface{}) bool { codeInfo := value.(*SMSCodeInfo) if now.After(codeInfo.ExpiresAt) { s.codes.Delete(key) } return true }) // 清理过期的频率限制记录(超过1小时未使用的记录) s.phoneLimits.Range(func(key, value interface{}) bool { limitInfo := value.(*RateLimitInfo) if now.Sub(limitInfo.LastTime) > 1*time.Hour { s.phoneLimits.Delete(key) } return true }) s.ipLimits.Range(func(key, value interface{}) bool { limitInfo := value.(*RateLimitInfo) if now.Sub(limitInfo.LastTime) > 1*time.Hour { s.ipLimits.Delete(key) } return true }) } }