@taicode/common-server 1.0.11 → 1.0.12
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-consumer.d.ts +107 -0
- package/output/redis-queue/batch-consumer.d.ts.map +1 -0
- package/output/redis-queue/batch-consumer.js +492 -0
- package/output/redis-queue/batch-consumer.test.d.ts +2 -0
- package/output/redis-queue/batch-consumer.test.d.ts.map +1 -0
- package/output/redis-queue/batch-consumer.test.js +216 -0
- package/output/redis-queue/batch-redis-queue.d.ts +2 -2
- package/output/redis-queue/batch-redis-queue.d.ts.map +1 -1
- package/output/redis-queue/batch-redis-queue.js +21 -11
- package/output/redis-queue/index.d.ts +5 -4
- package/output/redis-queue/index.d.ts.map +1 -1
- package/output/redis-queue/index.js +4 -2
- package/output/redis-queue/redis-batch-consumer.d.ts +85 -0
- package/output/redis-queue/redis-batch-consumer.d.ts.map +1 -0
- package/output/redis-queue/redis-batch-consumer.js +360 -0
- package/output/redis-queue/redis-batch-consumer.test.d.ts +2 -0
- package/output/redis-queue/redis-batch-consumer.test.d.ts.map +1 -0
- package/output/redis-queue/redis-batch-consumer.test.js +265 -0
- package/output/redis-queue/redis-queue-common.d.ts +61 -0
- package/output/redis-queue/redis-queue-common.d.ts.map +1 -0
- package/output/redis-queue/redis-queue-common.js +222 -0
- package/output/redis-queue/redis-queue-common.test.d.ts +2 -0
- package/output/redis-queue/redis-queue-common.test.d.ts.map +1 -0
- package/output/redis-queue/redis-queue-common.test.js +623 -0
- package/output/redis-queue/redis-queue-consumer.d.ts +102 -0
- package/output/redis-queue/redis-queue-consumer.d.ts.map +1 -0
- package/output/redis-queue/redis-queue-consumer.js +461 -0
- package/output/redis-queue/redis-queue-consumer.test.d.ts +2 -0
- package/output/redis-queue/redis-queue-consumer.test.d.ts.map +1 -0
- package/output/redis-queue/redis-queue-consumer.test.js +242 -0
- package/output/redis-queue/redis-queue-provider.d.ts +57 -0
- package/output/redis-queue/redis-queue-provider.d.ts.map +1 -0
- package/output/redis-queue/redis-queue-provider.js +188 -0
- package/output/redis-queue/redis-queue-provider.test.d.ts +2 -0
- package/output/redis-queue/redis-queue-provider.test.d.ts.map +1 -0
- package/output/redis-queue/redis-queue-provider.test.js +114 -0
- package/output/redis-queue/redis-queue.d.ts +1 -1
- package/output/redis-queue/redis-queue.d.ts.map +1 -1
- package/output/redis-queue/redis-queue.js +17 -7
- package/output/redis-queue/registry.d.ts +57 -0
- package/output/redis-queue/registry.d.ts.map +1 -0
- package/output/redis-queue/registry.js +30 -0
- package/output/redis-queue/types.d.ts +34 -14
- package/output/redis-queue/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-queue-common.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,eAAe,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAA;AAE7E;;;GAGG;AACH,8BAAsB,gBAAgB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IAClE,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAO;IAC9C,SAAS,CAAC,eAAe,EAAE,OAAO,CAAQ;IAE1C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACnC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACpC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAGvC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IACtC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IACvC,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAC1C,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;gBAE7B,MAAM,EAAE,sBAAsB;IAwC1C;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,MAAM;IAEzC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B;;OAEG;IACH,UAAU,IAAI,IAAI;IAalB;;OAEG;cACa,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAWhE;;OAEG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAUlD;;OAEG;cACa,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqChG;;;OAGG;cACa,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqD1G;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAevG;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;CASjC"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { createClient } from 'redis';
|
|
2
|
+
/**
|
|
3
|
+
* Redis 队列基础类
|
|
4
|
+
* 封装 Provider 和 Consumer 的公共逻辑
|
|
5
|
+
*/
|
|
6
|
+
export class RedisQueueCommon {
|
|
7
|
+
redis = null;
|
|
8
|
+
isExternalRedis = false; // 是否使用外部传入的 Redis 客户端
|
|
9
|
+
queueKey;
|
|
10
|
+
redisUrl;
|
|
11
|
+
cleanupDelay;
|
|
12
|
+
// 不同状态队列的键名
|
|
13
|
+
failedQueue;
|
|
14
|
+
pendingQueue;
|
|
15
|
+
processingQueue;
|
|
16
|
+
completedQueue;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
// 验证必填参数
|
|
19
|
+
if (!config.redisUrl && !config.redisClient) {
|
|
20
|
+
throw new Error('[RedisQueue] Either redisUrl or redisClient is required');
|
|
21
|
+
}
|
|
22
|
+
if (config.redisUrl && config.redisClient) {
|
|
23
|
+
throw new Error('[RedisQueue] Cannot specify both redisUrl and redisClient');
|
|
24
|
+
}
|
|
25
|
+
if (!config.queueKey) {
|
|
26
|
+
throw new Error('[RedisQueue] queueKey is required');
|
|
27
|
+
}
|
|
28
|
+
if (config.queueKey.length < 6) {
|
|
29
|
+
throw new Error('[RedisQueue] queueKey must be at least 6 characters long');
|
|
30
|
+
}
|
|
31
|
+
this.redisUrl = config.redisUrl;
|
|
32
|
+
this.queueKey = config.queueKey;
|
|
33
|
+
this.cleanupDelay = config.cleanupDelay ?? 86400; // 24 hours
|
|
34
|
+
// 初始化不同状态队列的键名
|
|
35
|
+
this.failedQueue = `${config.queueKey}:failed`;
|
|
36
|
+
this.pendingQueue = `${config.queueKey}:pending`;
|
|
37
|
+
this.completedQueue = `${config.queueKey}:completed`;
|
|
38
|
+
this.processingQueue = `${config.queueKey}:processing`;
|
|
39
|
+
// 使用外部客户端或创建新客户端
|
|
40
|
+
if (config.redisClient) {
|
|
41
|
+
this.redis = config.redisClient;
|
|
42
|
+
this.isExternalRedis = true;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
this.redis = createClient({ url: this.redisUrl });
|
|
46
|
+
this.isExternalRedis = false;
|
|
47
|
+
// 添加错误处理
|
|
48
|
+
this.redis.on('error', (err) => {
|
|
49
|
+
console.error(`[${this.getLogPrefix()}] Redis Client Error:`, err);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 连接 Redis
|
|
55
|
+
*/
|
|
56
|
+
async connect() {
|
|
57
|
+
if (this.redis && !this.redis.isOpen) {
|
|
58
|
+
await this.redis.connect().catch((error) => {
|
|
59
|
+
console.error(`[${this.getLogPrefix()}] Failed to connect to Redis:`, error);
|
|
60
|
+
throw error;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 断开 Redis 连接
|
|
66
|
+
*/
|
|
67
|
+
disconnect() {
|
|
68
|
+
// 如果是外部传入的客户端,不主动断开连接
|
|
69
|
+
if (this.isExternalRedis) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (this.redis && this.redis.isOpen) {
|
|
73
|
+
this.redis.disconnect().catch((error) => {
|
|
74
|
+
console.error(`[${this.getLogPrefix()}] Failed to disconnect:`, error);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 获取任务详情
|
|
80
|
+
*/
|
|
81
|
+
async getTask(taskId) {
|
|
82
|
+
if (!this.redis || !this.redis.isOpen)
|
|
83
|
+
return null;
|
|
84
|
+
const taskKey = `${this.queueKey}:task:${taskId}`;
|
|
85
|
+
const taskData = await this.redis.get(taskKey);
|
|
86
|
+
if (!taskData)
|
|
87
|
+
return null;
|
|
88
|
+
return JSON.parse(taskData);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 根据状态获取对应的队列键
|
|
92
|
+
*/
|
|
93
|
+
getQueueByStatus(status) {
|
|
94
|
+
switch (status) {
|
|
95
|
+
case 'pending': return this.pendingQueue;
|
|
96
|
+
case 'processing': return this.processingQueue;
|
|
97
|
+
case 'completed': return this.completedQueue;
|
|
98
|
+
case 'failed': return this.failedQueue;
|
|
99
|
+
default: return this.pendingQueue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 更新任务状态并移动到对应队列(原子操作)
|
|
104
|
+
*/
|
|
105
|
+
async applyStatus(taskId, oldStatus, newStatus) {
|
|
106
|
+
if (!this.redis || !this.redis.isOpen)
|
|
107
|
+
return;
|
|
108
|
+
const task = await this.getTask(taskId);
|
|
109
|
+
if (!task)
|
|
110
|
+
return;
|
|
111
|
+
task.status = newStatus;
|
|
112
|
+
const taskKey = `${this.queueKey}:task:${taskId}`;
|
|
113
|
+
const oldQueue = this.getQueueByStatus(oldStatus);
|
|
114
|
+
const newQueue = this.getQueueByStatus(newStatus);
|
|
115
|
+
if (oldQueue !== newQueue) {
|
|
116
|
+
// 使用 Lua 脚本确保原子性:更新任务 + 移动队列
|
|
117
|
+
const script = `
|
|
118
|
+
local taskKey = KEYS[1]
|
|
119
|
+
local oldQueue = KEYS[2]
|
|
120
|
+
local newQueue = KEYS[3]
|
|
121
|
+
local ttl = tonumber(ARGV[1])
|
|
122
|
+
local taskData = ARGV[2]
|
|
123
|
+
local taskId = ARGV[3]
|
|
124
|
+
|
|
125
|
+
redis.call('SETEX', taskKey, ttl, taskData)
|
|
126
|
+
redis.call('LREM', oldQueue, 0, taskId)
|
|
127
|
+
redis.call('RPUSH', newQueue, taskId)
|
|
128
|
+
return 1
|
|
129
|
+
`;
|
|
130
|
+
await this.redis.eval(script, {
|
|
131
|
+
keys: [taskKey, oldQueue, newQueue],
|
|
132
|
+
arguments: [this.cleanupDelay.toString(), JSON.stringify(task), taskId],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// 只更新任务数据
|
|
137
|
+
await this.redis.setEx(taskKey, this.cleanupDelay, JSON.stringify(task));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 批量更新任务状态(使用 Lua 脚本保证原子性)
|
|
142
|
+
* 注意: 此方法会在 Lua 脚本中读取任务数据,不需要提前获取
|
|
143
|
+
*/
|
|
144
|
+
async applyStatusBatch(taskIds, oldStatus, newStatus) {
|
|
145
|
+
if (!this.redis || !this.redis.isOpen || taskIds.length === 0)
|
|
146
|
+
return 0;
|
|
147
|
+
const oldQueue = this.getQueueByStatus(oldStatus);
|
|
148
|
+
const newQueue = this.getQueueByStatus(newStatus);
|
|
149
|
+
// 使用 Lua 脚本批量更新状态并移动队列
|
|
150
|
+
const batchUpdateScript = `
|
|
151
|
+
local oldQueue = KEYS[1]
|
|
152
|
+
local newQueue = KEYS[2]
|
|
153
|
+
local queueKeyPrefix = ARGV[1]
|
|
154
|
+
local ttl = tonumber(ARGV[2])
|
|
155
|
+
local newStatus = ARGV[3]
|
|
156
|
+
local updatedCount = 0
|
|
157
|
+
|
|
158
|
+
-- ARGV[4], ARGV[5], ARGV[6]... 是 taskId
|
|
159
|
+
for i = 4, #ARGV do
|
|
160
|
+
local taskId = ARGV[i]
|
|
161
|
+
local taskKey = queueKeyPrefix .. ':task:' .. taskId
|
|
162
|
+
local existingData = redis.call('GET', taskKey)
|
|
163
|
+
|
|
164
|
+
if existingData then
|
|
165
|
+
-- 读取现有任务并更新状态
|
|
166
|
+
local task = cjson.decode(existingData)
|
|
167
|
+
task.status = newStatus
|
|
168
|
+
redis.call('SETEX', taskKey, ttl, cjson.encode(task))
|
|
169
|
+
|
|
170
|
+
-- 如果队列不同,移动任务
|
|
171
|
+
if oldQueue ~= newQueue then
|
|
172
|
+
redis.call('LREM', oldQueue, 0, taskId)
|
|
173
|
+
redis.call('RPUSH', newQueue, taskId)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
updatedCount = updatedCount + 1
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
return updatedCount
|
|
181
|
+
`;
|
|
182
|
+
const result = await this.redis.eval(batchUpdateScript, {
|
|
183
|
+
keys: [oldQueue, newQueue],
|
|
184
|
+
arguments: [
|
|
185
|
+
this.queueKey,
|
|
186
|
+
this.cleanupDelay.toString(),
|
|
187
|
+
newStatus,
|
|
188
|
+
...taskIds,
|
|
189
|
+
],
|
|
190
|
+
});
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 获取队列统计信息(O(1) 时间复杂度)
|
|
195
|
+
*/
|
|
196
|
+
async statistics() {
|
|
197
|
+
if (!this.redis || !this.redis.isOpen) {
|
|
198
|
+
return { pending: 0, processing: 0, completed: 0, failed: 0 };
|
|
199
|
+
}
|
|
200
|
+
const [pending, processing, completed, failed] = await Promise.all([
|
|
201
|
+
this.redis.lLen(this.pendingQueue),
|
|
202
|
+
this.redis.lLen(this.processingQueue),
|
|
203
|
+
this.redis.lLen(this.completedQueue),
|
|
204
|
+
this.redis.lLen(this.failedQueue),
|
|
205
|
+
]);
|
|
206
|
+
return { pending, processing, completed, failed };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* 健康检查
|
|
210
|
+
*/
|
|
211
|
+
async health() {
|
|
212
|
+
if (!this.redis || !this.redis.isOpen)
|
|
213
|
+
return false;
|
|
214
|
+
try {
|
|
215
|
+
await this.redis.ping();
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-queue-common.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-common.test.ts"],"names":[],"mappings":""}
|