本次提交对调度器进行了多维度优化: 1. 扩展HandlerSummary模型,新增active_task_count字段并完善聚合逻辑 2. 重构手动触发任务接口,新增TriggerTaskOutput返回run_id与任务快照 3. 为幂等仓储添加operation参数隔离不同操作的缓存,修复幂等校验冲突问题 4. 为任务统计与列表接口添加分页总页数计算,完善回收站分页支持 5. 新增任务按handler、owner过滤的统计能力,优化更新任务的字段校验与重算逻辑 6. 为删除、恢复、硬删除操作添加幂等保护,完善操作审计与错误处理
136 lines
4.8 KiB
Python
136 lines
4.8 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,
|
||
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`` 在仓储层聚合
|
||
时提供,缺失时默认 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,
|
||
last_active_at=row.last_active_at,
|
||
)
|