更新时间:2024-01-10 GMT+08:00

Go

样例

发送短信发送分批短信

接收状态报告

环境要求

go1.11及以上版本。

引用库

github.com/satori/go.uuid

  • 发送短信为单模板群发短信示例,发送分批短信为多模板群发短信示例。
  • 本文档所述Demo在提供服务的过程中,可能会涉及个人数据的使用,建议您遵从国家的相关法律采取足够的措施,以确保用户的个人数据受到充分的保护。
  • 本文档所述Demo仅用于功能演示,不允许客户直接进行商业使用。
  • 本文档信息仅供参考,不构成任何要约或承诺。

发送短信

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
package main

import (
    "bytes"
    "crypto/sha256"
    "crypto/tls"
    "encoding/base64"
    "fmt"
    "github.com/satori/go.uuid"
    "io/ioutil"
    "net/http"
    "net/url"
    "strings"
    "time"
)

//无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
const WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\""
//无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
const AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\""

func main()  {
    //必填,请参考"开发准备"获取如下数据,替换为实际值
    apiAddress := "https://smsapi.ap-southeast-1.myhuaweicloud.com:443/sms/batchSendSms/v1" //APP接入地址(在控制台"应用管理"页面获取)+接口访问URI
    // 认证用的appKey和appSecret硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
    appKey := "c8RWg3ggEcyd4D3p94bf3Y7x1Ile" //APP_Key
    appSecret := "q4Ii87Bh************80SfD7Al" //APP_Secret
    sender := "csms12345678" //国内短信签名通道号或国际/港澳台短信通道号
    templateId := "8ff55eac1d0b478ab3c06c3c6a492300" //模板ID

    //条件必填,国内短信关注,当templateId指定的模板类型为通用模板时生效且必填,必须是已审核通过的,与模板类型一致的签名名称
    //国际/港澳台短信不用关注该参数
    signature := "华为云短信测试" //签名名称

    //必填,全局号码格式(包含国家码),示例:+8615123456789,多个号码之间用英文逗号分隔
    receiver := "+8615123456789,+8615234567890" //短信接收人号码

    //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
    statusCallBack := ""

    /*
     * 选填,使用无变量模板时请赋空值 string templateParas = "";
     * 单变量模板示例:模板内容为"您的验证码是${1}"时,templateParas可填写为"[\"369751\"]"
     * 双变量模板示例:模板内容为"您有${1}件快递请到${2}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]"
     * 模板中的每个变量都必须赋值,且取值不能为空
     * 查看更多模板和变量规范
     */
    templateParas := "[\"369751\"]" //模板变量,此处以单变量验证码短信为例,请客户自行生成6位验证码,并定义为字符串类型,以杜绝首位0丢失的问题(例如:002569变成了2569)。

    body := buildRequestBody(sender,receiver,templateId,templateParas,statusCallBack,signature)
    headers := make(map[string]string)
    headers["Content-Type"] = "application/x-www-form-urlencoded"
    headers["Authorization"] = AUTH_HEADER_VALUE;
    headers["X-WSSE"] = buildWsseHeader(appKey, appSecret);
    resp, err := post(apiAddress, []byte(body),headers)
    if err != nil {
        return
    }
    fmt.Println(resp);
}

/**
 * sender,receiver,templateId不能为空
 */
func buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, signature string) string {
    param := "from=" + url.QueryEscape(sender) + "&to=" + url.QueryEscape(receiver) + "&templateId=" + url.QueryEscape(templateId)
    if templateParas != "" {
        param += "&templateParas=" + url.QueryEscape(templateParas)
    }
    if statusCallBack != "" {
        param += "&statusCallback=" + url.QueryEscape(statusCallBack)
    }
    if signature != "" {
        param += "&signature=" + url.QueryEscape(signature)
    }
    return param
}

func post(url string, param []byte, headers map[string]string)(string,error) {
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}

    req, err := http.NewRequest("POST",url, bytes.NewBuffer(param));
    if err != nil {
        return "", err
    }
    for key, header := range headers {
        req.Header.Set(key, header)
    }

    resp, err := client.Do(req)
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    return string(body), nil;
}

