feat: 完成T-501c Shopee更新安全开关

新增 shopee_update 配置段,默认关闭真实提交和封面更新,限制测试商品ID、单次最大更新条数,并支持成功后关闭本轮自动新开编辑页。

Tab⑤ 设置页新增 Shopee 更新安全表单;Tab③ 开始更新前先读取安全配置并阻断未授权真实提交、超量、非测试商品和未允许的封面更新。

ApplyWorker 将 close_success_tab 传入 editor.apply_task;editor 仅在提交成功且页面为本轮自动新开时关闭 tab,失败和复用页面保留。

同步架构、API、路由、任务看板、当前状态与 progress 文档;补充 GUI 与 editor 单测覆盖安全设置保存、共享配置读取、安全拦截和 tab 关闭策略。
This commit is contained in:
chengma
2026-06-29 09:18:03 +08:00
parent 91683abe7d
commit b05a856796
11 changed files with 453 additions and 42 deletions
+123 -4
View File
@@ -1079,6 +1079,11 @@ if QT_IMPORT_ERROR is None:
if not tasks:
self._set_status("当前筛选结果没有可更新任务")
return
safety_error = self._update_safety_error(tasks)
if safety_error:
QMessageBox.warning(self, "更新安全开关", safety_error)
self._set_status(safety_error.replace("\n", " "))
return
answer = QMessageBox.question(
self,
"确认开始更新",
@@ -1089,7 +1094,13 @@ if QT_IMPORT_ERROR is None:
if answer != QMessageBox.Yes:
self._set_status("已取消开始更新")
return
worker = ApplyWorker(tasks, db_path=self.db_path, config=self.config)
update_cfg = self._shopee_update_config()
worker = ApplyWorker(
tasks,
db_path=self.db_path,
config=self.config,
close_success_tab=bool(update_cfg.get("close_success_tab", False)),
)
worker.progress.connect(self._on_apply_progress)
worker.row_updated.connect(self._on_apply_row_updated)
worker.log.connect(self._set_status)
@@ -1197,15 +1208,60 @@ if QT_IMPORT_ERROR is None:
return True
def _confirmation_message(self, tasks):
update_cfg = self._shopee_update_config()
cover_text = "允许" if update_cfg.get("allow_cover_update") else "不允许"
close_text = "是" if update_cfg.get("close_success_tab") else "否"
return (
"即将按当前筛选结果开始更新 Shopee 线上商品。\n\n"
f"批次:{self._batch_filter_label()}\n"
f"店铺:{self._shop_filter_label()}\n"
f"状态:{self._status_label()}\n"
f"任务数:{len(tasks)}\n\n"
"确认后后续执行会打开商品编辑页、替换标题和封面,并点击「更新」提交线上。"
"安全设置:"
f"测试商品ID={update_cfg.get('test_item_id') or '未配置'},"
f"封面更新={cover_text},"
f"最大条数={update_cfg.get('max_items_per_run', 1)},"
f"成功后关闭新页={close_text}\n\n"
"确认后后续执行会打开商品编辑页、替换标题/允许时替换封面,并点击「更新」提交线上。"
)
def _update_safety_error(self, tasks):
update_cfg = self._shopee_update_config()
if not update_cfg.get("allow_real_submit", False):
return "设置未开启「允许真实提交线上商品」,已阻止本次更新。"
max_items = max(1, int(update_cfg.get("max_items_per_run", 1) or 1))
if len(tasks) > max_items:
return f"当前筛选结果有 {len(tasks)} 条,超过单次最大更新条数 {max_items}。"
test_item_id = str(update_cfg.get("test_item_id", "")).strip()
if not test_item_id:
return "未配置测试商品ID,已阻止真实提交。"
mismatched = [
str(getattr(task, "item_id", ""))
for task in tasks
if str(getattr(task, "item_id", "")) != test_item_id
]
if mismatched:
shown = "、".join(mismatched[:5])
return f"当前任务包含非测试商品ID:{shown}。只允许更新测试商品 {test_item_id}。"
if not update_cfg.get("allow_cover_update", False):
cover_tasks = [
str(getattr(task, "item_id", ""))
for task in tasks
if getattr(task, "new_cover_path", None)
]
if cover_tasks:
return "设置未开启「允许更新封面」,当前任务包含新封面路径,已阻止本次更新。"
return None
def _shopee_update_config(self):
defaults = appconfig.default_config().get("shopee_update", {})
loaded = self.config.get("shopee_update", {})
if not isinstance(loaded, dict):
loaded = {}
merged = dict(defaults)
merged.update(loaded)
return merged
def _set_apply_running(self, running):
self.start_update_button.setEnabled(not running)
self.stop_update_button.setEnabled(running)
@@ -1967,12 +2023,20 @@ if QT_IMPORT_ERROR is None:
class ApplyWorker(BaseWorker):
"""Apply generated title/cover changes to Shopee one task at a time."""
def __init__(self, tasks, db_path=None, config=None, preflight=True):
def __init__(
self,
tasks,
db_path=None,
config=None,
preflight=True,
close_success_tab=False,
):
super().__init__()
self.tasks = list(tasks)
self.db_path = db_path
self.config = config
self.preflight = preflight
self.close_success_tab = close_success_tab
def execute(self):
account_rows = accounts.list_accounts(path=self.db_path, config=self.config)
@@ -2022,7 +2086,11 @@ if QT_IMPORT_ERROR is None:
try:
db.mark_running(task.id, "apply", path=self.db_path)
self.row_updated.emit(task.id, {"status": "running", "last_error": None})
result = editor.apply_task(account, task)
result = editor.apply_task(
account,
task,
close_success_tab=self.close_success_tab,
)
committed = bool(result.get("committed")) and not result.get("error")
error = result.get("error")
if committed:
@@ -2551,6 +2619,17 @@ if QT_IMPORT_ERROR is None:
self.cdp_ready_timeout_spin.setObjectName("cdpReadyTimeoutSpin")
self.cdp_ready_timeout_spin.setRange(1, 3600)
self.save_config_button = QPushButton("保存设置")
self.test_item_id_edit = QLineEdit()
self.test_item_id_edit.setObjectName("testItemIdEdit")
self.allow_real_submit_checkbox = QCheckBox("允许真实提交线上商品")
self.allow_real_submit_checkbox.setObjectName("allowRealSubmitCheckbox")
self.allow_cover_update_checkbox = QCheckBox("允许更新封面")
self.allow_cover_update_checkbox.setObjectName("allowCoverUpdateCheckbox")
self.max_items_per_run_spin = QSpinBox()
self.max_items_per_run_spin.setObjectName("maxItemsPerRunSpin")
self.max_items_per_run_spin.setRange(1, 9999)
self.close_success_tab_checkbox = QCheckBox("成功后关闭本次新开编辑页")
self.close_success_tab_checkbox.setObjectName("closeSuccessTabCheckbox")
form = QFormLayout()
form.addRow("", self.enabled_checkbox)
@@ -2591,6 +2670,13 @@ if QT_IMPORT_ERROR is None:
path_form.addRow("调试端口范围", port_range_layout)
path_form.addRow("CDP就绪超时(秒)", self.cdp_ready_timeout_spin)
update_form = QFormLayout()
update_form.addRow("测试商品ID", self.test_item_id_edit)
update_form.addRow("", self.allow_real_submit_checkbox)
update_form.addRow("", self.allow_cover_update_checkbox)
update_form.addRow("单次最大更新条数", self.max_items_per_run_spin)
update_form.addRow("", self.close_success_tab_checkbox)
right_panel = QWidget()
right_layout = QVBoxLayout(right_panel)
right_layout.setContentsMargins(12, 0, 0, 0)
@@ -2604,6 +2690,9 @@ if QT_IMPORT_ERROR is None:
right_layout.addSpacing(18)
right_layout.addWidget(QLabel("路径与端口"))
right_layout.addLayout(path_form)
right_layout.addSpacing(18)
right_layout.addWidget(QLabel("Shopee 更新安全"))
right_layout.addLayout(update_form)
right_layout.addWidget(self.save_config_button)
right_layout.addStretch(1)
@@ -2817,6 +2906,13 @@ if QT_IMPORT_ERROR is None:
"debug_port_range": [start_port, end_port],
"cdp_ready_timeout": self.cdp_ready_timeout_spin.value(),
"ai": ai_cfg,
"shopee_update": {
"test_item_id": self.test_item_id_edit.text().strip(),
"allow_real_submit": self.allow_real_submit_checkbox.isChecked(),
"allow_cover_update": self.allow_cover_update_checkbox.isChecked(),
"max_items_per_run": self.max_items_per_run_spin.value(),
"close_success_tab": self.close_success_tab_checkbox.isChecked(),
},
}
)
return settings
@@ -2867,8 +2963,31 @@ if QT_IMPORT_ERROR is None:
self.cdp_ready_timeout_spin.setValue(
int(appconfig.cdp_ready_timeout(self.config))
)
update_cfg = self._shopee_update_config()
self.test_item_id_edit.setText(str(update_cfg.get("test_item_id", "")))
self.allow_real_submit_checkbox.setChecked(
bool(update_cfg.get("allow_real_submit", False))
)
self.allow_cover_update_checkbox.setChecked(
bool(update_cfg.get("allow_cover_update", False))
)
self.max_items_per_run_spin.setValue(
max(1, int(update_cfg.get("max_items_per_run", 1) or 1))
)
self.close_success_tab_checkbox.setChecked(
bool(update_cfg.get("close_success_tab", False))
)
self._update_response_timeout_label()
def _shopee_update_config(self):
defaults = appconfig.default_config().get("shopee_update", {})
loaded = self.config.get("shopee_update", {})
if not isinstance(loaded, dict):
loaded = {}
merged = dict(defaults)
merged.update(loaded)
return merged
def _populate_role_model_combos(self):
ai_cfg = appconfig.ai_config(self.config)
self._populate_role_combo(