import { TASK_QUEUE_GROUPS, TASK_QUEUES, type TaskQueueGroup, type TaskQueueName, } from '@lingxi/shared'; /** 单个 worker 被分配到的队列定义(并发 / 重试参数全部来自 shared 的 TASK_QUEUES) */ export interface WorkerQueueDefinition { name: TaskQueueName; group: TaskQueueGroup; concurrency: number; attempts: number; backoffDelayMs: number; } /** `QUEUE=all`:启动全部队列(本地开发默认) */ export const ALL_QUEUE_SELECTOR = 'all'; /** * 逻辑队列名 → BullMQ 物理队列名(BullMQ 禁止冒号),与 apps/api 的 toBullQueueName 保持一致。 * 心跳 / 日志 / QUEUE 环境变量使用逻辑名,只有 BullMQ 实例使用物理名。 */ export function toBullQueueName(name: TaskQueueName): string { return name.replaceAll(':', '-'); } export function isQueueName(value: string): value is TaskQueueName { return TASK_QUEUES.some((definition) => definition.name === value); } export function isQueueGroup(value: string): value is TaskQueueGroup { return TASK_QUEUE_GROUPS.some((group) => group === value); } /** * 解析 QUEUE 环境变量:`all` / 分组名 / 队列名,逗号分隔(如 `QUEUE=verify`、`QUEUE=image,media:verify`)。 * 非法值给出中文原因(含可用取值),避免 worker 静默空转。 */ export function parseQueueSpec(spec: string): TaskQueueName[] { const tokens = spec .split(',') .map((token) => token.trim()) .filter((token) => token.length > 0); if (tokens.length === 0) { throw new Error('QUEUE 不能为空:请填 all、队列组名或队列名(如 media:verify)'); } if (tokens.includes(ALL_QUEUE_SELECTOR)) { if (tokens.length > 1) { throw new Error('QUEUE=all 不能与其他分组 / 队列混用'); } return TASK_QUEUES.map((definition) => definition.name); } const selected: TaskQueueName[] = []; for (const token of tokens) { if (isQueueName(token)) { selected.push(token); continue; } if (isQueueGroup(token)) { const group = token; selected.push( ...TASK_QUEUES.filter((definition) => definition.group === group).map( (definition) => definition.name, ), ); continue; } throw new Error( `含未知队列 / 分组:${token}(可用:all、${TASK_QUEUE_GROUPS.join(' / ')} 或具体队列名)`, ); } return [...new Set(selected)]; } /** 把队列名展开为 worker 启动定义;WORKER_CONCURRENCY 覆盖全部队列并发 */ export function buildQueueDefinitions( names: readonly string[], concurrencyOverride?: number, ): WorkerQueueDefinition[] { return names.map((name) => { if (!isQueueName(name)) { throw new Error(`未登记的队列:${name}`); } const definition = TASK_QUEUES.find((item) => item.name === name); if (definition === undefined) { throw new Error(`未登记的队列:${name}`); } return { name, group: definition.group, concurrency: concurrencyOverride ?? definition.concurrency, attempts: definition.attempts, backoffDelayMs: definition.backoffDelayMs, }; }); }