ForcePilot/backend/package/yuxi/scheduler/adapters/persistence/mappers.py
Kris e9d2486e20 feat(scheduler): 新增活跃任务数、手动触发返回run_id等多项功能增强
本次提交对调度器进行了多维度优化:
1. 扩展HandlerSummary模型,新增active_task_count字段并完善聚合逻辑
2. 重构手动触发任务接口,新增TriggerTaskOutput返回run_id与任务快照
3. 为幂等仓储添加operation参数隔离不同操作的缓存,修复幂等校验冲突问题
4. 为任务统计与列表接口添加分页总页数计算,完善回收站分页支持
5. 新增任务按handler、owner过滤的统计能力,优化更新任务的字段校验与重算逻辑
6. 为删除、恢复、硬删除操作添加幂等保护,完善操作审计与错误处理
2026-07-11 07:38:59 +08:00

136 lines
4.8 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,
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`` 在仓储层聚合
时提供,缺失时默认 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,
)