uview2.0+uni-app 无感刷新token

用的是这个开发框架:uview-template 集成uview-ui 的基础项目开发框架 - DCloud 插件市场

逻辑:

1、请求接口发现token过期或失效。在拦截器response里通过code判断是否需要刷新token。

2、刷新token,调用刷新接口。

3、在刷新接口没有返回token之前,把其他调用的接口存到数组里,pending状态。

4、接口返回token之后,重新调用数组里的接口,完成无感刷新token。

看到网上无感刷新token的版本都是这么写的FAQ | UNI-AJAX

javascript 复制代码
import ajax from 'uni-ajax'
import refreshToken from './refresh'

// 创建实例
const instance = ajax.create({
  baseURL: 'https://www.example.com/api'
})

// 添加请求拦截器
instance.interceptors.request.use(config => {
  // 给每条请求赋值 Token 请求头
  config.header['Authorization'] = uni.getStorageSync('TOKEN')
  return config
})

// 添加响应拦截器
instance.interceptors.response.use(
  response => {
    // 假设接口返回的 code === 401 时则需要刷新 Token
    if (response.data.code === 401) {
      return refreshToken().then(() => instance(response.config))
    }

    return response
  },
  error => {
    return Promise.reject(error)
  }
)

export default instance

对我来说有几个难点:

1、instance.interceptors.response.use()

2、response里return refreshToken().then(() => instance(response.config))

先不管,照抄。

主要就是这句代码:return refreshToken().then(() => instance(response.config))

抄过来发现response.config在uview的响应拦截里取不到。

在uview里拦截器是这么写的Http请求 | uView - 多平台快速开发的UI框架 - uni-app UI框架

javascript 复制代码
// /common/http.interceptor.js

// 这里的vm,就是我们在vue文件里面的this,所以我们能在这里获取vuex的变量,比如存放在里面的token变量
const install = (Vue, vm) => {
	// 此为自定义配置参数,具体参数见上方说明
	Vue.prototype.$u.http.setConfig({
		baseUrl: 'https://api.example.com',
		loadingText: '努力加载中~',
		loadingTime: 800,
		// ......
	});
	
	// 请求拦截,配置Token等参数
	Vue.prototype.$u.http.interceptor.request = (config) => {
		
		config.header.token = vm.token;
		
		// 可以对某个url进行特别处理,此url参数为this.$u.get(url)中的url值
		if(config.url == '/user/login') config.header.noToken = true;
		// 最后需要将config进行return
		return config;
		// 如果return一个false值,则会取消本次请求
		// if(config.url == '/user/rest') return false; // 取消某次请求
	}
	
	// 响应拦截,判断状态码是否通过
	Vue.prototype.$u.http.interceptor.response = (res) => {
		if(res.code == 200) {
			// res为服务端返回值,可能有code,result等字段
			// 这里对res.result进行返回,将会在this.$u.post(url).then(res => {})的then回调中的res的到
			// 如果配置了originalData为true,请留意这里的返回值
			return res.result;
		} else if(res.code == 201) {
			// 假设201为token失效,这里跳转登录
			vm.$u.toast('验证失败,请重新登录');
			setTimeout(() => {
				// 此为uView的方法,详见路由相关文档
				vm.$u.route('/pages/user/login')
			}, 1500)
			return false;
		} else {
			// 如果返回false,则会调用Promise的reject回调,
			// 并将进入this.$u.post(url).then().catch(res=>{})的catch回调中,res为服务端的返回值
			return false;
		}
	}
}

export default {
	install
}

然后要在main.js中引用

javascript 复制代码
import httpInterceptor from '@/common/http.interceptor.js'
Vue.use(httpInterceptor, app)

这个时候有两个解决方案,一个就是按照别人的写法,把拦截器都改了。但是因为这是个已经完成的项目,要改的话,相当于重构了。放弃这个方案;

还有一个就是去看下uview封装的http,里面是怎么处理的,是不是可以自己多加个config的返回。

看了下,还真可以加,加上之后响应里就有了config,问题解决了。注意,这里只改了originalData=false的返回参数。

