Skip to main content

wechat_minapp/client/
mod.rs

1//! 微信小程序服务端接口 Client 模块
2//!
3//! HTTP 客户端和令牌存储方式分离,可以按自己的需求实现不同的客户端和存储方式。
4//! 支持稳定版接口调用凭据和普通版接口调用凭据。默认使用稳定版接口调用凭据。
5//!
6//! ## Traits
7//! - [`HttpClient`]: 定义了 HTTP 客户端的行为,默认使用 [`ReqwestHttpClient`], 可参考实现其他 http client ,比如 `ureq` 。
8//! - [`TokenStorage`]: 定义了接口调用凭据读取保存的行为,默认使用 [`MemoryTokenStorage`], 可参考实现读取保存方式,比如 `redis`、`postgresql`、`mysql` 等。
9//! - [`TokenType`][token_type::TokenType]: 定义了接口调用凭据的行为,包括 [`StableToken`] (稳定版) 和 [`NonStableToken`] (普通版)。
10//!
11//! ## 默认客户端和存储方式
12//!
13//! 使用默认的 `reqwest` HTTP 客户端和内存 `Arc` 结构存储 Access Token。
14//!
15//! ```no_run
16//! use wechat_minapp::client::WechatMinappSDK;
17//!
18//! let app_id = "your_app_id";
19//! let secret = "your_app_secret";
20//! // 默认使用 StableToken (稳定版接口调用凭据)
21//! let client = WechatMinappSDK::new(app_id, secret);
22//! ```
23//!
24//! ## 自定义 HTTP 客户端和存储方式
25//!
26//! 示例展示了如何使用默认的 `ReqwestHttpClient` 和 `MemoryTokenStorage` (稳定版 `StableToken`) 来构造自定义客户端。
27//! 实际应用中,您可以替换为自己的实现。
28//!
29//! ```no_run
30//! use std::sync::Arc;
31//! use wechat_minapp::client::{MemoryTokenStorage, StableToken};
32//! use wechat_minapp::client::{ReqwestHttpClient, WechatMinappSDK, HttpClient};
33//!
34//! let app_id = "your_app_id";
35//! let secret = "your_app_secret";
36//!
37//! // 使用 reqwest 客户端
38//! let http_client: Arc<dyn HttpClient> = Arc::new(ReqwestHttpClient::new());
39//!
40//! // 使用 StableToken (稳定版接口调用凭据)
41//! let token_type = Arc::new(StableToken::new(
42//!        app_id,
43//!        secret,
44//!        false, // 不强制刷新
45//!        http_client.clone(),
46//!    ));
47//!
48//! // 使用内存存储
49//! let token_storage = Arc::new(MemoryTokenStorage::new(token_type));
50//!
51//! // 创建自定义客户端
52//! let client = WechatMinappSDK::custom(http_client, token_storage);
53//!
54//! // ... 客户端现在可以使用了
55//! ```
56//!
57mod access_token;
58mod token_storage;
59pub mod token_type;
60
61pub use access_token::AccessToken;
62pub use token_storage::{MemoryTokenStorage, TokenStorage};
63pub use token_type::{NonStableToken, StableToken};
64pub type WechatMinapp = WechatMinappSDK;
65pub type NormalToken = NonStableToken;
66
67use crate::Result;
68use async_trait::async_trait;
69use http::{Request, Response};
70use reqwest::Request as ReqwestRequest;
71use std::{fmt, sync::Arc};
72
73/// 微信小程序的 App ID 和 Secret 配置。
74#[derive(Debug, Clone)]
75pub struct AppConfig {
76    pub app_id: String,
77    pub secret: String,
78}
79
80/// 微信小程序 SDK 主客户端结构。
81///
82/// 封装了 `HttpClient` 和 `TokenStorage`,提供发送请求和获取 Access Token 的方法。
83///
84pub struct WechatMinappSDK {
85    pub client: Arc<dyn HttpClient>,
86    pub token_storage: Arc<dyn TokenStorage>,
87}
88
89impl fmt::Debug for WechatMinappSDK {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("WechatMinappSDK")
92            .field("client", &"Arc<dyn HttpClient>")
93            .field("token_storage", &"Arc<dyn TokenStorage>")
94            .finish()
95    }
96}
97
98impl Clone for WechatMinappSDK {
99    fn clone(&self) -> Self {
100        WechatMinappSDK {
101            client: self.client.clone(),
102            token_storage: self.token_storage.clone(),
103        }
104    }
105}
106
107impl WechatMinappSDK {
108    /// 使用默认配置(`ReqwestHttpClient` 和 `MemoryTokenStorage` 与 `StableToken`)创建客户端。
109    ///
110    /// # 参数
111    /// - `app_id`: 小程序 App ID。
112    /// - `secret`: 小程序 App Secret。
113    pub fn new(app_id: &str, secret: &str) -> Self {
114        let http_client = Arc::new(ReqwestHttpClient::new());
115        let token_type = Arc::new(StableToken::new(app_id, secret, false, http_client.clone()));
116        let token_storage = Arc::new(MemoryTokenStorage::new(token_type));
117
118        WechatMinappSDK {
119            client: http_client,
120            token_storage,
121        }
122    }
123
124    /// 使用自定义的 `HttpClient` 和 `TokenStorage` 创建客户端。
125    ///
126    /// # 参数
127    /// - `http_client`: 实现 [`HttpClient`] Trait 的实例。
128    /// - `token_storage`: 实现 [`TokenStorage`] Trait 的实例。
129    pub fn custom(http_client: Arc<dyn HttpClient>, token_storage: Arc<dyn TokenStorage>) -> Self {
130        WechatMinappSDK {
131            client: http_client,
132            token_storage,
133        }
134    }
135
136    /// 获取接口调用凭据(Access Token)。
137    ///
138    /// 此方法会通过 `TokenStorage` 自动处理 Token 的获取、缓存和刷新逻辑。
139    ///
140    /// # 返回
141    /// Access Token 字符串的 Result。
142    pub async fn token(&self) -> Result<String> {
143        self.token_storage.token().await
144    }
145
146    /// 获取当前客户端的 App ID 和 Secret 配置。
147    pub fn app_config(&self) -> AppConfig {
148        self.token_storage.token_type().app_config()
149    }
150}
151
152/// 定义 HTTP 客户端行为的 Trait。
153///
154/// 可根据爱好替换底层的 HTTP 实现。
155#[async_trait]
156pub trait HttpClient: Send + Sync {
157    /// 执行一个 HTTP 请求并返回响应。
158    ///
159    /// # 参数
160    /// - `request`: 要执行的 HTTP 请求。
161    ///
162    /// # 返回
163    /// 包含 HTTP 响应的 Result。
164    async fn execute(&self, request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>>;
165}
166
167/// 基于 `reqwest` 库的默认 HTTP 客户端实现。
168#[derive(Default, Clone)]
169pub struct ReqwestHttpClient {
170    pub client: Arc<reqwest::Client>,
171}
172
173impl ReqwestHttpClient {
174    pub fn new() -> Self {
175        ReqwestHttpClient {
176            client: Arc::new(reqwest::Client::new()),
177        }
178    }
179}
180
181/// 基于 `reqwest` 库的默认 HTTP 客户端实现。
182#[async_trait]
183impl HttpClient for ReqwestHttpClient {
184    /// 使用 `reqwest` 执行请求,并将 `http::Request` 转换为 `reqwest::Request`,
185    /// 再将 `reqwest::Response` 转换为 `http::Response`。
186    async fn execute(&self, req: Request<Vec<u8>>) -> Result<Response<Vec<u8>>> {
187        let reqwest_req: ReqwestRequest = req.try_into()?;
188
189        // 方便 cargo test 输出
190        #[cfg(test)]
191        eprintln!("reqwest url: {:?}", reqwest_req.url());
192
193        let reqwest_res = self.client.execute(reqwest_req).await?;
194
195        let status = reqwest_res.status();
196        let version = reqwest_res.version();
197        let headers = reqwest_res.headers().clone();
198
199        let body = reqwest_res.bytes().await?.to_vec();
200
201        let mut http_res_builder = Response::builder().status(status).version(version);
202
203        if let Some(headers_map) = http_res_builder.headers_mut() {
204            headers_map.extend(headers);
205        }
206
207        let http_res = http_res_builder.body(body)?;
208
209        Ok(http_res)
210    }
211}