在ant-design-cloud的src/utils目录下,我们创建了request.js文件,在该文件头部,我们引入了下面这些模块:
import axios from 'axios'import storage from 'store'import store from '@/store'import router from '@/router'import notification from 'ant-design-vue/es/notification'import { VueAxios } from './axios'import { ACCESS_TOKEN,REFRESH_TOKEN,EXPIRE_TIME } from '@/store/mutation-types'import { Modal } from 'ant-design-vue';import { refresh } from '@/api/auth/login'
@/是在vue.config.js里自定义别名,代表项目src目录。
创建请求对象
在request.js里,我们通过下面这段代码创建了一个Axios对象:
// 创建 axios 实例const request = axios.create({// API 请求的默认前缀baseURL: process.env.VUE_APP_API_BASE_URL,// timeout: 6000, // 请求超时时间timeout: requestTimeOut,responseType: 'json',validateStatus(status) {return status === success}})
- baseURL请求的基本路径前缀。因为和后端的交互都是通过微服务网关来完成的,所以在开发环境下,该值为vue.config.js里定义的http://localhost:8301/,当系统部署打包部署到生产环境时,请修改env.production文件里的VUE_APP_BASE_API值;
- timeout请求超时时间,这里为10000毫秒,即10秒钟;
- responseType响应数据格式,这里使用JSON格式;
- validateStatus方法里指定了只有当后端系统返回200HTTP状态码的时候,才认定请求成功,否则将认定为失败(可根据实际情况修改,比如在某些系统中,204状态码也是成功的)。
创建好请求对象service后,后续的Axios操作都基于该对象。
拦截请求
接着我们在request.js里配置了请求拦截器,代码如下所示:
请求前拦截器拼接请求的accessToken。
// request interceptorrequest.interceptors.request.use(config => {let _config = configtry {const expireTime = storage.get(EXPIRE_TIME)if (expireTime) {const left = expireTime - new Date().getTime()const refreshToken = storage.get(REFRESH_TOKEN)if (left < checkRegion && refreshToken) {if (left < 0) {store.commit('SET_TOKEN', '')store.commit('SET_REFRESH_TOKEN', '')store.commit('SET_EXPIRE_TIME', '')}_config = queryRefreshToken(_config, refreshToken)} else {const accessToken = storage.get(ACCESS_TOKEN)if (accessToken) {_config.headers['Authorization'] = 'bearer ' + accessToken}}}} catch (e) {console.error(e)}return _config},error => {console.log(error)return Promise.reject(error)})
请求拦截器的任务很简单,就是在发送请求前,判断浏览器内存中是否含有后端访问令牌,有的话在HTTP请求头部携带该令牌,key为Authorization,value为bearer+令牌,这和我们之前通过PostMan发送测试请求做法一致。
上面代码第二个参数用于打印发送请求之前出现的异常信息,比如在处理请求参数时发送的异常。
拦截响应
传统的后端系统,响应数据一般是下面这种格式:
{"code": "200","message": "请求成功",......}
请求是否成功通过返回数据的code字段判断。青锋 Cloud并没有采用这种方式,而是直接通过HTTP状态码来判断。因为HTTP响应里都会包含HTTP状态码,所以就没必要再通过一个code字段来表达响应状态了,这样做也更符合RESTful的风格。
于是,我们可以定义一个Axios响应拦截器,根据不同的HTTP状态码作出不同的响应
// response interceptorrequest.interceptors.response.use((response) => {return response}, (response) => {if (error.response) {const errorMessage = error.response.data === null ? '系统内部异常,请联系网站管理员' : error.response.data.messageswitch (error.response.status) {case 404:Message({message: '很抱歉,资源未找到',type: 'error',duration: messageDuration})breakcase 403:Message({message: '很抱歉,您暂无该操作权限',type: 'error',duration: messageDuration})breakcase 401:Message({message: '很抱歉,认证已失效,请重新登录',type: 'error',duration: messageDuration})breakdefault:if (errorMessage === 'refresh token无效') {MessageBox.alert('登录已过期,请重新登录', '温馨提示', {confirmButtonText: '确定',showClose: false,callback: action => {router.push('/login')}})} else {Message({message: errorMessage,type: 'error',duration: messageDuration})}break}}return Promise.reject(error)})
如上面代码所写的那样,我们根据不同的HTTP状态码,使用Element UI的消息提示组件作出了不同的提示,这样我们就不必在每个请求中通过catch块来一一判断处理了。
当然,如果你不想使用上面定义的默认行为,你也可以自己通过catch块对特定的HTTP状态码作特殊处理。
封装REST风格请求
青锋 Cloud后端接口采用RESTful风格,下面简单介绍下什么是RESTful。
REST实际上为Representational State Transfer的缩写,翻译为“表现层状态转化” 。如果一个架构符合REST 原则,就称它为RESTful架构。
实际上,“表现层状态转化”省略了主语,完整的说应该是“资源表现层状态转化”。
什么是资源(Resource)?资源指的是网络中信息的表现形式,比如一段文本,一首歌,一个视频文件等等;什么是表现层(Reresentational)?表现层即资源的展现在你面前的形式,比如文本可以是JSON格式的,也可以是XML形式的,甚至为二进制形式的。图片可以是gif,也可以是PNG;
什么是状态转换(State Transfer)?用户可使用URL通过HTTP协议来获取各种资源,HTTP协议包含了一些操作资源的方法,比如:GET 用来获取资源, POST 用来新建资源 , PUT 用来更新资源, DELETE 用来删除资源, PATCH 用来更新资源的部分属性。通过这些HTTP协议的方法来操作资源的过程即为状态转换。
下面对比下传统URL请求和RESTful风格请求的区别:
| 描述 | 传统请求 | 方法 | REST请求 | 方法 |
|---|---|---|---|---|
| 查询 | /user/query?name=mrbird | GET | /user?name=mrbird | GET |
| 详情 | /user/getInfo?id=1 | GET | /user/1 | GET |
| 创建 | /user/create?name=mrbird | POST | /user | POST |
| 修改 | /user/update?name=mrbird&id=1 | POST | /user/1 | PUT |
| 删除 | /user/delete?id=1 | GET | /user/1 | DELETE |
从上面这张表,我们大致可以总结下传统请求和RESTful请求的几个区别:
- 传统请求通过URL来描述行为,如create,delete等;RESTful请求通过URL来描述资源。
- RESTful请求通过HTTP请求的方法来描述行为,比如DELETE,POST,PUT等,并且使用HTTP状态码来表示不同的结果。
- RESTful请求通过JSON来交换数据。
总而言之,RESTful只是一种风格,并不是一种强制性的标准。
为了向后端发送不同的请求(如GET,POST,PUT,DELETE等),我们通过上面创建的service Axios对象封装几个相应的方法,参考角色模块案例:
import request from '@/utils/request'import querystring from 'querystring'//查询数据列表export function getListPage (params) {let queryString = querystring.stringify(params);return request({url: '/system/role/findListPage?'+queryString,method: 'get',headers: {'Content-Type': 'application/json;charset=UTF-8',}})}//保存或更新数据export function saveOrUpdate (params) {let url = '/system/role';let method = 'post';if(params.id!=''&¶ms.id!=undefined){method = 'put';}return request({url: url,method: method,data: params})}//删除数据export function del (ids) {return request({url: '/system/role/'+ids,method: 'delete',headers: {'Content-Type': 'application/json;charset=UTF-8',}})}//更新状态export function updateStatus(id,status) {return request({url: '/system/role/updateStatus',method: 'post',data: {id,status}})}//更新权限export function updateAuth (params) {return request({url: '/system/role/updateAuth',method: 'post',data: params})}//获取角色菜单列表export function findRoleMenuList (params) {return request({url: '/system/role/findRoleMenuList',method: 'post',data: params})}//查询export function getServiceList (parameter) {return request({url: "",method: 'get',params: parameter})}
全局注册
在request.js文件末尾,我们通过export default request将request对象暴露出去,接着在src/main.js里对request对象暴露的几个方法进行了全局注册:
const installer = {vm: {},install(Vue) {Vue.use(VueAxios, request)},}export default requestexport {installer as VueAxios,request as axios,}
完整的案例请参考项目源码src/utils/request.js和src/utils/axios/index.js 
