Files
solosw 99b11b04e4 1.0
2026-01-05 14:11:34 +08:00

262 lines
6.2 KiB
Go

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
})
}
}