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}