feat: 实现受控真实下单安全边界 (#99)
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user