feat: 定义任务模型与 SQLite Repository (#8)
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user