改 相对路径 完成客户端

This commit is contained in:
Neo
2026-01-27 22:14:01 +08:00
parent 04c064793a
commit 9f8976e75a
292 changed files with 307062 additions and 678 deletions

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
"""数据模型模块"""
from .task_model import TaskItem, TaskStatus, TaskHistory, TaskConfig, QueuedTask
from .schedule_model import (
ScheduledTask, ScheduleConfig, ScheduleType, IntervalUnit, ScheduleStore
)
__all__ = [
"TaskItem",
"TaskStatus",
"TaskHistory",
"TaskConfig",
"QueuedTask",
"ScheduledTask",
"ScheduleConfig",
"ScheduleType",
"IntervalUnit",
"ScheduleStore",
]

View File

@@ -97,8 +97,14 @@ class ScheduleConfig:
return f"Cron: {self.cron_expression}"
return "未知"
# 首次执行延迟秒数
FIRST_RUN_DELAY_SECONDS = 60
def get_next_run_time(self, last_run: Optional[datetime] = None) -> Optional[datetime]:
"""计算下次运行时间"""
"""计算下次运行时间
注意首次执行last_run 为 None时会延迟 60 秒,避免创建后立即执行
"""
now = datetime.now()
# 检查日期范围
@@ -112,12 +118,15 @@ class ScheduleConfig:
if now >= end:
return None
# 首次执行延迟 60 秒
first_run_time = now + timedelta(seconds=self.FIRST_RUN_DELAY_SECONDS)
if self.schedule_type == ScheduleType.ONCE:
return None if last_run else now
return None if last_run else first_run_time
elif self.schedule_type == ScheduleType.INTERVAL:
if not last_run:
return now
return first_run_time
if self.interval_unit == IntervalUnit.MINUTES:
delta = timedelta(minutes=self.interval_value)
elif self.interval_unit == IntervalUnit.HOURS:
@@ -177,6 +186,47 @@ class ScheduleConfig:
return None
@dataclass
class ScheduleExecutionRecord:
"""调度执行记录"""
task_id: str # 关联的 QueuedTask ID
executed_at: datetime # 执行时间
status: str = "" # 状态success, failed, pending
exit_code: Optional[int] = None # 退出码
duration_seconds: float = 0.0 # 耗时(秒)
summary: str = "" # 执行摘要
output: str = "" # 完整执行日志
error: str = "" # 错误信息
# 日志最大长度限制(字符数)
MAX_OUTPUT_LENGTH: int = 100000 # 100KB
def to_dict(self) -> dict:
return {
"task_id": self.task_id,
"executed_at": self.executed_at.isoformat(),
"status": self.status,
"exit_code": self.exit_code,
"duration_seconds": self.duration_seconds,
"summary": self.summary,
"output": self.output[:self.MAX_OUTPUT_LENGTH] if self.output else "",
"error": self.error[:5000] if self.error else "",
}
@classmethod
def from_dict(cls, data: dict) -> "ScheduleExecutionRecord":
return cls(
task_id=data.get("task_id", ""),
executed_at=datetime.fromisoformat(data["executed_at"]) if data.get("executed_at") else datetime.now(),
status=data.get("status", ""),
exit_code=data.get("exit_code"),
duration_seconds=data.get("duration_seconds", 0.0),
summary=data.get("summary", ""),
output=data.get("output", ""),
error=data.get("error", ""),
)
@dataclass
class ScheduledTask:
"""调度任务"""
@@ -193,9 +243,33 @@ class ScheduledTask:
run_count: int = 0
last_status: str = ""
# 执行历史(最近 N 次执行记录)
execution_history: List[ScheduleExecutionRecord] = field(default_factory=list)
MAX_HISTORY_SIZE: int = field(default=50, repr=False) # 保留最近50次执行记录
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
def add_execution_record(self, record: ScheduleExecutionRecord):
"""添加执行记录"""
self.execution_history.insert(0, record)
# 限制历史记录数量
if len(self.execution_history) > self.MAX_HISTORY_SIZE:
self.execution_history = self.execution_history[:self.MAX_HISTORY_SIZE]
def update_execution_record(self, task_id: str, status: str, exit_code: int, duration: float,
summary: str, output: str = "", error: str = ""):
"""更新执行记录状态"""
for record in self.execution_history:
if record.task_id == task_id:
record.status = status
record.exit_code = exit_code
record.duration_seconds = duration
record.summary = summary
record.output = output
record.error = error
break
def to_dict(self) -> dict:
"""转换为字典"""
return {
@@ -209,6 +283,7 @@ class ScheduledTask:
"next_run": self.next_run.isoformat() if self.next_run else None,
"run_count": self.run_count,
"last_status": self.last_status,
"execution_history": [r.to_dict() for r in self.execution_history],
"created_at": self.created_at.isoformat(),
"updated_at": self.updated_at.isoformat(),
}
@@ -216,6 +291,9 @@ class ScheduledTask:
@classmethod
def from_dict(cls, data: dict) -> "ScheduledTask":
"""从字典创建"""
history_data = data.get("execution_history", [])
execution_history = [ScheduleExecutionRecord.from_dict(r) for r in history_data]
return cls(
id=data["id"],
name=data["name"],
@@ -227,6 +305,7 @@ class ScheduledTask:
next_run=datetime.fromisoformat(data["next_run"]) if data.get("next_run") else None,
run_count=data.get("run_count", 0),
last_status=data.get("last_status", ""),
execution_history=execution_history,
created_at=datetime.fromisoformat(data["created_at"]) if data.get("created_at") else datetime.now(),
updated_at=datetime.fromisoformat(data["updated_at"]) if data.get("updated_at") else datetime.now(),
)