在ant-design-cloud的src/utils目录下,我们创建了request.js文件,在该文件头部,我们引入了下面这些模块:

  1. import axios from 'axios'
  2. import storage from 'store'
  3. import store from '@/store'
  4. import router from '@/router'
  5. import notification from 'ant-design-vue/es/notification'
  6. import { VueAxios } from './axios'
  7. import { ACCESS_TOKEN,REFRESH_TOKEN,EXPIRE_TIME } from '@/store/mutation-types'
  8. import { Modal } from 'ant-design-vue';
  9. import { refresh } from '@/api/auth/login'

@/是在vue.config.js里自定义别名,代表项目src目录。

创建请求对象

在request.js里,我们通过下面这段代码创建了一个Axios对象:

  1. // 创建 axios 实例
  2. const request = axios.create({
  3. // API 请求的默认前缀
  4. baseURL: process.env.VUE_APP_API_BASE_URL,
  5. // timeout: 6000, // 请求超时时间
  6. timeout: requestTimeOut,
  7. responseType: 'json',
  8. validateStatus(status) {
  9. return status === success
  10. }
  11. })
  1. baseURL请求的基本路径前缀。因为和后端的交互都是通过微服务网关来完成的,所以在开发环境下,该值为vue.config.js里定义的http://localhost:8301/,当系统部署打包部署到生产环境时,请修改env.production文件里的VUE_APP_BASE_API值;
  2. timeout请求超时时间,这里为10000毫秒,即10秒钟;
  3. responseType响应数据格式,这里使用JSON格式;
  4. validateStatus方法里指定了只有当后端系统返回200HTTP状态码的时候,才认定请求成功,否则将认定为失败(可根据实际情况修改,比如在某些系统中,204状态码也是成功的)。

创建好请求对象service后,后续的Axios操作都基于该对象。

拦截请求

接着我们在request.js里配置了请求拦截器,代码如下所示:
请求前拦截器拼接请求的accessToken。

  1. // request interceptor
  2. request.interceptors.request.use(config => {
  3. let _config = config
  4. try {
  5. const expireTime = storage.get(EXPIRE_TIME)
  6. if (expireTime) {
  7. const left = expireTime - new Date().getTime()
  8. const refreshToken = storage.get(REFRESH_TOKEN)
  9. if (left < checkRegion && refreshToken) {
  10. if (left < 0) {
  11. store.commit('SET_TOKEN', '')
  12. store.commit('SET_REFRESH_TOKEN', '')
  13. store.commit('SET_EXPIRE_TIME', '')
  14. }
  15. _config = queryRefreshToken(_config, refreshToken)
  16. } else {
  17. const accessToken = storage.get(ACCESS_TOKEN)
  18. if (accessToken) {
  19. _config.headers['Authorization'] = 'bearer ' + accessToken
  20. }
  21. }
  22. }
  23. } catch (e) {
  24. console.error(e)
  25. }
  26. return _config
  27. },
  28. error => {
  29. console.log(error)
  30. return Promise.reject(error)
  31. })

请求拦截器的任务很简单,就是在发送请求前,判断浏览器内存中是否含有后端访问令牌,有的话在HTTP请求头部携带该令牌,key为Authorization,value为bearer+令牌,这和我们之前通过PostMan发送测试请求做法一致。
上面代码第二个参数用于打印发送请求之前出现的异常信息,比如在处理请求参数时发送的异常。

拦截响应

传统的后端系统,响应数据一般是下面这种格式:

  1. {
  2. "code": "200",
  3. "message": "请求成功",
  4. ......
  5. }

