vue2-client 1.18.46 → 1.18.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,245 @@
1
+ /**
2
+ * 请求去重管理模块
3
+ *
4
+ * 支持两种去重策略:
5
+ * - reject: 拒绝重复请求(默认,适用于表单提交等写操作)
6
+ * - reuse: 复用请求结果(适用于下拉框数据、列表查询等读操作)
7
+ *
8
+ * @example
9
+ * // 写操作 - 默认拒绝重复
10
+ * post('/api/save', data)
11
+ *
12
+ * // 读操作 - 复用结果
13
+ * post('/api/options', params, { dedupe: true, dedupeStrategy: 'reuse' })
14
+ *
15
+ * // 关闭去重
16
+ * post('/api/query', params, { dedupe: false })
17
+ */
18
+
19
+ // ============ 去重策略常量 ============
20
+ export const DEDUPE_STRATEGY = {
21
+ REJECT: 'reject', // 拒绝重复请求
22
+ REUSE: 'reuse' // 复用请求结果
23
+ }
24
+
25
+ // 是否为开发环境
26
+ const isDev = process.env.NODE_ENV === 'development'
27
+
28
+ // 安全的日志输出(仅开发环境,且不输出完整 URL 避免敏感信息泄露)
29
+ const logger = {
30
+ warn: (msg, url) => isDev && console.warn(`[请求去重] ${msg}`, url ? `(${getUrlPath(url)})` : ''),
31
+ log: (msg, url) => isDev && console.log(`[请求去重] ${msg}`, url ? `(${getUrlPath(url)})` : '')
32
+ }
33
+
34
+ // 提取 URL 路径部分,去除查询参数(可能包含敏感信息)
35
+ function getUrlPath (url) {
36
+ if (!url) return ''
37
+ try {
38
+ // 只保留路径部分,去除查询参数
39
+ return url.split('?')[0]
40
+ } catch (e) {
41
+ return '[URL解析失败]'
42
+ }
43
+ }
44
+
45
+ // ============ 存储进行中的请求 ============
46
+ // Map<requestKey, { controller, config, promise?, resolvers? }>
47
+ const pendingRequests = new Map()
48
+
49
+ /**
50
+ * 生成请求唯一标识
51
+ * @param {object} config - axios 请求配置
52
+ * @returns {string} 请求标识
53
+ */
54
+ export function generateRequestKey(config) {
55
+ const { method, url, data, params } = config
56
+ let dataStr = ''
57
+ let paramsStr = ''
58
+ try {
59
+ dataStr = data ? JSON.stringify(data) : ''
60
+ paramsStr = params ? JSON.stringify(params) : ''
61
+ } catch (e) {
62
+ // 循环引用或其他序列化问题,使用时间戳保证唯一性
63
+ logger.warn('参数序列化失败,跳过去重')
64
+ dataStr = `_${Date.now()}_${Math.random()}`
65
+ }
66
+ return `${method}:${url}:${dataStr}:${paramsStr}`
67
+ }
68
+
69
+ /**
70
+ * 检查是否有相同的请求正在进行
71
+ * @param {string} requestKey - 请求标识
72
+ * @returns {boolean}
73
+ */
74
+ export function hasPendingRequest(requestKey) {
75
+ return pendingRequests.has(requestKey)
76
+ }
77
+
78
+ /**
79
+ * 获取 pending 请求信息
80
+ * @param {string} requestKey - 请求标识
81
+ * @returns {object|undefined}
82
+ */
83
+ export function getPendingRequest(requestKey) {
84
+ return pendingRequests.get(requestKey)
85
+ }
86
+
87
+ /**
88
+ * 添加请求到 pending 列表
89
+ * @param {string} requestKey - 请求标识
90
+ * @param {object} config - axios 请求配置
91
+ * @param {string} strategy - 去重策略
92
+ * @returns {object} { controller, isReuse, promise? }
93
+ */
94
+ export function addPendingRequest(requestKey, config, strategy = DEDUPE_STRATEGY.REJECT) {
95
+ // 创建 AbortController 用于取消请求
96
+ const controller = new AbortController()
97
+
98
+ const pendingInfo = {
99
+ controller,
100
+ config,
101
+ strategy,
102
+ promise: null,
103
+ resolvers: null
104
+ }
105
+
106
+ // 如果是复用策略,创建一个可共享的 Promise
107
+ if (strategy === DEDUPE_STRATEGY.REUSE) {
108
+ let _resolve, _reject
109
+ pendingInfo.promise = new Promise((resolve, reject) => {
110
+ _resolve = resolve
111
+ _reject = reject
112
+ })
113
+ pendingInfo.resolvers = { resolve: _resolve, reject: _reject }
114
+ }
115
+
116
+ pendingRequests.set(requestKey, pendingInfo)
117
+
118
+ return {
119
+ controller,
120
+ isReuse: false,
121
+ signal: controller.signal
122
+ }
123
+ }
124
+
125
+ /**
126
+ * 处理重复请求
127
+ * @param {string} requestKey - 请求标识
128
+ * @param {object} config - axios 请求配置
129
+ * @returns {object} { shouldProceed, promise?, error? }
130
+ */
131
+ export function handleDuplicateRequest(requestKey, config) {
132
+ const pending = pendingRequests.get(requestKey)
133
+
134
+ if (!pending) {
135
+ return { shouldProceed: true }
136
+ }
137
+
138
+ const strategy = config.dedupeStrategy || DEDUPE_STRATEGY.REJECT
139
+
140
+ // 复用策略:返回相同的 Promise
141
+ if (strategy === DEDUPE_STRATEGY.REUSE && pending.promise) {
142
+ logger.log('复用进行中的请求', config.url)
143
+ return {
144
+ shouldProceed: false,
145
+ isReuse: true,
146
+ promise: pending.promise
147
+ }
148
+ }
149
+
150
+ // 拒绝策略:返回错误
151
+ logger.warn('重复请求被拦截', config.url)
152
+ const error = new Error('重复请求被取消')
153
+ error.code = 'ERR_DUPLICATE_REQUEST'
154
+ error.config = config
155
+ return {
156
+ shouldProceed: false,
157
+ isReuse: false,
158
+ error
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 请求成功时的处理
164
+ * @param {string} requestKey - 请求标识
165
+ * @param {any} data - 响应数据
166
+ */
167
+ export function resolvePendingRequest(requestKey, data) {
168
+ const pending = pendingRequests.get(requestKey)
169
+ if (pending?.resolvers) {
170
+ pending.resolvers.resolve(data)
171
+ }
172
+ pendingRequests.delete(requestKey)
173
+ }
174
+
175
+ /**
176
+ * 请求失败时的处理
177
+ * @param {string} requestKey - 请求标识
178
+ * @param {Error} error - 错误信息
179
+ */
180
+ export function rejectPendingRequest(requestKey, error) {
181
+ const pending = pendingRequests.get(requestKey)
182
+ if (pending?.resolvers) {
183
+ pending.resolvers.reject(error)
184
+ }
185
+ pendingRequests.delete(requestKey)
186
+ }
187
+
188
+ /**
189
+ * 移除已完成的请求(兼容旧逻辑)
190
+ * @param {string} requestKey - 请求标识
191
+ */
192
+ export function removePendingRequest(requestKey) {
193
+ pendingRequests.delete(requestKey)
194
+ }
195
+
196
+ /**
197
+ * 清空所有 pending 请求
198
+ */
199
+ export function clearPendingRequests() {
200
+ pendingRequests.forEach(({ controller, resolvers }) => {
201
+ controller.abort()
202
+ // 如果有等待复用的请求,也要拒绝它们
203
+ if (resolvers) {
204
+ const error = new Error('请求被取消')
205
+ error.code = 'ERR_CANCELED'
206
+ resolvers.reject(error)
207
+ }
208
+ })
209
+ pendingRequests.clear()
210
+ }
211
+
212
+ /**
213
+ * 获取当前 pending 请求数量(用于调试)
214
+ * @returns {number}
215
+ */
216
+ export function getPendingCount() {
217
+ return pendingRequests.size
218
+ }
219
+
220
+ /**
221
+ * 判断是否应该启用去重
222
+ * @param {object} config - axios 请求配置
223
+ * @returns {boolean}
224
+ */
225
+ export function shouldEnableDedupe(config) {
226
+ // 显式关闭去重
227
+ if (config.dedupe === false) {
228
+ return false
229
+ }
230
+ // 显式开启去重
231
+ if (config.dedupe === true) {
232
+ return true
233
+ }
234
+ // 默认:POST 请求开启去重
235
+ return config.method?.toLowerCase() === 'post'
236
+ }
237
+
238
+ /**
239
+ * 获取去重策略
240
+ * @param {object} config - axios 请求配置
241
+ * @returns {string}
242
+ */
243
+ export function getDedupeStrategy(config) {
244
+ return config.dedupeStrategy || DEDUPE_STRATEGY.REJECT
245
+ }