@pluve/logger-sdk 0.0.4 → 0.0.5
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.
- package/README.md +186 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -2
- package/dist/loggerSDK.d.ts +25 -1
- package/dist/loggerSDK.js +327 -28
- package/dist/queueManager.d.ts +59 -0
- package/dist/queueManager.js +201 -0
- package/dist/retryManager.d.ts +57 -0
- package/dist/retryManager.js +223 -0
- package/dist/types.d.ts +20 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## 简介
|
|
4
4
|
|
|
5
|
-
- 轻量级前端日志采集 SDK,支持 H5
|
|
5
|
+
- 轻量级前端日志采集 SDK,支持 H5 浏览器环境和微信小程序
|
|
6
6
|
- 使用 Beacon 和像素图(Image)方式上报,确保兼容性与可靠性
|
|
7
7
|
- 标准化日志格式,易于分析和处理
|
|
8
|
+
- 支持批量上报、异常重试、持久化存储等高级功能
|
|
8
9
|
|
|
9
10
|
## 安装与引入
|
|
10
11
|
|
|
@@ -89,6 +90,22 @@ interface SDKOptions {
|
|
|
89
90
|
debug?: boolean; // 是否开启调试模式
|
|
90
91
|
pixelParam?: string; // 像素上报参数名,默认 'data'
|
|
91
92
|
maxPixelUrlLen?: number; // 像素上报 URL 最大长度,默认 1900
|
|
93
|
+
|
|
94
|
+
// 批量上报配置
|
|
95
|
+
enableBatch?: boolean; // 是否启用批量上报,默认 true
|
|
96
|
+
batchSize?: number; // 批量上报最大数量,默认 10
|
|
97
|
+
batchInterval?: number; // 批量上报时间间隔(毫秒),默认 5000
|
|
98
|
+
maxQueueSize?: number; // 队列最大长度,默认 100
|
|
99
|
+
|
|
100
|
+
// 持久化存储配置
|
|
101
|
+
enableStorage?: boolean; // 是否启用持久化存储,默认 true
|
|
102
|
+
storagePrefix?: string; // 持久化存储的 key 前缀,默认 'logger_sdk'
|
|
103
|
+
|
|
104
|
+
// 重试配置
|
|
105
|
+
enableRetry?: boolean; // 是否启用重试,默认 true
|
|
106
|
+
maxRetries?: number; // 最大重试次数,默认 3
|
|
107
|
+
retryDelay?: number; // 重试基础延迟时间(毫秒),默认 1000
|
|
108
|
+
retryBackoff?: boolean; // 是否使用指数退避策略,默认 true
|
|
92
109
|
}
|
|
93
110
|
```
|
|
94
111
|
|
|
@@ -263,3 +280,171 @@ const sdk = new LoggerSDK({
|
|
|
263
280
|
debug: true, // 开启调试
|
|
264
281
|
});
|
|
265
282
|
```
|
|
283
|
+
|
|
284
|
+
## 高级功能
|
|
285
|
+
|
|
286
|
+
### 批量上报
|
|
287
|
+
|
|
288
|
+
SDK 支持批量上报功能,可以显著减少网络请求次数,提高性能:
|
|
289
|
+
|
|
290
|
+
- **队列缓存**:日志先缓存到内存队列,达到条件后批量发送
|
|
291
|
+
- **智能触发**:支持按数量阈值和时间间隔触发上报
|
|
292
|
+
- **手动控制**:提供 `flush()` 方法可手动立即上报
|
|
293
|
+
|
|
294
|
+
### 异常重试
|
|
295
|
+
|
|
296
|
+
为确保日志可靠上报,SDK 提供了自动重试机制:
|
|
297
|
+
|
|
298
|
+
- **自动重试**:上报失败时自动重试(默认最多3次)
|
|
299
|
+
- **指数退避**:采用指数退避策略(1秒→2秒→4秒...)
|
|
300
|
+
- **随机抖动**:添加0-30%随机延迟避免惊群效应
|
|
301
|
+
|
|
302
|
+
### 持久化存储
|
|
303
|
+
|
|
304
|
+
为了防止因页面意外关闭导致日志丢失,SDK 支持持久化存储:
|
|
305
|
+
|
|
306
|
+
- **自动保存**:队列数据自动保存到 localStorage(浏览器)或 Storage(微信小程序)
|
|
307
|
+
- **自动恢复**:页面刷新后自动恢复未上报的日志
|
|
308
|
+
|
|
309
|
+
## 核心流程
|
|
310
|
+
|
|
311
|
+
```mermaid
|
|
312
|
+
graph TB
|
|
313
|
+
A[用户调用 track() 方法] --> B{SDK是否已销毁?}
|
|
314
|
+
B -- 是 --> C[直接返回,不处理]
|
|
315
|
+
B -- 否 --> D[生成标准化日志对象]
|
|
316
|
+
D --> E{是否启用批量上报?}
|
|
317
|
+
E -- 否 --> F[直接上报日志]
|
|
318
|
+
E -- 是 --> G[将日志加入队列]
|
|
319
|
+
G --> H{队列大小是否达到批处理阈值?}
|
|
320
|
+
H -- 是 --> I[立即触发批量上报]
|
|
321
|
+
H -- 否 --> J[等待下次触发]
|
|
322
|
+
|
|
323
|
+
F --> K{是否启用重试机制?}
|
|
324
|
+
K -- 是 --> L[使用重试管理器发送]
|
|
325
|
+
K -- 否 --> M[直接发送请求]
|
|
326
|
+
|
|
327
|
+
L --> N[重试管理器执行发送任务]
|
|
328
|
+
N --> O{发送是否成功?}
|
|
329
|
+
O -- 是 --> P[上报完成]
|
|
330
|
+
O -- 否 --> Q[是否达到最大重试次数?]
|
|
331
|
+
Q -- 否 --> R[计算延迟时间<br/>(指数退避+随机抖动)]
|
|
332
|
+
R --> S[等待后重试]
|
|
333
|
+
S --> N
|
|
334
|
+
Q -- 是 --> T[上报失败,记录错误]
|
|
335
|
+
|
|
336
|
+
M --> U{发送是否成功?}
|
|
337
|
+
U -- 是 --> P
|
|
338
|
+
U -- 否 --> T
|
|
339
|
+
|
|
340
|
+
I --> V[执行批量上报]
|
|
341
|
+
V --> W[从队列获取待发送日志]
|
|
342
|
+
W --> X[调用重试管理器发送批量日志]
|
|
343
|
+
X --> Y{批量发送是否成功?}
|
|
344
|
+
Y -- 是 --> Z[从队列中移除已发送日志]
|
|
345
|
+
Y -- 否 --> AA[保留队列中的日志,下次重试]
|
|
346
|
+
|
|
347
|
+
J --> AB{定时器是否触发?}
|
|
348
|
+
AB -- 是 --> AC[执行批量上报]
|
|
349
|
+
AB -- 否 --> AD{页面是否卸载?}
|
|
350
|
+
AD -- 是 --> AE[调用 flush() 方法上报所有日志]
|
|
351
|
+
|
|
352
|
+
subgraph 队列管理
|
|
353
|
+
AF[内存队列]
|
|
354
|
+
AG[持久化存储<br/>localStorage/Storage]
|
|
355
|
+
AF <--> AG
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
subgraph 传输适配器
|
|
359
|
+
AH[Beacon 适配器]
|
|
360
|
+
AI[Image 像素适配器]
|
|
361
|
+
AJ[微信小程序适配器]
|
|
362
|
+
AK[自动选择最佳适配器]
|
|
363
|
+
AH --> AK
|
|
364
|
+
AI --> AK
|
|
365
|
+
AJ --> AK
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
Z --> P
|
|
369
|
+
AA --> P
|
|
370
|
+
AC --> V
|
|
371
|
+
AE --> V
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## 使用示例
|
|
375
|
+
|
|
376
|
+
### 基础使用(默认启用所有高级功能)
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { LoggerSDK } from '@pluve/logger-sdk';
|
|
380
|
+
|
|
381
|
+
const sdk = new LoggerSDK({
|
|
382
|
+
endpoint: 'https://your-api.com/logs',
|
|
383
|
+
appId: 'my-app',
|
|
384
|
+
env: 'prod',
|
|
385
|
+
debug: false,
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// 记录错误日志
|
|
389
|
+
sdk.track('error', 'TypeError: Cannot read property', {
|
|
390
|
+
level: 'error',
|
|
391
|
+
stack: 'Error stack trace...',
|
|
392
|
+
userId: '123',
|
|
393
|
+
tags: {
|
|
394
|
+
component: 'checkout',
|
|
395
|
+
browser: 'Chrome 120',
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### 批量上报配置
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
const sdk = new LoggerSDK({
|
|
404
|
+
endpoint: 'https://your-api.com/logs',
|
|
405
|
+
appId: 'my-app',
|
|
406
|
+
|
|
407
|
+
// 批量上报配置
|
|
408
|
+
enableBatch: true, // 启用批量上报
|
|
409
|
+
batchSize: 20, // 队列达到20条时批量上报
|
|
410
|
+
batchInterval: 10000, // 或每隔10秒上报一次
|
|
411
|
+
maxQueueSize: 100, // 队列最大100条日志
|
|
412
|
+
|
|
413
|
+
// 持久化存储配置
|
|
414
|
+
enableStorage: true, // 启用持久化存储
|
|
415
|
+
storagePrefix: 'my_app_logger',
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### 重试机制配置
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const sdk = new LoggerSDK({
|
|
423
|
+
endpoint: 'https://your-api.com/logs',
|
|
424
|
+
appId: 'my-app',
|
|
425
|
+
|
|
426
|
+
// 重试配置
|
|
427
|
+
enableRetry: true, // 启用重试机制
|
|
428
|
+
maxRetries: 5, // 最多重试5次
|
|
429
|
+
retryDelay: 2000, // 基础延迟2秒
|
|
430
|
+
retryBackoff: true, // 启用指数退避
|
|
431
|
+
});
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 手动刷新队列
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
// 立即上报所有待发送日志
|
|
438
|
+
await sdk.flush();
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### 禁用批量上报(实时上报)
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const sdk = new LoggerSDK({
|
|
445
|
+
endpoint: 'https://your-api.com/logs',
|
|
446
|
+
appId: 'my-app',
|
|
447
|
+
enableBatch: false, // 禁用批量上报,每条日志立即发送
|
|
448
|
+
enableRetry: true, // 仍然启用重试
|
|
449
|
+
});
|
|
450
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -4,3 +4,7 @@ export type { TransportAdapter, TransportOptions } from './transportAdapter';
|
|
|
4
4
|
export type { SDKOptions, LogEvent, LogEventType, LogEventLevel, Env } from './types';
|
|
5
5
|
export type { PlatformType, EnvironmentInfo } from './utils';
|
|
6
6
|
export { getEnvironmentInfo, parseBrowserInfo, isWeChatMiniProgram, gzipCompress, isGzipSupported } from './utils';
|
|
7
|
+
export { QueueManager } from './queueManager';
|
|
8
|
+
export type { QueueOptions } from './queueManager';
|
|
9
|
+
export { RetryManager } from './retryManager';
|
|
10
|
+
export type { RetryOptions } from './retryManager';
|
package/dist/index.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* @Author : 黄震 huangzhen@yfpharmacy.com
|
|
3
3
|
* @Date : 2025-11-21 14:25:26
|
|
4
4
|
* @LastEditors : 黄震 huangzhen@yfpharmacy.com
|
|
5
|
-
* @LastEditTime : 2025-12-
|
|
5
|
+
* @LastEditTime : 2025-12-18 13:26:30
|
|
6
6
|
* @Description : 描述
|
|
7
7
|
* Copyright (c) 2025 by 益丰大药房连锁股份有限公司, All Rights Reserved.
|
|
8
8
|
*/
|
|
9
9
|
export { LoggerSDK } from "./loggerSDK";
|
|
10
10
|
export { defaultTransport, TransportAdapters } from "./transportAdapter";
|
|
11
|
-
export { getEnvironmentInfo, parseBrowserInfo, isWeChatMiniProgram, gzipCompress, isGzipSupported } from "./utils";
|
|
11
|
+
export { getEnvironmentInfo, parseBrowserInfo, isWeChatMiniProgram, gzipCompress, isGzipSupported } from "./utils";
|
|
12
|
+
export { QueueManager } from "./queueManager";
|
|
13
|
+
export { RetryManager } from "./retryManager";
|
package/dist/loggerSDK.d.ts
CHANGED
|
@@ -5,6 +5,14 @@ export declare class LoggerSDK {
|
|
|
5
5
|
private closed;
|
|
6
6
|
/** 预收集的环境信息 tags */
|
|
7
7
|
private envTags;
|
|
8
|
+
/** 队列管理器 */
|
|
9
|
+
private queueManager?;
|
|
10
|
+
/** 重试管理器 */
|
|
11
|
+
private retryManager?;
|
|
12
|
+
/** 批量上报定时器 */
|
|
13
|
+
private batchTimer?;
|
|
14
|
+
/** 是否正在上报 */
|
|
15
|
+
private isSending;
|
|
8
16
|
constructor(options: SDKOptions);
|
|
9
17
|
private logDebug;
|
|
10
18
|
/**
|
|
@@ -24,10 +32,26 @@ export declare class LoggerSDK {
|
|
|
24
32
|
* 设置用户信息
|
|
25
33
|
*/
|
|
26
34
|
identify(userId: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* 手动刷新队列,立即上报所有待发送日志
|
|
37
|
+
*/
|
|
38
|
+
flush(): Promise<void>;
|
|
27
39
|
/**
|
|
28
40
|
* 销毁实例
|
|
29
41
|
*/
|
|
30
|
-
destroy(): void
|
|
42
|
+
destroy(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* 发送单个事件(带重试)
|
|
45
|
+
*/
|
|
46
|
+
private sendEvent;
|
|
47
|
+
/**
|
|
48
|
+
* 批量发送事件(带重试)
|
|
49
|
+
*/
|
|
50
|
+
private sendBatch;
|
|
51
|
+
/**
|
|
52
|
+
* 启动批量上报定时器
|
|
53
|
+
*/
|
|
54
|
+
private startBatchTimer;
|
|
31
55
|
/**
|
|
32
56
|
* 监听页面卸载事件
|
|
33
57
|
*/
|
package/dist/loggerSDK.js
CHANGED
|
@@ -12,6 +12,8 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
12
12
|
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
13
13
|
import { defaultTransport } from "./transportAdapter";
|
|
14
14
|
import { isBrowser, isWeChatMiniProgram, now, getSessionId, getCurrentUrl, getEnvironmentInfo, parseBrowserInfo, generateUUID } from "./utils";
|
|
15
|
+
import { QueueManager } from "./queueManager";
|
|
16
|
+
import { RetryManager } from "./retryManager";
|
|
15
17
|
export var LoggerSDK = /*#__PURE__*/function () {
|
|
16
18
|
function LoggerSDK(options) {
|
|
17
19
|
_classCallCheck(this, LoggerSDK);
|
|
@@ -20,6 +22,14 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
20
22
|
_defineProperty(this, "closed", false);
|
|
21
23
|
/** 预收集的环境信息 tags */
|
|
22
24
|
_defineProperty(this, "envTags", void 0);
|
|
25
|
+
/** 队列管理器 */
|
|
26
|
+
_defineProperty(this, "queueManager", void 0);
|
|
27
|
+
/** 重试管理器 */
|
|
28
|
+
_defineProperty(this, "retryManager", void 0);
|
|
29
|
+
/** 批量上报定时器 */
|
|
30
|
+
_defineProperty(this, "batchTimer", void 0);
|
|
31
|
+
/** 是否正在上报 */
|
|
32
|
+
_defineProperty(this, "isSending", false);
|
|
23
33
|
this.opts = {
|
|
24
34
|
endpoint: options.endpoint,
|
|
25
35
|
appId: options.appId || 'unknown',
|
|
@@ -27,12 +37,53 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
27
37
|
debug: !!options.debug,
|
|
28
38
|
pixelParam: options.pixelParam || 'data',
|
|
29
39
|
maxPixelUrlLen: options.maxPixelUrlLen || 1900,
|
|
30
|
-
enableGzip: !!options.enableGzip
|
|
40
|
+
enableGzip: !!options.enableGzip,
|
|
41
|
+
// 批量上报配置
|
|
42
|
+
enableBatch: options.enableBatch !== false,
|
|
43
|
+
// 默认启用
|
|
44
|
+
batchSize: options.batchSize || 10,
|
|
45
|
+
batchInterval: options.batchInterval || 5000,
|
|
46
|
+
maxQueueSize: options.maxQueueSize || 100,
|
|
47
|
+
// 持久化存储配置
|
|
48
|
+
enableStorage: options.enableStorage !== false,
|
|
49
|
+
// 默认启用
|
|
50
|
+
storagePrefix: options.storagePrefix || 'logger_sdk',
|
|
51
|
+
// 重试配置
|
|
52
|
+
enableRetry: options.enableRetry !== false,
|
|
53
|
+
// 默认启用
|
|
54
|
+
maxRetries: options.maxRetries || 3,
|
|
55
|
+
retryDelay: options.retryDelay || 1000,
|
|
56
|
+
retryBackoff: options.retryBackoff !== false // 默认启用
|
|
31
57
|
};
|
|
32
58
|
|
|
33
59
|
// 初始化时收集环境信息
|
|
34
60
|
this.envTags = this.collectEnvironmentTags();
|
|
35
61
|
|
|
62
|
+
// 初始化队列管理器
|
|
63
|
+
if (this.opts.enableBatch) {
|
|
64
|
+
this.queueManager = new QueueManager({
|
|
65
|
+
maxSize: this.opts.maxQueueSize,
|
|
66
|
+
enableStorage: this.opts.enableStorage,
|
|
67
|
+
storagePrefix: this.opts.storagePrefix,
|
|
68
|
+
debug: this.opts.debug
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 初始化重试管理器
|
|
73
|
+
if (this.opts.enableRetry) {
|
|
74
|
+
this.retryManager = new RetryManager({
|
|
75
|
+
maxRetries: this.opts.maxRetries,
|
|
76
|
+
baseDelay: this.opts.retryDelay,
|
|
77
|
+
useBackoff: this.opts.retryBackoff,
|
|
78
|
+
debug: this.opts.debug
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 启动批量上报定时器
|
|
83
|
+
if (this.opts.enableBatch) {
|
|
84
|
+
this.startBatchTimer();
|
|
85
|
+
}
|
|
86
|
+
|
|
36
87
|
// 监听页面卸载事件
|
|
37
88
|
this.attachUnloadHandlers();
|
|
38
89
|
}
|
|
@@ -121,22 +172,41 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
121
172
|
tags: _objectSpread(_objectSpread({}, this.envTags), (options === null || options === void 0 ? void 0 : options.tags) || {})
|
|
122
173
|
};
|
|
123
174
|
this.logDebug('track', logEvent);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
175
|
+
|
|
176
|
+
// 如果启用批量上报,添加到队列
|
|
177
|
+
if (!(this.opts.enableBatch && this.queueManager)) {
|
|
178
|
+
_context.next = 13;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
this.queueManager.enqueue(logEvent);
|
|
182
|
+
// 如果队列已满,立即上报
|
|
183
|
+
if (!(this.queueManager.size() >= this.opts.batchSize)) {
|
|
184
|
+
_context.next = 11;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
this.logDebug('Queue size reached batch size, flushing immediately');
|
|
188
|
+
_context.next = 11;
|
|
189
|
+
return this.flush();
|
|
190
|
+
case 11:
|
|
191
|
+
_context.next = 21;
|
|
192
|
+
break;
|
|
193
|
+
case 13:
|
|
194
|
+
_context.prev = 13;
|
|
195
|
+
_context.next = 16;
|
|
196
|
+
return this.sendEvent(logEvent);
|
|
197
|
+
case 16:
|
|
198
|
+
_context.next = 21;
|
|
129
199
|
break;
|
|
130
|
-
case
|
|
131
|
-
_context.prev =
|
|
132
|
-
_context.t0 = _context["catch"](
|
|
200
|
+
case 18:
|
|
201
|
+
_context.prev = 18;
|
|
202
|
+
_context.t0 = _context["catch"](13);
|
|
133
203
|
this.logDebug('track failed', _context.t0);
|
|
134
204
|
// 静默失败,不影响主流程
|
|
135
|
-
case
|
|
205
|
+
case 21:
|
|
136
206
|
case "end":
|
|
137
207
|
return _context.stop();
|
|
138
208
|
}
|
|
139
|
-
}, _callee, this, [[
|
|
209
|
+
}, _callee, this, [[13, 18]]);
|
|
140
210
|
}));
|
|
141
211
|
function track(_x, _x2, _x3, _x4) {
|
|
142
212
|
return _track.apply(this, arguments);
|
|
@@ -154,13 +224,241 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
154
224
|
// userId 将在 track 时传入
|
|
155
225
|
}
|
|
156
226
|
|
|
227
|
+
/**
|
|
228
|
+
* 手动刷新队列,立即上报所有待发送日志
|
|
229
|
+
*/
|
|
230
|
+
}, {
|
|
231
|
+
key: "flush",
|
|
232
|
+
value: (function () {
|
|
233
|
+
var _flush = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2() {
|
|
234
|
+
var events;
|
|
235
|
+
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
|
|
236
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
237
|
+
case 0:
|
|
238
|
+
if (!(!this.queueManager || this.queueManager.size() === 0)) {
|
|
239
|
+
_context2.next = 2;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
return _context2.abrupt("return");
|
|
243
|
+
case 2:
|
|
244
|
+
if (!this.isSending) {
|
|
245
|
+
_context2.next = 5;
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
this.logDebug('Already sending, skip flush');
|
|
249
|
+
return _context2.abrupt("return");
|
|
250
|
+
case 5:
|
|
251
|
+
this.isSending = true;
|
|
252
|
+
this.logDebug("Flushing ".concat(this.queueManager.size(), " events"));
|
|
253
|
+
_context2.prev = 7;
|
|
254
|
+
// 获取所有待发送的日志(不移除)
|
|
255
|
+
events = this.queueManager.peek(this.queueManager.size());
|
|
256
|
+
if (!(events.length === 0)) {
|
|
257
|
+
_context2.next = 11;
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
return _context2.abrupt("return");
|
|
261
|
+
case 11:
|
|
262
|
+
_context2.next = 13;
|
|
263
|
+
return this.sendBatch(events);
|
|
264
|
+
case 13:
|
|
265
|
+
// 发送成功后,从队列中移除
|
|
266
|
+
this.queueManager.dequeue(events.length);
|
|
267
|
+
this.logDebug("Flushed ".concat(events.length, " events successfully"));
|
|
268
|
+
_context2.next = 20;
|
|
269
|
+
break;
|
|
270
|
+
case 17:
|
|
271
|
+
_context2.prev = 17;
|
|
272
|
+
_context2.t0 = _context2["catch"](7);
|
|
273
|
+
this.logDebug('Flush failed', _context2.t0);
|
|
274
|
+
// 失败不移除队列,下次继续重试
|
|
275
|
+
case 20:
|
|
276
|
+
_context2.prev = 20;
|
|
277
|
+
this.isSending = false;
|
|
278
|
+
return _context2.finish(20);
|
|
279
|
+
case 23:
|
|
280
|
+
case "end":
|
|
281
|
+
return _context2.stop();
|
|
282
|
+
}
|
|
283
|
+
}, _callee2, this, [[7, 17, 20, 23]]);
|
|
284
|
+
}));
|
|
285
|
+
function flush() {
|
|
286
|
+
return _flush.apply(this, arguments);
|
|
287
|
+
}
|
|
288
|
+
return flush;
|
|
289
|
+
}()
|
|
157
290
|
/**
|
|
158
291
|
* 销毁实例
|
|
159
292
|
*/
|
|
293
|
+
)
|
|
160
294
|
}, {
|
|
161
295
|
key: "destroy",
|
|
162
|
-
value: function
|
|
163
|
-
|
|
296
|
+
value: (function () {
|
|
297
|
+
var _destroy = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3() {
|
|
298
|
+
return _regeneratorRuntime().wrap(function _callee3$(_context3) {
|
|
299
|
+
while (1) switch (_context3.prev = _context3.next) {
|
|
300
|
+
case 0:
|
|
301
|
+
this.closed = true;
|
|
302
|
+
|
|
303
|
+
// 停止批量上报定时器
|
|
304
|
+
if (this.batchTimer) {
|
|
305
|
+
clearInterval(this.batchTimer);
|
|
306
|
+
this.batchTimer = null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 刷新队列
|
|
310
|
+
_context3.next = 4;
|
|
311
|
+
return this.flush();
|
|
312
|
+
case 4:
|
|
313
|
+
// 清理资源
|
|
314
|
+
if (this.queueManager) {
|
|
315
|
+
this.queueManager.clear();
|
|
316
|
+
}
|
|
317
|
+
if (this.retryManager) {
|
|
318
|
+
this.retryManager.clear();
|
|
319
|
+
}
|
|
320
|
+
case 6:
|
|
321
|
+
case "end":
|
|
322
|
+
return _context3.stop();
|
|
323
|
+
}
|
|
324
|
+
}, _callee3, this);
|
|
325
|
+
}));
|
|
326
|
+
function destroy() {
|
|
327
|
+
return _destroy.apply(this, arguments);
|
|
328
|
+
}
|
|
329
|
+
return destroy;
|
|
330
|
+
}() // ========== 内部方法 ===========
|
|
331
|
+
/**
|
|
332
|
+
* 发送单个事件(带重试)
|
|
333
|
+
*/
|
|
334
|
+
)
|
|
335
|
+
}, {
|
|
336
|
+
key: "sendEvent",
|
|
337
|
+
value: function () {
|
|
338
|
+
var _sendEvent = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(event) {
|
|
339
|
+
var _this = this;
|
|
340
|
+
var sendFn;
|
|
341
|
+
return _regeneratorRuntime().wrap(function _callee5$(_context5) {
|
|
342
|
+
while (1) switch (_context5.prev = _context5.next) {
|
|
343
|
+
case 0:
|
|
344
|
+
sendFn = /*#__PURE__*/function () {
|
|
345
|
+
var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4() {
|
|
346
|
+
return _regeneratorRuntime().wrap(function _callee4$(_context4) {
|
|
347
|
+
while (1) switch (_context4.prev = _context4.next) {
|
|
348
|
+
case 0:
|
|
349
|
+
_context4.next = 2;
|
|
350
|
+
return defaultTransport(event, _this.opts);
|
|
351
|
+
case 2:
|
|
352
|
+
case "end":
|
|
353
|
+
return _context4.stop();
|
|
354
|
+
}
|
|
355
|
+
}, _callee4);
|
|
356
|
+
}));
|
|
357
|
+
return function sendFn() {
|
|
358
|
+
return _ref.apply(this, arguments);
|
|
359
|
+
};
|
|
360
|
+
}(); // 如果启用重试
|
|
361
|
+
if (!(this.opts.enableRetry && this.retryManager)) {
|
|
362
|
+
_context5.next = 6;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
_context5.next = 4;
|
|
366
|
+
return this.retryManager.executeWithRetry(event.logId, sendFn);
|
|
367
|
+
case 4:
|
|
368
|
+
_context5.next = 8;
|
|
369
|
+
break;
|
|
370
|
+
case 6:
|
|
371
|
+
_context5.next = 8;
|
|
372
|
+
return sendFn();
|
|
373
|
+
case 8:
|
|
374
|
+
case "end":
|
|
375
|
+
return _context5.stop();
|
|
376
|
+
}
|
|
377
|
+
}, _callee5, this);
|
|
378
|
+
}));
|
|
379
|
+
function sendEvent(_x5) {
|
|
380
|
+
return _sendEvent.apply(this, arguments);
|
|
381
|
+
}
|
|
382
|
+
return sendEvent;
|
|
383
|
+
}()
|
|
384
|
+
/**
|
|
385
|
+
* 批量发送事件(带重试)
|
|
386
|
+
*/
|
|
387
|
+
}, {
|
|
388
|
+
key: "sendBatch",
|
|
389
|
+
value: (function () {
|
|
390
|
+
var _sendBatch = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7(events) {
|
|
391
|
+
var _this2 = this;
|
|
392
|
+
var batchId, sendFn;
|
|
393
|
+
return _regeneratorRuntime().wrap(function _callee7$(_context7) {
|
|
394
|
+
while (1) switch (_context7.prev = _context7.next) {
|
|
395
|
+
case 0:
|
|
396
|
+
if (!(events.length === 0)) {
|
|
397
|
+
_context7.next = 2;
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
return _context7.abrupt("return");
|
|
401
|
+
case 2:
|
|
402
|
+
// 生成批次 ID
|
|
403
|
+
batchId = "batch_".concat(now(), "_").concat(Math.random().toString(36).substring(2, 9));
|
|
404
|
+
sendFn = /*#__PURE__*/function () {
|
|
405
|
+
var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6() {
|
|
406
|
+
return _regeneratorRuntime().wrap(function _callee6$(_context6) {
|
|
407
|
+
while (1) switch (_context6.prev = _context6.next) {
|
|
408
|
+
case 0:
|
|
409
|
+
_context6.next = 2;
|
|
410
|
+
return defaultTransport(events, _this2.opts);
|
|
411
|
+
case 2:
|
|
412
|
+
case "end":
|
|
413
|
+
return _context6.stop();
|
|
414
|
+
}
|
|
415
|
+
}, _callee6);
|
|
416
|
+
}));
|
|
417
|
+
return function sendFn() {
|
|
418
|
+
return _ref2.apply(this, arguments);
|
|
419
|
+
};
|
|
420
|
+
}(); // 如果启用重试
|
|
421
|
+
if (!(this.opts.enableRetry && this.retryManager)) {
|
|
422
|
+
_context7.next = 9;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
_context7.next = 7;
|
|
426
|
+
return this.retryManager.executeWithRetry(batchId, sendFn);
|
|
427
|
+
case 7:
|
|
428
|
+
_context7.next = 11;
|
|
429
|
+
break;
|
|
430
|
+
case 9:
|
|
431
|
+
_context7.next = 11;
|
|
432
|
+
return sendFn();
|
|
433
|
+
case 11:
|
|
434
|
+
case "end":
|
|
435
|
+
return _context7.stop();
|
|
436
|
+
}
|
|
437
|
+
}, _callee7, this);
|
|
438
|
+
}));
|
|
439
|
+
function sendBatch(_x6) {
|
|
440
|
+
return _sendBatch.apply(this, arguments);
|
|
441
|
+
}
|
|
442
|
+
return sendBatch;
|
|
443
|
+
}()
|
|
444
|
+
/**
|
|
445
|
+
* 启动批量上报定时器
|
|
446
|
+
*/
|
|
447
|
+
)
|
|
448
|
+
}, {
|
|
449
|
+
key: "startBatchTimer",
|
|
450
|
+
value: function startBatchTimer() {
|
|
451
|
+
var _this3 = this;
|
|
452
|
+
if (this.batchTimer) return;
|
|
453
|
+
this.batchTimer = setInterval(function () {
|
|
454
|
+
if (!_this3.closed && _this3.queueManager && _this3.queueManager.size() > 0) {
|
|
455
|
+
_this3.logDebug('Batch timer triggered, flushing queue');
|
|
456
|
+
_this3.flush().catch(function (error) {
|
|
457
|
+
_this3.logDebug('Batch timer flush failed', error);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}, this.opts.batchInterval);
|
|
461
|
+
this.logDebug("Batch timer started with interval ".concat(this.opts.batchInterval, "ms"));
|
|
164
462
|
}
|
|
165
463
|
|
|
166
464
|
// ========== 自动采集 ===========
|
|
@@ -171,6 +469,7 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
171
469
|
}, {
|
|
172
470
|
key: "attachUnloadHandlers",
|
|
173
471
|
value: function attachUnloadHandlers() {
|
|
472
|
+
var _this4 = this;
|
|
174
473
|
// 微信小程序环境
|
|
175
474
|
if (isWeChatMiniProgram()) {
|
|
176
475
|
// 微信小程序使用 App 生命周期
|
|
@@ -187,11 +486,11 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
187
486
|
document.addEventListener && document.addEventListener('visibilitychange', function () {
|
|
188
487
|
try {
|
|
189
488
|
if (document.visibilityState === 'hidden') {
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
console.log('Page hidden');
|
|
489
|
+
// 页面隐藏时刷新队列
|
|
490
|
+
_this4.flush().catch(function () {
|
|
491
|
+
// 忽略错误
|
|
492
|
+
});
|
|
493
|
+
console.log('Page hidden, flushed queue');
|
|
195
494
|
}
|
|
196
495
|
} catch (_unused) {
|
|
197
496
|
//
|
|
@@ -201,11 +500,11 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
201
500
|
// 页面隐藏
|
|
202
501
|
win.addEventListener && win.addEventListener('pagehide', function () {
|
|
203
502
|
try {
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
console.log('Page hide');
|
|
503
|
+
// 页面隐藏时刷新队列(同步)
|
|
504
|
+
_this4.flush().catch(function () {
|
|
505
|
+
// 忽略错误
|
|
506
|
+
});
|
|
507
|
+
console.log('Page hide, flushed queue');
|
|
209
508
|
} catch (_unused2) {
|
|
210
509
|
//
|
|
211
510
|
}
|
|
@@ -214,11 +513,11 @@ export var LoggerSDK = /*#__PURE__*/function () {
|
|
|
214
513
|
// 页面卸载前
|
|
215
514
|
win.addEventListener && win.addEventListener('beforeunload', function () {
|
|
216
515
|
try {
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
console.log('Page unload');
|
|
516
|
+
// 页面卸载前刷新队列(同步)
|
|
517
|
+
_this4.flush().catch(function () {
|
|
518
|
+
// 忽略错误
|
|
519
|
+
});
|
|
520
|
+
console.log('Page unload, flushed queue');
|
|
222
521
|
} catch (_unused3) {
|
|
223
522
|
//
|
|
224
523
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { LogEvent } from './types';
|
|
2
|
+
/** 队列配置选项 */
|
|
3
|
+
export interface QueueOptions {
|
|
4
|
+
/** 队列最大长度 */
|
|
5
|
+
maxSize: number;
|
|
6
|
+
/** 是否启用持久化存储 */
|
|
7
|
+
enableStorage: boolean;
|
|
8
|
+
/** 存储 key 前缀 */
|
|
9
|
+
storagePrefix: string;
|
|
10
|
+
/** 调试模式 */
|
|
11
|
+
debug?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 队列管理器
|
|
15
|
+
* - 内存队列:快速访问
|
|
16
|
+
* - 持久化存储:防止页面刷新/关闭时数据丢失
|
|
17
|
+
*/
|
|
18
|
+
export declare class QueueManager {
|
|
19
|
+
private queue;
|
|
20
|
+
private opts;
|
|
21
|
+
private storageKey;
|
|
22
|
+
constructor(options: QueueOptions);
|
|
23
|
+
/**
|
|
24
|
+
* 添加日志到队列
|
|
25
|
+
*/
|
|
26
|
+
enqueue(event: LogEvent): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 批量获取日志(不移除)
|
|
29
|
+
*/
|
|
30
|
+
peek(count: number): LogEvent[];
|
|
31
|
+
/**
|
|
32
|
+
* 批量移除日志
|
|
33
|
+
*/
|
|
34
|
+
dequeue(count: number): LogEvent[];
|
|
35
|
+
/**
|
|
36
|
+
* 获取队列长度
|
|
37
|
+
*/
|
|
38
|
+
size(): number;
|
|
39
|
+
/**
|
|
40
|
+
* 清空队列
|
|
41
|
+
*/
|
|
42
|
+
clear(): void;
|
|
43
|
+
/**
|
|
44
|
+
* 从持久化存储加载队列
|
|
45
|
+
*/
|
|
46
|
+
private loadFromStorage;
|
|
47
|
+
/**
|
|
48
|
+
* 保存队列到持久化存储
|
|
49
|
+
*/
|
|
50
|
+
private saveToStorage;
|
|
51
|
+
/**
|
|
52
|
+
* 从持久化存储移除队列
|
|
53
|
+
*/
|
|
54
|
+
private removeFromStorage;
|
|
55
|
+
/**
|
|
56
|
+
* 调试日志
|
|
57
|
+
*/
|
|
58
|
+
private logDebug;
|
|
59
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
3
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
4
|
+
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
5
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
6
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
7
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
8
|
+
/*
|
|
9
|
+
* @Author : 黄震 huangzhen@yfpharmacy.com
|
|
10
|
+
* @Date : 2025-12-18
|
|
11
|
+
* @LastEditors : 黄震 huangzhen@yfpharmacy.com
|
|
12
|
+
* @LastEditTime : 2025-12-18
|
|
13
|
+
* @Description : 队列管理器 - 支持内存队列和持久化存储
|
|
14
|
+
* Copyright (c) 2025 by 益丰大药房连锁股份有限公司, All Rights Reserved.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { isBrowser, isWeChatMiniProgram, safeStringify } from "./utils";
|
|
18
|
+
|
|
19
|
+
/** 队列配置选项 */
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 队列管理器
|
|
23
|
+
* - 内存队列:快速访问
|
|
24
|
+
* - 持久化存储:防止页面刷新/关闭时数据丢失
|
|
25
|
+
*/
|
|
26
|
+
export var QueueManager = /*#__PURE__*/function () {
|
|
27
|
+
function QueueManager(options) {
|
|
28
|
+
_classCallCheck(this, QueueManager);
|
|
29
|
+
_defineProperty(this, "queue", []);
|
|
30
|
+
_defineProperty(this, "opts", void 0);
|
|
31
|
+
_defineProperty(this, "storageKey", void 0);
|
|
32
|
+
this.opts = options;
|
|
33
|
+
this.storageKey = "".concat(options.storagePrefix, "_queue");
|
|
34
|
+
|
|
35
|
+
// 从持久化存储恢复队列
|
|
36
|
+
if (this.opts.enableStorage) {
|
|
37
|
+
this.loadFromStorage();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 添加日志到队列
|
|
43
|
+
*/
|
|
44
|
+
_createClass(QueueManager, [{
|
|
45
|
+
key: "enqueue",
|
|
46
|
+
value: function enqueue(event) {
|
|
47
|
+
// 队列已满,丢弃最旧的日志
|
|
48
|
+
if (this.queue.length >= this.opts.maxSize) {
|
|
49
|
+
this.queue.shift();
|
|
50
|
+
this.logDebug('Queue full, dropped oldest event');
|
|
51
|
+
}
|
|
52
|
+
this.queue.push(event);
|
|
53
|
+
this.logDebug('Enqueued event', event.logId);
|
|
54
|
+
|
|
55
|
+
// 持久化到存储
|
|
56
|
+
if (this.opts.enableStorage) {
|
|
57
|
+
this.saveToStorage();
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 批量获取日志(不移除)
|
|
64
|
+
*/
|
|
65
|
+
}, {
|
|
66
|
+
key: "peek",
|
|
67
|
+
value: function peek(count) {
|
|
68
|
+
return this.queue.slice(0, count);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 批量移除日志
|
|
73
|
+
*/
|
|
74
|
+
}, {
|
|
75
|
+
key: "dequeue",
|
|
76
|
+
value: function dequeue(count) {
|
|
77
|
+
var items = this.queue.splice(0, count);
|
|
78
|
+
this.logDebug("Dequeued ".concat(items.length, " events"));
|
|
79
|
+
|
|
80
|
+
// 更新持久化存储
|
|
81
|
+
if (this.opts.enableStorage) {
|
|
82
|
+
this.saveToStorage();
|
|
83
|
+
}
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 获取队列长度
|
|
89
|
+
*/
|
|
90
|
+
}, {
|
|
91
|
+
key: "size",
|
|
92
|
+
value: function size() {
|
|
93
|
+
return this.queue.length;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 清空队列
|
|
98
|
+
*/
|
|
99
|
+
}, {
|
|
100
|
+
key: "clear",
|
|
101
|
+
value: function clear() {
|
|
102
|
+
this.queue = [];
|
|
103
|
+
this.logDebug('Queue cleared');
|
|
104
|
+
if (this.opts.enableStorage) {
|
|
105
|
+
this.removeFromStorage();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 从持久化存储加载队列
|
|
111
|
+
*/
|
|
112
|
+
}, {
|
|
113
|
+
key: "loadFromStorage",
|
|
114
|
+
value: function loadFromStorage() {
|
|
115
|
+
try {
|
|
116
|
+
var stored = null;
|
|
117
|
+
|
|
118
|
+
// 微信小程序环境
|
|
119
|
+
if (isWeChatMiniProgram()) {
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
stored = wx.getStorageSync(this.storageKey);
|
|
122
|
+
}
|
|
123
|
+
// 浏览器环境
|
|
124
|
+
else if (isBrowser() && typeof localStorage !== 'undefined') {
|
|
125
|
+
stored = localStorage.getItem(this.storageKey);
|
|
126
|
+
}
|
|
127
|
+
if (stored) {
|
|
128
|
+
var parsed = JSON.parse(stored);
|
|
129
|
+
if (Array.isArray(parsed)) {
|
|
130
|
+
this.queue = parsed.slice(0, this.opts.maxSize);
|
|
131
|
+
this.logDebug("Loaded ".concat(this.queue.length, " events from storage"));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
this.logDebug('Failed to load queue from storage', error);
|
|
136
|
+
// 加载失败不影响运行,使用空队列
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 保存队列到持久化存储
|
|
142
|
+
*/
|
|
143
|
+
}, {
|
|
144
|
+
key: "saveToStorage",
|
|
145
|
+
value: function saveToStorage() {
|
|
146
|
+
try {
|
|
147
|
+
var data = safeStringify(this.queue);
|
|
148
|
+
|
|
149
|
+
// 微信小程序环境
|
|
150
|
+
if (isWeChatMiniProgram()) {
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
wx.setStorageSync(this.storageKey, data);
|
|
153
|
+
}
|
|
154
|
+
// 浏览器环境
|
|
155
|
+
else if (isBrowser() && typeof localStorage !== 'undefined') {
|
|
156
|
+
localStorage.setItem(this.storageKey, data);
|
|
157
|
+
}
|
|
158
|
+
} catch (error) {
|
|
159
|
+
this.logDebug('Failed to save queue to storage', error);
|
|
160
|
+
// 保存失败不影响运行
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 从持久化存储移除队列
|
|
166
|
+
*/
|
|
167
|
+
}, {
|
|
168
|
+
key: "removeFromStorage",
|
|
169
|
+
value: function removeFromStorage() {
|
|
170
|
+
try {
|
|
171
|
+
// 微信小程序环境
|
|
172
|
+
if (isWeChatMiniProgram()) {
|
|
173
|
+
// @ts-ignore
|
|
174
|
+
wx.removeStorageSync(this.storageKey);
|
|
175
|
+
}
|
|
176
|
+
// 浏览器环境
|
|
177
|
+
else if (isBrowser() && typeof localStorage !== 'undefined') {
|
|
178
|
+
localStorage.removeItem(this.storageKey);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.logDebug('Failed to remove queue from storage', error);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 调试日志
|
|
187
|
+
*/
|
|
188
|
+
}, {
|
|
189
|
+
key: "logDebug",
|
|
190
|
+
value: function logDebug() {
|
|
191
|
+
if (this.opts.debug) {
|
|
192
|
+
var _console;
|
|
193
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
194
|
+
args[_key] = arguments[_key];
|
|
195
|
+
}
|
|
196
|
+
(_console = console).debug.apply(_console, ['[QueueManager]'].concat(args));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}]);
|
|
200
|
+
return QueueManager;
|
|
201
|
+
}();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** 重试配置选项 */
|
|
2
|
+
export interface RetryOptions {
|
|
3
|
+
/** 最大重试次数 */
|
|
4
|
+
maxRetries: number;
|
|
5
|
+
/** 基础延迟时间(毫秒) */
|
|
6
|
+
baseDelay: number;
|
|
7
|
+
/** 是否使用指数退避策略 */
|
|
8
|
+
useBackoff: boolean;
|
|
9
|
+
/** 调试模式 */
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 重试管理器
|
|
14
|
+
* - 支持自定义重试次数
|
|
15
|
+
* - 支持指数退避策略(exponential backoff)
|
|
16
|
+
* - 防止重复重试同一任务
|
|
17
|
+
*/
|
|
18
|
+
export declare class RetryManager {
|
|
19
|
+
private opts;
|
|
20
|
+
private retryingTasks;
|
|
21
|
+
constructor(options: RetryOptions);
|
|
22
|
+
/**
|
|
23
|
+
* 执行带重试的任务
|
|
24
|
+
* @param taskId - 任务唯一标识
|
|
25
|
+
* @param fn - 要执行的异步函数
|
|
26
|
+
* @returns Promise
|
|
27
|
+
*/
|
|
28
|
+
executeWithRetry<T>(taskId: string, fn: () => Promise<T>): Promise<T>;
|
|
29
|
+
/**
|
|
30
|
+
* 执行任务(带重试逻辑)
|
|
31
|
+
*/
|
|
32
|
+
private executeTask;
|
|
33
|
+
/**
|
|
34
|
+
* 计算延迟时间
|
|
35
|
+
* @param attempt - 当前重试次数
|
|
36
|
+
* @param baseDelay - 基础延迟时间
|
|
37
|
+
* @param useBackoff - 是否使用指数退避
|
|
38
|
+
* @returns 延迟时间(毫秒)
|
|
39
|
+
*/
|
|
40
|
+
private calculateDelay;
|
|
41
|
+
/**
|
|
42
|
+
* 睡眠函数
|
|
43
|
+
*/
|
|
44
|
+
private sleep;
|
|
45
|
+
/**
|
|
46
|
+
* 获取正在重试的任务数量
|
|
47
|
+
*/
|
|
48
|
+
getRetryingCount(): number;
|
|
49
|
+
/**
|
|
50
|
+
* 清空所有重试任务
|
|
51
|
+
*/
|
|
52
|
+
clear(): void;
|
|
53
|
+
/**
|
|
54
|
+
* 调试日志
|
|
55
|
+
*/
|
|
56
|
+
private logDebug;
|
|
57
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
2
|
+
function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator.return && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, catch: function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
|
|
3
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
4
|
+
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
5
|
+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
6
|
+
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
7
|
+
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
8
|
+
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
9
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
|
10
|
+
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
11
|
+
/*
|
|
12
|
+
* @Author : 黄震 huangzhen@yfpharmacy.com
|
|
13
|
+
* @Date : 2025-12-18
|
|
14
|
+
* @LastEditors : 黄震 huangzhen@yfpharmacy.com
|
|
15
|
+
* @LastEditTime : 2025-12-18
|
|
16
|
+
* @Description : 重试管理器 - 支持指数退避策略
|
|
17
|
+
* Copyright (c) 2025 by 益丰大药房连锁股份有限公司, All Rights Reserved.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** 重试配置选项 */
|
|
21
|
+
|
|
22
|
+
/** 重试任务 */
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 重试管理器
|
|
26
|
+
* - 支持自定义重试次数
|
|
27
|
+
* - 支持指数退避策略(exponential backoff)
|
|
28
|
+
* - 防止重复重试同一任务
|
|
29
|
+
*/
|
|
30
|
+
export var RetryManager = /*#__PURE__*/function () {
|
|
31
|
+
function RetryManager(options) {
|
|
32
|
+
_classCallCheck(this, RetryManager);
|
|
33
|
+
_defineProperty(this, "opts", void 0);
|
|
34
|
+
_defineProperty(this, "retryingTasks", new Map());
|
|
35
|
+
this.opts = options;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 执行带重试的任务
|
|
40
|
+
* @param taskId - 任务唯一标识
|
|
41
|
+
* @param fn - 要执行的异步函数
|
|
42
|
+
* @returns Promise
|
|
43
|
+
*/
|
|
44
|
+
_createClass(RetryManager, [{
|
|
45
|
+
key: "executeWithRetry",
|
|
46
|
+
value: (function () {
|
|
47
|
+
var _executeWithRetry = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(taskId, fn) {
|
|
48
|
+
var task, result;
|
|
49
|
+
return _regeneratorRuntime().wrap(function _callee$(_context) {
|
|
50
|
+
while (1) switch (_context.prev = _context.next) {
|
|
51
|
+
case 0:
|
|
52
|
+
if (!this.retryingTasks.has(taskId)) {
|
|
53
|
+
_context.next = 3;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
this.logDebug("Task ".concat(taskId, " already retrying, skipped"));
|
|
57
|
+
throw new Error("Task ".concat(taskId, " already retrying"));
|
|
58
|
+
case 3:
|
|
59
|
+
task = {
|
|
60
|
+
id: taskId,
|
|
61
|
+
fn: fn,
|
|
62
|
+
retries: 0,
|
|
63
|
+
maxRetries: this.opts.maxRetries,
|
|
64
|
+
baseDelay: this.opts.baseDelay,
|
|
65
|
+
useBackoff: this.opts.useBackoff
|
|
66
|
+
};
|
|
67
|
+
this.retryingTasks.set(taskId, task);
|
|
68
|
+
_context.prev = 5;
|
|
69
|
+
_context.next = 8;
|
|
70
|
+
return this.executeTask(task);
|
|
71
|
+
case 8:
|
|
72
|
+
result = _context.sent;
|
|
73
|
+
this.retryingTasks.delete(taskId);
|
|
74
|
+
return _context.abrupt("return", result);
|
|
75
|
+
case 13:
|
|
76
|
+
_context.prev = 13;
|
|
77
|
+
_context.t0 = _context["catch"](5);
|
|
78
|
+
this.retryingTasks.delete(taskId);
|
|
79
|
+
throw _context.t0;
|
|
80
|
+
case 17:
|
|
81
|
+
case "end":
|
|
82
|
+
return _context.stop();
|
|
83
|
+
}
|
|
84
|
+
}, _callee, this, [[5, 13]]);
|
|
85
|
+
}));
|
|
86
|
+
function executeWithRetry(_x, _x2) {
|
|
87
|
+
return _executeWithRetry.apply(this, arguments);
|
|
88
|
+
}
|
|
89
|
+
return executeWithRetry;
|
|
90
|
+
}()
|
|
91
|
+
/**
|
|
92
|
+
* 执行任务(带重试逻辑)
|
|
93
|
+
*/
|
|
94
|
+
)
|
|
95
|
+
}, {
|
|
96
|
+
key: "executeTask",
|
|
97
|
+
value: (function () {
|
|
98
|
+
var _executeTask = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(task) {
|
|
99
|
+
var result, delay;
|
|
100
|
+
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
|
|
101
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
102
|
+
case 0:
|
|
103
|
+
if (!(task.retries <= task.maxRetries)) {
|
|
104
|
+
_context2.next = 21;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
_context2.prev = 1;
|
|
108
|
+
_context2.next = 4;
|
|
109
|
+
return task.fn();
|
|
110
|
+
case 4:
|
|
111
|
+
result = _context2.sent;
|
|
112
|
+
if (task.retries > 0) {
|
|
113
|
+
this.logDebug("Task ".concat(task.id, " succeeded after ").concat(task.retries, " retries"));
|
|
114
|
+
}
|
|
115
|
+
return _context2.abrupt("return", result);
|
|
116
|
+
case 9:
|
|
117
|
+
_context2.prev = 9;
|
|
118
|
+
_context2.t0 = _context2["catch"](1);
|
|
119
|
+
task.retries += 1;
|
|
120
|
+
|
|
121
|
+
// 达到最大重试次数
|
|
122
|
+
if (!(task.retries > task.maxRetries)) {
|
|
123
|
+
_context2.next = 15;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
this.logDebug("Task ".concat(task.id, " failed after ").concat(task.maxRetries, " retries"));
|
|
127
|
+
throw _context2.t0;
|
|
128
|
+
case 15:
|
|
129
|
+
// 计算延迟时间
|
|
130
|
+
delay = this.calculateDelay(task.retries, task.baseDelay, task.useBackoff);
|
|
131
|
+
this.logDebug("Task ".concat(task.id, " failed (attempt ").concat(task.retries, "/").concat(task.maxRetries, "), retrying in ").concat(delay, "ms"));
|
|
132
|
+
|
|
133
|
+
// 等待后重试
|
|
134
|
+
_context2.next = 19;
|
|
135
|
+
return this.sleep(delay);
|
|
136
|
+
case 19:
|
|
137
|
+
_context2.next = 0;
|
|
138
|
+
break;
|
|
139
|
+
case 21:
|
|
140
|
+
throw new Error("Task ".concat(task.id, " exceeded max retries"));
|
|
141
|
+
case 22:
|
|
142
|
+
case "end":
|
|
143
|
+
return _context2.stop();
|
|
144
|
+
}
|
|
145
|
+
}, _callee2, this, [[1, 9]]);
|
|
146
|
+
}));
|
|
147
|
+
function executeTask(_x3) {
|
|
148
|
+
return _executeTask.apply(this, arguments);
|
|
149
|
+
}
|
|
150
|
+
return executeTask;
|
|
151
|
+
}()
|
|
152
|
+
/**
|
|
153
|
+
* 计算延迟时间
|
|
154
|
+
* @param attempt - 当前重试次数
|
|
155
|
+
* @param baseDelay - 基础延迟时间
|
|
156
|
+
* @param useBackoff - 是否使用指数退避
|
|
157
|
+
* @returns 延迟时间(毫秒)
|
|
158
|
+
*/
|
|
159
|
+
)
|
|
160
|
+
}, {
|
|
161
|
+
key: "calculateDelay",
|
|
162
|
+
value: function calculateDelay(attempt, baseDelay, useBackoff) {
|
|
163
|
+
if (!useBackoff) {
|
|
164
|
+
return baseDelay;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 指数退避策略:baseDelay * 2^(attempt-1)
|
|
168
|
+
// 第1次重试:baseDelay * 2^0 = baseDelay
|
|
169
|
+
// 第2次重试:baseDelay * 2^1 = baseDelay * 2
|
|
170
|
+
// 第3次重试:baseDelay * 2^2 = baseDelay * 4
|
|
171
|
+
var delay = baseDelay * Math.pow(2, attempt - 1);
|
|
172
|
+
|
|
173
|
+
// 添加随机抖动,避免惊群效应(thundering herd)
|
|
174
|
+
var jitter = Math.random() * 0.3 * delay; // 0-30% 的随机抖动
|
|
175
|
+
return Math.min(delay + jitter, 30000); // 最大不超过 30 秒
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 睡眠函数
|
|
180
|
+
*/
|
|
181
|
+
}, {
|
|
182
|
+
key: "sleep",
|
|
183
|
+
value: function sleep(ms) {
|
|
184
|
+
return new Promise(function (resolve) {
|
|
185
|
+
return setTimeout(resolve, ms);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 获取正在重试的任务数量
|
|
191
|
+
*/
|
|
192
|
+
}, {
|
|
193
|
+
key: "getRetryingCount",
|
|
194
|
+
value: function getRetryingCount() {
|
|
195
|
+
return this.retryingTasks.size;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 清空所有重试任务
|
|
200
|
+
*/
|
|
201
|
+
}, {
|
|
202
|
+
key: "clear",
|
|
203
|
+
value: function clear() {
|
|
204
|
+
this.retryingTasks.clear();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 调试日志
|
|
209
|
+
*/
|
|
210
|
+
}, {
|
|
211
|
+
key: "logDebug",
|
|
212
|
+
value: function logDebug() {
|
|
213
|
+
if (this.opts.debug) {
|
|
214
|
+
var _console;
|
|
215
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
216
|
+
args[_key] = arguments[_key];
|
|
217
|
+
}
|
|
218
|
+
(_console = console).debug.apply(_console, ['[RetryManager]'].concat(args));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}]);
|
|
222
|
+
return RetryManager;
|
|
223
|
+
}();
|
package/dist/types.d.ts
CHANGED
|
@@ -20,6 +20,26 @@ export interface SDKOptions {
|
|
|
20
20
|
maxPixelUrlLen?: number;
|
|
21
21
|
/** 是否启用 gzip 压缩,默认 false */
|
|
22
22
|
enableGzip?: boolean;
|
|
23
|
+
/** 是否启用批量上报,默认 true */
|
|
24
|
+
enableBatch?: boolean;
|
|
25
|
+
/** 批量上报最大数量,默认 10 */
|
|
26
|
+
batchSize?: number;
|
|
27
|
+
/** 批量上报时间间隔(毫秒),默认 5000 */
|
|
28
|
+
batchInterval?: number;
|
|
29
|
+
/** 队列最大长度,默认 100 */
|
|
30
|
+
maxQueueSize?: number;
|
|
31
|
+
/** 是否启用持久化存储,默认 true */
|
|
32
|
+
enableStorage?: boolean;
|
|
33
|
+
/** 持久化存储的 key 前缀,默认 'logger_sdk' */
|
|
34
|
+
storagePrefix?: string;
|
|
35
|
+
/** 是否启用重试,默认 true */
|
|
36
|
+
enableRetry?: boolean;
|
|
37
|
+
/** 最大重试次数,默认 3 */
|
|
38
|
+
maxRetries?: number;
|
|
39
|
+
/** 重试基础延迟时间(毫秒),默认 1000 */
|
|
40
|
+
retryDelay?: number;
|
|
41
|
+
/** 是否使用指数退避策略,默认 true */
|
|
42
|
+
retryBackoff?: boolean;
|
|
23
43
|
}
|
|
24
44
|
/** 标准化日志上报格式 */
|
|
25
45
|
export interface LogEvent {
|