本次提交包含多维度功能增强: 1. 新增TaskRecycleCleanupHandler,实现过期软删除任务物理清理能力 2. 扩展任务统计模型,新增死信任务计数并完善统计逻辑 3. 新增异常任务聚合查询接口,支持按死信/连续失败/长期未执行分类返回 4. 实现任务批量暂停/恢复/软删除操作 5. 扩展运行日志与任务查询过滤条件,新增handler_name维度 6. 优化健康检查逻辑,支持动态健康窗口并返回配置快照 7. 完善数据映射与DTO定义,补充缺失字段与类型支持
138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
"""ORM ↔ dataclass 映射边界。
|
||
|
||
本模块是 ``adapters/persistence`` 层负责 ORM 实例 ↔ core dataclass 转换的位置。
|
||
scheduler 限界上下文无敏感字段,mapper 仅做字段映射,不调用 ``encrypt`` /
|
||
``decrypt``(与 ``external_systems/adapters/persistence/mappers.py`` 不同)。
|
||
|
||
设计原则(见设计方案 §10):
|
||
|
||
- **mapper 不调用 ORM 的 ``to_dict`` 方法**:直接读 ORM 字段构造 dataclass。
|
||
原因:``to_dict`` 返回的 dict 结构与 dataclass 字段不完全一致(如 DateTime
|
||
字段在 ``to_dict`` 中被 ``format_utc_datetime`` 格式化为字符串)。
|
||
- **无敏感字段**:scheduler 域所有字段均为明文存储,无需加解密。
|
||
- **处理 None 入参**:ORM 实例为 None 时返回 None,避免调用方需额外判空。
|
||
- **仓储层 dataclass → core dataclass**:``DailyStat`` / ``HandlerSummary`` 在
|
||
仓储层与 core 层均有同名 dataclass,mapper 按字段名转换。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import TYPE_CHECKING, Any
|
||
|
||
from yuxi.scheduler.core.models import (
|
||
DailyStat as DailyStatDC,
|
||
)
|
||
from yuxi.scheduler.core.models import (
|
||
HandlerSummary as HandlerSummaryDC,
|
||
)
|
||
from yuxi.scheduler.core.models import (
|
||
ScheduledTask as ScheduledTaskDC,
|
||
)
|
||
from yuxi.scheduler.core.models import (
|
||
ScheduledTaskRunLog as ScheduledTaskRunLogDC,
|
||
)
|
||
|
||
if TYPE_CHECKING:
|
||
from yuxi.storage.postgres.models_scheduler import (
|
||
ScheduledTask as ScheduledTaskORM,
|
||
)
|
||
from yuxi.storage.postgres.models_scheduler import (
|
||
ScheduledTaskRunLog as ScheduledTaskRunLogORM,
|
||
)
|
||
|
||
|
||
def orm_to_task(orm: ScheduledTaskORM | None) -> ScheduledTaskDC | None:
|
||
"""ORM → dataclass,``ScheduledTask`` 字段一一映射。
|
||
|
||
DateTime 字段直接赋值(core 用 ``Any``),JSON 字段直接赋值。
|
||
入参为 None 时返回 None,避免调用方需额外判空。
|
||
"""
|
||
if orm is None:
|
||
return None
|
||
return ScheduledTaskDC(
|
||
id=orm.id,
|
||
task_id=orm.task_id,
|
||
handler_name=orm.handler_name,
|
||
owner_scope=orm.owner_scope,
|
||
owner_id=orm.owner_id,
|
||
schedule_kind=orm.schedule_kind,
|
||
cron_expression=orm.cron_expression,
|
||
run_at=orm.run_at,
|
||
tz=orm.tz,
|
||
payload=orm.payload or {},
|
||
enabled=orm.enabled,
|
||
delete_after_run=orm.delete_after_run,
|
||
block_strategy=orm.block_strategy,
|
||
stagger_seconds=orm.stagger_seconds,
|
||
consecutive_errors=orm.consecutive_errors,
|
||
status=orm.status,
|
||
last_run_at=orm.last_run_at,
|
||
next_run_at=orm.next_run_at,
|
||
last_error=orm.last_error,
|
||
created_by=orm.created_by,
|
||
updated_by=orm.updated_by,
|
||
created_at=orm.created_at,
|
||
updated_at=orm.updated_at,
|
||
is_deleted=orm.is_deleted,
|
||
deleted_at=orm.deleted_at,
|
||
)
|
||
|
||
|
||
def orm_to_run_log(orm: ScheduledTaskRunLogORM | None) -> ScheduledTaskRunLogDC | None:
|
||
"""ORM → dataclass,``ScheduledTaskRunLog`` 字段一一映射。
|
||
|
||
入参为 None 时返回 None,避免调用方需额外判空。
|
||
"""
|
||
if orm is None:
|
||
return None
|
||
return ScheduledTaskRunLogDC(
|
||
id=orm.id,
|
||
task_id=orm.task_id,
|
||
handler_name=orm.handler_name,
|
||
run_id=orm.run_id,
|
||
triggered_by=orm.triggered_by,
|
||
status=orm.status,
|
||
error_message=orm.error_message,
|
||
output=orm.output,
|
||
started_at=orm.started_at,
|
||
finished_at=orm.finished_at,
|
||
created_by=orm.created_by,
|
||
updated_by=orm.updated_by,
|
||
created_at=orm.created_at,
|
||
updated_at=orm.updated_at,
|
||
is_deleted=orm.is_deleted,
|
||
deleted_at=orm.deleted_at,
|
||
)
|
||
|
||
|
||
def daily_stat_row_to_dataclass(row: Any) -> DailyStatDC:
|
||
"""仓储层 ``DailyStat`` → core ``DailyStat`` dataclass,按字段名映射。
|
||
|
||
仓储层 ``DailyStat`` 是 dataclass,row 可能是 NamedTuple 或 dataclass 实例,
|
||
统一按字段名读取并转换为 core dataclass。
|
||
"""
|
||
return DailyStatDC(
|
||
stat_date=row.stat_date,
|
||
handler_name=row.handler_name,
|
||
success_count=int(row.success_count),
|
||
failure_count=int(row.failure_count),
|
||
timeout_count=int(row.timeout_count),
|
||
dead_letter_count=int(row.dead_letter_count),
|
||
)
|
||
|
||
|
||
def handler_summary_row_to_dataclass(row: Any) -> HandlerSummaryDC:
|
||
"""仓储层 ``HandlerSummary`` → core ``HandlerSummary`` dataclass,按字段名映射。
|
||
|
||
仓储层 ``HandlerSummary`` 是 dataclass,row 可能是 NamedTuple 或 dataclass 实例,
|
||
统一按字段名读取并转换为 core dataclass。``active_task_count`` /
|
||
``dead_letter_count`` 在仓储层聚合时提供,缺失时默认 0。
|
||
"""
|
||
return HandlerSummaryDC(
|
||
name=row.name,
|
||
task_count=int(row.task_count),
|
||
active_task_count=int(row.active_task_count) if hasattr(row, "active_task_count") else 0,
|
||
dead_letter_count=int(row.dead_letter_count) if hasattr(row, "dead_letter_count") else 0,
|
||
last_active_at=row.last_active_at,
|
||
)
|