- 无痛刷新token续接401请求
在小程序开发中,我们都知道小程序是没有cookie的,那么用户身份是如何确定的,后段颁发token,前端每次请求头部附带token。 既然是token,那么肯定有它的过期时间,没有一个token是永久的,永久的token就相当于一串永久的密码,是不安全的, 那么既然有刷新时间,问题就来了 1.前后端交互的过程中token如何存储? 2.token过期时,前端该怎么处理? 3.当用户正在操作时,遇到token过期该怎么办?直接跳回登陆页面? token如何存储? cookie的大小约4k,兼容性在ie6及以上 都兼容,在浏览器和服务器间来回传递,因此它得在服务器的环境下运行,而且可以设定过期时间,默认的过期时间是session会话结束。 localStorage的大小约5M,兼容性在ie7及以上都兼容,有浏览器就可以,不需要在服务器的环境下运行, 会一直存在,除非手动清除 。 答案大致分为2种 存在 [代码]cookie[代码] 中 存在 [代码]localStorage[代码] 中 token过期时,前端该怎么处理? 1.第一种:跳回登陆页面重新登陆 2.第二种:拦截401重新获取token class HttpClient { /** * Create a new instance of HttpClient. */ constructor() { this.interceptors = { request: [], response: [] }; } /** * Sends a single request to server. * * @param {Object} options - Coming soon. */ sendRequest(options) { let requestOptions = options; if (!requestOptions.header) { requestOptions.header = {}; } // 重新设置 Accept 和 Content-Type requestOptions.header = Object.assign( { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=utf-8' }, requestOptions.header ); this.interceptors.request.forEach((interceptor) => { const request = interceptor(requestOptions); requestOptions = request.options; }); // 将以 Promise 返回数据, 无 success、fail、complete 参数 // let response = uni.request(requestOptions); // 使用Promise包装一下, 以 complete方式来接收接口调用结果 let response = new Promise((resolve, reject) => { requestOptions.complete = (res) => { const { statusCode } = res; const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304; if(statusCode==401){ //拦截401请求 uni.reLaunch({ //关闭所有页面直接跳到登陆页面 url: '/pages/login/login' }); } if (isSuccess) { if(res.data.code==1){ resolve(res.data); }else{ reject(res); } } else { reject(res); } }; requestOptions.requestId = new Date().getTime(); uni.request(requestOptions); }); this.interceptors.response.forEach((interceptor) => { response = interceptor(response); }); return response; } } export default HttpClient; 这种方法适用余有登陆页面的小程序,但同样存在问题,假如用户在填写表单,填写完毕你却告诉我重新登陆,确定用户不会删掉你的小程序??? 有人说了 异常退出 我会本地缓存填写的表单内容,当然你要是能接受这种我也无话可说!!! 我们要做的是无痛刷新toekn,那么首先要在401拦截的时候去重新登陆获取新的token 继续优化改造 import store from "../store/index.js"; class HttpClient { /** * Create a new instance of HttpClient. */ constructor() { this.interceptors = { request: [], response: [] }; } /** * Sends a single request to server. * * @param {Object} options - Coming soon. */ sendRequest(options) { let requestOptions = options; if (!requestOptions.header) { requestOptions.header = {}; } // 重新设置 Accept 和 Content-Type requestOptions.header = Object.assign( { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=utf-8' }, requestOptions.header ); this.interceptors.request.forEach((interceptor) => { const request = interceptor(requestOptions); requestOptions = request.options; }); // 将以 Promise 返回数据, 无 success、fail、complete 参数 // let response = uni.request(requestOptions); // 使用Promise包装一下, 以 complete方式来接收接口调用结果 let response = new Promise((resolve, reject) => { requestOptions.complete = (res) => { const { statusCode } = res; const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304; if(statusCode==401){ //拦截401请求 store .dispatch("auth/login") //调用vueX中的登陆将登陆信息保存到VueX .then(()=>{ //提示用户刚才操作无效重新操作一次 uni.showToast({ title: '请重新操作', duration: 2000, icon: "none", }); }) .catch(()=>{ uni.showToast({ title: '账户异常请重启程序', duration: 2000, icon: "none", }); }) } if (isSuccess) { if(res.data.code==1){ resolve(res.data); }else{ reject(res); } } else { reject(res); } }; requestOptions.requestId = new Date().getTime(); uni.request(requestOptions); }); this.interceptors.response.forEach((interceptor) => { response = interceptor(response); }); return response; } } export default HttpClient; 到此我们实现的在401错误时候去重新登陆获取新的token,且告知用户重新操作一次 到此你会发现一个问题,当页面存在一个请求,目前方案毫无问题,但是当存在两个、三个、四个请求,你会骂娘 失败3个请求会重新调用3次登陆会刷新3次token 那么此时要做的就是保证不多次登陆 思路加一个开关,当在登陆过程中后续错误不再走登陆接口 import store from "../store/index.js"; // 是否正在重新登陆刷新的标记 var loginRefreshing = false class HttpClient { /** * Create a new instance of HttpClient. */ constructor() { this.interceptors = { request: [], response: [] }; } /** * Sends a single request to server. * * @param {Object} options - Coming soon. */ sendRequest(options) { let requestOptions = options; if (!requestOptions.header) { requestOptions.header = {}; } // 重新设置 Accept 和 Content-Type requestOptions.header = Object.assign( { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=utf-8' }, requestOptions.header ); this.interceptors.request.forEach((interceptor) => { const request = interceptor(requestOptions); requestOptions = request.options; }); // 将以 Promise 返回数据, 无 success、fail、complete 参数 // let response = uni.request(requestOptions); // 使用Promise包装一下, 以 complete方式来接收接口调用结果 let response = new Promise((resolve, reject) => { requestOptions.complete = (res) => { const { statusCode } = res; const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304; if(statusCode==401){ //拦截401请求 if(!loginRefreshing){//防止重复登陆 loginRefreshing = true store .dispatch("auth/login") //调用vueX中的登陆将登陆信息保存到VueX .then(()=>{ //提示用户刚才操作无效重新操作一次 uni.showToast({ title: '请重新操作', duration: 2000, icon: "none", }); }) .catch(()=>{ uni.showToast({ title: '账户异常请重启程序', duration: 2000, icon: "none", }); }) .finally(()=>{ //销毁 是否正在重新登陆刷新的标记 loginRefreshing = false }); } } if (isSuccess) { if(res.data.code==1){ resolve(res.data); }else{ reject(res); } } else { reject(res); } }; requestOptions.requestId = new Date().getTime(); uni.request(requestOptions); }); this.interceptors.response.forEach((interceptor) => { response = interceptor(response); }); return response; } } export default HttpClient; [图片] 我们可以看到在遇到两个401错误时候并没有请求两次login,只请求一次,到此刷新token算是完成了,但是需要用户配合去重新操作一次,还不是真正的无痛刷线token,做到用户无感知 思路:将请求401的请求缓存起来,在重新登陆完成之后再将缓存中的请求重新发出, 废话不多说直接上代码 import AuthService from "@/services/auth.service"; import store from "../store/index.js"; // 是否正在重新登陆刷新的标记 var loginRefreshing = false // 重试队列,每一项将是一个待执行的函数形式 let requests = [] class HttpClient { /** * Create a new instance of HttpClient. */ constructor() { this.interceptors = { request: [], response: [] }; } /** * Sends a single request to server. * * @param {Object} options - Coming soon. */ sendRequest(options) { let requestOptions = options; if (!requestOptions.header) { requestOptions.header = {}; } // 重新设置 Accept 和 Content-Type requestOptions.header = Object.assign( { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=utf-8' }, requestOptions.header ); this.interceptors.request.forEach((interceptor) => { const request = interceptor(requestOptions); requestOptions = request.options; }); // 将以 Promise 返回数据, 无 success、fail、complete 参数 // let response = uni.request(requestOptions); // 使用Promise包装一下, 以 complete方式来接收接口调用结果 let response = new Promise((resolve, reject) => { let timeId = setTimeout(()=>{ reject({statusCode:504}); },10000) requestOptions.complete = (res) => { clearTimeout(timeId) const { statusCode } = res; const isSuccess = (statusCode >= 200 && statusCode < 300) || statusCode === 304; if(statusCode==401){ //无痛刷新token if(!loginRefreshing){//防止重复登陆 loginRefreshing = true store.dispatch("auth/logout"); store .dispatch("auth/login") .then(()=>{ //所有存储到对列组中的请求重新执行。 requests.forEach(callback=>{ callback(AuthService.getToken() ? AuthService.getToken() : "") }) //重试队列清空 requests = [] }) .catch(()=>{ uni.showToast({ title: '账户异常请重启程序', duration: 2000, icon: "none", }); }) .finally(()=>{ //销毁 是否正在重新登陆刷新的标记 loginRefreshing = false }); } return new Promise((resolve) => { // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行 requests.push((token) => { requestOptions.header.token = token //带着登陆后的新token resolve(uni.request(requestOptions)) }) }) } if (isSuccess) { if(res.data.code==1){ resolve(res.data); }else{ reject(res); } } else { reject(res); } }; requestOptions.requestId = new Date().getTime(); uni.request(requestOptions); }); this.interceptors.response.forEach((interceptor) => { response = interceptor(response); }); return response; } } export default HttpClient; 知识点,在重新获取新的token后要将缓存起来的请求中的token替换为重新登陆后新的token [图片] 到此无痛刷新token续接401请求的方法已经处理完毕,在用户提交表单时候遇到token失效重新获取新的token再续接表单请求,此时用户毫无感知,可能在请求时间上多了延迟,体验好感度99+,哈哈哈哈哈 到此无痛刷新token续接401已经完成请求快去试试吧 提示:有些后端在接口请求做了签名,记得像更换token一样在重新登陆完成之后更换新的时间戳新的签名等字段 附上本请求封装的uniapp基础性脚手架 https://github.com/wkiwi/uni-app-base
2020-09-18 - 小程序滚动渲染弹层延迟,老板要把我炒了
小程序滚动渲染弹层延迟,文字显示很慢。
2018-12-21