fix: Windows Server 下载兼容性修复 & 错误信息增强

- browser.js: acquire 失败时携带 Daemon 返回的 detail 信息,
  不再丢失真正的错误原因

- daemon/engine.js: 添加 --safebrowsing-disable-download-protection
  和 --safebrowsing-disable-extension-blacklist 启动参数,
  防止 Windows Server 上 Safe Browsing 验毒超时拦截下载

- gemini-ops.js (downloadFullSizeImage):
  · 用 path.resolve() 规范化下载路径,修复 Windows Server 上
    正斜杠路径导致 CDP 下载失败的问题
  · CDP 下载模式从 allowAndName 改为 allow,避免 GUID 临时文件
    被安全策略拦截,简化下载流程(无需重命名)
  · 下载完成后增加文件存在性检查,未找到文件时返回明确错误
This commit is contained in:
WJZ_P
2026-03-23 12:04:27 +08:00
parent 9ba918cdf4
commit 914427da6e
3 changed files with 16 additions and 22 deletions

View File

@@ -165,7 +165,8 @@ export async function ensureBrowser() {
acquireData = await res.json(); acquireData = await res.json();
if (!acquireData.ok) { if (!acquireData.ok) {
throw new Error(acquireData.error || 'Daemon 返回失败'); const detail = acquireData.detail ? ` (${acquireData.detail})` : '';
throw new Error(`${acquireData.error || 'Daemon 返回失败'}${detail}`);
} }
} catch (err) { } catch (err) {
throw new Error( throw new Error(

View File

@@ -197,6 +197,9 @@ const BROWSER_ARGS = [
'--disable-crash-reporter', '--disable-crash-reporter',
'--hide-crash-restore-bubble', '--hide-crash-restore-bubble',
'--test-type', '--test-type',
// Windows Server 安全策略绕过:防止 Safe Browsing 验毒超时导致浏览器的下载被拦截
'--safebrowsing-disable-download-protection',
'--safebrowsing-disable-extension-blacklist',
]; ];
/** 端口探活 */ /** 端口探活 */

View File

@@ -771,12 +771,14 @@ export function createOps(page) {
if (!imgInfo.ok) return imgInfo; if (!imgInfo.ok) return imgInfo;
// 2. 通过 CDP 设置下载路径到 config.outputDir // 2. 通过 CDP 设置下载路径到 config.outputDir
const downloadDir = config.outputDir; // 用 resolve() 规范化路径,确保 Windows Server 上是标准反斜杠路径
const { resolve: pathResolve } = await import('node:path');
const downloadDir = pathResolve(config.outputDir);
mkdirSync(downloadDir, { recursive: true }); mkdirSync(downloadDir, { recursive: true });
const client = page._client(); const client = page._client();
await client.send('Browser.setDownloadBehavior', { await client.send('Browser.setDownloadBehavior', {
behavior: 'allowAndName', behavior: 'allow', // 不用 allowAndName避免 GUID 临时文件被 Windows Server 安全策略拦截
downloadPath: downloadDir, downloadPath: downloadDir,
eventsEnabled: true, eventsEnabled: true,
}); });
@@ -789,21 +791,18 @@ export function createOps(page) {
reject(new Error('download_timeout')); reject(new Error('download_timeout'));
}, timeout); }, timeout);
let guid = null;
let suggestedFilename = null; let suggestedFilename = null;
function onBegin(evt) { function onBegin(evt) {
guid = evt.guid;
suggestedFilename = evt.suggestedFilename || null; suggestedFilename = evt.suggestedFilename || null;
} }
function onProgress(evt) { function onProgress(evt) {
if (evt.guid !== guid) return;
if (evt.state === 'completed') { if (evt.state === 'completed') {
clearTimeout(timer); clearTimeout(timer);
client.off('Browser.downloadWillBegin', onBegin); client.off('Browser.downloadWillBegin', onBegin);
client.off('Browser.downloadProgress', onProgress); client.off('Browser.downloadProgress', onProgress);
resolve({ suggestedFilename, guid }); resolve({ suggestedFilename });
} else if (evt.state === 'canceled') { } else if (evt.state === 'canceled') {
clearTimeout(timer); clearTimeout(timer);
client.off('Browser.downloadWillBegin', onBegin); client.off('Browser.downloadWillBegin', onBegin);
@@ -839,27 +838,18 @@ export function createOps(page) {
} }
// 6. 等待下载完成 // 6. 等待下载完成
// allowAndName 模式下Chrome 会把文件以 GUID 命名保存到 downloadDir // allow 模式下Chrome 直接用 suggestedFilename 保存到 downloadDir无需重命名。
// 真正的文件名在 downloadWillBegin 事件的 suggestedFilename 里。
// 下载完成后需要把 GUID 文件重命名为目标文件名。
try { try {
const { suggestedFilename, guid } = await downloadPromise; const { suggestedFilename } = await downloadPromise;
const { join } = await import('node:path'); const { join } = await import('node:path');
const { renameSync, existsSync } = await import('node:fs'); const { existsSync } = await import('node:fs');
const targetName = suggestedFilename || `gemini_fullsize_${Date.now()}.png`; const targetName = suggestedFilename || `gemini_fullsize_${Date.now()}.png`;
const guidPath = join(downloadDir, guid);
const filePath = join(downloadDir, targetName); const filePath = join(downloadDir, targetName);
// 将 GUID 文件重命名为正确的文件名 if (!existsSync(filePath)) {
if (existsSync(guidPath)) { console.warn(`[ops] 下载文件未找到: ${filePath}`);
renameSync(guidPath, filePath); return { ok: false, error: 'downloaded_file_not_found', filePath, src: imgInfo.src, index: imgInfo.index, total: imgInfo.total };
console.log(`[ops] 已重命名: ${guid}${targetName}`);
} else {
// 有些 Chrome 版本可能已经用 suggestedFilename 保存了,检查一下
if (!existsSync(filePath)) {
console.warn(`[ops] 下载文件未找到: 既不存在 ${guidPath} 也不存在 ${filePath}`);
}
} }
// 去水印处理 // 去水印处理