架构二次封装需要实现如下功能
- API下发
- 取消多余请求 - 统一处理
- HTTP错误统一处理
- 服务器特殊错误处理
- API-mock方案
封装目标:
1)页面只管API调用,与服务端的接口定义限制在封装结构中。
2)在封装结构中设计开关,切换api-mock时完全不需要页面进行修改。
3)前后端分离的更干脆,前端页面更干净,接口简洁易懂,只处理返回逻辑。
上代码
# Axios二次封装Service
文件 api/service.js
import axios from 'axios'
import { Message } from 'element-ui'
const getUrlKey = config => `SYSNAME_PC_${config.url}`
const cancelToken = {} // 被取消的请求池
const CancelToken = axios.CancelToken
/**
* @func 取消多余请求-统一处理
* @desc type 操作类别
* @desc config 当前请求的配置
*/
const cancelThis = (type, config) => {
const key = getUrlKey(config)
if (type === 'check') {
if (cancelToken[key]) {
cancelToken[key]()
} else {
config.cancelToken = new CancelToken(c => {
cancelToken[key] = c
})
}
} else if (type === 'remove') {
delete cancelToken[key]
}
}
const cancelTokenWhiteList = []
/**
* @func axios二次封装为service
*/
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_URL,
withCredentials: true, // 跨域携带 cookie
timeout: 15000
})
// request interceptor
axios.interceptors.request.use(
config => {
if (cancelTokenWhiteList.indexOf(config.url) !== -1) {
cancelThis('check', config)
}
config.crossDomain = true
config.headers.common = {
'Content-Type': 'application/json;charset=UTF-8',
'X-ER-System-Info': 'default',
'Accept-Language': 'zh-CN'
}
return config
},
err => {
console.log('request interceptor err:', err)
return Promise.reject(err)
}
)
// response interceptor
axios.interceptors.response.use(
async response => {
cancelThis('remove', response.config)
const result = response.data || {}
const { code, data, message } = result
if (code === '9999' || code === '-1') {
Message.closeAll()
Message.error(message || '服务器异常,请稍后重试或联系我们')
return Promise.reject(new Error(message || 'Error'))
} else {
return data
}
},
// http request err
err => {
console.log('response interceptor error: ', err)
const res = err.response
// 请求执行成功,属Http Code错误
if (res) {
const { status, data } = JSON.parse(JSON.stringify(res)) || {}
if (Number(status) === 500) {
Message.error(data.message || '服务器异常,请您稍后重试或联系我们')
} else {
handleHttpCodeError(Number(status))
}
} else {
const ErrStr = err.toString() || ''
if (ErrStr.toString().indexOf('Cancel') !== -1) { // 请求被主动取消,忽略
return
} else {
// 请求执行失败,断网/超时
handleNotExceptError(ErrStr)
}
}
}
)
/**
* @func 通用的HttpCode异常处理
* @param {Number} code http异常码
*/
function handleHttpCodeError(code) {
const HttpError = {
400: '请求错误',
403: '拒绝访问',
404: '请求地址错误',
408: '请求超时',
500: '服务器开个小差,请稍后再试',
501: '服务器开个小差,请稍后再试',
502: '服务器开个小差,请稍后再试',
503: '服务器开个小差,请稍后再试',
504: '服务器开个小差,请稍后再试',
505: 'HTTP版本不受支持'
}
Message.error(HttpError[code])
}
/**
* @func 请求执行失败,断网/超时/未知错误
* @param {Object} err 错误信息载体
*/
function handleNotExceptError(ErrStr) {
Message.closeAll()
if (ErrStr.indexOf('Request failed') !== -1) {
Message.error('当前网络不可用,请检查网络设置')
} else if (ErrStr.indexOf('timeout') !== -1) {
Message.error('您当前网络较差,请切换网络活稍后重试')
} else {
Message.error('发生未知错误,请稍后重试')
}
}
export default service
# API封装
同级文件, api/login.js
// api/login.js
import service from './service.js'
//实现api-mock
import {
initLoginMock,
getUserInfoMock
} from './api-mock/login.js'
const mock = process.env.VUE_APP_MOCK === 'true'
// post
export function initLogin(data) {
// mock-api策略仁者见仁、智者见智,也可以通过url匹配取值,本方案个人比较喜欢。
if (mock) return initLoginMock()
return service({
url: 'v1/user/login',
method: 'post',
data
})
}
// get
export function getUserInfo(params) {
if (mock) return getUserInfoMock()
return service({
url: 'v1/user/getUserInfo',
method: 'get',
params
})
}
# API调用
页面 login.vue
<script>
import { initLogin, getUserInfo } from '@/api/login.js'
// 登录页初始化
initLogin({macAdress: 'adress'}).then(data => {
console.log(data)
}).catch(e => {
console.log(e)
})
</script>