前言:
1、为什么不适用uniapp自带的请求功能?
答:uniapp自带的请求功能,在刷新了令牌后,重新请求返回的数据无法返回给第一次发起请求的方法。也就是说,刷新令牌后重新发起的请求和第一次发起请求的方法是割裂的。
2、封装文件中,我设置了无感刷新令牌功能。我后台的判断逻辑是,当前端请求的令牌已过期,或令牌还有不到10分钟过期时,刷新令牌。在后台可把令牌过期时间封装到令牌中,或放入redis中。
一、安装axios
1.1、使用HBuilder打开uniapp项目,点击视图->显示终端,打开npm操作页面。
1.2、如果项目中还没有“package.json”文件,请先初始化项目。
npm init -y
1.3、安装axios,建议锁定低版本(使用uniapp-vue3版本时,axios的版本需要0.26.0以下)。
npm i axios@0.26.0
1.4 在main.js中配置axios
import axios from 'axios'//引入axios import * as request from '@/common/api/request.js'//自定义请求封装文件 Vue.prototype.$http = request //封装的请求方法 // 解决uniapp 适配axios请求,避免报adapter is not a function错误 // 此配置也可以放在自定义请求封装文件中(例如 request.js) axios.defaults.adapter = config => { return new Promise((resolve, reject) => { let settle = require('axios/lib/core/settle'); let buildURL = require('axios/lib/helpers/buildURL'); uni.request({ method: config.method.toUpperCase(), url: config.baseURL + buildURL(config.url, config.params, config.paramsSerializer), header: config.headers, data: config.data, dataType: config.dataType, responseType: config.responseType, sslVerify: config.sslVerify, complete: function complete(response) { response = { data: response.data, status: response.statusCode, errMsg: response.errMsg, header: response.header, config: config }; settle(resolve, reject, response); } }) }) }
二、自定义请求封装文件 request.js,我的文件路径是 /根目录/common/api/request.js。
import axios from 'axios' // 引入axios import store from '@/store/index.js'//引入store仓库 function getToken() { //获取令牌 let token = store.state.accountValue.accessToken //store仓库中的令牌 if (!token) { //令牌不存在,显示登录弹窗 openLoginDialog() } return token } //令牌过期,刷新令牌请求 function refreshToken() { return service.get('/account/user/refresh/login').then(res => res) } //设置store及微信缓存中的账号信息 function setAccountInfo(accountInfo) { store.commit('accountValue/SET_ACCOUNT_INFO', accountInfo) //修改store中的账号信息 //存储账号信息到微信缓存中 wx.setStorage({ key: "userInfo", data: JSON.stringify(accountInfo) }) } axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' // 创建axios实例 const baseUrl = store.state.constantValue.hostUrl + store.state.constantValue.port //域名/地址公共前缀 const appid = store.state.constantValue.appid //小程序的appid const requestTimeout = store.state.constantValue.requestTimeout //默认的请求超时时间,单位毫秒 const service = axios.create({ baseURL: baseUrl, //axios的默认请求地址前缀 timeout: requestTimeout //默认的请求超时时间,单位毫秒 }) let requests = [] // 重试队列数组,把需要刷新令牌的请求都放入这个数组中,每一项将是一个待执行的函数形式 let common = {//默认的请求配置信息 data: {}, header: { "Content-Type": "application/json", "Content-Type": "application/x-www-form-urlencoded" }, method: "GET", dataType: "json" } // 请求拦截器 service.interceptors.request.use( config => { // 每次发送请求之前判断vuex中是否存在token // 如果存在,则统一在http请求的headers都加上token,这样后台根据token判断你的登录情况 // 即使本地存在token,也有可能token是过期的,所以在响应拦截器中要对返回状态进行判断 config.method = config.method || common.method; if (config.method !== 'GET') { config.data = config.data || common.data; } config.dataType = config.dataType || common.dataType; config.header = config.header || common.header; if (config.header.NeedToken === 'NEED') { //如果需要访问令牌 const token = getToken(); !isNull(token) && (config.headers.Authorization = 'Bearer ' + token); } return config; }, error => { return Promise.error(error); }); // 响应拦截器 service.interceptors.response.use( response => { // 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据 // 否则的话抛出错误 if (response.status === 200) { let code = response.data.code //code为自定义的后台响应code if (code === 20009 || code === 20010) { //未登录或者登录异常,需要重新登录时 openLoginDialog()//打开登录弹窗 return Promise.resolve(response.data); //返回响应结果 } else { if (code === 20017) { //后台要求刷新令牌 const config = response.config if (!store.state.constantValue.isRefreshing) { //如果正在刷新令牌状态为false //设置正在刷新令牌状态为true,进入更新令牌阶段 store.commit('constantValue/SET_IS_REFRESHING', true) //获取刷新令牌请求 return refreshToken().then(res => { if (res.code === 20000) { // 刷新令牌成功,设置store及微信缓存中的账号信息 const accountInfo = res.data setAccountInfo(accountInfo) //已经刷新了令牌,重新发起重试队列数组中的请求 requests.forEach(cb => cb(accountInfo.accessToken)) // 完成后清空这个数组 requests = [] //重置当前请求的配置,重新发起请求 config.headers.Authorization = 'Bearer ' + accountInfo.accessToken config.baseURL = baseUrl //axios的默认请求地址前缀 config.timeout = requestTimeout //默认的请求超时时间 return service(config) //重新发起请求,返回的就是当前请求 } else { openLoginDialog()//打开登录弹窗 } }).catch(rej => { //令牌刷新发生错误 openLoginDialog()//打开登录弹窗 }).finally(() => { //完成之后设置正在刷新令牌状态为false store.commit('constantValue/SET_IS_REFRESHING', false) }) } else { //如果正在刷新令牌状态为true if (config.header.NeedToken === 'NEED') { //请求需要令牌 // 返回一个未执行resolve的promise return new Promise((resolve) => { // 将resolve放进重试队列数组,用一个函数形式来保存,等令牌刷新后直接执行 requests.push((token) => { config.headers.Authorization = 'Bearer ' + token resolve(service(config)) }) }) } } } return Promise.resolve(response.data) //返回响应信息 } } else { //状态码不为200时 return Promise.reject(response.data) //返回响应异常信息 } }, error => { return Promise.reject(error) //返回响应错误信息 } ); export default service;