feat: 实现受控真实下单安全边界 (#99)

This commit is contained in:
chengma
2026-08-10 16:35:38 +08:00
parent 7106f6b024
commit e960cab1fb
30 changed files with 1562 additions and 66 deletions
+55 -12
View File
@@ -19,6 +19,7 @@ from .android_device_service import (
AndroidDeviceService,
)
from .purchase_task_service import (
LivePurchaseAdapterFactory,
PurchaseAdapterFactory,
PurchaseTaskService,
)
@@ -95,6 +96,7 @@ def admin_task_to_new_claimed_task(task: AdminTask) -> NewClaimedTask:
original_task = {
"id": task.task_id,
"type": task.task_type.value,
"execution_mode": task.execution_mode,
"version": task.version,
"priority": task.priority,
"payload": payload,
@@ -105,6 +107,7 @@ def admin_task_to_new_claimed_task(task: AdminTask) -> NewClaimedTask:
remote_task_id=task.task_id,
task_type=task.task_type,
goods_url=goods_url.strip(),
execution_mode=task.execution_mode,
goods_id=goods_id.strip() if isinstance(goods_id, str) else None,
target_color=target_color,
target_size=target_size,
@@ -128,6 +131,10 @@ class TaskDispatcher:
*,
collect_service_factory: Optional[CollectServiceFactory] = None,
purchase_adapter_factory: Optional[PurchaseAdapterFactory] = None,
live_purchase_adapter_factory: Optional[
LivePurchaseAdapterFactory
] = None,
purchase_mode: str = "dry_run",
purchase_reconcile_factory: Optional[PurchaseReconcileFactory] = None,
cancelled: Callable[[], bool] = lambda: False,
device_connection_checker: Optional[Callable[[str], None]] = None,
@@ -138,6 +145,10 @@ class TaskDispatcher:
self._device_address = str(device_address or "").strip()
self._collect_factory = collect_service_factory
self._purchase_factory = purchase_adapter_factory
self._live_purchase_factory = live_purchase_adapter_factory
if purchase_mode not in {"dry_run", "live"}:
raise ValueError("purchase_mode 只允许 dry_run 或 live")
self._purchase_mode = purchase_mode
self._reconcile_factory = purchase_reconcile_factory
self._cancelled = cancelled
self._device_connection_checker = (
@@ -167,24 +178,38 @@ class TaskDispatcher:
return ClaimCapabilities(
device=device,
supported_types=tuple(supported),
purchase_mode="dry_run",
purchase_mode=(
"live"
if self.purchase_ready
and self._purchase_mode == "live"
and self._live_purchase_factory is not None
else "dry_run"
),
schema_versions=(1,),
)
def execute_one(self) -> TaskDispatchOutcome:
"""严格按 Outbox、本地任务、Admin 新任务的顺序处理。"""
"""不可逆采购优先核单,其余按 Outbox、本地任务、Admin 顺序处理。"""
pending = self._repository.next_pending_outbox()
if pending is not None:
return self._submit_pending(pending)
if not self._device_address:
raise AndroidDeviceSearchError(
"请先在设置页选择并保存 Android 设备"
unresolved_reader = getattr(
self._repository, "unresolved_irreversible_purchase", None
)
unresolved = (
unresolved_reader() if unresolved_reader is not None else None
)
if unresolved is not None:
raise RuntimeError(
f"任务 {unresolved.remote_task_id} 已进入不可逆阶段但尚未转入核单;"
"已停止所有采购,绝不重新下单"
)
self._device_connection_checker(self._device_address)
reconcile_task = self._repository.next_purchase_reconcile_task()
if reconcile_task is not None:
if not self._device_address:
raise AndroidDeviceSearchError(
"存在只允许核对的采购任务;请先连接并保存原 Android 设备"
)
self._device_connection_checker(self._device_address)
if self._reconcile_factory is None:
raise RuntimeError(
f"任务 {reconcile_task.remote_task_id} 只允许核对订单,"
@@ -200,6 +225,15 @@ class TaskDispatcher:
outcome.kind, outcome.message, outcome.task_id
)
pending = self._repository.next_pending_outbox()
if pending is not None:
return self._submit_pending(pending)
if not self._device_address:
raise AndroidDeviceSearchError(
"请先在设置页选择并保存 Android 设备"
)
self._device_connection_checker(self._device_address)
if not self.purchase_ready:
pending_purchase = self._repository.next_purchase_task()
if pending_purchase is not None:
@@ -244,16 +278,25 @@ class TaskDispatcher:
)
outcome = service.execute_selected(task.remote_task_id)
else:
if self._purchase_factory is None:
factory = self._purchase_factory
if task.execution_mode == "live":
if self.claim_capabilities().purchase_mode != "live":
raise RuntimeError(
f"真实采购任务 {task.remote_task_id} 的本地授权已关闭或绑定不一致;"
"任务保持待执行,不会降级为演练"
)
factory = self._live_purchase_factory
if factory is None:
raise RuntimeError(
"采购演练执行器未就绪,已停止领取以避免任务卡住"
f"采购任务 {task.remote_task_id} 的 {task.execution_mode} "
"执行器未就绪,已停止且不会改变任务模式"
)
purchase_service = PurchaseTaskService(
self._gateway,
self._repository,
self._client,
self._device_address,
self._purchase_factory,
factory,
cancelled=self._cancelled,
)
outcome = purchase_service.execute_selected(task.remote_task_id)