前端廢物的自救之路(1)實現(xiàn)axios攔截器:對Nuxt.js中的@nuxtjs/axios進行封裝
前言
手頭有一個去年五月寫的遺留項目,以前使用的是Spring Boot+Thymeleaf+AmazeUI(前臺)+X-admin(后臺),由于用戶反映有許多需要改進的地方(并且打開項目一看,那代碼我自己也看不懂了,而且以現(xiàn)在的視角看那些代碼真的是爛到極致),故進行徹底重構(gòu)。
由于前臺需要SEO,并且考慮到上一版中使用AmazeUI+Thymeleaf開發(fā)的體驗極為痛苦,且新的后端(Gin)的模板引擎十分惡心(可能有人不這么認為,反正我認為Gin的模板語法比Thymeleaf惡心多了),于是前端選擇了Nuxt.js。并且,由于前臺對響應(yīng)式有需求,而且客戶認為Vuetify太花哨,iView基礎(chǔ)版的組件又不夠用,所以UI框架前后臺統(tǒng)一使用Ant Design Vue。
注:后端使用的是
Gin,并且實現(xiàn)了DDD和CQS。關(guān)于這個項目的后端我們過段時間再另行討論。但是為了解釋部分前端代碼,這一系列文章中可能也會貼出一些后端代碼以供參考。其中前端代碼會給出具體的文件名和路徑,后端代碼會在代碼前標識為“后端代碼”。
由于網(wǎng)絡(luò)上使用Nuxt.js+Ant Design Vue的文章實在是太少了,作為一個后端一天能寫600+行(以前用Java的時候一天最多甚至能寫1200+行),前端一天寫100行能工作的代碼就滿足了的前端廢物,為了讓以后再寫類似的項目的時候能不這么肉疼,于是決定記錄下本次開發(fā)的過程,不為別的,就為自己以后再用Nuxt和Ant Design Vue的時候少掉幾根頭發(fā)。
雖然這個項目不能開源,但是考慮到以后可能還會做類似的項目,并且類似的解決方案的Demo實在是太少了,所以在這個項目完結(jié)后我也會根據(jù)我的開發(fā)過程整理一個開源的種子項目發(fā)布在GitHub上以供參考和使用。
為什么需要axios攔截器?
項目本身是前后端分離的,鑒權(quán)使用的是JWT。由于后端是根據(jù)請求頭中的X-Token字段進行鑒權(quán)的,所以需要在每個請求的請求中放入Token。但是我們不可能在每個接口里都這么做,最佳的解決方案當然是利用axios攔截器來完成這個工作。
另外,后端所有的響應(yīng)無論成功還是失敗都具有統(tǒng)一的格式:
后端代碼
// @Description 生成一個成功請求的標準響應(yīng)格式的JSON
// @param data interface{} 響應(yīng)中要攜帶的數(shù)據(jù)
// @return result 生成的JSON
func Success(data interface{}) (result gin.H) {
result = gin.H{
"code": Ok,
"message": "Success",
"data": data,
}
return
}
// @Description 生成一個失敗請求的標準響應(yīng)格式的JSON
// @param errorCode ErrorCode 錯誤碼
// @param message string 要顯示給用戶的錯誤信息
// @return result 生成的JSON
func Error(errorCode ErrorCode, message string) (result gin.H) {
result = gin.H{
"code": errorCode,
"message": message,
"data": nil,
}
return
}
我們也不可能在每個接口里都去解析這個格式,這樣不僅不優(yōu)雅還很容易出錯,這時候就需要axios攔截器對響應(yīng)做統(tǒng)一的處理了。
封裝@nuxtjs/axios
由于Nuxt.js對axios做了封裝,所以使用起來與直接使用axios有一些不同,在實現(xiàn)axios攔截器時與直接使用axios也不太一樣。
注:我在新建項目的時候就選擇了axios,所以我的項目創(chuàng)建出來是自帶
@nuxtjs/axios這個依賴包的,如果沒有的話可以使用npm自行安裝。如果使用的是我這篇文章所講的方法,安裝時要安裝的包是@nuxtjs/axios而不是axios。
添加axios攔截器插件
與在Vue中配置axios攔截器不同,在Nuxt.js中配置axios攔截器使用的是Nuxt.js的插件機制。要配置axios攔截器,我們需要在@/plugins目錄下新建一個axios.js(名稱無所謂,只要是.js文件就可以了,但是該文件的名稱與配置文件中的名稱要對應(yīng))文件,在這個文件中進行具體的配置:
由于前端才剛剛開始寫,所以這個文件的內(nèi)容非常不全面,僅供參考,以后如果添加了新的重要的邏輯,會在后續(xù)文章中對這部分代碼進行編輯。
@/plugins/axios.js
import { message } from 'ant-design-vue'
export default function ({ store, redirect, app: { $axios } }) {
// 后端接口地址
$axios.defaults.baseURL = 'http://127.0.0.1:19090/api/'
// Request攔截器:設(shè)置Token
$axios.onRequest((config) => {
// TODO 使用Vuex存儲Token,并做持久化處理
// config.headers.common['X-Token'] = store.state.token
})
// Error攔截器:出現(xiàn)錯誤的時候被調(diào)用,根據(jù)狀態(tài)碼做對應(yīng)判斷并顯示全局Message
$axios.onError((error) => {
const code = parseInt(error.response && error.response.status)
switch (code) {
// 未登錄
case 401:
redirect('/login')
break
default:
break
}
// 使用Ant Design Vue的message模塊顯示異常信息
message.error(error.response.data.message, 5)
})
// Response攔截器:對正常返回的數(shù)據(jù)進行處理
$axios.onResponse((response) => {
return response.data
})
}
$axios.onRequest、$axios.onResponse和$axios.onError是Nuxt.js提供的方法,類似于直接使用axios時的$axios.interceptors.response.use方法,可以在這些方法中完成對請求信息和響應(yīng)信息的判斷與處理。這些方法與直接使用axios最大的不同就是默認情況下不必返回任何內(nèi)容(當然,onResponse還是需要返回相應(yīng)數(shù)據(jù)response.data,也就是響應(yīng)體本身(JSON),包括了message和code——當然如果不需要這兩部分的話也可以返回response.data.data),例如onRequest方法不必返回config等。
下面貼出純凈版的代碼供大家使用:
@/plugins/axios.js
export default function ({ store, redirect, app: { $axios } }) {
$axios.onRequest((config) => {
})
$axios.onError((error) => {
})
$axios.onResponse((response) => {
})
}
使用Ant Design Vue顯示全局消息
由于我在初始化項目的時候UI框架選擇了
Ant Design Vue,所以項目里直接就整合好了Ant Design Vue,需要的時候直接引入就可以了——但是目前項目中沒有配置按需引入,所以前端性能可能會不太好,最后會配置一下按需引入。如果沒有選擇的話,可以使用npm進行安裝,再使用Nuxt插件進行整合。
可以看到我的代碼中有這么一句:
// 使用Ant Design Vue的message模塊顯示異常信息
message.error(error.response.data.message, 5)
因為對于錯誤信息,個人認為使用全局顯示的方法比組件內(nèi)單獨顯示更好,因為這樣可以更方便地做全局處理(當然也有人不這么認為,那樣的話可以在組件內(nèi)單獨使用Alert組件),所以每當請求出現(xiàn)錯誤的時候,都需要全局顯示這樣一條錯誤信息。與在組件內(nèi)顯示錯誤信息使用this.$message.error等不同,Ant Design Vue也給出了全局顯示錯誤信息的方法:
組件提供了一些靜態(tài)方法,使用方式和參數(shù)如下:
message.success(content, [duration], onClose)message.error(content, [duration], onClose)message.info(content, [duration], onClose)message.warning(content, [duration], onClose)message.warn(content, [duration], onClose)// alias of warningmessage.loading(content, [duration], onClose)——Ant Design Vue官方文檔
記得使用之前先導(dǎo)入message模塊:
import { message } from 'ant-design-vue'
效果:

