├── LICENSE ├── QcloudSms ├── QcloudSms.go ├── sms.go ├── util.go └── voice.go ├── README.md └── example ├── SmsMultiSender.go ├── SmsSingleSender.go ├── SmsStatusPuller.go └── SmsVoiceVerifyCodeSender.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 秦新 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QcloudSms/QcloudSms.go: -------------------------------------------------------------------------------- 1 | package QcloudSms 2 | 3 | import "errors" 4 | 5 | type qcloudsms struct { 6 | appid int 7 | appkey string 8 | SmsSingleSender *smsSingleSender 9 | SmsMultiSender *smsMultiSender 10 | SmsStatusPuller *smsStatusPuller 11 | SmsMobileStatusPuller *smsMobileStatusPuller 12 | SmsVoicePromptSender *promptVoiceSender 13 | PromptVoiceSender *promptVoiceSender 14 | SmsVoiceVerifyCodeSender *codeVoiceSender 15 | CodeVoiceSender *codeVoiceSender 16 | TtsVoiceSender *ttsVoiceSender 17 | VoiceFileUploader *voiceFileUploader 18 | FileVoiceSender *fileVoiceSender 19 | } 20 | 21 | //NewQcloudSms new一个qcloudsms实例 22 | func NewQcloudSms(appid int, appkey string) (*qcloudsms, error) { 23 | if appkey == "" { 24 | return nil, errors.New("appkey is nil") 25 | } 26 | return &qcloudsms{ 27 | appid: appid, 28 | appkey: appkey, 29 | SmsSingleSender: newSmsSingleSender(appid, appkey), 30 | SmsMultiSender: newSmsMultiSender(appid, appkey), 31 | SmsStatusPuller: newSmsStatusPuller(appid, appkey), 32 | SmsMobileStatusPuller: newSmsMobileStatusPuller(appid, appkey), 33 | SmsVoicePromptSender: newPromptVoiceSender(appid, appkey), 34 | PromptVoiceSender: newPromptVoiceSender(appid, appkey), 35 | SmsVoiceVerifyCodeSender: newCodeVoiceSender(appid, appkey), 36 | CodeVoiceSender: newCodeVoiceSender(appid, appkey), 37 | TtsVoiceSender: newTtsVoiceSender(appid, appkey), 38 | VoiceFileUploader: newVoiceFileUploader(appid, appkey), 39 | FileVoiceSender: newFileVoiceSender(appid, appkey), 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /QcloudSms/sms.go: -------------------------------------------------------------------------------- 1 | package QcloudSms 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | ) 7 | 8 | type smsSingleSender struct { 9 | appID int 10 | appKey, 11 | url string 12 | } 13 | 14 | func newSmsSingleSender(appID int, appKey string) *smsSingleSender { 15 | return &smsSingleSender{ 16 | appID: appID, 17 | appKey: appKey, 18 | url: `https://yun.tim.qq.com/v5/tlssmssvr/sendsms`, 19 | } 20 | } 21 | 22 | //Send 单发短信 23 | func (s *smsSingleSender) Send(msgType, nationCode int, phoneNumber, msg, extend, ext string, callback callbackFunc) error { 24 | reqUrl, err := url.Parse(s.url) 25 | if err != nil { 26 | return err 27 | } 28 | random := getRandom() 29 | now := getCurrentTime() 30 | headers := make(map[string]string) 31 | headers["Content-Type"] = "application/json" 32 | var phoneNumbers []string 33 | phoneNumbers = append(phoneNumbers, phoneNumber) 34 | 35 | type body struct { 36 | Tel tel `json:"tel"` 37 | Type int `json:"type"` 38 | Msg string `json:"msg"` 39 | Sig string `json:"sig"` 40 | Time int64 `json:"time"` 41 | Extend string `json:"extend"` 42 | Ext string `json:"ext"` 43 | } 44 | 45 | Tel := tel{ 46 | NationCode: strconv.Itoa(nationCode), 47 | Mobile: phoneNumber, 48 | } 49 | 50 | Body := body{ 51 | Tel: Tel, 52 | Type: msgType, 53 | Msg: msg, 54 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 55 | Time: now, 56 | Extend: extend, 57 | Ext: ext, 58 | } 59 | 60 | option := option{ 61 | Protocol: reqUrl.Scheme, 62 | Host: reqUrl.Host, 63 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 64 | Method: "POST", 65 | Headers: headers, 66 | Body: Body, 67 | } 68 | return request(option, callback) 69 | } 70 | 71 | //SendWithParam 指定模板ID单发 72 | func (s *smsSingleSender) SendWithParam(nationCode int, phoneNumber string, templID int, params []string, sign, extend, ext string, callback callbackFunc) error { 73 | reqUrl, err := url.Parse(s.url) 74 | if err != nil { 75 | return err 76 | } 77 | random := getRandom() 78 | now := getCurrentTime() 79 | headers := make(map[string]string) 80 | headers["Content-Type"] = "application/json" 81 | var phoneNumbers []string 82 | phoneNumbers = append(phoneNumbers, phoneNumber) 83 | 84 | type body struct { 85 | Tel tel `json:"tel"` 86 | Sign string `json:"sign"` 87 | TplID int `json:"tpl_id"` 88 | Params []string `json:"params"` 89 | Sig string `json:"sig"` 90 | Time int64 `json:"time"` 91 | Extend string `json:"extend"` 92 | Ext string `json:"ext"` 93 | } 94 | 95 | Tel := tel{ 96 | NationCode: strconv.Itoa(nationCode), 97 | Mobile: phoneNumber, 98 | } 99 | 100 | Body := body{ 101 | Tel: Tel, 102 | Sign: sign, 103 | TplID: templID, 104 | Params: params, 105 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 106 | Time: now, 107 | Extend: extend, 108 | Ext: ext, 109 | } 110 | 111 | option := option{ 112 | Protocol: reqUrl.Scheme, 113 | Host: reqUrl.Host, 114 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 115 | Method: "POST", 116 | Headers: headers, 117 | Body: Body, 118 | } 119 | return request(option, callback) 120 | } 121 | 122 | type smsMultiSender struct { 123 | appID int 124 | appKey, 125 | url string 126 | } 127 | 128 | func newSmsMultiSender(appID int, appKey string) *smsMultiSender { 129 | return &smsMultiSender{ 130 | appID: appID, 131 | appKey: appKey, 132 | url: `https://yun.tim.qq.com/v5/tlssmssvr/sendmultisms2`, 133 | } 134 | } 135 | 136 | //Send 群发短信 137 | func (s *smsMultiSender) Send(msgType, nationCode int, phoneNumbers []string, msg, extend, ext string, callback callbackFunc) error { 138 | reqUrl, err := url.Parse(s.url) 139 | if err != nil { 140 | return err 141 | } 142 | random := getRandom() 143 | now := getCurrentTime() 144 | headers := make(map[string]string) 145 | headers["Content-Type"] = "application/json" 146 | 147 | type body struct { 148 | Tel []tel `json:"tel"` 149 | Type int `json:"type"` 150 | Msg string `json:"msg"` 151 | Sig string `json:"sig"` 152 | Time int64 `json:"time"` 153 | Extend string `json:"extend"` 154 | Ext string `json:"ext"` 155 | } 156 | 157 | var Tel []tel 158 | for _, v := range phoneNumbers { 159 | Tel = append(Tel, tel{ 160 | NationCode: strconv.Itoa(nationCode), 161 | Mobile: v, 162 | }) 163 | } 164 | 165 | Body := body{ 166 | Tel: Tel, 167 | Type: msgType, 168 | Msg: msg, 169 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 170 | Time: now, 171 | Extend: extend, 172 | Ext: ext, 173 | } 174 | 175 | option := option{ 176 | Protocol: reqUrl.Scheme, 177 | Host: reqUrl.Host, 178 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 179 | Method: "POST", 180 | Headers: headers, 181 | Body: Body, 182 | } 183 | return request(option, callback) 184 | } 185 | 186 | // SendWithParam 指定模板ID群发 187 | func (s *smsMultiSender) SendWithParam(nationCode int, phoneNumbers []string, templID int, params []string, sign, extend, ext string, callback callbackFunc) error { 188 | reqUrl, err := url.Parse(s.url) 189 | if err != nil { 190 | return err 191 | } 192 | random := getRandom() 193 | now := getCurrentTime() 194 | headers := make(map[string]string) 195 | headers["Content-Type"] = "application/json" 196 | 197 | type body struct { 198 | Tel []tel `json:"tel"` 199 | Sign string `json:"sign"` 200 | TplID int `json:"tpl_id"` 201 | Params []string `json:"params"` 202 | Sig string `json:"sig"` 203 | Time int64 `json:"time"` 204 | Extend string `json:"extend"` 205 | Ext string `json:"ext"` 206 | } 207 | 208 | var Tel []tel 209 | for _, v := range phoneNumbers { 210 | Tel = append(Tel, tel{ 211 | NationCode: strconv.Itoa(nationCode), 212 | Mobile: v, 213 | }) 214 | } 215 | 216 | Body := body{ 217 | Tel: Tel, 218 | Sign: sign, 219 | TplID: templID, 220 | Params: params, 221 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 222 | Time: now, 223 | Extend: extend, 224 | Ext: extend, 225 | } 226 | 227 | option := option{ 228 | Protocol: reqUrl.Scheme, 229 | Host: reqUrl.Host, 230 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 231 | Method: "POST", 232 | Headers: headers, 233 | Body: Body, 234 | } 235 | return request(option, callback) 236 | } 237 | 238 | type smsStatusPuller struct { 239 | appID int 240 | appKey, 241 | url string 242 | } 243 | 244 | func newSmsStatusPuller(appID int, appKey string) *smsStatusPuller { 245 | return &smsStatusPuller{ 246 | appID: appID, 247 | appKey: appKey, 248 | url: `https://yun.tim.qq.com/v5/tlssmssvr/pullstatus`, 249 | } 250 | } 251 | 252 | func (s *smsStatusPuller) pull(msgType, max int, callback callbackFunc) error { 253 | reqUrl, err := url.Parse(s.url) 254 | if err != nil { 255 | return err 256 | } 257 | random := getRandom() 258 | now := getCurrentTime() 259 | headers := make(map[string]string) 260 | headers["Content-Type"] = "application/json" 261 | 262 | type body struct { 263 | Sig string `json:"sig"` 264 | Type int `json:"type"` 265 | Time int64 `json:"time"` 266 | Max int `json:"max"` 267 | } 268 | 269 | Body := body{ 270 | Sig: calculateSignature(s.appKey, random, now, []string{}), 271 | Type: msgType, 272 | Time: now, 273 | Max: max, 274 | } 275 | 276 | option := option{ 277 | Protocol: reqUrl.Scheme, 278 | Host: reqUrl.Host, 279 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 280 | Method: "POST", 281 | Headers: headers, 282 | Body: Body, 283 | } 284 | return request(option, callback) 285 | } 286 | //PullCallBack 拉取短信回执 287 | func (s *smsStatusPuller) PullCallBack(max int, callback callbackFunc) error { 288 | return s.pull(0, max, callback) 289 | } 290 | 291 | //PullReply 拉取回复 292 | func (s *smsStatusPuller) PullReply(max int, callback callbackFunc) error { 293 | return s.pull(1, max, callback) 294 | } 295 | 296 | type smsMobileStatusPuller struct { 297 | appID int 298 | appKey, 299 | url string 300 | } 301 | 302 | func newSmsMobileStatusPuller(appID int, appKey string) *smsMobileStatusPuller { 303 | return &smsMobileStatusPuller{ 304 | appID: appID, 305 | appKey: appKey, 306 | url: `https://yun.tim.qq.com/v5/tlssmssvr/pullstatus4mobile`, 307 | } 308 | } 309 | 310 | func (s *smsMobileStatusPuller) pull(msgType, nationCode int, mobile string, beginTime, endTime, max int, callback callbackFunc) error { 311 | reqUrl, err := url.Parse(s.url) 312 | if err != nil { 313 | return err 314 | } 315 | random := getRandom() 316 | now := getCurrentTime() 317 | headers := make(map[string]string) 318 | headers["Content-Type"] = "application/json" 319 | 320 | type body struct { 321 | Sig string `json:"sig"` 322 | Type int `json:"type"` 323 | Time int64 `json:"time"` 324 | Max int `json:"max"` 325 | BeginTime int `json:"begin_time"` 326 | EndTime int `json:"end_time"` 327 | NationCode string `json:"nationcode"` 328 | Mobile string `json:"mobile"` 329 | } 330 | 331 | Body := body{ 332 | Sig: calculateSignature(s.appKey, random, now, []string{}), 333 | Type: msgType, 334 | Time: now, 335 | Max: max, 336 | BeginTime: beginTime, 337 | EndTime: endTime, 338 | NationCode: strconv.Itoa(nationCode), 339 | Mobile: mobile, 340 | } 341 | 342 | option := option{ 343 | Protocol: reqUrl.Scheme, 344 | Host: reqUrl.Host, 345 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 346 | Method: "POST", 347 | Headers: headers, 348 | Body: Body, 349 | } 350 | return request(option, callback) 351 | } 352 | 353 | //PullCallBack 拉取单个手机号短信回执 354 | func (s *smsMobileStatusPuller) PullCallBack(nationCode int, mobile string, beginTime, endTime, max int, callback callbackFunc) error { 355 | return s.pull(0, nationCode, mobile, beginTime, endTime, max, callback) 356 | } 357 | 358 | //PullReply 拉取单个手机号回复 359 | func (s *smsMobileStatusPuller) PullReply(nationCode int, mobile string, beginTime, endTime, max int, callback callbackFunc) error { 360 | return s.pull(1, nationCode, mobile, beginTime, endTime, max, callback) 361 | } 362 | -------------------------------------------------------------------------------- /QcloudSms/util.go: -------------------------------------------------------------------------------- 1 | package QcloudSms 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "time" 7 | "crypto/sha256" 8 | "strconv" 9 | "net/http" 10 | "encoding/json" 11 | "bytes" 12 | "io/ioutil" 13 | "crypto/sha1" 14 | "encoding/hex" 15 | ) 16 | 17 | type callbackFunc func(error, *http.Response, string) 18 | 19 | type option struct { 20 | Protocol string `json:"protocol"` 21 | Host string `json:"host"` 22 | Path string `json:"path"` 23 | Method string `json:"method"` 24 | Headers map[string]string `json:"headers"` 25 | Body interface{} `json:"body"` 26 | } 27 | 28 | type tel struct { 29 | NationCode string `json:"nationcode"` 30 | Mobile string `json:"mobile"` 31 | } 32 | 33 | func getRandom() int { 34 | return int(math.Round(rand.Float64() * 99999)) 35 | } 36 | 37 | func getCurrentTime() int64 { 38 | return time.Now().Unix() 39 | } 40 | 41 | func calculateSignature(appKey string, random int, time int64, phoneNumbers []string) string { 42 | var err error 43 | h := sha256.New() 44 | if len(phoneNumbers) != 0 { 45 | var PhoneNumbers string 46 | for _, v := range phoneNumbers { 47 | PhoneNumbers = PhoneNumbers + v + "," 48 | } 49 | PhoneNumbers = PhoneNumbers[:len(PhoneNumbers)-1] 50 | _, err = h.Write([]byte("appkey=" + appKey + "&random=" + strconv.Itoa(random) + "&time=" + strconv.FormatInt(time, 10) + "&mobile=" + PhoneNumbers)) 51 | if err != nil { 52 | return "" 53 | } 54 | } else { 55 | _, err = h.Write([]byte("appkey=" + appKey + "&random=" + strconv.Itoa(random) + "&time=" + strconv.FormatInt(time, 10))) 56 | if err != nil { 57 | return "" 58 | } 59 | } 60 | return hex.EncodeToString(h.Sum(nil)) 61 | } 62 | 63 | func calculateAuth(appKey string, random int, time int64, fileSha1Sum string) string { 64 | var err error 65 | h := sha256.New() 66 | _, err = h.Write([]byte("appkey=" + appKey + "&random=" + strconv.Itoa(random) + "&time=" + strconv.FormatInt(time, 10) + "&content-sha1=" + fileSha1Sum)) 67 | if err != nil { 68 | return "" 69 | } 70 | return hex.EncodeToString(h.Sum(nil)) 71 | } 72 | 73 | func sha1sum(buf []byte) string { 74 | s := sha1.New() 75 | s.Write([]byte(buf)) 76 | return hex.EncodeToString(s.Sum(nil)) 77 | } 78 | 79 | func request(options option, callback callbackFunc) error { 80 | var err error 81 | var rawBody []byte 82 | rawBody, err = json.Marshal(options.Body) 83 | if err != nil { 84 | return nil 85 | } 86 | body := bytes.NewReader(rawBody) 87 | url := options.Protocol + `://` + options.Host + options.Path 88 | var req *http.Request 89 | req, err = http.NewRequest(options.Method, url, body) 90 | if err != nil { 91 | return err 92 | } 93 | q := req.URL.Query() 94 | req.URL.RawQuery = q.Encode() 95 | for k, v := range options.Headers { 96 | req.Header.Add(k, v) 97 | } 98 | var resp *http.Response 99 | c := http.Client{} 100 | resp, err = c.Do(req) 101 | if err != nil { 102 | return err 103 | } 104 | defer resp.Body.Close() 105 | var data []byte 106 | data, err = ioutil.ReadAll(resp.Body) 107 | if err != nil { 108 | return err 109 | } 110 | if callback != nil { 111 | callback(err, resp, string(data)) 112 | } 113 | return err 114 | } 115 | -------------------------------------------------------------------------------- /QcloudSms/voice.go: -------------------------------------------------------------------------------- 1 | package QcloudSms 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | "errors" 7 | ) 8 | 9 | type promptVoiceSender struct { 10 | appID int 11 | appKey, 12 | url string 13 | } 14 | 15 | func newPromptVoiceSender(appID int, appKey string) *promptVoiceSender { 16 | return &promptVoiceSender{ 17 | appID: appID, 18 | appKey: appKey, 19 | url: `https://cloud.tim.qq.com/v5/tlsvoicesvr/sendvoiceprompt`, 20 | } 21 | } 22 | 23 | //Send 发送语音通知和验证码 24 | func (s *promptVoiceSender) Send(nationCode int, phoneNumber string, promptType int, msg string, playTimes int, ext string, callback callbackFunc) error { 25 | if playTimes <= 0 { 26 | return errors.New("playtimes must great than zero") 27 | } 28 | reqUrl, err := url.Parse(s.url) 29 | if err != nil { 30 | return err 31 | } 32 | random := getRandom() 33 | now := getCurrentTime() 34 | headers := make(map[string]string) 35 | headers["Content-Type"] = "application/json" 36 | var phoneNumbers []string 37 | phoneNumbers = append(phoneNumbers, phoneNumber) 38 | 39 | type body struct { 40 | Tel tel `json:"tel"` 41 | PromptType string `json:"prompttype"` 42 | PromptFile string `json:"promptfile"` 43 | PlayTimes string `json:"playtimes"` 44 | Sig string `json:"sig"` 45 | Time int64 `json:"time"` 46 | Ext string `json:"ext"` 47 | } 48 | 49 | Tel := tel{ 50 | NationCode: strconv.Itoa(nationCode), 51 | Mobile: phoneNumber, 52 | } 53 | 54 | Body := body{ 55 | Tel: Tel, 56 | PromptType: strconv.Itoa(promptType), 57 | PromptFile: msg, 58 | PlayTimes: strconv.Itoa(playTimes), 59 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 60 | Time: now, 61 | Ext: ext, 62 | } 63 | 64 | option := option{ 65 | Protocol: reqUrl.Scheme, 66 | Host: reqUrl.Host, 67 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 68 | Method: "POST", 69 | Headers: headers, 70 | Body: Body, 71 | } 72 | return request(option, callback) 73 | } 74 | 75 | type codeVoiceSender struct { 76 | appID int 77 | appKey, 78 | url string 79 | } 80 | 81 | func newCodeVoiceSender(appID int, appKey string) *codeVoiceSender { 82 | return &codeVoiceSender{ 83 | appID: appID, 84 | appKey: appKey, 85 | url: `https://cloud.tim.qq.com/v5/tlsvoicesvr/sendcvoice`, 86 | } 87 | } 88 | 89 | func (s *codeVoiceSender) Send(nationCode int, phoneNumber, msg string, playTimes int, ext string, callback callbackFunc) error { 90 | if playTimes <= 0 { 91 | return errors.New("playtimes must great than zero") 92 | } 93 | reqUrl, err := url.Parse(s.url) 94 | if err != nil { 95 | return err 96 | } 97 | random := getRandom() 98 | now := getCurrentTime() 99 | headers := make(map[string]string) 100 | headers["Content-Type"] = "application/json" 101 | var phoneNumbers []string 102 | phoneNumbers = append(phoneNumbers, phoneNumber) 103 | 104 | type body struct { 105 | Tel tel `json:"tel"` 106 | Msg string `json:"msg"` 107 | PlayTimes string `json:"playtimes"` 108 | Sig string `json:"sig"` 109 | Time int64 `json:"time"` 110 | Ext string `json:"ext"` 111 | } 112 | 113 | Tel := tel{ 114 | NationCode: strconv.Itoa(nationCode), 115 | Mobile: phoneNumber, 116 | } 117 | 118 | Body := body{ 119 | Tel: Tel, 120 | Msg: msg, 121 | PlayTimes: strconv.Itoa(playTimes), 122 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 123 | Time: now, 124 | Ext: ext, 125 | } 126 | 127 | option := option{ 128 | Protocol: reqUrl.Scheme, 129 | Host: reqUrl.Host, 130 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 131 | Method: "POST", 132 | Headers: headers, 133 | Body: Body, 134 | } 135 | return request(option, callback) 136 | } 137 | 138 | type ttsVoiceSender struct { 139 | appID int 140 | appKey, 141 | url string 142 | } 143 | 144 | func newTtsVoiceSender(appID int, appKey string) *ttsVoiceSender { 145 | return &ttsVoiceSender{ 146 | appID: appID, 147 | appKey: appKey, 148 | url: `https://cloud.tim.qq.com/v5/tlsvoicesvr/sendtvoice`, 149 | } 150 | } 151 | 152 | func (s *ttsVoiceSender) Send(nationCode int, phoneNumber string, templID int, params []string, playTimes int, ext string, callback callbackFunc) error { 153 | if playTimes <= 0 { 154 | return errors.New("playtimes must great than zero") 155 | } 156 | reqUrl, err := url.Parse(s.url) 157 | if err != nil { 158 | return err 159 | } 160 | random := getRandom() 161 | now := getCurrentTime() 162 | headers := make(map[string]string) 163 | headers["Content-Type"] = "application/json" 164 | var phoneNumbers []string 165 | phoneNumbers = append(phoneNumbers, phoneNumber) 166 | 167 | type body struct { 168 | Tel tel `json:"tel"` 169 | TplID string `json:"tpl_id"` 170 | Params []string `json:"params"` 171 | PlayTimes string `json:"playtimes"` 172 | Sig string `json:"sig"` 173 | Time int64 `json:"time"` 174 | Ext string `json:"ext"` 175 | } 176 | 177 | Tel := tel{ 178 | NationCode: strconv.Itoa(nationCode), 179 | Mobile: phoneNumber, 180 | } 181 | 182 | Body := body{ 183 | Tel: Tel, 184 | Params: params, 185 | TplID: strconv.Itoa(templID), 186 | PlayTimes: strconv.Itoa(playTimes), 187 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 188 | Time: now, 189 | Ext: ext, 190 | } 191 | 192 | option := option{ 193 | Protocol: reqUrl.Scheme, 194 | Host: reqUrl.Host, 195 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 196 | Method: "POST", 197 | Headers: headers, 198 | Body: Body, 199 | } 200 | return request(option, callback) 201 | } 202 | 203 | type fileVoiceSender struct { 204 | appID int 205 | appKey, 206 | url string 207 | } 208 | 209 | func newFileVoiceSender(appID int, appKey string) *fileVoiceSender { 210 | return &fileVoiceSender{ 211 | appID: appID, 212 | appKey: appKey, 213 | url: `https://cloud.tim.qq.com/v5/tlsvoicesvr/sendfvoice`, 214 | } 215 | } 216 | 217 | func (s *fileVoiceSender) Send(nationCode int, phoneNumber, fid string, playTimes int, ext string, callback callbackFunc) error { 218 | if playTimes <= 0 { 219 | return errors.New("playtimes must great than zero") 220 | } 221 | reqUrl, err := url.Parse(s.url) 222 | if err != nil { 223 | return err 224 | } 225 | random := getRandom() 226 | now := getCurrentTime() 227 | headers := make(map[string]string) 228 | headers["Content-Type"] = "application/json" 229 | var phoneNumbers []string 230 | phoneNumbers = append(phoneNumbers, phoneNumber) 231 | 232 | type body struct { 233 | Tel tel `json:"tel"` 234 | Fid string `json:"fid"` 235 | PlayTimes string `json:"playtimes"` 236 | Sig string `json:"sig"` 237 | Time int64 `json:"time"` 238 | Ext string `json:"ext"` 239 | } 240 | 241 | Tel := tel{ 242 | NationCode: strconv.Itoa(nationCode), 243 | Mobile: phoneNumber, 244 | } 245 | 246 | Body := body{ 247 | Tel: Tel, 248 | Fid: fid, 249 | PlayTimes: strconv.Itoa(playTimes), 250 | Sig: calculateSignature(s.appKey, random, now, phoneNumbers), 251 | Time: now, 252 | Ext: ext, 253 | } 254 | 255 | option := option{ 256 | Protocol: reqUrl.Scheme, 257 | Host: reqUrl.Host, 258 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(s.appID) + "&random=" + strconv.Itoa(random), 259 | Method: "POST", 260 | Headers: headers, 261 | Body: Body, 262 | } 263 | return request(option, callback) 264 | } 265 | 266 | type voiceFileUploader struct { 267 | appID int 268 | appKey, 269 | url string 270 | } 271 | 272 | func newVoiceFileUploader(appID int, appKey string) *voiceFileUploader { 273 | return &voiceFileUploader{ 274 | appID: appID, 275 | appKey: appKey, 276 | url: `https://cloud.tim.qq.com/v5/tlsvoicesvr/uploadvoicefile`, 277 | } 278 | } 279 | 280 | func (u *voiceFileUploader) Upload(fileContent []byte, contentType string, callback callbackFunc) error { 281 | if contentType != "mp3" && contentType != "wav" { 282 | return errors.New(`contentType is invalid and should be 'mp3' or 'wav'`) 283 | } 284 | var Type string 285 | if contentType == "wav" { 286 | Type = "audio/wav" 287 | } else { 288 | Type = "audio/mpeg" 289 | } 290 | 291 | reqUrl, err := url.Parse(u.url) 292 | if err != nil { 293 | return err 294 | } 295 | random := getRandom() 296 | now := getCurrentTime() 297 | fileSha1Sum := sha1sum(fileContent) 298 | headers := make(map[string]string) 299 | headers["Content-Type"] = Type 300 | headers["x-content-sha1"] = fileSha1Sum 301 | headers["Authorization"] = calculateAuth(u.appKey, random, now, fileSha1Sum) 302 | 303 | option := option{ 304 | Protocol: reqUrl.Scheme, 305 | Host: reqUrl.Host, 306 | Path: reqUrl.Path + "?sdkappid=" + strconv.Itoa(u.appID) + "&random=" + strconv.Itoa(random), 307 | Method: "POST", 308 | Headers: headers, 309 | Body: fileContent, 310 | } 311 | return request(option, callback) 312 | } 313 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 腾讯云短信SDK Go语言实现 2 | 3 | ## 说明 4 | > 此 SDK 为非官方版本,命名和结构上与官方版本有一些区别。 5 | 6 | > 海外短信和国内短信使用同一接口,只需替换相应的国家码与手机号码,每次请求群发接口手机号码需全部为国内或者海外手机号码。 7 | 8 | > 语音通知目前支持语音验证码以及语音通知功能。 9 | 10 | ## 功能 11 | 12 | ##### 短信 13 | - [x] 单发短信 14 | - [x] 指定模板单发短信 15 | - [x] 群发短信 16 | - [x] 群发模板短信 17 | - [ ] 短信下发状态通知 18 | - [ ] 短信回复 19 | - [x] 拉取短信状态 20 | - [x] 拉取单个手机短信状态 21 | 22 | ##### 语音 23 | - [x] 发送语音验证码 24 | - [x] 发送语音通知 25 | - [ ] 语音验证码状态通知 26 | - [ ] 语音通知状态通知 27 | - [ ] 语音通知按键通知 28 | - [ ] 语音送达失败原因推送 29 | 30 | ## 如何使用 31 | 32 | ### 单发短信 33 | 34 | ```go 35 | package main 36 | 37 | import ( 38 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 39 | "net/http" 40 | "fmt" 41 | ) 42 | 43 | var appID = 122333333 44 | var appKey = "111111111112132312xx" 45 | var phoneNumber = "21212313123" 46 | var templId = 7839 47 | var params = []string{"指定模板单发", "深圳", "小明"} 48 | 49 | func callback(err error, resp *http.Response, resData string) { 50 | if err != nil { 51 | fmt.Println("err: ", err) 52 | } else { 53 | fmt.Println("response data: ", resData) 54 | } 55 | } 56 | 57 | func main() { 58 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 59 | if err != nil { 60 | panic(err) 61 | } 62 | //单发短信 63 | //发送短信没有指定模板ID时,发送的内容需要与已审核通过的模板内容相匹配,才可能下发成功,否则返回失败。 64 | //如需发送海外短信,同样可以使用此接口,只需将国家码"86"改写成对应国家码号。 65 | qcloudsms.SmsSingleSender.Send(0, 86, phoneNumber, 66 | "测试短信,普通单发,深圳,小明,上学。", "", "", callback) 67 | 68 | //指定模板ID单发 69 | //无论单发短信还是指定模板 ID 单发短信都需要从控制台中申请模板并且模板已经审核通过,才可能下发成功,否则返回失败。 70 | qcloudsms.SmsSingleSender.SendWithParam(86, phoneNumber, templId, params, "", "", "", callback) 71 | } 72 | ``` 73 | 74 | ### 群发短信 75 | 76 | ```go 77 | package main 78 | 79 | import ( 80 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 81 | "net/http" 82 | "fmt" 83 | ) 84 | 85 | var appID = 122333333 86 | var appKey = "111111111112132312xx" 87 | var phoneNumbers = []string{"21212313123", "12345678902", "12345678903"} 88 | var templId = 7839 89 | var params = []string{"指定模板群发", "深圳", "小明"} 90 | 91 | func callback(err error, resp *http.Response, resData string) { 92 | if err != nil { 93 | fmt.Println("err: ", err) 94 | } else { 95 | fmt.Println("response data: ", resData) 96 | } 97 | } 98 | 99 | func main() { 100 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 101 | if err != nil { 102 | panic(err) 103 | } 104 | //群发短信 105 | qcloudsms.SmsMultiSender.Send(0, 86, phoneNumbers, 106 | "测试短信,普通群发,深圳,小明,上学。", "", "", callback) 107 | 108 | //指定模板ID群发 109 | //群发一次请求最多支持 200 个号码 110 | qcloudsms.SmsMultiSender.SendWithParam(86, phoneNumbers, templId, params, "", "", "", callback) 111 | } 112 | ``` 113 | 114 | ### 发送语音验证码 115 | 116 | ```go 117 | package main 118 | 119 | import ( 120 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 121 | "net/http" 122 | "fmt" 123 | ) 124 | 125 | var appID = 122333333 126 | var appKey = "111111111112132312xx" 127 | var phoneNumber = "21212313123" 128 | 129 | func callback(err error, resp *http.Response, resData string) { 130 | if err != nil { 131 | fmt.Println("err: ", err) 132 | } else { 133 | fmt.Println("response data: ", resData) 134 | } 135 | } 136 | 137 | func main() { 138 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 139 | if err != nil { 140 | panic(err) 141 | } 142 | //发送语音验证码 143 | //语音验证码发送只需提供验证码数字,例如在 msg=“123”,您收到的语音通知为“您的语音验证码是1 2 3”,如需自定义内容,可以使用语音通知。 144 | qcloudsms.SmsVoiceVerifyCodeSender.Send(86, phoneNumber, "1234", 2, "", callback) 145 | 146 | //发送语音通知 147 | qcloudsms.SmsVoicePromptSender.Send(86, phoneNumber, 2, "1234", 2, "", callback) 148 | } 149 | ``` 150 | 151 | ### 拉取短信回执以及回复 152 | 153 | ```go 154 | package main 155 | 156 | import ( 157 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 158 | "net/http" 159 | "fmt" 160 | ) 161 | 162 | var appID = 122333333 163 | var appKey = "111111111112132312xx" 164 | var phoneNumber = "21212313123" 165 | 166 | func callback(err error, resp *http.Response, resData string) { 167 | if err != nil { 168 | fmt.Println("err: ", err) 169 | } else { 170 | fmt.Println("response data: ", resData) 171 | } 172 | } 173 | 174 | func main() { 175 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 176 | if err != nil { 177 | panic(err) 178 | } 179 | //拉取短信回执以及回复 180 | //拉取短信回执 181 | qcloudsms.SmsStatusPuller.PullCallBack(10, callback) 182 | 183 | //拉取回复 184 | qcloudsms.SmsStatusPuller.PullReply(10, callback) 185 | 186 | //拉取单个手机短信状态 187 | //拉取短信回执 188 | qcloudsms.SmsMobileStatusPuller.PullCallBack(86, phoneNumber, 1511125600, 1511841600, 10, callback) 189 | 190 | //拉取回复 191 | qcloudsms.SmsMobileStatusPuller.PullReply(86, phoneNumber, 1511125600, 1511841600, 10, callback) 192 | } 193 | ``` 194 | 195 | ## 许可 196 | 197 | 这个项目使用MIT许可. 198 | -------------------------------------------------------------------------------- /example/SmsMultiSender.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 5 | "net/http" 6 | "fmt" 7 | ) 8 | 9 | var appID = 122333333 10 | var appKey = "111111111112132312xx" 11 | var phoneNumbers = []string{"21212313123", "12345678902", "12345678903"} 12 | var templId = 7839 13 | var params = []string{"指定模板群发", "深圳", "小明"} 14 | 15 | func callback(err error, resp *http.Response, resData string) { 16 | if err != nil { 17 | fmt.Println("err: ", err) 18 | } else { 19 | fmt.Println("response data: ", resData) 20 | } 21 | } 22 | 23 | func main() { 24 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 25 | if err != nil { 26 | panic(err) 27 | } 28 | //群发短信 29 | qcloudsms.SmsMultiSender.Send(0, 86, phoneNumbers, 30 | "测试短信,普通群发,深圳,小明,上学。", "", "", callback) 31 | 32 | //指定模板ID群发 33 | //群发一次请求最多支持 200 个号码 34 | qcloudsms.SmsMultiSender.SendWithParam(86, phoneNumbers, templId, params, "", "", "", callback) 35 | } 36 | -------------------------------------------------------------------------------- /example/SmsSingleSender.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 5 | "net/http" 6 | "fmt" 7 | ) 8 | 9 | var appID = 122333333 10 | var appKey = "111111111112132312xx" 11 | var phoneNumber = "21212313123" 12 | var templId = 7839 13 | var params = []string{"指定模板单发", "深圳", "小明"} 14 | 15 | func callback(err error, resp *http.Response, resData string) { 16 | if err != nil { 17 | fmt.Println("err: ", err) 18 | } else { 19 | fmt.Println("response data: ", resData) 20 | } 21 | } 22 | 23 | func main() { 24 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 25 | if err != nil { 26 | panic(err) 27 | } 28 | //单发短信 29 | //发送短信没有指定模板ID时,发送的内容需要与已审核通过的模板内容相匹配,才可能下发成功,否则返回失败。 30 | //如需发送海外短信,同样可以使用此接口,只需将国家码"86"改写成对应国家码号。 31 | qcloudsms.SmsSingleSender.Send(0, 86, phoneNumber, 32 | "测试短信,普通单发,深圳,小明,上学。", "", "", callback) 33 | 34 | //指定模板ID单发 35 | //无论单发短信还是指定模板 ID 单发短信都需要从控制台中申请模板并且模板已经审核通过,才可能下发成功,否则返回失败。 36 | qcloudsms.SmsSingleSender.SendWithParam(86, phoneNumber, templId, params, "", "", "", callback) 37 | } 38 | -------------------------------------------------------------------------------- /example/SmsStatusPuller.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 5 | "net/http" 6 | "fmt" 7 | ) 8 | 9 | var appID = 122333333 10 | var appKey = "111111111112132312xx" 11 | var phoneNumber = "21212313123" 12 | 13 | func callback(err error, resp *http.Response, resData string) { 14 | if err != nil { 15 | fmt.Println("err: ", err) 16 | } else { 17 | fmt.Println("response data: ", resData) 18 | } 19 | } 20 | 21 | func main() { 22 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 23 | if err != nil { 24 | panic(err) 25 | } 26 | //拉取短信回执以及回复 27 | //拉取短信回执 28 | qcloudsms.SmsStatusPuller.PullCallBack(10, callback) 29 | 30 | //拉取回复 31 | qcloudsms.SmsStatusPuller.PullReply(10, callback) 32 | 33 | //拉取单个手机短信状态 34 | //拉取短信回执 35 | qcloudsms.SmsMobileStatusPuller.PullCallBack(86, phoneNumber, 1511125600, 1511841600, 10, callback) 36 | 37 | //拉取回复 38 | qcloudsms.SmsMobileStatusPuller.PullReply(86, phoneNumber, 1511125600, 1511841600, 10, callback) 39 | } 40 | -------------------------------------------------------------------------------- /example/SmsVoiceVerifyCodeSender.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/qinxin0720/QcloudSms-go/QcloudSms" 5 | "net/http" 6 | "fmt" 7 | ) 8 | 9 | var appID = 122333333 10 | var appKey = "111111111112132312xx" 11 | var phoneNumber = "21212313123" 12 | 13 | func callback(err error, resp *http.Response, resData string) { 14 | if err != nil { 15 | fmt.Println("err: ", err) 16 | } else { 17 | fmt.Println("response data: ", resData) 18 | } 19 | } 20 | 21 | func main() { 22 | qcloudsms, err := QcloudSms.NewQcloudSms(appID, appKey) 23 | if err != nil { 24 | panic(err) 25 | } 26 | //发送语音验证码 27 | //语音验证码发送只需提供验证码数字,例如在 msg=“123”,您收到的语音通知为“您的语音验证码是1 2 3”,如需自定义内容,可以使用语音通知。 28 | qcloudsms.SmsVoiceVerifyCodeSender.Send(86, phoneNumber, "1234", 2, "", callback) 29 | 30 | //发送语音通知 31 | qcloudsms.SmsVoicePromptSender.Send(86, phoneNumber, 2, "1234", 2, "", callback) 32 | } 33 | --------------------------------------------------------------------------------