笔者要使用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; } }