package service import ( "ai_xhs/config" "crypto/rand" "errors" "fmt" "log" "math/big" "sync" "time" openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v4/client" util "github.com/alibabacloud-go/tea-utils/v2/service" "github.com/alibabacloud-go/tea/tea" ) // SmsService 短信服务 type SmsService struct { client *dysmsapi20170525.Client signName string templateCode string codeCache map[string]*VerificationCode cacheMutex sync.RWMutex alertPhone string // 宕机通知手机号 } // VerificationCode 验证码缓存 type VerificationCode struct { Code string ExpireTime time.Time SentAt time.Time } var ( smsServiceInstance *SmsService smsServiceOnce sync.Once ) // GetSmsService 获取短信服务单例 func GetSmsService() *SmsService { smsServiceOnce.Do(func() { smsServiceInstance = NewSmsService() }) return smsServiceInstance } // NewSmsService 创建短信服务 func NewSmsService() *SmsService { // 从配置读取阿里云短信配置 accessKeyId := config.AppConfig.AliSms.AccessKeyID accessKeySecret := config.AppConfig.AliSms.AccessKeySecret signName := config.AppConfig.AliSms.SignName templateCode := config.AppConfig.AliSms.TemplateCode if accessKeyId == "" || accessKeySecret == "" { log.Printf("[短信服务] 警告: 阿里云短信配置未设置,短信功能将不可用") return &SmsService{ signName: signName, templateCode: templateCode, codeCache: make(map[string]*VerificationCode), } } // 创建阿里云短信客户端 apiConfig := &openapi.Config{ AccessKeyId: tea.String(accessKeyId), AccessKeySecret: tea.String(accessKeySecret), } apiConfig.Endpoint = tea.String("dysmsapi.aliyuncs.com") client, err := dysmsapi20170525.NewClient(apiConfig) if err != nil { log.Printf("[短信服务] 创建阿里云客户端失败: %v", err) return &SmsService{ signName: signName, templateCode: templateCode, codeCache: make(map[string]*VerificationCode), } } log.Printf("[短信服务] 阿里云短信服务初始化成功") return &SmsService{ client: client, signName: signName, templateCode: templateCode, codeCache: make(map[string]*VerificationCode), } } // generateCode 生成随机6位数字验证码 func (s *SmsService) generateCode() string { code := "" for i := 0; i < 6; i++ { n, _ := rand.Int(rand.Reader, big.NewInt(10)) code += fmt.Sprintf("%d", n.Int64()) } return code } // SendVerificationCode 发送验证码 func (s *SmsService) SendVerificationCode(phone string) (string, error) { if s.client == nil { return "", errors.New("短信服务未配置") } // 生成验证码 code := s.generateCode() log.Printf("[短信服务] 正在发送验证码到 %s,验证码: %s", phone, code) // 构建短信请求 sendSmsRequest := &dysmsapi20170525.SendSmsRequest{ PhoneNumbers: tea.String(phone), SignName: tea.String(s.signName), TemplateCode: tea.String(s.templateCode), TemplateParam: tea.String(fmt.Sprintf(`{"code":"%s"}`, code)), } runtime := &util.RuntimeOptions{} // 发送短信 resp, err := s.client.SendSmsWithOptions(sendSmsRequest, runtime) if err != nil { log.Printf("[短信服务] 发送短信失败: %v", err) return "", fmt.Errorf("发送短信失败: %v", err) } // 检查返回结果 if resp.Body.Code == nil || *resp.Body.Code != "OK" { errMsg := "未知错误" if resp.Body.Message != nil { errMsg = *resp.Body.Message } log.Printf("[短信服务] 短信发送失败: %s", errMsg) return "", fmt.Errorf("短信发送失败: %s", errMsg) } // 缓存验证码 s.cacheMutex.Lock() s.codeCache[phone] = &VerificationCode{ Code: code, ExpireTime: time.Now().Add(5 * time.Minute), // 5分钟过期 SentAt: time.Now(), } s.cacheMutex.Unlock() log.Printf("[短信服务] 验证码发送成功,手机号: %s", phone) return code, nil } // VerifyCode 验证验证码 func (s *SmsService) VerifyCode(phone, code string) error { s.cacheMutex.RLock() cached, exists := s.codeCache[phone] s.cacheMutex.RUnlock() if !exists { return errors.New("验证码未发送或已过期,请重新获取") } // 检查是否过期 if time.Now().After(cached.ExpireTime) { s.cacheMutex.Lock() delete(s.codeCache, phone) s.cacheMutex.Unlock() return errors.New("验证码已过期,请重新获取") } // 验证码匹配 if code != cached.Code { return errors.New("验证码错误,请重新输入") } // 验证成功后删除验证码(一次性使用) s.cacheMutex.Lock() delete(s.codeCache, phone) s.cacheMutex.Unlock() log.Printf("[短信服务] 验证码验证成功,手机号: %s", phone) return nil } // CleanupExpiredCodes 清理过期的验证码(定时任务调用) func (s *SmsService) CleanupExpiredCodes() { s.cacheMutex.Lock() defer s.cacheMutex.Unlock() now := time.Now() expiredPhones := []string{} for phone, cached := range s.codeCache { if now.After(cached.ExpireTime) { expiredPhones = append(expiredPhones, phone) } } for _, phone := range expiredPhones { delete(s.codeCache, phone) } if len(expiredPhones) > 0 { log.Printf("[短信服务] 已清理 %d 个过期验证码", len(expiredPhones)) } } // StartCleanupTask 启动清理过期验证码的定时任务 func (s *SmsService) StartCleanupTask() { ticker := time.NewTicker(1 * time.Minute) // 每分钟清理一次 go func() { for range ticker.C { s.CleanupExpiredCodes() } }() log.Printf("[短信服务] 验证码清理任务已启动") } // SendServiceDownAlert 发送服务宕机通知短信 // 向指定手机号发送验证码为11111的通知短信 func (s *SmsService) SendServiceDownAlert(phone string, serviceName string) error { if s.client == nil { return errors.New("短信服务未配置") } // 固定验证码为11111作为宕机通知标识 alertCode := "11111" log.Printf("[短信服务] 发送服务宕机通知到 %s,服务: %s", phone, serviceName) // 构建短信请求 sendSmsRequest := &dysmsapi20170525.SendSmsRequest{ PhoneNumbers: tea.String(phone), SignName: tea.String(s.signName), TemplateCode: tea.String(s.templateCode), TemplateParam: tea.String(fmt.Sprintf(`{"code":"%s"}`, alertCode)), } runtime := &util.RuntimeOptions{} // 发送短信 resp, err := s.client.SendSmsWithOptions(sendSmsRequest, runtime) if err != nil { log.Printf("[短信服务] 发送宕机通知失败: %v", err) return fmt.Errorf("发送宕机通知失败: %v", err) } // 检查返回结果 if resp.Body.Code == nil || *resp.Body.Code != "OK" { errMsg := "未知错误" if resp.Body.Message != nil { errMsg = *resp.Body.Message } log.Printf("[短信服务] 宕机通知发送失败: %s", errMsg) return fmt.Errorf("宕机通知发送失败: %s", errMsg) } log.Printf("[短信服务] 服务宕机通知发送成功,手机号: %s,通知码: %s", phone, alertCode) return nil }