222 lines
6.2 KiB
Python
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
|
|
} |