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, ":", 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) }