wechat_backend_auth/error.rs
1use thiserror::Error;
2
3/// 微信授权错误类型
4#[derive(Error, Debug)]
5pub enum WeChatError {
6 /// 配置错误
7 #[error("配置错误: {0}")]
8 Configuration(String),
9
10 /// 网络传输错误
11 #[error("网络传输错误: {0}")]
12 Transport(#[from] TransportError),
13
14 /// 响应解析失败
15 #[error("响应解析失败 - URL: {url}, 错误: {error}, 预览: {preview}")]
16 ResponseParseFailed {
17 url: String,
18 error: String,
19 preview: String,
20 },
21
22 /// 微信API通用错误
23 #[error("微信API错误 (errcode: {errcode}): {errmsg}")]
24 ApiError { errcode: i32, errmsg: String },
25
26 // ===== Access Token 相关错误 =====
27 /// Access Token过期或无效
28 ///
29 /// 错误码: 40001, 40014, 42001
30 ///
31 /// 解决方案:
32 /// 1. 检查AppSecret是否正确
33 /// 2. 使用refresh_token刷新access_token
34 /// 3. 如果refresh_token也过期,需要重新授权
35 #[error("Access Token已过期或无效 (errcode: {code})")]
36 AccessTokenExpired { code: i32 },
37
38 /// Access Token缺失
39 ///
40 /// 错误码: 41001
41 ///
42 /// 解决方案: 检查API调用时是否传递了access_token参数
43 #[error("缺少access_token参数")]
44 AccessTokenMissing,
45
46 /// 用户修改密码导致Token失效
47 ///
48 /// 错误码: 42007
49 ///
50 /// 原因: 用户修改了微信密码,导致之前授权的所有token失效
51 ///
52 /// 解决方案: 引导用户重新进行授权
53 #[error("用户修改密码,access_token和refresh_token已失效,需要重新授权")]
54 TokenInvalidated,
55
56 // ===== Authorization Code 相关错误 =====
57 /// 无效的授权码
58 ///
59 /// 错误码: 40029
60 ///
61 /// 原因:
62 /// - 授权码格式错误
63 /// - 授权码不是通过微信授权流程获得
64 /// - AppID与授权码不匹配
65 ///
66 /// 解决方案: 检查授权流程是否正确,确保使用正确的AppID
67 #[error("无效的授权码")]
68 InvalidCode,
69
70 /// 授权码已被使用
71 ///
72 /// 错误码: 40163
73 ///
74 /// 原因:
75 /// - 授权码(code)只能使用一次
76 /// - 页面刷新或重复请求导致code被重复使用
77 /// - 服务端错误导致回调接口被调用多次
78 ///
79 /// 解决方案:
80 /// 1. 检查前端是否有重复请求
81 /// 2. 确保回调接口没有抛出异常
82 /// 3. 避免页面刷新时重复使用旧的code
83 /// 4. 让用户重新进行授权获取新的code
84 #[error("授权码已被使用(code只能使用一次,请重新授权)")]
85 CodeUsed,
86
87 /// 授权码已过期
88 ///
89 /// 错误码: 42003
90 ///
91 /// 原因: 授权码(code)的有效期为5分钟,超时后无法使用
92 ///
93 /// 解决方案: 引导用户重新进行授权获取新的code
94 #[error("授权码已过期(有效期5分钟,请重新授权)")]
95 CodeExpired,
96
97 // ===== Refresh Token 相关错误 =====
98 /// Refresh Token无效
99 ///
100 /// 错误码: 40030
101 ///
102 /// 解决方案: 引导用户重新授权
103 #[error("Refresh Token无效")]
104 RefreshTokenInvalid,
105
106 /// Refresh Token已过期
107 ///
108 /// 错误码: 42002
109 ///
110 /// 原因: refresh_token的有效期为30天,超时后无法使用
111 ///
112 /// 解决方案: 引导用户重新进行授权
113 #[error("Refresh Token已过期(有效期30天,请重新授权)")]
114 RefreshTokenExpired,
115
116 // ===== 权限和限制相关错误 =====
117 /// 用户未授权该API
118 ///
119 /// 错误码: 50001
120 ///
121 /// 原因:
122 /// - 授权作用域不足
123 /// - 账号类型不支持该API
124 /// - 未配置授权回调域名
125 ///
126 /// 解决方案:
127 /// 1. 检查授权时的scope参数
128 /// 2. 确认账号类型是否支持(如需要认证的服务号)
129 /// 3. 在微信后台配置授权回调域名
130 #[error("用户未授权该API,请检查授权作用域和账号权限")]
131 UserUnauthorized,
132
133 /// API调用频率超限
134 ///
135 /// 错误码: 45009(分钟级), 45011(日级)
136 ///
137 /// 解决方案:
138 /// 1. 实施请求限流
139 /// 2. 使用缓存减少API调用
140 /// 3. 等待限制解除后重试
141 #[error("API调用频率超限 (errcode: {code}): {msg}")]
142 RateLimitExceeded { code: i32, msg: String },
143
144 /// 服务器IP未在白名单中
145 ///
146 /// 错误码: 40164
147 ///
148 /// 解决方案: 在微信公众平台后台添加服务器IP到白名单
149 #[error("服务器IP未在微信后台白名单中")]
150 IpNotWhitelisted,
151
152 // ===== 系统错误 =====
153 /// 微信系统繁忙
154 ///
155 /// 错误码: -1
156 ///
157 /// 解决方案: 这是临时性错误,等待后重试即可
158 #[error("微信系统繁忙,请稍后重试")]
159 SystemBusy,
160
161 /// 参数错误
162 ///
163 /// 错误码: 61451
164 ///
165 /// 解决方案: 检查API调用参数是否正确
166 #[error("请求参数错误: {0}")]
167 InvalidParameter(String),
168
169 /// HTTP构建错误
170 #[error("HTTP请求构建错误: {0}")]
171 HttpBuildError(String),
172}
173
174/// 网络传输错误
175#[derive(Error, Debug)]
176pub enum TransportError {
177 /// HTTP请求失败
178 #[error("HTTP请求失败: {0}")]
179 RequestFailed(String),
180
181 /// 连接超时
182 #[error("连接超时")]
183 Timeout,
184
185 /// SSL/TLS错误
186 #[error("SSL/TLS错误: {0}")]
187 TlsError(String),
188
189 /// 网络连接错误
190 #[error("网络连接错误: {0}")]
191 ConnectionError(String),
192
193 /// 其他传输错误
194 #[error("传输错误: {0}")]
195 Other(String),
196}
197
198impl From<reqwest::Error> for WeChatError {
199 fn from(err: reqwest::Error) -> Self {
200 if err.is_timeout() {
201 WeChatError::Transport(TransportError::Timeout)
202 } else if err.is_connect() {
203 WeChatError::Transport(TransportError::ConnectionError(err.to_string()))
204 } else if err.is_request() {
205 WeChatError::Transport(TransportError::RequestFailed(err.to_string()))
206 } else {
207 WeChatError::Transport(TransportError::Other(err.to_string()))
208 }
209 }
210}
211
212/// 从微信API错误码创建对应的错误类型
213///
214/// 根据微信官方文档将错误码映射到具体的错误类型,便于业务层做针对性处理
215///
216/// # 参数
217///
218/// - `errcode`: 微信API返回的错误码
219/// - `errmsg`: 微信API返回的错误消息
220///
221/// # 返回
222///
223/// 返回对应的 `WeChatError` 类型
224pub(crate) fn from_errcode(errcode: i32, errmsg: String) -> WeChatError {
225 match errcode {
226 // 系统错误
227 -1 => WeChatError::SystemBusy,
228
229 // Access Token相关错误
230 40001 | 40014 => WeChatError::AccessTokenExpired { code: errcode },
231 41001 => WeChatError::AccessTokenMissing,
232 42001 => WeChatError::AccessTokenExpired { code: errcode },
233 42007 => WeChatError::TokenInvalidated,
234
235 // Authorization Code相关错误
236 40029 => WeChatError::InvalidCode,
237 40163 => WeChatError::CodeUsed,
238 42003 => WeChatError::CodeExpired,
239
240 // Refresh Token相关错误
241 40030 => WeChatError::RefreshTokenInvalid,
242 42002 => WeChatError::RefreshTokenExpired,
243
244 // IP白名单错误
245 40164 => WeChatError::IpNotWhitelisted,
246
247 // 权限和作用域错误
248 50001 => WeChatError::UserUnauthorized,
249
250 // 频率限制错误
251 45009 | 45011 => WeChatError::RateLimitExceeded {
252 code: errcode,
253 msg: errmsg.clone(),
254 },
255
256 // 参数错误
257 61451 => WeChatError::InvalidParameter(errmsg.clone()),
258
259 // 其他未分类错误
260 _ => WeChatError::ApiError { errcode, errmsg },
261 }
262}
263
264impl WeChatError {
265 /// 判断错误是否可以重试
266 ///
267 /// # 返回值
268 ///
269 /// - `true`: 错误是临时性的,可以重试
270 /// - `false`: 错误需要人工介入,重试无效
271 ///
272 /// # 示例
273 ///
274 /// ```
275 /// # use wechat_backend_auth::WeChatError;
276 /// # fn example(error: WeChatError) {
277 /// if error.is_retriable() {
278 /// // 等待后重试
279 /// println!("可以重试");
280 /// } else {
281 /// println!("无法重试,需要人工处理");
282 /// }
283 /// # }
284 /// ```
285 pub fn is_retriable(&self) -> bool {
286 matches!(
287 self,
288 WeChatError::SystemBusy
289 | WeChatError::Transport(TransportError::Timeout)
290 | WeChatError::Transport(TransportError::ConnectionError(_))
291 | WeChatError::RateLimitExceeded { .. }
292 )
293 }
294
295 /// 判断错误是否需要重新授权
296 ///
297 /// # 返回值
298 ///
299 /// - `true`: 需要引导用户重新进行微信授权
300 /// - `false`: 不需要重新授权
301 ///
302 /// # 示例
303 ///
304 /// ```
305 /// # use wechat_backend_auth::WeChatError;
306 /// # fn example(error: WeChatError) {
307 /// if error.requires_reauthorization() {
308 /// // 清除本地token缓存
309 /// // 返回授权URL给前端
310 /// println!("需要重新授权");
311 /// }
312 /// # }
313 /// ```
314 pub fn requires_reauthorization(&self) -> bool {
315 matches!(
316 self,
317 WeChatError::CodeExpired
318 | WeChatError::CodeUsed
319 | WeChatError::RefreshTokenExpired
320 | WeChatError::RefreshTokenInvalid
321 | WeChatError::TokenInvalidated
322 )
323 }
324
325 /// 判断错误是否是配置问题
326 ///
327 /// # 返回值
328 ///
329 /// - `true`: 是配置问题,需要修改配置
330 /// - `false`: 不是配置问题
331 ///
332 /// # 示例
333 ///
334 /// ```
335 /// # use wechat_backend_auth::WeChatError;
336 /// # fn example(error: WeChatError) {
337 /// if error.is_configuration_error() {
338 /// // 记录配置错误日志
339 /// // 发送告警通知
340 /// eprintln!("配置错误: {}", error);
341 /// }
342 /// # }
343 /// ```
344 pub fn is_configuration_error(&self) -> bool {
345 matches!(
346 self,
347 WeChatError::Configuration(_)
348 | WeChatError::IpNotWhitelisted
349 | WeChatError::UserUnauthorized
350 | WeChatError::AccessTokenMissing
351 )
352 }
353
354 /// 获取错误的推荐重试延迟(秒)
355 ///
356 /// # 返回值
357 ///
358 /// - `Some(seconds)`: 建议延迟的秒数
359 /// - `None`: 不建议重试
360 ///
361 /// # 示例
362 ///
363 /// ```
364 /// # use wechat_backend_auth::WeChatError;
365 /// # async fn example(error: WeChatError) {
366 /// if let Some(delay) = error.recommended_retry_delay() {
367 /// println!("建议{}秒后重试", delay);
368 /// // tokio::time::sleep(Duration::from_secs(delay)).await;
369 /// }
370 /// # }
371 /// ```
372 pub fn recommended_retry_delay(&self) -> Option<u64> {
373 match self {
374 WeChatError::SystemBusy => Some(1),
375 WeChatError::RateLimitExceeded { code, .. } => match code {
376 45009 => Some(60), // 分钟级限制,等待1分钟
377 45011 => Some(3600), // 日级限制,等待1小时
378 _ => Some(60),
379 },
380 WeChatError::Transport(TransportError::Timeout) => Some(2),
381 WeChatError::Transport(TransportError::ConnectionError(_)) => Some(5),
382 _ => None,
383 }
384 }
385}