本次提交包含多维度功能增强: 1. 新增TaskRecycleCleanupHandler,实现过期软删除任务物理清理能力 2. 扩展任务统计模型,新增死信任务计数并完善统计逻辑 3. 新增异常任务聚合查询接口,支持按死信/连续失败/长期未执行分类返回 4. 实现任务批量暂停/恢复/软删除操作 5. 扩展运行日志与任务查询过滤条件,新增handler_name维度 6. 优化健康检查逻辑,支持动态健康窗口并返回配置快照 7. 完善数据映射与DTO定义,补充缺失字段与类型支持
751 lines
25 KiB
Python
751 lines
25 KiB
Python
"""scheduler 限界上下文用例 DTO 定义。
|
||
|
||
包含:
|
||
- 任务管理 Input/Output DTO(CRUD / 暂停 / 恢复 / 手动触发)
|
||
- 执行日志 Input/Output DTO(列表查询)
|
||
- 可观测性 Output DTO(handler 摘要 / 状态计数 / 即将执行 / 健康检查)
|
||
- 运维恢复 Input/Output DTO(回收僵尸执行)
|
||
|
||
字段约束(``min_length`` / ``max_length`` / ``pattern`` / ``ge`` / ``le``)在 DTO 中
|
||
声明,由 Pydantic 在构造时校验。DateTime 字段在 Output DTO 中以 ISO 格式字符串
|
||
表达(``str | None``),由 Router 层在序列化时格式化。JSON 字段(如 ``payload``)
|
||
使用 ``dict[str, Any]``。
|
||
|
||
对齐 ``external_systems/use_cases/dto/system.py`` 写法。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from pydantic import BaseModel, ConfigDict, Field
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 任务管理 DTO
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class CreateTaskInput(BaseModel):
|
||
"""创建定时任务输入 DTO。
|
||
|
||
``schedule_kind`` 决定调度类型:``cron`` 必填 ``cron_expression``,
|
||
``at`` 必填 ``run_at``。``task_id`` 由系统生成(UUID4),不接受客户端指定。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
handler_name: str = Field(
|
||
...,
|
||
min_length=1,
|
||
max_length=128,
|
||
pattern=r"^[a-zA-Z_][a-zA-Z0-9_-]*$",
|
||
description="handler 标识,与 TaskHandler.name 引用一致",
|
||
)
|
||
owner_scope: str = Field(
|
||
...,
|
||
min_length=1,
|
||
max_length=64,
|
||
description="任务归属范围:system(平台级)/ business(业务方)",
|
||
)
|
||
owner_id: str = Field(
|
||
...,
|
||
min_length=1,
|
||
max_length=128,
|
||
description="业务方标识,格式如 channel_type:account_id",
|
||
)
|
||
schedule_kind: str = Field(
|
||
...,
|
||
pattern=r"^(cron|at)$",
|
||
description="调度类型:cron(周期)/ at(一次性)",
|
||
)
|
||
cron_expression: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="cron 表达式(标准 5 字段),schedule_kind=cron 时必填",
|
||
)
|
||
run_at: str | None = Field(
|
||
default=None,
|
||
description="一次性执行时间(ISO 格式字符串),schedule_kind=at 时必填且须为未来时间",
|
||
)
|
||
tz: str = Field(
|
||
default="Asia/Shanghai",
|
||
max_length=64,
|
||
description="时区标识,如 Asia/Shanghai",
|
||
)
|
||
payload: dict[str, Any] = Field(
|
||
default_factory=dict,
|
||
description="任务执行时传给 handler 的参数,序列化后不超过 64KB",
|
||
)
|
||
enabled: bool = Field(default=True, description="是否启用")
|
||
delete_after_run: bool = Field(
|
||
default=False,
|
||
description="一次性任务执行完成后是否自动删除(schedule_kind=at 时有意义)",
|
||
)
|
||
block_strategy: str = Field(
|
||
default="discard_later",
|
||
pattern=r"^(discard_later)$",
|
||
description="阻塞策略:discard_later(存在 running 执行时丢弃本次触发)",
|
||
)
|
||
created_by: str = Field(default="system", max_length=128)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|
||
|
||
|
||
class UpdateTaskInput(BaseModel):
|
||
"""更新定时任务输入 DTO。
|
||
|
||
允许更新字段(PRD §FR-ST-05):``cron_expression`` / ``run_at`` / ``tz`` /
|
||
``payload`` / ``enabled`` / ``delete_after_run`` / ``owner_scope`` / ``owner_id``。
|
||
``schedule_kind`` 与 ``handler_name`` 不可修改。修改 ``cron_expression`` 时
|
||
由用例层重新计算 ``next_run_at`` 与 ``stagger_seconds``。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(
|
||
...,
|
||
min_length=1,
|
||
max_length=64,
|
||
description="任务标识(UUID4)",
|
||
)
|
||
cron_expression: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="新的 cron 表达式,传入时重算 next_run_at 与 stagger_seconds",
|
||
)
|
||
run_at: str | None = Field(
|
||
default=None,
|
||
description="新的一次性执行时间(ISO 格式字符串)",
|
||
)
|
||
tz: str | None = Field(
|
||
default=None,
|
||
max_length=64,
|
||
description="新的时区标识",
|
||
)
|
||
payload: dict[str, Any] | None = Field(
|
||
default=None,
|
||
description="新的 payload,传入时整体替换",
|
||
)
|
||
enabled: bool | None = Field(default=None, description="是否启用")
|
||
delete_after_run: bool | None = Field(
|
||
default=None,
|
||
description="一次性任务执行完成后是否自动删除",
|
||
)
|
||
owner_scope: str | None = Field(
|
||
default=None,
|
||
min_length=1,
|
||
max_length=64,
|
||
)
|
||
owner_id: str | None = Field(
|
||
default=None,
|
||
min_length=1,
|
||
max_length=128,
|
||
)
|
||
updated_by: str | None = Field(default=None, max_length=128)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|
||
|
||
|
||
class GetTaskInput(BaseModel):
|
||
"""获取任务详情输入 DTO。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
|
||
|
||
class DeleteTaskInput(BaseModel):
|
||
"""删除任务输入 DTO(软删除)。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
updated_by: str | None = Field(default=None, max_length=128)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|
||
|
||
|
||
class PauseTaskInput(BaseModel):
|
||
"""暂停任务输入 DTO。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
updated_by: str | None = Field(default=None, max_length=128)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|
||
|
||
|
||
class ResumeTaskInput(BaseModel):
|
||
"""恢复任务输入 DTO。
|
||
|
||
对 ``status=paused`` 任务调用 ``pause`` 的逆操作;对 ``status=dead_letter``
|
||
任务重置 ``consecutive_errors`` / ``last_error`` 并恢复为 ``active``。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
updated_by: str | None = Field(default=None, max_length=128)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|
||
|
||
|
||
class TriggerTaskInput(BaseModel):
|
||
"""手动触发任务输入 DTO。
|
||
|
||
``triggered_by`` 标识触发来源(``auto`` / ``manual``),手动触发不入正常
|
||
``next_run_at`` 流,不影响下一次自动执行。死信任务允许手动触发用于验证
|
||
handler 是否已修复,但不重置 ``status``。``operator`` 为触发人 uid,透传到
|
||
worker 写入 ``run_log.created_by`` / ``updated_by`` 审计字段。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
triggered_by: str = Field(
|
||
default="manual",
|
||
pattern=r"^(auto|manual)$",
|
||
description="触发来源:auto(tick 自动)/ manual(管理员手动)",
|
||
)
|
||
payload: dict[str, Any] | None = Field(
|
||
default=None,
|
||
description="可选 payload 覆盖,None 时使用任务定义中的 payload",
|
||
)
|
||
operator: str = Field(
|
||
default="system",
|
||
max_length=128,
|
||
description="触发人 uid,写入 run_log 审计字段",
|
||
)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|
||
|
||
|
||
class TaskOutput(BaseModel):
|
||
"""任务输出 DTO。字段与 ``ScheduledTask`` dataclass 对齐。
|
||
|
||
DateTime 字段(``run_at`` / ``last_run_at`` / ``next_run_at`` / ``created_at``
|
||
/ ``updated_at`` / ``deleted_at``)以 ISO 格式字符串表达,由 Router 层格式化。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str
|
||
handler_name: str
|
||
owner_scope: str
|
||
owner_id: str
|
||
schedule_kind: str
|
||
cron_expression: str | None = None
|
||
run_at: str | None = None
|
||
tz: str = "Asia/Shanghai"
|
||
payload: dict[str, Any] = Field(default_factory=dict)
|
||
enabled: bool = True
|
||
delete_after_run: bool = False
|
||
block_strategy: str = "discard_later"
|
||
stagger_seconds: int | None = None
|
||
consecutive_errors: int = 0
|
||
status: str = "active"
|
||
last_run_at: str | None = None
|
||
next_run_at: str | None = None
|
||
last_error: str | None = None
|
||
created_by: str = "system"
|
||
updated_by: str | None = None
|
||
id: int | None = None
|
||
created_at: str | None = None
|
||
updated_at: str | None = None
|
||
is_deleted: int = 0
|
||
deleted_at: str | None = None
|
||
|
||
|
||
class TriggerTaskOutput(BaseModel):
|
||
"""手动触发任务输出 DTO。
|
||
|
||
返回本次执行标识 ``run_id`` 与任务快照 ``task``,便于调用方立即追踪
|
||
执行日志与结果。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
run_id: str = Field(..., description="本次执行唯一标识")
|
||
task: TaskOutput = Field(..., description="被触发任务的快照")
|
||
|
||
|
||
class ListTasksInput(BaseModel):
|
||
"""列出任务输入 DTO。
|
||
|
||
过滤字段对齐 PRD §FR-ST-05 列表 API 查询参数与仓储层 ``list`` 方法签名。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
page: int = Field(default=1, ge=1, description="页码,从 1 开始")
|
||
page_size: int = Field(default=20, ge=1, le=100, description="每页条数,最大 100")
|
||
owner_scope: str | None = Field(default=None, max_length=64)
|
||
owner_id: str | None = Field(default=None, max_length=128)
|
||
handler_name: str | None = Field(default=None, max_length=128)
|
||
keyword: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="关键词模糊搜索,匹配 task_id 或 handler_name",
|
||
)
|
||
enabled: bool | None = None
|
||
status: str | None = Field(
|
||
default=None,
|
||
pattern=r"^(active|paused|dead_letter)$",
|
||
description="任务状态过滤",
|
||
)
|
||
sort_by: str | None = Field(
|
||
default=None,
|
||
pattern=r"^(created_at|next_run_at|last_run_at|consecutive_errors|updated_at)$",
|
||
description="排序字段",
|
||
)
|
||
sort_order: str | None = Field(
|
||
default=None,
|
||
pattern=r"^(asc|desc)$",
|
||
description="排序方向,默认 desc",
|
||
)
|
||
|
||
|
||
class BatchTaskOperationOutput(BaseModel):
|
||
"""批量任务操作输出 DTO。
|
||
|
||
``success_count`` 为实际受影响行数,``skipped_count`` 为未匹配或状态
|
||
不允许的任务数(``task_ids`` 总数减去 ``success_count``)。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
success_count: int = Field(..., description="实际操作成功的任务数")
|
||
skipped_count: int = Field(..., description="未匹配或状态不允许的任务数")
|
||
failed: list[str] = Field(default_factory=list, description="未成功的 task_id 列表")
|
||
|
||
|
||
class ListTasksOutput(BaseModel):
|
||
"""列出任务输出 DTO。
|
||
|
||
``total_pages`` 为派生字段,由 ``total / page_size`` 上取整得到,
|
||
便于前端分页组件直接消费。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
items: list[TaskOutput] = Field(default_factory=list)
|
||
total: int = 0
|
||
page: int = 1
|
||
page_size: int = 20
|
||
total_pages: int = 1
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 执行日志 DTO
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ListRunLogsInput(BaseModel):
|
||
"""列出执行日志输入 DTO。
|
||
|
||
``task_id`` 必填,按任务维度查询执行明细。``status`` / ``start_date`` /
|
||
``end_date`` 为可选过滤字段,``start_date`` / ``end_date`` 为 ISO 格式字符串,
|
||
由用例层解析为 ``datetime`` 后传入仓储层。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
page: int = Field(default=1, ge=1)
|
||
page_size: int = Field(default=20, ge=1, le=100)
|
||
status: str | None = Field(
|
||
default=None,
|
||
pattern=r"^(running|success|failure|timeout|skipped)$",
|
||
description="执行状态过滤",
|
||
)
|
||
start_date: str | None = Field(
|
||
default=None,
|
||
description="起始时间(ISO 格式字符串),按 started_at 过滤",
|
||
)
|
||
end_date: str | None = Field(
|
||
default=None,
|
||
description="截止时间(ISO 格式字符串),按 started_at 过滤",
|
||
)
|
||
|
||
|
||
class GetRunLogInput(BaseModel):
|
||
"""获取执行日志详情输入 DTO。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
run_id: str = Field(..., min_length=1, max_length=64)
|
||
|
||
|
||
class RunLogOutput(BaseModel):
|
||
"""执行日志输出 DTO。字段与 ``ScheduledTaskRunLog`` dataclass 对齐。
|
||
|
||
DateTime 字段(``started_at`` / ``finished_at`` / ``created_at`` /
|
||
``updated_at`` / ``deleted_at``)以 ISO 格式字符串表达。
|
||
``duration_seconds`` 为派生字段,由 mapper 计算 ``finished_at - started_at``
|
||
得到,``finished_at`` 为 None 时(running 状态)返回 None。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str
|
||
handler_name: str | None = None
|
||
run_id: str
|
||
triggered_by: str = "auto"
|
||
status: str = "running"
|
||
error_message: str | None = None
|
||
output: dict[str, Any] | None = None
|
||
started_at: str | None = None
|
||
finished_at: str | None = None
|
||
duration_seconds: float | None = None
|
||
created_by: str = "system"
|
||
updated_by: str | None = None
|
||
id: int | None = None
|
||
created_at: str | None = None
|
||
updated_at: str | None = None
|
||
is_deleted: int = 0
|
||
deleted_at: str | None = None
|
||
|
||
|
||
class ListRunLogsOutput(BaseModel):
|
||
"""列出执行日志输出 DTO。
|
||
|
||
``total_pages`` 为派生字段,由 ``total / page_size`` 上取整得到。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
items: list[RunLogOutput] = Field(default_factory=list)
|
||
total: int = 0
|
||
page: int = 1
|
||
page_size: int = 20
|
||
total_pages: int = 1
|
||
|
||
|
||
class ListAllRunLogsInput(BaseModel):
|
||
"""跨任务执行日志查询输入 DTO。
|
||
|
||
``task_id`` / ``status`` / ``handler_name`` / 时间范围均可选,
|
||
但至少需指定 ``task_id``、``status`` 或时间范围之一(由用例层校验),
|
||
避免无过滤的全表扫描。``start_date`` / ``end_date`` 为 ISO 格式字符串,
|
||
由用例层解析为 ``datetime`` 后传入仓储层。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
page: int = Field(default=1, ge=1)
|
||
page_size: int = Field(default=20, ge=1, le=100)
|
||
status: str | None = Field(
|
||
default=None,
|
||
pattern=r"^(running|success|failure|timeout|skipped)$",
|
||
description="执行状态过滤",
|
||
)
|
||
handler_name: str | None = Field(
|
||
default=None,
|
||
min_length=1,
|
||
max_length=128,
|
||
description="handler 名称过滤(跨任务按 handler 维度查询)",
|
||
)
|
||
start_date: str | None = Field(
|
||
default=None,
|
||
description="起始时间(ISO 格式字符串),按 started_at 过滤",
|
||
)
|
||
end_date: str | None = Field(
|
||
default=None,
|
||
description="截止时间(ISO 格式字符串),按 started_at 过滤",
|
||
)
|
||
task_id: str | None = Field(default=None, min_length=1, max_length=64)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 可观测性 DTO
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class HandlerSummaryOutput(BaseModel):
|
||
"""handler 聚合摘要输出 DTO。
|
||
|
||
对齐 PRD §FR-ST-05 ``GET /handlers`` 端点响应字段,走 DB 聚合
|
||
(``task_repo.list_handler_summary``),不查内存注册表。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
handler_name: str
|
||
task_count: int = 0
|
||
active_task_count: int = 0
|
||
dead_letter_count: int = 0
|
||
last_active_at: str | None = None
|
||
|
||
|
||
class ListHandlerSummaryOutput(BaseModel):
|
||
"""handler 聚合摘要列表输出 DTO。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
items: list[HandlerSummaryOutput] = Field(default_factory=list)
|
||
|
||
|
||
class CountByStatusInput(BaseModel):
|
||
"""任务状态计数输入 DTO。
|
||
|
||
过滤字段与 ``ListTasksInput`` 对齐,便于仪表盘按 owner/handler 下钻。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
owner_scope: str | None = Field(default=None, max_length=64)
|
||
owner_id: str | None = Field(default=None, max_length=128)
|
||
handler_name: str | None = Field(default=None, max_length=128)
|
||
|
||
|
||
class CountByStatusOutput(BaseModel):
|
||
"""任务状态计数输出 DTO。
|
||
|
||
对齐 PRD §FR-ST-07 健康检查与指标需求,按 ``status`` 维度统计任务数。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
active: int = 0
|
||
paused: int = 0
|
||
dead_letter: int = 0
|
||
|
||
|
||
class ListUpcomingInput(BaseModel):
|
||
"""即将执行任务查询输入 DTO。
|
||
|
||
对齐 PRD §FR-ST-09 统计查询,按 ``next_run_at`` 升序返回即将执行的任务列表。
|
||
``hours_ahead`` 控制预览窗口(默认 24 小时,最大 7 天)。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
limit: int = Field(default=100, ge=1, le=1000, description="返回条数上限")
|
||
handler_name: str | None = Field(default=None, max_length=128)
|
||
hours_ahead: int = Field(
|
||
default=24,
|
||
ge=1,
|
||
le=168,
|
||
description="预览窗口小时数(默认 24,最大 168=7 天)",
|
||
)
|
||
|
||
|
||
class ListUpcomingOutput(BaseModel):
|
||
"""即将执行任务列表输出 DTO。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
items: list[TaskOutput] = Field(default_factory=list)
|
||
|
||
|
||
class ListAnomaliesOutput(BaseModel):
|
||
"""工作台异常任务聚合输出 DTO。
|
||
|
||
聚合三类异常任务供工作台一次性拉取,避免前端依赖活跃任务列表前 N 条
|
||
做前端过滤导致的漏报问题:
|
||
- ``dead_letter``:死信任务(status=dead_letter,按 updated_at 降序)
|
||
- ``consecutive_failure``:连续失败任务(consecutive_errors >= 3,
|
||
非 dead_letter,按 consecutive_errors 降序)
|
||
- ``stale``:长期未执行任务(active 状态,last_run_at 早于 7 天前,
|
||
按 last_run_at 升序)
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
dead_letter: list[TaskOutput] = Field(default_factory=list)
|
||
consecutive_failure: list[TaskOutput] = Field(default_factory=list)
|
||
stale: list[TaskOutput] = Field(default_factory=list)
|
||
|
||
|
||
class SchedulerConfigOutput(BaseModel):
|
||
"""调度器配置快照(只读),用于运维中心展示当前生效配置。
|
||
|
||
字段值由 ``scheduler_service.get_health`` 从 ``app_config`` 读取并填充,
|
||
确保前端展示与后端实际配置一致,避免硬编码默认值导致的信息不同步。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
enabled: bool = Field(description="定时任务总开关")
|
||
tick_interval_seconds: int = Field(description="tick 频率(秒)")
|
||
task_timeout_seconds: int = Field(description="单任务超时(秒)")
|
||
max_consecutive_errors: int = Field(description="死信阈值(连续失败次数)")
|
||
backoff_schedule: list[int] = Field(description="指数退避序列(秒)")
|
||
stagger_max_seconds: int = Field(description="整点抖动上限(秒)")
|
||
run_log_retention_days: int = Field(description="run_logs 保留天数")
|
||
idempotency_retention_hours: int = Field(description="幂等记录保留小时数")
|
||
min_cron_interval_minutes: int = Field(description="cron 最小间隔(分钟)")
|
||
|
||
|
||
class GetHealthOutput(BaseModel):
|
||
"""调度器健康检查输出 DTO。
|
||
|
||
对齐 PRD §FR-ST-07 健康检查端点响应:``status`` 表示 worker 活跃度健康
|
||
判断结果;``last_tick_at`` 为最近一次任务活动时间(不限状态);``config``
|
||
为当前生效的调度器配置快照。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
status: str = Field(
|
||
default="healthy",
|
||
pattern=r"^(healthy|unhealthy)$",
|
||
description="健康状态:healthy / unhealthy",
|
||
)
|
||
last_tick_at: str | None = Field(default=None, description="最近一次任务活动时间(ISO 格式字符串)")
|
||
active_task_count: int = 0
|
||
dead_letter_count: int = 0
|
||
running_count: int = 0
|
||
config: SchedulerConfigOutput = Field(description="当前生效的调度器配置快照")
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 日聚合统计 DTO
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ListDailyStatsInput(BaseModel):
|
||
"""日聚合统计查询输入 DTO。
|
||
|
||
``start_date`` / ``end_date`` 为 ISO 日期字符串(``YYYY-MM-DD``),由用例层
|
||
解析为 ``date`` 后传入仓储层。日期范围上限 90 天。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
start_date: str = Field(..., description="ISO 日期 YYYY-MM-DD")
|
||
end_date: str = Field(..., description="ISO 日期 YYYY-MM-DD")
|
||
handler_name: str | None = Field(default=None, max_length=128)
|
||
|
||
|
||
class DailyStatOutput(BaseModel):
|
||
"""日聚合统计输出 DTO。字段对齐 ``DailyStat`` dataclass,补充派生字段。
|
||
|
||
``total_count`` / ``success_rate`` 为派生字段,由 mapper 计算:
|
||
``total_count = success_count + failure_count + timeout_count + dead_letter_count``
|
||
(dead_letter 视为失败终态,纳入分母),
|
||
``success_rate = success_count / total_count if total_count > 0 else 0.0``。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
stat_date: str
|
||
handler_name: str
|
||
success_count: int = 0
|
||
failure_count: int = 0
|
||
timeout_count: int = 0
|
||
dead_letter_count: int = 0
|
||
total_count: int = 0
|
||
success_rate: float = 0.0
|
||
|
||
|
||
class ListDailyStatsOutput(BaseModel):
|
||
"""日聚合统计列表输出 DTO。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
items: list[DailyStatOutput] = Field(default_factory=list)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 运维恢复 DTO
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ReclaimStaleRunsInput(BaseModel):
|
||
"""回收僵尸执行输入 DTO。
|
||
|
||
用于回收 ``status=running`` 但实际已超时的执行记录,将其标记为 ``timeout``
|
||
并写入回收错误信息。``timeout_seconds`` 为可选参数,未传入时由用例层从配置
|
||
取默认值(``scheduler_task_timeout_seconds``)。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
timeout_seconds: int | None = Field(
|
||
default=None,
|
||
ge=1,
|
||
description="执行超时阈值(秒),None 时从配置取默认值",
|
||
)
|
||
|
||
|
||
class ReclaimStaleRunsOutput(BaseModel):
|
||
"""回收僵尸执行输出 DTO。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
reclaimed_count: int = 0
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 任务回收站 DTO
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class ListDeletedTasksInput(BaseModel):
|
||
"""列出已删除任务(回收站)输入 DTO。
|
||
|
||
``start_date`` / ``end_date`` 为 ISO 格式字符串,由用例层解析为 ``datetime``
|
||
后按 ``deleted_at`` 过滤。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
page: int = Field(default=1, ge=1)
|
||
page_size: int = Field(default=20, ge=1, le=100)
|
||
start_date: str | None = Field(
|
||
default=None,
|
||
description="起始时间(ISO 格式字符串),按 deleted_at 过滤",
|
||
)
|
||
end_date: str | None = Field(
|
||
default=None,
|
||
description="截止时间(ISO 格式字符串),按 deleted_at 过滤",
|
||
)
|
||
|
||
|
||
class RestoreTaskInput(BaseModel):
|
||
"""恢复已删除任务输入 DTO。支持幂等键以避免恢复/硬删除重复执行。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
updated_by: str | None = Field(default=None, max_length=128)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|
||
|
||
|
||
class HardDeleteTaskInput(BaseModel):
|
||
"""硬删除任务输入 DTO。支持幂等键以避免重复物理删除。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
task_id: str = Field(..., min_length=1, max_length=64)
|
||
updated_by: str | None = Field(default=None, max_length=128)
|
||
idempotency_key: str | None = Field(
|
||
default=None,
|
||
max_length=128,
|
||
description="客户端传入的 Idempotency-Key,None 时不启用幂等保护",
|
||
)
|