javascript 复制代码
// http.interceptor.js

import refreshToken from './getToken'
const install = (Vue, vm) => {
	// 此为自定义配置参数,具体参数见上方说明
	Vue.prototype.$u.http.setConfig({
		baseUrl: 'XXXX', // 请求域名
		method: 'GET',
		// 设置为json,返回后会对数据进行一次JSON.parse()
		dataType: 'json',
		showLoading, // 是否显示请求中的loading
		loadingText, // 请求loading中的文字提示
		loadingTime, // 在此时间内,请求还没回来的话,就显示加载中动画,单位ms
		originalData: false, // 是否在拦截器中返回服务端的原始数据
		loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
		// 配置请求头信息
		header: {
			'content-type': contentType
		},
	});

	// 请求拦截,配置Token等参数
	Vue.prototype.$u.http.interceptor.request = (config) => {
		config.header[headerTokenName] = vm.token ? vm.token : '';
		return config;
	}

	// 响应拦截,判断状态码是否通过
	// config 修改uview-ui/libs/request/index/js的返回options
	Vue.prototype.$u.http.interceptor.response = (res, config) => {
		
		if (res[codeName] == successCode) {
			return res.data;
		} else if ( res[codeName] == invalidCode1 || res[codeName]==invalidCode2) {
			let token = vm.token
			if(token != '' && token != null && token != undefined){
				// 更新token 重点代码!!!!!!!!
				return refreshToken(vm).then( () => vm.$u.http.request(config))
			}else{
				vm.$u.vuex('token', '')
				vm.$u.vuex('isLogin', false)
				vm.$u.vuex('userInfo', '')
				vm.$u.func.showToast({
					title: '需要您先登录一下~',
					success: () => {
						vm.$u.route('/pages/login/login')
					}
				})
			}
			return false
		}else{
			vm.$u.toast(res.errmsg)
			return false;
		}
	}
}

export default {
	install
}

然后重点就在于实现第二步和第三步。先看下别人写的。

javascript 复制代码
import request from './ajax'

let isRefreshing = false  // 当前是否在请求刷新 Token
let requestQueue = []     // 将在请求刷新 Token 中的请求暂存起来,等刷新 Token 后再重新请求

// 执行暂存起来的请求
const executeQueue = error => {
  requestQueue.forEach(promise => {
    if (error) {
      promise.reject(error)
    } else {
      promise.resolve()
    }
  })

  requestQueue = []
}

// 获取 Token 请求
const getToken = () => request.post('/oauth/token')

// 刷新 Token 请求处理,参数为刷新成功后的回调函数
const refreshToken = () => {
  // 如果当前是在请求刷新 Token 中,则将期间的请求暂存起来
  if (isRefreshing) {
    return new Promise((resolve, reject) => {
      requestQueue.push({ resolve, reject })
    })
  }

  isRefreshing = true

  return new Promise((resolve, reject) => {
    getToken()
      // 假设请求成功接口返回的 code === 200 为刷新成功,其他情况都是刷新失败
      .then(res => (res.data.code === 200 ? res : Promise.reject(res)))
      .then(res => {
        uni.setStorageSync('TOKEN', res.data.data)
        resolve()
        executeQueue(null)
      })
      .catch(err => {
        uni.removeStorageSync('TOKEN')
        reject(err)
        executeQueue(err || new Error('Refresh token error'))
      })
      .finally(() => {
        isRefreshing = false
      })
  })
}

export default refreshToken

uview里不是用的ajax,是用的uni-app的请求方式,改下请求方式和接口。

按照下面的代码照抄就行。

只要把请求的接口和接口返回的之后的数据处理改下就行。

区分了微信和企业微信,因为我们的小程序需要在微信和企业微信里都可使用。

javascript 复制代码
let isRefreshing = false // 当前是否在请求刷新 Token
let requestQueue = [] // 将在请求刷新 Token 中的请求暂存起来,等刷新 Token 后再重新请求

