前言

如今,在项目中,普遍采用Axios库进行Http接口请求。它是基于promise的http库,可运行在浏览器端和node.js中。此外还有拦截请求和响应、转换JSON数据、客户端防御XSRF等优秀的特性。axios源码

「链接-axios中文文档

考虑到各个项目实际使用时写法混乱,不统一。对Axios进行一下通用化的封装,目的是帮助简化代码和利于后期的更新维护,尽量通用化。

项目说明

项目代码本身依附于@vue/CLI3构建,当然通过剥离vue代码,该Axios封装也可以直接应用到其他项目中。

封装要达成的目标:

  • 统一维护管理接口;
  • 支持接口代理转发;
  • 读取环境配置,区分处理环境。
  • 拦截请求和响应,处理登录超时、404等异常情况;
  • 根据请求的配置匹配接口URL前缀且作支持做特殊处理。

封装

安装axios

  1. npm install axios;

创建实例

通过创建实例,操作实例的方式进行接口请求。

  1. //request.js
  2. import axios from 'axios'; // 引入axios
  3. import Qs from 'qs'; // 引入qs模块,用来序列化post类型的数据
  4. import { autoMatch, checkStatus } from './utils'; // 处理函数
  5. import { Toast } from 'mint-ui'; //提示框
  6. // 创建axios实例
  7. const instance = axios.create({
  8. // baseURL: process.env.BASE_URL,
  9. timeout: 30000, // 请求超时时间
  10. // `transformRequest` 允许在向服务器发送前,修改请求数据
  11. transformRequest: [function (data) {
  12. // 对 data 进行任意转换处理
  13. return data;
  14. }],
  15. // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  16. transformResponse: [function (data) {
  17. // 对 data 进行任意转换处理
  18. return JSON.parse(data);
  19. }]
  20. })

添加请求和响应拦截器

给实例添加请求和响应拦截器,根据实际数据格式进行相应的处理。
比如:
请求时,应后端要求根据Content-type设置data传参格式;
响应时,统一处理登录超时的情况和请求失败的错误提示处理等。

  1. //request.js
  2. // 实例添加请求拦截器
  3. instance.interceptors.request.use(function (config) {
  4. // 在发送请求之前做处理...
  5. config.headers = Object.assign(config.method === 'get' ? {
  6. 'Accept': 'application/json',
  7. 'Content-Type': 'application/json; charset=UTF-8'
  8. } : {
  9. 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
  10. }, config.headers);
  11. if (config.method === 'post') {
  12. const contentType = config.headers['Content-Type'];
  13. // 根据Content-Type转换data格式
  14. if (contentType) {
  15. if (contentType.includes('multipart')) { // 类型 'multipart/form-data;'
  16. // config.data = data;
  17. } else if (contentType.includes('json')) { // 类型 'application/json;'
  18. // 服务器收到的raw body(原始数据) "{name:"nowThen",age:"18"}"(普通字符串)
  19. config.data = JSON.stringify(config.data);
  20. } else { // 类型 'application/x-www-form-urlencoded;'
  21. // 服务器收到的raw body(原始数据) name=nowThen&age=18
  22. config.data = Qs.stringify(config.data);
  23. }
  24. }
  25. }
  26. return Promise.resolve(config);
  27. }, function (error) {
  28. // 对请求错误做处理...
  29. return Promise.reject(error);
  30. });
  31. // 实例添加响应拦截器
  32. instance.interceptors.response.use(function (response) {
  33. // 对响应数据做处理,以下根据实际数据结构改动!!...
  34. // const { reason_code } = response.data || {};
  35. // if (reason_code === '001') { // 请求超时,跳转登录页
  36. // const instance = Toast('请求超时,即将跳转到登录页面...');
  37. // setTimeout(() => {
  38. // instance.close();
  39. // window.location.href = '/login';
  40. // }, 3000)
  41. // }
  42. return Promise.resolve(checkStatus(response));
  43. }, function (error) {
  44. // 对响应错误做处理...
  45. if (error.response) {
  46. return Promise.reject(checkStatus(error.response));
  47. } else if (
  48. error.code == "ECONNABORTED" &&
  49. error.message.indexOf("timeout") != -1
  50. ) {
  51. return Promise.reject({ msg: "请求超时" });
  52. } else {
  53. return Promise.reject({});
  54. }
  55. });

处理错误状态:

  1. //utils.js
  2. export function checkStatus (response) {
  3. const status = response.status || -1000; // -1000 自己定义,连接错误的status
  4. if ((status >= 200 && status < 300) || status === 304) {
  5. // 如果http状态码正常,则直接返回数据
  6. return response.data;
  7. } else {
  8. let errorInfo = '';
  9. switch (status) {
  10. case -1:
  11. errorInfo = '远程服务响应失败,请稍后重试';
  12. break;
  13. case 400:
  14. errorInfo = '400:错误请求';
  15. break;
  16. case 401:
  17. errorInfo = '401:访问令牌无效或已过期';
  18. break;
  19. case 403:
  20. errorInfo = '403:拒绝访问';
  21. break;
  22. case 404:
  23. errorInfo = '404:资源不存在';
  24. break;
  25. case 405:
  26. errorInfo = '405:请求方法未允许'
  27. break;
  28. case 408:
  29. errorInfo = '408:请求超时'
  30. break;
  31. case 500:
  32. errorInfo = '500:访问服务失败';
  33. break;
  34. case 501:
  35. errorInfo = '501:未实现';
  36. break;
  37. case 502:
  38. errorInfo = '502:无效网关';
  39. break;
  40. case 503:
  41. errorInfo = '503:服务不可用'
  42. break;
  43. default:
  44. errorInfo = `连接错误`
  45. }
  46. return {
  47. status,
  48. msg: errorInfo
  49. }
  50. }
  51. }

