feat: 定义任务模型与 SQLite Repository (#8)

This commit is contained in:
chengma
2026-08-06 16:21:39 +08:00
parent d64ebef922
commit e60b0c91ac
5 changed files with 770 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
"""Client 任务数据库使用的枚举和简单数据对象。
本文件不依赖 Qt、Admin 客户端或 uiautomator2,只表达数据含义。
"""
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, Mapping, Optional
class TaskType(str, Enum):
"""PDD 任务类型。"""
COLLECT = "collect"
PURCHASE = "purchase"
class TaskStatus(str, Enum):
"""PDD 任务本地状态。"""
CLAIMED = "claimed"
RUNNING = "running"
RESULT_PENDING = "result_pending"
RETRY_WAIT = "retry_wait"
MANUAL_REVIEW = "manual_review"
SUCCEEDED = "succeeded"
FAILED = "failed"
CANCELLED = "cancelled"
class RunStatus(str, Enum):
"""单次任务执行状态。"""
RUNNING = "running"
SUCCEEDED = "succeeded"
FAILED = "failed"
CANCELLED = "cancelled"
MANUAL_REVIEW = "manual_review"
class OutboxEventType(str, Enum):
"""等待提交 Admin 的事件类型。"""
COLLECT_RESULT = "collect_result"
PURCHASE_RESULT = "purchase_result"
TASK_FAILURE = "task_failure"
class OutboxStatus(str, Enum):
"""Outbox 事件发送状态。"""
PENDING = "pending"
SENDING = "sending"
SENT = "sent"
FAILED = "failed"
@dataclass(frozen=True)
class NewClaimedTask:
"""刚从 Admin 领取、准备写入本地数据库的任务。"""
remote_task_id: str
task_type: TaskType
goods_url: str
goods_id: Optional[str] = None
title: Optional[str] = None
target_color: Optional[str] = None
target_size: Optional[str] = None
price_cent: Optional[int] = None
quantity: Optional[int] = None
priority: int = 0
version: int = 1
admin_payload: Mapping[str, Any] = field(default_factory=dict)
def __post_init__(self) -> None:
if not isinstance(self.task_type, TaskType):
raise ValueError("task_type 必须是 TaskType")
if not self.remote_task_id.strip():
raise ValueError("remote_task_id 不能为空")
if not self.goods_url.strip():
raise ValueError("goods_url 不能为空")
if self.price_cent is not None and self.price_cent < 0:
raise ValueError("price_cent 不能小于 0")
if self.quantity is not None and self.quantity <= 0:
raise ValueError("quantity 必须大于 0")
if self.version <= 0:
raise ValueError("version 必须大于 0")
@dataclass(frozen=True)
class TaskFilters:
"""本地任务列表的可选筛选条件。"""
task_type: Optional[TaskType] = None
status: Optional[TaskStatus] = None
keyword: str = ""
@dataclass(frozen=True)
class TaskSummary:
"""任务表格使用的轻量数据,不包含完整 JSON。"""
id: int
remote_task_id: str
task_type: TaskType
goods_id: Optional[str]
title: Optional[str]
target_color: Optional[str]
target_size: Optional[str]
price_cent: Optional[int]
quantity: Optional[int]
status: TaskStatus
updated_at: str
@dataclass(frozen=True)
class TaskDetail:
"""一条 PDD 任务的完整本地记录。"""
id: int
remote_task_id: str
task_type: TaskType
goods_id: Optional[str]
goods_url: str
title: Optional[str]
target_color: Optional[str]
target_size: Optional[str]
price_cent: Optional[int]
quantity: Optional[int]
status: TaskStatus
current_step: Optional[str]
priority: int
version: int
admin_payload: Dict[str, Any]
pdd_data: Optional[Dict[str, Any]]
retry_count: int
last_error_code: Optional[str]
last_error_message: Optional[str]
received_at: str
started_at: Optional[str]
finished_at: Optional[str]
created_at: str
updated_at: str
@dataclass(frozen=True)
class TaskRunRecord:
"""一次任务执行的数据库记录。"""
id: int
task_id: int
attempt_id: str
attempt_no: int
device_address: str
run_status: RunStatus
current_step: Optional[str]
started_at: str
finished_at: Optional[str]
irreversible_action_at: Optional[str]
order_submitted_at: Optional[str]
error_code: Optional[str]
error_message: Optional[str]
diagnostics_json: Optional[Dict[str, Any]]
artifact_directory: Optional[str]
created_at: str
updated_at: str
@dataclass(frozen=True)
class OutboxEventRecord:
"""一条等待提交 Admin 的可靠事件记录。"""
id: int
task_id: int
event_type: OutboxEventType
idempotency_key: str
payload_json: Dict[str, Any]
status: OutboxStatus
attempt_count: int
next_retry_at: Optional[str]
last_error: Optional[str]
created_at: str
updated_at: str
sent_at: Optional[str]
@dataclass(frozen=True)
class AppSettingRecord:
"""一条非敏感应用设置。"""
setting_key: str
value: Any
updated_at: str