func buildWsseHeader(appKey,appSecret string)string {
    var cTime = time.Now().Format("2006-01-02T15:04:05Z")
    var nonce = uuid.NewV4().String()
    nonce = strings.ReplaceAll(nonce,"-","")

    h := sha256.New()
    h.Write([]byte(nonce + cTime + appSecret))
    passwordDigestBase64Str := base64.StdEncoding.EncodeToString(h.Sum(nil))

    return fmt.Sprintf(WSSE_HEADER_FORMAT,appKey,passwordDigestBase64Str,nonce, cTime);
}

发送分批短信

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package main

import (
    "bytes"
    "crypto/sha256"
    "crypto/tls"
    "encoding/base64"
    "encoding/json"
    "fmt"
    "github.com/satori/go.uuid"
    "io/ioutil"
    "net/http"
    "strings"
    "time"
)

//无需修改,用于格式化鉴权头域,给"X-WSSE"参数赋值
const WSSE_HEADER_FORMAT = "UsernameToken Username=\"%s\",PasswordDigest=\"%s\",Nonce=\"%s\",Created=\"%s\""
//无需修改,用于格式化鉴权头域,给"Authorization"参数赋值
const AUTH_HEADER_VALUE = "WSSE realm=\"SDP\",profile=\"UsernameToken\",type=\"Appkey\""

func main()  {
	//必填,请参考"开发准备"获取如下数据,替换为实际值
    url := "https://smsapi.ap-southeast-1.myhuaweicloud.com:443/sms/batchSendDiffSms/v1" //APP接入地址(在控制台"应用管理"页面获取)+接口访问URI
    // 认证用的appKey和appSecret硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全;
    appKey := "c8RWg3ggEcyd4D3p94bf3Y7x1Ile" //APP_Key
    appSecret := "q4Ii87Bh************80SfD7Al" //APP_Secret
    sender := "csms12345678" //国内短信签名通道号或国际/港澳台短信通道号
    templateId1 := "8ff55eac1d0b478ab3c06c3c6a492300" //模板ID1
    templateId2 := "8ff55eac1d0b478ab3c06c3c6a492300" //模板ID2

    //条件必填,国内短信关注,当templateId指定的模板类型为通用模板时生效且必填,必须是已审核通过的,与模板类型一致的签名名称
    //国际/港澳台短信不用关注该参数
    signature1 := "华为云短信测试" //签名名称1
    signature2 := "华为云短信测试" //签名名称2

    //必填,全局号码格式(包含国家码),示例:+8615123456789,多个号码之间用英文逗号分隔
    receiver1 := []string{"+8615123456789", "+8615234567890"}; //模板1的接收号码
    receiver2 := []string{"+8615123456789", "+8615234567890"}; //模板2的接收号码

    //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告
    statusCallBack := "";

    /**
     * 选填,使用无变量模板时请赋空值 String[] templateParas = {};
     * 单变量模板示例:模板内容为"您的验证码是${1}"时,templateParas可填写为{"369751"}
     * 双变量模板示例:模板内容为"您有${1}件快递请到${2}领取"时,templateParas可填写为{"3","人民公园正门"}
     * ${DATE}${TIME}变量不允许取值为空,${TXT_20}变量可以使用英文空格或点号替代空值,${NUM_6}变量可以使用0替代空值
     * 查看更多模板和变量规范
     */
    templateParas1 := []string{"123456"}; //模板1变量,此处以单变量验证码短信为例,请客户自行生成6位验证码,并定义为字符串类型,以杜绝首位0丢失的问题(例如:002569变成了2569)。
    templateParas2 := []string{"234567"}; //模板2变量,此处以单变量验证码短信为例,请客户自行生成6位验证码,并定义为字符串类型,以杜绝首位0丢失的问题(例如:002569变成了2569)。

    item1 := initDiffSms(receiver1, templateId1, templateParas1, signature1);
    item2 := initDiffSms(receiver2, templateId2, templateParas2, signature2);

    item := []map[string]interface{}{item1,item2}
    body := buildRequestBody(sender, item, statusCallBack)

    headers := make(map[string]string)
    headers["Content-Type"] = "application/json;charset=utf-8"
    headers["Authorization"] = AUTH_HEADER_VALUE;
    headers["X-WSSE"] = buildWsseHeader(appKey, appSecret);
    resp, err := post(url,body,headers)
    if err != nil {
        return
    }
    fmt.Println(resp);
}

