cangku/backend/src/jobs/stockSelectionSyncJob.js

111 lines
3.5 KiB
JavaScript

'use strict';
const cron = require('node-cron');
const planningDatabase = require('../services/planningDatabase');
const {
syncStockSelectionTables,
rebuildStockSelectionBase,
rebuildStockSelectionResult,
DEFAULT_REQUEST_TIMEOUT_MS,
} = require('../services/stockSelectionSync');
let baseRunning = false;
let resultRunning = false;
const JOB_LOCK_WAIT_SECONDS = Math.max(parseInt(process.env.STOCK_SELECTION_JOB_LOCK_WAIT_SECONDS || '1', 10), 0);
async function withGlobalJobLock(lockKey, trigger, runner) {
const pool = await planningDatabase.getPool();
const connection = await pool.getConnection();
let lockAcquired = false;
try {
const [rows] = await connection.query('SELECT GET_LOCK(?, ?) AS lockStatus', [ lockKey, JOB_LOCK_WAIT_SECONDS ]);
const lockStatus = rows && rows[0] ? Number(rows[0].lockStatus) : 0;
lockAcquired = lockStatus === 1;
if (!lockAcquired) {
console.log(`[StockSelectionSync] global lock not acquired, skip trigger=${trigger}, lockKey=${lockKey}`);
return { success: false, skipped: true, reason: 'global_lock_not_acquired', trigger, lockKey };
}
return runner();
} finally {
try {
if (lockAcquired) {
await connection.query('DO RELEASE_LOCK(?)', [ lockKey ]);
}
} finally {
connection.release();
}
}
}
async function runStockSelectionBaseJob(trigger = 'manual_base') {
if (baseRunning) {
console.log(`[StockSelectionSync] base job is already running, skip trigger=${trigger}`);
return { success: false, skipped: true, reason: 'running' };
}
baseRunning = true;
try {
return withGlobalJobLock('stock_selection_base_daily', trigger, async () => {
return rebuildStockSelectionBase({
syncType: trigger,
requestTimeout: DEFAULT_REQUEST_TIMEOUT_MS,
});
});
} finally {
baseRunning = false;
}
}
async function runStockSelectionResultJob(trigger = 'manual_result') {
if (resultRunning) {
console.log(`[StockSelectionSync] result job is already running, skip trigger=${trigger}`);
return { success: false, skipped: true, reason: 'running' };
}
resultRunning = true;
try {
return withGlobalJobLock('stock_selection_result_daily', trigger, async () => {
return rebuildStockSelectionResult({
syncType: trigger,
requestTimeout: DEFAULT_REQUEST_TIMEOUT_MS,
});
});
} finally {
resultRunning = false;
}
}
function startStockSelectionSchedulers() {
cron.schedule('0 5 * * *', () => {
runStockSelectionBaseJob('daily_schedule_base').catch((error) => {
console.error('[StockSelectionSync] base scheduled job failed', error);
});
}, { timezone: 'Asia/Shanghai' });
cron.schedule('0 7 * * *', () => {
runStockSelectionResultJob('daily_schedule_result').catch((error) => {
console.error('[StockSelectionSync] result scheduled job failed', error);
});
}, { timezone: 'Asia/Shanghai' });
console.log('[Scheduler] 条码打标底表任务已注册: 每天05:00执行');
console.log('[Scheduler] 条码打标结果表任务已注册: 每天07:00执行');
}
async function manualStockSelectionSync() {
const baseResult = await runStockSelectionBaseJob('manual_base');
const resultResult = await runStockSelectionResultJob('manual_result');
return { success: Boolean(baseResult?.success && resultResult?.success), baseResult, resultResult };
}
module.exports = {
startStockSelectionSchedulers,
manualStockSelectionSync,
runStockSelectionBaseJob,
runStockSelectionResultJob,
runStockSelectionSyncJob: syncStockSelectionTables,
};