本项目是基于ant-design开发的一个前端项目,项目的上线地址:http://user-front.66bond.com/

本项目的开发软件:

  • webstorm2021:关注公众号”青椒工具”,发送”webstorm”获取安装包下载链接;

为了让后端统一处理各种异常情况,我们把后端发往前端的数据做了简单的封装如下:

1
2
3
4
5
6
{
"code":,
"data":
"message":
"description":
}

所以前端要做修改:

  • 对接后端的返回值,取data;

与后端对应,前端的typings.d.ts文件中,创建一个对应的数据类型:

1
2
3
4
5
6
type BaseResponse<T>={
code:number,
data:T,
message: string,
descritpion: string
}

然后以register访问的/api/user/register为例;

1
2
3
4
5
6
7
8
9
10
export 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,然后在异常处理中,将错误描述信息再显示到前端。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  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所示。

图7.1数据库中有重复账户-裁剪

图1 前端提示注册账户重复

与Register类似,前端其他的api接口都需要进行适配,包括:

  • login, /api/user/login
  • searchUsers, /api/user/search
  • currentUser, /api/user/current
  • outLogin, /api/user/logout
  • register, /api/user/register

有这么多方法,难道我们对每个方法都要写一遍类似如下的判断逻辑么:

1
2
3
if(res.code === 0 && res.data > 0){
....
}

有没有统一处理、集中处理的方法?

思路:对来自后台的response做统一的处理,从response中取出data;或者根据response集中做错误处理,比如用户未登录、没有权限之类。优势:不用在每个接口请求中都去写相同的逻辑。

要实现上述思路,我们需要写一个response的全局拦截器,在拦截器中做统一的处理。

我们根据/Register/index.tsx中的request方法,可以顺藤摸瓜,跟踪到src/.umi/plugin-request/requests.ts中的请求拦截器和响应拦截器:

1
2
3
4
5
6
7
8
9
// 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);
});

此处我们可以定义自己的拦截器;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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全局响应”,可以搜到如下的网页:

1
https://blog.csdn.net/huantai3334/article/details/116780020

其中获取数据的代码行:

1
const data = await response.clone().json();

将上述这行代码添加到我们的代码中,新的问题:我们之前写的代码消失了。原来在src/.umi/目录下的代码是框架动态自动生成的,新生成的会覆盖掉我们之前修改的,所以我们还得写到其他地方。

参照上述csdn网页中的代码,我们在src下创建/plugins/globalRequests.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import {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中,将如下语句:

1
import { request } from 'umi'

修改为:

1
import request from '@/plugins/globalRequest';

完成上述修改后,前端可以正常地登录,展示数据,以及logout。但是唯一的问题:在注册成功,跳转到登录页时,还会弹出”请先登录”的错误;按道理不应该弹出,如图2所示。

图7.2注册成功后提示登录错误

图2 注册后提示请先登录错误

我们修改下response拦截器的代码,增加一个判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 所有响应拦截器
*/
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的逻辑修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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);
}
};

经过上述的修改,注册后到登录页的错误提示消失。

至此,前端对后端封装的异常完成了适配。

参考资料:

本文参考自如下知识星球中的视频教程,更多的完整的相关视频教程,见如下的收费知识星球,近3万人的学习社区,

编程有人同行,学习不再迷茫

编程导航知识星球