在Nuxt.js中配置插件
有了插件之后還需要在Nuxt的配置文件nuxt.config.js中進行配置才能真正生效:
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
'@/plugins/antd-ui',
'@/plugins/axios'
],
注意這里的文件名是剛剛在@/plugins目錄下新建.js文件時所使用的文件名。我的文件名是axios.js所以這里填寫@/plugins/axios,如果是axios-config.js就需要填寫@/plugins/axios-config,以此類推。
組件內(nèi)使用
一般方法
以登錄為例:
@/pages/login.vue
methods: {
login () {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.$axios.post('/login', {
username: this.form.username,
password: this.form.password
}).then((res) => {
// TODO 登錄后的Token保存、跳轉(zhuǎn)等操作
this.$message.success(res.data.username, 5)
})
} else {
return false
}
})
},
reset () {
this.$refs.loginForm.resetFields()
}
}
與沒有axios攔截器的方法完全相同。
效果:

asyncData()方法
以獲取一個測試接口的信息為例,后端提供了一個測試接口/api/admin/test,攜帶Token進行請求的話會返回成功信息:
后端代碼
// TODO admin測試
adminGroup.GET("/test", func(c *gin.Context) {
c.JSON(http.StatusOK, response.Success("/api/admin/test: Admin Test Succeeded!??"))
})
在@/pages/admin目錄下新建一個測試用的頁面并在asyncData()方法中請求對應(yīng)數(shù)據(jù)(在請求之前需要設(shè)置一下Token。這里因為我還沒整合Vuex,直接寫死了,所以對應(yīng)的代碼沒什么參考價值,下一篇文章中會講如何整合Vuex以及在請求頭中設(shè)置Token):
@/pages/admin/test.vue
<template>
<div>
{{ info }}
</div>
</template>
<script>
export default {
name: 'AdminTest',
asyncData (context) {
return context.$axios.get('/admin/test').then((res) => {
return {
info: res
}
})
},
data () {
return {
info: ''
}
}
}
</script>
效果:
