refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构

- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
fawney19
2026-04-03 16:26:16 +08:00
parent 8f26e1a31f
commit 1d9c77522a
868 changed files with 1735 additions and 2433 deletions

View File

@@ -0,0 +1,303 @@
"""
统一定时任务调度器
使用 APScheduler 管理所有定时任务,支持时区配置。
所有定时任务使用应用时区APP_TIMEZONE配置执行时间
数据存储仍然使用 UTC。
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Callable
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.date import DateTrigger
from apscheduler.triggers.interval import IntervalTrigger
from src.config import config
from src.core.logger import logger
# 统一从 config 读取,不再重复 os.getenv
APP_TIMEZONE = config.app_timezone
class TaskScheduler:
"""统一定时任务调度器"""
_instance: TaskScheduler | None = None
def __init__(self) -> None:
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: Callable[..., Any],
hour: int | str,
minute: int = 0,
day_of_week: str | int | None = None,
job_id: str | None = None,
name: str | None = None,
timezone: str | None = None,
**kwargs: Any,
) -> Any:
"""
添加 cron 定时任务
Args:
func: 要执行的函数
hour: 执行时间(小时),使用业务时区
minute: 执行时间(分钟)
day_of_week: 星期几执行(如 "sun", "mon" 或 0-6
job_id: 任务ID
name: 任务名称(用于日志)
**kwargs: 传递给任务函数的参数
"""
trigger_timezone = timezone or APP_TIMEZONE
trigger = CronTrigger(
day_of_week=day_of_week, hour=hour, minute=minute, timezone=trigger_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}:{minute:02d} ({trigger_timezone})"
)
def add_interval_job(
self,
func: Callable[..., Any],
seconds: int | None = None,
minutes: int | None = None,
hours: int | None = None,
job_id: str | None = None,
name: str | None = None,
**kwargs: Any,
) -> Any:
"""
添加间隔执行任务
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 add_date_job(
self,
func: Callable[..., Any],
run_date: datetime,
job_id: str | None = None,
name: str | None = None,
**kwargs: Any,
) -> Any:
"""
添加一次性定时任务(在指定时间执行一次)
Args:
func: 要执行的函数
run_date: 执行时间datetime 对象)
job_id: 任务ID
name: 任务名称(用于日志)
**kwargs: 传递给任务函数的参数
"""
trigger = DateTrigger(run_date=run_date)
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("已注册一次性任务: {}, 执行时间: {}", display_name, run_date.isoformat())
def start(self) -> Any:
"""启动调度器"""
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) -> Any:
"""停止调度器"""
if not self._started:
return
self.scheduler.shutdown(wait=False)
self._started = False
logger.info("定时任务调度器已停止")
def _log_next_run_times(self) -> None:
"""记录所有任务的下次执行时间"""
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}分钟后)"
)
def remove_job(self, job_id: str) -> None:
"""
移除指定的定时任务
Args:
job_id: 任务ID
"""
try:
self.scheduler.remove_job(job_id)
logger.info(f"已移除定时任务: {job_id}")
except Exception as e:
logger.warning(f"移除定时任务失败 {job_id}: {e}")
def reschedule_cron_job(
self,
job_id: str,
hour: int,
minute: int = 0,
) -> bool:
"""
重新调度 cron 定时任务的执行时间
Args:
job_id: 任务ID
hour: 新的执行时间(小时),使用业务时区
minute: 新的执行时间(分钟)
Returns:
是否成功重新调度
"""
try:
job = self.scheduler.get_job(job_id)
if not job:
logger.warning(f"任务不存在: {job_id}")
return False
trigger = CronTrigger(hour=hour, minute=minute, timezone=APP_TIMEZONE)
self.scheduler.reschedule_job(job_id, trigger=trigger)
logger.info(
f"已重新调度定时任务: {job.name}, "
f"新执行时间: {hour:02d}:{minute:02d} ({APP_TIMEZONE})"
)
return True
except Exception as e:
logger.exception(f"重新调度任务失败 {job_id}: {e}")
return False
def get_job_info(self, job_id: str) -> dict | None:
"""
获取任务信息
Args:
job_id: 任务ID
Returns:
任务信息字典,包含 name, next_run_time 等
"""
try:
job = self.scheduler.get_job(job_id)
if not job:
return None
next_run = job.next_run_time
return {
"id": job.id,
"name": job.name,
"next_run_time": next_run.isoformat() if next_run else None,
}
except Exception as e:
logger.warning(f"获取任务信息失败 {job_id}: {e}")
return None
@property
def is_running(self) -> bool:
"""调度器是否在运行"""
return self._started
# 便捷函数
def get_scheduler() -> TaskScheduler:
"""获取调度器单例"""
return TaskScheduler.get_instance()