async_wechat/official_account/
token.rs

1use deadpool_redis::redis::cmd;
2use serde::{Deserialize, Serialize};
3use url::{Url, form_urlencoded};
4
5use crate::OfficialAccount;
6use crate::constants::keys;
7
8pub(crate) const TOKEN_URL: &str = "https://api.weixin.qq.com/cgi-bin/token";
9
10#[derive(Serialize, Deserialize, Debug)]
11pub struct TokenResponse {
12    pub access_token: String,
13    expires_in: u64,
14}
15
16impl OfficialAccount {
17    /// [Retrieves the access token for the official account](https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html)
18    ///
19    /// This function attempts to fetch the access token from the Redis database.
20    /// If not found, it requests a new token from the WeChat API using the
21    /// application ID and secret, then stores the token in Redis for future use.
22    ///
23    /// # Returns
24    ///
25    /// * A `Result` containing the access token as a `String` on success, or a boxed
26    ///   error on failure.
27    ///
28    /// # Errors
29    ///
30    /// * Returns an error if the Redis operation fails, if the HTTP request fails,
31    ///   or if the response cannot be deserialized into a `TokenResponse`.
32    pub async fn token(&self) -> Result<String, Box<dyn std::error::Error>> {
33        let mut rdb = self.rdb_pool.get().await?;
34
35        let value: Option<String> = cmd("GET")
36            .arg(keys::GLOBAL_TOKEN)
37            .query_async(&mut rdb)
38            .await
39            .unwrap_or(None);
40        if let Some(bytes) = value {
41            let at: TokenResponse = serde_json::from_str(&bytes).unwrap();
42            return Ok(at.access_token);
43        }
44
45        let mut url = Url::parse(TOKEN_URL).unwrap();
46        let query = form_urlencoded::Serializer::new(String::new())
47            .append_pair("appid", &self.config.appid)
48            .append_pair("secret", &self.config.app_secret)
49            .append_pair("grant_type", "client_credential")
50            .finish();
51
52        url.set_query(Some(&query));
53        let url = url.to_string();
54
55        let response = self.client.get(&url).send().await?;
56        let at: TokenResponse = response.json::<TokenResponse>().await?;
57        cmd("SETEX")
58            .arg(keys::GLOBAL_TOKEN)
59            .arg(60 * 50 * 2)
60            .arg(serde_json::to_string(&at).unwrap())
61            .query_async::<()>(&mut rdb)
62            .await
63            .unwrap();
64
65        Ok(at.access_token)
66    }
67}