阿里云官方的skd(aliyun-net-sdk-core,aliyun-net-sdk-dysmsapi)在dnc中发送短信会出错,nuget上的包貌似也一样不管用。直接改下sdk当然也可以,但就发个短信,官方的sdk实在是有点繁杂,其实可以简单化,一个类就搞定。
1 using Newtonsoft.Json; 2 using System; 3 using System.Collections.Generic; 4 using System.Globalization; 5 using System.Net; 6 using System.Net.Http; 7 using System.Security.Cryptography; 8 using System.Text; 9 using System.Threading.Tasks; 10 using System.Web; 11 12 namespace Vino.Core.Communication.SMS 13 { 14 public class AliyunSmsSender : ISmsSender 15 { 16 private string RegionId = "cn-hangzhou"; 17 private string Version = "2017-05-25"; 18 private string Action = "SendSms"; 19 private string Format = "JSON"; 20 private string Domain = "dysmsapi.aliyuncs.com"; 21 22 private int MaxRetryNumber = 3; 23 private bool AutoRetry = true; 24 private const string SEPARATOR = "&"; 25 private int TimeoutInMilliSeconds = 100000; 26 27 private string AccessKeyId; 28 private string AccessKeySecret; 29 30 public AliyunSmsSender(string accessKeyId, string accessKeySecret) 31 { 32 this.AccessKeyId = accessKeyId; 33 this.AccessKeySecret = accessKeySecret; 34 } 35 36 /// <summary> 37 /// 发送短信 38 /// </summary> 39 public async Task<(bool success, string response)> Send(SmsObject sms) 40 { 41 var paramers = new Dictionary<string, string>(); 42 paramers.Add("PhoneNumbers", sms.Mobile); 43 paramers.Add("SignName", sms.Signature); 44 paramers.Add("TemplateCode", sms.TempletKey); 45 paramers.Add("TemplateParam", JsonConvert.SerializeObject(sms.Data)); 46 paramers.Add("OutId", sms.OutId); 47 paramers.Add("AccessKeyId", AccessKeyId); 48 49 try 50 { 51 string url = GetSignUrl(paramers, AccessKeySecret); 52 53 int retryTimes = 1; 54 var reply = await HttpGetAsync(url); 55 while (500 <= reply.StatusCode && AutoRetry && retryTimes < MaxRetryNumber) 56 { 57 url = GetSignUrl(paramers, AccessKeySecret); 58 reply = await HttpGetAsync(url); 59 retryTimes++; 60 } 61 62 if (!string.IsNullOrEmpty(reply.response)) 63 { 64 var res = JsonConvert.DeserializeObject<Dictionary<string, string>>(reply.response); 65 if (res != null && res.ContainsKey("Code") && "OK".Equals(res["Code"])) 66 { 67 return (true, response: reply.response); 68 } 69 } 70 71 return (false, response: reply.response); 72 } 73 catch (Exception ex) 74 { 75 return (false, response: ex.Message); 76 } 77 } 78 79 private string GetSignUrl(Dictionary<string, string> parameters, string accessSecret) 80 { 81 var imutableMap = new Dictionary<string, string>(parameters); 82 imutableMap.Add("Timestamp", FormatIso8601Date(DateTime.Now)); 83 imutableMap.Add("SignatureMethod", "HMAC-SHA1"); 84 imutableMap.Add("SignatureVersion", "1.0"); 85 imutableMap.Add("SignatureNonce", Guid.NewGuid().ToString()); 86 imutableMap.Add("Action", Action); 87 imutableMap.Add("Version", Version); 88 imutableMap.Add("Format", Format); 89 imutableMap.Add("RegionId", RegionId); 90 91 IDictionary<string, string> sortedDictionary = new SortedDictionary<string, string>(imutableMap, StringComparer.Ordinal); 92 StringBuilder canonicalizedQueryString = new StringBuilder(); 93 foreach (var p in sortedDictionary) 94 { 95 canonicalizedQueryString.Append("&") 96 .Append(PercentEncode(p.Key)).Append("=") 97 .Append(PercentEncode(p.Value)); 98 } 99 100 StringBuilder stringToSign = new StringBuilder(); 101 stringToSign.Append("GET"); 102 stringToSign.Append(SEPARATOR); 103 stringToSign.Append(PercentEncode("/")); 104 stringToSign.Append(SEPARATOR); 105 stringToSign.Append(PercentEncode(canonicalizedQueryString.ToString().Substring(1))); 106 107 string signature = SignString(stringToSign.ToString(), accessSecret + "&"); 108 109 imutableMap.Add("Signature", signature); 110 111 return ComposeUrl(Domain, imutableMap); 112 } 113 114 private static string FormatIso8601Date(DateTime date) 115 { 116 return date.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.CreateSpecificCulture("en-US")); 117 } 118 119 /// <summary> 120 /// 签名 121 /// </summary> 122 public static string SignString(string source, string accessSecret) 123 { 124 using (var algorithm = new HMACSHA1(Encoding.UTF8.GetBytes(accessSecret.ToCharArray()))) 125 { 126 return Convert.ToBase64String(algorithm.ComputeHash(Encoding.UTF8.GetBytes(source.ToCharArray()))); 127 } 128 } 129 130 private static string ComposeUrl(string endpoint, Dictionary<String, String> parameters) 131 { 132 StringBuilder urlBuilder = new StringBuilder(""); 133 urlBuilder.Append("http://").Append(endpoint); 134 if (-1 == urlBuilder.ToString().IndexOf("?")) 135 { 136 urlBuilder.Append("/?"); 137 } 138 string query = ConcatQueryString(parameters); 139 return urlBuilder.Append(query).ToString(); 140 } 141 142 private static string ConcatQueryString(Dictionary<string, string> parameters) 143 { 144 if (null == parameters) 145 { 146 return null; 147 } 148 StringBuilder sb = new StringBuilder(); 149 150 foreach (var entry in parameters) 151 { 152 String key = entry.Key; 153 String val = entry.Value; 154 155 sb.Append(HttpUtility.UrlEncode(key, Encoding.UTF8)); 156 if (val != null) 157 { 158 sb.Append("=").Append(HttpUtility.UrlEncode(val, Encoding.UTF8)); 159 } 160 sb.Append("&"); 161 } 162 163 int strIndex = sb.Length; 164 if (parameters.Count > 0) 165 sb.Remove(strIndex - 1, 1); 166 167 return sb.ToString(); 168 } 169 170 public static string PercentEncode(string value) 171 { 172 StringBuilder stringBuilder = new StringBuilder(); 173 string text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~"; 174 byte[] bytes = Encoding.GetEncoding("UTF-8").GetBytes(value); 175 foreach (char c in bytes) 176 { 177 if (text.IndexOf(c) >= 0) 178 { 179 stringBuilder.Append(c); 180 } 181 else 182 { 183 stringBuilder.Append("%").Append( 184 string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)c)); 185 } 186 } 187 return stringBuilder.ToString(); 188 } 189 190 private async Task<(int StatusCode, string response)> HttpGetAsync(string url) 191 { 192 HttpClientHandler handler = new HttpClientHandler(); 193 handler.Proxy = null; 194 handler.AutomaticDecompression = DecompressionMethods.GZip; 195 196 using (var http = new HttpClient(handler)) 197 { 198 http.Timeout = new TimeSpan(TimeSpan.TicksPerMillisecond * TimeoutInMilliSeconds); 199 HttpResponseMessage response = await http.GetAsync(url); 200 return ((int)response.StatusCode, await response.Content.ReadAsStringAsync()); 201 } 202 } 203 } 204 }
SmsObject.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 5 namespace Vino.Core.Communication.SMS 6 { 7 public class SmsObject 8 { 9 /// <summary> 10 /// 手机号 11 /// </summary> 12 public string Mobile { set; get; } 13 14 /// <summary> 15 /// 签名 16 /// </summary> 17 public string Signature { get; set; } 18 19 /// <summary> 20 /// 模板Key 21 /// </summary> 22 public string TempletKey { set; get; } 23 24 /// <summary> 25 /// 短信数据 26 /// </summary> 27 public IDictionary<string, string> Data { set; get; } 28 29 /// <summary> 30 /// 业务ID 31 /// </summary> 32 public string OutId { set; get; } 33 } 34 }
调用方法
1 IDictionary<string, string> data = new Dictionary<string, string>(); 2 data.Add("code", "111111"); 3 var sms = new SmsObject 4 { 5 Mobile = "18812345678", 6 Signature = "我的签名", 7 TempletKey = "模板ID", 8 Data = data, 9 OutId = "OutId" 10 }; 11 12 var res = await new AliyunSmsSender(accessKeyId, accessKeySecret).Send(sms); 13 Debug.WriteLine($"发送结果:{res.success}"); 14 Debug.WriteLine($"Response:{res.response}");