筆者要使用redis緩存一下請求到的憑據,所以需要在Cargo.toml裡面引入一些必要的庫
redis = { version = "0.26.1", features = ["tokio-comp"] }
reqwest = { version = "0.12.5" }
chrono = { version = "0.4.38" }
serde = { version = "1.0.204", features = ["derive"] }
serde_json = { version = "1.0.120" }
rand = { version = "0.8.5" }
sha1 = { version = "0.10.6" }
hex = { version = "0.4.3" }筆者事先在項目文档裡定義了幾個常量,比如網站的域名、appid、appsecret
整個文档的源碼
use reqwest::get;
use chrono::Utc;
use rand::Rng;
use redis::{AsyncCommands, RedisResult};
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_string};
use crate::conf::{DOMAIN_NAME, SOME_APPID, SOME_APPSECRET};
use crate::database::handler::redis;
use sha1::{Digest, Sha1};
#[derive(Serialize, Deserialize)]
struct JsApiTicket {
expire_time: i64,
jsapi_ticket: String,
}
#[derive(Serialize, Deserialize)]
struct AccessToken {
expire_time: i64,
access_token: String,
}
#[allow(dead_code)]
pub struct SignPackage {
pub appid: String,
pub nonce_str: String,
pub timestamp: i64,
pub url: String,
pub signature: String,
pub raw_string: String,
}
async fn sha1_encrypt(input: &str) -> String {
let mut hasher = Sha1::new();
hasher.update(input.as_bytes());
let result = hasher.finalize();
hex::encode(result)
}
pub async fn get_sign_package(request_uri: &str) -> SignPackage {
let jsapiticket = get_jsapiticket().await;
let url: &str = &format!("https://{}{}", DOMAIN_NAME, request_uri);
let timestamp: i64 = Utc::now().timestamp();
let nonce_str = create_nonce_str(16).await;
let raw_string: &str = &format!("jsapi_ticket={}&noncestr={}×tamp={}&url={}", jsapiticket, nonce_str, timestamp, url);
let signature = sha1_encrypt(raw_string).await;
return SignPackage {
appid: SOME_APPID.to_string(),
nonce_str,
timestamp,
url: url.to_string(),
signature,
raw_string: raw_string.to_string(),
};
}
async fn create_nonce_str(length: usize) -> String {
let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let mut rng = rand::thread_rng();
let mut str = String::new();
for _ in 0..length {
str.push(chars.chars().nth(rng.gen_range(0..chars.len())).unwrap());
}
str
}
pub async fn get_jsapiticket() -> String {
let mut conn = redis(1).await;
let data_r: RedisResult<String> = conn.get("JsapiTicket").await;
// let data_r = redis::cmd("GET").arg("JsapiTicket").query_async(&mut conn).await;
let ticket: JsApiTicket;
if let Err(_) = &data_r {
tracing::warn!("Redis GET Jsapi ticket failed.");
ticket = JsApiTicket {
expire_time: 0,
jsapi_ticket: "".to_string(),
}
} else {
let ticket_json = data_r.unwrap();
ticket = from_str(&ticket_json).unwrap();
}
let now: i64 = Utc::now().timestamp();
if ticket.expire_time == 0 || ticket.expire_time < now {
let token = get_access_token().await;
if "" == &token {
return "".to_string();
}
let resp = get(&format!("https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token={}", &token)).await;
if let Err(e) = &resp {
tracing::warn!("Request access token failed: {:?}", e);
return "".to_string();
} else {
let ticket_json = resp.unwrap().text().await.unwrap();
let ticket_r = from_str(&ticket_json);
let ticket = ticket_r.unwrap_or(JsApiTicket {
expire_time: 0,
jsapi_ticket: "".to_string(),
});
if ticket.expire_time != 0 {
let tmp_ticket = JsApiTicket {
expire_time: now + 7000,
jsapi_ticket: ticket.jsapi_ticket.clone(),
};
let ticket_json = to_string(&tmp_ticket).unwrap();
let r1: RedisResult<()> = conn.set("JsapiTicket", &ticket_json).await;
if let Err(_) = r1 {
tracing::warn!("Redis SET JsApi Ticket Failed.");
} else {
let _: RedisResult<()> = conn.expire("JsapiTicket", 7000).await;
}
// redis::cmd("SET").arg(&[]).query(&mut conn).await;
// redis::cmd("EXPIRE").arg(&["JsapiTicket"]).arg(7000).query(&mut conn).await;
return ticket.jsapi_ticket;
}
}
}
return ticket.jsapi_ticket;
}
pub async fn get_access_token() -> String {
let mut conn = redis(1).await;
let data_r: RedisResult<String> = redis::cmd("GET").arg("AccessToken").query_async(&mut conn).await;
let token: AccessToken;
if let Err(_) = &data_r {
tracing::warn!("Redis GET access token failed.");
token = AccessToken {
expire_time: 0,
access_token: "".to_string(),
}
} else {
let token_json = data_r.unwrap();
token = from_str(&token_json).unwrap();
}
let now: i64 = Utc::now().timestamp();
if token.expire_time == 0 || token.expire_time < now {
let resp = get(&format!("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}", SOME_APPID, SOME_APPSECRET)).await;
if let Err(_) = &resp {
return "".to_string();
} else {
let token_json = resp.unwrap().text().await.unwrap();
let token_r = from_str(&token_json);
let token = token_r.unwrap_or(AccessToken {
expire_time: 0,
access_token: "".to_string(),
});
if token.expire_time != 0 {
let tmp_token = AccessToken {
expire_time: now + 7000,
access_token: token.access_token.clone(),
};
let token_json = to_string(&tmp_token).unwrap();
let r1: RedisResult<()> = redis::cmd("SET").arg(&token_json).query_async(&mut conn).await;
if let Err(_) = r1 {
tracing::warn!("Redis SET access token Failed.");
} else {
let _: RedisResult<()> = redis::cmd("EXPIRE").arg(&token_json).arg(7000).query_async(&mut conn).await;
}
return token.access_token;
}
}
return "".to_string();
} else {
return token.access_token;
}
}