import { Queue } from "bullmq"; import { RPA_JOB_ATTEMPTS, RPA_QUEUE_NAME, ADDRESS_CANDIDATES_TIMEOUT_MS, RPA_JOB_TIMEOUT_MS, } from "@/lib/constants/rpa"; import { getRedisConnectionOptions } from "@/lib/redis"; import type { AddressCandidatesJobData } from "@/modules/address/candidates-queue-types"; import type { SessionConfirmJobData } from "@/modules/address/session-confirm-queue-types"; import type { ParkedSessionJobData } from "@/modules/address/parked-session-queue-types"; import type { QuoteJobData } from "@/modules/quote/rpa-queue"; export type RpaQueueJobData = | QuoteJobData | AddressCandidatesJobData | ParkedSessionJobData | SessionConfirmJobData; let queueInstance: Queue | null = null; export function getQuoteRpaQueue(): Queue { if (!queueInstance) { queueInstance = new Queue(RPA_QUEUE_NAME, { connection: getRedisConnectionOptions(), defaultJobOptions: { attempts: RPA_JOB_ATTEMPTS, backoff: { type: "fixed", delay: 1_000 }, removeOnComplete: 200, removeOnFail: 500, }, }); } return queueInstance; } export async function addQuoteRpaJob(data: QuoteJobData): Promise { const queue = getQuoteRpaQueue(); await queue.add("quote", data, { jobId: data.quoteId, }); } export async function addAddressCandidatesJob( data: AddressCandidatesJobData, ): Promise { const queue = getQuoteRpaQueue(); await queue.add("address-candidates", data, { jobId: data.jobId, }); } export async function addParkedSessionJob( name: "parked-session-preheat" | "release-parked-session", sessionId: string, ): Promise { const queue = getQuoteRpaQueue(); const payload: ParkedSessionJobData = { sessionId }; await queue.add(name, payload, { // BullMQ 禁止 custom jobId 含 ':'(Redis 6.0.x 下会抛 Custom Id cannot contain :) jobId: `${name}_${sessionId}`, removeOnComplete: 50, removeOnFail: 100, }); } export async function addSessionConfirmJob( data: SessionConfirmJobData, ): Promise { const queue = getQuoteRpaQueue(); await queue.add("session-confirm-addresses", data, { jobId: data.jobId, }); } export function getRpaWorkerLockDurationMs(): number { return ( Math.max(RPA_JOB_TIMEOUT_MS, ADDRESS_CANDIDATES_TIMEOUT_MS) + 15_000 ); } export async function getQueueDepth(): Promise { const queue = getQuoteRpaQueue(); const counts = await queue.getJobCounts("waiting", "delayed", "active"); return counts.waiting + counts.delayed + counts.active; } export type QuoteRpaQueueCounts = { waiting: number; active: number; delayed: number; failed: number; completed: number; }; export async function getQuoteRpaQueueCounts(): Promise { const queue = getQuoteRpaQueue(); const counts = await queue.getJobCounts( "waiting", "active", "delayed", "failed", "completed", ); return { waiting: counts.waiting ?? 0, active: counts.active ?? 0, delayed: counts.delayed ?? 0, failed: counts.failed ?? 0, completed: counts.completed ?? 0, }; } export async function closeQuoteRpaQueue(): Promise { if (queueInstance) { await queueInstance.close(); queueInstance = null; } } export { RPA_QUEUE_NAME };