411 lines
11 KiB
Go
411 lines
11 KiB
Go
package aliyun
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
|
|
"code.yun.ink/pkg/smsx/entity"
|
|
"code.yun.ink/pkg/smsx/interfaces"
|
|
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"
|
|
"github.com/yuninks/loggerx"
|
|
)
|
|
|
|
type Aliyun struct {
|
|
interfaces.DefaultSmsx
|
|
client *dysmsapi20170525.Client
|
|
params consts.SmsConfigData
|
|
logger loggerx.LoggerInterface
|
|
}
|
|
|
|
func (l *Aliyun) InitSmsx(ctx context.Context, params consts.SmsConfigData, logger loggerx.LoggerInterface) (interfaces.Smsx, error) {
|
|
l.logger = logger
|
|
if params.SysType != consts.Platform3rdTypeAliyun {
|
|
return nil, errors.New("not aliyun")
|
|
}
|
|
|
|
config := &openapi.Config{
|
|
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
|
|
AccessKeyId: tea.String(params.Aliyun.AccessKeyId),
|
|
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
|
|
AccessKeySecret: tea.String(params.Aliyun.AccessKeySecret),
|
|
}
|
|
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
|
|
if params.Aliyun.Endpoint == "" {
|
|
params.Aliyun.Endpoint = "dysmsapi.aliyuncs.com"
|
|
}
|
|
|
|
config.Endpoint = tea.String(params.Aliyun.Endpoint)
|
|
// _result = &dysmsapi20170525.Client{}
|
|
_result, err := dysmsapi20170525.NewClient(config)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Aliyun{
|
|
client: _result,
|
|
params: params,
|
|
logger: logger,
|
|
}, nil
|
|
}
|
|
|
|
// Send 发送短信
|
|
func (l *Aliyun) Send(ctx context.Context, templateCode string, phone string, params []interfaces.SmsSendParam) error {
|
|
if l.client == nil {
|
|
return errors.New("not init")
|
|
}
|
|
|
|
l.logger.Infof(ctx, "Aliyun send templateId:%+v phone:%+v params:%+v", templateCode, phone, params)
|
|
|
|
paramMap := map[string]string{}
|
|
for _, param := range params {
|
|
paramMap[param.Field] = param.Value
|
|
}
|
|
|
|
b, _ := json.Marshal(paramMap)
|
|
|
|
sendSmsRequest := &dysmsapi20170525.SendSmsRequest{
|
|
SignName: tea.String(l.params.Aliyun.SignName),
|
|
TemplateCode: tea.String(templateCode),
|
|
PhoneNumbers: tea.String(phone),
|
|
TemplateParam: tea.String(string(b)),
|
|
}
|
|
|
|
runtime := &util.RuntimeOptions{}
|
|
tryErr := func() (_e error) {
|
|
defer func() {
|
|
if r := tea.Recover(recover()); r != nil {
|
|
_e = r
|
|
}
|
|
}()
|
|
// 复制代码运行请自行打印 API 的返回值
|
|
resp, _err := l.client.SendSmsWithOptions(sendSmsRequest, runtime)
|
|
l.logger.Infof(ctx, "Aliyun resp resp:%+v err:%+v", resp, _err)
|
|
if _err != nil {
|
|
return _err
|
|
}
|
|
|
|
if resp == nil || resp.Body == nil {
|
|
return errors.New("未知响应")
|
|
}
|
|
|
|
if *resp.Body.Code != "OK" {
|
|
return errors.New(tea.StringValue(resp.Body.Message))
|
|
}
|
|
|
|
// {
|
|
// "headers": {
|
|
// "access-control-allow-origin": "*",
|
|
// "access-control-expose-headers": "*",
|
|
// "connection": "keep-alive",
|
|
// "content-length": "133",
|
|
// "content-type": "application/json;charset=utf-8",
|
|
// "date": "Tue, 27 Aug 2024 10:37:25 GMT",
|
|
// "etag": "1YHcV2uF0Hw6v4+PJKSpMwA1",
|
|
// "keep-alive": "timeout=25",
|
|
// "x-acs-request-id": "B7CCDAD3-AA89-55ED-94F5-4C08D746399B",
|
|
// "x-acs-trace-id": "cef86cfb21d73f60869bbb9034794714"
|
|
// },
|
|
// "statusCode": 200,
|
|
// "body": {
|
|
// "Code": "isv.SMS_SIGNATURE_ILLEGAL",
|
|
// "Message": "该账号下找不到对应签名",
|
|
// "RequestId": "B7CCDAD3-AA89-55ED-94F5-4C08D746399B"
|
|
// }
|
|
// }
|
|
|
|
// {
|
|
// "headers": {
|
|
// "access-control-allow-origin": "*",
|
|
// "access-control-expose-headers": "*",
|
|
// "connection": "keep-alive",
|
|
// "content-length": "110",
|
|
// "content-type": "application/json;charset=utf-8",
|
|
// "date": "Tue, 27 Aug 2024 10:39:59 GMT",
|
|
// "etag": "19rMwUYhdQd2sDRXr2iLPLg0",
|
|
// "keep-alive": "timeout=25",
|
|
// "x-acs-request-id": "D8F5E530-57BD-5A6C-AE57-18AD67BDDF70",
|
|
// "x-acs-trace-id": "95bdbb6a3f64ca8f7da8d398410c1bfa"
|
|
// },
|
|
// "statusCode": 200,
|
|
// "body": {
|
|
// "BizId": "181408424755199417^0",
|
|
// "Code": "OK",
|
|
// "Message": "OK",
|
|
// "RequestId": "D8F5E530-57BD-5A6C-AE57-18AD67BDDF70"
|
|
// }
|
|
// }
|
|
|
|
return nil
|
|
}()
|
|
|
|
if tryErr != nil {
|
|
|
|
return tryErr
|
|
|
|
// var error = &tea.SDKError{}
|
|
// if _t, ok := tryErr.(*tea.SDKError); ok {
|
|
// error = _t
|
|
// } else {
|
|
// error.Message = tea.String(tryErr.Error())
|
|
// }
|
|
// // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
|
// // 错误 message
|
|
// fmt.Println(tea.StringValue(error.Message))
|
|
// // 诊断地址
|
|
// var data interface{}
|
|
// d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data)))
|
|
// d.Decode(&data)
|
|
// if m, ok := data.(map[string]interface{}); ok {
|
|
// recommend, _ := m["Recommend"]
|
|
// fmt.Println(recommend)
|
|
// }
|
|
// _, _err := util.AssertAsString(error.Message)
|
|
// if _err != nil {
|
|
// return _err
|
|
// }
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetTemp 获取模板
|
|
func (l *Aliyun) GetTemp(ctx context.Context) ([]interfaces.TemplateInfo, error) {
|
|
page := 0
|
|
resp := []interfaces.TemplateInfo{}
|
|
|
|
l.logger.Infof(ctx, "aliyun getTemp: start")
|
|
|
|
for {
|
|
page++
|
|
r, err := l.getTemp(ctx, int32(page))
|
|
if err != nil {
|
|
l.logger.Errorf(ctx, "aliyun getTemp: err:%+v", err)
|
|
return nil, err
|
|
}
|
|
if len(r) == 0 {
|
|
break
|
|
}
|
|
resp = append(resp, r...)
|
|
}
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
func (l *Aliyun) getTemp(ctx context.Context, page int32) ([]interfaces.TemplateInfo, error) {
|
|
if l.client == nil {
|
|
return nil, errors.New("not init")
|
|
}
|
|
|
|
querySmsTemplateListRequest := &dysmsapi20170525.QuerySmsTemplateListRequest{
|
|
PageIndex: tea.Int32(page),
|
|
PageSize: tea.Int32(20),
|
|
}
|
|
runtime := &util.RuntimeOptions{}
|
|
|
|
resps := []interfaces.TemplateInfo{}
|
|
|
|
tryErr := func() (_e error) {
|
|
defer func() {
|
|
if r := tea.Recover(recover()); r != nil {
|
|
l.logger.Errorf(ctx, "aliyun getTemp: err:%+v", r)
|
|
_e = r
|
|
}
|
|
}()
|
|
// 复制代码运行请自行打印 API 的返回值
|
|
resp, _err := l.client.QuerySmsTemplateListWithOptions(querySmsTemplateListRequest, runtime)
|
|
if _err != nil {
|
|
l.logger.Errorf(ctx, "aliyun getTemp: err:%+v", _err)
|
|
return _err
|
|
}
|
|
|
|
l.logger.Infof(ctx, "aliyun getTemp: resp:%+v err:%+v", resp, _err)
|
|
|
|
if resp == nil || resp.Body == nil {
|
|
return fmt.Errorf("resp is nil")
|
|
}
|
|
|
|
for _, val := range resp.Body.SmsTemplateList {
|
|
s := l.extractParams(tea.StringValue(val.TemplateContent))
|
|
|
|
params := []consts.SmsTemplateParam{}
|
|
for _, p := range s {
|
|
params = append(params, consts.SmsTemplateParam{
|
|
FieldName: p,
|
|
})
|
|
}
|
|
|
|
resps = append(resps, interfaces.TemplateInfo{
|
|
TempId: tea.StringValue(val.TemplateCode),
|
|
TempName: tea.StringValue(val.TemplateName),
|
|
TempType: l.tempTypePlatToLocal(tea.Int32Value(val.OuterTemplateType)),
|
|
TempRange: l.tempRangePlatToLocal(tea.Int32Value(val.OuterTemplateType)),
|
|
Content: tea.StringValue(val.TemplateContent),
|
|
Status: l.auditStatusPlatToLocal(tea.StringValue(val.AuditStatus)),
|
|
Params: params,
|
|
})
|
|
}
|
|
|
|
return nil
|
|
}()
|
|
|
|
if tryErr != nil {
|
|
var error = &tea.SDKError{}
|
|
if _t, ok := tryErr.(*tea.SDKError); ok {
|
|
error = _t
|
|
} else {
|
|
error.Message = tea.String(tryErr.Error())
|
|
}
|
|
return nil, error
|
|
|
|
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
|
// 错误 message
|
|
// fmt.Println(tea.StringValue(error.Message))
|
|
// 诊断地址
|
|
// var data interface{}
|
|
// d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data)))
|
|
// d.Decode(&data)
|
|
// if m, ok := data.(map[string]interface{}); ok {
|
|
// recommend, _ := m["Recommend"]
|
|
// fmt.Println(recommend)
|
|
// }
|
|
// _, _err := util.AssertAsString(error.Message)
|
|
// if _err != nil {
|
|
// return nil, _err
|
|
// }
|
|
}
|
|
|
|
return resps, nil
|
|
}
|
|
|
|
// TempDel 删除模板
|
|
func (l *Aliyun) TempDel(ctx context.Context, templateId string) error {
|
|
if l.client == nil {
|
|
return errors.New("not init")
|
|
}
|
|
|
|
deleteSmsTemplateRequest := &dysmsapi20170525.DeleteSmsTemplateRequest{
|
|
TemplateCode: tea.String(templateId),
|
|
}
|
|
runtime := &util.RuntimeOptions{}
|
|
tryErr := func() (_e error) {
|
|
defer func() {
|
|
if r := tea.Recover(recover()); r != nil {
|
|
_e = r
|
|
}
|
|
}()
|
|
// 复制代码运行请自行打印 API 的返回值
|
|
_, _err := l.client.DeleteSmsTemplateWithOptions(deleteSmsTemplateRequest, runtime)
|
|
if _err != nil {
|
|
return _err
|
|
}
|
|
|
|
return nil
|
|
}()
|
|
|
|
if tryErr != nil {
|
|
|
|
return tryErr
|
|
|
|
// var error = &tea.SDKError{}
|
|
// if _t, ok := tryErr.(*tea.SDKError); ok {
|
|
// error = _t
|
|
// } else {
|
|
// error.Message = tea.String(tryErr.Error())
|
|
// }
|
|
// // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
|
// // 错误 message
|
|
// fmt.Println(tea.StringValue(error.Message))
|
|
// // 诊断地址
|
|
// var data interface{}
|
|
// d := json.NewDecoder(strings.NewReader(tea.StringValue(error.Data)))
|
|
// d.Decode(&data)
|
|
// if m, ok := data.(map[string]interface{}); ok {
|
|
// recommend, _ := m["Recommend"]
|
|
// fmt.Println(recommend)
|
|
// }
|
|
// _, _err := util.AssertAsString(error.Message)
|
|
// if _err != nil {
|
|
// return _err
|
|
// }
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 审核状态转换 短信平台=>本地
|
|
func (l *Aliyun) auditStatusPlatToLocal(status string) consts.SmsTemplateStatus {
|
|
|
|
// AUDIT_STATE_INIT:审核中。
|
|
// AUDIT_STATE_PASS:通过审核。
|
|
// AUDIT_STATE_NOT_PASS:未通过审核,请在返回参数 Reason 中查看审核未通过原因。
|
|
// AUDIT_STATE_CANCEL:取消审核。
|
|
// AUDIT_SATE_CANCEL:取消审核。
|
|
|
|
switch status {
|
|
case "AUDIT_STATE_INIT":
|
|
return consts.SmsTemplateStatusAudit
|
|
case "AUDIT_STATE_PASS":
|
|
return consts.SmsTemplateStatusPass
|
|
case "AUDIT_STATE_NOT_PASS":
|
|
return consts.SmsTemplateStatusUnPass
|
|
case "AUDIT_STATE_CANCEL":
|
|
return consts.SmsTemplateStatusCancel
|
|
case "AUDIT_SATE_CANCEL":
|
|
return consts.SmsTemplateStatusCancel
|
|
default:
|
|
return consts.SmsTemplateStatusUnknown
|
|
}
|
|
}
|
|
|
|
func (l *Aliyun) tempTypePlatToLocal(platType int32) consts.SmsTemplateType {
|
|
// 0:验证码短信。
|
|
// 1:通知短信。
|
|
// 2:推广短信。
|
|
// 3:国际/港澳台短信。
|
|
// 7:数字短信。
|
|
|
|
switch platType {
|
|
case 0:
|
|
return consts.SmsTemplateTypeVerifyCode
|
|
case 1:
|
|
return consts.SmsTemplateTypeNotice
|
|
case 2:
|
|
return consts.SmsTemplateTypePromotion
|
|
case 3:
|
|
return consts.SmsTemplateTypeInternational
|
|
case 7:
|
|
return consts.SmsTemplateTypeDigital
|
|
default:
|
|
return consts.SmsTemplateTypeUnknown
|
|
}
|
|
}
|
|
|
|
func (l *Aliyun) tempRangePlatToLocal(platType int32) consts.SmsTemplateRange {
|
|
// 非国际/港澳台短信。
|
|
if platType == 3 {
|
|
return consts.SmsTemplateRangeInternational
|
|
}
|
|
return consts.SmsTemplateRangeChina
|
|
}
|
|
|
|
// 提取参数
|
|
func (l *Aliyun) extractParams(str string) []string {
|
|
// str := "订单尾号${dingdanhao}超过${shijian}分钟,系统已自动放行。交易完成,请登录查看账户变动。"
|
|
|
|
re := regexp.MustCompile(`\$\{(.+?)\}`)
|
|
matches := re.FindAllStringSubmatch(str, -1)
|
|
|
|
var keys []string
|
|
for _, match := range matches {
|
|
keys = append(keys, match[1])
|
|
}
|
|
|
|
return keys
|
|
}
|