func buildRequestBody(sender string, item []map[string]interface{}, statusCallBack string) []byte{
    body := make(map[string]interface{})
    body["smsContent"] = item
    body["from"] = sender
    if statusCallBack != "" {
        body["statusCallback"] = statusCallBack
    }
    res, _ := json.Marshal(body)
    return res;
}

func initDiffSms(reveiver []string, templateId string, templateParas []string, signature string) map[string]interface{} {
    diffSms := make(map[string]interface{});
    diffSms["to"] = reveiver
    diffSms["templateId"] = templateId
    if templateParas != nil && len(templateParas) > 0 {
        diffSms["templateParas"] = templateParas
    }
    if signature != "" {
        diffSms["signature"] = signature
    }
    return diffSms;
}

func post(url string, param []byte, headers map[string]string)(string,error) {
    tr := &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
    }
    client := &http.Client{Transport: tr}

    req, err := http.NewRequest("POST",url, bytes.NewBuffer(param));
    if err != nil {
        return "", err
    }
    for key, header := range headers {
        req.Header.Set(key, header)
    }

    resp, err := client.Do(req)
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }
    return string(body), nil;
}

func buildWsseHeader(appKey,appSecret string)string {

    var cTime = time.Now().Format("2006-01-02T15:04:05Z")
    var nonce = uuid.NewV4().String()
    nonce = strings.ReplaceAll(nonce,"-","")

    h := sha256.New()
    h.Write([]byte(nonce + cTime + appSecret))
    passwordDigestBase64Str := base64.StdEncoding.EncodeToString(h.Sum(nil))

    return fmt.Sprintf(WSSE_HEADER_FORMAT,appKey,passwordDigestBase64Str,nonce, cTime);
}

接收状态报告

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main

import (
    "fmt"
    "net/url"
    "strings"
)

func main() {
    // 短信平台上报状态报告数据样例(urlencode)
    //success_body := "sequence=1&total=1&updateTime=2018-10-31T08%3A43%3A41Z&source=2&smsMsgId=2ea20735-f856-4376-afbf-570bd70a46ee_11840135&status=DELIVRD";
    failed_body := "orgCode=E200027&sequence=1&total=1&updateTime=2018-10-31T08%3A43%3A41Z&source=2&smsMsgId=2ea20735-f856-4376-afbf-570bd70a46ee_11840135&status=RTE_ERR";
    //onSmsStatusReport(success_body);
    onSmsStatusReport(failed_body);
}

func onSmsStatusReport(data string) {
    ss, _ := url.QueryUnescape(data)
    params := strings.Split(ss, "&")
    keyValues := make(map[string]string)
    for i := range params {
        temp := strings.Split(params[i],"=")
        keyValues[temp[0]] = temp[1];
    }
    /**
    * Example: 此处已解析status为例,请按需解析所需参数并自行实现相关处理
    * 
    * 'smsMsgId': 短信唯一标识
    * 'total': 长短信拆分条数
    * 'sequence': 拆分后短信序号
    * 'source': 状态报告来源
    * 'updateTime': 资源更新时间
    * 'status': 状态报告枚举值
    * 'orgCode': 状态码
    */
    status := keyValues["status"];
    if status == "DELIVRD" {
        fmt.Println("Send sms success. smsMsgId: " + keyValues["smsMsgId"])
    } else {
        fmt.Println("Send sms failed. smsMsgId: " + keyValues["smsMsgId"])
        fmt.Println("Failed status:  " + keyValues["status"])
        fmt.Println("Failed orgCode:  " + keyValues["orgCode"])
    }
}