# -*- 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 }