206 lines
6.7 KiB
Python
206 lines
6.7 KiB
Python
"""
|
||
定时任务基类
|
||
|
||
提供 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} -> 转换失败")
|