'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, };