Files
ai_wht_wechat/go_backend/service/sms_service.go
2026-01-06 19:36:42 +08:00

265 lines
6.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}