fix: 恢复图片下载重试UI,修复兼容性问题

- 恢复 workers.vue 中"图片下载重试"UI (imgDlRetry, imgDlRetryMaxRetries)
- 恢复批量代理弹窗提示文字
- 修复实例标识:有 id 用 id,没有用 name
- download.js 参数名 maxRetries -> retries,与 adapter 调用一致
- routes.js 重试下载读取配置并传递 retries
This commit is contained in:
daidai
2026-03-28 12:38:32 +08:00
Unverified
parent 463881ceda
commit 38f9fd3b84
3 changed files with 51 additions and 14 deletions
+7 -7
View File
@@ -21,22 +21,22 @@ function isRetryableError(message) {
* @param {import('playwright-core').Page} page - Playwright 页面对象
* @param {object} [options] - 可选配置
* @param {number} [options.timeout=60000] - 超时时间(毫秒)
* @param {number} [options.maxRetries=3] - 最大重试次数
* @param {number} [options.retries=3] - 最大重试次数
* @param {number} [options.retryDelay=1000] - 重试延迟基数(毫秒)
* @returns {Promise<{ image?: string, imageUrl?: string, error?: string }>} 下载结果(包含原始 URL)
*/
export async function useContextDownload(url, page, options = {}) {
const { timeout = 120000, maxRetries = 3, retryDelay = 1000 } = options;
const { timeout = 120000, retries = 3, retryDelay = 1000 } = options;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await page.request.get(url, { timeout });
if (!response.ok()) {
const status = response.status();
// 5xx 错误可重试
if (status >= 500 && attempt < maxRetries) {
logger.warn('下载', `HTTP ${status},重试 ${attempt}/${maxRetries}...`);
if (status >= 500 && attempt < retries) {
logger.warn('下载', `HTTP ${status},重试 ${attempt}/${retries}...`);
await new Promise(r => setTimeout(r, retryDelay * attempt));
continue;
}
@@ -50,8 +50,8 @@ export async function useContextDownload(url, page, options = {}) {
return { image: `data:${mimeType};base64,${base64}`, imageUrl: url };
} catch (e) {
if (isRetryableError(e.message) && attempt < maxRetries) {
logger.warn('下载', `${e.message},重试 ${attempt}/${maxRetries}...`);
if (isRetryableError(e.message) && attempt < retries) {
logger.warn('下载', `${e.message},重试 ${attempt}/${retries}...`);
await new Promise(r => setTimeout(r, retryDelay * attempt));
continue;
}
+4 -1
View File
@@ -572,7 +572,10 @@ export function createAdminRouter(context) {
const poolContext = queueManager?.getPoolContext?.();
const page = poolContext?.getFirstPage?.();
if (page) {
downloadFn = (url) => useContextDownload(url, page);
const imgDlCfg = config?.backend?.pool?.failover || {};
downloadFn = (url) => useContextDownload(url, page, {
retries: imgDlCfg.imgDlRetry ? (imgDlCfg.imgDlRetryMaxRetries || 3) : 1
});
}
} catch { /* Pool 未初始化,使用后备方案 */ }
+40 -6
View File
@@ -87,6 +87,9 @@ const instanceData = computed({
set: (val) => { settingsStore.workerConfig = val; }
});
// 获取实例唯一标识(优先 id,没有则用 name)
const getInstanceKey = (inst) => inst.id || inst.name;
// 批量选择
const selectedRowKeys = ref([]);
const rowSelection = computed(() => ({
@@ -121,7 +124,7 @@ const openBatchProxy = () => {
const handleBatchProxySave = async () => {
const newList = (instanceData.value || []).map(inst => {
if (!selectedRowKeys.value.includes(inst.name)) return inst;
if (!selectedRowKeys.value.includes(getInstanceKey(inst))) return inst;
return {
...inst,
proxy: batchProxyForm.value.proxy ? {
@@ -152,7 +155,7 @@ const handleBatchDelete = () => {
cancelText: '取消',
async onOk() {
const newList = (instanceData.value || []).filter(
inst => !selectedRowKeys.value.includes(inst.name)
inst => !selectedRowKeys.value.includes(getInstanceKey(inst))
);
const success = await settingsStore.saveWorkerConfig(newList);
if (success) {
@@ -225,7 +228,8 @@ const handleEdit = (record) => {
// 删除实例
const handleDelete = async (record) => {
const newList = instanceData.value.filter(item => item.name !== record.name);
const key = getInstanceKey(record);
const newList = instanceData.value.filter(item => getInstanceKey(item) !== key);
await settingsStore.saveWorkerConfig(newList);
};
@@ -253,8 +257,9 @@ const handleSaveEdit = async () => {
// 创建
newList.push(instanceToSave);
} else {
// 更新 - 用原始 name 查找
const index = newList.findIndex(item => item.name === editingInstance.value.name);
// 更新 - 用唯一标识查找
const editingKey = getInstanceKey(editingInstance.value);
const index = newList.findIndex(item => getInstanceKey(item) === editingKey);
if (index > -1) {
newList[index] = instanceToSave;
}
@@ -374,6 +379,32 @@ const handleRemoveWorker = (index) => {
</div>
</a-col>
</a-row>
<a-divider style="margin: 12px 0;" />
<a-row :gutter="16">
<a-col :xs="24" :md="12">
<div style="margin-bottom: 8px;">
<div style="font-weight: 600; margin-bottom: 8px;">图片下载重试</div>
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 12px;">
启用后图片/视频下载失败时会自动重试下载不重新生成
</div>
<a-switch v-model:checked="poolConfig.failover.imgDlRetry" />
</div>
</a-col>
<a-col :xs="24" :md="12">
<div style="margin-bottom: 8px;">
<div style="font-weight: 600; margin-bottom: 8px;">下载重试次数</div>
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 12px;">
图片下载失败时的最大重试次数范围 1-10
</div>
<a-input-number v-model:value="poolConfig.failover.imgDlRetryMaxRetries" :min="1"
:max="10" :disabled="!poolConfig.failover.imgDlRetry" style="width: 100%"
placeholder="请输入下载重试次数" />
</div>
</a-col>
</a-row>
</a-collapse-panel>
</a-collapse>
</div>
@@ -408,7 +439,7 @@ const handleRemoveWorker = (index) => {
<!-- 实例表格 -->
<a-table :columns="columns" :data-source="instanceData" :pagination="false"
:row-selection="rowSelection" row-key="name">
:row-selection="rowSelection" :row-key="record => record.id || record.name">
<template #bodyCell="{ column, record }">
<!-- 实例名称 -->
<template v-if="column.key === 'name'">
@@ -609,6 +640,9 @@ const handleRemoveWorker = (index) => {
<!-- 批量代理设置模态框 -->
<a-modal v-model:open="batchProxyVisible" title="批量设置代理" okText="确定" cancelText="取消"
@ok="handleBatchProxySave">
<div style="font-size: 12px; color: #8c8c8c; margin-bottom: 16px;">
将对选中的 {{ selectedRowKeys.length }} 个实例统一设置代理
</div>
<div style="margin-bottom: 16px;">
<a-switch v-model:checked="batchProxyForm.proxy" />
<span style="margin-left: 8px;">