前言
如今,在项目中,普遍采用Axios库进行Http接口请求。它是基于promise的http库,可运行在浏览器端和node.js中。此外还有拦截请求和响应、转换JSON数据、客户端防御XSRF等优秀的特性。axios源码
「链接-axios中文文档」
考虑到各个项目实际使用时写法混乱,不统一。对Axios进行一下通用化的封装,目的是帮助简化代码和利于后期的更新维护,尽量通用化。
项目说明
项目代码本身依附于@vue/CLI3构建,当然通过剥离vue代码,该Axios封装也可以直接应用到其他项目中。
封装要达成的目标:
- 统一维护管理接口;
- 支持接口代理转发;
- 读取环境配置,区分处理环境。
- 拦截请求和响应,处理登录超时、404等异常情况;
- 根据请求的配置匹配接口URL前缀且作支持做特殊处理。
封装
安装axios
npm install axios;
创建实例
通过创建实例,操作实例的方式进行接口请求。
//request.jsimport axios from 'axios'; // 引入axiosimport Qs from 'qs'; // 引入qs模块,用来序列化post类型的数据import { autoMatch, checkStatus } from './utils'; // 处理函数import { Toast } from 'mint-ui'; //提示框// 创建axios实例const instance = axios.create({// baseURL: process.env.BASE_URL,timeout: 30000, // 请求超时时间// `transformRequest` 允许在向服务器发送前,修改请求数据transformRequest: [function (data) {// 对 data 进行任意转换处理return data;}],// `transformResponse` 在传递给 then/catch 前,允许修改响应数据transformResponse: [function (data) {// 对 data 进行任意转换处理return JSON.parse(data);}]})
添加请求和响应拦截器
给实例添加请求和响应拦截器,根据实际数据格式进行相应的处理。
比如:
请求时,应后端要求根据Content-type设置data传参格式;
响应时,统一处理登录超时的情况和请求失败的错误提示处理等。
//request.js// 实例添加请求拦截器instance.interceptors.request.use(function (config) {// 在发送请求之前做处理...config.headers = Object.assign(config.method === 'get' ? {'Accept': 'application/json','Content-Type': 'application/json; charset=UTF-8'} : {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, config.headers);if (config.method === 'post') {const contentType = config.headers['Content-Type'];// 根据Content-Type转换data格式if (contentType) {if (contentType.includes('multipart')) { // 类型 'multipart/form-data;'// config.data = data;} else if (contentType.includes('json')) { // 类型 'application/json;'// 服务器收到的raw body(原始数据) "{name:"nowThen",age:"18"}"(普通字符串)config.data = JSON.stringify(config.data);} else { // 类型 'application/x-www-form-urlencoded;'// 服务器收到的raw body(原始数据) name=nowThen&age=18config.data = Qs.stringify(config.data);}}}return Promise.resolve(config);}, function (error) {// 对请求错误做处理...return Promise.reject(error);});// 实例添加响应拦截器instance.interceptors.response.use(function (response) {// 对响应数据做处理,以下根据实际数据结构改动!!...// const { reason_code } = response.data || {};// if (reason_code === '001') { // 请求超时,跳转登录页// const instance = Toast('请求超时,即将跳转到登录页面...');// setTimeout(() => {// instance.close();// window.location.href = '/login';// }, 3000)// }return Promise.resolve(checkStatus(response));}, function (error) {// 对响应错误做处理...if (error.response) {return Promise.reject(checkStatus(error.response));} else if (error.code == "ECONNABORTED" &&error.message.indexOf("timeout") != -1) {return Promise.reject({ msg: "请求超时" });} else {return Promise.reject({});}});
处理错误状态:
//utils.jsexport function checkStatus (response) {const status = response.status || -1000; // -1000 自己定义,连接错误的statusif ((status >= 200 && status < 300) || status === 304) {// 如果http状态码正常,则直接返回数据return response.data;} else {let errorInfo = '';switch (status) {case -1:errorInfo = '远程服务响应失败,请稍后重试';break;case 400:errorInfo = '400:错误请求';break;case 401:errorInfo = '401:访问令牌无效或已过期';break;case 403:errorInfo = '403:拒绝访问';break;case 404:errorInfo = '404:资源不存在';break;case 405:errorInfo = '405:请求方法未允许'break;case 408:errorInfo = '408:请求超时'break;case 500:errorInfo = '500:访问服务失败';break;case 501:errorInfo = '501:未实现';break;case 502:errorInfo = '502:无效网关';break;case 503:errorInfo = '503:服务不可用'break;default:errorInfo = `连接错误`}return {status,msg: errorInfo}}}
封装实例
实例封装到async\await异步函数中。
//request.jsconst request = async function (opt) {try {const options = Object.assign({method: 'get',ifHandleError: true // 是否统一处理接口失败(提示)}, opt);// 匹配接口前缀 开发环境则通过proxy配置转发请求; 生产环境根据实际配置options.baseURL = autoMatch(options.prefix);const res = await instance(options);// console.log(res);if (!res.success && options.ifHandleError) { // 自定义参数,是否允许全局提示错误信息Toast(res.error || '请求处理失败!')}return res;} catch (err) {if (options.ifHandleError) { // 自定义参数,是否允许全局提示错误信息Toast(err.msg || '请求处理失败!')}return err;}}
- 此处可以增加对请求入参进行合并或默认值处理;
- 自定义参数配置
项目中统一处理报错提示。但有时对于接口请求比较多的情况,很多时候并不希望某些接口被全局报错提示。这时这种接口可以设置自定义的ifHandleError参数处理,不进行全局错误提示,而是到业务代码中再做处理。
同理,其他的一些特殊业务情况也可以通过自定义参数处理。
- 根据自定义的prefix参数匹配接口前缀;
对于项目中请求多个后端的接口时,每个接口在请求封装时附带prefix参数,根据prefix的值autoMatch函数统一处理,自动匹配接口前缀。
- 开发环境可通过webpack proxy配置转发请求。
- 生产环境读取实际配置(根据实际项目的情况)。
autoMath方法:
// utils.jsconst isDev = process.env.NODE_ENV === 'development'; // 开发 or 生产// 匹配接口前缀export function autoMatch (prefix) {let baseUrl = '';if (isDev) {// 开发环境 通过proxy配置转发请求;baseUrl = `/${prefix || 'default'}`;} else {// 生产环境 根据实际配置 根据 prefix 匹配url;// 配置来源 根据实际应用场景更改配置。(1.从全局读取;2.线上配置中心读取)switch (prefix) {case 'baidu':baseUrl = window.LOCAL_CONFIG.baidu;break;case 'alipay':baseUrl = window.LOCAL_CONFIG.alipay;break;default:baseUrl = window.LOCAL_CONFIG.default;}}return baseUrl;}
开发环境接口代理
webpack配置,比如:@vue/CLI3在vue.config.js中配置:
devServer: {proxy: {'/baidu': {target: 'http://10.59.81.31:8088',changeOrigin: true,pathRewrite: { '^/baidu': '' }},'/default': {target: 'http://10.59.81.31:8088',changeOrigin: true,pathRewrite: {'^/default' : ''}},}},
统一配置接口
在一个新文件apiUrl.js中统一管理项目全部的接口,方便维护。
//apiUrl.jsexport const apiUrl = {login: '/api/login',loginOut: '/api/loginOut',qryPageConfig: '/test/qryPageConfig',setPageConfig: '/test/setPageConfig',//...}
生成接口
在index.js中:
import Vue from 'vue';import request from './request';import { apiUrl } from './apiUrl';let services = {};Object.entries(apiUrl).forEach((item) => {services[item[0]] = function (options = {}) {return request(Object.assign({url: item[1]}, options))}})// 将services挂载到vue的原型上// 业务中引用的方法:this.$services.接口名(小驼峰)Object.defineProperty(Vue.prototype, '$services', {value: services});export default services;
在上述代码中,通过读取配置的接口信息(名称和请求路径),生成全部接口。 并挂载到vue的原型上,这样在业务中就可以通过this.$services.接口名直接调用对应的接口。
当然,这是本项目使用的方法。 因具体项目而异,也可以不挂载到Vue原型上,使用services模块;或直接调用request.js封装的请求函数。
业务调用
以下为使用事例:
// index.vue methods中methods: {// 获取页面配置信息接口 get Promise$_qryPageConfig() {this.$services.qryPageConfig({method: 'get', //默认params: {page: 'login'},}).then((res) => {this.pageConfig = res.data;}).finally(() => {...this.close();});},// 设置页面配置接口 post async/await$_order: async function () {this.loading = true;const res = await this.$services.setPageConfig({prefix: 'baidu', //匹配url前缀ifHandleError: true, //不对该接口进行全局错误提示。method: 'post',headers: {'Content-Type': 'application/json; charset=UTF-8',},data: {userId: this.idCard,userName: this.realName,color: 'red',}})this.loading = false;if (res.data) {Total('success');}},}
项目代码
项目代码本身依附于@vue/CLI3构建,是CLI3构建移动H5端应用的一部分。当然通过剥离vue代码,该Axios封装也可以直接应用到其他项目中。
项目源码:
@vue/CLI3构建移动H5端应用:https://www.yuque.com/nowthen/longroad/vue_cli3;
Github链接:https://github.com/now1then/vue-h5-pro;
— end —
