为了让后端统一处理各种异常情况,我们把后端发往前端的数据做了简单的封装如下:
{ "code":, "data": "message": "description":}所以前端要做修改:
对接后端的返回值,取data;
与后端对应,前端的typings.d.ts文件中,创建一个对应的数据类型:
xxxxxxxxxxtype BaseResponse<T>={ code:number, data:T, message: string, descritpion: string}然后以register访问的/api/user/register为例;
xxxxxxxxxxexport async function register(body: API.RegisterParams, options?: { [key: string]: any }) { return request<API.BaseResponse<API.RegisterResult>>('/api/user/register', { method: 'POST', headers: { 'Content-Type': 'application/json', }, data: body, (options || {}), });}Register/index.tsx的修改如下,主要将看else语句中,将descritption设置给Error,然后在异常处理中,将错误描述信息再显示到前端。
xxxxxxxxxx try { const res = await register({ values, type, }); if(res.code === 0 && res.data > 0){ const defaultLoginSuccessMessage = '注册成功!'; message.success(defaultLoginSuccessMessage); await fetchUserInfo(); /** 此方法会跳转到 redirect 参数所在的位置 */ if (!history) return; const { query } = history.location; const { redirect } = query as { redirect: string; }; history.push('/user/login'); return; }else throw new Error(res.description); } catch (error: any) { const defaultLoginFailureMessage = "注册失败,请重试!!"; message.error(error.message??defaultLoginFailureMessage); } };我们采用数据库中已有的账户注册,这个时候可以发现前端会提示对应的错误,如图1所示。

与Register类似,前端其他的api接口都需要进行适配,包括:
login, /api/user/login
searchUsers, /api/user/search
currentUser, /api/user/current
outLogin, /api/user/logout
register, /api/user/register
有这么多方法,难道我们对每个方法都要写一遍类似如下的判断逻辑么:
xxxxxxxxxx if(res.code === 0 && res.data > 0){ . }有没有统一处理、集中处理的方法?
思路:对来自后台的response做统一的处理,从response中取出data;或者根据response集中做错误处理,比如用户未登录、没有权限之类。优势:不用在每个接口请求中都去写相同的逻辑。
要实现上述思路,我们需要写一个response的全局拦截器,在拦截器中做统一的处理。
我们根据/Register/index.tsx中的request方法,可以顺藤摸瓜,跟踪到src/.umi/plugin-request/requests.ts中的请求拦截器和响应拦截器:
xxxxxxxxxx // Add user custom interceptors const requestInterceptors = requestConfig.requestInterceptors || []; const responseInterceptors = requestConfig.responseInterceptors || []; requestInterceptors.map((ri) => { requestMethodInstance.interceptors.request.use(ri); }); responseInterceptors.map((ri) => { requestMethodInstance.interceptors.response.use(ri); });此处我们可以定义自己的拦截器;
x // Add user custom interceptors requestConfig.responseInterceptors = [ function(response:Response, options:RequestOptionsInit): Response | Promise<Response>{ console.log("全局response拦截器",response); } ];
const requestInterceptors = requestConfig.requestInterceptors || []; const responseInterceptors = requestConfig.responseInterceptors || []; requestInterceptors.map((ri) => { requestMethodInstance.interceptors.request.use(ri); }); responseInterceptors.map((ri) => { requestMethodInstance.interceptors.response.use(ri); });再登录注册页面,点击"注册"按钮,打开浏览器开发者工具/终端,可以看到上述的"全局Response拦截器"字眼,并且没有按照预期跳转到登录页;
解释:说明我们上面写的requestConfig.responseInterceptors,开始工作;出现错误是因为上述拦截器没有返回数据;
新的问题:如何返回数据,尤其是data部分。现在的response中(从终端输出)没有看到对应的数据。
不能直接从数据或者源码中获取答案,那就试试百度和官方文档,百度以关键词"umi request全局响应",可以搜到如下的网页:
xxxxxxxxxxhttps://blog.csdn.net/huantai3334/article/details/116780020其中获取数据的代码行:
xxxxxxxxxxconst data = await response.clone().json();将上述这行代码添加到我们的代码中,新的问题:我们之前写的代码消失了。原来在src/.umi/目录下的代码是框架动态自动生成的,新生成的会覆盖掉我们之前修改的,所以我们还得写到其他地方。
参照上述csdn网页中的代码,我们在src下创建/plugins/globalRequests.ts
xxxxxxxxxximport {extend} from 'umi-request';import {message} from "antd";import {history} from "@@/core/history";import {stringify} from "querystring";
/** * 配置request请求时的默认参数 */const request = extend({ credentials: 'include', // 默认请求是否带上cookie // requestType: 'form',});
/** * 所以请求拦截器 */request.interceptors.request.use((url, options): any => { console.log('do request url = {}', url);
return { url, options: { options, headers: { }, }, };});
/** * 所有响应拦截器 */request.interceptors.response.use(async (response, options): Promise<any> => { const res = await response.clone().json();
if(res.code === 0){ return res.data; }
if(res.code === 40100){ message.error('请先登录'); history.replace({ pathname: '/user/login', search: stringify({ redirect: location.pathname, }), }); }else{ message.error(res.description) } return res.data;});
export default request;
完成globalRequests.ts后,很重要的一个点,要在api.ts中,将如下语句:
xxxxxxxxxximport { request } from 'umi'修改为:
xxxxxxxxxximport request from '@/plugins/globalRequest';完成上述修改后,前端可以正常地登录,展示数据,以及logout。但是唯一的问题:在注册成功,跳转到登录页时,还会弹出"请先登录"的错误;按道理不应该弹出,如图2所示。

我们修改下response拦截器的代码,增加一个判断:
xxxxxxxxxx/** * 所有响应拦截器 */request.interceptors.response.use(async (response, options): Promise<any> => { const res = await response.clone().json(); if(res.code === 0){ return res.data; } if(res.code === 40100){ if(history.location.pathname !== '/user/register') { message.error('请先登录'); history.replace({ pathname: '/user/login', search: stringify({ redirect: location.pathname, }), }); }else{ history.replace({ pathname: '/user/login', search: stringify({ redirect: location.pathname, }), }); } }else{ message.error(res.description) } return res.data;});然后,将Register/index.tsx中的handleSubmit的逻辑修改为如下:
xxxxxxxxxx const handleSubmit = async (values: API.RegisterParams) => {
const {userPassword, checkPassword} = values; if(userPassword !== checkPassword){ message.error('两次密码输入不相同!!'); return; }
try { const id = await register({ values, type, }); if(id >= 0) { // if(res.code === 0 && res.data>0){ const defaultLoginSuccessMessage = '注册成功!'; message.success(defaultLoginSuccessMessage); await fetchUserInfo(); /** 此方法会跳转到 redirect 参数所在的位置 */ if (!history) return; const {query} = history.location; const {redirect} = query as { redirect: string; }; history.push('/user/login'); return; } } catch (error: any) { const defaultLoginFailureMessage = "注册失败,请重试!!"; message.error(defaultLoginFailureMessage); } };经过上述的修改,注册后到登录页的错误提示消失。
至此,前端对后端封装的异常完成了适配。