""" 定时任务基类 提供 crontab 表达式到 APScheduler CronTrigger 的转换功能 以及便捷的任务添加方法 """ import logging from typing import Optional from app.core.ScheduledTaskManager import ScheduledTaskManager from apscheduler.triggers.cron import CronTrigger logger = logging.getLogger(__name__) class ScheduledTask: """ 定时任务基类 提供将标准 crontab 表达式转换为 APScheduler CronTrigger 对象的功能 Crontab 表达式格式:分 时 日 月 周 例如: - "0 2 * * *" -> 每天凌晨 2:00 执行 - "*/5 * * * *" -> 每 5 分钟执行一次 - "0 0 * * 0" -> 每周日 00:00 执行 - "0 0 1 * *" -> 每月 1 日 00:00 执行 """ def __init__(self, task_manager: Optional[ScheduledTaskManager] = None): """ 初始化定时任务 Args: task_manager: 可选的调度任务管理器实例,如果不提供则需要外部提供 """ self.task_manager = task_manager def add_cron_task( self, task_id: str, task_name: str, crontab: str, task_func, args=None, kwargs=None ): """ 通过 crontab 表达式添加定时任务 Args: task_id: 任务唯一标识符 task_name: 任务名称 crontab: crontab 表达式字符串,格式为 "分 时 日 月 周" 例如: "0 2 * * *" 表示每天凌晨 2 点执行 task_func: 要执行的任务函数 args: 传递给任务函数的位置参数(元组) kwargs: 传递给任务函数的关键字参数(字典) Returns: bool: 添加成功返回 True,失败返回 False 示例: task = ScheduledTask(task_manager) task.add_cron_task( task_id="daily_backup", task_name="每日备份", crontab="0 2 * * *", task_func=backup_function, args=() ) """ if self.task_manager is None: logger.error("任务管理器未初始化,无法添加任务") return False try: # 将 crontab 表达式转换为 CronTrigger trigger = self.covert(crontab) if trigger is None: logger.error(f"crontab 表达式转换失败: {crontab}") return False # 使用调度器添加任务 job = self.task_manager.scheduler.add_job( task_func, trigger, id=task_id, name=task_name, args=args, kwargs=kwargs ) # 记录到 jobs 字典中 self.task_manager.jobs[task_id] = job logger.info(f"已添加定时任务: {task_name} ({task_id}), crontab: {crontab}") return True except Exception as e: logger.error(f"添加定时任务失败: {e}") return False def covert(self, crontab: str) -> Optional[CronTrigger]: """ 将字符串的 crontab 表达式转换为 CronTrigger 对象 Args: crontab: crontab 表达式字符串 格式: "分钟 小时 日期 月份 星期" 支持的特殊字符: * (任意值), / (间隔), - (范围), , (列表) Returns: Optional[CronTrigger]: CronTrigger 对象,解析失败返回 None Crontab 字段说明: 字段 取值范围 特殊字符 分钟 0-59 * / - , 小时 0-23 * / - , 日期 1-31 * / - , 月份 1-12 * / - , 星期 0-7 (0和7都代表周日) * / - , 示例: "0 2 * * *" -> 每天凌晨 2:00 "*/5 * * * *" -> 每 5 分钟 "0 0 * * 0" -> 每周日 00:00 "0 0 1 * *" -> 每月 1 日 00:00 "30 8 * * 1-5" -> 工作日早上 8:30 "0 9-17 * * 1-5" -> 工作日每小时 9:00-17:00 """ if not crontab or not isinstance(crontab, str): logger.error(f"无效的 crontab 表达式: {crontab}") return None try: # 去除首尾空格并按空格分割 parts = crontab.strip().split() # 验证格式:crontab 表达式应该有 5 或 6 个部分 # 5 部分: 分 时 日 月 周 # 6 部分: 分 时 日 月 周 年 (可选) if len(parts) < 5 or len(parts) > 6: logger.error(f"crontab 表达式格式错误,应为 5 或 6 个部分,实际为 {len(parts)} 个: {crontab}") return None # 解析各个字段 minute = parts[0] # 分钟: 0-59 hour = parts[1] # 小时: 0-23 day = parts[2] # 日期: 1-31 month = parts[3] # 月份: 1-12 day_of_week = parts[4] # 星期: 0-7 (0 和 7 都代表周日) # 处理星期字段的特殊值 # 在 crontab 中,0 和 7 都代表周日 # 在 APScheduler 中,周日使用 6 (mon=0, tue=1, ..., sun=6) # 需要进行转换 if day_of_week in ['0', '7']: day_of_week = '6' # 转换为 APScheduler 的周日表示 # 创建 CronTrigger 对象 trigger = CronTrigger( minute=minute, hour=hour, day=day, month=month, day_of_week=day_of_week ) logger.debug(f"成功转换 crontab 表达式: {crontab} -> {trigger}") return trigger except ValueError as e: logger.error(f"crontab 表达式值无效: {crontab}, 错误: {e}") return None except Exception as e: logger.error(f"转换 crontab 表达式失败: {crontab}, 错误: {e}") return None # 使用示例 if __name__ == "__main__": # 创建任务实例 task = ScheduledTask() # 测试各种 crontab 表达式 test_cases = [ "0 2 * * *", # 每天凌晨 2 点 "*/5 * * * *", # 每 5 分钟 "0 0 * * 0", # 每周日 "0 0 1 * *", # 每月 1 日 "30 8 * * 1-5", # 工作日早上 8:30 "0 9-17 * * 1-5", # 工作日每小时 ] print("Crontab 表达式转换测试:") print("-" * 60) for crontab in test_cases: trigger = task.covert(crontab) if trigger: print(f"✓ {crontab:20s} -> {trigger}") else: print(f"✗ {crontab:20s} -> 转换失败")