ForcePilot/backend/package/yuxi/scheduler/adapters/persistence/mappers.py
Kris 36d0add930 feat: 新增回收站任务清理handler,完善任务统计与批量操作能力
本次提交包含多维度功能增强:
1. 新增TaskRecycleCleanupHandler,实现过期软删除任务物理清理能力
2. 扩展任务统计模型,新增死信任务计数并完善统计逻辑
3. 新增异常任务聚合查询接口,支持按死信/连续失败/长期未执行分类返回
4. 实现任务批量暂停/恢复/软删除操作
5. 扩展运行日志与任务查询过滤条件,新增handler_name维度
6. 优化健康检查逻辑,支持动态健康窗口并返回配置快照
7. 完善数据映射与DTO定义,补充缺失字段与类型支持
2026-07-11 21:53:56 +08:00

138 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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 层均有同名 dataclassmapper 按字段名转换。
"""
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`` 是 dataclassrow 可能是 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`` 是 dataclassrow 可能是 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,
)