Vue 2 请求封装的最佳实践:基于 Axios 的高效解决方案

06-01 1090阅读
1. 引言

在 Vue.js 开发中,与后端服务器的通信是必不可少的。axios 是一个功能强大的 HTTP 客户端,广泛用于 Vue 项目中。然而,直接使用 axios 可能会导致代码重复和难以维护。通过封装 axios,我们可以实现统一的请求管理、错误处理和数据安全机制,从而提高开发效率和代码质量。

本文将介绍一个基于 Vue 2 和 axios 的请求封装方案,帮助你构建更高效、更安全的前端项目。

2. 为什么需要封装请求

在大型 Vue 项目中,直接使用 axios 存在以下问题:

  • 重复代码:每次请求都需要设置相同的配置(如超时时间、基础 URL、Token)。

  • 错误处理分散:不同地方的请求需要单独处理错误,容易遗漏。

  • 数据安全问题:请求数据和响应数据可能需要加密或验证,直接使用 axios 无法实现。

  • 缺乏统一管理:请求和响应的逻辑分散在各个组件中,难以维护。

    封装请求模块可以解决这些问题,实现统一的配置、错误处理和数据安全机制。

    3. 封装代码解析

    import axios from 'axios';
    import store from '@/store';
    import QS from 'qs';
    import { Message, MessageBox } from 'element-ui';
    import { getToken } from '@/utils/auth';
    import { sha256 } from 'js-sha256';
    import { isJSONString } from './index';
    import { encrypt, decrypt } from './aes';
    const service = axios.create({
      timeout: 60000,
      baseURL: process.env.VUE_APP_BASE_API,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
        'Access-Control-Allow-Origin': '*'
      }
    });
    3.1 请求拦截器

    请求拦截器的主要作用是在发送请求之前对请求数据进行处理,例如添加 Token、加密数据、设置请求头等。

    service.interceptors.request.use(
      config => {
        if (store.getters.token) {
          config.headers['Authorization'] = getToken();
        }
        const obj = config.data;
        if (obj) {
          console.log(typeof (obj));
          if (typeof (obj) === 'string') {
            var jsonString = isJSONString(obj);
            if (!jsonString) {
              return Promise.reject(jsonString, '参数不是JSON格式,无法解析');
            }
            config.headers['Content-Type'] = 'application/json; charset=UTF-8';
            if (store.getters.hash === true && !!store.state.security.aes) {
              config.headers['request-hash'] = sha256.sha256(obj);
              config.data = encrypt(obj);
            }
          } else {
            if (store.getters.hash === true && !!store.state.security.aes) {
              config.headers['Content-Type'] = 'application/json; charset=UTF-8';
              const str = JSON.stringify(obj);
              config.data = encrypt(str);
              config.headers['request-hash'] = sha256.sha256(str);
            } else {
              config.data = QS.stringify(obj, { arrayFormat: 'repeat' });
              config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
            }
          }
        }
        console.log(config);
        return config;
      },
      error => {
        Promise.reject(error);
      }
    );
    • 添加 Token:从 Vuex 中获取 Token 并添加到请求头中。

    • 数据加密:如果启用了加密机制(store.state.security.aes),对请求数据进行加密,并生成哈希值用于验证。

    • 内容类型:根据请求数据的类型(JSON 或表单数据),动态设置 Content-Type。

      3.2 响应拦截器

      响应拦截器用于处理服务器返回的数据,例如解密数据、验证哈希值、统一处理错误等。

      service.interceptors.response.use(
        response => {
          if (response.request.responseType === 'blob') {
            return response;
          } else {
            const responseHash = response.headers['response-hash'];
            let res = response.data;
            if (responseHash && store.getters.hash === true && store.state.security.aes) {
              const obj = decrypt(res);
              if (responseHash === sha256.sha256(obj)) {
                res = JSON.parse(obj);
                response.data = res;
              } else return Promise.reject(responseHash, '得到的结果与期望的不一致');
            }
            if (res.success !== true) {
              Message({
                message: res.message || '请求失败、请联系管理员',
                type: 'error',
                duration: 5 * 1000
              });
              if (res.code === '2018020801') {
                MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
                  confirmButtonText: '重新登录',
                  cancelButtonText: '取消',
                  type: 'warning'
                }).then(() => {
                  store.dispatch('FedLogOut').then(() => {
                    location.reload();
                  });
                });
              }
              return Promise.reject(res);
            } else {
              const authorization = response.headers['authorization'];
              if (authorization) { store.dispatch('RefreshToken', authorization); }
              return response;
            }
          }
        },
        error => {
          console.log('err' + error);
          if (error.config.url === '/logout') {
            store.dispatch('FedLogOut').then(() => {
              location.reload();
            });
          } else {
            Message({
              message: error.message || error,
              type: 'error',
              duration: 5 * 1000
            });
          }
          return Promise.reject(error || '请求失败、请联系管理员');
        }
      );
      • 数据解密:如果响应数据被加密,解密并验证哈希值。

        Vue 2 请求封装的最佳实践:基于 Axios 的高效解决方案
        (图片来源网络,侵删)
      • 错误处理:如果响应中的 success 属性为 false,显示错误信息。对于特定错误代码(如登出),弹出确认框并处理。

      • Token 刷新:如果响应头中包含新的 Token,更新 Vuex 中的 Token。

        Vue 2 请求封装的最佳实践:基于 Axios 的高效解决方案
        (图片来源网络,侵删)
        4. 功能亮点
        • 统一配置:通过 Axios 实例统一设置超时时间、基础 URL 和默认请求头。

        • Token 管理:自动从 Vuex 中获取 Token 并添加到请求头中,支持 Token 刷新。

          Vue 2 请求封装的最佳实践:基于 Axios 的高效解决方案
          (图片来源网络,侵删)
        • 数据加密与哈希验证:支持 AES 加密和 SHA-256 哈希验证,增强数据安全性。

        • 错误处理:统一处理请求失败和错误提示,避免重复代码。

        • 灵活的数据格式支持:支持表单数据和 JSON 数据的发送,根据后端需求动态调整。

          5. 使用方法

          在项目中引入封装好的 service 实例即可使用:

          import request from "@/api/axios/request";
          //get路径传参
          export function getYourInterfaceById(id) {
            const url = `/yourInterface/${id}`;
            return request({
              url: url,
              method: "get"
            });
          }
          //get
          export function getYourInterface(data) {
            return request({
              url: "/yourInterface",
              method: "get",
              params: data
            });
          }
          //post
          export function postYourInterface(data) {
            // data = data.data ? data : { data }
            return request({
              url: '/post/yourInterface',
              method: 'post',
              data: data
            })
          }
          6. 注意事项
        • 跨域问题:虽然代码中设置了 Access-Control-Allow-Origin,但实际跨域问题需要后端支持。

        • Token 刷新机制:需要确保 Vuex 中的 Token 管理逻辑正确。

        • 加密与解密:确保加密和解密的密钥一致,避免数据无法正确解析。

        • 错误代码:根据项目需求自定义错误代码和处理逻辑。

          7. 总结与展望

          通过封装 axios,我们实现了一个高效、安全且易于维护的请求模块。它不仅减少了重复代码,还提高了项目的整体质量。未来,可以进一步优化封装,例如支持动态切换基础 URL 或集成更多安全机制。

          希望本文对你有所帮助!如果你有任何问题或建议,欢迎留言交流。

        • 附录:(完整封装请求代码)

          import axios from 'axios'
          import store from '@/store'
          import QS from 'qs'
          import { Message, MessageBox } from 'element-ui'
          import { getToken } from '@/utils/auth'
          import { sha256 } from 'js-sha256'
          import { isJSONString } from './index'
          import { encrypt, decrypt } from './aes'
          const service = axios.create({
            timeout: 60000,
            baseURL: process.env.VUE_APP_BASE_API,
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
              'Access-Control-Allow-Origin': '*'
            }
          })
          /**
           * 通用情况下,会采用application/x-www-form-urlencoded;charset=UTF-8 作为请求头;
           * 如果后台controller中,参数有RequestBody注解,则前端参数必须先JSON.stringify();
           * 后台禁止参数是简单JAVA对象的情况下带@RequestBody注解
           */
          service.interceptors.request.use(
            config => {
              if (store.getters.token) {
                config.headers['Authorization'] = getToken()
              }
              const obj = config.data
              if (obj) {
                console.log(typeof (obj))
                // @RequestBody
                if (typeof (obj) === 'string') {
                  var jsonString = isJSONString(obj)
                  if (!jsonString) {
                    return Promise.reject(jsonString, '参数不是JSON格式,无法解析')
                  }
                  config.headers['Content-Type'] = 'application/json; charset=UTF-8'
                  if (store.getters.hash === true && !!store.state.security.aes) {
                    config.headers['request-hash'] = sha256.sha256(obj)
                    config.data = encrypt(obj)
                  }
                } else {
                  if (store.getters.hash === true && !!store.state.security.aes) {
                    config.headers['Content-Type'] = 'application/json; charset=UTF-8'
                    const str = JSON.stringify(obj)
                    config.data = encrypt(str)
                    config.headers['request-hash'] = sha256.sha256(str)
                  } else {
                    config.data = QS.stringify(obj, { arrayFormat: 'repeat' })
                    config.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
                  }
                }
              }
              console.log(config)
              return config
            },
            error => {
              Promise.reject(error)
            }
          )
          service.interceptors.response.use(
            response => {
              if (response.request.responseType === 'blob') {
                return response
              } else {
                const responseHash = response.headers['response-hash']
                let res = response.data
                if (responseHash && store.getters.hash === true && store.state.security.aes) {
                  const obj = decrypt(res)
                  if (responseHash === sha256.sha256(obj)) {
                    res = JSON.parse(obj)
                    response.data = res
                  } else return Promise.reject(responseHash, '得到的结果与期望的不一致')
                }
                if (res.success !== true) {
                  Message({
                    message: res.message || '请求失败、请联系管理员', type: 'error', duration: 5 * 1000
                  })
                  if (res.code === '2018020801') {
                    MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录', '确定登出', {
                      confirmButtonText: '重新登录',
                      cancelButtonText: '取消',
                      type: 'warning'
                    }).then(() => {
                      store.dispatch('FedLogOut').then(() => {
                        location.reload() // 需要重新实例化vue-router对象 避免bug
                      })
                    })
                  }
                  return Promise.reject(res)
                } else {
                  const authorization = response.headers['authorization']
                  if (authorization) { store.dispatch('RefreshToken', authorization) }
                  return response
                }
              }
            },
            error => {
              console.log('err' + error) // for debug
              if (error.config.url === '/logout') {
                store.dispatch('FedLogOut').then(() => {
                  location.reload() // 需要重新实例化vue-router对象 避免bug
                })
              } else {
                Message({
                  message: error.message || error,
                  type: 'error',
                  duration: 5 * 1000
                })
              }
              return Promise.reject(error || '请求失败、请联系管理员')
            }
          )
          export default service
          
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码