api-server/src/infrastructure/queue/queue-definitions.ts
wangdl 4fb652d273
Some checks failed
Deploy API Server / build-and-deploy (push) Failing after 39s
feat: M-AI-01 Worker 进程边界与部署收口(全 8 个 Issue)
M-AI-01-01: ADR-001 统一 AI 架构决策记录 + 附录 A 依赖闭包审计
M-AI-01-02: Worker 执行依赖闭包审计(附录 A 文档)
M-AI-01-03: 分离 AppModule/WorkerModule Processor 注册
M-AI-01-04: 统一 Queue Definition Registry + 环境变量覆盖
M-AI-01-05: 独立 Worker 启动与生命周期(instanceId/校验/优雅关闭)
M-AI-01-06: Docker + systemd 部署拓扑(unit 文件 + 回滚文档)
M-AI-01-07: Worker Heartbeat(Redis TTL 90s)+ Admin 在线状态 API
M-AI-01-08: 集成验收脚本

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-19 21:22:48 +08:00

116 lines
4.2 KiB
TypeScript

import {
QUEUE_AI_ANALYSIS,
QUEUE_DOCUMENT_IMPORT,
QUEUE_NOTIFICATION,
QUEUE_DOMAIN_EVENTS,
QUEUE_AUDIT_LOG,
QUEUE_FILE_CLEANUP,
} from './queue.service';
// ═══════════════════════════════════════════════════════════════════════
// Queue Definition Registry
//
// Centralizes BullMQ worker options and default job options for every queue.
// Environment variables override the defaults defined here.
//
// lockDuration is NOT a business timeout — it is the BullMQ lock renewal
// interval. A processor that runs longer than lockDuration will have its
// lock automatically extended by the worker, unless the worker crashes
// (then stalledInterval detects the stall and maxStalledCount controls
// retry). Business timeouts must be enforced inside the processor logic.
// ═══════════════════════════════════════════════════════════════════════
function envInt(key: string, fallback: number, min?: number): number {
const raw = process.env[key];
if (raw === undefined || raw === '') return fallback;
const n = parseInt(raw, 10);
if (isNaN(n) || n < 0) return fallback;
if (min !== undefined && n < min) return fallback;
return n;
}
export interface QueueWorkerOptions {
concurrency: number;
lockDuration: number;
stalledInterval: number;
maxStalledCount: number;
}
export interface QueueDefaultJobOptions {
attempts: number;
backoff: { type: 'exponential'; delay: number };
removeOnComplete: { count: number; age: number };
removeOnFail: { count: number; age: number };
}
export interface QueueDefinition {
name: string;
workerOptions: QueueWorkerOptions;
defaultJobOptions: QueueDefaultJobOptions;
}
const DEFAULT_WORKER: QueueWorkerOptions = {
concurrency: 1,
lockDuration: 30_000,
stalledInterval: 30_000,
maxStalledCount: 1,
};
const DEFAULT_JOB: QueueDefaultJobOptions = {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
removeOnComplete: { count: 1000, age: 24 * 3600 },
removeOnFail: { count: 5000, age: 7 * 24 * 3600 },
};
function buildDef(name: string): QueueDefinition {
const prefix = name.toUpperCase().replace(/-/g, '_');
return {
name,
workerOptions: {
concurrency: envInt(`BULL_${prefix}_CONCURRENCY`, DEFAULT_WORKER.concurrency, 1),
lockDuration: envInt(`BULL_${prefix}_LOCK_DURATION`, DEFAULT_WORKER.lockDuration, 1),
stalledInterval: envInt(`BULL_${prefix}_STALLED_INTERVAL`, DEFAULT_WORKER.stalledInterval, 1),
maxStalledCount: envInt(`BULL_${prefix}_MAX_STALLED`, DEFAULT_WORKER.maxStalledCount, 1),
},
defaultJobOptions: {
attempts: envInt(`BULL_${prefix}_ATTEMPTS`, DEFAULT_JOB.attempts, 1),
backoff: {
type: 'exponential',
delay: envInt(`BULL_${prefix}_BACKOFF_DELAY`, DEFAULT_JOB.backoff.delay),
},
removeOnComplete: {
count: envInt('BULL_COMPLETED_MAX_COUNT', DEFAULT_JOB.removeOnComplete.count),
age: envInt('BULL_COMPLETED_MAX_AGE_SEC', DEFAULT_JOB.removeOnComplete.age),
},
removeOnFail: {
count: envInt('BULL_FAILED_MAX_COUNT', DEFAULT_JOB.removeOnFail.count),
age: envInt('BULL_FAILED_MAX_AGE_SEC', DEFAULT_JOB.removeOnFail.age),
},
},
};
}
export const QUEUE_DEFINITIONS: Record<string, QueueDefinition> = {
[QUEUE_AI_ANALYSIS]: buildDef(QUEUE_AI_ANALYSIS),
[QUEUE_DOCUMENT_IMPORT]: buildDef(QUEUE_DOCUMENT_IMPORT),
[QUEUE_NOTIFICATION]: buildDef(QUEUE_NOTIFICATION),
[QUEUE_DOMAIN_EVENTS]: buildDef(QUEUE_DOMAIN_EVENTS),
[QUEUE_AUDIT_LOG]: buildDef(QUEUE_AUDIT_LOG),
[QUEUE_FILE_CLEANUP]: buildDef(QUEUE_FILE_CLEANUP),
};
export function queueDef(name: string): QueueDefinition {
const def = QUEUE_DEFINITIONS[name];
if (!def) throw new Error(`Unknown queue: ${name}`);
return def;
}
export function workerOptionsFor(name: string): QueueWorkerOptions {
return queueDef(name).workerOptions;
}
export function defaultJobOptionsFor(name: string): QueueDefaultJobOptions {
return queueDef(name).defaultJobOptions;
}