Files
ai_wht_wechat/go_backend/service/sms_service.go

265 lines
6.9 KiB
Go
Raw Normal View History

2026-01-06 19:36:42 +08:00
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
}