import cache from '@/plugins/cache' import axiosInstance from './index' import Vue from 'vue' import TwoFAWindow from '@/components/common/TwoFAWindow' const requestMethods = ['get', 'post', 'delete', 'put', 'head', 'options', 'patch', 'request'] const extendsMethods = {} /** * 模拟Promise,达到Promise的then,catch和finally可以重复执行 * * @param fn 回调 */ const RepeatablePromise = function (fn) { this.__then = [] this.__catch = null this.__finally = null this.then = function (thenFn) { this.__then.push(thenFn) return this } this.catch = function (catchFn) { this.__catch = catchFn return this } this.finally = function (finallyFn) { this.__finally = finallyFn return this } fn(data => { try { let returnData = null for (let i = 0; i < this.__then.length; i++) { if (i === 0) { returnData = this.__then[i](data) } else { returnData = this.__then[i](returnData) } } } catch (e) { if (this.__catch) { throw new Error('Eva: ') } this.__catch && this.__catch(e) } this.__finally && this.__finally() }, e => { this.__catch && this.__catch(e) this.__finally && this.__finally() }) } /** * 构建基础请求对象Promise代理对象 * * @param method 请求方式 * @param args 请求参数 * @returns {{__access(*, *=): *, finally(): *, then(): *, catch(): *, __result_promise: null, __arguments: *}|*} */ const buildBasePromiseProxy = (method, args) => { return { // post,get等请求方法的调用参数 __arguments: args, // 请求结果的promise对象 __result_promise: null, // 代理then方法,直接调用目标promise的then方法 then () { return this.__access('then', arguments) }, // 代理catch方法,直接调用目标promise的catch方法 catch () { return this.__access('catch', arguments) }, // 代理finally方法,直接调用目标promise的finally方法 finally () { return this.__access('finally', arguments) }, __access (methodName, args) { if (this.__result_promise != null) { return this.__result_promise[methodName].apply(this.__result_promise, args) } // 开启了2FA认证 if (this.__with_2fa) { if (this.__2fa_window != null) { this.__result_promise = new RepeatablePromise((resolve, reject) => { this.__2fa_window.$on('then', resolve) this.__2fa_window.$on('catch', reject) this.__2fa_window.$on('cancel', reject) }) // - 打开窗口,在此处打开窗口,意味着必须执行then,catch或finally才会打开2fa认证窗口 this.__2fa_window.open(axiosInstance, method, this.__arguments) } } // 未开启2fa的情况下,直接发送请求 if (this.__result_promise == null) { this.__result_promise = axiosInstance[method].apply(axiosInstance, this.__arguments) } // 如果开启了缓存,则在请求成功后写入缓存 if (this.__with_cache) { this.__result_promise.then(data => { this.__cache_impl.set(this.__cache_key, data) return data }) } return this.__result_promise[methodName].apply(this.__result_promise, args) } } } /** * 构建缓存请求对象Promise代理对象 * * @param cacheKey 缓存key * @param method 请求方式 * @param args 请求参数 * @param cacheImplName 缓存实现名称 * @returns {{cache(): *, __with_cache: boolean, __cache_key: *, __cache_impl: *}|Promise|buildCachePromiseProxy} */ const buildCachePromiseProxy = (cacheKey, method, args, cacheImplName) => { return { // 缓存标记 __with_cache: true, // 缓存key __cache_key: cacheKey, // 缓存实现 __cache_impl: cache[cacheImplName], // 从缓存中获取数据 cache () { // 从缓存中获取数据 const data = this.__cache_impl.get(cacheKey) // 如果从缓存中获取到了数据,则直接构造一个成功的promise if (data != null) { this.__result_promise = Promise.resolve(data) } // 如果已经获取到了数据,则由以上成功的promise来接手then和catch的处理 if (this.__result_promise != null) { return this.__result_promise } return this } } } /** * 构建2FA请求对象Promise代理对象 * * @param twoFAWindow 2FA认证窗口实例 * @returns {{__2fa_window: *, __with_2fa: boolean}} */ const build2FAPromiseProxy = twoFAWindow => { return { // 2fa标记 __with_2fa: true, // 2fa窗口对象 __2fa_window: twoFAWindow } } /** * 扩展方法:开启缓存 * * @param cacheKey 缓存的key * @param isLocal 是否缓存到本地缓存LocalStorage,为false时缓存到SessionStorage * @usage:request.cache('test').[post(), get()...] * @returns {{isExtendsAxiosInstance: boolean, post: Function, get: Function, ...}} */ extendsMethods.cache = function (cacheKey, isLocal = false) { if (cacheKey == null) { throw Error('Request cache key can not be null.') } let cacheAxiosInstance = { // 标记为axios扩展实例,用于与原生axios作区分 isExtendsAxiosInstance: true } if (this.isExtendsAxiosInstance) { cacheAxiosInstance = this } for (const method of requestMethods) { if (cacheAxiosInstance[method] == null) { cacheAxiosInstance[method] = function () { return { ...buildBasePromiseProxy(method, arguments), ...buildCachePromiseProxy(cacheKey, method, arguments, isLocal ? 'local' : 'session') } } continue } // 不为null时说明在调用cache前调用了其他扩展方法,此时诸如post,get方法的返回值做合并,防止扩展方法丢失。 const originMethod = cacheAxiosInstance[method] cacheAxiosInstance[method] = function () { const request = originMethod() Object.assign(request, { ...buildBasePromiseProxy(method, arguments), ...buildCachePromiseProxy(cacheKey, method, arguments, isLocal ? 'local' : 'session') }) return request } } // 添加扩展方法 for (const key in extendsMethods) { cacheAxiosInstance[key] = extendsMethods[key] } return cacheAxiosInstance } /** * 扩展方法:开启2FA认证 * * @usage:request.twoFA().[post(), get()...] * @returns {{isExtendsAxiosInstance: boolean, post: Function, get: Function, ...}} */ extendsMethods.twoFA = function (props) { // 打开2FA窗口 let $twoFAWindow = null // - 只有在为获取到密码时(未保存密码或密码已过期)时才打开2FA窗口 if (cache.twoFA.getPassword() == null) { const TwoFAWindowVue = Vue.extend(TwoFAWindow) $twoFAWindow = new TwoFAWindowVue({ el: document.createElement('div'), propsData: props }) document.body.appendChild($twoFAWindow.$el) } let twofaAxiosInstance = { // 标记为axios扩展实例,用于与原生axios作区分 isExtendsAxiosInstance: true } if (this.isExtendsAxiosInstance) { twofaAxiosInstance = this } for (const method of requestMethods) { if (twofaAxiosInstance[method] == null) { twofaAxiosInstance[method] = function () { return { ...buildBasePromiseProxy(method, arguments), ...build2FAPromiseProxy($twoFAWindow) } } continue } // 不为null时说明在调用twoFA前调用了其他扩展方法,此时诸如post,get方法的返回值做合并,防止扩展方法丢失。 const originMethod = twofaAxiosInstance[method] twofaAxiosInstance[method] = function () { const request = originMethod() Object.assign(request, { ...buildBasePromiseProxy(method, arguments), ...build2FAPromiseProxy($twoFAWindow) }) return request } } // 添加扩展方法 for (const key in extendsMethods) { twofaAxiosInstance[key] = extendsMethods[key] } return twofaAxiosInstance } export default extendsMethods