1.0
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user