1 module appbase.gateway.sms; 2 3 import std.string; 4 import std.conv; 5 import std.base64; 6 import std.net.curl; 7 import std.uri; 8 import std.json; 9 import std.typecons : Tuple; 10 11 import crypto.aes; 12 import appbase.utils; 13 import appbase.utils.xml; 14 15 struct Sms1 16 { 17 /++ 18 The document of soap response: 19 <?xml version="1.0" encoding="utf-8"?> 20 <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 21 <soap:Body> 22 <SendSMS_2Response xmlns="http://tempuri.org/"> 23 <SendSMS_2Result> 24 <xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> 25 <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true"> 26 <xs:complexType> 27 <xs:choice minOccurs="0" maxOccurs="unbounded"> 28 <xs:element name="Table1"> 29 <xs:complexType> 30 <xs:sequence> 31 <xs:element name="Result" type="xs:long" minOccurs="0" /> 32 <xs:element name="Description" type="xs:string" minOccurs="0" /> 33 </xs:sequence> 34 </xs:complexType> 35 </xs:element> 36 </xs:choice> 37 </xs:complexType> 38 </xs:element> 39 </xs:schema> 40 <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1"> 41 <NewDataSet xmlns=""> 42 <Table1 diffgr:id="Table11" msdata:rowOrder="0" diffgr:hasChanges="inserted"> 43 <Result>-6</Result> 44 <Description>手机号码格式错误</Description> 45 </Table1> 46 </NewDataSet> 47 </diffgr:diffgram> 48 </SendSMS_2Result> 49 </SendSMS_2Response> 50 </soap:Body> 51 </soap:Envelope> 52 +/ 53 static Tuple!(int, string) send(const string gatewayUrl, const string account, const string key, const string mobile, const string content) 54 { 55 Tuple!(int, string) result; 56 57 const string timeStamp = dateTimeToString(now); 58 const string sign = MD5(account ~ timeStamp ~ content ~ mobile ~ timeStamp.replace("-", "").replace(":", "") ~ key); 59 const string request = format(`<?xml version="1.0" encoding="utf-8"?> 60 <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> 61 <soap12:Body> 62 <SendSMS_2 xmlns="http://tempuri.org/"> 63 <RegCode>%s</RegCode> 64 <TimeStamp>%s</TimeStamp> 65 <Sign>%s</Sign> 66 <Content>%s</Content> 67 <To>%s</To> 68 <SendTime>%s</SendTime> 69 </SendSMS_2> 70 </soap12:Body> 71 </soap12:Envelope>`, account, timeStamp, sign, content, mobile, timeStamp.replace(" ", "T")); 72 73 auto http = HTTP(gatewayUrl); 74 http.setPostData(request, "application/soap+xml; charset=utf-8"); 75 string response; 76 http.onReceive = (ubyte[] data) { response ~= cast(string)data; return data.length; }; 77 78 try 79 { 80 http.perform(); 81 } 82 catch (Exception e) 83 { 84 result[0] = -1001; 85 result[1] = e.msg; 86 87 return result; 88 } 89 90 if (response.empty) 91 { 92 result[0] = -1002; 93 result[1] = "Response empty."; 94 95 return result; 96 } 97 98 DocumentParser xml; 99 try 100 { 101 xml = new DocumentParser(response); 102 } 103 catch (Exception e) 104 { 105 result[0] = -1003; 106 result[1] = e.msg; 107 108 return result; 109 } 110 111 string ret_code, ret_description; 112 xml.onStartTag["Table1"] = (ElementParser xml) 113 { 114 xml.onEndTag["Result"] = (in Element e) { ret_code = e.text(); }; 115 xml.onEndTag["Description"] = (in Element e) { ret_description = e.text(); }; 116 xml.parse(); 117 }; 118 xml.parse(); 119 120 result[0] = as!int(ret_code, -1004); 121 result[1] = ret_description; 122 123 return result; 124 } 125 } 126 127 // for Sms1 128 unittest 129 { 130 import std.stdio : writeln; 131 writeln(Sms1.send("https://....", "account", "key", "135xxxxxxxx", "content")); 132 } 133 134 struct Sms2 135 { 136 static Tuple!(int, string) send(const string gatewayUrl, const string account, const string key, const string id, const string mobile, const string templateCode, const string templateParams, const string smsSign) 137 { 138 Tuple!(int, string) result; 139 140 string url = buildRequestUrl(gatewayUrl, account, key, id, mobile, templateCode, templateParams, smsSign); 141 142 string response; 143 try 144 { 145 response = cast(string)get(url); 146 } 147 catch (Exception e) 148 { 149 result[0] = -2; 150 result[1] = e.msg; 151 152 return result; 153 } 154 155 JSONValue json; 156 try 157 { 158 json = parseJSON(response); 159 } 160 catch (Exception e) 161 { 162 result[0] = -3; 163 result[1] = e.msg; 164 165 return result; 166 } 167 168 string res_code, res_message, res_timestamp, res_body, res_sign; 169 try 170 { 171 res_code = json["code"].str; 172 res_message = decodeComponent!dchar(json["message"].str.to!dstring).to!string; 173 res_timestamp = decodeComponent(json["timestamp"].str); 174 res_body = decodeComponent(json["body"].str); 175 res_sign = json["sign"].str; 176 } 177 catch (Exception e) 178 { 179 result[0] = -4; 180 result[1] = e.msg; 181 182 return result; 183 } 184 185 if (!checkResponseSign(key, res_code, res_message, res_body, res_timestamp, res_sign)) 186 { 187 result[0] = -5; 188 result[1] = "Received data signature error"; 189 190 return result; 191 } 192 193 if (res_code != "0") 194 { 195 result[0] = -6; 196 result[1] = res_message ~ ", code: " ~ res_code; 197 198 return result; 199 } 200 201 // res_body = decryptAES(res_body); 202 // JSONValue json_body; 203 204 // try 205 // { 206 // json_body = parseJSON(res_body); 207 // } 208 // catch (Exception e) 209 // { 210 // result[0] = -7; 211 // result[1] = e.msg; 212 213 // return result; 214 // } 215 216 result[0] = 0; 217 return result; 218 } 219 220 private: 221 222 static ubyte[] AES_IV = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; 223 224 static string encryptAES(const string key, const string data) 225 { 226 char[] bkey = cast(char[])strToByte_hex(MD5(key)); 227 return Base64.encode(AESUtils.encrypt!AES128(cast(ubyte[])data, bkey, AES_IV, PaddingMode.PKCS5)); 228 } 229 230 static string decryptAES(const string key, const string data) 231 { 232 char[] bkey = cast(char[])strToByte_hex(MD5(key)); 233 return cast(string)AESUtils.decrypt!AES128(Base64.decode(data), bkey, AES_IV, PaddingMode.PKCS5); 234 } 235 236 static string buildRequestUrl(const string gatewayUrl, const string account, const string key, const string id, const string mobile, const string templateCode, const string templateParams, const string smsSign) 237 { 238 string content = encryptAES(key, 239 format(`{ "action":"SendSms", "smsid":"%s", "phone":"%s", "signName":"%s", "templateCode":"%s", "templateParams":"{%s}", "sendTime":"", "version":"1.0.0" }`, 240 id, mobile, smsSign, templateCode, templateParams)); 241 string timestamp = dateTimeToString(now); 242 string sign = MD5(format("apiAccount=%s&body=%s×tamp=%s&apikey=%s", account, content, timestamp, key)); 243 244 return format("%s?apiAccount=%s&body=%s×tamp=%s&sign=%s", gatewayUrl, account, encodeComponent(content), encodeComponent(timestamp), sign); 245 } 246 247 static bool checkResponseSign(const string key, const string code, const string message, const string content, const string timestamp, const string sign) 248 { 249 string local_sign = MD5(format("body=%s&code=%s&message=%s×tamp=%s&apikey=%s", content, code, message, timestamp, key)); 250 return (local_sign == sign.toUpper()); 251 } 252 } 253 254 // for Sms2 255 unittest 256 { 257 import std.stdio : writeln; 258 writeln(Sms2.send("ipaddress", "account", "key", "1", "135xxxxxxxx", "TP001", "a,b", "【签名】")); 259 }