Files
smsx/aliyun/aliyun.go
T
2024-11-15 19:01:25 +08:00

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
}