feat: 图片下载重试机制优化

- 新增 isRetryableError() 判断网络错误是否可重试
- 默认启用重试 (maxRetries=3)
- 5xx 错误自动重试
- 网络超时、断开等错误智能重试
- 增加重试延迟(指数退避)
- 返回 imageUrl 便于失败后重试
- 超时延长到 120s
This commit is contained in:
daidai
2026-03-27 19:45:21 +08:00
Unverified
parent 73b7388312
commit 72e664b16c
+33 -9
View File
@@ -3,6 +3,17 @@
* @description 图片下载与 Base64 转换
*/
import { logger } from '../../utils/logger.js';
/**
* 判断错误是否可重试
* @param {string} message - 错误消息
* @returns {boolean}
*/
function isRetryableError(message) {
return /timeout|network|econnreset|econnrefused|etimedout|disconnected|tls|socket/i.test(message);
}
/**
* 使用页面上下文下载图片并转换为 Base64
* 自动继承页面的 Cookie 和 Session,解决鉴权问题
@@ -10,19 +21,26 @@
* @param {import('playwright-core').Page} page - Playwright 页面对象
* @param {object} [options] - 可选配置
* @param {number} [options.timeout=60000] - 超时时间(毫秒)
* @param {number} [options.retries=0] - 下载失败时的重试次数
* @returns {Promise<{ image?: string, error?: string }>} 下载结果
* @param {number} [options.maxRetries=3] - 最大重试次数
* @param {number} [options.retryDelay=1000] - 重试延迟基数(毫秒)
* @returns {Promise<{ image?: string, imageUrl?: string, error?: string }>} 下载结果(包含原始 URL)
*/
export async function useContextDownload(url, page, options = {}) {
const { timeout = 60000, retries = 0 } = options;
const { timeout = 120000, maxRetries = 3, retryDelay = 1000 } = options;
for (let attempt = 0; attempt <= retries; attempt++) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await page.request.get(url, { timeout });
if (!response.ok()) {
if (attempt < retries) continue;
return { error: `下载失败: HTTP ${response.status()}` };
const status = response.status();
// 5xx 错误可重试
if (status >= 500 && attempt < maxRetries) {
logger.warn('下载', `HTTP ${status},重试 ${attempt}/${maxRetries}...`);
await new Promise(r => setTimeout(r, retryDelay * attempt));
continue;
}
return { error: `下载失败: HTTP ${status}`, imageUrl: url };
}
const buffer = await response.body();
@@ -30,10 +48,16 @@ export async function useContextDownload(url, page, options = {}) {
const contentType = response.headers()['content-type'] || 'image/png';
const mimeType = contentType.split(';')[0].trim();
return { image: `data:${mimeType};base64,${base64}` };
return { image: `data:${mimeType};base64,${base64}`, imageUrl: url };
} catch (e) {
if (attempt < retries) continue;
return { error: `已获取结果,但图片下载时遇到错误: ${e.message}` };
if (isRetryableError(e.message) && attempt < maxRetries) {
logger.warn('下载', `${e.message},重试 ${attempt}/${maxRetries}...`);
await new Promise(r => setTimeout(r, retryDelay * attempt));
continue;
}
return { error: `已获取结果,但图片下载时遇到错误: ${e.message}`, imageUrl: url };
}
}
return { error: '下载失败: 已达最大重试次数', imageUrl: url };
}