Ant Design Pro项目实战:用Umi-request优雅处理401无感刷新与文件下载(避坑指南)
Ant Design Pro实战Umi-request的401无感刷新与文件下载全解析最近在重构公司内部的管理系统时遇到了两个让人头疼的问题Token过期后的无感刷新和文件下载的异常处理。作为基于Ant Design Pro搭建的项目这些问题看似简单实则暗藏玄机。本文将分享我在解决这些问题时积累的实战经验希望能帮助遇到类似困境的开发者少走弯路。1. 理解Umi-request的核心机制Umi-request作为Umi框架内置的HTTP客户端兼具了fetch的现代API和axios的易用性。但在深入使用前有几个关键特性需要明确拦截器机制与axios类似提供了请求和响应拦截能力错误处理内置了基础错误处理但业务场景需要自定义响应类型默认处理JSON响应其他类型需要特殊处理// 基础request实例创建 import { extend } from umi-request; const request extend({ timeout: 10000, credentials: include });在Ant Design Pro的生态中Umi-request已经预先配置了基础拦截器但针对特定业务场景我们需要进行深度定制。2. 401状态的无感刷新方案Token过期是后台管理系统常见的问题传统方案是直接跳转登录页但这样会中断用户操作流程。更优雅的方式是实现无感刷新。2.1 基础拦截器配置首先我们需要在响应拦截器中识别401状态request.interceptors.response.use(async (response) { if (response.status 401) { // 处理Token过期逻辑 } return response; });2.2 刷新Token的并发控制当多个请求同时返回401时需要避免重复刷新Tokenlet isRefreshing false; let requests: ((token: string) void)[] []; request.interceptors.response.use(async (response) { if (response.status 401 !isRefreshing) { isRefreshing true; try { const newToken await refreshToken(); requests.forEach(cb cb(newToken)); requests []; return request(response.url, response.options); } finally { isRefreshing false; } } else if (response.status 401 isRefreshing) { return new Promise((resolve) { requests.push((token) { response.options.headers.Authorization Bearer ${token}; resolve(request(response.url, response.options)); }); }); } return response; });注意刷新Token接口本身不能加入401拦截逻辑否则会导致死循环2.3 用户无感体验优化除了Token刷新还需要考虑以下场景刷新Token失败后的降级处理长时间无操作后的自动登出关键操作前的Token有效性检查const refreshToken async () { try { const result await request(/api/auth/refresh, { method: POST }); setNewToken(result.token); return result.token; } catch (error) { // 刷新失败跳转登录页 redirectToLogin(); throw error; } };3. 文件下载的Blob处理实战文件下载是另一个常见需求但处理不当容易导致页面崩溃或内存泄漏。3.1 基础文件下载实现request.interceptors.response.use(async (response) { if (response.url.includes(/download/)) { const blob await response.clone().blob(); const filename getFilenameFromHeaders(response.headers); downloadFile(blob, filename); return null; // 中断后续处理 } return response; }); function downloadFile(blob: Blob, filename: string) { const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }3.2 大文件下载优化对于大文件下载需要考虑以下问题内存管理使用流式处理避免内存暴涨进度显示通过ReadableStream实现下载进度错误恢复支持断点续传const downloadLargeFile async (url: string, filename: string) { const response await fetch(url); const reader response.body.getReader(); const contentLength response.headers.get(Content-Length); let receivedLength 0; const chunks []; while(true) { const {done, value} await reader.read(); if (done) break; chunks.push(value); receivedLength value.length; updateProgress(receivedLength / contentLength); } const blob new Blob(chunks); downloadFile(blob, filename); };3.3 常见问题排查问题现象可能原因解决方案下载文件损坏未正确处理Blob类型检查Content-Type和文件扩展名内存泄漏未释放ObjectURL确保调用URL.revokeObjectURL进度不准确未获取Content-Length服务端需返回正确头信息大文件失败内存不足改用流式处理方案4. 全局请求的最佳实践4.1 请求重试机制对于网络波动导致的失败合理的重试策略能提升用户体验const MAX_RETRY 3; const RETRY_DELAY 1000; const fetchWithRetry async (url: string, options {}, retries 0) { try { return await request(url, options); } catch (error) { if (retries MAX_RETRY shouldRetry(error)) { await delay(RETRY_DELAY * (retries 1)); return fetchWithRetry(url, options, retries 1); } throw error; } }; function shouldRetry(error) { return error.response?.status 500 || error.message Network Error; }4.2 性能监控集成在拦截器中加入性能统计request.interceptors.request.use((url, options) { const startTime Date.now(); options.metadata { startTime }; return { url, options }; }); request.interceptors.response.use((response, options) { const duration Date.now() - options.metadata.startTime; trackApiPerformance(response.url, duration); return response; });4.3 安全防护措施CSRF防护确保正确配置credentials参数过滤拦截器中过滤敏感参数请求限流防止接口被滥用request.interceptors.request.use((url, options) { // 过滤敏感参数 if (options.data?.password) { options.data.password [FILTERED]; } // 添加CSRF Token options.headers[X-CSRF-TOKEN] getCsrfToken(); return { url, options }; });5. 异常处理的艺术5.1 错误分类处理不同业务场景需要不同的错误处理策略const errorHandler (error) { if (error.timeout) { showToast(请求超时请检查网络); } else if (error.response) { switch (error.response.status) { case 401: handleUnauthorized(); break; case 403: showForbiddenModal(); break; case 500: logServerError(error); break; default: showGenericError(error); } } else { showNetworkError(); } };5.2 错误上报集成结合Sentry等监控工具实现错误上报request.interceptors.response.use(null, (error) { if (isCriticalError(error)) { Sentry.captureException(error, { extra: { url: error.config.url, params: error.config.params } }); } return Promise.reject(error); });5.3 用户友好的错误展示避免直接显示技术性错误信息function showUserFriendlyError(error) { const userMessages { ECONNABORTED: 请求超时请稍后重试, ERR_NETWORK: 网络连接异常请检查网络设置, default: 系统繁忙请稍后再试 }; const message userMessages[error.code] || userMessages.default; notification.error({ message }); }在Ant Design Pro项目中这些技术细节的合理处理能显著提升应用稳定性和用户体验。经过多个项目的实践验证这套方案在保证功能完整性的同时也兼顾了性能和可维护性。