// 执行暂存起来的请求
const executeQueue = error => {
	requestQueue.forEach(promise => {
		if (error) {
			promise.reject(error)
		} else {
			promise.resolve()
		}
	})

	requestQueue = []
}

// 获取 Token 请求
const getopenid = (params = {}) => uni.$u.get('/bbm/applet/auc/getInfo', params);
//企业微信登陆 获取TOKEN 企业微信调用
const OnLogin = (params = {}) => uni.$u.get('/bbm/applet/auc/getCpToken', params);


// 刷新 Token 请求处理,参数为刷新成功后的回调函数
const refreshToken = (vm) => {
	// debugger
	// 如果当前是在请求刷新 Token 中,则将期间的请求暂存起来
	if (isRefreshing) {
		return new Promise((resolve, reject) => {
			// debugger
			requestQueue.push({
				resolve,
				reject
			})
			
			console.log(requestQueue)
		})
	}

	isRefreshing = true

	return new Promise((resolve, reject) => {
		debugger
		uni.login({
			provider: 'weixin',
			success: login => {
				console.log("已进入刷新token", login.code)
				let that = this;
				
				//企业微信端登陆
				wx.getSystemInfo({
					success(res) {
						if (res.environment == 'wxwork') { //企业微信端
							// uni.showModal({
							// 	title:"开始调用wx.qy.login",
							// 	showCancel:false
							// })
							wx.qy.login({
								success: result => {
									if (result.code) {
										
										OnLogin({
											agentid: 1000098,
											code: res.code
										})
										.then(res => {
											
											if (res.token) {
												
												vm.$u.vuex('token', res.token)
												vm.$u.vuex('isLogin', true)
												vm.$u.vuex('userInfo', res)
												// debugger
												resolve()
												executeQueue(null)
											}
											
										
										}).catch(err => {
											uni.removeStorageSync('lifeData')
											reject(err)
											executeQueue(err || new Error('Refresh token error'))
										}).finally(() => {
											isRefreshing = false
										})
										
									}
								}
							})		
						} else {
							// debugger
							getopenid({
								code: login.code
							}).then(res => {
								uni.hideLoading()
								// debugger
								if (res.token) {
									
									vm.$u.vuex('token', res.token)
									vm.$u.vuex('isLogin', true)
									vm.$u.vuex('userInfo', res)
									// debugger
									resolve()
									executeQueue(null)
								}
							})
							.catch(err => {
								uni.removeStorageSync('lifeData')
								reject(err)
								executeQueue(err || new Error('Refresh token error'))
							})
							.finally(() => {
								isRefreshing = false
							})
							
						}
					}
				})
			},
			fail: () => {
				this.$refs.vToast.show({
					title: '登录失败,请重试',
					type: 'warning',
					position: 'top'
				})
			}
		})



	})
}

export default refreshToken

参考:

一文搞懂前端请求XHR,AJAX,Fetch和Axios - 掘金

FAQ | UNI-AJAX

相关推荐
mosen86836 分钟前
uniapp中uni.scss如何引入页面内或生效
前端·uni-app·scss
lyz24685938 分钟前
uniapp popup弹窗组件的自定义使用方法
uni-app
沙尘暴炒饭38 分钟前
uniapp 前端解决精度丢失的问题 (后端返回分布式id)
前端·uni-app
牛牛科技39 分钟前
生产管理系统PHP+Uniapp源码
uni-app
Smile_ping41 分钟前
uniapp——APP读取bin文件,解析文件的数据内容(一)
uni-app·uniapp 读取文件·app端读取bin文件
CDERP-plus42 分钟前
uniapp 3分钟集成轮播广告图
uni-app·erp·erp移动端
Liberty_yes44 分钟前
uniapp input苹果中文键盘输入拼音直接切换输入焦点监听失效
uni-app
街尾杂货店&1 小时前
webpakc介绍
uni-app
洗发水很好用14 小时前
uniApp打包H5发布到服务器(docker)
uni-app
YUJIAN。14 小时前
使用uniapp开发微信小程序-框架搭建
微信小程序·小程序·uni-app