0. axios为什么需要二次封装及好处是什么
通常我们的项目会越做越大,页面也会越来越多,如果页面非常的少,直接用axios也没有什么大的影响,那页面组件多了起来,上百个接口呢,这个时候后端改了接口,有一个请求多加了一个参数什么的呢?那就只有找到发请求那个页面,进去修改,如果多个页面都使用了这个页面发请求,整个过程很繁琐不易于项目的维护和迭代
这个时候如果我们统一的区管理接口,需要修改某一个接口的时候直接在api里修改对应的请求是不是很方便呢?
api统一管理,不管接口有多少,所有的接口都可以非常清晰,容易维护.
通常来说在项目开发中有三个阶段:
1、开发环境
2、测试环境
3、生产环境不同环境访问接口的域名是不同的,直接修改域名,这就是封装请求的原因。
1.封装目的
此次进行简单的封装,所以暂时没有考虑取消重复请求、重复发送请求、请求缓存等情况!这里主要实现以下目的:
- 实现请求拦截
- 实现响应拦截
- 常见错误信息处理
- 请求头设置
- api 集中式管理
2.初始化 axios 实例
请求拦截器
发送请求之前运行一般会判断是否有token,如果token存在则在请求头加上这个token。服务端会判断我这个token是否过期。可以设置加载全局loading。等
响应拦截器
对服务端返回的数据进行处理,存储token,错误处理、登录、登录失效路由跳转、关闭全局loading等。
目录结构如下:
index.js代码如下
/*
* @Description:
* @Version: 2.0
* @Autor: Cookie
* @Date: 2022-06-28 11:33:32
* @LastEditors: Zhang
* @LastEditTime: 2022-06-28 15:38:21
*/
import serverConfig from "./config";
import axios from 'axios'
import qs from "qs";
import { Message, Loading, MessageBox } from 'element-ui';
// loading函数
// 记录请求次数
let needLoadingRequestCount = 0;
let loading;
function startLoading() {
loading = Loading.service({
lock: true,
text: '加载中……',
background: 'rgba(0, 0, 0, 0.5)',
});
}
function endLoading() {
// 延迟500ms,防止网速特快加载中画面一闪而过
setTimeout(function () {
if (loading) loading.close();
}, 500);
}
// 打开loading
function showFullScreenLoading() {
if (needLoadingRequestCount === 0) {
startLoading();
}
needLoadingRequestCount++;
}
// 关闭loading
function tryHideFullScreenLoading() {
if (needLoadingRequestCount <= 0) return;
needLoadingRequestCount--;
if (needLoadingRequestCount === 0) {
endLoading();
}
}
// 创建实例
const serviceAxios = axios.create({
baseURL: serverConfig.baseURL, // 基础请求地址
timeout: 10000, // 请求超时设置
// withCredentials: false, // 跨域请求是否需要携带 cookie
});
// 添加请求拦截器
serviceAxios.interceptors.request.use(
config => {
// 在发送请求之前做些什么
// 打开loading
showFullScreenLoading();
// 如果开启 token 认证
if (serverConfig.useTokenAuthorization) {
config.headers["Authorization"] = JSON.parse(localStorage.getItem("token")); // 请求头携带 token
}
// axios.defaults.headers.common['Authorization'] = localStorage.getItem("token")
// 设置请求头
if (!config.headers["content-type"]) { // 如果没有设置请求头
if (config.method === 'post') {
config.headers["content-type"] = "application/x-www-form-urlencoded"; // post 请求
config.data = qs.stringify(config.data); // 序列化,比如表单数据
} else {
config.headers["content-type"] = "application/json"; // 默认类型(get)
// axios.defaults.headers.post['Content-Type'] = 'application/json';
}
}
console.log("请求配置", config);
return config;
},
error => {
// 对请求错误做些什么
// 关闭loading
tryHideFullScreenLoading();
return Promise.reject(error);
}
);
// 添加响应拦截器
serviceAxios.interceptors.response.use(
res => {
console.log('响应', res)
// 关闭loading
tryHideFullScreenLoading();
let data = res.data
if (!res.success) {
// B002:Token 过期了;
if (res.code === 'B002') {
// 最后一次出弹框
if (needLoadingRequestCount === 0) {
MessageBox.confirm(
`你已被登出,可以取消继续留在该页面,
或者重新登录, 确定登出`,
{
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
},
).then(() => {
// 返回登录页
// ...做些事情
// 为了重新实例化vue-router对象 避免bug
location.reload();
});
}
} else {
Message({
message: res.msg,
type: 'error',
});
}
}
// 对响应数据做点什么
return data;
},
error => {
console.log('响应fail', error)
// 对响应错误做点什么
let message = "";
if (error && error.response) {
switch (error.response.status) {
case 302:
message = "接口重定向了!";
break;
case 400:
message = "参数不正确!";
break;
case 401:
message = "您未登录,或者登录已经超时,请先登录!";
break;
case 403:
message = "您没有权限操作!";
break;
case 404:
message = `请求地址出错: ${error.response.config.url}`;
break;
case 408:
message = "请求超时!";
break;
case 409:
message = "系统已存在相同数据!";
break;
case 500:
message = "服务器内部错误!";
break;
case 501:
message = "服务未实现!";
break;
case 502:
message = "网关错误!";
break;
case 503:
message = "服务不可用!";
break;
case 504:
message = "服务暂时无法访问,请稍后再试!";
break;
case 505:
message = "HTTP 版本不受支持!";
break;
default:
message = "异常问题,请联系管理员!";
break;
}
}
return Promise.reject(message);
}
);
console.log(serviceAxios)
export default serviceAxios; //导出实例
config/index.js代码如下:
const serverConfig = {
baseURL: 'url', // 请求基础地址,可根据环境自定义
useTokenAuthorization: true, // 是否开启 token 认证
}
export default serverConfig
api/user.js
import serviceAxios from "../index";
const login = (data) => {
return serviceAxios({
url: "/app/login",
method: "post",
data,
});
}
const getUserInfo = (params) => {
return serviceAxios({
url: "/app/getSystemName",
method: "get",
params,
});
}
export {
login,
getUserInfo
}
vue文件中使用:
mounted() {
this.init();
this.getInfo();
},
methods: {
async init() {
let params = {
nickName: "admin",
password: "6SJ2Lmmt0BKKiSWhQipe+Q=="
};
let res = await login(params);
localStorage.setItem("token", JSON.stringify(res.data)); // 存放token
console.log(res.data);
},
getInfo() {
let params = {};
getUserInfo(params).then(res => { // 也可以then的方式链式调用
console.log(res);
})
}
}
3.总结
我们在此基础上可以根据业务场景做一些以下扩展建议:
- 请求拦截里面针对 token 进行处理
- 响应拦截里面判断 token 是否过期等等
- 在 config/index.js 里面动态更改 baseURL
- 在请求拦截里面根据业务场景修改请求头
- 在拦截里面设置全局请求进度条等等