mirror of
https://github.com/yyhuni/xingrin.git
synced 2026-01-31 19:53:11 +08:00
136 lines
4.2 KiB
Python
136 lines
4.2 KiB
Python
"""
|
||
APScheduler 定时任务调度器
|
||
|
||
替代 Prefect Work Pool,用于触发定时任务。
|
||
实际任务执行通过 task_distributor 分发到各 Worker。
|
||
"""
|
||
import logging
|
||
from apscheduler.schedulers.background import BackgroundScheduler
|
||
from apscheduler.triggers.cron import CronTrigger
|
||
from apscheduler.triggers.interval import IntervalTrigger
|
||
from django.conf import settings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 全局调度器实例
|
||
_scheduler: BackgroundScheduler | None = None
|
||
|
||
|
||
def get_scheduler() -> BackgroundScheduler:
|
||
"""获取调度器实例"""
|
||
global _scheduler
|
||
if _scheduler is None:
|
||
_scheduler = BackgroundScheduler(
|
||
timezone=settings.TIME_ZONE,
|
||
job_defaults={
|
||
'coalesce': True, # 合并错过的任务
|
||
'max_instances': 1, # 同一任务最多同时运行1个实例
|
||
'misfire_grace_time': 60 * 5, # 错过5分钟内仍然执行
|
||
}
|
||
)
|
||
return _scheduler
|
||
|
||
|
||
def start_scheduler():
|
||
"""启动调度器并注册所有定时任务"""
|
||
scheduler = get_scheduler()
|
||
|
||
if scheduler.running:
|
||
logger.info("调度器已在运行")
|
||
return
|
||
|
||
# 注册定时任务
|
||
_register_scheduled_jobs(scheduler)
|
||
|
||
# 启动调度器
|
||
scheduler.start()
|
||
logger.info("✓ APScheduler 定时调度器已启动")
|
||
|
||
|
||
def shutdown_scheduler():
|
||
"""关闭调度器"""
|
||
global _scheduler
|
||
if _scheduler and _scheduler.running:
|
||
_scheduler.shutdown(wait=False)
|
||
logger.info("APScheduler 调度器已关闭")
|
||
_scheduler = None
|
||
|
||
|
||
def _register_scheduled_jobs(scheduler: BackgroundScheduler):
|
||
"""注册所有定时任务"""
|
||
|
||
# 1. 定时扫描任务(检查并执行到期的定时扫描)
|
||
scheduler.add_job(
|
||
_trigger_scheduled_scans,
|
||
trigger=IntervalTrigger(minutes=1), # 每分钟检查并触发到期任务
|
||
id='scheduled_scans',
|
||
name='定时扫描任务',
|
||
replace_existing=True,
|
||
)
|
||
logger.info(" - 已注册: 定时扫描任务(每分钟)")
|
||
|
||
# 2. 资产统计刷新(每小时)
|
||
scheduler.add_job(
|
||
_trigger_statistics_refresh,
|
||
trigger=CronTrigger(minute=0), # 每小时整点
|
||
id='statistics_refresh',
|
||
name='资产统计刷新',
|
||
replace_existing=True,
|
||
)
|
||
logger.info(" - 已注册: 资产统计刷新(每小时)")
|
||
|
||
# 3. 扫描结果清理(每天凌晨3点)
|
||
scheduler.add_job(
|
||
_trigger_cleanup,
|
||
trigger=CronTrigger(hour=3, minute=0),
|
||
id='scan_cleanup',
|
||
name='扫描结果清理',
|
||
replace_existing=True,
|
||
)
|
||
logger.info(" - 已注册: 扫描结果清理(每天 03:00)")
|
||
|
||
# 注意:搜索物化视图刷新已迁移到 pg_ivm 增量维护,无需定时任务
|
||
|
||
|
||
def _trigger_scheduled_scans():
|
||
"""触发到期的定时扫描任务"""
|
||
try:
|
||
from apps.scan.services.scheduled_scan_service import ScheduledScanService
|
||
|
||
service = ScheduledScanService()
|
||
triggered_count = service.trigger_due_scans()
|
||
|
||
if triggered_count > 0:
|
||
logger.info(f"定时扫描: 已触发 {triggered_count} 个任务")
|
||
|
||
except Exception as e:
|
||
logger.error(f"定时扫描任务执行失败: {e}", exc_info=True)
|
||
|
||
|
||
def _trigger_statistics_refresh():
|
||
"""触发资产统计刷新"""
|
||
try:
|
||
from apps.asset.services.statistics_service import AssetStatisticsService
|
||
|
||
service = AssetStatisticsService()
|
||
service.refresh_statistics()
|
||
|
||
logger.info("资产统计刷新完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"资产统计刷新失败: {e}", exc_info=True)
|
||
|
||
|
||
def _trigger_cleanup():
|
||
"""触发扫描结果清理(分发到各 Worker)"""
|
||
try:
|
||
from apps.engine.services.task_distributor import TaskDistributor
|
||
|
||
distributor = TaskDistributor()
|
||
results = distributor.execute_cleanup_on_all_workers()
|
||
|
||
logger.info(f"扫描清理任务已分发到 {len(results)} 个 Worker")
|
||
|
||
except Exception as e:
|
||
logger.error(f"扫描清理任务分发失败: {e}", exc_info=True)
|