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};
64
65use crate::Result;
66use async_trait::async_trait;
67use http::{Request, Response};
68use std::{fmt, sync::Arc};
69
70/// 微信小程序的 App ID 和 Secret 配置。
71#[derive(Debug, Clone)]
72pub struct AppConfig {
73    pub app_id: String,
74    pub secret: String,
75}
76
77/// 微信小程序 SDK 主客户端结构。
78///
79/// 封装了 `HttpClient` 和 `TokenStorage`,提供发送请求和获取 Access Token 的方法。
80///
81pub struct WechatMinappSDK {
82    pub client: Arc<dyn HttpClient>,
83    pub token_storage: Arc<dyn TokenStorage>,
84}
85
86impl fmt::Debug for WechatMinappSDK {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        f.debug_struct("WechatMinappSDK")
89            .field("client", &"Arc<dyn HttpClient>")
90            .field("token_storage", &"Arc<dyn TokenStorage>")
91            .finish()
92    }
93}
94
95impl Clone for WechatMinappSDK {
96    fn clone(&self) -> Self {
97        WechatMinappSDK {
98            client: self.client.clone(),
99            token_storage: self.token_storage.clone(),
100        }
101    }
102}
103
104impl WechatMinappSDK {
105    /// 使用默认配置(`ReqwestHttpClient` 和 `MemoryTokenStorage` 与 `StableToken`)创建客户端。
106    ///
107    /// # 参数
108    /// - `app_id`: 小程序 App ID。
109    /// - `secret`: 小程序 App Secret。
110    pub fn new(app_id: &str, secret: &str) -> Self {
111        let http_client = Arc::new(ReqwestHttpClient::new());
112        let token_type = Arc::new(StableToken::new(app_id, secret, false, http_client.clone()));
113        let token_storage = Arc::new(MemoryTokenStorage::new(token_type));
114
115        WechatMinappSDK {
116            client: http_client,
117            token_storage,
118        }
119    }
120
121    /// 使用自定义的 `HttpClient` 和 `TokenStorage` 创建客户端。
122    ///
123    /// # 参数
124    /// - `http_client`: 实现 [`HttpClient`] Trait 的实例。
125    /// - `token_storage`: 实现 [`TokenStorage`] Trait 的实例。
126    pub fn custom(http_client: Arc<dyn HttpClient>, token_storage: Arc<dyn TokenStorage>) -> Self {
127        WechatMinappSDK {
128            client: http_client,
129            token_storage,
130        }
131    }
132
133    /// 获取接口调用凭据(Access Token)。
134    ///
135    /// 此方法会通过 `TokenStorage` 自动处理 Token 的获取、缓存和刷新逻辑。
136    ///
137    /// # 返回
138    /// Access Token 字符串的 Result。
139    pub async fn token(&self) -> Result<String> {
140        self.token_storage.token().await
141    }
142
143    /// 获取当前客户端的 App ID 和 Secret 配置。
144    pub fn app_config(&self) -> AppConfig {
145        self.token_storage.token_type().app_config()
146    }
147}
148
149/// 定义 HTTP 客户端行为的 Trait。
150///
151/// 可根据爱好替换底层的 HTTP 实现。
152#[async_trait]
153pub trait HttpClient: Send + Sync {
154    /// 执行一个 HTTP 请求并返回响应。
155    ///
156    /// # 参数
157    /// - `request`: 要执行的 HTTP 请求。
158    ///
159    /// # 返回
160    /// 包含 HTTP 响应的 Result。
161    async fn execute(&self, request: Request<Vec<u8>>) -> Result<Response<Vec<u8>>>;
162}
163
164/// 基于 `reqwest` 库的默认 HTTP 客户端实现。
165#[derive(Default, Clone)]
166pub struct ReqwestHttpClient {
167    pub client: Arc<reqwest::Client>,
168}
169
170impl ReqwestHttpClient {
171    pub fn new() -> Self {
172        ReqwestHttpClient {
173            client: Arc::new(reqwest::Client::new()),
174        }
175    }
176}
177
178/// 基于 `reqwest` 库的默认 HTTP 客户端实现。
179#[async_trait]
180impl HttpClient for ReqwestHttpClient {
181    /// 使用 `reqwest` 执行请求,并将 `http::Request` 转换为 `reqwest::Request`,
182    /// 再将 `reqwest::Response` 转换为 `http::Response`。
183    async fn execute(&self, req: Request<Vec<u8>>) -> Result<Response<Vec<u8>>> {
184        let reqwest_req = req.try_into()?;
185
186        let reqwest_res = self.client.execute(reqwest_req).await?;
187
188        let status = reqwest_res.status();
189        let version = reqwest_res.version();
190        let headers = reqwest_res.headers().clone();
191
192        let body = reqwest_res.bytes().await?.to_vec();
193
194        let mut http_res_builder = Response::builder().status(status).version(version);
195
196        if let Some(headers_map) = http_res_builder.headers_mut() {
197            headers_map.extend(headers);
198        }
199
200        let http_res = http_res_builder.body(body)?;
201
202        Ok(http_res)
203    }
204}