Vue 2 请求封装的最佳实践:基于 Axios 的高效解决方案
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 || '请求失败、请联系管理员'); } );
-
数据解密:如果响应数据被加密,解密并验证哈希值。
(图片来源网络,侵删) -
错误处理:如果响应中的 success 属性为 false,显示错误信息。对于特定错误代码(如登出),弹出确认框并处理。
-
Token 刷新:如果响应头中包含新的 Token,更新 Vuex 中的 Token。
(图片来源网络,侵删)4. 功能亮点
-
统一配置:通过 Axios 实例统一设置超时时间、基础 URL 和默认请求头。
-
Token 管理:自动从 Vuex 中获取 Token 并添加到请求头中,支持 Token 刷新。
(图片来源网络,侵删) -
数据加密与哈希验证:支持 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
-
-
-