请求是否成功通过返回数据的code字段判断。青锋 Cloud并没有采用这种方式,而是直接通过HTTP状态码来判断。因为HTTP响应里都会包含HTTP状态码,所以就没必要再通过一个code字段来表达响应状态了,这样做也更符合RESTful的风格。
于是,我们可以定义一个Axios响应拦截器,根据不同的HTTP状态码作出不同的响应

  1. // response interceptor
  2. request.interceptors.response.use((response) => {
  3. return response
  4. }, (response) => {
  5. if (error.response) {
  6. const errorMessage = error.response.data === null ? '系统内部异常,请联系网站管理员' : error.response.data.message
  7. switch (error.response.status) {
  8. case 404:
  9. Message({
  10. message: '很抱歉,资源未找到',
  11. type: 'error',
  12. duration: messageDuration
  13. })
  14. break
  15. case 403:
  16. Message({
  17. message: '很抱歉,您暂无该操作权限',
  18. type: 'error',
  19. duration: messageDuration
  20. })
  21. break
  22. case 401:
  23. Message({
  24. message: '很抱歉,认证已失效,请重新登录',
  25. type: 'error',
  26. duration: messageDuration
  27. })
  28. break
  29. default:
  30. if (errorMessage === 'refresh token无效') {
  31. MessageBox.alert('登录已过期,请重新登录', '温馨提示', {
  32. confirmButtonText: '确定',
  33. showClose: false,
  34. callback: action => {
  35. router.push('/login')
  36. }
  37. })
  38. } else {
  39. Message({
  40. message: errorMessage,
  41. type: 'error',
  42. duration: messageDuration
  43. })
  44. }
  45. break
  46. }
  47. }
  48. return Promise.reject(error)
  49. })

如上面代码所写的那样,我们根据不同的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请求的几个区别:

  1. 传统请求通过URL来描述行为,如create,delete等;RESTful请求通过URL来描述资源。
  2. RESTful请求通过HTTP请求的方法来描述行为,比如DELETE,POST,PUT等,并且使用HTTP状态码来表示不同的结果。
  3. RESTful请求通过JSON来交换数据。

总而言之,RESTful只是一种风格,并不是一种强制性的标准。
为了向后端发送不同的请求(如GET,POST,PUT,DELETE等),我们通过上面创建的service Axios对象封装几个相应的方法,参考角色模块案例:

  1. import request from '@/utils/request'
  2. import querystring from 'querystring'
  3. //查询数据列表
  4. export function getListPage (params) {
  5. let queryString = querystring.stringify(params);
  6. return request({
  7. url: '/system/role/findListPage?'+queryString,
  8. method: 'get',
  9. headers: {
  10. 'Content-Type': 'application/json;charset=UTF-8',
  11. }
  12. })
  13. }
  14. //保存或更新数据
  15. export function saveOrUpdate (params) {
  16. let url = '/system/role';
  17. let method = 'post';
  18. if(params.id!=''&&params.id!=undefined){
  19. method = 'put';
  20. }
  21. return request({
  22. url: url,
  23. method: method,
  24. data: params
  25. })
  26. }
  27. //删除数据
  28. export function del (ids) {
  29. return request({
  30. url: '/system/role/'+ids,
  31. method: 'delete',
  32. headers: {
  33. 'Content-Type': 'application/json;charset=UTF-8',
  34. }
  35. })
  36. }
  37. //更新状态
  38. export function updateStatus(id,status) {
  39. return request({
  40. url: '/system/role/updateStatus',
  41. method: 'post',
  42. data: {
  43. id,
  44. status
  45. }
  46. })
  47. }
  48. //更新权限
  49. export function updateAuth (params) {
  50. return request({
  51. url: '/system/role/updateAuth',
  52. method: 'post',
  53. data: params
  54. })
  55. }
  56. //获取角色菜单列表
  57. export function findRoleMenuList (params) {
  58. return request({
  59. url: '/system/role/findRoleMenuList',
  60. method: 'post',
  61. data: params
  62. })
  63. }
  64. //查询
  65. export function getServiceList (parameter) {
  66. return request({
  67. url: "",
  68. method: 'get',
  69. params: parameter
  70. })
  71. }

全局注册

在request.js文件末尾,我们通过export default request将request对象暴露出去,接着在src/main.js里对request对象暴露的几个方法进行了全局注册:

  1. const installer = {
  2. vm: {},
  3. install(Vue) {
  4. Vue.use(VueAxios, request)
  5. },
  6. }
  7. export default request
  8. export {
  9. installer as VueAxios,
  10. request as axios,
  11. }

完整的案例请参考项目源码src/utils/request.js和src/utils/axios/index.js
image.png