封装实例

实例封装到async\await异步函数中。

  1. //request.js
  2. const request = async function (opt) {
  3. try {
  4. const options = Object.assign({
  5. method: 'get',
  6. ifHandleError: true // 是否统一处理接口失败(提示)
  7. }, opt);
  8. // 匹配接口前缀 开发环境则通过proxy配置转发请求; 生产环境根据实际配置
  9. options.baseURL = autoMatch(options.prefix);
  10. const res = await instance(options);
  11. // console.log(res);
  12. if (!res.success && options.ifHandleError) { // 自定义参数,是否允许全局提示错误信息
  13. Toast(res.error || '请求处理失败!')
  14. }
  15. return res;
  16. } catch (err) {
  17. if (options.ifHandleError) { // 自定义参数,是否允许全局提示错误信息
  18. Toast(err.msg || '请求处理失败!')
  19. }
  20. return err;
  21. }
  22. }
  1. 此处可以增加对请求入参进行合并或默认值处理;
  2. 自定义参数配置

项目中统一处理报错提示。但有时对于接口请求比较多的情况,很多时候并不希望某些接口被全局报错提示。这时这种接口可以设置自定义的ifHandleError参数处理,不进行全局错误提示,而是到业务代码中再做处理。
同理,其他的一些特殊业务情况也可以通过自定义参数处理。

  1. 根据自定义的prefix参数匹配接口前缀;

对于项目中请求多个后端的接口时,每个接口在请求封装时附带prefix参数,根据prefix的值autoMatch函数统一处理,自动匹配接口前缀。

  • 开发环境可通过webpack proxy配置转发请求。
  • 生产环境读取实际配置(根据实际项目的情况)。

autoMath方法:

  1. // utils.js
  2. const isDev = process.env.NODE_ENV === 'development'; // 开发 or 生产
  3. // 匹配接口前缀
  4. export function autoMatch (prefix) {
  5. let baseUrl = '';
  6. if (isDev) {
  7. // 开发环境 通过proxy配置转发请求;
  8. baseUrl = `/${prefix || 'default'}`;
  9. } else {
  10. // 生产环境 根据实际配置 根据 prefix 匹配url;
  11. // 配置来源 根据实际应用场景更改配置。(1.从全局读取;2.线上配置中心读取)
  12. switch (prefix) {
  13. case 'baidu':
  14. baseUrl = window.LOCAL_CONFIG.baidu;
  15. break;
  16. case 'alipay':
  17. baseUrl = window.LOCAL_CONFIG.alipay;
  18. break;
  19. default:
  20. baseUrl = window.LOCAL_CONFIG.default;
  21. }
  22. }
  23. return baseUrl;
  24. }

开发环境接口代理

webpack配置,比如:@vue/CLI3在vue.config.js中配置:

  1. devServer: {
  2. proxy: {
  3. '/baidu': {
  4. target: 'http://10.59.81.31:8088',
  5. changeOrigin: true,
  6. pathRewrite: { '^/baidu': '' }
  7. },
  8. '/default': {
  9. target: 'http://10.59.81.31:8088',
  10. changeOrigin: true,
  11. pathRewrite: {'^/default' : ''}
  12. },
  13. }
  14. },

统一配置接口

在一个新文件apiUrl.js中统一管理项目全部的接口,方便维护。

  1. //apiUrl.js
  2. export const apiUrl = {
  3. login: '/api/login',
  4. loginOut: '/api/loginOut',
  5. qryPageConfig: '/test/qryPageConfig',
  6. setPageConfig: '/test/setPageConfig',
  7. //...
  8. }

生成接口

index.js中:

  1. import Vue from 'vue';
  2. import request from './request';
  3. import { apiUrl } from './apiUrl';
  4. let services = {};
  5. Object.entries(apiUrl).forEach((item) => {
  6. services[item[0]] = function (options = {}) {
  7. return request(Object.assign({
  8. url: item[1]
  9. }, options))
  10. }
  11. })
  12. // 将services挂载到vue的原型上
  13. // 业务中引用的方法:this.$services.接口名(小驼峰)
  14. Object.defineProperty(Vue.prototype, '$services', {
  15. value: services
  16. });
  17. export default services;

在上述代码中,通过读取配置的接口信息(名称和请求路径),生成全部接口。 并挂载到vue的原型上,这样在业务中就可以通过this.$services.接口名直接调用对应的接口。

当然,这是本项目使用的方法。 因具体项目而异,也可以不挂载到Vue原型上,使用services模块;或直接调用request.js封装的请求函数。

业务调用

以下为使用事例:

  1. // index.vue methods中
  2. methods: {
  3. // 获取页面配置信息接口 get Promise
  4. $_qryPageConfig() {
  5. this.$services.qryPageConfig({
  6. method: 'get', //默认
  7. params: {
  8. page: 'login'
  9. },
  10. }).then((res) => {
  11. this.pageConfig = res.data;
  12. }).finally(() => {
  13. ...
  14. this.close();
  15. });
  16. },
  17. // 设置页面配置接口 post async/await
  18. $_order: async function () {
  19. this.loading = true;
  20. const res = await this.$services.setPageConfig({
  21. prefix: 'baidu', //匹配url前缀
  22. ifHandleError: true, //不对该接口进行全局错误提示。
  23. method: 'post',
  24. headers: {
  25. 'Content-Type': 'application/json; charset=UTF-8',
  26. },
  27. data: {
  28. userId: this.idCard,
  29. userName: this.realName,
  30. color: 'red',
  31. }
  32. })
  33. this.loading = false;
  34. if (res.data) {
  35. Total('success');
  36. }
  37. },
  38. }

项目代码

项目代码本身依附于@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 —