在之前介紹的火車票查詢工具中,利用郵件和短信將查詢結果推送給用戶。免費短信的條數只有5條,用完之后只能單獨使用郵件提醒。
最近發現騰訊雲的福利,簡單的介紹一下用法。
騰訊雲-》產品-》通信服務-》短信-》開通服務-》添加應用-》創建簽名和模板-》等待審核通過-》按照Demo測試
在整個流程中,最耗時的就是創建簽名和模板,對個人來說只能選擇app、公眾號或小程序的方式進行申請,多虧之前折騰過一點小程序,從而闖關成功。
接下來查看官方Demo https://github.com/qcloudsms/qcloudsms/tree/master/demo/csharp
整理后的測試Demo
調用代碼:
1 using QcloudSms; 2 using System; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Threading.Tasks; 7 8 namespace ConsoleTest 9 { 10 class SmsSDKDemo 11 { 12 /// <summary> 13 /// appId 14 /// </summary> 15 private static int appId = 140xxxxx; 16 /// <summary> 17 /// appKey 18 /// </summary> 19 private static string appKey = "xxxxxxxxxxxxxxx"; 20 /// <summary> 21 /// 接收手機號 22 /// </summary> 23 private static string phoneNumber = "xxxxxxxx"; 24 /// <summary> 25 /// 短信模板ID 26 /// </summary> 27 private static int tmplateId = xxxxxxx; 28 29 static void Main(string[] args) 30 { 31 try 32 { 33 SmsSingleSenderResult singleResult; 34 SmsSingleSender singleSender = new SmsSingleSender(appId, appKey); 35 36 singleResult = singleSender.Send(SmsType.普通短信, phoneNumber, ""); 37 Console.WriteLine(singleResult); 38 39 List<string> templParams = new List<string>(); 40 templParams.Add("G2619"); 41 templParams.Add("二等座:23 無座:128"); 42 singleResult = singleSender.SendWithParam(phoneNumber, tmplateId, templParams); 43 Console.WriteLine(singleResult); 44 } 45 catch (Exception e) 46 { 47 Console.WriteLine(e); 48 } 49 Console.Read(); 50 } 51 } 52 }

1 namespace QcloudSms 2 { 3 #region 短信類型枚舉 4 /// <summary> 5 /// 短信類型枚舉 6 /// </summary> 7 public enum SmsType 8 { 9 普通短信 = 0, 10 營銷短信 = 1 11 } 12 #endregion 13 14 #region 單發 15 /// <summary> 16 /// 單發 17 /// </summary> 18 class SmsSingleSender 19 { 20 #region 變量 21 /// <summary> 22 /// appId 23 /// </summary> 24 private int appId; 25 /// <summary> 26 /// appkey 27 /// </summary> 28 private string appkey; 29 /// <summary> 30 /// url 31 /// </summary> 32 private string url = "https://yun.tim.qq.com/v5/tlssmssvr/sendsms"; 33 /// <summary> 34 /// util 35 /// </summary> 36 private SmsSenderUtil util = new SmsSenderUtil(); 37 #endregion 38 39 #region 構造 40 /// <summary> 41 /// 構造函數 42 /// </summary> 43 /// <param name="sdkappid"></param> 44 /// <param name="appkey"></param> 45 public SmsSingleSender(int sdkappid, string appkey) 46 { 47 this.appId = sdkappid; 48 this.appkey = appkey; 49 } 50 #endregion 51 52 #region 普通單發短信接口 53 /// <summary> 54 /// 普通單發短信接口,明確指定內容,如果有多個簽名,請在內容中以【】的方式添加到信息內容中,否則系統將使用默認簽名 55 /// </summary> 56 /// <param name="type">短信類型,0 為普通短信,1 營銷短信</param> 57 /// <param name="phoneNumber">不帶國家碼的手機號</param> 58 /// <param name="msg">信息內容,必須與申請的模板格式一致,否則將返回錯誤</param> 59 /// <param name="extend">短信碼號擴展號,格式為純數字串,其他格式無效。默認沒有開通</param> 60 /// <param name="ext">服務端原樣返回的參數,可填空</param> 61 /// <returns>SmsSingleSenderResult</returns> 62 public SmsSingleSenderResult Send(SmsType type, string phoneNumber, string msg, string extend = "", string ext = "") 63 { 64 long random = util.GetRandom(); 65 long curTime = util.GetCurTime(); 66 67 // 按照協議組織 post 請求包體 68 JObject tel = new JObject(); 69 tel.Add("nationcode", SmsSenderUtil.nationCode); 70 tel.Add("mobile", phoneNumber); 71 JObject data = new JObject(); 72 data.Add("tel", tel); 73 data.Add("msg", msg); 74 data.Add("type", (int)type); 75 data.Add("sig", util.StrToHash(string.Format("appkey={0}&random={1}&time={2}&mobile={3}", appkey, random, curTime, phoneNumber))); 76 data.Add("time", curTime); 77 data.Add("extend", extend); 78 data.Add("ext", ext); 79 80 string wholeUrl = url + "?sdkappid=" + appId + "&random=" + random; 81 HttpWebRequest request = util.GetPostHttpConn(wholeUrl); 82 byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)); 83 request.ContentLength = requestData.Length; 84 Stream requestStream = request.GetRequestStream(); 85 requestStream.Write(requestData, 0, requestData.Length); 86 requestStream.Close(); 87 88 // 接收返回包 89 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 90 Stream responseStream = response.GetResponseStream(); 91 StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8")); 92 string responseStr = streamReader.ReadToEnd(); 93 streamReader.Close(); 94 responseStream.Close(); 95 SmsSingleSenderResult result; 96 if (HttpStatusCode.OK == response.StatusCode) 97 { 98 result = util.ResponseStrToSingleSenderResult(responseStr); 99 } 100 else 101 { 102 result = new SmsSingleSenderResult(); 103 result.result = -1; 104 result.errmsg = "http error " + response.StatusCode + " " + responseStr; 105 } 106 return result; 107 } 108 #endregion 109 110 #region 指定模板單發 111 /// <summary> 112 /// 指定模板單發 113 /// </summary> 114 /// <param name="phoneNumber">不帶國家碼的手機號</param> 115 /// <param name="templId">模板 id</param> 116 /// <param name="templParams">模板參數列表,如模板 {1}...{2}...{3},那么需要帶三個參數</param> 117 /// <param name="sign">短信簽名,如果使用默認簽名,該字段可缺省</param> 118 /// <param name="extend">擴展碼,可填空</param> 119 /// <param name="ext">服務端原樣返回的參數,可填空</param> 120 /// <returns>SmsSingleSenderResult</returns> 121 public SmsSingleSenderResult SendWithParam(string phoneNumber, int templId, List<string> templParams, string sign = "", string extend = "", string ext = "") 122 { 123 long random = util.GetRandom(); 124 long curTime = util.GetCurTime(); 125 126 // 按照協議組織 post 請求包體 127 JObject tel = new JObject(); 128 tel.Add("nationcode", SmsSenderUtil.nationCode); 129 tel.Add("mobile", phoneNumber); 130 JObject data = new JObject(); 131 data.Add("tel", tel); 132 data.Add("sig", util.CalculateSigForTempl(appkey, random, curTime, phoneNumber)); 133 data.Add("tpl_id", templId); 134 data.Add("params", util.SmsParamsToJSONArray(templParams)); 135 data.Add("sign", sign); 136 data.Add("time", curTime); 137 data.Add("extend", extend); 138 data.Add("ext", ext); 139 140 string wholeUrl = url + "?sdkappid=" + appId + "&random=" + random; 141 HttpWebRequest request = util.GetPostHttpConn(wholeUrl); 142 byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)); 143 request.ContentLength = requestData.Length; 144 Stream requestStream = request.GetRequestStream(); 145 requestStream.Write(requestData, 0, requestData.Length); 146 requestStream.Close(); 147 148 // 接收返回包 149 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 150 Stream responseStream = response.GetResponseStream(); 151 StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8")); 152 string responseStr = streamReader.ReadToEnd(); 153 streamReader.Close(); 154 responseStream.Close(); 155 SmsSingleSenderResult result; 156 if (HttpStatusCode.OK == response.StatusCode) 157 { 158 result = util.ResponseStrToSingleSenderResult(responseStr); 159 } 160 else 161 { 162 result = new SmsSingleSenderResult(); 163 result.result = -1; 164 result.errmsg = "http error " + response.StatusCode + " " + responseStr; 165 } 166 return result; 167 } 168 #endregion 169 } 170 #endregion 171 172 #region 單發結果 173 /// <summary> 174 /// 單發結果 175 /// </summary> 176 class SmsSingleSenderResult 177 { 178 /// <summary> 179 /// 錯誤碼,0 表示成功(計費依據),非 0 表示失敗 180 /// </summary> 181 public int result { set; get; } 182 /// <summary> 183 /// 錯誤消息,result 非 0 時的具體錯誤信息 184 /// </summary> 185 public string errmsg { set; get; } 186 /// <summary> 187 /// 用戶的 session 內容,騰訊 server 回包中會原樣返回 188 /// </summary> 189 public string ext { set; get; } 190 /// <summary> 191 /// 本次發送標識 id,標識一次短信下發記錄 192 /// </summary> 193 public string sid { set; get; } 194 /// <summary> 195 /// 短信計費的條數 196 /// </summary> 197 public int fee { set; get; } 198 /// <summary> 199 /// ToString() 200 /// </summary> 201 /// <returns></returns> 202 public override string ToString() 203 { 204 return string.Format("SmsSingleSenderResult\nresult {0}\nerrMsg {1}\next {2}\nsid {3}\nfee {4}", result, errmsg, ext, sid, fee); 205 } 206 } 207 #endregion 208 209 #region 群發 210 /// <summary> 211 /// 群發 212 /// </summary> 213 class SmsMultiSender 214 { 215 #region 變量 216 /// <summary> 217 /// appId 218 /// </summary> 219 private int appId; 220 /// <summary> 221 /// appkey 222 /// </summary> 223 private string appkey; 224 /// <summary> 225 /// url 226 /// </summary> 227 private string url = "https://yun.tim.qq.com/v5/tlssmssvr/sendmultisms2"; 228 /// <summary> 229 /// util 230 /// </summary> 231 private SmsSenderUtil util = new SmsSenderUtil(); 232 #endregion 233 234 #region 構造 235 /// <summary> 236 /// 構造 237 /// </summary> 238 /// <param name="sdkappid"></param> 239 /// <param name="appkey"></param> 240 public SmsMultiSender(int sdkappid, string appkey) 241 { 242 this.appId = sdkappid; 243 this.appkey = appkey; 244 } 245 #endregion 246 247 #region 普通群發短信接口 248 /// <summary> 249 /// 普通群發短信接口,明確指定內容,如果有多個簽名,請在內容中以【】的方式添加到信息內容中,否則系統將使用默認簽名 250 ///【注意】 海外短信無群發功能 251 /// </summary> 252 /// <param name="type">短信類型,0 為普通短信,1 營銷短信</param> 253 /// <param name="nationCode"></param> 254 /// <param name="phoneNumbers">不帶國家碼的手機號列表</param> 255 /// <param name="msg">信息內容,必須與申請的模板格式一致,否則將返回錯誤</param> 256 /// <param name="extend">擴展碼,可填空</param> 257 /// <param name="ext">服務端原樣返回的參數,可填空</param> 258 /// <returns>SmsMultiSenderResult</returns> 259 public SmsMultiSenderResult Send(SmsType type, List<string> phoneNumbers, string msg, string extend = "", string ext = "") 260 { 261 long random = util.GetRandom(); 262 long curTime = util.GetCurTime(); 263 264 // 按照協議組織 post 請求包體 265 JObject data = new JObject(); 266 data.Add("tel", util.PhoneNumbersToJSONArray(SmsSenderUtil.nationCode, phoneNumbers)); 267 data.Add("type", (int)type); 268 data.Add("msg", msg); 269 data.Add("sig", util.CalculateSig(appkey, random, curTime, phoneNumbers)); 270 data.Add("time", curTime); 271 data.Add("extend", extend); 272 data.Add("ext", ext); 273 274 string wholeUrl = url + "?sdkappid=" + appId + "&random=" + random; 275 HttpWebRequest request = util.GetPostHttpConn(wholeUrl); 276 byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)); 277 request.ContentLength = requestData.Length; 278 Stream requestStream = request.GetRequestStream(); 279 requestStream.Write(requestData, 0, requestData.Length); 280 requestStream.Close(); 281 282 // 接收返回包 283 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 284 Stream responseStream = response.GetResponseStream(); 285 StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8")); 286 string responseStr = streamReader.ReadToEnd(); 287 streamReader.Close(); 288 responseStream.Close(); 289 SmsMultiSenderResult result; 290 if (HttpStatusCode.OK == response.StatusCode) 291 { 292 result = util.ResponseStrToMultiSenderResult(responseStr); 293 } 294 else 295 { 296 result = new SmsMultiSenderResult(); 297 result.result = -1; 298 result.errmsg = "http error " + response.StatusCode + " " + responseStr; 299 } 300 return result; 301 } 302 #endregion 303 304 #region 指定模板群發 305 /// <summary> 306 /// 指定模板群發 307 /// 【注意】海外短信無群發功能 308 /// </summary> 309 /// <param name="phoneNumbers">不帶國家碼的手機號列表</param> 310 /// <param name="templId"> 模板 id</param> 311 /// <param name="templParams">模板參數列表</param> 312 /// <param name="sign"> 簽名,如果填空,系統會使用默認簽名</param> 313 /// <param name="extend">擴展碼,可以填空</param> 314 /// <param name="ext">服務端原樣返回的參數,可以填空</param> 315 /// <returns> SmsMultiSenderResult</returns> 316 public SmsMultiSenderResult SendWithParam(List<string> phoneNumbers, int templId, List<string> templParams, string sign = "", string extend = "", string ext = "") 317 { 318 long random = util.GetRandom(); 319 long curTime = util.GetCurTime(); 320 321 // 按照協議組織 post 請求包體 322 JObject data = new JObject(); 323 data.Add("tel", util.PhoneNumbersToJSONArray(SmsSenderUtil.nationCode, phoneNumbers)); 324 data.Add("sig", util.CalculateSigForTempl(appkey, random, curTime, phoneNumbers)); 325 data.Add("tpl_id", templId); 326 data.Add("params", util.SmsParamsToJSONArray(templParams)); 327 data.Add("sign", sign); 328 data.Add("time", curTime); 329 data.Add("extend", extend); 330 data.Add("ext", ext); 331 332 string wholeUrl = url + "?sdkappid=" + appId + "&random=" + random; 333 HttpWebRequest request = util.GetPostHttpConn(wholeUrl); 334 byte[] requestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(data)); 335 request.ContentLength = requestData.Length; 336 Stream requestStream = request.GetRequestStream(); 337 requestStream.Write(requestData, 0, requestData.Length); 338 requestStream.Close(); 339 340 // 接收返回包 341 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 342 Stream responseStream = response.GetResponseStream(); 343 StreamReader streamReader = new StreamReader(responseStream, Encoding.GetEncoding("utf-8")); 344 string responseStr = streamReader.ReadToEnd(); 345 streamReader.Close(); 346 responseStream.Close(); 347 SmsMultiSenderResult result; 348 if (HttpStatusCode.OK == response.StatusCode) 349 { 350 result = util.ResponseStrToMultiSenderResult(responseStr); 351 } 352 else 353 { 354 result = new SmsMultiSenderResult(); 355 result.result = -1; 356 result.errmsg = "http error " + response.StatusCode + " " + responseStr; 357 } 358 return result; 359 } 360 #endregion 361 } 362 #endregion 363 364 #region 群發結果 365 /// <summary> 366 /// 群發結果 367 /// </summary> 368 class SmsMultiSenderResult 369 { 370 public class Detail 371 { 372 /// <summary> 373 /// 錯誤碼,0 表示成功(計費依據),非 0 表示失敗 374 /// </summary> 375 public int result { get; set; } 376 /// <summary> 377 /// 錯誤消息,result 非 0 時的具體錯誤信息 378 /// </summary> 379 public string errmsg { get; set; } 380 /// <summary> 381 /// 手機號碼 382 /// </summary> 383 public string mobile { get; set; } 384 /// <summary> 385 /// 國家碼 386 /// </summary> 387 public string nationcode { get; set; } 388 /// <summary> 389 /// 本次發送標識 id,標識一次短信下發記錄 390 /// </summary> 391 public string sid { get; set; } 392 /// <summary> 393 /// 短信計費的條數 394 /// </summary> 395 public int fee { get; set; } 396 /// <summary> 397 /// ToString() 398 /// </summary> 399 /// <returns></returns> 400 public override string ToString() 401 { 402 return string.Format( 403 "\tDetail result {0} errmsg {1} mobile {2} nationcode {3} sid {4} fee {5}", 404 result, errmsg, mobile, nationcode, sid, fee); 405 } 406 } 407 408 public int result; 409 public string errmsg = ""; 410 public string ext = ""; 411 public IList<Detail> detail; 412 413 public override string ToString() 414 { 415 if (null != detail) 416 { 417 return String.Format( 418 "SmsMultiSenderResult\nresult {0}\nerrmsg {1}\next {2}\ndetail:\n{3}", 419 result, errmsg, ext, String.Join("\n", detail)); 420 } 421 else 422 { 423 return String.Format( 424 "SmsMultiSenderResult\nresult {0}\nerrmsg {1}\next {2}\n", 425 result, errmsg, ext); 426 } 427 } 428 } 429 #endregion 430 431 #region 公共類 432 /// <summary> 433 /// 公共類 434 /// </summary> 435 class SmsSenderUtil 436 { 437 /// <summary> 438 /// 國家碼 439 /// </summary> 440 public static string nationCode = "86"; 441 /// <summary> 442 /// 隨機數生成器 443 /// </summary> 444 private Random random = new Random(); 445 446 #region GetPostHttpConn 447 /// <summary> 448 /// GetPostHttpConn 449 /// </summary> 450 /// <param name="url"></param> 451 /// <returns></returns> 452 public HttpWebRequest GetPostHttpConn(string url) 453 { 454 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); 455 request.Method = "POST"; 456 request.ContentType = "application/x-www-form-urlencoded"; 457 return request; 458 } 459 #endregion 460 461 #region 生成隨機數 462 /// <summary> 463 /// 生成隨機數 464 /// </summary> 465 /// <returns></returns> 466 public long GetRandom() 467 { 468 return random.Next(999999) % 900000 + 100000; 469 } 470 #endregion 471 472 #region 獲取請求發起時間 473 /// <summary> 474 /// 獲取請求發起時間, 475 /// unix 時間戳(單位:秒),如果和系統時間相差超過 10 分鍾則會返回失敗 476 /// </summary> 477 /// <returns></returns> 478 public long GetCurTime() 479 { 480 Int32 unixTimestamp = (Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; 481 return unixTimestamp; 482 } 483 #endregion 484 485 #region 字符串轉SHA256 486 /// <summary> 487 /// 字符串轉SHA256 488 /// </summary> 489 /// <param name="str"></param> 490 /// <returns></returns> 491 public string StrToHash(string str) 492 { 493 SHA256 sha256 = SHA256Managed.Create(); 494 byte[] resultByteArray = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(str)); 495 return ByteArrayToHex(resultByteArray); 496 } 497 498 /// <summary> 499 /// 將二進制的數值轉換為 16 進制字符串,如 "abc" => "616263" 500 /// </summary> 501 /// <param name="byteArray"></param> 502 /// <returns></returns> 503 private static string ByteArrayToHex(byte[] byteArray) 504 { 505 string returnStr = ""; 506 if (byteArray != null) 507 { 508 for (int i = 0; i < byteArray.Length; i++) 509 { 510 returnStr += byteArray[i].ToString("x2"); 511 } 512 } 513 return returnStr; 514 } 515 #endregion 516 517 #region 將單發回包解析成結果對象 518 /// <summary> 519 /// 將單發回包解析成結果對象 520 /// </summary> 521 /// <param name="str"></param> 522 /// <returns></returns> 523 public SmsSingleSenderResult ResponseStrToSingleSenderResult(string str) 524 { 525 SmsSingleSenderResult result = JsonConvert.DeserializeObject<SmsSingleSenderResult>(str); 526 return result; 527 } 528 #endregion 529 530 #region 將群發回包解析成結果對象 531 /// <summary> 532 /// 將群發回包解析成結果對象 533 /// </summary> 534 /// <param name="str"></param> 535 /// <returns></returns> 536 public SmsMultiSenderResult ResponseStrToMultiSenderResult(string str) 537 { 538 SmsMultiSenderResult result = JsonConvert.DeserializeObject<SmsMultiSenderResult>(str); 539 return result; 540 } 541 #endregion 542 543 #region List<string>轉JArray 544 /// <summary> 545 /// List<string>轉JArray 546 /// </summary> 547 /// <param name="templParams"></param> 548 /// <returns></returns> 549 public JArray SmsParamsToJSONArray(List<string> templParams) 550 { 551 JArray smsParams = new JArray(); 552 foreach (string templParamsElement in templParams) 553 { 554 smsParams.Add(templParamsElement); 555 } 556 return smsParams; 557 } 558 #endregion 559 560 #region PhoneNumbersToJSONArray 561 /// <summary> 562 /// PhoneNumbersToJSONArray 563 /// </summary> 564 /// <param name="nationCode"></param> 565 /// <param name="phoneNumbers"></param> 566 /// <returns></returns> 567 public JArray PhoneNumbersToJSONArray(string nationCode, List<string> phoneNumbers) 568 { 569 JArray tel = new JArray(); 570 int i = 0; 571 do 572 { 573 JObject telElement = new JObject(); 574 telElement.Add("nationcode", nationCode); 575 telElement.Add("mobile", phoneNumbers.ElementAt(i)); 576 tel.Add(telElement); 577 } while (++i < phoneNumbers.Count); 578 return tel; 579 } 580 #endregion 581 582 #region 計算App憑證 583 /* 584 "sig" 字段根據公式 sha256(appkey=$appkey&random=$random&time=$time&mobile=$mobile)生成 585 */ 586 public string CalculateSigForTempl(string appkey, long random, long curTime, List<string> phoneNumbers) 587 { 588 string phoneNumbersString = phoneNumbers.ElementAt(0); 589 for (int i = 1; i < phoneNumbers.Count; i++) 590 { 591 phoneNumbersString += "," + phoneNumbers.ElementAt(i); 592 } 593 return StrToHash(String.Format( 594 "appkey={0}&random={1}&time={2}&mobile={3}", 595 appkey, random, curTime, phoneNumbersString)); 596 } 597 598 public string CalculateSigForTempl(string appkey, long random, long curTime, string phoneNumber) 599 { 600 List<string> phoneNumbers = new List<string>(); 601 phoneNumbers.Add(phoneNumber); 602 return CalculateSigForTempl(appkey, random, curTime, phoneNumbers); 603 } 604 605 public string CalculateSig(string appkey, long random, long curTime, List<string> phoneNumbers) 606 { 607 string phoneNumbersString = phoneNumbers.ElementAt(0); 608 for (int i = 1; i < phoneNumbers.Count; i++) 609 { 610 phoneNumbersString += "," + phoneNumbers.ElementAt(i); 611 } 612 return StrToHash(String.Format( 613 "appkey={0}&random={1}&time={2}&mobile={3}", 614 appkey, random, curTime, phoneNumbersString)); 615 } 616 #endregion 617 } 618 #endregion 619 }
demo下載路徑為:https://files.cnblogs.com/files/LikeHeart/ConsoleTest.zip