306 lines
6.9 KiB
Go
306 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 = getConfig("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 := getConfig("sms.sms_pwd_sign")
|
|
if code == "" {
|
|
code = SmsPwd
|
|
}
|
|
return SendSMS(mobile, code, data)
|
|
}
|