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:
@@ -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(
|
||||||
|
|||||||
@@ -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',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 端口探活 */
|
/** 端口探活 */
|
||||||
|
|||||||
@@ -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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 去水印处理
|
// 去水印处理
|
||||||
|
|||||||
Reference in New Issue
Block a user