265 lines
6.9 KiB
Go
265 lines
6.9 KiB
Go
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
|
||
}
|