jiangping
2023-08-21 2837bdd57f72e386bbf9a725e7b3a13e5eb9e930
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
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<any>|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