- 新增 order_items.manual_sku_key 生成列(迁移 106),统一内部 SKU 口径(skuNick → outSkuSn → sku_code → sku_name,去空格) - 人工渠道匹配 8.4s → 62ms;人工商品归一写回 7.0s → 307ms - 销售事实重算改为物化订单集合 + 预聚合成本/退款,6.9s → 0.5s 内 - 成本异常写入去掉逐项 LATERAL 工单探测,收口按最近 7 天窗口,7–8s → 0.5s 内 - 销售毛利订单统计去掉重复 NOT EXISTS,9.4s → 0.4s - 接单平台店铺下拉改 EXISTS 半连接,3.1s → 0.3s - cost-finance-sync 默认间隔 5 分钟 → 15 分钟;完整同步 51.2s → 34.9s
284 lines
8.2 KiB
TypeScript
284 lines
8.2 KiB
TypeScript
import path from 'node:path'
|
|
import type { JsonObject } from '../../types/json.js'
|
|
|
|
import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js'
|
|
import { PROJECT_ROOT } from '../../config/runtime.js'
|
|
import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js'
|
|
|
|
const SCHEDULED_JOBS_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'scheduled-jobs.json')
|
|
const WORK_ORDER_TIMEOUT_JOB_ID = 'work-order-timeout'
|
|
const DEPOSIT_UNFREEZE_JOB_ID = 'deposit-unfreeze'
|
|
const DATA_RETENTION_JOB_ID = 'data-retention'
|
|
const PDD_ORDER_SYNC_JOB_ID = 'pdd_order_sync'
|
|
const COST_FINANCE_SYNC_JOB_ID = 'cost-finance-sync'
|
|
|
|
export function getScheduledJobsFilePath() {
|
|
return SCHEDULED_JOBS_FILE_PATH
|
|
}
|
|
|
|
export function getScheduledJobsConfig() {
|
|
return readAppConfigEntry({
|
|
configKey: APP_CONFIG_KEYS.scheduledJobs,
|
|
legacyFilePath: SCHEDULED_JOBS_FILE_PATH,
|
|
fallback: createDefaultScheduledJobsConfig,
|
|
normalize: normalizeScheduledJobsConfig,
|
|
})
|
|
}
|
|
|
|
export function saveScheduledJobsConfig(rawValue: unknown) {
|
|
return saveAppConfigEntry({
|
|
configKey: APP_CONFIG_KEYS.scheduledJobs,
|
|
legacyFilePath: SCHEDULED_JOBS_FILE_PATH,
|
|
value: rawValue,
|
|
normalize: normalizeScheduledJobsConfig,
|
|
})
|
|
}
|
|
|
|
export function getWorkOrderTimeoutJob(config: JsonObject = getScheduledJobsConfig()) {
|
|
return (
|
|
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
|
(item) => String(item.id || '').trim() === WORK_ORDER_TIMEOUT_JOB_ID,
|
|
) || createDefaultWorkOrderTimeoutJob()
|
|
)
|
|
}
|
|
|
|
export function getDepositUnfreezeJob(config: JsonObject = getScheduledJobsConfig()) {
|
|
return (
|
|
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
|
(item) => String(item.id || '').trim() === DEPOSIT_UNFREEZE_JOB_ID,
|
|
) || createDefaultDepositUnfreezeJob()
|
|
)
|
|
}
|
|
|
|
export function getPddOrderSyncJob(config: JsonObject = getScheduledJobsConfig()) {
|
|
return (
|
|
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
|
(item) => String(item.id || '').trim() === PDD_ORDER_SYNC_JOB_ID,
|
|
) || createDefaultPddOrderSyncJob()
|
|
)
|
|
}
|
|
|
|
export function getScheduledJobById(jobId: unknown, config: JsonObject = getScheduledJobsConfig()) {
|
|
return (
|
|
(Array.isArray(config.jobs) ? config.jobs : []).find(
|
|
(item) => String(item.id || '').trim() === String(jobId || '').trim(),
|
|
) || null
|
|
)
|
|
}
|
|
|
|
export function normalizeScheduledJobsConfig(rawValue: unknown) {
|
|
const source = isPlainObject(rawValue) ? rawValue : {}
|
|
const rawJobs = Array.isArray(source.jobs) ? source.jobs : []
|
|
const jobs = rawJobs
|
|
.map((item) => normalizeScheduledJob(item))
|
|
.filter((item): item is NonNullable<ReturnType<typeof normalizeScheduledJob>> => Boolean(item))
|
|
const hasWorkOrderTimeout = jobs.some((item) => item.id === WORK_ORDER_TIMEOUT_JOB_ID)
|
|
const hasDepositUnfreeze = jobs.some((item) => item.id === DEPOSIT_UNFREEZE_JOB_ID)
|
|
const hasDataRetention = jobs.some((item) => item.id === DATA_RETENTION_JOB_ID)
|
|
const hasPddOrderSync = jobs.some((item) => item.id === PDD_ORDER_SYNC_JOB_ID)
|
|
const hasCostFinanceSync = jobs.some((item) => item.id === COST_FINANCE_SYNC_JOB_ID)
|
|
|
|
if (!hasWorkOrderTimeout) {
|
|
jobs.push(createDefaultWorkOrderTimeoutJob())
|
|
}
|
|
if (!hasDepositUnfreeze) {
|
|
jobs.push(createDefaultDepositUnfreezeJob())
|
|
}
|
|
if (!hasDataRetention) {
|
|
jobs.push(createDefaultDataRetentionJob())
|
|
}
|
|
if (!hasPddOrderSync) {
|
|
jobs.push(createDefaultPddOrderSyncJob())
|
|
}
|
|
if (!hasCostFinanceSync) {
|
|
jobs.push(createDefaultCostFinanceSyncJob())
|
|
}
|
|
|
|
return {
|
|
enabled: typeof source.enabled === 'boolean' ? source.enabled : true,
|
|
jobs,
|
|
}
|
|
}
|
|
|
|
function normalizeScheduledJob(rawValue: unknown) {
|
|
if (!isPlainObject(rawValue)) {
|
|
return null
|
|
}
|
|
|
|
const type = String(rawValue.type || '').trim()
|
|
// 已下线的 cloudtentacles_health 配置直接丢弃
|
|
if (type === 'cloudtentacles_health') {
|
|
return null
|
|
}
|
|
if (type === 'work_order_timeout') {
|
|
return normalizeWorkOrderTimeoutJob(rawValue)
|
|
}
|
|
if (type === 'deposit_unfreeze') {
|
|
return normalizeDepositUnfreezeJob(rawValue)
|
|
}
|
|
if (type === 'data_retention') {
|
|
return normalizeDataRetentionJob(rawValue)
|
|
}
|
|
if (type === 'pdd_order_sync') {
|
|
return normalizePddOrderSyncJob(rawValue)
|
|
}
|
|
if (type === 'cost_finance_sync') {
|
|
return normalizeCostFinanceSyncJob(rawValue)
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
function normalizePddOrderSyncJob(rawValue: JsonObject) {
|
|
const config = isPlainObject(rawValue.config) ? rawValue.config : {}
|
|
|
|
return {
|
|
id: PDD_ORDER_SYNC_JOB_ID,
|
|
type: 'pdd_order_sync',
|
|
enabled: rawValue.enabled !== false,
|
|
intervalSeconds: normalizeRangeInteger(rawValue.intervalSeconds, 20, 10, 3600),
|
|
config: {
|
|
pageSize: normalizeRangeInteger(config.pageSize, 10, 1, 50),
|
|
},
|
|
}
|
|
}
|
|
|
|
function normalizeCostFinanceSyncJob(rawValue: JsonObject) {
|
|
const config = isPlainObject(rawValue.config) ? rawValue.config : {}
|
|
|
|
return {
|
|
id: COST_FINANCE_SYNC_JOB_ID,
|
|
type: 'cost_finance_sync',
|
|
enabled: rawValue.enabled !== false,
|
|
intervalSeconds: normalizeRangeInteger(rawValue.intervalSeconds, 300, 60, 3600),
|
|
config: {
|
|
days: normalizeRangeInteger(config.days, 7, 1, 90),
|
|
},
|
|
}
|
|
}
|
|
|
|
function normalizeDepositUnfreezeJob(rawValue: JsonObject) {
|
|
const config = isPlainObject(rawValue.config) ? rawValue.config : {}
|
|
|
|
return {
|
|
id: DEPOSIT_UNFREEZE_JOB_ID,
|
|
type: 'deposit_unfreeze',
|
|
enabled: rawValue.enabled !== false,
|
|
intervalSeconds: normalizeRangeInteger(rawValue.intervalSeconds, 60, 30, 3600),
|
|
config: {
|
|
scanLimit: normalizeRangeInteger(config.scanLimit, 100, 1, 2000),
|
|
},
|
|
}
|
|
}
|
|
|
|
function normalizeDataRetentionJob(rawValue: JsonObject) {
|
|
return {
|
|
id: DATA_RETENTION_JOB_ID,
|
|
type: 'data_retention',
|
|
enabled: rawValue.enabled !== false,
|
|
intervalSeconds: normalizeRangeInteger(rawValue.intervalSeconds, 86400, 3600, 604800),
|
|
config: {},
|
|
}
|
|
}
|
|
|
|
function normalizeWorkOrderTimeoutJob(rawValue: JsonObject) {
|
|
const config = isPlainObject(rawValue.config) ? rawValue.config : {}
|
|
|
|
return {
|
|
id: WORK_ORDER_TIMEOUT_JOB_ID,
|
|
type: 'work_order_timeout',
|
|
enabled: rawValue.enabled !== false,
|
|
intervalSeconds: normalizeRangeInteger(rawValue.intervalSeconds, 60, 30, 3600),
|
|
config: {
|
|
scanLimit: normalizeRangeInteger(config.scanLimit, 50, 1, 1000),
|
|
},
|
|
}
|
|
}
|
|
|
|
function createDefaultScheduledJobsConfig() {
|
|
return {
|
|
enabled: true,
|
|
jobs: [
|
|
createDefaultWorkOrderTimeoutJob(),
|
|
createDefaultDepositUnfreezeJob(),
|
|
createDefaultDataRetentionJob(),
|
|
createDefaultPddOrderSyncJob(),
|
|
createDefaultCostFinanceSyncJob(),
|
|
],
|
|
}
|
|
}
|
|
|
|
function createDefaultPddOrderSyncJob() {
|
|
return {
|
|
id: PDD_ORDER_SYNC_JOB_ID,
|
|
type: 'pdd_order_sync',
|
|
enabled: true,
|
|
intervalSeconds: 20,
|
|
config: {
|
|
pageSize: 10,
|
|
},
|
|
}
|
|
}
|
|
|
|
function createDefaultCostFinanceSyncJob() {
|
|
return {
|
|
id: COST_FINANCE_SYNC_JOB_ID,
|
|
type: 'cost_finance_sync',
|
|
enabled: true,
|
|
// 一次同步要跑 6 条全窗口聚合(41 万单实测 30–60 秒)。5 分钟一次会让
|
|
// 数据库长期处在 10–20% 的同步负载下,后台页面首屏被拖慢;财务事实
|
|
// 15 分钟刷新已足够,需要更快可在后台定时任务里改回 300。
|
|
intervalSeconds: 900,
|
|
config: {
|
|
days: 7,
|
|
},
|
|
}
|
|
}
|
|
|
|
function createDefaultDataRetentionJob() {
|
|
return {
|
|
id: DATA_RETENTION_JOB_ID,
|
|
type: 'data_retention',
|
|
enabled: true,
|
|
intervalSeconds: 86400,
|
|
config: {},
|
|
}
|
|
}
|
|
|
|
function createDefaultDepositUnfreezeJob() {
|
|
return {
|
|
id: DEPOSIT_UNFREEZE_JOB_ID,
|
|
type: 'deposit_unfreeze',
|
|
enabled: true,
|
|
intervalSeconds: 60,
|
|
config: {
|
|
scanLimit: 100,
|
|
},
|
|
}
|
|
}
|
|
|
|
function createDefaultWorkOrderTimeoutJob() {
|
|
return {
|
|
id: WORK_ORDER_TIMEOUT_JOB_ID,
|
|
type: 'work_order_timeout',
|
|
enabled: true,
|
|
intervalSeconds: 60,
|
|
config: {
|
|
scanLimit: 50,
|
|
},
|
|
}
|
|
}
|
|
|
|
function normalizeRangeInteger(value: unknown, fallback: number, min: number, max: number) {
|
|
const parsed = Number(value)
|
|
if (!Number.isInteger(parsed)) {
|
|
return fallback
|
|
}
|
|
|
|
return Math.min(max, Math.max(min, parsed))
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is JsonObject {
|
|
return Object.prototype.toString.call(value) === '[object Object]'
|
|
}
|