diff --git a/apps/backend/src/repositories/kuaishou-industry-voucher-repo.ts b/apps/backend/src/repositories/kuaishou-industry-voucher-repo.ts index cf4bbc66..b2c21558 100644 --- a/apps/backend/src/repositories/kuaishou-industry-voucher-repo.ts +++ b/apps/backend/src/repositories/kuaishou-industry-voucher-repo.ts @@ -110,10 +110,31 @@ async function insertKuaishouIndustryVoucher( task_id = COALESCE(kuaishou_industry_vouchers.task_id, EXCLUDED.task_id), valid_start_time = EXCLUDED.valid_start_time, valid_end_time = EXCLUDED.valid_end_time, - send_callback_status = EXCLUDED.send_callback_status, - send_callback_last_error = EXCLUDED.send_callback_last_error, - send_callback_response_json = EXCLUDED.send_callback_response_json, - send_callback_sent_at = EXCLUDED.send_callback_sent_at, + send_callback_status = CASE + WHEN kuaishou_industry_vouchers.send_callback_status = 'success' + THEN kuaishou_industry_vouchers.send_callback_status + ELSE EXCLUDED.send_callback_status + END, + send_callback_attempt_count = CASE + WHEN kuaishou_industry_vouchers.send_callback_status = 'success' + THEN kuaishou_industry_vouchers.send_callback_attempt_count + ELSE EXCLUDED.send_callback_attempt_count + END, + send_callback_last_error = CASE + WHEN kuaishou_industry_vouchers.send_callback_status = 'success' + THEN kuaishou_industry_vouchers.send_callback_last_error + ELSE EXCLUDED.send_callback_last_error + END, + send_callback_response_json = CASE + WHEN kuaishou_industry_vouchers.send_callback_status = 'success' + THEN kuaishou_industry_vouchers.send_callback_response_json + ELSE EXCLUDED.send_callback_response_json + END, + send_callback_sent_at = CASE + WHEN kuaishou_industry_vouchers.send_callback_status = 'success' + THEN kuaishou_industry_vouchers.send_callback_sent_at + ELSE EXCLUDED.send_callback_sent_at + END, raw_payload_json = EXCLUDED.raw_payload_json, updated_at = EXCLUDED.updated_at RETURNING * @@ -163,6 +184,23 @@ export async function listKuaishouIndustryVouchersByOid( return result.rows } +export async function listKuaishouIndustryVouchersForSendCallbackRetry( + limit = 100, +): Promise { + const result = await query( + ` + SELECT * + FROM kuaishou_industry_vouchers + WHERE send_callback_status <> 'success' + ORDER BY COALESCE(send_callback_sent_at, created_at) ASC, id ASC + LIMIT $1 + `, + [normalizePositiveLimit(limit, 100)], + ) + + return result.rows +} + export async function listKuaishouIndustryVouchersByTaskId( taskId: number | string, ): Promise { @@ -388,6 +426,11 @@ function normalizeNonNegativeInteger(value: unknown, fallback: number) { return Number.isInteger(parsed) && parsed >= 0 ? parsed : fallback } +function normalizePositiveLimit(value: unknown, fallback: number): number { + const parsed = Number(value) + return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : fallback +} + function stringifyJson(value: unknown): string { if (typeof value === 'string') { return value diff --git a/apps/backend/src/services/open-91/order-create-service.ts b/apps/backend/src/services/open-91/order-create-service.ts index 4796f752..698d69d8 100644 --- a/apps/backend/src/services/open-91/order-create-service.ts +++ b/apps/backend/src/services/open-91/order-create-service.ts @@ -86,6 +86,16 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) { }) } + if (isWaitingKuaishouIndustrySendCallback(result.ignoreReason) && result.order) { + return buildOpen91SuccessResponse({ + orderNo: normalized.orderNo, + outTradeNo: buildOpen91OutTradeNo(result.order), + orderStatus: 10, + orderCost: buildOpen91OrderCost(0), + cards: '', + }) + } + if (!result.order || !Array.isArray(result.tasks) || result.tasks.length === 0) { return buildOpen91SuccessResponse({ orderNo: normalized.orderNo, @@ -106,9 +116,13 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) { } function resolveOpen91CreateFailReason(ignoreReason: unknown) { - if (String(ignoreReason || '').trim() === 'kuaishou_industry_send_callback_unconfirmed') { + if (isWaitingKuaishouIndustrySendCallback(ignoreReason)) { return '电子凭证发码回调未成功,订单暂不能履约' } return '商品未配置或订单无法履约' } + +function isWaitingKuaishouIndustrySendCallback(ignoreReason: unknown) { + return String(ignoreReason || '').trim() === 'kuaishou_industry_send_callback_unconfirmed' +} diff --git a/apps/backend/src/services/open-91/order-query-service.ts b/apps/backend/src/services/open-91/order-query-service.ts index 0d386412..11879185 100644 --- a/apps/backend/src/services/open-91/order-query-service.ts +++ b/apps/backend/src/services/open-91/order-query-service.ts @@ -1,4 +1,5 @@ import { findLatestOrderByPlatformOrderId } from '../../repositories/order-repo.js' +import { listKuaishouIndustryVouchersByOid } from '../../repositories/kuaishou-industry-voucher-repo.js' import { listTasksByOrderId } from '../../repositories/task-repo.js' import { buildClaimUrl } from '../claim/claim-service.js' import { ensureTaskClaimLink } from '../fulfillment/kuaishou-cloud/index.js' @@ -11,6 +12,10 @@ import { OPEN_91_MANUAL_FAILED_STATUS, OPEN_91_PENDING_CONFIG_STATUS, } from '../platforms/ninetyone/order-service.js' +import { + isKuaishouIndustryVoucherSendCallbackSuccess, + resolveKuaishouIndustryVoucherSendCallbackMessage, +} from '../platforms/kuaishou-industry/voucher-service.js' import { assertOpen91Config, OPEN_91_DEFAULT_FAIL_CODE, @@ -30,7 +35,7 @@ import { buildOpen91SuccessResponse, resolveOpen91QueryState, } from './response.js' -import type { OrderRow } from '../../types/repository/rows.js' +import type { KuaishouIndustryVoucherRow, OrderRow, TaskRow } from '../../types/repository/rows.js' type JsonObject = Record @@ -59,6 +64,7 @@ export async function queryOpen91Order(payload: JsonObject = {}, { requestId = ' } const tasks = await listTasksByOrderId(order.id) + const industryVoucherState = await resolveOpen91IndustryVoucherState(order, tasks) if (tasks.length === 0) { if (String(order.order_status || '').trim() === OPEN_91_PENDING_CONFIG_STATUS) { @@ -82,10 +88,23 @@ export async function queryOpen91Order(payload: JsonObject = {}, { requestId = ' failReason: resolveOpen91OrderFailReason(order) || '商家手动标记无法履约', }) } + + if (industryVoucherState.voucherCount > 0) { + return buildOpen91SuccessResponse({ + orderNo: normalized.orderNo, + outTradeNo: buildOpen91OutTradeNo(order), + orderStatus: 10, + failCode: 0, + failReason: '', + orderCost: buildOpen91OrderCost(0), + cards: '', + }) + } } const cardItems = [] const readyTaskIds = [] + const industryReadyTaskIds = new Set(industryVoucherState.readyTaskIds) for (const task of tasks) { let claimUrl = '' @@ -115,6 +134,10 @@ export async function queryOpen91Order(payload: JsonObject = {}, { requestId = ' continue } + if (!industryReadyTaskIds.has(Number(task.id))) { + continue + } + readyTaskIds.push(Number(task.id)) cardItems.push(buildOpen91CardItem({ claimUrl, @@ -133,6 +156,9 @@ export async function queryOpen91Order(payload: JsonObject = {}, { requestId = ' orderId: order.id, taskCount: tasks.length, readyTaskCount: readyTaskIds.length, + voucherCount: industryVoucherState.voucherCount, + voucherReadyTaskCount: industryVoucherState.readyTaskIds.length, + voucherPendingReason: industryVoucherState.pendingReason, orderStatus: queryState.orderStatus, }) @@ -189,6 +215,50 @@ function resolveOpen91OrderFailReason(order: OrderRow) { return String(payload.manualFailedReason || '').trim() } +async function resolveOpen91IndustryVoucherState(order: OrderRow, tasks: TaskRow[]) { + const vouchers = await listKuaishouIndustryVouchersByOid(order.platform_order_id) + const voucherByTaskId = new Map() + const voucherByUnitIndex = new Map() + + for (const voucher of vouchers) { + const taskId = Number(voucher.task_id || 0) + const unitIndex = Number(voucher.unit_index || 0) + if (taskId > 0 && !voucherByTaskId.has(taskId)) { + voucherByTaskId.set(taskId, voucher) + } + if (unitIndex > 0 && !voucherByUnitIndex.has(unitIndex)) { + voucherByUnitIndex.set(unitIndex, voucher) + } + } + + const readyTaskIds: number[] = [] + let pendingReason = '' + + for (const task of tasks) { + const taskId = Number(task.id || 0) + const unitIndex = Number(task.unit_index || 0) + const voucher = voucherByTaskId.get(taskId) || voucherByUnitIndex.get(unitIndex) || null + + if (!voucher) { + pendingReason = pendingReason || '电子凭证发码通知尚未完成' + continue + } + + if (!isKuaishouIndustryVoucherSendCallbackSuccess(voucher)) { + pendingReason = pendingReason || resolveKuaishouIndustryVoucherSendCallbackMessage(voucher) + continue + } + + readyTaskIds.push(taskId) + } + + return { + voucherCount: vouchers.length, + readyTaskIds, + pendingReason, + } +} + function parseJsonObject(value: unknown): JsonObject { if (!value) { return {} diff --git a/apps/backend/src/services/platforms/kuaishou-industry/send-code-service.ts b/apps/backend/src/services/platforms/kuaishou-industry/send-code-service.ts index cb43cc78..33aaa059 100644 --- a/apps/backend/src/services/platforms/kuaishou-industry/send-code-service.ts +++ b/apps/backend/src/services/platforms/kuaishou-industry/send-code-service.ts @@ -2,10 +2,13 @@ import { findLatestOrderByPlatformOrderId } from '../../../repositories/order-re import { listTasksByOrderId } from '../../../repositories/task-repo.js' import { findKuaishouIndustryVoucherByCode, + listKuaishouIndustryVouchersByOid, + listKuaishouIndustryVouchersForSendCallbackRetry, updateKuaishouIndustryVoucherByCode, upsertKuaishouIndustryVoucher, } from '../../../repositories/kuaishou-industry-voucher-repo.js' import { OPEN_91_PLATFORM, OPEN_91_PROVIDER } from '../../open-91/config.js' +import { retryOpen91Order } from '../ninetyone/order-service.js' import { normalizeTimestampIso } from '../../../utils/time.js' import { logIntegration } from '../../../utils/logger.js' import { @@ -15,7 +18,6 @@ import { import { normalizeSendCodePayload, assertSendCodePayload } from './payload.js' import { assertKuaishouIndustrySignature } from './crypto.js' import { - buildIndustryErrorResponse, buildIndustrySuccessResponse, buildIndustryEticketItem, buildIndustrySendCodeData, @@ -23,6 +25,7 @@ import { import { sendCallback } from './send-callback-service.js' import { buildKuaishouIndustryEticketFromVoucher, + isKuaishouIndustryVoucherSendCallbackSuccess, KUAISHOU_INDUSTRY_SEND_CALLBACK_STATUS, resolveKuaishouIndustryVoucherValidity, } from './voucher-service.js' @@ -41,6 +44,15 @@ type SendCodeCallbackParams = { ext?: string } +const SEND_CALLBACK_INITIAL_DELAY_MS = 3_000 +const SEND_CALLBACK_SCAN_INTERVAL_MS = 15_000 +const SEND_CALLBACK_MAX_ATTEMPTS = 8 +const SEND_CALLBACK_BACKOFF_MS = [3_000, 10_000, 30_000, 60_000, 120_000, 300_000] + +const sendCallbackRetryTimers = new Map() +let sendCallbackWorkerTimer: NodeJS.Timeout | null = null +let sendCallbackWorkerRunning = false + export async function handleSendCode(rawBody: JsonObject = {}) { const config = getKuaishouIndustryConfig() @@ -99,25 +111,6 @@ export async function handleSendCode(rawBody: JsonObject = {}) { buildKuaishouIndustryEticketFromVoucher(voucher, params.eticketType), ) - const callbackResult = await sendAndRecordCallback({ - oid: normalizedOid, - sendType: params.sendType, - etickets, - vouchers, - sellerId: params.sellerId, - token: params.token, - eticketType: params.eticketType, - ext: params.ext, - preferredTotalGoodsValue: resolveSendCallbackPreferredTotalGoodsValue(order), - }) - - if (!callbackResult.success) { - return buildIndustryErrorResponse( - 4010003, - resolveSendCallbackFailureMessage(callbackResult), - ) - } - if (order) { await bindKuaishouIndustryVouchersToOrderTasks(order, tasks, { source: 'kuaishou_industry_send_code', @@ -125,6 +118,10 @@ export async function handleSendCode(rawBody: JsonObject = {}) { }) } + scheduleKuaishouIndustrySendCallbackRetry(normalizedOid, SEND_CALLBACK_INITIAL_DELAY_MS, { + source: 'send_code_accepted', + }) + return buildIndustrySuccessResponse( buildIndustrySendCodeData({ oid: normalizedOid, @@ -135,6 +132,26 @@ export async function handleSendCode(rawBody: JsonObject = {}) { ) } +export function startKuaishouIndustrySendCallbackRetryWorker() { + if (sendCallbackWorkerTimer) { + return + } + + scheduleSendCallbackWorkerScan(5_000) +} + +export function stopKuaishouIndustrySendCallbackRetryWorker() { + if (sendCallbackWorkerTimer) { + clearTimeout(sendCallbackWorkerTimer) + sendCallbackWorkerTimer = null + } + + for (const timer of sendCallbackRetryTimers.values()) { + clearTimeout(timer) + } + sendCallbackRetryTimers.clear() +} + export async function resendKuaishouIndustryVoucherSendCallback(input: { voucherCode: string oid?: string @@ -170,12 +187,273 @@ export async function resendKuaishouIndustryVoucherSendCallback(input: { }) const updatedVoucher = await findKuaishouIndustryVoucherByCode(voucher.voucher_code, voucher.oid) + if (result.success) { + await syncOpen91OrderAfterSendCallbackSuccess(params.oid, { + source: 'admin_resend', + }) + } + return { ...result, voucher: updatedVoucher || voucher, } } +async function runKuaishouIndustrySendCallbackForOid( + oid: string, + { source = 'async_retry' }: { source?: string } = {}, +) { + const normalizedOid = String(oid || '').trim() + if (!normalizedOid) { + return { + success: false, + error: '缺少订单号,无法发起电子凭证发货回调', + } + } + + const vouchers = await listKuaishouIndustryVouchersByOid(normalizedOid) + const pendingVouchers = vouchers + .filter((voucher) => !isKuaishouIndustryVoucherSendCallbackSuccess(voucher)) + .filter((voucher) => + normalizePositiveInteger(voucher.send_callback_attempt_count) < SEND_CALLBACK_MAX_ATTEMPTS, + ) + + if (pendingVouchers.length === 0) { + return { + success: vouchers.length > 0, + skipped: true, + reason: vouchers.length > 0 ? 'send_callback_already_success_or_max_attempts' : 'voucher_missing', + } + } + + const rawPayload = parseJsonObject(pendingVouchers[0]?.raw_payload_json) + const body = parseJsonObject(rawPayload.body) + const firstVoucher = pendingVouchers[0] + if (!firstVoucher) { + return { + success: false, + error: '缺少待回调电子凭证', + } + } + + const params = resolveSendCodeCallbackParams(firstVoucher, body) + const eticketType = String(params.eticketType || '').trim() + const etickets = pendingVouchers.map((voucher) => + buildKuaishouIndustryEticketFromVoucher(voucher, eticketType), + ) + const order = await findLatestOrderByPlatformOrderId({ + provider: OPEN_91_PROVIDER, + platform: OPEN_91_PLATFORM, + platformOrderId: normalizedOid, + }) + const result = await sendAndRecordCallback({ + oid: params.oid, + sendType: params.sendType, + etickets, + vouchers: pendingVouchers, + sellerId: params.sellerId, + token: params.token, + eticketType, + source, + preferredTotalGoodsValue: resolveSendCallbackPreferredTotalGoodsValue(order), + ...(params.ext ? { ext: params.ext } : {}), + }) + + if (result.success) { + await syncOpen91OrderAfterSendCallbackSuccess(normalizedOid, { source }) + return result + } + + const nextAttemptCount = + Math.max(...pendingVouchers.map((voucher) => + normalizePositiveInteger(voucher.send_callback_attempt_count), + )) + 1 + if ( + nextAttemptCount < SEND_CALLBACK_MAX_ATTEMPTS && + isRetriableSendCallbackFailure(result) + ) { + scheduleKuaishouIndustrySendCallbackRetry(normalizedOid, resolveSendCallbackBackoffMs(nextAttemptCount), { + source: `${source}_retry`, + }) + } + + return result +} + +function scheduleKuaishouIndustrySendCallbackRetry( + oid: string, + delayMs: number, + { source = 'async_retry' }: { source?: string } = {}, +) { + const normalizedOid = String(oid || '').trim() + if (!normalizedOid || sendCallbackRetryTimers.has(normalizedOid)) { + return false + } + + const normalizedDelayMs = Math.max(0, Number(delayMs) || 0) + const timer = setTimeout(() => { + sendCallbackRetryTimers.delete(normalizedOid) + void runKuaishouIndustrySendCallbackForOid(normalizedOid, { source }).catch((error) => { + logIntegration('[kuaishou-industry/send-code]', '异步电子凭证发货回调执行异常', { + source, + oid: normalizedOid, + error: error instanceof Error ? error.message : String(error), + }, { level: 'warn' }) + scheduleKuaishouIndustrySendCallbackRetry(normalizedOid, resolveSendCallbackBackoffMs(1), { + source: `${source}_exception_retry`, + }) + }) + }, normalizedDelayMs) + + sendCallbackRetryTimers.set(normalizedOid, timer) + logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调已进入异步队列', { + source, + oid: normalizedOid, + delayMs: normalizedDelayMs, + }) + return true +} + +function scheduleSendCallbackWorkerScan(delayMs: number) { + if (sendCallbackWorkerTimer) { + return + } + + sendCallbackWorkerTimer = setTimeout(() => { + sendCallbackWorkerTimer = null + void runSendCallbackWorkerScan().finally(() => { + scheduleSendCallbackWorkerScan(SEND_CALLBACK_SCAN_INTERVAL_MS) + }) + }, Math.max(0, Number(delayMs) || 0)) +} + +async function runSendCallbackWorkerScan() { + if (sendCallbackWorkerRunning) { + return + } + + sendCallbackWorkerRunning = true + try { + const vouchers = await listKuaishouIndustryVouchersForSendCallbackRetry(200) + const dueOids = new Set() + for (const voucher of vouchers) { + if (isVoucherDueForSendCallbackRetry(voucher)) { + dueOids.add(String(voucher.oid || '').trim()) + } + } + + for (const oid of dueOids) { + scheduleKuaishouIndustrySendCallbackRetry(oid, 0, { + source: 'worker_scan', + }) + } + } catch (error) { + logIntegration('[kuaishou-industry/send-code]', '扫描待重试电子凭证发货回调失败', { + error: error instanceof Error ? error.message : String(error), + }, { level: 'warn' }) + } finally { + sendCallbackWorkerRunning = false + } +} + +function isVoucherDueForSendCallbackRetry(voucher: KuaishouIndustryVoucherRow) { + if (isKuaishouIndustryVoucherSendCallbackSuccess(voucher)) { + return false + } + + const attemptCount = normalizePositiveInteger(voucher.send_callback_attempt_count) + if (attemptCount >= SEND_CALLBACK_MAX_ATTEMPTS) { + return false + } + + const baseAt = voucher.send_callback_sent_at || voucher.created_at + const baseTime = Date.parse(String(baseAt || '')) + const elapsedMs = Date.now() - (Number.isFinite(baseTime) ? baseTime : 0) + const requiredDelayMs = attemptCount === 0 + ? SEND_CALLBACK_INITIAL_DELAY_MS + : resolveSendCallbackBackoffMs(attemptCount) + + return elapsedMs >= requiredDelayMs +} + +function resolveSendCallbackBackoffMs(attemptCount: unknown) { + const attempt = Math.max(1, normalizePositiveInteger(attemptCount)) + return SEND_CALLBACK_BACKOFF_MS[Math.min(attempt, SEND_CALLBACK_BACKOFF_MS.length - 1)] || + SEND_CALLBACK_BACKOFF_MS[SEND_CALLBACK_BACKOFF_MS.length - 1] || + SEND_CALLBACK_INITIAL_DELAY_MS +} + +async function syncOpen91OrderAfterSendCallbackSuccess( + oid: string, + { source = 'send_callback_success' }: { source?: string } = {}, +) { + const normalizedOid = String(oid || '').trim() + if (!normalizedOid) { + return + } + + const order = await findLatestOrderByPlatformOrderId({ + provider: OPEN_91_PROVIDER, + platform: OPEN_91_PLATFORM, + platformOrderId: normalizedOid, + }) + if (!order) { + return + } + + const tasks = await listTasksByOrderId(order.id) + if (tasks.length > 0) { + await bindKuaishouIndustryVouchersToOrderTasks(order, tasks, { + source, + now: normalizeTimestampIso(new Date().toISOString()), + }) + return + } + + try { + const result = await retryOpen91Order(order.id) + logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调成功后已触发 91 订单重试', { + source, + oid: normalizedOid, + orderId: order.id, + taskCount: result.tasks.length, + }) + } catch (error) { + logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调成功后重试 91 订单失败', { + source, + oid: normalizedOid, + orderId: order.id, + error: error instanceof Error ? error.message : String(error), + }, { level: 'warn' }) + } +} + +function isRetriableSendCallbackFailure(result: Awaited>) { + if (result.success) { + return false + } + + if (String(result.error || '').trim()) { + return true + } + + const response = parseJsonObject(result.response) + const marker = [ + response.result, + response.code, + response.sub_code, + response.error_msg, + response.sub_msg, + response.msg, + ].map((item) => String(item || '').trim()).join(' ') + + return ( + marker.includes('9994') || + marker.includes('807000') || + marker.includes('并发冲突') + ) +} + async function sendAndRecordCallback(input: { oid: string sendType: string @@ -254,7 +532,7 @@ async function sendAndRecordCallback(input: { )) if (!result.success) { - logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行失败,发码请求已拒绝继续', { + logIntegration('[kuaishou-industry/send-code]', '电子凭证发货回调执行失败,等待异步重试', { source: input.source || 'send_code', oid: input.oid, error: result.error || '', diff --git a/apps/backend/src/startup/bootstrap.ts b/apps/backend/src/startup/bootstrap.ts index 3ed38e95..afef4764 100644 --- a/apps/backend/src/startup/bootstrap.ts +++ b/apps/backend/src/startup/bootstrap.ts @@ -1,6 +1,7 @@ import { runDatabaseMigrations } from "../db/migrate.js"; import { ensureAdminUsersBootstrapped } from "../services/admin/admin-auth-service.js"; import { ensureFulfillmentCatalogBootstrapped } from "../services/bootstrap/fulfillment-bootstrap-service.js"; +import { startKuaishouIndustrySendCallbackRetryWorker } from "../services/platforms/kuaishou-industry/send-code-service.js"; import { startScheduledJobs } from "../services/scheduler/scheduler-service.js"; import { logError, logInfo } from "../utils/logger.js"; import { formatStartupError, type StartupState } from "./state.js"; @@ -42,6 +43,7 @@ export async function bootstrapCoreServices( }); startScheduledJobs(); + startKuaishouIndustrySendCallbackRetryWorker(); break; } catch (error) { const message = formatStartupError(error); diff --git a/apps/backend/src/startup/shutdown.ts b/apps/backend/src/startup/shutdown.ts index 2ee3134a..80ee4ded 100644 --- a/apps/backend/src/startup/shutdown.ts +++ b/apps/backend/src/startup/shutdown.ts @@ -1,5 +1,6 @@ import type { Server } from "node:http"; +import { stopKuaishouIndustrySendCallbackRetryWorker } from "../services/platforms/kuaishou-industry/send-code-service.js"; import { stopScheduledJobs } from "../services/scheduler/scheduler-service.js"; import { logError, logInfo } from "../utils/logger.js"; import type { StartupState } from "./state.js"; @@ -20,6 +21,7 @@ export function createShutdownController( logInfo("[shutdown]", `received ${signal}, closing HTTP server`); stopScheduledJobs(); + stopKuaishouIndustrySendCallbackRetryWorker(); if (!server.listening) { return;