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 }