mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-12 12:38:31 +08:00
Initial commit
This commit is contained in:
187
src/services/system/scheduler.py
Normal file
187
src/services/system/scheduler.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
统一定时任务调度器
|
||||
|
||||
使用 APScheduler 管理所有定时任务,支持时区配置。
|
||||
所有定时任务使用应用时区(APP_TIMEZONE)配置执行时间,
|
||||
数据存储仍然使用 UTC。
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
|
||||
from src.core.logger import logger
|
||||
|
||||
# 应用时区配置,默认为 Asia/Shanghai
|
||||
APP_TIMEZONE = os.getenv("APP_TIMEZONE", "Asia/Shanghai")
|
||||
|
||||
|
||||
class TaskScheduler:
|
||||
"""统一定时任务调度器"""
|
||||
|
||||
_instance: Optional["TaskScheduler"] = None
|
||||
|
||||
def __init__(self):
|
||||
self.scheduler = AsyncIOScheduler(timezone=APP_TIMEZONE)
|
||||
self._started = False
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls) -> "TaskScheduler":
|
||||
"""获取调度器单例"""
|
||||
if cls._instance is None:
|
||||
cls._instance = TaskScheduler()
|
||||
return cls._instance
|
||||
|
||||
def add_cron_job(
|
||||
self,
|
||||
func,
|
||||
hour: int,
|
||||
minute: int = 0,
|
||||
job_id: str = None,
|
||||
name: str = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
添加 cron 定时任务
|
||||
|
||||
Args:
|
||||
func: 要执行的函数
|
||||
hour: 执行时间(小时),使用业务时区
|
||||
minute: 执行时间(分钟)
|
||||
job_id: 任务ID
|
||||
name: 任务名称(用于日志)
|
||||
**kwargs: 传递给任务函数的参数
|
||||
"""
|
||||
trigger = CronTrigger(hour=hour, minute=minute, timezone=APP_TIMEZONE)
|
||||
|
||||
job_id = job_id or func.__name__
|
||||
display_name = name or job_id
|
||||
|
||||
self.scheduler.add_job(
|
||||
func,
|
||||
trigger,
|
||||
id=job_id,
|
||||
name=display_name,
|
||||
replace_existing=True,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"已注册定时任务: {display_name}, "
|
||||
f"执行时间: {hour:02d}:{minute:02d} ({APP_TIMEZONE})"
|
||||
)
|
||||
|
||||
def add_interval_job(
|
||||
self,
|
||||
func,
|
||||
seconds: int = None,
|
||||
minutes: int = None,
|
||||
hours: int = None,
|
||||
job_id: str = None,
|
||||
name: str = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
添加间隔执行任务
|
||||
|
||||
Args:
|
||||
func: 要执行的函数
|
||||
seconds: 间隔秒数
|
||||
minutes: 间隔分钟数
|
||||
hours: 间隔小时数
|
||||
job_id: 任务ID
|
||||
name: 任务名称
|
||||
**kwargs: 传递给任务函数的参数
|
||||
"""
|
||||
# 构建 trigger 参数,过滤掉 None 值
|
||||
trigger_kwargs = {}
|
||||
if seconds is not None:
|
||||
trigger_kwargs["seconds"] = seconds
|
||||
if minutes is not None:
|
||||
trigger_kwargs["minutes"] = minutes
|
||||
if hours is not None:
|
||||
trigger_kwargs["hours"] = hours
|
||||
|
||||
trigger = IntervalTrigger(**trigger_kwargs)
|
||||
|
||||
job_id = job_id or func.__name__
|
||||
display_name = name or job_id
|
||||
|
||||
# 计算间隔描述
|
||||
interval_parts = []
|
||||
if hours:
|
||||
interval_parts.append(f"{hours}小时")
|
||||
if minutes:
|
||||
interval_parts.append(f"{minutes}分钟")
|
||||
if seconds:
|
||||
interval_parts.append(f"{seconds}秒")
|
||||
interval_desc = "".join(interval_parts) or "未知间隔"
|
||||
|
||||
self.scheduler.add_job(
|
||||
func,
|
||||
trigger,
|
||||
id=job_id,
|
||||
name=display_name,
|
||||
replace_existing=True,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
|
||||
logger.info(f"已注册间隔任务: {display_name}, 执行间隔: {interval_desc}")
|
||||
|
||||
def start(self):
|
||||
"""启动调度器"""
|
||||
if self._started:
|
||||
logger.warning("调度器已在运行中")
|
||||
return
|
||||
|
||||
self.scheduler.start()
|
||||
self._started = True
|
||||
logger.info(f"定时任务调度器已启动,应用时区: {APP_TIMEZONE}")
|
||||
|
||||
# 打印下次执行时间
|
||||
self._log_next_run_times()
|
||||
|
||||
def stop(self):
|
||||
"""停止调度器"""
|
||||
if not self._started:
|
||||
return
|
||||
|
||||
self.scheduler.shutdown(wait=False)
|
||||
self._started = False
|
||||
logger.info("定时任务调度器已停止")
|
||||
|
||||
def _log_next_run_times(self):
|
||||
"""记录所有任务的下次执行时间"""
|
||||
jobs = self.scheduler.get_jobs()
|
||||
if not jobs:
|
||||
return
|
||||
|
||||
logger.info("已注册的定时任务:")
|
||||
for job in jobs:
|
||||
next_run = job.next_run_time
|
||||
if next_run:
|
||||
# 计算距离下次执行的时间
|
||||
now = datetime.now(next_run.tzinfo)
|
||||
delta = next_run - now
|
||||
hours, remainder = divmod(int(delta.total_seconds()), 3600)
|
||||
minutes = remainder // 60
|
||||
|
||||
logger.info(
|
||||
f" - {job.name}: 下次执行 {next_run.strftime('%Y-%m-%d %H:%M')} "
|
||||
f"({hours}小时{minutes}分钟后)"
|
||||
)
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""调度器是否在运行"""
|
||||
return self._started
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def get_scheduler() -> TaskScheduler:
|
||||
"""获取调度器单例"""
|
||||
return TaskScheduler.get_instance()
|
||||
Reference in New Issue
Block a user