1use crate::{
9 now_time, rand_str,
10 sms_lib::{phone_numbers_check, response_check, SendStatus},
11 BranchSendNotifyResult, SendNotifyItem, SendNotifyStatus,
12};
13use hmac::{Hmac, Mac};
14
15use chrono::{DateTime, NaiveDateTime, Utc};
16use reqwest::{
17 header::{HeaderMap, HeaderValue},
18 Client, Method,
19};
20use serde_json::json;
21use sha2::{Digest, Sha256};
22use std::collections::HashMap;
23use tracing::debug;
24
25use super::{
26 response_msg, BranchSendDetailResult, BranchSendResult, SendDetailItem, SendError,
27 SendResultItem,
28};
29pub struct AliSms {}
31
32impl AliSms {
33 pub fn send_notify_output(res: &Result<(), String>) -> String {
34 match res {
35 Ok(_) => {
36 json!({
37 "code" : 0,
38 "msg" : "接收成功"
39 })
40 }
41 Err(err) => {
42 json!({
43 "code" : 400,
44 "msg" : err
45 })
46 }
47 }
48 .to_string()
49 }
50 pub fn send_notify_parse(notify_data: &str) -> BranchSendNotifyResult {
52 let items = gjson::parse(notify_data);
67 let mut out = Vec::with_capacity(items.array().len());
68 for tmp in items.array() {
69 let send_time = chrono::NaiveDateTime::parse_from_str(
70 &tmp.get("send_time").to_string(),
71 "%Y-%m-%d %H:%M:%S",
72 )
73 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
74 .ok();
75 let receive_time = chrono::NaiveDateTime::parse_from_str(
76 &tmp.get("report_time").to_string(),
77 "%Y-%m-%d %H:%M:%S",
78 )
79 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
80 .ok();
81
82 out.push(SendNotifyItem {
83 status: if tmp.get("success").bool() {
84 SendNotifyStatus::Completed
85 } else {
86 SendNotifyStatus::Failed
87 },
88 message: tmp.get("err_msg").to_string(),
89 send_time,
90 receive_time,
91 code: tmp.get("err_code").to_string(),
92 send_id: tmp.get("biz_id").to_string(),
93 mobile: Some(tmp.get("phone_number").to_string()),
94 });
95 }
96 Ok(out)
97 }
98 pub async fn send_detail(
99 client: Client,
100 access_key_id: &str,
101 access_secret: &str,
102 send_id: &str,
103 mobile: &str,
104 send_date: &str,
105 ) -> BranchSendDetailResult {
106 let mut params = HashMap::new();
107 params.insert("PhoneNumber", mobile);
108 params.insert("BizId", send_id);
109 params.insert("SendDate", send_date);
110 params.insert("PageSize", "1");
111 params.insert("CurrentPage", "1");
112 params.insert("Version", "2017-05-25");
113 params.insert("Action", "QuerySendDetails");
114
115 let res = Self::build_request(
116 client,
117 Method::POST,
118 "dysmsapi.aliyuncs.com",
119 "/",
120 "",
121 "QuerySendDetails",
122 ¶ms,
123 access_key_id,
124 access_secret,
125 )
126 .await?;
127 if gjson::get(&res, "Code").str() == "OK" {
128 let status =
129 gjson::get(&res, "SmsSendDetailDTOs.SmsSendDetailDTO.0.SendStatus").to_string();
130 let send_time = chrono::NaiveDateTime::parse_from_str(
131 &gjson::get(&res, "SmsSendDetailDTOs.SmsSendDetailDTO.0.SendDate").to_string(),
132 "%Y-%m-%d %H:%M:%S",
133 )
134 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
135 .ok();
136 let receive_time = chrono::NaiveDateTime::parse_from_str(
137 &gjson::get(&res, "SmsSendDetailDTOs.SmsSendDetailDTO.0.ReceiveDate").to_string(),
138 "%Y-%m-%d %H:%M:%S",
139 )
140 .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
141 .ok();
142 return Ok(vec![SendDetailItem {
144 status: if status.as_str() == "2" {
145 SendNotifyStatus::Failed
146 } else if status.as_str() == "3" {
147 SendNotifyStatus::Completed
148 } else {
149 SendNotifyStatus::Progress
150 },
151 message: gjson::get(&res, "SmsSendDetailDTOs.SmsSendDetailDTO.0.Content")
152 .to_string(),
153 send_time,
154 receive_time,
155 code: gjson::get(&res, "SmsSendDetailDTOs.SmsSendDetailDTO.0.ErrCode").to_string(),
156 send_id: send_id.to_string(),
157 mobile: Some(mobile.to_string()),
158 }]);
159 }
160 Err(response_msg(&res, &["Message"]))
161 }
162 pub fn branch_limit() -> u16 {
163 100
164 }
165 #[allow(clippy::too_many_arguments)]
166 pub async fn branch_send(
167 client: Client,
168 region: &str, access_key_id: &str,
170 access_secret: &str,
171 sign_name: &str,
172 template_code: &str,
173 template_param: &str,
174 phone_numbers: &[&str],
175 out_id: &str,
176 ip: &str,
177 ) -> BranchSendResult {
178 let phone_numbers = phone_numbers_check(phone_numbers)?;
179 let mut params = HashMap::new();
180 let mobile = json!(phone_numbers).to_string();
181 if !region.is_empty() {
182 params.insert("RegionId", region);
183 }
184 let sign_name = json!(vec![sign_name; phone_numbers.len()]).to_string();
185 let mut param_data = vec![template_param; phone_numbers.len()].join(",");
186 param_data = format!("[{}]", param_data);
187
188 params.insert("Version", "2017-05-25");
196 params.insert("PhoneNumberJson", mobile.as_str());
197 params.insert("SignNameJson", sign_name.as_str());
198 params.insert("TemplateCode", template_code);
199 params.insert("TemplateParamJson", param_data.as_str());
200 params.insert("Action", "SendBatchSms");
201 if !out_id.is_empty() {
202 params.insert("OutId", out_id);
203 }
204
205 if !ip.is_empty() {
206 params.insert("SourceIp", ip);
207 }
208
209 let res = Self::build_request(
210 client,
211 Method::POST,
212 "dysmsapi.aliyuncs.com",
213 "/",
214 "",
215 "SendBatchSms",
216 ¶ms,
217 access_key_id,
218 access_secret,
219 )
220 .await
221 .map_err(SendError::Next)?;
222
223 if gjson::get(&res, "Code").str() == "OK" {
226 return Ok(phone_numbers
227 .iter()
228 .map(|e| SendResultItem {
229 mobile: e.to_string(),
230 status: SendStatus::Progress,
231 message: gjson::get(&res, "Message").to_string(),
232 send_id: gjson::get(&res, "BizId").to_string(),
233 })
234 .collect());
235 }
236 Err(SendError::Next(response_msg(&res, &["Message"])))
237 }
238 #[allow(clippy::too_many_arguments)]
239 pub async fn build_request(
240 client: Client,
241 method: Method,
242 host: &str,
243 uri: &str,
244 query: &str,
245 action: &str,
246 params: &HashMap<&str, &str>,
247 secret_id: &str,
248 secret_key: &str,
249 ) -> Result<String, String> {
250 let now_time = now_time().unwrap_or_default();
251
252 let req_json =
253 serde_urlencoded::to_string(params).map_err(|e| format!("urlencoded fail:{}", e))?;
254
255 let mut hasher = Sha256::new();
256 hasher.update(&req_json);
257 let json_hash = format!("{:x}", hasher.finalize()).to_lowercase();
258
259 let datetime = NaiveDateTime::from_timestamp_opt(now_time as i64, 0).unwrap_or_default();
260
261 let datetime_str = datetime.format("%Y-%m-%dT%H:%M:%SZ").to_string();
262 let rand_s = rand_str(32);
263
264 let mut headers = HeaderMap::new();
265 if let Ok(value) = HeaderValue::from_str(host) {
266 headers.insert("Host", value);
267 }
268 if let Ok(value) = HeaderValue::from_str(action) {
269 headers.insert("x-acs-action", value);
270 }
271 if let Ok(value) = HeaderValue::from_str("2017-05-25") {
272 headers.insert("x-acs-version", value);
273 }
274 if let Ok(value) = HeaderValue::from_str(&datetime_str) {
275 headers.insert("x-acs-date", value);
276 }
277 if let Ok(value) = HeaderValue::from_str(&rand_s) {
278 headers.insert("x-acs-signature-nonce", value);
279 }
280 if let Ok(value) = HeaderValue::from_str(&json_hash) {
281 headers.insert("x-acs-content-sha256", value);
282 }
283
284 let sign_header_arr = &[
285 "host",
286 "x-acs-action",
287 "x-acs-content-sha256",
288 "x-acs-date",
289 "x-acs-signature-nonce",
290 "x-acs-version",
291 ];
292 let sign_header = sign_header_arr.join(";");
293 let mut my_header = Vec::with_capacity(sign_header_arr.len());
294 for tmp in sign_header_arr {
295 if *tmp == "host" {
296 my_header.push(format!("{}:{}", tmp, host))
297 } else if let Some(tval) = headers.get(*tmp) {
298 my_header.push(format!("{}:{}", tmp, tval.to_str().unwrap_or_default()))
299 }
300 }
301
302 let sign = format!(
303 "{}\n{}\n{}\n{}\n\n{}\n{}",
304 method.as_str(),
305 uri,
306 query,
307 my_header.join("\n"),
308 sign_header,
309 json_hash
310 );
311
312 debug!("post json:{} header:{}", req_json, sign);
313
314 let mut hasher1 = Sha256::new();
315 hasher1.update(&sign);
316 let result = hasher1.finalize();
317
318 let string_to_sign = format!("ACS3-HMAC-SHA256\n{:x}", result);
319
320 debug!("sign body:{}", string_to_sign);
323
324 let mut mac = Hmac::<sha2::Sha256>::new_from_slice(secret_key.as_bytes())
325 .map_err(|e| format!("use data key on sha256 fail:{}", e))?;
326 mac.update(string_to_sign.as_bytes());
327 let signature = mac.finalize();
328
329 let data_sign = hex::encode(signature.into_bytes());
330 let authdata = format!(
332 "ACS3-HMAC-SHA256 Credential={},SignedHeaders={},Signature={}",
333 secret_id, sign_header, data_sign
334 );
335
336 debug!(" authorization:{}", authdata);
337
338 if let Ok(value) = HeaderValue::from_str(&authdata) {
339 headers.insert("Authorization", value);
340 }
341 let url = format!("https://{}", host);
342
343 let request = client.request(method, url).headers(headers).form(params);
349 let result = request
350 .send()
351 .await
352 .map_err(|e| format!("request send fail:{}", e))?;
353 let (_, res) = response_check(result, true).await?;
354 Ok(res)
355 }
356}