@anyul/koishi-plugin-rss 5.0.2 → 5.0.4
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 +76 -13
- package/lib/core/feeder-old.d.ts +15 -0
- package/lib/core/feeder-old.js +403 -0
- package/lib/core/feeder.d.ts +6 -1
- package/lib/core/feeder.js +114 -96
- package/lib/core/notification-queue.d.ts +119 -0
- package/lib/core/notification-queue.js +352 -0
- package/lib/core/renderer.js +2 -1
- package/lib/database.js +18 -0
- package/lib/index.js +397 -79
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/utils/template.js +2 -2
- package/package.json +1 -1
package/lib/core/feeder.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.findRssItem = findRssItem;
|
|
4
37
|
exports.getLastContent = getLastContent;
|
|
@@ -10,12 +43,19 @@ exports.stopFeeder = stopFeeder;
|
|
|
10
43
|
const koishi_1 = require("koishi");
|
|
11
44
|
const logger_1 = require("../utils/logger");
|
|
12
45
|
const common_1 = require("../utils/common");
|
|
13
|
-
const media_1 = require("../utils/media");
|
|
14
46
|
const parser_1 = require("./parser");
|
|
15
47
|
const constants_1 = require("../constants");
|
|
16
|
-
const message_cache_1 = require("../utils/message-cache");
|
|
17
48
|
let interval = null;
|
|
49
|
+
let queueInterval = null;
|
|
18
50
|
function findRssItem(rssList, keyword) {
|
|
51
|
+
// 优先匹配列表索引(用户看到的序号 1, 2, 3...)
|
|
52
|
+
if (typeof keyword === 'number' || /^\d+$/.test(String(keyword))) {
|
|
53
|
+
const listIndex = parseInt(String(keyword)) - 1; // 转换为数组索引(0-based)
|
|
54
|
+
if (listIndex >= 0 && listIndex < rssList.length) {
|
|
55
|
+
return rssList[listIndex];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// 其他匹配方式:按 rssId、url、title 等
|
|
19
59
|
let index = ((rssList.findIndex(i => i.rssId === +keyword) + 1) ||
|
|
20
60
|
(rssList.findIndex(i => i.url == keyword) + 1) ||
|
|
21
61
|
(rssList.findIndex(i => i.url.indexOf(keyword) + 1) + 1) ||
|
|
@@ -28,13 +68,23 @@ function getLastContent(item, config) {
|
|
|
28
68
|
return { ...obj, description: String(obj?.description).replaceAll(/\s/g, '') };
|
|
29
69
|
}
|
|
30
70
|
function formatArg(options, config) {
|
|
31
|
-
let { arg, template } = options;
|
|
71
|
+
let { arg, template, auth } = options;
|
|
72
|
+
// 特殊处理:提取完整的 proxyAgent URL
|
|
73
|
+
let proxyAgentUrl;
|
|
74
|
+
if (arg && arg.includes('proxyAgent:')) {
|
|
75
|
+
const match = arg.match(/proxyAgent:([^,]+)/);
|
|
76
|
+
if (match) {
|
|
77
|
+
proxyAgentUrl = match[1];
|
|
78
|
+
// 从 arg 中移除 proxyAgent,避免被 split(":") 破坏
|
|
79
|
+
arg = arg.replace(/proxyAgent:[^,]+/, '').replace(/^,|,$/g, '').replace(/,,/g, ',');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
32
82
|
let json = Object.assign({}, ...(arg?.split(',')?.map((i) => ({ [i.split(":")[0]]: i.split(":")[1] })) || []));
|
|
33
|
-
let key = ["forceLength", "reverse", "timeout", "interval", "merge", "maxRssItem", "firstLoad", "bodyWidth", "bodyPadding", "
|
|
83
|
+
let key = ["forceLength", "reverse", "timeout", "interval", "merge", "maxRssItem", "firstLoad", "bodyWidth", "bodyPadding", "filter", "block"];
|
|
34
84
|
let booleanKey = ['firstLoad', "reverse", 'merge'];
|
|
35
85
|
let numberKey = ['forceLength', "timeout", 'interval', 'maxRssItem', 'bodyWidth', 'bodyPadding'];
|
|
36
86
|
let falseContent = ['false', 'null', ''];
|
|
37
|
-
json = Object.assign({}, ...Object.keys(json).filter((i) => key.some((key) => key == i)).map((key) => ({ [key]: booleanKey.some((bkey) => bkey == key) ? falseContent.some((c) => c == json[key]) : numberKey.some((nkey) => nkey == key) ? (+json[key]) : json[key] })));
|
|
87
|
+
json = Object.assign({}, ...Object.keys(json).filter((i) => key.some((key) => key == i)).map((key) => ({ [key]: booleanKey.some((bkey) => bkey == key) ? !falseContent.some((c) => c == json[key]) : numberKey.some((nkey) => nkey == key) ? (+json[key]) : json[key] })));
|
|
38
88
|
if (template && config.template) {
|
|
39
89
|
json['template'] = template;
|
|
40
90
|
}
|
|
@@ -48,25 +98,26 @@ function formatArg(options, config) {
|
|
|
48
98
|
json.filter = json.filter.split("/");
|
|
49
99
|
if (json.block && typeof json.block === 'string')
|
|
50
100
|
json.block = json.block.split("/");
|
|
51
|
-
// Proxy Argument Parsing
|
|
52
|
-
if (
|
|
53
|
-
if (['false', 'none', ''].includes(String(
|
|
101
|
+
// Proxy Argument Parsing (使用提取的完整 URL)
|
|
102
|
+
if (proxyAgentUrl) {
|
|
103
|
+
if (['false', 'none', ''].includes(String(proxyAgentUrl))) {
|
|
54
104
|
json.proxyAgent = { enabled: false };
|
|
55
105
|
}
|
|
56
|
-
else if (typeof
|
|
106
|
+
else if (typeof proxyAgentUrl === 'string') {
|
|
57
107
|
// Parse string proxy: socks5://127.0.0.1:7890
|
|
58
|
-
let protocolMatch =
|
|
108
|
+
let protocolMatch = proxyAgentUrl.match(/^(http|https|socks5)/);
|
|
59
109
|
let protocol = protocolMatch ? protocolMatch[1] : 'http';
|
|
60
|
-
let hostMatch =
|
|
110
|
+
let hostMatch = proxyAgentUrl.match(/:\/\/([^:\/]+)/);
|
|
61
111
|
let host = hostMatch ? hostMatch[1] : '';
|
|
62
|
-
let portMatch =
|
|
112
|
+
let portMatch = proxyAgentUrl.match(/:(\d+)/);
|
|
63
113
|
let port = portMatch ? parseInt(portMatch[1]) : 7890;
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
114
|
+
let proxyAgentObj = { enabled: true, protocol, host, port };
|
|
115
|
+
// Use auth from options if provided
|
|
116
|
+
if (auth) {
|
|
117
|
+
let [username, password] = auth.split("/");
|
|
118
|
+
proxyAgentObj.auth = { username, password };
|
|
68
119
|
}
|
|
69
|
-
json.proxyAgent =
|
|
120
|
+
json.proxyAgent = proxyAgentObj;
|
|
70
121
|
}
|
|
71
122
|
}
|
|
72
123
|
return json;
|
|
@@ -148,9 +199,11 @@ function mixinArg(arg, config) {
|
|
|
148
199
|
(0, logger_1.debug)(config, `[DEBUG_PROXY] mixinArg return: ${JSON.stringify(res.proxyAgent)}`, 'mixin', 'details');
|
|
149
200
|
return res;
|
|
150
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* 生产者:抓取 RSS,发现新消息,存入队列
|
|
204
|
+
*/
|
|
151
205
|
async function feeder(deps, processor) {
|
|
152
|
-
const { ctx, config, $http } = deps;
|
|
153
|
-
// debug(config, "feeder run", 'debug');
|
|
206
|
+
const { ctx, config, $http, queueManager } = deps;
|
|
154
207
|
// Use type assertion for custom table
|
|
155
208
|
const rssList = await ctx.database.get('rssOwl', {});
|
|
156
209
|
if (!rssList || rssList.length === 0)
|
|
@@ -275,8 +328,9 @@ async function feeder(deps, processor) {
|
|
|
275
328
|
}
|
|
276
329
|
(0, logger_1.debug)(config, `${rssItem.title}: Found ${rssItemArray.length} new items`, 'feeder', 'info');
|
|
277
330
|
(0, logger_1.debug)(config, rssItemArray.map(i => i.title), '', 'info');
|
|
278
|
-
// 6.
|
|
331
|
+
// 6. 生成消息并添加到队列(生产者核心逻辑)
|
|
279
332
|
const itemsToSend = [...rssItemArray].reverse();
|
|
333
|
+
// 生成所有消息
|
|
280
334
|
const messageList = (await Promise.all(itemsToSend.map(async (i) => await processor.parseRssItem(i, { ...rssItem, ...arg }, rssItem.author)))).filter(m => m); // Filter empty messages
|
|
281
335
|
if (messageList.length === 0) {
|
|
282
336
|
(0, logger_1.debug)(config, `${rssItem.title}: Items found but parsed to empty messages`, 'feeder', 'info');
|
|
@@ -284,7 +338,7 @@ async function feeder(deps, processor) {
|
|
|
284
338
|
await ctx.database.set('rssOwl', { id: rssItem.id }, { lastPubDate, arg: originalArg, lastContent: { itemArray: currentContent } });
|
|
285
339
|
continue;
|
|
286
340
|
}
|
|
287
|
-
// 7.
|
|
341
|
+
// 7. 构建最终消息
|
|
288
342
|
let message = "";
|
|
289
343
|
const shouldMerge = arg.merge === true || config.basic?.merge === '一直合并' || (config.basic?.merge === '有多条更新时合并' && messageList.length > 1);
|
|
290
344
|
// Check for video merge requirement
|
|
@@ -300,78 +354,28 @@ async function feeder(deps, processor) {
|
|
|
300
354
|
const mentions = rssItem.followers.map((id) => `<at ${id === 'all' ? 'type="all"' : `id="${id}"`}/>`).join(" ");
|
|
301
355
|
message += `<message>${mentions}</message>`;
|
|
302
356
|
}
|
|
303
|
-
// 8.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return '\n[视频不支持]\n';
|
|
326
|
-
});
|
|
327
|
-
try {
|
|
328
|
-
await ctx.broadcast([`${rssItem.platform}:${rssItem.guildId}`], fallbackMessage);
|
|
329
|
-
(0, logger_1.debug)(config, `降级发送成功:${rssItem.title}`, '', 'info');
|
|
330
|
-
}
|
|
331
|
-
catch (retryError) {
|
|
332
|
-
(0, logger_1.debug)(config, `降级发送也失败: ${retryError.message}`, 'feeder', 'error');
|
|
333
|
-
throw retryError;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
throw sendError;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
// 缓存最终发送的消息
|
|
341
|
-
if (config.cache?.enabled && messageList.length > 0) {
|
|
342
|
-
const cache = (0, message_cache_1.getMessageCache)();
|
|
343
|
-
if (cache) {
|
|
344
|
-
// 缓存每条消息的最终形式
|
|
345
|
-
for (let i = 0; i < itemsToSend.length && i < messageList.length; i++) {
|
|
346
|
-
const item = itemsToSend[i];
|
|
347
|
-
const finalMsg = messageList[i];
|
|
348
|
-
try {
|
|
349
|
-
await cache.addMessage({
|
|
350
|
-
rssId: rssItem.rssId.toString(),
|
|
351
|
-
guildId: rssItem.guildId,
|
|
352
|
-
platform: rssItem.platform,
|
|
353
|
-
title: item.title || '',
|
|
354
|
-
content: item.description || '',
|
|
355
|
-
link: item.link || '',
|
|
356
|
-
pubDate: (0, common_1.parsePubDate)(config, item.pubDate),
|
|
357
|
-
imageUrl: item.enclosure?.url || '',
|
|
358
|
-
videoUrl: '',
|
|
359
|
-
finalMessage: finalMsg // 缓存最终发送的消息
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
catch (err) {
|
|
363
|
-
(0, logger_1.debug)(config, `缓存消息失败: ${err.message}`, 'cache', 'info');
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
catch (err) {
|
|
370
|
-
(0, logger_1.debug)(config, `RSS推送失败 [${rssItem.title}]: ${err.message}`, 'feeder', 'error');
|
|
371
|
-
console.error(`RSS推送失败 [${rssItem.title}]: ${err.message}`);
|
|
372
|
-
// 即使发送失败,也要更新数据库状态,避免无限重试
|
|
373
|
-
}
|
|
374
|
-
// 9. Update Database State
|
|
357
|
+
// 8. 添加任务到队列(关键变更:不再直接发送)
|
|
358
|
+
const taskContent = {
|
|
359
|
+
message,
|
|
360
|
+
originalItem: itemsToSend[0],
|
|
361
|
+
isDowngraded: false,
|
|
362
|
+
title: itemsToSend[0]?.title,
|
|
363
|
+
description: itemsToSend[0]?.description,
|
|
364
|
+
link: itemsToSend[0]?.link,
|
|
365
|
+
pubDate: (0, common_1.parsePubDate)(config, itemsToSend[0]?.pubDate),
|
|
366
|
+
imageUrl: itemsToSend[0]?.enclosure?.url
|
|
367
|
+
};
|
|
368
|
+
await queueManager.addTask({
|
|
369
|
+
subscribeId: String(rssItem.id),
|
|
370
|
+
rssId: rssItem.rssId || rssItem.title,
|
|
371
|
+
uid: itemsToSend[0]?.link || itemsToSend[0]?.guid || `${Date.now()}`,
|
|
372
|
+
guildId: rssItem.guildId,
|
|
373
|
+
platform: rssItem.platform,
|
|
374
|
+
content: taskContent
|
|
375
|
+
});
|
|
376
|
+
(0, logger_1.debug)(config, `✓ 已添加到发送队列: ${rssItem.title}`, 'feeder', 'info');
|
|
377
|
+
// 9. 更新数据库状态(关键:无论发送是否成功,都更新 lastPubDate)
|
|
378
|
+
// 这样即使 Bot 掉线,重启后也不会重复发送旧消息
|
|
375
379
|
await ctx.database.set('rssOwl', { id: rssItem.id }, {
|
|
376
380
|
lastPubDate,
|
|
377
381
|
arg: originalArg,
|
|
@@ -383,21 +387,35 @@ async function feeder(deps, processor) {
|
|
|
383
387
|
}
|
|
384
388
|
}
|
|
385
389
|
}
|
|
386
|
-
function startFeeder(ctx, config, $http, processor) {
|
|
387
|
-
const deps = { ctx, config, $http };
|
|
390
|
+
function startFeeder(ctx, config, $http, processor, queueManager) {
|
|
391
|
+
const deps = { ctx, config, $http, queueManager };
|
|
388
392
|
// Initial run
|
|
389
393
|
feeder(deps, processor).catch(err => console.error("Initial feeder run failed:", err));
|
|
394
|
+
// 启动生产者定时器(抓取 RSS)
|
|
390
395
|
const refreshInterval = (config.basic?.refresh || 600) * 1000;
|
|
391
396
|
interval = setInterval(async () => {
|
|
392
397
|
if (config.basic?.imageMode === 'File') {
|
|
393
|
-
await (
|
|
398
|
+
const { delCache } = await Promise.resolve().then(() => __importStar(require('../utils/media')));
|
|
399
|
+
await delCache(config);
|
|
394
400
|
}
|
|
395
401
|
await feeder(deps, processor);
|
|
396
402
|
}, refreshInterval);
|
|
403
|
+
// 启动消费者定时器(处理发送队列)
|
|
404
|
+
// 频率更高,确保消息快速发送
|
|
405
|
+
const queueProcessInterval = 30 * 1000; // 每 30 秒处理一次队列
|
|
406
|
+
queueInterval = setInterval(async () => {
|
|
407
|
+
await queueManager.processQueue();
|
|
408
|
+
}, queueProcessInterval);
|
|
409
|
+
// 立即处理一次队列(启动时)
|
|
410
|
+
queueManager.processQueue().catch(err => console.error("Initial queue processing failed:", err));
|
|
397
411
|
}
|
|
398
412
|
function stopFeeder() {
|
|
399
413
|
if (interval) {
|
|
400
414
|
clearInterval(interval);
|
|
401
415
|
interval = null;
|
|
402
416
|
}
|
|
417
|
+
if (queueInterval) {
|
|
418
|
+
clearInterval(queueInterval);
|
|
419
|
+
queueInterval = null;
|
|
420
|
+
}
|
|
403
421
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息发送队列管理器
|
|
3
|
+
* 实现可靠的消息推送,支持重试、降级和错误处理
|
|
4
|
+
*/
|
|
5
|
+
import { Context } from 'koishi';
|
|
6
|
+
import { Config } from '../types';
|
|
7
|
+
export type QueueStatus = 'PENDING' | 'RETRY' | 'FAILED' | 'SUCCESS';
|
|
8
|
+
/**
|
|
9
|
+
* 队列任务接口
|
|
10
|
+
*/
|
|
11
|
+
export interface QueueTask {
|
|
12
|
+
id?: number;
|
|
13
|
+
subscribeId: string;
|
|
14
|
+
rssId: string;
|
|
15
|
+
uid: string;
|
|
16
|
+
guildId: string;
|
|
17
|
+
platform: string;
|
|
18
|
+
content: QueueTaskContent;
|
|
19
|
+
status: QueueStatus;
|
|
20
|
+
retryCount: number;
|
|
21
|
+
nextRetryTime?: Date;
|
|
22
|
+
createdAt: Date;
|
|
23
|
+
updatedAt: Date;
|
|
24
|
+
failReason?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 队列任务内容
|
|
28
|
+
*/
|
|
29
|
+
export interface QueueTaskContent {
|
|
30
|
+
message: string;
|
|
31
|
+
originalItem?: any;
|
|
32
|
+
isDowngraded?: boolean;
|
|
33
|
+
title?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
link?: string;
|
|
36
|
+
pubDate?: Date;
|
|
37
|
+
imageUrl?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 消息发送队列管理器
|
|
41
|
+
*/
|
|
42
|
+
export declare class NotificationQueueManager {
|
|
43
|
+
private ctx;
|
|
44
|
+
private config;
|
|
45
|
+
private processing;
|
|
46
|
+
private maxRetries;
|
|
47
|
+
private batchSize;
|
|
48
|
+
private backoffDelays;
|
|
49
|
+
constructor(ctx: Context, config: Config);
|
|
50
|
+
/**
|
|
51
|
+
* 添加任务到队列
|
|
52
|
+
*/
|
|
53
|
+
addTask(task: Omit<QueueTask, 'id' | 'status' | 'retryCount' | 'createdAt' | 'updatedAt'>): Promise<QueueTask>;
|
|
54
|
+
/**
|
|
55
|
+
* 处理队列中的任务
|
|
56
|
+
*/
|
|
57
|
+
processQueue(): Promise<void>;
|
|
58
|
+
/**
|
|
59
|
+
* 获取待处理任务
|
|
60
|
+
*/
|
|
61
|
+
private getPendingTasks;
|
|
62
|
+
/**
|
|
63
|
+
* 处理单个任务
|
|
64
|
+
*/
|
|
65
|
+
private processTask;
|
|
66
|
+
/**
|
|
67
|
+
* 发送消息(带降级机制)
|
|
68
|
+
*/
|
|
69
|
+
private sendMessage;
|
|
70
|
+
/**
|
|
71
|
+
* 处理发送错误
|
|
72
|
+
*/
|
|
73
|
+
private handleSendError;
|
|
74
|
+
/**
|
|
75
|
+
* 判断是否为永久性错误
|
|
76
|
+
*/
|
|
77
|
+
private isFatalError;
|
|
78
|
+
/**
|
|
79
|
+
* 降级消息(移除媒体元素)
|
|
80
|
+
*/
|
|
81
|
+
private downgradeMessage;
|
|
82
|
+
/**
|
|
83
|
+
* 标记任务为成功
|
|
84
|
+
*/
|
|
85
|
+
private markTaskSuccess;
|
|
86
|
+
/**
|
|
87
|
+
* 标记任务为重试
|
|
88
|
+
*/
|
|
89
|
+
private markTaskRetry;
|
|
90
|
+
/**
|
|
91
|
+
* 更新任务为降级重试
|
|
92
|
+
*/
|
|
93
|
+
private updateTaskForDowngrade;
|
|
94
|
+
/**
|
|
95
|
+
* 标记任务为失败
|
|
96
|
+
*/
|
|
97
|
+
private markTaskFailed;
|
|
98
|
+
/**
|
|
99
|
+
* 缓存成功发送的消息
|
|
100
|
+
*/
|
|
101
|
+
private cacheMessage;
|
|
102
|
+
/**
|
|
103
|
+
* 获取队列统计信息
|
|
104
|
+
*/
|
|
105
|
+
getStats(): Promise<{
|
|
106
|
+
pending: number;
|
|
107
|
+
retry: number;
|
|
108
|
+
failed: number;
|
|
109
|
+
success: number;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* 重试失败的任务
|
|
113
|
+
*/
|
|
114
|
+
retryFailedTasks(taskId?: number): Promise<number>;
|
|
115
|
+
/**
|
|
116
|
+
* 清理旧的成功任务
|
|
117
|
+
*/
|
|
118
|
+
cleanupSuccessTasks(olderThanHours?: number): Promise<number>;
|
|
119
|
+
}
|