use chrono::{SecondsFormat, Utc};
use ring::hmac;
use std::collections::HashMap;
const SMS_VERSION: &'static str = "2017-05-25";
const SIGNATURE_VERSION: &'static str = "1.0";
const SIGNATURE_METHOD: &'static str = "HMAC-SHA1";
const FORMAT: &'static str = "json";
pub struct Aliyun<'a> {
access_key_id: &'a str,
access_secret: &'a str,
}
impl<'a> Aliyun<'a> {
pub fn new(access_key_id: &'a str, access_secret: &'a str) -> Self {
Self {
access_key_id,
access_secret,
}
}
pub async fn send_sms(
&self,
phone_numbers: &'a str,
sign_name: &'a str,
template_code: &'a str,
template_param: &'a str,
) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let mut params = HashMap::new();
params.insert("PhoneNumbers", phone_numbers);
params.insert("SignName", sign_name);
params.insert("TemplateCode", template_code);
params.insert("RegionId", "cn-hangzhou");
params.insert("TemplateParam", template_param);
params.insert("Action", "SendSms");
params.insert("Version", SMS_VERSION);
let canonicalize_query_string = self.canonicalize_query_string(¶ms);
let signature = self.signature(
format!(
"GET&%2F&{}",
urlencoding::encode(&canonicalize_query_string)
)
.as_bytes(),
);
let url = format!(
"https://dysmsapi.aliyuncs.com/?{}&Signature={}",
canonicalize_query_string, signature
);
let resp = reqwest::get(url)
.await?
.json::<HashMap<String, String>>()
.await?;
Ok(resp)
}
fn canonicalize_query_string(&self, params: &HashMap<&str, &'a str>) -> String {
let now = Utc::now();
let signature_method = now.timestamp_nanos().to_string();
let timestamp = now.to_rfc3339_opts(SecondsFormat::Secs, true);
let mut all_params = HashMap::new();
all_params.insert("AccessKeyId", self.access_key_id);
all_params.insert("Format", FORMAT);
all_params.insert("SignatureMethod", SIGNATURE_METHOD);
all_params.insert("SignatureNonce", signature_method.as_str());
all_params.insert("SignatureVersion", SIGNATURE_VERSION);
all_params.insert("Timestamp", timestamp.as_str());
params.iter().for_each(|(&k, &v)| {
all_params.insert(k, v);
});
let mut vec_arams: Vec<String> = all_params
.iter()
.map(|(&k, &v)| format!("{}={}", k, urlencoding::encode(&v)))
.collect();
vec_arams.sort();
vec_arams.join("&")
}
fn signature(&self, string_to_sign: &[u8]) -> String {
let key = hmac::Key::new(
hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
format!("{}&", self.access_secret).as_bytes(),
);
let sign = hmac::sign(&key, string_to_sign);
base64::encode(sign.as_ref())
}
}