优化后的目标包括:
- 签名的生成:抽离签名生成逻辑,使得代码更加简洁、复用性更高。
- 请求去重:减少重复请求,提高性能,避免不必要的请求发送。
- 请求配置与错误处理:优化请求配置和错误处理,使代码更易于理解和维护。
准备工作:目前代码基于 vue + element-plus + axios + md5 + dayjs + crypto-js(注:本项目的加密方式是hmac-sha256 加密传输)
"axios": "^1.6.8",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"element-plus": "2.7.0",
"js-cookie": "^3.0.5",
"js-md5": "^0.8.3",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^4.2.0",
"readify-manager": "file:",
"vue": "^3.4.21",
"vue-error-boundary": "^2.0.3",
"vue-router": "^4.3.0"
1. 优化后的 api.js 更简洁、清晰
// api.js 部分
import { HTTP_Request } from '../axios' // 具体位置具体导入
// 付费套餐分类列表
export const getBillingCategoryList = params => {
return HTTP_Request.get('/admin/categoryList', params) // 具体路径具体输入
}
// 添加付费套餐分类
export const addBillingCategory = params => {
return HTTP_Request.post('/admin/addCategory', params)
}
// 删除添加付费套餐分类
export const deleteBillingCategory = params => {
return HTTP_Request.delete(`/admin/delCategory/${params.id}`)
}
// 更新付费套餐分类
export const updateBillingCategory = params => {
return HTTP_Request.put('/admin/editCategory', params)
}
...
2. 封装的 axios.js 部分
// axios.js
import axios from 'axios'
import { sortObjASCII } from '@/utils'
import { Message } from '@/utils/message'
import Cookies from 'js-cookie'
import md5 from 'js-md5'
import router from '@/router'
import CryptoJS from 'crypto-js'
const CancelToken = axios.CancelToken
const pending = new Map()
// 创建实例
class HttpRequest {
constructor (baseURL) {
this.baseURL = baseURL
this.timeout = 1000 * 60 * 2
this.withCredentials = true
}
// 中间件
middleRequest (options = {}) {
const instance = axios.create()
const config = {
...options,
baseURL: this.baseURL,
timeout: this.timeout,
withCredentials: this.withCredentials,
headers: {
Authorization: 'Bearer ' + Cookies.get('token')
// 'Content-Type': 'application/json'
}
}
if (options.data instanceof FormData) {
// 如果是文件上传,Content-Type 应该是 multipart/form-data,axios 会自动设置
config.headers['Content-Type'] = 'multipart/form-data'
} else {
// 默认设置 JSON 格式
config.headers['Content-Type'] = 'application/json'
}
this.setInterceptors(instance, options.url)
return instance(config)
}
// GET
get (url, params, options = {}) {
const GET = this.middleRequest({
method: 'get',
url: url,
params: params,
...options
})
return GET
}
// POST
post (url, params, options = {}) {
const POST = this.middleRequest({
method: 'post',
url: url,
data: params,
...options
})
return POST
}
// PUT
put (url, params, options = {}) {
const PUT = this.middleRequest({
method: 'put',
url: url,
data: params,
...options
})
return PUT
}
// PUT
patch (url, params, options = {}) {
const PATCH = this.middleRequest({
method: 'patch',
url: url,
data: params,
...options
})
return PATCH
}
// DELETE
delete (url, params, options = {}) {
const DELETE = this.middleRequest({
method: 'delete',
url: url,
data: params,
...options
})
return DELETE
}
// appid:dkFv5OrWyQhte1ZG appsecret : d34cea92316d1da113135158bed9ca97 sign_secret: &4F2g4Y6b4L4R1
// 签名
sign (config) {
const appid = 'dkFv5OrWyQhte1ZG'
const appsecret = 'd34cea92316d1da113135158bed9ca97'
const Timestamp = Date.parse(new Date()).toString()
const Url = decodeURIComponent(config.baseURL + config.url)
const signparamsData = config.data || config.params
let signData = { appid, appsecret, Timestamp, Url, ...signparamsData }
signData = sortObjASCII(signData)
let signStr = Object.entries(signData)
.map(([key, val]) => `${key}=${val}`)
.join('&')
config.headers.appid = appid
config.headers.appsecret = appsecret
config.headers.sign_secret = md5(md5(signStr))
config.headers.Timestamp = Timestamp
}
// 撤销重复请求
changePending (config) {
this.sign(config)
const url = [config.method, config.url].join('&')
config.cancelToken =
config.cancelToken ||
new CancelToken(cancel => {
if (pending.has(url)) {
pending.set(url, cancel)
} else if (pending.has(url)) {
const cancel = pending.get(url)
cancel(url)
pending.delete(url)
}
})
return config
}
// 清空请求
clearPending () {
for (const [url, cancel] of this.pending) {
cancel(url)
}
this.pending.clear()
}
// 拦截器
setInterceptors (instance) {
this.requestInterceptors(instance)
this.responseInterceptors(instance)
}
// 请求拦截器
requestInterceptors (instance) {
instance.interceptors.request.use(
config => {
return this.changePending(config)
},
err => {
return Promise.reject(err)
}
)
}
// 响应拦截器
/**
* code:
* 400:请求出错
* 401:登录失效,cookie中无Authorization字段
* 403:请求被拒绝
* 404:请求未找到
* 500:服务异常
*
*/
responseInterceptors (instance) {
instance.interceptors.response.use(
response => {
if (
response.status >= 200 &&
response.status < 300 &&
response.data.code == 200
) {
return Promise.resolve(response.data)
} else {
// The token has expired, please log in again
if ([401].includes(response.status || response.data.code)) {
Message.error(response.data.message)
Cookies.remove('token')
router.push('/login', { replace: true })
localStorage.clear()
sessionStorage.clear()
return Promise.reject(response.data)
}
Message.error(response.data.message)
return Promise.reject(response.data)
}
},
error => {
console.log(error)
// The token has expired, please log in again
if ([401].includes(error.status)) {
Cookies.remove('token')
router.push('/login', { replace: true })
return Promise.reject(error.response?.data || error)
}
if (['ERR_NETWORK'].includes(error.code)) {
Message.error(error.message)
return Promise.reject(error)
}
return Promise.reject(error.response?.data || error)
}
)
}
}
const HTTP_Request = new HttpRequest(import.meta.env.VITE_BASE_URL)
export { HTTP_Request }
3. utils/sortObjASCII.js 部分
// utils/sortObjASCII.js 部分
// 引入day.js
import dayjs from "dayjs"
import HmacSHA256 from "crypto-js/hmac-sha256";
// data type
export const getDataType = (data) => {
return Object.prototype.toString.call(data).match(/\s(\w+)/)[1]
}
// isBlob
export const isBlob = (data) => {
const obj = new Blob([data])
return obj instanceof Blob ? true : false
}
// isNaN
export const isNumber = val => {
return !isNaN(parseFloat(val)) && isFinite(val);
}
// format date
export const formatDate = (date) => {
if (!date) return
let formatDate = dayjs(date).format('YYYY-MM-DD HH:mm:ss')
return formatDate
}
// format money 保留两位小数
export const formatMoney = (val, prfix = 'CA$') => {
if (!val) return prfix + '0.00'
let formatMoney = parseFloat(val).toFixed(2);
return prfix + formatMoney
}
// 获取对象排序
export const sortObjASCII = (data) => {
let sortObj = {}
const jsonString = JSON.stringify(data);
const obj = JSON.parse(jsonString);
let arr = Object.keys(obj).sort()
if (arr.length === 0) return {}
for (let ii in arr) {
sortObj[arr[ii]] = obj[arr[ii]]
}
return sortObj
}
// blob转base64
export const blobToBase64 = (blob) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
// 是否中文
export const containsChinese = (val) => {
const chineseRegex = /[\u4e00-\u9fff]/;
return chineseRegex.test(val);
}
// 延时函数
export const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));
export const encryptHmacSHA256 = (data) => {
// 密钥
const secretKey = "fA3bX9zKp2LmTqY7";
const hash = HmacSHA256(data, secretKey).toString();
return hash;
};