@taicode/common-server 1.0.10 → 1.0.11
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/output/redis-queue/batch-redis-queue.d.ts +25 -25
- package/output/redis-queue/batch-redis-queue.d.ts.map +1 -1
- package/output/redis-queue/batch-redis-queue.js +83 -90
- package/output/redis-queue/batch-redis-queue.test.js +96 -251
- package/output/redis-queue/redis-queue.d.ts +26 -25
- package/output/redis-queue/redis-queue.d.ts.map +1 -1
- package/output/redis-queue/redis-queue.js +25 -28
- package/output/redis-queue/redis-queue.test.js +96 -698
- package/output/redis-queue/types.d.ts +14 -5
- package/output/redis-queue/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { createClient } from 'redis';
|
|
3
3
|
import { catchIt } from '@taicode/common-base';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* 通用任务队列类(泛型)
|
|
6
6
|
*
|
|
7
7
|
* 提供基于 Redis 的任务队列功能,支持:
|
|
8
8
|
* - 任务入队和持久化
|
|
@@ -10,24 +10,30 @@ import { catchIt } from '@taicode/common-base';
|
|
|
10
10
|
* - 任务状态追踪
|
|
11
11
|
* - 分布式消费
|
|
12
12
|
*
|
|
13
|
+
* @template T 任务数据类型
|
|
14
|
+
*
|
|
13
15
|
* @example
|
|
14
16
|
* ```ts
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* })
|
|
17
|
+
* interface EmailTask {
|
|
18
|
+
* to: string
|
|
19
|
+
* subject: string
|
|
20
|
+
* }
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* const queue = new RedisQueue<EmailTask>({
|
|
23
|
+
* redisUrl: 'redis://localhost:6379',
|
|
24
|
+
* queueKey: 'email-queue',
|
|
25
|
+
* concurrency: 5,
|
|
26
|
+
* handler: async (data) => {
|
|
27
|
+
* await sendEmail(data.to, data.subject)
|
|
28
|
+
* return catchIt(() => {})
|
|
29
|
+
* }
|
|
24
30
|
* })
|
|
25
31
|
*
|
|
26
32
|
* // 连接 Redis(自动启动消费者)
|
|
27
33
|
* await queue.connect()
|
|
28
34
|
*
|
|
29
35
|
* // 入队任务(自动开始处理)
|
|
30
|
-
* await queue.enqueue(
|
|
36
|
+
* await queue.enqueue({ to: 'user@example.com', subject: 'Hello' })
|
|
31
37
|
*
|
|
32
38
|
* // 获取队列统计信息(O(1) 时间复杂度)
|
|
33
39
|
* const stats = await queue.statistics()
|
|
@@ -41,7 +47,7 @@ export class RedisQueue {
|
|
|
41
47
|
recoveryInterval = null;
|
|
42
48
|
processingTasks = 0; // 当前正在处理的任务数
|
|
43
49
|
config;
|
|
44
|
-
|
|
50
|
+
handler;
|
|
45
51
|
// 不同状态队列的键名
|
|
46
52
|
failedQueue;
|
|
47
53
|
pendingQueue;
|
|
@@ -58,6 +64,10 @@ export class RedisQueue {
|
|
|
58
64
|
if (config.queueKey.length < 6) {
|
|
59
65
|
throw new Error('[TaskQueue] queueKey must be at least 6 characters long');
|
|
60
66
|
}
|
|
67
|
+
if (!config.handler) {
|
|
68
|
+
throw new Error('[TaskQueue] handler is required');
|
|
69
|
+
}
|
|
70
|
+
this.handler = config.handler;
|
|
61
71
|
this.config = {
|
|
62
72
|
queueKey: config.queueKey,
|
|
63
73
|
redisUrl: config.redisUrl,
|
|
@@ -105,13 +115,7 @@ export class RedisQueue {
|
|
|
105
115
|
});
|
|
106
116
|
}
|
|
107
117
|
}
|
|
108
|
-
|
|
109
|
-
* 注册任务处理器
|
|
110
|
-
*/
|
|
111
|
-
handle(type, handler) {
|
|
112
|
-
this.handlers.set(type, handler);
|
|
113
|
-
}
|
|
114
|
-
async enqueue(type, data) {
|
|
118
|
+
async enqueue(data) {
|
|
115
119
|
if (!this.redis) {
|
|
116
120
|
console.warn('[TaskQueue] Redis not available, skipping task enqueue');
|
|
117
121
|
return Array.isArray(data) ? [] : '';
|
|
@@ -141,11 +145,10 @@ export class RedisQueue {
|
|
|
141
145
|
for (const item of dataList) {
|
|
142
146
|
// 检查 data 中是否包含自定义 id
|
|
143
147
|
const customId = item.id;
|
|
144
|
-
const taskId = customId
|
|
148
|
+
const taskId = customId || randomUUID();
|
|
145
149
|
const taskKey = `${this.config.queueKey}:task:${taskId}`;
|
|
146
150
|
const task = {
|
|
147
151
|
id: taskId,
|
|
148
|
-
type,
|
|
149
152
|
data: item,
|
|
150
153
|
retryCount: 0,
|
|
151
154
|
maxRetries: this.config.maxRetries,
|
|
@@ -162,7 +165,7 @@ export class RedisQueue {
|
|
|
162
165
|
console.log(`[TaskQueue] Task already exists, skipping: ${taskId}`);
|
|
163
166
|
}
|
|
164
167
|
else {
|
|
165
|
-
console.log(`[TaskQueue] Enqueued task: ${task.
|
|
168
|
+
console.log(`[TaskQueue] Enqueued task: ${task.id}`);
|
|
166
169
|
}
|
|
167
170
|
taskIds.push(taskId);
|
|
168
171
|
}
|
|
@@ -314,12 +317,6 @@ export class RedisQueue {
|
|
|
314
317
|
await this.applyStatus(taskId, task.status, 'failed');
|
|
315
318
|
return;
|
|
316
319
|
}
|
|
317
|
-
const handler = this.handlers.get(task.type);
|
|
318
|
-
if (!handler) {
|
|
319
|
-
console.error(`[TaskQueue] No handler registered for task type: ${task.type}`);
|
|
320
|
-
await this.applyStatus(taskId, 'pending', 'failed');
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
320
|
try {
|
|
324
321
|
// 任务已在 processing 队列中(由 Lua 脚本完成),只需更新状态和开始时间
|
|
325
322
|
task.status = 'processing';
|
|
@@ -327,7 +324,7 @@ export class RedisQueue {
|
|
|
327
324
|
const taskKey = `${this.config.queueKey}:task:${taskId}`;
|
|
328
325
|
await this.redis.setEx(taskKey, this.config.cleanupDelay, JSON.stringify(task));
|
|
329
326
|
// 执行任务处理器
|
|
330
|
-
await handler(task.data);
|
|
327
|
+
await this.handler(task.data);
|
|
331
328
|
// 更新状态为完成
|
|
332
329
|
await this.applyStatus(taskId, 'processing', 'completed');
|
|
333
330
|
console.log(`[TaskQueue] Task completed: ${taskId}`);
|