# 前端废物的自救之路(1)实现axios拦截器:对Nuxt.js中的@nuxtjs/axios进行封装

# 前言

手头有一个去年五月写的遗留项目,以前使用的是Spring Boot+Thymeleaf+AmazeUI(前台)+X-admin(后台),由于用户反映有许多需要改进的地方(并且打开项目一看,那代码我自己也看不懂了,而且以现在的视角看那些代码真的是烂到极致),故进行彻底重构。

由于前台需要SEO,并且考虑到上一版中使用AmazeUI+Thymeleaf开发的体验极为痛苦,且新的后端(Gin)的模板引擎十分恶心(可能有人不这么认为,反正我认为Gin的模板语法比Thymeleaf恶心多了),于是前端选择了Nuxt.js。并且,由于前台对响应式有需求,而且客户认为Vuetify太花哨,iView基础版的组件又不够用,所以UI框架前后台统一使用Ant Design Vue

注:后端使用的是Gin,并且实现了DDDCQS。关于这个项目的后端我们过段时间再另行讨论。但是为了解释部分前端代码,这一系列文章中可能也会贴出一些后端代码以供参考。其中前端代码会给出具体的文件名和路径,后端代码会在代码前标识为“后端代码”。

由于网络上使用Nuxt.js+Ant Design Vue的文章实在是太少了,作为一个后端一天能写600+行(以前用Java的时候一天最多甚至能写1200+行),前端一天写100行能工作的代码就满足了的前端废物,为了让以后再写类似的项目的时候能不这么肉疼,于是决定记录下本次开发的过程,不为别的,就为自己以后再用NuxtAnt Design Vue的时候少掉几根头发。

虽然这个项目不能开源,但是考虑到以后可能还会做类似的项目,并且类似的解决方案的Demo实在是太少了,所以在这个项目完结后我也会根据我的开发过程整理一个开源的种子项目发布在GitHub上以供参考和使用。

# 为什么需要axios拦截器?

项目本身是前后端分离的,鉴权使用的是JWT。由于后端是根据请求头中的X-Token字段进行鉴权的,所以需要在每个请求的请求中放入Token。但是我们不可能在每个接口里都这么做,最佳的解决方案当然是利用axios拦截器来完成这个工作。

另外,后端所有的响应无论成功还是失败都具有统一的格式:

后端代码

// @Description 生成一个成功请求的标准响应格式的JSON
// @param data interface{} 响应中要携带的数据
// @return result 生成的JSON
func Success(data interface{}) (result gin.H) {
	result = gin.H{
		"code":    Ok,
		"message": "Success",
		"data":    data,
	}
	return
}

// @Description 生成一个失败请求的标准响应格式的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
}

我们也不可能在每个接口里都去解析这个格式,这样不仅不优雅还很容易出错,这时候就需要axios拦截器对响应做统一的处理了。

# 封装@nuxtjs/axios

由于Nuxt.jsaxios做了封装,所以使用起来与直接使用axios有一些不同,在实现axios拦截器时与直接使用axios也不太一样。

注:我在新建项目的时候就选择了axios,所以我的项目创建出来是自带@nuxtjs/axios这个依赖包的,如果没有的话可以使用npm自行安装。如果使用的是我这篇文章所讲的方法,安装时要安装的包是@nuxtjs/axios而不是axios

# 添加axios拦截器插件

与在Vue中配置axios拦截器不同,在Nuxt.js中配置axios拦截器使用的是Nuxt.js的插件机制。要配置axios拦截器,我们需要在@/plugins目录下新建一个axios.js(名称无所谓,只要是.js文件就可以了,但是该文件的名称与配置文件中的名称要对应)文件,在这个文件中进行具体的配置:

由于前端才刚刚开始写,所以这个文件的内容非常不全面,仅供参考,以后如果添加了新的重要的逻辑,会在后续文章中对这部分代码进行编辑。

@/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拦截器:设置Token
  $axios.onRequest((config) => {
    // TODO 使用Vuex存储Token,并做持久化处理
    // config.headers.common['X-Token'] = store.state.token
  })
  // Error拦截器:出现错误的时候被调用,根据状态码做对应判断并显示全局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拦截器:对正常返回的数据进行处理
  $axios.onResponse((response) => {
    return response.data
  })
}

$axios.onRequest$axios.onResponse$axios.onErrorNuxt.js提供的方法,类似于直接使用axios时的$axios.interceptors.response.use方法,可以在这些方法中完成对请求信息和响应信息的判断与处理。这些方法与直接使用axios最大的不同就是默认情况下不必返回任何内容(当然,onResponse还是需要返回相应数据response.data,也就是响应体本身(JSON),包括了messagecode——当然如果不需要这两部分的话也可以返回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)

因为对于错误信息,个人认为使用全局显示的方法比组件内单独显示更好,因为这样可以更方便地做全局处理(当然也有人不这么认为,那样的话可以在组件内单独使用Alert组件),所以每当请求出现错误的时候,都需要全局显示这样一条错误信息。与在组件内显示错误信息使用this.$message.error等不同,Ant Design Vue也给出了全局显示错误信息的方法:

组件提供了一些静态方法,使用方式和参数如下:

  • 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 warning
  • message.loading(content, [duration], onClose)

——Ant Design Vue官方文档

记得使用之前先导入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,以此类推。

# 组件内使用

# 一般方法

以登录为例:

@/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保存、跳转等操作
            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()方法中请求对应数据(在请求之前需要设置一下Token。这里因为我还没整合Vuex,直接写死了,所以对应的代码没什么参考价值,下一篇文章中会讲如何整合Vuex以及在请求头中设置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>

效果: