Kamixitong/app/utils/scheduler.py
2025-12-12 11:35:14 +08:00

222 lines
6.2 KiB
Python

# -*- coding: utf-8 -*-
"""
定时任务调度器
管理所有后台定时任务的启动、停止和配置
"""
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger
from flask import Flask
import logging
logger = logging.getLogger(__name__)
# 全局调度器实例
scheduler = None
def init_scheduler(app: Flask):
"""
初始化定时任务调度器
Args:
app: Flask应用实例
"""
global scheduler
if scheduler is not None:
logger.warning("调度器已初始化,跳过重复初始化")
return scheduler
# 创建后台调度器
scheduler = BackgroundScheduler(
timezone='UTC',
# 如果使用数据库存储作业,可以配置:
# jobstores={'default': SQLAlchemyJobStore(url=app.config['SQLALCHEMY_DATABASE_URI'])},
job_defaults={
'coalesce': False, # 当错过多个执行时机时,不合并执行
'max_instances': 1, # 同一作业的最大并发实例数
}
)
# 添加定时任务
# 1. 每小时检查一次过期卡密
scheduler.add_job(
func=check_and_update_expired_licenses,
trigger=IntervalTrigger(hours=1),
id='update_expired_licenses_hourly',
name='每小时更新过期卡密状态',
replace_existing=True,
max_instances=1
)
# 2. 每天凌晨2点执行全面检查
scheduler.add_job(
func=daily_license_health_check,
trigger=CronTrigger(hour=2, minute=0),
id='daily_license_health_check',
name='每日卡密健康检查',
replace_existing=True,
max_instances=1
)
# 3. 每周清理一次旧日志
scheduler.add_job(
func=weekly_cleanup_logs,
trigger=CronTrigger(day_of_week='sun', hour=3, minute=0),
id='weekly_cleanup_logs',
name='每周清理日志',
replace_existing=True,
max_instances=1
)
logger.info("定时任务调度器初始化完成")
logger.info("已添加以下定时任务:")
logger.info(" 1. 每小时更新过期卡密状态")
logger.info(" 2. 每天凌晨2点卡密健康检查")
logger.info(" 3. 每周日凌晨3点清理日志")
return scheduler
def start_scheduler():
"""启动调度器"""
global scheduler
if scheduler is None:
raise RuntimeError("调度器未初始化,请先调用 init_scheduler()")
if scheduler.running:
logger.warning("调度器已在运行中")
return
scheduler.start()
logger.info("定时任务调度器已启动")
def stop_scheduler():
"""停止调度器"""
global scheduler
if scheduler is None:
logger.warning("调度器未初始化")
return
if not scheduler.running:
logger.warning("调度器未运行")
return
scheduler.shutdown()
logger.info("定时任务调度器已停止")
def check_and_update_expired_licenses():
"""
检查并更新过期卡密状态
这是定时任务的包装函数,导入在函数内部以避免循环导入
"""
try:
from .background_tasks import update_expired_licenses
result = update_expired_licenses()
if result['success']:
logger.info(f"过期卡密检查完成: {result['message']}")
else:
logger.error(f"过期卡密检查失败: {result['message']}")
return result
except Exception as e:
logger.error(f"执行过期卡密检查时发生错误: {str(e)}", exc_info=True)
return {
'success': False,
'message': f'执行过期卡密检查时发生错误: {str(e)}'
}
def daily_license_health_check():
"""
每日卡密健康检查
执行全面的卡密状态检查和统计
"""
try:
from .background_tasks import check_licenses_batch
result = check_licenses_batch()
if result['success']:
stats = result.get('statistics', {})
logger.info(
f"每日卡密健康检查完成:\n"
f" 已激活但过期: {stats.get('active_but_expired', 0)}\n"
f" 已过期且已标记: {stats.get('expired_and_marked', 0)}\n"
f" 已激活且有效: {stats.get('active_and_valid', 0)}\n"
f" 未激活: {stats.get('inactive', 0)}\n"
f" 已禁用: {stats.get('disabled', 0)}"
)
else:
logger.error(f"每日卡密健康检查失败: {result['message']}")
return result
except Exception as e:
logger.error(f"执行每日卡密健康检查时发生错误: {str(e)}", exc_info=True)
return {
'success': False,
'message': f'执行每日卡密健康检查时发生错误: {str(e)}'
}
def weekly_cleanup_logs():
"""
每周清理日志
清理过期的审计日志和验证记录
"""
try:
from .background_tasks import cleanup_old_license_logs
result = cleanup_old_license_logs()
if result['success']:
logger.info(f"每周日志清理完成: {result['message']}")
else:
logger.error(f"每周日志清理失败: {result['message']}")
return result
except Exception as e:
logger.error(f"执行每周日志清理时发生错误: {str(e)}", exc_info=True)
return {
'success': False,
'message': f'执行每周日志清理时发生错误: {str(e)}'
}
def get_scheduler():
"""获取全局调度器实例"""
return scheduler
def get_job_status():
"""
获取所有定时任务的状态
Returns:
dict: 包含所有任务状态的字典
"""
if scheduler is None:
return {
'running': False,
'jobs': []
}
jobs_info = []
for job in scheduler.get_jobs():
jobs_info.append({
'id': job.id,
'name': job.name,
'next_run_time': job.next_run_time.isoformat() if job.next_run_time else None,
'trigger': str(job.trigger)
})
return {
'running': scheduler.running,
'jobs': jobs_info
}