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:
+123
-4
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user