Skip to main content

lsys_lib_sms/sms_lib/
sender_cloopen.rs

1use base64::Engine;
2use chrono::{DateTime, NaiveDateTime, Utc};
3
4use reqwest::header::HeaderMap;
5use reqwest::header::HeaderValue;
6use reqwest::Client;
7use reqwest::Method;
8use reqwest::StatusCode;
9use serde_json::json;
10
11use tracing::warn;
12
13use super::{BranchSendResult, SendError, CUSTOM_ENGINE};
14use crate::BranchSendNotifyResult;
15use crate::SendNotifyError;
16use crate::SendNotifyItem;
17use crate::SendNotifyStatus;
18use crate::{
19    now_time, response_check, response_msg, sms_lib::phone_numbers_check, BranchSendDetailResult,
20    SendDetailItem, SendResultItem, SendStatus,
21};
22
23pub struct CloOpenSms {}
24
25impl CloOpenSms {
26    pub fn send_notify_output(res: &Result<(), String>) -> (StatusCode, String) {
27        match res {
28            Ok(_) => (StatusCode::OK, "".to_owned()),
29            Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_owned()),
30        }
31    }
32    //send_detail 跟  send_notify_parse 只有一个
33    //post json
34    pub fn send_notify_parse(notify_data: &str) -> BranchSendNotifyResult {
35        // {
36        //     "Request": {
37        //     "action": "SMSArrived",
38        //     "smsType": "1",
39        //     "apiVersion": "2013-12-26",
40        //     "content": "4121908f3d1b4edb9210f0eb4742f62c",
41        //     "fromNum": "13912345678",
42        //     "dateSent": "20130923010000",
43        //     "deliverCode": "DELIVRD",
44        //     "recvTime": "20130923010010",
45        //     "status": "0",
46        //     "reqId": "123",
47        //     "smsCount": "2",
48        //     "spCode": "10690876"
49        //     }
50        //     }
51        if gjson::get(notify_data, "Request.smsType")
52            .to_string()
53            .as_str()
54            != "1"
55        {
56            return Err(SendNotifyError::Ignore);
57        }
58        let tmp = gjson::get(notify_data, "Request");
59
60        let receive_time =
61            chrono::NaiveDateTime::parse_from_str(&tmp.get("recvTime").to_string(), "%Y%m%d%H%M%S")
62                .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
63                .ok();
64        let send_time =
65            chrono::NaiveDateTime::parse_from_str(&tmp.get("dateSent").to_string(), "%Y%m%d%H%M%S")
66                .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
67                .ok();
68
69        Ok(vec![SendNotifyItem {
70            send_id: tmp.get("content").to_string(),
71            status: if send_time.unwrap_or_default() > 0 {
72                if tmp.get("status").i8() == 0 {
73                    SendNotifyStatus::Completed
74                } else {
75                    SendNotifyStatus::Failed
76                }
77            } else {
78                SendNotifyStatus::Progress
79            },
80            message: tmp.get("deliverCode").to_string(),
81            send_time,
82            receive_time,
83            code: tmp.get("deliverCode").to_string(),
84            mobile: Some(tmp.get("fromNum").to_string()),
85        }])
86    }
87    /// 构建签名字符串
88    fn sig(account_sid: &str, account_token: &str, time: u64) -> String {
89        let datetime = NaiveDateTime::from_timestamp_opt(time as i64, 0).unwrap_or_default();
90        let datetime_str = datetime.format("%Y%m%d%H%M%S").to_string();
91        let sig_str = format!("{}{}{}", account_sid, account_token, datetime_str);
92        let result = md5::compute(sig_str);
93        format!("{:x}", result).to_uppercase()
94    }
95    /// 构建验证字符串
96    fn auth(account_sid: &str, time: u64) -> String {
97        let datetime = NaiveDateTime::from_timestamp_opt(time as i64, 0).unwrap_or_default();
98        let datetime_str = datetime.format("%Y%m%d%H%M%S").to_string();
99        let auth_str = format!("{}:{}", account_sid, datetime_str);
100        CUSTOM_ENGINE.encode(auth_str)
101    }
102
103    pub async fn send_detail(
104        client: Client,
105        account_sid: &str,
106        account_token: &str,
107        app_id: &str,
108    ) -> BranchSendDetailResult {
109        let ntime = now_time().unwrap_or_default();
110        let sig = Self::sig(account_sid, account_token, ntime);
111        let auth = Self::auth(account_sid, ntime);
112        // appId	String	必选	应用Id
113        // 	String	可选	0:上行短信数据 1:短信状态报告 缺省1
114        // 	String	可选	查询状态的数量。最大500,缺省100
115        let reqjson = json!({
116            "smsType": "1",
117            "count": "500",
118            "appId": app_id,
119        });
120
121        let mut headers = HeaderMap::new();
122
123        if let Ok(value) = HeaderValue::from_str("application/json;charset=utf-8") {
124            headers.insert("Content-Type", value);
125        }
126        if let Ok(value) = HeaderValue::from_str(auth.as_str()) {
127            headers.insert("Authorization", value);
128        }
129
130        let request = client
131            .request(
132                Method::POST,
133                format!(
134                    "https://app.cloopen.com:8883/2013-12-26/Accounts/{}/SMS/GetArrived?sig={}",
135                    account_sid, sig
136                ),
137            )
138            .headers(headers)
139            .body(reqjson.to_string());
140        let result = request.send().await.map_err(|e| e.to_string())?;
141        let (status, res) = response_check(result, true).await?;
142        // println!("{}", res);
143        if status != StatusCode::OK {
144            warn!("sms response fail: {}", &res);
145            return Err(format!("http bad:{}", res));
146        }
147        let code = gjson::get(&res, "statusCode").to_string();
148        if code.as_str() == "000000" {
149            let items = gjson::get(&res, "reports");
150            let mut out = Vec::with_capacity(items.array().len());
151            for tmp in items.array() {
152                let receive_time = chrono::NaiveDateTime::parse_from_str(
153                    &tmp.get("recvTime").to_string(),
154                    "%Y%m%d%H%M%S",
155                )
156                .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
157                .ok();
158                let send_time = chrono::NaiveDateTime::parse_from_str(
159                    &tmp.get("dateSent").to_string(),
160                    "%Y%m%d%H%M%S",
161                )
162                .map(|tmp| DateTime::<Utc>::from_naive_utc_and_offset(tmp, Utc).timestamp() as u64)
163                .ok();
164                out.push(SendDetailItem {
165                    send_id: tmp.get("content").to_string(),
166                    status: if send_time.unwrap_or_default() > 0 {
167                        if tmp.get("status").i8() == 0 {
168                            SendNotifyStatus::Completed
169                        } else {
170                            SendNotifyStatus::Failed
171                        }
172                    } else {
173                        SendNotifyStatus::Progress
174                    },
175                    message: match tmp.get("status").i8() {
176                        0 => "OK".to_owned(),
177                        _ => "send fail".to_owned(),
178                    },
179                    send_time,
180                    receive_time,
181                    code: tmp.get("status").to_string(),
182                    mobile: Some(tmp.get("fromNum").to_string()),
183                });
184            }
185            return Ok(out);
186        }
187        Err(response_msg(&res, &["statusMsg"]))
188    }
189    pub fn branch_limit() -> u16 {
190        200
191    }
192    //执行短信发送
193    pub async fn branch_send(
194        client: Client,
195        account_sid: &str,
196        account_token: &str,
197        app_id: &str,
198        template_id: &str,
199        template_arr: Option<Vec<String>>,
200        phone_numbers: &[&str],
201    ) -> BranchSendResult {
202        let phone_numbers = phone_numbers_check(phone_numbers)?;
203        let ntime = 1700447467; //now_time().unwrap_or_default();
204
205        let sig = Self::sig(account_sid, account_token, ntime);
206        let auth = Self::auth(account_sid, ntime);
207        let reqjson = json!({
208            "to": phone_numbers.join(","),
209            "templateId": template_id,
210            "appId": app_id,
211            "datas":template_arr
212        });
213
214        let mut headers = HeaderMap::new();
215        if let Ok(value) = HeaderValue::from_str("application/json") {
216            headers.insert("Accept", value);
217        }
218        if let Ok(value) = HeaderValue::from_str("application/json;charset=utf-8") {
219            headers.insert("Content-Type", value);
220        }
221        if let Ok(value) = HeaderValue::from_str(auth.as_str()) {
222            headers.insert("Authorization", value);
223        }
224
225        let url = format!(
226            "https://app.cloopen.com:8883/2013-12-26/Accounts/{}/SMS/TemplateSMS?sig={}",
227            account_sid, sig
228        );
229
230        // println!("{}-{}", url, auth);
231
232        let request = client
233            .request(Method::POST, url)
234            .headers(headers)
235            .body(reqjson.to_string());
236        let result = request
237            .send()
238            .await
239            .map_err(|e| SendError::Next(format!("request send fail:{}", e)))?;
240        let (status, res) = response_check(result, true)
241            .await
242            .map_err(SendError::Next)?;
243        if status != StatusCode::OK {
244            warn!("sms response fail: {}", &res);
245            return Err(SendError::Next(format!("http bad:{}", res)));
246        }
247        //  println!("{}", res);
248        let code = gjson::get(&res, "statusCode").to_string();
249        if code.as_str() == "000000" {
250            // {"statusCode":"000000","templateSMS":{"dateCreated":"20130201155306","smsMessageSid":" ff8080813c373cab013c94b0f0512345"}}
251            return Ok(phone_numbers
252                .iter()
253                .map(|e| SendResultItem {
254                    mobile: e.to_string(),
255                    status: SendStatus::Progress,
256                    message: gjson::get(&res, "statusMsg").to_string(),
257                    send_id: gjson::get(&res, "templateSMS.smsMessageSid").to_string(),
258                })
259                .collect());
260        }
261        Err(SendError::Next(response_msg(&res, &["statusMsg"])))
262    }
263}