Skip to main content

lsys_lib_sms/sms_lib/
sender_netease.rs

1use chrono::{DateTime, Utc};
2use reqwest::header::HeaderMap;
3use reqwest::header::HeaderValue;
4use reqwest::Client;
5use reqwest::Method;
6use reqwest::StatusCode;
7use ring::digest;
8use serde_json::json;
9use tracing::{debug, info, warn};
10
11use super::{BranchSendDetailResult, BranchSendResult, SendDetailItem, SendResultItem, SendStatus};
12
13use crate::SendNotifyStatus;
14use crate::{
15    sms_lib::{now_time, phone_numbers_check, rand_str, response_check, response_msg, SendError},
16    BranchSendNotifyResult, SendNotifyError, SendNotifyItem,
17};
18
19pub struct NeteaseSms {}
20impl NeteaseSms {
21    pub fn send_notify_output(res: &Result<(), String>) -> String {
22        match res {
23            Ok(_) => {
24                json!({
25                  "code" : 200,
26                  "msg" : "接收成功"
27                })
28            }
29            Err(err) => {
30                json!({
31                  "code" : 500,
32                  "msg" : err
33                })
34            }
35        }
36        .to_string()
37    }
38    //post json
39
40    pub fn send_notify_parse(
41        notify_data: &str,
42        // app_secret
43        // header -> MD5: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx //根据请求中的request body计算出来的MD5值
44        // header -> CurTime: 1440570500855    //当前UTC时间戳,从1970年1月1日0点0 分0 秒开始到现在的毫秒数(String)
45        // header -> CheckSum: 001511b8435e0b28044ca50a78e8f983026c5e01
46        sign_data: Option<(&str, &str, &str, &str)>,
47    ) -> BranchSendNotifyResult {
48        if let Some((app_secret, header_md5, header_curtime, header_checksum)) = sign_data {
49            let result = md5::compute(notify_data.as_bytes());
50            let hex_string = format!("{:x}", result);
51            debug!("body md5:{}", hex_string);
52            if hex_string.to_lowercase() != header_md5.to_lowercase() {
53                info!("md5 not match:{}!={}", hex_string, header_md5);
54                return Err(SendNotifyError::Sign(format!(
55                    "body md5 not match on :{}",
56                    hex_string
57                )));
58            }
59            let data = format!("{}{}{}", app_secret, hex_string, header_curtime);
60            let actual = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, data.as_bytes());
61            let sign = hex::encode(actual.as_ref());
62            if sign.to_lowercase() != header_checksum.to_lowercase() {
63                info!("md5 not match:{}!={}", hex_string, header_checksum);
64                return Err(SendNotifyError::Sign(format!("sign bad on :{}", sign)));
65            }
66        }
67        if gjson::get(notify_data, "eventType").to_string().as_str() != "11" {
68            return Err(SendNotifyError::Ignore);
69        }
70        // {
71        //     "mobile": "12345678945",
72        //     "sendid": "1490",
73        //     "result": "DELIVRD",
74        //     "sendTime": "2017-06-02 14:40:45",
75        //     "reportTime": "2017-06-06 10:40:30",
76        //     "spliced": "1",
77        //     "templateId": 1234
78        //     },
79        let items = gjson::get(notify_data, "objects");
80        let mut out = Vec::with_capacity(items.array().len());
81        for tmp in items.array() {
82            let send_time = chrono::NaiveDateTime::parse_from_str(
83                &tmp.get("sendTime").to_string(),
84                "%Y-%m-%d %H:%M:%S",
85            )
86            .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
87            .ok();
88            let receive_time = chrono::NaiveDateTime::parse_from_str(
89                &tmp.get("reportTime").to_string(),
90                "%Y-%m-%d %H:%M:%S",
91            )
92            .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
93            .ok();
94
95            out.push(SendNotifyItem {
96                status: if tmp.get("result").str() == "DELIVRD" {
97                    SendNotifyStatus::Completed
98                } else {
99                    SendNotifyStatus::Failed
100                },
101                message: tmp.get("result").to_string(),
102
103                send_time,
104                receive_time,
105                code: tmp.get("result").to_string(),
106                send_id: tmp.get("sendid").to_string(),
107                mobile: Some(tmp.get("mobile").to_string()),
108            });
109        }
110        Ok(out)
111    }
112    /// 构建签名字符串
113    fn signature(app_secret: &str) -> (String, String, String) {
114        let nowtime = now_time().unwrap_or_default();
115        let randstring = rand_str(32);
116        let check_sum = {
117            let tmp = format!("{}{}{}", app_secret, randstring, nowtime);
118            let sha1 = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, tmp.as_bytes());
119            hex::encode(sha1)
120        };
121        //println!("{}", check_sum);
122        (randstring, nowtime.to_string(), check_sum)
123    }
124
125    pub async fn send_detail(
126        client: Client,
127        app_key: &str,
128        app_secret: &str,
129        sendid: &str,
130    ) -> BranchSendDetailResult {
131        let (randstring, nowtime, check_sum) = Self::signature(app_secret);
132
133        let mut headers = HeaderMap::new();
134        if let Ok(value) = HeaderValue::from_str(app_key) {
135            headers.insert("AppKey", value);
136        }
137        if let Ok(value) = HeaderValue::from_str(&randstring) {
138            headers.insert("Nonce", value);
139        }
140        if let Ok(value) = HeaderValue::from_str(&nowtime) {
141            headers.insert("CurTime", value);
142        }
143        if let Ok(value) = HeaderValue::from_str(&check_sum) {
144            headers.insert("CheckSum", value);
145        }
146        if let Ok(value) = HeaderValue::from_str("application/x-www-form-urlencoded;charset=utf-8")
147        {
148            headers.insert("Content-Type", value);
149        }
150
151        let form_data = vec![("sendid", sendid)];
152
153        let request = client
154            .request(
155                Method::POST,
156                "https://api.netease.im/sms/querystatus.action",
157            )
158            .headers(headers)
159            .form(&form_data);
160        let result = request.send().await.map_err(|e| e.to_string())?;
161        let (status, res) = response_check(result, true).await?;
162        if status != StatusCode::OK {
163            warn!("sms response fail: {}", &res);
164            return Err(format!("http bad:{}", res));
165        }
166        let code = gjson::get(&res, "code").to_string();
167        if code.as_str() == "200" {
168            let items = gjson::get(&res, "obj");
169            let mut out = Vec::with_capacity(items.array().len());
170            for tmp in items.array() {
171                out.push(SendDetailItem {
172                    send_id: sendid.to_owned(),
173                    status: if tmp.get("status").i8() == 2 || tmp.get("status").i8() == 3 {
174                        SendNotifyStatus::Failed
175                    } else if tmp.get("status").i8() == 1 {
176                        SendNotifyStatus::Completed
177                    } else {
178                        SendNotifyStatus::Progress
179                    },
180                    message: match tmp.get("status").i8() {
181                        1 => "OK".to_owned(),
182                        3 => "spam".to_owned(),
183                        _ => "send fail".to_owned(),
184                    },
185                    send_time: Some(tmp.get("status").u64() / 1000),
186                    receive_time: None,
187                    code: code.to_string(),
188                    mobile: Some(tmp.get("mobile").to_string()),
189                });
190            }
191            return Ok(out);
192        }
193        Err(response_msg(&res, &["obj"]))
194    }
195    pub fn branch_limit() -> u16 {
196        100
197    }
198    //执行短信发送
199    pub async fn branch_send(
200        client: Client,
201        app_key: &str,
202        app_secret: &str,
203        template_id: &str,
204        template_arr: Option<Vec<String>>,
205        phone_numbers: &[&str],
206    ) -> BranchSendResult {
207        let phone_numbers = phone_numbers_check(phone_numbers)?;
208
209        let (randstring, nowtime, check_sum) = Self::signature(app_secret);
210
211        let mut headers = HeaderMap::new();
212        if let Ok(value) = HeaderValue::from_str(app_key) {
213            headers.insert("AppKey", value);
214        }
215        if let Ok(value) = HeaderValue::from_str(&randstring) {
216            headers.insert("Nonce", value);
217        }
218        if let Ok(value) = HeaderValue::from_str(&nowtime) {
219            headers.insert("CurTime", value);
220        }
221        if let Ok(value) = HeaderValue::from_str(&check_sum) {
222            headers.insert("CheckSum", value);
223        }
224        if let Ok(value) = HeaderValue::from_str("application/x-www-form-urlencoded;charset=utf-8")
225        {
226            headers.insert("Content-Type", value);
227        }
228
229        let mut form_data = vec![];
230        form_data.push(("templateid", template_id));
231        let mobile = json!(phone_numbers).to_string();
232        form_data.push(("mobiles", &mobile));
233        let ptmp = if let Some(params) = template_arr {
234            json!(params).to_string()
235        } else {
236            "".to_string()
237        };
238        if !ptmp.is_empty() {
239            form_data.push(("params", ptmp.as_str()));
240        }
241
242        let request = client
243            .request(
244                Method::POST,
245                "https://api.netease.im/sms/sendtemplate.action",
246            )
247            .headers(headers)
248            .form(&form_data);
249        let result = request
250            .send()
251            .await
252            .map_err(|e| SendError::Next(format!("request send fail:{}", e)))?;
253        let (status, res) = response_check(result, true)
254            .await
255            .map_err(SendError::Next)?;
256        if status != StatusCode::OK {
257            warn!("sms response fail: {}", &res);
258            return Err(SendError::Next(format!("http bad:{}", res)));
259        }
260
261        let code = gjson::get(&res, "code").to_string();
262        if code.as_str() == "200" {
263            return Ok(phone_numbers
264                .iter()
265                .map(|e| SendResultItem {
266                    mobile: e.to_string(),
267                    status: SendStatus::Progress,
268                    message: gjson::get(&res, "obj").to_string(),
269                    send_id: gjson::get(&res, "msg").to_string(),
270                })
271                .collect());
272        }
273        if code.as_str() == "412" || code.as_str() == "601" {
274            return Err(SendError::Finish(response_msg(&res, &["obj"])));
275        }
276        Err(SendError::Next(response_msg(&res, &["obj"])))
277    }
278}