├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc ├── dysms_python_20170525.zip └── tips.md ├── dysms ├── common.go ├── common_test.go ├── errors.go ├── query_send_details_request.go ├── send_sms_request.go ├── signature.go └── signature_test.go ├── example ├── sample-dysms.go └── sample.go ├── sample-dysms.go ├── sms ├── signature.go ├── sms.go └── sms_test.go └── wercker.yml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.2 5 | - 1.3 6 | - 1.4 7 | - 1.5 8 | - 1.6 9 | - 1.7 10 | - tip 11 | 12 | install: 13 | - go get github.com/GiterLab/urllib 14 | - go get github.com/tobyzxj/uuid 15 | - go get github.com/GiterLab/aliyun-sms-go-sdk/sms 16 | 17 | script: 18 | - cd sms 19 | - go test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aliyun-sms-go-sdk 2 | Aliyun SMS SDK for golang 3 | 4 | [![wercker status](https://app.wercker.com/status/5ef19ea6b2a854db200521592d0d7b2e/m/master "wercker status")](https://app.wercker.com/project/byKey/5ef19ea6b2a854db200521592d0d7b2e) 5 | 6 | [![Build Status](https://travis-ci.org/GiterLab/aliyun-sms-go-sdk.svg?branch=master)](https://travis-ci.org/GiterLab/aliyun-sms-go-sdk) 7 | [![GoDoc](https://godoc.org/github.com/GiterLab/aliyun-sms-go-sdk/sms?status.svg)](https://godoc.org/github.com/GiterLab/aliyun-sms-go-sdk/sms) 8 | 9 | ## About 10 | 短信服务(Short Message Service)是阿里云为用户提供的一种通信服务的能力,支持快速发送短信验证码、短信通知等。 完美支撑双11期间2亿用户,发送6亿短信,8万并发量。三网合一专属通道,与工信部携号转网平台实时互联。电信级运维保障,实时监控自动切换,到达率高达99%。 11 | 12 | 13 | 注意: 14 | 2017年12月20日至2018年1月21日 消息服务中的短信功能和云市场(阿里短信服务)将迁移至云通信短信服务 15 | 为了尽快使用更专业的服务,还请您确认迁移后尽快下载正确的SKD和API代码 16 | ## Install 17 | 18 | $ go get -u -v github.com/GiterLab/aliyun-sms-go-sdk 19 | 20 | `github.com/GiterLab/aliyun-sms-go-sdk/sms` 将停止维护 21 | `github.com/GiterLab/aliyun-sms-go-sdk/dysms` 为迁移至云通信后的新SDK 22 | 23 | ## Usage 24 | 25 | [使用帮助](https://github.com/GiterLab/aliyun-sms-go-sdk/blob/master/doc/tips.md) 26 | 27 | **已过时示例** 28 | 29 | package main 30 | 31 | import ( 32 | "fmt" 33 | "os" 34 | 35 | "github.com/GiterLab/aliyun-sms-go-sdk/sms" 36 | ) 37 | 38 | // modify it to yours 39 | const ( 40 | ACCESSID = "your_accessid" 41 | ACCESSKEY = "your_accesskey" 42 | ) 43 | 44 | func main() { 45 | // 2017年12月20日至2018年1月21日 消息服务中的短信功能和云市场(阿里短信服务)将迁移至云通信短信服务 46 | // 为了尽快使用更专业的服务,还请您确认迁移后尽快下载正确的SKD和API代码 47 | // 此测试接口过时,请勿再使用 48 | sms.HttpDebugEnable = true 49 | c := sms.New(ACCESSID, ACCESSKEY) 50 | // send to one person 51 | e, err := c.SendOne("1375821****", "多协云", "SMS_22175101", `{"company":"duoxieyun"}`) 52 | if err != nil { 53 | fmt.Println("send sms failed", err, e.Error()) 54 | os.Exit(0) 55 | } 56 | // send to more than one person 57 | e, err = c.SendMulti([]string{"1375821****", "1835718****"}, "多协云", "SMS_22175101", `{"company":"duoxieyun"}`) 58 | if err != nil { 59 | fmt.Println("send sms failed", err, e.Error()) 60 | os.Exit(0) 61 | } 62 | fmt.Println("send sms succeed", e.GetRequestId()) 63 | } 64 | 65 | **迁移后的例子:** 66 | 67 | package main 68 | 69 | import ( 70 | "fmt" 71 | "os" 72 | 73 | "github.com/GiterLab/aliyun-sms-go-sdk/dysms" 74 | "github.com/tobyzxj/uuid" 75 | ) 76 | 77 | // modify it to yours 78 | const ( 79 | ACCESSID = "your_accessid" 80 | ACCESSKEY = "your_accesskey" 81 | ) 82 | 83 | func main() { 84 | dysms.HTTPDebugEnable = true 85 | dysms.SetACLClient(ACCESSID, ACCESSKEY) // dysms.New(ACCESSID, ACCESSKEY) 86 | 87 | // send to one person 88 | respSendSms, err := dysms.SendSms(uuid.New(), "1375821****", "多协云", "SMS_22175101", `{"company":"duoxieyun"}`).DoActionWithException() 89 | if err != nil { 90 | fmt.Println("send sms failed", err, respSendSms.Error()) 91 | os.Exit(0) 92 | } 93 | fmt.Println("send sms succeed", respSendSms.GetRequestID()) 94 | } 95 | 96 | 97 | 98 | 99 | ## Links 100 | - [Short Message Service,SMS(短信服务)](https://www.aliyun.com/product/sms) 101 | - [HTTP协议及签名](https://help.aliyun.com/document_detail/56189.html?spm=5176.doc56189.6.576.JIUq2i) 102 | - [API签名机制](https://help.aliyun.com/document_detail/56189.html?spm=5176.product44282.6.576.VQczaW) 103 | 104 | ## License 105 | 106 | This project is under the Apache Licence, Version 2.0. See the [LICENSE](https://github.com/GiterLab/aliyun-sms-go-sdk/blob/master/LICENSE) file for the full license text. -------------------------------------------------------------------------------- /doc/dysms_python_20170525.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GiterLab/aliyun-sms-go-sdk/fcc9f11de968b3470ec55298e8a7f8f21588cd21/doc/dysms_python_20170525.zip -------------------------------------------------------------------------------- /doc/tips.md: -------------------------------------------------------------------------------- 1 | 开发阿里云短信服务注意事项 2 | ======================= 3 | 4 | ### 名词解释 ### 5 | 6 | 以下几个变量名称在调用 `SendOne` 和 `SendMulti` 函数时使用) 7 | 8 | `recnum`: 用户接收的手机号 9 | 10 | `signname`: 签名名称 11 | 12 | `templatecode`: 模板CODE 13 | 14 | `paramstring`: 参数字符串 15 | 16 | ### 准备工作 ### 17 | 18 | [直达帮助页面](https://help.aliyun.com/document_detail/44346.html?spm=5176.doc44348.6.103.z0JAmF) 19 | 20 | 1. 新建短信签名 21 | 22 | 用户需要先`新建短信签名`, 阿里云审核通过后会得到一个`签名名称`, 此`签名名称`即为`signname` 23 | 24 | 2. 新建模板 25 | 26 | - 用户需要先`新建模板`, 阿里云审核通过后会得到一个`模板CODE`, 此`模板CODE`即为`templatecode` 27 | 28 | - 用户在创建模板的时候,会在模板中添加变量(一个或多个),如下为一个例子: 29 | 30 | 尊敬的用户,您的${device_id}(${devicename})设备已离线 31 | 32 | 上面的`device_id`和`devicename`既是模板变量,用户在使用此SDK时,需要把这两个变量转换为参数字符串`paramstring`,此`paramstring`是一个json对象,如下所示: 33 | 34 | {"device_id":"T0000001","devicename":"测试设备"} 35 | 36 | 以上字符串即为 `paramstring`. 37 | 38 | 下面提供一个golang方式转换方法: 39 | 40 | A. 为每一个模板CODE建立一个模板参数结构体,实现一个String()方法 41 | 42 | type Alarm_Offline_SMS_22120102 struct { 43 | DeviceId string `json:"device_id"` 44 | DeviceName string `json:"devicename"` 45 | } 46 | 47 | func (this Alarm_Offline_SMS_22120102) String() string { 48 | body, err := json.Marshal(this) 49 | if err != nil { 50 | return "" 51 | } 52 | return string(body) 53 | } 54 | 55 | B. 在设置paramstring参数时,直接使用String()方法产生: 56 | 57 | o := new(Alarm_Offline_SMS_22120102) 58 | o.DeviceId = "T0000001" 59 | o.DeviceName = "测试设备" 60 | paramstring := o.String() 61 | 62 | // 这里paramstring将会是符合要求的结果:{"device_id":"T0000001","devicename":"测试设备"} 63 | fmt.Println(paramstring) 64 | 65 | C. 例子sample.go,可以改写成如下形式: 66 | 67 | package main 68 | 69 | import ( 70 | "encoding/json" 71 | "fmt" 72 | "os" 73 | 74 | "github.com/GiterLab/aliyun-sms-go-sdk/sms" 75 | ) 76 | 77 | // modify it to yours 78 | const ( 79 | ENDPOINT = "https://sms.aliyuncs.com/" 80 | ACCESSID = "your_accessid" 81 | ACCESSKEY = "your_accesskey" 82 | ) 83 | 84 | type Register_SMS_22175101 struct { 85 | CompanyName string `json:"company"` 86 | } 87 | 88 | func (this *Register_SMS_22175101) String() string { 89 | body, err := json.Marshal(this) 90 | if err != nil { 91 | return "" 92 | } 93 | return string(body) 94 | } 95 | 96 | func main() { 97 | sms.HttpDebugEnable = true 98 | c := sms.New(ACCESSID, ACCESSKEY) 99 | 100 | // create a paramstring object 101 | o := new(Register_SMS_22175101) 102 | o.CompanyName = "duoxieyun" 103 | 104 | // send to one person 105 | e, err := c.SendOne("1375821****", "多协云", "SMS_22175101", o.String()) 106 | if err != nil { 107 | fmt.Println("send sms failed", err, e.Error()) 108 | os.Exit(0) 109 | } 110 | // send to more than one person 111 | e, err = c.SendMulti([]string{"1375821****", "1835718****"}, "多协云", "SMS_22175101", o.String()) 112 | if err != nil { 113 | fmt.Println("send sms failed", err, e.Error()) 114 | os.Exit(0) 115 | } 116 | fmt.Println("send sms succeed", e.GetRequestId()) 117 | } 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /dysms/common.go: -------------------------------------------------------------------------------- 1 | // Package dysms Copyright 2016 The GiterLab Authors. All rights reserved. 2 | package dysms 3 | 4 | import ( 5 | "compress/gzip" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "net" 10 | "net/http" 11 | "net/url" 12 | "sort" 13 | "strings" 14 | "time" 15 | 16 | "github.com/GiterLab/urllib" 17 | "github.com/tobyzxj/uuid" 18 | ) 19 | 20 | // HTTPDebugEnable http调试开关 21 | var HTTPDebugEnable = false 22 | 23 | // acsClient 服务权限配置信息 24 | var acsClient Client 25 | 26 | func init() { 27 | acsClient.SetVersion("2017-05-25") 28 | acsClient.SetRegion("cn-hangzhou") 29 | acsClient.SetEndPoint("http://dysmsapi.aliyuncs.com/") 30 | } 31 | 32 | // Request 请求参数设置 33 | type Request struct { 34 | Param map[string]string 35 | } 36 | 37 | // Put 添加请求参数 38 | func (r *Request) Put(key, value string) error { 39 | if r != nil { 40 | if r.Param == nil { 41 | r.Param = make(map[string]string) 42 | } 43 | r.Param[key] = value 44 | } 45 | return errors.New("requset is nil") 46 | } 47 | 48 | // Get 获取请求参数 49 | func (r *Request) Get(key string) string { 50 | if r != nil && r.Param != nil { 51 | return r.Param[key] 52 | } 53 | return "" 54 | } 55 | 56 | // CalcStringToSign 计算签名字符串 57 | func (r *Request) CalcStringToSign(httpMethod string) string { 58 | if r != nil && r.Param != nil { 59 | strslice := make([]string, len(r.Param)) 60 | i := 0 61 | for k, v := range r.Param { 62 | data := url.Values{} 63 | data.Add(k, v) 64 | strslice[i] = data.Encode() 65 | strslice[i] = percentEncodeBefore(strslice[i]) 66 | i++ 67 | } 68 | sort.Strings(strslice) 69 | return httpMethod + "&" + percentEncode("/") + "&" + percentEncode(strings.Join(strslice, "&")) 70 | } 71 | return "" 72 | } 73 | 74 | // Do 发送HTTP请求 75 | func (r *Request) Do(action string) (body []byte, httpCode int, err error) { 76 | if r == nil || r.Param == nil { 77 | return nil, 0, errors.New("requset is nil") 78 | } 79 | 80 | if action != "" { 81 | r.Put("Action", action) 82 | } 83 | signature := signatureMethod(acsClient.AccessKey, r.CalcStringToSign("GET")) 84 | 85 | // HTTP requset 86 | httpReq := urllib.Get(acsClient.EndPoint) 87 | if HTTPDebugEnable { 88 | httpReq.Debug(true) 89 | } 90 | for k, v := range r.Param { 91 | httpReq.Param(k, v) 92 | } 93 | httpReq.Param("Signature", signature) 94 | resp, err := httpReq.Response() 95 | if err != nil { 96 | return nil, 0, err 97 | } 98 | if resp.Body == nil { 99 | return nil, resp.StatusCode, nil 100 | } 101 | defer resp.Body.Close() 102 | if resp.Header.Get("Content-Encoding") == "gzip" { 103 | reader, errGzip := gzip.NewReader(resp.Body) 104 | if errGzip != nil { 105 | return nil, resp.StatusCode, errGzip 106 | } 107 | body, err = ioutil.ReadAll(reader) 108 | } else { 109 | body, err = ioutil.ReadAll(resp.Body) 110 | } 111 | if err != nil { 112 | return nil, resp.StatusCode, err 113 | } 114 | if HTTPDebugEnable { 115 | fmt.Println("C-->S:", httpReq.DumpRequestString()) 116 | fmt.Println("S-->C:", string(body)) 117 | } 118 | return body, resp.StatusCode, nil 119 | } 120 | 121 | // 创建一个新的请求参数 122 | func newRequset() *Request { 123 | req := &Request{Param: make(map[string]string)} 124 | 125 | // 1. 系统参数 126 | req.Put("SignatureMethod", "HMAC-SHA1") 127 | req.Put("SignatureNonce", uuid.New()) 128 | req.Put("AccessKeyId", acsClient.AccessID) 129 | req.Put("SignatureVersion", "1.0") 130 | req.Put("Timestamp", time.Now().UTC().Format(time.RFC3339)) 131 | req.Put("Format", "JSON") 132 | 133 | // 2. 业务API参数 134 | // req.Put("Action", "SendSms") 135 | req.Put("Version", acsClient.Version) 136 | req.Put("RegionId", acsClient.Region) 137 | // req.Put("PhoneNumbers", "your_phonenumbers") 138 | // req.Put("SignName", "your_signname") 139 | // req.Put("TemplateParam", "your_ParamString") 140 | // req.Put("TemplateCode", "your_templatecode") 141 | // req.Put("OutId", "your_outid") 142 | 143 | return req 144 | } 145 | 146 | // Client HTTP请求配置信息 147 | type Client struct { 148 | // API版本 149 | Version string 150 | // SMS服务地域, 默认为cn-hangzhou 151 | Region string 152 | // SMS服务的地址,默认为(http://dysmsapi.aliyuncs.com/) 153 | EndPoint string 154 | // 访问SMS服务的accessid,通过官方网站申请或通过管理员获取 155 | AccessID string 156 | // 访问SMS服务的accesskey,通过官方网站申请或通过管理员获取 157 | AccessKey string 158 | // 连接池中每个连接的Socket超时,单位为秒,可以为int或float。默认值为30 159 | SocketTimeout int 160 | } 161 | 162 | // SetVersion API版本 163 | func (c *Client) SetVersion(version string) { 164 | if c != nil { 165 | c.Version = version 166 | } 167 | } 168 | 169 | // SetRegion 设置SMS服务地域 170 | func (c *Client) SetRegion(region string) { 171 | if c != nil { 172 | c.Region = region 173 | } 174 | } 175 | 176 | // SetEndPoint 设置短信服务器 177 | func (c *Client) SetEndPoint(endPoint string) { 178 | if c != nil { 179 | c.EndPoint = endPoint 180 | } 181 | } 182 | 183 | // SetAccessID 设置短信服务的accessid,通过官方网站申请或通过管理员获取 184 | func (c *Client) SetAccessID(accessid string) { 185 | if c != nil { 186 | c.AccessID = accessid 187 | } 188 | } 189 | 190 | // SetAccessKey 设置短信服务的accesskey,通过官方网站申请或通过管理员获取 191 | func (c *Client) SetAccessKey(accesskey string) { 192 | if c != nil { 193 | c.AccessKey = accesskey 194 | } 195 | } 196 | 197 | // SetSocketTimeout 设置短信服务的Socket超时,单位为秒,可以为int或float。默认值为30 198 | func (c *Client) SetSocketTimeout(sockettimeout int) { 199 | if sockettimeout == 0 { 200 | sockettimeout = 30 201 | } 202 | if c != nil { 203 | c.SocketTimeout = sockettimeout 204 | } 205 | } 206 | 207 | // SetACLClient 配置默认的服务权限信息 208 | func SetACLClient(accessid, accesskey string) *Client { 209 | acsClient.SetAccessID(accessid) 210 | acsClient.SetAccessKey(accesskey) 211 | 212 | if urllib.GetDefaultSetting().Transport == nil { 213 | // set default setting for urllib 214 | trans := &http.Transport{ 215 | MaxIdleConnsPerHost: 500, 216 | Dial: (&net.Dialer{ 217 | Timeout: time.Duration(15) * time.Second, 218 | }).Dial, 219 | } 220 | 221 | urlSetting := urllib.HttpSettings{ 222 | ShowDebug: false, // ShowDebug 223 | UserAgent: "GiterLab", // UserAgent 224 | ConnectTimeout: 15 * time.Second, // ConnectTimeout 225 | ReadWriteTimeout: 30 * time.Second, // ReadWriteTimeout 226 | TlsClientConfig: nil, // TlsClientConfig 227 | Proxy: nil, // Proxy 228 | Transport: trans, // Transport 229 | EnableCookie: false, // EnableCookie 230 | Gzip: true, // Gzip 231 | DumpBody: true, // DumpBody 232 | } 233 | if acsClient.SocketTimeout != 0 { 234 | urlSetting.ConnectTimeout = time.Duration(acsClient.SocketTimeout) * time.Second 235 | urlSetting.ReadWriteTimeout = time.Duration(acsClient.SocketTimeout) * time.Second 236 | } 237 | if HTTPDebugEnable { 238 | urlSetting.ShowDebug = true 239 | } else { 240 | urlSetting.ShowDebug = false 241 | } 242 | urllib.SetDefaultSetting(urlSetting) 243 | } 244 | 245 | return &acsClient 246 | } 247 | 248 | // New 兼容 sms SDK 249 | func New(accessid, accesskey string) *Client { 250 | return SetACLClient(accessid, accesskey) 251 | } 252 | -------------------------------------------------------------------------------- /dysms/common_test.go: -------------------------------------------------------------------------------- 1 | package dysms 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_stringToSign(t *testing.T) { 8 | c := new(Client) 9 | c.EndPoint = "http://dysmsapi.aliyuncs.com/" 10 | c.AccessID = "testId" 11 | c.AccessKey = "testSecret" 12 | 13 | req := newRequset() 14 | // 1. 系统参数 15 | req.Put("SignatureMethod", "HMAC-SHA1") 16 | req.Put("SignatureNonce", "45e25e9b-0a6f-4070-8c85-2956eda1b466") 17 | req.Put("AccessKeyId", c.AccessID) 18 | req.Put("SignatureVersion", "1.0") 19 | req.Put("Timestamp", "2017-07-12T02:42:19Z") 20 | req.Put("Format", "XML") 21 | // 2. 业务API参数 22 | req.Put("Action", "SendSms") 23 | req.Put("Version", "2017-05-25") 24 | req.Put("RegionId", "cn-hangzhou") 25 | req.Put("PhoneNumbers", "15300000001") 26 | req.Put("SignName", "阿里云短信测试专用") 27 | req.Put("TemplateParam", "{\"customer\":\"test\"}") 28 | req.Put("TemplateCode", "SMS_71390007") 29 | req.Put("OutId", "123") 30 | stringToSign := req.CalcStringToSign("GET") 31 | stringToSignResult := `GET&%2F&AccessKeyId%3DtestId%26Action%3DSendSms%26Format%3DXML%26OutId%3D123%26PhoneNumbers%3D15300000001%26RegionId%3Dcn-hangzhou%26SignName%3D%25E9%2598%25BF%25E9%2587%258C%25E4%25BA%2591%25E7%259F%25AD%25E4%25BF%25A1%25E6%25B5%258B%25E8%25AF%2595%25E4%25B8%2593%25E7%2594%25A8%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D45e25e9b-0a6f-4070-8c85-2956eda1b466%26SignatureVersion%3D1.0%26TemplateCode%3DSMS_71390007%26TemplateParam%3D%257B%2522customer%2522%253A%2522test%2522%257D%26Timestamp%3D2017-07-12T02%253A42%253A19Z%26Version%3D2017-05-25` 32 | if stringToSign != stringToSignResult { 33 | t.Error("calcStringToSign failed") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /dysms/errors.go: -------------------------------------------------------------------------------- 1 | // Package dysms Copyright 2016 The GiterLab Authors. All rights reserved. 2 | package dysms 3 | 4 | import ( 5 | "encoding/json" 6 | ) 7 | 8 | // ErrorMessage 短信服务器返回的错误信息 9 | type ErrorMessage struct { 10 | HTTPCode int `json:"-"` 11 | RequestID *string `json:"RequestId,omitempty"` 12 | Code *string `json:"Code,omitempty"` 13 | Message *string `json:"Message,omitempty"` 14 | } 15 | 16 | // SetHTTPCode 设置HTTP错误码 17 | func (e *ErrorMessage) SetHTTPCode(code int) { 18 | e.HTTPCode = code 19 | } 20 | 21 | // GetHTTPCode 获取HTTP请求的错误码 22 | func (e *ErrorMessage) GetHTTPCode() int { 23 | return e.HTTPCode 24 | } 25 | 26 | // GetRequestID 获取请求的ID序列 27 | func (e *ErrorMessage) GetRequestID() string { 28 | if e != nil && e.RequestID != nil { 29 | return *e.RequestID 30 | } 31 | return "" 32 | } 33 | 34 | // GetCode 获取请求的错误码 35 | func (e *ErrorMessage) GetCode() string { 36 | if e != nil && e.Code != nil { 37 | return *e.Code 38 | } 39 | return "" 40 | } 41 | 42 | // GetMessage 获取错误信息 43 | func (e *ErrorMessage) GetMessage() string { 44 | if e != nil && e.Message != nil { 45 | return *e.Message 46 | } 47 | return "" 48 | } 49 | 50 | // Error 序列化成字符串 51 | func (e *ErrorMessage) Error() string { 52 | body, err := json.Marshal(e) 53 | if err != nil { 54 | return "" 55 | } 56 | return string(body) 57 | } 58 | -------------------------------------------------------------------------------- /dysms/query_send_details_request.go: -------------------------------------------------------------------------------- 1 | // Package dysms Copyright 2016 The GiterLab Authors. All rights reserved. 2 | package dysms 3 | 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | // SmsSendDetailDTO 短信发送记录信息 10 | type SmsSendDetailDTO struct { 11 | PhoneNum string `json:"PhoneNum"` // 手机号码 12 | SendStatus int `json:"SendStatus"` // 发送状态 1:等待回执,2:发送失败,3:发送成功 13 | ErrCode string `json:"ErrCode"` // 运营商短信错误码 14 | TemplateCode string `json:"TemplateCode"` // 模板ID 15 | Content string `json:"Content"` // 短信内容 16 | SendDate string `json:"SendDate"` // 发送时间 17 | ReceiveDate string `json:"ReceiveDate"` // 接收时间 18 | OutID string `json:"OutId"` // 外部流水扩展字段 19 | } 20 | 21 | // SmsSendDetailDTOs 短信发送记录查询列表 22 | type SmsSendDetailDTOs struct { 23 | SmsSendDetailDTO []SmsSendDetailDTO `json:"SmsSendDetailDTO"` 24 | } 25 | 26 | // QuerySendDetailsResponse 短信发送记录查询接口服务器响应 27 | type QuerySendDetailsResponse struct { 28 | ErrorMessage 29 | TotalCount *int `json:"TotalCount,omitempty"` // 发送总条数 30 | TotalPage *int `json:"TotalPage,omitempty"` // 总页数 31 | SmsSendDetailDTOs *SmsSendDetailDTOs `json:"SmsSendDetailDTOs,omitempty"` // 发送明细结构体 32 | } 33 | 34 | // GetTotalCount 发送总条数 35 | func (q *QuerySendDetailsResponse) GetTotalCount() int { 36 | if q != nil && q.TotalCount != nil { 37 | return *q.TotalCount 38 | } 39 | return 0 40 | } 41 | 42 | // GetTotalPage 总页数 43 | func (q *QuerySendDetailsResponse) GetTotalPage() int { 44 | if q != nil && q.TotalPage != nil { 45 | return *q.TotalPage 46 | } 47 | return 0 48 | } 49 | 50 | // GetSmsSendDetailDTOs 获取短信发送记录 51 | func (q *QuerySendDetailsResponse) GetSmsSendDetailDTOs() *SmsSendDetailDTOs { 52 | if q != nil && q.SmsSendDetailDTOs != nil { 53 | return q.SmsSendDetailDTOs 54 | } 55 | return nil 56 | } 57 | 58 | // String 序列化成JSON字符串 59 | func (q QuerySendDetailsResponse) String() string { 60 | body, err := json.Marshal(q) 61 | if err != nil { 62 | return "" 63 | } 64 | return string(body) 65 | } 66 | 67 | // QuerySendDetailsRequest 短信发送记录查询接口请求 68 | type QuerySendDetailsRequest struct { 69 | Request *Request 70 | } 71 | 72 | // SetSendDate 设置短信发送日期 必须 73 | // 短信发送日期格式yyyyMMdd,支持最近30天记录查询 74 | func (q *QuerySendDetailsRequest) SetSendDate(sendDate string) { 75 | if q != nil && q.Request != nil { 76 | q.Request.Put("SendDate", sendDate) 77 | } 78 | } 79 | 80 | // GetSendDate 获取短信发送日期 81 | func (q *QuerySendDetailsRequest) GetSendDate() string { 82 | if q != nil && q.Request != nil { 83 | return q.Request.Get("SendDate") 84 | } 85 | return "" 86 | } 87 | 88 | // SetPageSize 设置页大小 必须 89 | // 页大小Max=50 90 | func (q *QuerySendDetailsRequest) SetPageSize(pageSize string) { 91 | if q != nil && q.Request != nil { 92 | q.Request.Put("PageSize", pageSize) 93 | } 94 | } 95 | 96 | // GetPageSize 获取设置页大小 97 | func (q *QuerySendDetailsRequest) GetPageSize() string { 98 | if q != nil && q.Request != nil { 99 | return q.Request.Get("PageSize") 100 | } 101 | return "" 102 | } 103 | 104 | // SetResourceOwnerID 来源于python,未知参数 105 | func (q *QuerySendDetailsRequest) SetResourceOwnerID(resourceOwnerID string) { 106 | if q != nil && q.Request != nil { 107 | q.Request.Put("ResourceOwnerId", resourceOwnerID) 108 | } 109 | } 110 | 111 | // GetResourceOwnerID 来源于python,未知参数 112 | func (q *QuerySendDetailsRequest) GetResourceOwnerID() string { 113 | if q != nil && q.Request != nil { 114 | return q.Request.Get("ResourceOwnerId") 115 | } 116 | return "" 117 | } 118 | 119 | // SetOwnerID 来源于python,未知参数 120 | func (q *QuerySendDetailsRequest) SetOwnerID(ownerID string) { 121 | if q != nil && q.Request != nil { 122 | q.Request.Put("OwnerId", ownerID) 123 | } 124 | } 125 | 126 | // GetOwnerID 来源于python,未知参数 127 | func (q *QuerySendDetailsRequest) GetOwnerID() string { 128 | if q != nil && q.Request != nil { 129 | return q.Request.Get("OwnerId") 130 | } 131 | return "" 132 | } 133 | 134 | // SetPhoneNumber 设置短信接收号码 必须 135 | func (q *QuerySendDetailsRequest) SetPhoneNumber(phoneNumber string) { 136 | if q != nil && q.Request != nil { 137 | q.Request.Put("PhoneNumber", phoneNumber) 138 | } 139 | } 140 | 141 | // GetPhoneNumber 获取短信接收号码 142 | func (q *QuerySendDetailsRequest) GetPhoneNumber() string { 143 | if q != nil && q.Request != nil { 144 | return q.Request.Get("PhoneNumber") 145 | } 146 | return "" 147 | } 148 | 149 | // SetCurrentPage 设置当前页码 必须 150 | func (q *QuerySendDetailsRequest) SetCurrentPage(currentPage string) { 151 | if q != nil && q.Request != nil { 152 | q.Request.Put("CurrentPage", currentPage) 153 | } 154 | } 155 | 156 | // GetCurrentPage 获取当前页码 157 | func (q *QuerySendDetailsRequest) GetCurrentPage() string { 158 | if q != nil && q.Request != nil { 159 | return q.Request.Get("CurrentPage") 160 | } 161 | return "" 162 | } 163 | 164 | // SetBizID 设置发送流水号 可选 165 | // 从调用发送接口返回值中获取 166 | func (q *QuerySendDetailsRequest) SetBizID(bizID string) { 167 | if q != nil && q.Request != nil { 168 | q.Request.Put("BizId", bizID) 169 | } 170 | } 171 | 172 | // GetBizID 获取发送流水号 173 | func (q *QuerySendDetailsRequest) GetBizID() string { 174 | if q != nil && q.Request != nil { 175 | return q.Request.Get("BizId") 176 | } 177 | return "" 178 | } 179 | 180 | // SetResourceOwnerAccount 来源于python,未知参数 181 | func (q *QuerySendDetailsRequest) SetResourceOwnerAccount(resourceOwnerAccount string) { 182 | if q != nil && q.Request != nil { 183 | q.Request.Put("ResourceOwnerAccount", resourceOwnerAccount) 184 | } 185 | } 186 | 187 | // GetResourceOwnerAccount 来源于python,未知参数 188 | func (q *QuerySendDetailsRequest) GetResourceOwnerAccount() string { 189 | if q != nil && q.Request != nil { 190 | return q.Request.Get("ResourceOwnerAccount") 191 | } 192 | return "" 193 | } 194 | 195 | // DoActionWithException 发起HTTP请求 196 | func (q *QuerySendDetailsRequest) DoActionWithException() (resp *QuerySendDetailsResponse, err error) { 197 | if q != nil && q.Request != nil { 198 | resp := &QuerySendDetailsResponse{} 199 | body, httpCode, err := q.Request.Do("QuerySendDetails") 200 | resp.SetHTTPCode(httpCode) 201 | if err != nil { 202 | return resp, err 203 | } 204 | err = json.Unmarshal(body, resp) 205 | if err != nil { 206 | return resp, err 207 | } 208 | if httpCode != 200 { 209 | return resp, errors.New(resp.GetCode()) 210 | } 211 | return resp, nil 212 | } 213 | return nil, errors.New("QuerySendDetailsRequest is nil") 214 | } 215 | 216 | // QuerySendDetails 短信发送记录查询接口 217 | // bizID 可选 - 流水号 218 | // phoneNumber 查询的手机号码 219 | // pageSize 必填 - 页大小 220 | // currentPage 必填 - 当前页码从1开始计数 221 | // sendDate 必填 - 发送日期 支持30天内记录查询,格式yyyyMMdd 222 | func QuerySendDetails(bizID, phoneNumber, pageSize, currentPage, sendDate string) *QuerySendDetailsRequest { 223 | req := newRequset() 224 | req.Put("Version", "2017-05-25") 225 | req.Put("Action", "QuerySendDetails") 226 | 227 | r := &QuerySendDetailsRequest{Request: req} 228 | r.SetPhoneNumber(phoneNumber) // 查询的手机号码 229 | if bizID != "" { 230 | r.SetBizID(bizID) // 可选 - 流水号 231 | } 232 | r.SetSendDate(sendDate) // 必填 - 发送日期 支持30天内记录查询,格式yyyyMMdd 233 | r.SetCurrentPage(currentPage) // 必填 - 当前页码从1开始计数 234 | r.SetPageSize(pageSize) // 必填 - 页大小 235 | return r 236 | } 237 | -------------------------------------------------------------------------------- /dysms/send_sms_request.go: -------------------------------------------------------------------------------- 1 | // Package dysms Copyright 2016 The GiterLab Authors. All rights reserved. 2 | package dysms 3 | 4 | import ( 5 | "encoding/json" 6 | "errors" 7 | ) 8 | 9 | // SendSmsResponse 发送短信接口服务器响应 10 | type SendSmsResponse struct { 11 | ErrorMessage 12 | BizID *string `json:"BizId,omitempty"` // 发送回执ID,可根据该ID查询具体的发送状态 13 | } 14 | 15 | // GetBizID 发送回执ID,可根据该ID查询具体的发送状态 16 | func (s *SendSmsResponse) GetBizID() string { 17 | if s != nil && s.BizID != nil { 18 | return *s.BizID 19 | } 20 | return "" 21 | } 22 | 23 | // String 序列化成JSON字符串 24 | func (s SendSmsResponse) String() string { 25 | body, err := json.Marshal(s) 26 | if err != nil { 27 | return "" 28 | } 29 | return string(body) 30 | } 31 | 32 | // SendSmsRequest 发送短信接口请求 33 | type SendSmsRequest struct { 34 | Request *Request 35 | } 36 | 37 | // SetOutID 设置外部流水扩展字段 38 | func (s *SendSmsRequest) SetOutID(outID string) { 39 | if s != nil && s.Request != nil { 40 | s.Request.Put("OutId", outID) 41 | } 42 | } 43 | 44 | // GetOutID 获取外部流水扩展字段 45 | func (s *SendSmsRequest) GetOutID(outID string) string { 46 | if s != nil && s.Request != nil { 47 | return s.Request.Get("OutId") 48 | } 49 | return "" 50 | } 51 | 52 | // SetSignName 设置短信签名 53 | func (s *SendSmsRequest) SetSignName(signName string) { 54 | if s != nil && s.Request != nil { 55 | s.Request.Put("SignName", signName) 56 | } 57 | } 58 | 59 | // GetSignName 获取短信签名 60 | func (s *SendSmsRequest) GetSignName() string { 61 | if s != nil && s.Request != nil { 62 | return s.Request.Get("SignName") 63 | } 64 | return "" 65 | } 66 | 67 | // SetResourceOwnerID 来源于python,未知参数 68 | func (s *SendSmsRequest) SetResourceOwnerID(resourceOwnerID string) { 69 | if s != nil && s.Request != nil { 70 | s.Request.Put("ResourceOwnerId", resourceOwnerID) 71 | } 72 | } 73 | 74 | // GetResourceOwnerID 来源于python,未知参数 75 | func (s *SendSmsRequest) GetResourceOwnerID() string { 76 | if s != nil && s.Request != nil { 77 | return s.Request.Get("ResourceOwnerId") 78 | } 79 | return "" 80 | } 81 | 82 | // SetOwnerID 来源于python,未知参数 83 | func (s *SendSmsRequest) SetOwnerID(ownerID string) { 84 | if s != nil && s.Request != nil { 85 | s.Request.Put("OwnerId", ownerID) 86 | } 87 | } 88 | 89 | // GetOwnerID 来源于python,未知参数 90 | func (s *SendSmsRequest) GetOwnerID() string { 91 | if s != nil && s.Request != nil { 92 | return s.Request.Get("OwnerId") 93 | } 94 | return "" 95 | } 96 | 97 | // SetTemplateCode 短信模板ID 98 | func (s *SendSmsRequest) SetTemplateCode(templateCode string) { 99 | if s != nil && s.Request != nil { 100 | s.Request.Put("TemplateCode", templateCode) 101 | } 102 | } 103 | 104 | // GetTemplateCode 获取短信模板ID 105 | func (s *SendSmsRequest) GetTemplateCode() string { 106 | if s != nil && s.Request != nil { 107 | return s.Request.Get("TemplateCode") 108 | } 109 | return "" 110 | } 111 | 112 | // SetPhoneNumbers 短信接收号码。 113 | // 支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟, 114 | // 验证码类型的短信推荐使用单条调用的方式 115 | func (s *SendSmsRequest) SetPhoneNumbers(phoneNumbers string) { 116 | if s != nil && s.Request != nil { 117 | s.Request.Put("PhoneNumbers", phoneNumbers) 118 | } 119 | } 120 | 121 | // GetPhoneNumbers 获取短信接收号码。 122 | func (s *SendSmsRequest) GetPhoneNumbers() string { 123 | if s != nil && s.Request != nil { 124 | return s.Request.Get("PhoneNumbers") 125 | } 126 | return "" 127 | } 128 | 129 | // SetResourceOwnerAccount 来源于python,未知参数 130 | func (s *SendSmsRequest) SetResourceOwnerAccount(resourceOwnerAccount string) { 131 | if s != nil && s.Request != nil { 132 | s.Request.Put("ResourceOwnerAccount", resourceOwnerAccount) 133 | } 134 | } 135 | 136 | // GetResourceOwnerAccount 来源于python,未知参数 137 | func (s *SendSmsRequest) GetResourceOwnerAccount() string { 138 | if s != nil && s.Request != nil { 139 | return s.Request.Get("ResourceOwnerAccount") 140 | } 141 | return "" 142 | } 143 | 144 | // SetTemplateParam 短信模板变量替换JSON串, 145 | // 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求, 146 | // 比如短信内容中包含\r\n的情况在JSON中需要表示成\r\n,否则会导致JSON在服务端解析失败 147 | func (s *SendSmsRequest) SetTemplateParam(templateParam string) { 148 | if s != nil && s.Request != nil { 149 | s.Request.Put("TemplateParam", templateParam) 150 | } 151 | } 152 | 153 | // GetTemplateParam 获取短信模板变量替换JSON串, 154 | func (s *SendSmsRequest) GetTemplateParam() string { 155 | if s != nil && s.Request != nil { 156 | return s.Request.Get("TemplateParam") 157 | } 158 | return "" 159 | } 160 | 161 | // DoActionWithException 发起HTTP请求 162 | func (s *SendSmsRequest) DoActionWithException() (resp *SendSmsResponse, err error) { 163 | if s != nil && s.Request != nil { 164 | resp := &SendSmsResponse{} 165 | body, httpCode, err := s.Request.Do("SendSms") 166 | resp.SetHTTPCode(httpCode) 167 | if err != nil { 168 | return resp, err 169 | } 170 | err = json.Unmarshal(body, resp) 171 | if err != nil { 172 | return resp, err 173 | } 174 | if httpCode != 200 { 175 | return resp, errors.New(resp.GetCode()) 176 | } 177 | return resp, nil 178 | } 179 | return nil, errors.New("SendSmsRequest is nil") 180 | } 181 | 182 | // SendSms 发送短信接口 183 | // businessID 设置业务请求流水号,必填。 184 | // phoneNumbers 短信发送的号码列表,必填。 多手机号使用,分割 185 | // signName 短信签名 186 | // templateCode 申请的短信模板编码,必填 187 | // templateParam 短信模板变量参数 188 | func SendSms(businessID, phoneNumbers, signName, templateCode, templateParam string) *SendSmsRequest { 189 | req := newRequset() 190 | req.Put("Version", "2017-05-25") 191 | req.Put("Action", "SendSms") 192 | 193 | r := &SendSmsRequest{Request: req} 194 | r.SetOutID(businessID) // 设置业务请求流水号,必填 195 | r.SetSignName(signName) // 短信签名 196 | r.SetPhoneNumbers(phoneNumbers) // 短信发送的号码列表,必填。 197 | r.SetTemplateCode(templateCode) // 短信模板 198 | if templateParam != "" { 199 | r.SetTemplateParam(templateParam) // 短信模板参数 200 | } 201 | return r 202 | } 203 | -------------------------------------------------------------------------------- /dysms/signature.go: -------------------------------------------------------------------------------- 1 | // Package dysms Copyright 2016 The GiterLab Authors. All rights reserved. 2 | package dysms 3 | 4 | import ( 5 | "crypto/hmac" 6 | "crypto/sha1" 7 | "encoding/base64" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | // 计算 HMAC 值。 13 | // 按照 RFC2104 的定义,使用得到的签名字符串计算签名 HMAC 值。 14 | // 注意:计算签名时使用的 Key 就是您持有的 Access Key Secret 并加上一个 “&” 字符(ASCII:38),使用的哈希算法是 SHA1。 15 | // 计算签名值。 16 | // 按照 Base64 编码规则 把步骤 3 中的 HMAC 值编码成字符串,即得到签名值(Signature)。 17 | func signatureMethod(key, stringToSign string) string { 18 | // The signature method is supposed to be HmacSHA1 19 | // A switch case is required if there is other methods available 20 | mac := hmac.New(sha1.New, []byte(key+"&")) 21 | mac.Write([]byte(stringToSign)) 22 | return base64.StdEncoding.EncodeToString(mac.Sum(nil)) 23 | } 24 | 25 | // 把编码后的字符串中加号(+)替换成%20、星号(*)替换成%2A、%7E 替换回波浪号(~), 即可得到所需要的编码字符串 26 | func percentEncodeBefore(s string) string { 27 | s = strings.Replace(s, "+", "%20", -1) 28 | s = strings.Replace(s, "*", "%2A", -1) 29 | s = strings.Replace(s, "%7E", "~", -1) 30 | 31 | return s 32 | } 33 | 34 | // 一般支持 URL 编码的库(比如 Java 中的 java.net.URLEncoder)都是按照“application/x-www-form-urlencoded”的MIME类型的规则进行编码的。 35 | // 实现时可以直接使用这类方式进行编码, 36 | func percentEncode(s string) string { 37 | s = url.QueryEscape(s) 38 | return percentEncodeBefore(s) 39 | } 40 | -------------------------------------------------------------------------------- /dysms/signature_test.go: -------------------------------------------------------------------------------- 1 | package dysms 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | ) 7 | 8 | func Test_signatureMethod(t *testing.T) { 9 | stringToSign := `GET&%2F&AccessKeyId%3DtestId%26Action%3DSendSms%26Format%3DXML%26OutId%3D123%26PhoneNumbers%3D15300000001%26RegionId%3Dcn-hangzhou%26SignName%3D%25E9%2598%25BF%25E9%2587%258C%25E4%25BA%2591%25E7%259F%25AD%25E4%25BF%25A1%25E6%25B5%258B%25E8%25AF%2595%25E4%25B8%2593%25E7%2594%25A8%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D45e25e9b-0a6f-4070-8c85-2956eda1b466%26SignatureVersion%3D1.0%26TemplateCode%3DSMS_71390007%26TemplateParam%3D%257B%2522customer%2522%253A%2522test%2522%257D%26Timestamp%3D2017-07-12T02%253A42%253A19Z%26Version%3D2017-05-25` 10 | signature := signatureMethod(`testSecret`, stringToSign) 11 | if url.QueryEscape(signature) != `zJDF%2BLrzhj%2FThnlvIToysFRq6t4%3D` { 12 | t.Error("signatureMethod failed") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/sample-dysms.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/GiterLab/aliyun-sms-go-sdk/dysms" 8 | "github.com/tobyzxj/uuid" 9 | ) 10 | 11 | // modify it to yours 12 | const ( 13 | ACCESSID = "your_accessid" 14 | ACCESSKEY = "your_accesskey" 15 | ) 16 | 17 | func main() { 18 | dysms.HTTPDebugEnable = true 19 | dysms.SetACLClient(ACCESSID, ACCESSKEY) // dysms.New(ACCESSID, ACCESSKEY) 20 | 21 | // 短信发送 22 | respSendSms, err := dysms.SendSms(uuid.New(), "1375821****", "多协云", "SMS_22175101", `{"company":"duoxieyun"}`).DoActionWithException() 23 | if err != nil { 24 | fmt.Println("send sms failed", err, respSendSms.Error()) 25 | os.Exit(0) 26 | } 27 | fmt.Println("send sms succeed", respSendSms.String()) 28 | 29 | // 查询短信 30 | respQuerySendDetails, err := dysms.QuerySendDetails("612710515335092485^0", "1375821****", "10", "1", "20180107").DoActionWithException() 31 | if err != nil { 32 | fmt.Println("query sms failed", err, respQuerySendDetails.Error()) 33 | os.Exit(0) 34 | } 35 | fmt.Println("query sms succeed", respQuerySendDetails.String()) 36 | } 37 | -------------------------------------------------------------------------------- /example/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/GiterLab/aliyun-sms-go-sdk/sms" 8 | ) 9 | 10 | // modify it to yours 11 | const ( 12 | ACCESSID = "your_accessid" 13 | ACCESSKEY = "your_accesskey" 14 | ) 15 | 16 | func main() { 17 | // 2017年12月20日至2018年1月21日 消息服务中的短信功能和云市场(阿里短信服务)将迁移至云通信短信服务 18 | // 为了尽快使用更专业的服务,还请您确认迁移后尽快下载正确的SKD和API代码 19 | // 此测试接口过时,请勿再使用 20 | sms.HTTPDebugEnable = true 21 | c := sms.New(ACCESSID, ACCESSKEY) 22 | // send to one person 23 | e, err := c.SendOne("1375821****", "多协云", "SMS_22175101", `{"company":"duoxieyun"}`) 24 | if err != nil { 25 | fmt.Println("send sms failed", err, e.Error()) 26 | os.Exit(0) 27 | } 28 | // send to more than one person 29 | e, err = c.SendMulti([]string{"1375821****", "1835718****"}, "多协云", "SMS_22175101", `{"company":"duoxieyun"}`) 30 | if err != nil { 31 | fmt.Println("send sms failed", err, e.Error()) 32 | os.Exit(0) 33 | } 34 | fmt.Println("send sms succeed", e.GetRequestID()) 35 | } 36 | -------------------------------------------------------------------------------- /sample-dysms.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/GiterLab/aliyun-sms-go-sdk/dysms" 8 | "github.com/tobyzxj/uuid" 9 | ) 10 | 11 | // modify it to yours 12 | const ( 13 | ACCESSID = "your_accessid" 14 | ACCESSKEY = "your_accesskey" 15 | ) 16 | 17 | func main() { 18 | dysms.HTTPDebugEnable = true 19 | dysms.SetACLClient(ACCESSID, ACCESSKEY) // dysms.New(ACCESSID, ACCESSKEY) 20 | 21 | // 短信发送 22 | respSendSms, err := dysms.SendSms(uuid.New(), "1375821****", "多协云", "SMS_22175101", `{"company":"duoxieyun"}`).DoActionWithException() 23 | if err != nil { 24 | fmt.Println("send sms failed", err, respSendSms.Error()) 25 | os.Exit(0) 26 | } 27 | fmt.Println("send sms succeed", respSendSms.String()) 28 | 29 | // 查询短信 30 | respQuerySendDetails, err := dysms.QuerySendDetails("612710515335092485^0", "1375821****", "10", "1", "20180107").DoActionWithException() 31 | if err != nil { 32 | fmt.Println("query sms failed", err, respQuerySendDetails.Error()) 33 | os.Exit(0) 34 | } 35 | fmt.Println("query sms succeed", respQuerySendDetails.String()) 36 | } 37 | -------------------------------------------------------------------------------- /sms/signature.go: -------------------------------------------------------------------------------- 1 | // Package sms Copyright 2016 The GiterLab Authors. All rights reserved. 2 | package sms 3 | 4 | import ( 5 | "crypto/hmac" 6 | "crypto/sha1" 7 | "encoding/base64" 8 | "net/url" 9 | "strings" 10 | ) 11 | 12 | // 计算 HMAC 值。 13 | // 按照 RFC2104 的定义,使用得到的签名字符串计算签名 HMAC 值。 14 | // 注意:计算签名时使用的 Key 就是您持有的 Access Key Secret 并加上一个 “&” 字符(ASCII:38),使用的哈希算法是 SHA1。 15 | // 计算签名值。 16 | // 按照 Base64 编码规则 把步骤 3 中的 HMAC 值编码成字符串,即得到签名值(Signature)。 17 | func signatureMethod(key, stringToSign string) string { 18 | // The signature method is supposed to be HmacSHA1 19 | // A switch case is required if there is other methods available 20 | mac := hmac.New(sha1.New, []byte(key+"&")) 21 | mac.Write([]byte(stringToSign)) 22 | return base64.StdEncoding.EncodeToString(mac.Sum(nil)) 23 | } 24 | 25 | // 把编码后的字符串中加号(+)替换成%20、星号(*)替换成%2A、%7E 替换回波浪号(~), 即可得到所需要的编码字符串 26 | func percentEncodeBefore(s string) string { 27 | s = strings.Replace(s, "+", "%20", -1) 28 | s = strings.Replace(s, "*", "%2A", -1) 29 | s = strings.Replace(s, "%7E", "~", -1) 30 | 31 | return s 32 | } 33 | 34 | // 一般支持 URL 编码的库(比如 Java 中的 java.net.URLEncoder)都是按照“application/x-www-form-urlencoded”的MIME类型的规则进行编码的。 35 | // 实现时可以直接使用这类方式进行编码, 36 | func percentEncode(s string) string { 37 | s = url.QueryEscape(s) 38 | return percentEncodeBefore(s) 39 | } 40 | -------------------------------------------------------------------------------- /sms/sms.go: -------------------------------------------------------------------------------- 1 | // Package sms Copyright 2016 The GiterLab Authors. All rights reserved. 2 | package sms 3 | 4 | import ( 5 | "compress/gzip" 6 | "encoding/json" 7 | "errors" 8 | "fmt" 9 | "io/ioutil" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "sort" 14 | "strings" 15 | "time" 16 | 17 | "github.com/GiterLab/urllib" 18 | "github.com/tobyzxj/uuid" 19 | ) 20 | 21 | // HTTPDebugEnable http调试开关 22 | var HTTPDebugEnable = false 23 | 24 | // Param 短信发送所需要的参数 25 | type Param struct { 26 | // 系统参数 27 | AccessKeyID string // 阿里云颁发给用户的访问服务所用的密钥ID 28 | Timestamp string // 格式为:yyyy-MM-dd’T’HH:mm:ss’Z’;时区为:GMT 29 | Format string // 没传默认为JSON,可选填值:XML 30 | SignatureMethod string // 建议固定值:HMAC-SHA1 31 | SignatureVersion string // 建议固定值:1.0 32 | SignatureNonce string // 用于请求的防重放攻击,每次请求唯一 33 | Signature string // 最终生成的签名结果值 34 | 35 | // 业务参数 36 | Action string // API的命名,固定值,如发送短信API的值为:SendSms 37 | Version string // API的版本,固定值,如短信API的值为:2017-05-25 38 | RegionID string // API支持的RegionID,如短信API的值为:cn-hangzhou 39 | RecNum string // 手机号 40 | SignName string // 短信签名 41 | TemplateCode string // 短信模板 42 | ParamString string // 短信版本中的参数 43 | OutID string // 外部流水扩展字段 44 | } 45 | 46 | // SetAccessKeyID 设置密钥ID 47 | func (p *Param) SetAccessKeyID(accesskeyid string) { 48 | p.AccessKeyID = accesskeyid 49 | } 50 | 51 | // GetAccessKeyID 获取密钥ID 52 | func (p *Param) GetAccessKeyID() string { 53 | return p.AccessKeyID 54 | } 55 | 56 | // SetTimestamp 设置时间戳 57 | func (p *Param) SetTimestamp(timestamp string) { 58 | p.Timestamp = timestamp 59 | } 60 | 61 | // GetTimestamp 获取时间戳 62 | func (p *Param) GetTimestamp() string { 63 | return p.Timestamp 64 | } 65 | 66 | // SetFormat 设置返回格式,JSON/XML 67 | func (p *Param) SetFormat(format string) { 68 | p.Format = format 69 | } 70 | 71 | // GetFormat 获取返回格式 72 | func (p *Param) GetFormat() string { 73 | return p.Format 74 | } 75 | 76 | // SetSignatureMethod 设置签名方法 77 | func (p *Param) SetSignatureMethod(signaturemethod string) { 78 | p.SignatureMethod = signaturemethod 79 | } 80 | 81 | // GetSignatureMethod 获取签名方法 82 | func (p *Param) GetSignatureMethod() string { 83 | return p.SignatureMethod 84 | } 85 | 86 | // SetSignatureVersion 设置签名版本 87 | func (p *Param) SetSignatureVersion(signatureversion string) { 88 | p.SignatureVersion = signatureversion 89 | } 90 | 91 | // GetSignatureVersion 获取签名版本 92 | func (p *Param) GetSignatureVersion() string { 93 | return p.SignatureVersion 94 | } 95 | 96 | // SetSignatureNonce 设置每一次请求的唯一序列 97 | func (p *Param) SetSignatureNonce(signaturenonce string) { 98 | p.SignatureNonce = signaturenonce 99 | } 100 | 101 | // GetSignatureNonce 获取当前请求的序列 102 | func (p *Param) GetSignatureNonce() string { 103 | return p.SignatureNonce 104 | } 105 | 106 | // SetSignature 设置最终的签名结果 107 | func (p *Param) SetSignature(signature string) { 108 | p.Signature = signature 109 | } 110 | 111 | // GetSignature 获取签名结果 112 | func (p *Param) GetSignature() string { 113 | return p.Signature 114 | } 115 | 116 | // SetAction 设置API请求方法参数 117 | func (p *Param) SetAction(action string) { 118 | p.Action = action 119 | } 120 | 121 | // GetAction 获取API请求方法参数 122 | func (p *Param) GetAction() string { 123 | return p.Action 124 | } 125 | 126 | // SetVersion 设置API版本 127 | func (p *Param) SetVersion(version string) { 128 | p.Version = version 129 | } 130 | 131 | // GetVersion 获取API版本 132 | func (p *Param) GetVersion() string { 133 | return p.Version 134 | } 135 | 136 | // SetRegionID 设置API的RegionID 137 | func (p *Param) SetRegionID(regioniD string) { 138 | p.RegionID = regioniD 139 | } 140 | 141 | // GetRegionID 获取API的RegionID 142 | func (p *Param) GetRegionID() string { 143 | return p.RegionID 144 | } 145 | 146 | // SetRecNum 设置短信接收的手机号 147 | func (p *Param) SetRecNum(RecNum string) { 148 | p.RecNum = RecNum 149 | } 150 | 151 | // GetRecNum 获取短信接收的手机号 152 | func (p *Param) GetRecNum() string { 153 | return p.RecNum 154 | } 155 | 156 | // SetSignName 设置签名参数 157 | func (p *Param) SetSignName(signname string) { 158 | p.SignName = signname 159 | } 160 | 161 | // GetSignName 获取签名参数 162 | func (p *Param) GetSignName() string { 163 | return p.SignName 164 | } 165 | 166 | // SetTemplateCode 设置短信模板 167 | func (p *Param) SetTemplateCode(templatecode string) { 168 | p.TemplateCode = templatecode 169 | } 170 | 171 | // GetTemplateCode 获取短信模板 172 | func (p *Param) GetTemplateCode() string { 173 | return p.TemplateCode 174 | } 175 | 176 | // SetParamString 设置短信模板参数 177 | func (p *Param) SetParamString(ParamString string) { 178 | p.ParamString = ParamString 179 | } 180 | 181 | // GetParamString 获取短信模板参数 182 | func (p *Param) GetParamString() string { 183 | return p.ParamString 184 | } 185 | 186 | // SetOutID 设置外部流水扩展字段 187 | func (p *Param) SetOutID(outid string) { 188 | p.OutID = outid 189 | } 190 | 191 | // GetOutID 获取外部流水扩展字段 192 | func (p *Param) GetOutID() string { 193 | return p.OutID 194 | } 195 | 196 | // ErrorMessage 短信服务器返回的错误信息 197 | type ErrorMessage struct { 198 | HTTPCode int `json:"-"` 199 | Model *string `json:"Model,omitempty"` 200 | RequestID *string `json:"RequestId,omitempty"` 201 | Message *string `json:"Message,omitempty"` 202 | Code *string `json:"Code,omitempty"` 203 | } 204 | 205 | // GetHTTPCode 获取HTTP请求的错误码 206 | func (e *ErrorMessage) GetHTTPCode() int { 207 | return e.HTTPCode 208 | } 209 | 210 | // SetHTTPCode 设置HTTP错误码 211 | func (e *ErrorMessage) SetHTTPCode(code int) { 212 | e.HTTPCode = code 213 | } 214 | 215 | // GetModel get model 216 | func (e *ErrorMessage) GetModel() string { 217 | if e != nil && e.Model != nil { 218 | return *e.Model 219 | } 220 | return "" 221 | } 222 | 223 | // GetRequestID 获取请求的ID序列 224 | func (e *ErrorMessage) GetRequestID() string { 225 | if e != nil && e.RequestID != nil { 226 | return *e.RequestID 227 | } 228 | return "" 229 | } 230 | 231 | // GetMessage 获取错误信息 232 | func (e *ErrorMessage) GetMessage() string { 233 | if e != nil && e.Message != nil { 234 | return *e.Message 235 | } 236 | return "" 237 | } 238 | 239 | // GetCode 获取请求的错误码 240 | func (e *ErrorMessage) GetCode() string { 241 | if e != nil && e.Code != nil { 242 | return *e.Code 243 | } 244 | return "" 245 | } 246 | 247 | // Error 序列化成字符串 248 | func (e *ErrorMessage) Error() string { 249 | body, err := json.Marshal(e) 250 | if err != nil { 251 | return "" 252 | } 253 | return string(body) 254 | } 255 | 256 | // Client HTTP请求配置信息 257 | type Client struct { 258 | // SMS服务的地址,默认为(https://sms.aliyuncs.com) 259 | EndPoint string 260 | // 访问SMS服务的accessid,通过官方网站申请或通过管理员获取 261 | AccessID string 262 | // 访问SMS服务的accesskey,通过官方网站申请或通过管理员获取 263 | AccessKey string 264 | // 连接池中每个连接的Socket超时,单位为秒,可以为int或float。默认值为30 265 | SocketTimeout int 266 | 267 | // 其他参数 268 | Param Param 269 | param map[string]string 270 | } 271 | 272 | // SetEndPoint 设置短信服务器 273 | func (c *Client) SetEndPoint(endPoint string) { 274 | c.EndPoint = endPoint 275 | } 276 | 277 | // SetAccessID 设置短信服务的accessid,通过官方网站申请或通过管理员获取 278 | func (c *Client) SetAccessID(accessid string) { 279 | c.AccessID = accessid 280 | } 281 | 282 | // SetAccessKey 设置短信服务的accesskey,通过官方网站申请或通过管理员获取 283 | func (c *Client) SetAccessKey(accesskey string) { 284 | c.AccessKey = accesskey 285 | } 286 | 287 | // SetSocketTimeout 设置短信服务的Socket超时,单位为秒,可以为int或float。默认值为30 288 | func (c *Client) SetSocketTimeout(sockettimeout int) { 289 | if sockettimeout == 0 { 290 | sockettimeout = 30 291 | } 292 | c.SocketTimeout = sockettimeout 293 | } 294 | 295 | func (c *Client) calcStringToSign() string { 296 | c.param = make(map[string]string) 297 | c.param["SignatureMethod"] = c.Param.GetSignatureMethod() 298 | c.param["SignatureNonce"] = uuid.New() 299 | // sync c.Param.SignatureNonce 300 | c.Param.SetSignatureNonce(c.param["SignatureNonce"]) 301 | c.param["AccessKeyId"] = c.Param.GetAccessKeyID() 302 | c.param["SignatureVersion"] = c.Param.GetSignatureVersion() 303 | c.param["Timestamp"] = time.Now().UTC().Format(time.RFC3339) 304 | // sync c.Param.Timestamp 305 | c.Param.SetTimestamp(c.param["Timestamp"]) 306 | c.param["Format"] = c.Param.GetFormat() 307 | 308 | c.param["Action"] = c.Param.GetAction() 309 | c.param["Version"] = c.Param.GetVersion() 310 | c.param["RegionId"] = c.Param.GetRegionID() 311 | c.param["RecNum"] = c.Param.GetRecNum() 312 | c.param["SignName"] = c.Param.GetSignName() 313 | c.param["ParamString"] = c.Param.GetParamString() 314 | c.param["TemplateCode"] = c.Param.GetTemplateCode() 315 | 316 | strslice := make([]string, len(c.param)) 317 | i := 0 318 | for k, v := range c.param { 319 | data := url.Values{} 320 | data.Add(k, v) 321 | strslice[i] = data.Encode() 322 | strslice[i] = percentEncodeBefore(strslice[i]) 323 | i++ 324 | } 325 | sort.Strings(strslice) 326 | return "POST&" + percentEncode("/") + "&" + percentEncode(strings.Join(strslice, "&")) 327 | } 328 | 329 | // SendOne 发送给一个手机号 330 | func (c *Client) SendOne(RecNum, signname, templatecode, ParamString string) (e *ErrorMessage, err error) { 331 | var body []byte 332 | 333 | e = &ErrorMessage{} 334 | c.Param.SetSignName(signname) 335 | c.Param.SetTemplateCode(templatecode) 336 | c.Param.SetParamString(ParamString) 337 | c.Param.SetRecNum(RecNum) 338 | signature := signatureMethod(c.AccessKey, c.calcStringToSign()) 339 | 340 | req := urllib.Post(c.EndPoint) 341 | if HTTPDebugEnable { 342 | req.Debug(true) 343 | } 344 | for k, v := range c.param { 345 | req.Param(k, v) 346 | } 347 | req.Param("Signature", signature) 348 | resp, err := req.Response() 349 | if err != nil { 350 | return nil, err 351 | } 352 | if resp.Body == nil { 353 | return nil, nil 354 | } 355 | defer resp.Body.Close() 356 | if resp.Header.Get("Content-Encoding") == "gzip" { 357 | reader, errGzip := gzip.NewReader(resp.Body) 358 | if errGzip != nil { 359 | return nil, errGzip 360 | } 361 | body, err = ioutil.ReadAll(reader) 362 | } else { 363 | body, err = ioutil.ReadAll(resp.Body) 364 | } 365 | if err != nil { 366 | return nil, err 367 | } 368 | err = json.Unmarshal(body, e) 369 | e.SetHTTPCode(resp.StatusCode) 370 | if HTTPDebugEnable { 371 | fmt.Println("C-->S:", req.DumpRequestString()) 372 | fmt.Println("S-->C:", e.Error()) 373 | } 374 | if err != nil { 375 | return e, err 376 | } 377 | if e.GetCode() != "" { 378 | return e, errors.New(e.GetCode()) 379 | } 380 | return e, nil 381 | } 382 | 383 | // SendMulti 发送给多个手机号, 最多100个 384 | func (c *Client) SendMulti(RecNum []string, signname, templatecode, ParamString string) (e *ErrorMessage, err error) { 385 | var body []byte 386 | 387 | e = &ErrorMessage{} 388 | if len(RecNum) > 100 { 389 | return nil, errors.New("number of RecNum should be less than 100") 390 | } 391 | c.Param.SetSignName(signname) 392 | c.Param.SetTemplateCode(templatecode) 393 | c.Param.SetParamString(ParamString) 394 | c.Param.SetRecNum(strings.Join(RecNum, ",")) 395 | signature := signatureMethod(c.AccessKey, c.calcStringToSign()) 396 | 397 | req := urllib.Post(c.EndPoint) 398 | for k, v := range c.param { 399 | req.Param(k, v) 400 | } 401 | req.Param("Signature", signature) 402 | resp, err := req.Response() 403 | if err != nil { 404 | return nil, err 405 | } 406 | if resp.Body == nil { 407 | return nil, nil 408 | } 409 | defer resp.Body.Close() 410 | if resp.Header.Get("Content-Encoding") == "gzip" { 411 | reader, errGzip := gzip.NewReader(resp.Body) 412 | if errGzip != nil { 413 | return nil, errGzip 414 | } 415 | body, err = ioutil.ReadAll(reader) 416 | } else { 417 | body, err = ioutil.ReadAll(resp.Body) 418 | } 419 | if err != nil { 420 | return nil, err 421 | } 422 | err = json.Unmarshal(body, e) 423 | e.SetHTTPCode(resp.StatusCode) 424 | if HTTPDebugEnable { 425 | fmt.Println("C-->S:", req.DumpRequestString()) 426 | fmt.Println("S-->C:", e.Error()) 427 | } 428 | if err != nil { 429 | return e, err 430 | } 431 | if e.GetCode() != "" { 432 | return e, errors.New(e.GetCode()) 433 | } 434 | return e, nil 435 | } 436 | 437 | // New 创建一个短信发送客户端 438 | func New(accessid, accesskey string) (c *Client) { 439 | c = new(Client) 440 | if c.EndPoint == "" { 441 | c.EndPoint = "https://sms.aliyuncs.com/" 442 | } 443 | c.AccessID = accessid 444 | c.AccessKey = accesskey 445 | c.Param.SetSignatureMethod("HMAC-SHA1") 446 | c.Param.SetSignatureNonce(uuid.New()) 447 | c.Param.SetAccessKeyID(accessid) 448 | c.Param.SetSignatureVersion("1.0") 449 | c.Param.SetTimestamp(time.Now().UTC().Format(time.RFC3339)) 450 | c.Param.SetFormat("JSON") 451 | 452 | c.Param.SetAction("SingleSendSms") 453 | c.Param.SetVersion("2016-09-27") 454 | c.Param.SetRegionID("cn-hangzhou") 455 | c.Param.SetRecNum("your_RecNum") 456 | c.Param.SetSignName("your_signname") 457 | c.Param.SetParamString("your_ParamString") 458 | c.Param.SetTemplateCode("your_templatecode") 459 | 460 | if urllib.GetDefaultSetting().Transport == nil { 461 | // set default setting for urllib 462 | trans := &http.Transport{ 463 | MaxIdleConnsPerHost: 500, 464 | Dial: (&net.Dialer{ 465 | Timeout: time.Duration(15) * time.Second, 466 | }).Dial, 467 | } 468 | 469 | urlSetting := urllib.HttpSettings{ 470 | ShowDebug: false, // ShowDebug 471 | UserAgent: "GiterLab", // UserAgent 472 | ConnectTimeout: 15 * time.Second, // ConnectTimeout 473 | ReadWriteTimeout: 30 * time.Second, // ReadWriteTimeout 474 | TlsClientConfig: nil, // TlsClientConfig 475 | Proxy: nil, // Proxy 476 | Transport: trans, // Transport 477 | EnableCookie: false, // EnableCookie 478 | Gzip: true, // Gzip 479 | DumpBody: true, // DumpBody 480 | } 481 | if c.SocketTimeout != 0 { 482 | urlSetting.ConnectTimeout = time.Duration(c.SocketTimeout) * time.Second 483 | urlSetting.ReadWriteTimeout = time.Duration(c.SocketTimeout) * time.Second 484 | } 485 | if HTTPDebugEnable { 486 | urlSetting.ShowDebug = true 487 | } else { 488 | urlSetting.ShowDebug = false 489 | } 490 | urllib.SetDefaultSetting(urlSetting) 491 | } 492 | 493 | return c 494 | } 495 | -------------------------------------------------------------------------------- /sms/sms_test.go: -------------------------------------------------------------------------------- 1 | package sms 2 | 3 | import ( 4 | "net/url" 5 | "testing" 6 | ) 7 | 8 | func Test_signatureMethod(t *testing.T) { 9 | stringToSign := `POST&%2F&AccessKeyId%3Dtestid%26Action%3DSingleSendSms%26Format%3DXML%26ParamString%3D%257B%2522name%2522%253A%2522d%2522%252C%2522name1%2522%253A%2522d%2522%257D%26RecNum%3D13098765432%26RegionId%3Dcn-hangzhou%26SignName%3D%25E6%25A0%2587%25E7%25AD%25BE%25E6%25B5%258B%25E8%25AF%2595%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D9e030f6b-03a2-40f0-a6ba-157d44532fd0%26SignatureVersion%3D1.0%26TemplateCode%3DSMS_1650053%26Timestamp%3D2016-10-20T05%253A37%253A52Z%26Version%3D2016-09-27` 10 | signature := signatureMethod(`testsecret`, stringToSign) 11 | if url.QueryEscape(signature) != `ka8PDlV7S9sYqxEMRnmlBv%2FDoAE%3D` { 12 | t.Error("signatureMethod failed") 13 | } 14 | } 15 | 16 | func Test_stringToSign(t *testing.T) { 17 | c := new(Client) 18 | c.EndPoint = "https://sms.aliyuncs.com/" 19 | c.AccessID = "testid" 20 | c.AccessKey = "testsecret" 21 | c.Param.SetAction("SingleSendSms") 22 | c.Param.SetSignName("标签测试") 23 | c.Param.SetTemplateCode("SMS_1650053") 24 | c.Param.SetRecNum("13098765432") 25 | c.Param.SetParamString(`{"name":"d","name1":"d"}`) 26 | c.Param.SetFormat("XML") 27 | c.Param.SetVersion("2016-09-27") 28 | c.Param.SetAccessKeyID("testid") 29 | c.Param.SetSignatureMethod("HMAC-SHA1") 30 | c.Param.SetTimestamp("2016-10-20T05:37:52Z") 31 | c.Param.SetSignatureVersion("1.0") 32 | c.Param.SetSignatureNonce("9e030f6b-03a2-40f0-a6ba-157d44532fd0") 33 | c.Param.SetRegionID("cn-hangzhou") 34 | stringToSign := c.calcStringToSign() 35 | stringToSignResult := `POST&%2F&AccessKeyId%3Dtestid%26Action%3DSingleSendSms%26Format%3DXML%26ParamString%3D%257B%2522name%2522%253A%2522d%2522%252C%2522name1%2522%253A%2522d%2522%257D%26RecNum%3D13098765432%26RegionId%3Dcn-hangzhou%26SignName%3D%25E6%25A0%2587%25E7%25AD%25BE%25E6%25B5%258B%25E8%25AF%2595%26SignatureMethod%3DHMAC-SHA1%26SignatureNonce%3D` + url.QueryEscape(url.QueryEscape(c.Param.GetSignatureNonce())) + `%26SignatureVersion%3D1.0%26TemplateCode%3DSMS_1650053%26Timestamp%3D` + url.QueryEscape(url.QueryEscape(c.Param.GetTimestamp())) + `%26Version%3D2016-09-27` 36 | if stringToSign != stringToSignResult { 37 | t.Error("calcStringToSign failed") 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | # This references the default golang container from 2 | # the Docker Hub: https://registry.hub.docker.com/u/library/golang/ 3 | # If you want Google's container you would reference google/golang 4 | # Read more about containers on our dev center 5 | # http://devcenter.wercker.com/docs/containers/index.html 6 | box: golang 7 | # This is the build pipeline. Pipelines are the core of wercker 8 | # Read more about pipelines on our dev center 9 | # http://devcenter.wercker.com/docs/pipelines/index.html 10 | 11 | # You can also use services such as databases. Read more on our dev center: 12 | # http://devcenter.wercker.com/docs/services/index.html 13 | # services: 14 | # - postgres 15 | # http://devcenter.wercker.com/docs/services/postgresql.html 16 | 17 | # - mongo 18 | # http://devcenter.wercker.com/docs/services/mongodb.html 19 | build: 20 | # The steps that will be executed on build 21 | # Steps make up the actions in your pipeline 22 | # Read more about steps on our dev center: 23 | # http://devcenter.wercker.com/docs/steps/index.html 24 | steps: 25 | # Sets the go workspace and places you package 26 | # at the right place in the workspace tree 27 | - setup-go-workspace 28 | 29 | # Gets the dependencies 30 | - script: 31 | name: go get 32 | code: | 33 | go get 34 | 35 | # Build the project 36 | - script: 37 | name: go build 38 | code: | 39 | go build example/sample.go 40 | go build example/sample-dysms.go 41 | 42 | # Test the project 43 | - script: 44 | name: go test 45 | code: | 46 | go test ./sms 47 | go test ./dysms 48 | --------------------------------------------------------------------------------