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