Files
lone-services/pkg/utils/sms.go
T
2026-08-07 10:51:47 +08:00

307 lines
6.9 KiB
Go

package utils
import (
"crypto/tls"
"encoding/json"
"fmt"
"net/smtp"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v3/client"
"github.com/alibabacloud-go/tea/tea"
"github.com/joho/godotenv"
)
// 短信签名常量
const (
//SignLone = "爱萝湾"
SignLone = "爱萝湾上海科技有限公司"
)
// 短信模板CODE常量
const (
SmsCode = "SMS_506160477"
SmsPwd = "SMS_506200455"
)
// 状态码常量(模拟PHP中的Code类)
const (
CodeOK = 0 // 成功状态码
CodeError = -1 // 错误状态码
)
// ResponseData 短信发送返回结构体
type ResponseData struct {
Code int `json:"code"`
Msg interface{} `json:"msg"`
}
type EmailMsg struct {
To []string // 收件人
Cc []string // 抄送
Bcc []string // 密送
Subject string // 标题
Body string // 正文,支持html
IsHTML bool // 是否HTML格式
}
// CreateClient 创建阿里云短信客户端
func CreateClient() (*dysmsapi20170525.Client, error) {
// 加载.env文件(如果需要)
_ = godotenv.Load()
key := GetConfigString("sms.sms_ali_api_key")
secret := GetConfigString("sms.sms_ali_api_secret")
// 初始化配置
config := &openapi.Config{
// 从环境变量获取AccessKey ID
AccessKeyId: &key,
// 从环境变量获取AccessKey Secret
AccessKeySecret: &secret,
}
// 设置短信服务接入点
config.Endpoint = tea.String("dysmsapi.aliyuncs.com")
// 创建客户端
client, err := dysmsapi20170525.NewClient(config)
if err != nil {
return nil, err
}
return client, nil
}
// SendSMS 发送短信主方法
// mobile: 手机号
// code: 短信模板CODE
// data: 模板参数(map格式)
// sign: 短信签名(可选,默认使用SignLone)
func SendSMS(mobile, code string, data map[string]interface{}, sign ...string) ResponseData {
test := GetConfigBool("sms.sms_test")
retData := ResponseData{
Code: CodeOK,
Msg: "",
}
if test {
return retData
}
// 设置默认签名
signName := SignLone
name := GetConfigString("sms.sms_sign")
if name != "" {
signName = name
}
if len(sign) > 0 && sign[0] != "" {
signName = sign[0]
}
// 创建客户端
client, err := CreateClient()
if err != nil {
return ResponseData{
Code: CodeError,
Msg: fmt.Sprintf("创建客户端失败: %s", err.Error()),
}
}
// 转换模板参数为JSON字符串
templateParam, err := json.Marshal(data)
if err != nil {
return ResponseData{
Code: CodeError,
Msg: fmt.Sprintf("参数JSON编码失败: %s", err.Error()),
}
}
// 构建发送短信请求
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
PhoneNumbers: tea.String(mobile),
SignName: tea.String(signName),
TemplateCode: tea.String(code),
TemplateParam: tea.String(string(templateParam)),
}
Logger.Error(signName, "m=", mobile, "<UNK>:", sendSmsRequest, "2=", code, "tmpo:", string(templateParam))
// 发送短信
response, err := client.SendSms(sendSmsRequest)
if err != nil {
// 处理错误
retData.Code = CodeError
retData.Msg = fmt.Sprintf("发送短信失败: %s", err.Error())
return retData
}
statusCode := *response.StatusCode
// 检查响应状态
if statusCode != 200 {
retData.Code = CodeError
}
// 将响应转换为map返回
retData.Msg = map[string]interface{}{
"StatusCode": response.StatusCode,
"Body": response.Body,
}
return retData
}
// 简化调用示例(可选)
// SendCodeSMS 发送验证码短信
func SendCodeSMS(mobile string, code string) ResponseData {
data := map[string]interface{}{
"code": code,
}
code = GetConfigString("sms.sms_code_sign")
if code == "" {
code = SmsCode
}
return SendSMS(mobile, code, data)
}
func SendCodeMail(msg EmailMsg) ResponseData {
user := GetConfigString("sms.sms_mail_user")
pwd := GetConfigString("sms.sms_mail_pwd")
host := GetConfigString("sms.sms_mail_host")
port := GetConfigInt("sms.sms_mail_port")
name := GetConfigString("sms.sms_mail_show_name")
auth := smtp.PlainAuth("", user, pwd, host)
from := fmt.Sprintf("%s <%s>", name, user)
// 构建邮件头部
var header string
header += fmt.Sprintf("From: %s\r\n", from)
header += fmt.Sprintf("To: %s\r\n", joinStrings(msg.To, ","))
if len(msg.Cc) > 0 {
header += fmt.Sprintf("Cc: %s\r\n", joinStrings(msg.Cc, ","))
}
if len(msg.Bcc) > 0 {
header += fmt.Sprintf("Bcc: %s\r\n", joinStrings(msg.Bcc, ","))
}
header += fmt.Sprintf("Subject: %s\r\n", msg.Subject)
contentType := "text/plain; charset=utf-8"
if msg.IsHTML {
contentType = "text/html; charset=utf-8"
}
header += fmt.Sprintf("Content-Type: %s\r\n\r\n", contentType)
fullMsg := []byte(header + msg.Body)
tlsConfig := &tls.Config{
ServerName: host,
}
addr := fmt.Sprintf("%s:%d", host, port)
// 1. 先建立SSL连接(465端口)
conn, err := tls.Dial("tcp", addr, tlsConfig)
if err != nil {
Logger.Error("tls dial err:", err)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
// 2. 关键:用加密连接创建smtp client
client, err := smtp.NewClient(conn, host)
if err != nil {
_ = conn.Close()
Logger.Error("smtp.NewClient err:", err)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
// 由client统一管理连接关闭
defer client.Close()
if err = client.Auth(auth); err != nil {
Logger.Error("client.Auth err:", err)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
if err = client.Mail(user); err != nil {
Logger.Error("client.Mail err:", err)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
receivers := append(append(msg.To, msg.Cc...), msg.Bcc...)
for _, r := range receivers {
if err = client.Rcpt(r); err != nil {
Logger.Error("client.Rcpt err:", err, "rcpt:", r)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
}
w, err := client.Data()
if err != nil {
Logger.Error("client.Data err:", err)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
_, err = w.Write(fullMsg)
if err != nil {
Logger.Error("write mail body err:", err)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
err = w.Close()
if err != nil {
Logger.Error("w.Close err:", err)
return ResponseData{
Code: CodeError,
Msg: err.Error(),
}
}
err = client.Quit()
if err != nil {
Logger.Error("client.Quit err:", err)
return ResponseData{
Code: CodeError,
Msg: "smtp quit failed: " + err.Error(),
}
}
return ResponseData{
Code: CodeOK,
Msg: "",
}
}
func joinStrings(arr []string, sep string) string {
if len(arr) == 0 {
return ""
}
res := arr[0]
for i := 1; i < len(arr); i++ {
res += sep + arr[i]
}
return res
}
// SendPwdSMS 发送密码相关短信
func SendPwdSMS(mobile string, pwd string) ResponseData {
data := map[string]interface{}{
"code": pwd,
}
code := GetConfigString("sms.sms_code_sign")
if code == "" {
code = SmsPwd
}
return SendSMS(mobile, code, data)
}