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
+113 -4
View File
@@ -1,7 +1,7 @@
"""一条本地采购任务的安全演练流程。
"""一条本地采购任务的安全执行流程。
本模块固定为 ``dry_run``。它会核对商品、动态规格、数量和价格,并停在最终
提交订单之前;代码中没有提交订单或付款入口。
``dry_run`` 始终停在提交前;``live`` 只在独立 Adapter 就绪时允许一次提交,
并且必须先持久化不可逆标记。任何路径都不包含付款。
"""
from __future__ import annotations
@@ -12,6 +12,7 @@ from typing import Callable, Mapping
from .admin_gateway import AdminGateway, AdminGatewayError, ClientInfo
from .pdd_purchase_adapter import (
PddLivePurchaseAdapter,
PddPurchaseAdapter,
PddPurchaseError,
PurchasePageState,
@@ -51,6 +52,9 @@ class PurchaseTaskOutcome:
PurchaseAdapterFactory = Callable[
[str, Callable[[], bool]], PddPurchaseAdapter
]
LivePurchaseAdapterFactory = Callable[
[str, Callable[[], bool]], PddLivePurchaseAdapter
]
class PurchaseTaskService:
@@ -87,7 +91,7 @@ class PurchaseTaskService:
return self.execute_selected(task.remote_task_id)
def execute_selected(self, remote_task_id: str) -> PurchaseTaskOutcome:
"""对指定的本地采购任务执行一次演练。"""
"""按任务不可变执行模式安全执行一次采购。"""
if not self._device_address:
raise ValueError("请先在设置页选择并保存 Android 设备")
@@ -104,6 +108,15 @@ class PurchaseTaskService:
try:
target = self._target_from_task(started.task)
adapter = self._factory(self._device_address, self._cancelled)
execution_mode = started.task.execution_mode
if execution_mode == "live" and not isinstance(
adapter, PddLivePurchaseAdapter
):
raise PddPurchaseError(
"PURCHASE_LIVE_ADAPTER_UNAVAILABLE",
"真实采购执行器未就绪,未进入商品页面",
step="purchase_prepare",
)
step = "purchase_open_goods"
self._enter_step(remote_task_id, started.attempt_id, step)
@@ -150,6 +163,15 @@ class PurchaseTaskService:
)
self._validate_checkout_values(state, target)
if execution_mode == "live":
assert isinstance(adapter, PddLivePurchaseAdapter)
return self._submit_live_once(
remote_task_id,
started.attempt_id,
target,
adapter,
)
step = "purchase_dry_run_stopped"
self._enter_step(remote_task_id, started.attempt_id, step)
adapter.stop_before_submit()
@@ -182,6 +204,64 @@ class PurchaseTaskService:
adapter.close()
return self._submit(event)
def _submit_live_once(
self,
remote_task_id: str,
attempt_id: str,
target: PurchaseTarget,
adapter: PddLivePurchaseAdapter,
) -> PurchaseTaskOutcome:
"""最终复核、先落不可逆标记、单击一次,然后只转核单。"""
step = "purchase_live_final_check"
self._enter_step(remote_task_id, attempt_id, step)
state = adapter.read_state()
self._validate_live_confirmation(state, target)
if self._cancelled():
raise PddPurchaseError(
"PURCHASE_CANCELLED",
"用户已在不可逆标记写入前停止真实采购",
step=step,
)
irreversible_at = self._repository.mark_purchase_irreversible(
remote_task_id, attempt_id
)
submitted_at = None
message = "订单提交点击已执行,结果待只读核对;绝不重新下单"
try:
adapter.submit_order_once()
submitted_at = utc_now_iso()
except Exception as exc:
message = (
"订单提交点击结果不确定,已转只读核对;"
f"绝不重新下单:{str(exc) or type(exc).__name__}"
)
try:
self._repository.move_purchase_to_reconcile(
remote_task_id,
attempt_id,
order_submitted_at=submitted_at,
message=message,
)
except Exception as exc:
# 不可逆标记已经提交成功。这里绝不能保存成普通失败或再次执行;
# 重启恢复会根据 irreversible_action_at 强制进入只读核单。
return PurchaseTaskOutcome(
"manual_review",
(
f"任务 {remote_task_id} 已在 {irreversible_at} 进入不可逆阶段,"
f"但核单状态保存失败:{exc};请关闭自动获取并重启,绝不重下"
),
remote_task_id,
)
return PurchaseTaskOutcome(
"manual_review",
f"任务 {remote_task_id} {message}",
remote_task_id,
)
def _enter_step(
self, remote_task_id: str, attempt_id: str, step: str
) -> None:
@@ -328,6 +408,35 @@ class PurchaseTaskService:
step="purchase_verify_quantity_price",
)
@staticmethod
def _validate_live_confirmation(
state: PurchasePageState, target: PurchaseTarget
) -> None:
"""在不可逆标记前核对最新确认页和唯一提交目标。"""
if state.page_kind != "order_confirmation":
raise PddPurchaseError(
"PURCHASE_CONFIRMATION_LOST",
"最终提交前确认页已变化,禁止真实下单",
step="purchase_live_final_check",
)
PurchaseTaskService._validate_checkout_values(state, target)
if not state.in_stock:
raise PddPurchaseError(
"PURCHASE_OUT_OF_STOCK",
"当前规格库存不足或已售罄,禁止真实下单",
step="purchase_live_final_check",
)
if state.submit_candidate_count != 1:
raise PddPurchaseError(
"PURCHASE_SUBMIT_TARGET_AMBIGUOUS",
"最终提交按钮不是唯一可靠目标,禁止真实下单",
step="purchase_live_final_check",
diagnostics={
"candidate_count": state.submit_candidate_count
},
)
@staticmethod
def _validate_common_state(state: PurchasePageState) -> None:
if state.page_kind == "captcha":