Files
ele-HeiXiu/electron/main/ipc/utils/download.ts
YoungestSongMo 136a17c0bf feat: 任务列表右键菜单回调 + 主进程下载 + sharp 缩略图 + IPC 精简重构
- 任务右键菜单:实现打开输出目录(inputs/outputs/.cache 结构)、
    复制资源链接、重新编辑、再次生成、拉取结果、重新下载、收藏、删除记录
  - 主进程下载:通过 Electron net 模块绕过 CORS,支持 CDN 媒体文件下载
  - sharp 缩略图:替换 nativeImage,支持 4K/8K+ 大尺寸素材的流式处理
  - IPC 精简:40+ 冗余通道 → 15 个分组通道,移除未使用的 preload 桥接方法
  - 任务存储:新增收藏字段、schema 迁移、task-store 持久化
  - 清理废弃模块:canvas-ipc / updater / request / pricing

refactor: IPC/Preload/Services 模块化拆分 — 单一职责 + 向后兼容

  主要变更:
  - shared/constants/ipc-channels.ts → ipc/ 模块 (base/storage/canvas/updater)
  - electron/preload.ts → preload/ 模块 (base/safe-storage/file-storage/canvas)
  - electron/main/ipc/canvas-ipc.ts → canvas/ 模块 (types/walk/sidecar/mime/thumbnail)
  - electron/main/ipc/storage-ipc.ts → 提取 utils/ (path/download/thumbnail)
  - electron/main/updater.ts → updater/ 模块 (provider/platform-updaters)
  - src/services/request.ts → request/ 模块 (token/interceptors/methods)
  - src/shared/utils/pricing.ts → pricing/ 模块 (types/helpers/format/calculate/fields)
  - src/modules/home/center/views/ModelInputForm.tsx → 提取 utils + DependentFormItem

  架构优化:
  - 所有拆分均保持向后兼容(旧导入路径仍可用)
  - TypeScript 编译通过 ✓
  - 每个模块单一职责,平均 ~130 行
2026-07-03 11:13:36 +08:00

98 lines
3.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// download.ts — 文件下载工具函数
//
// 职责:
// - 通过 Electron net 模块下载文件到磁盘
// - 支持重定向、超时、自定义请求头
//
// 设计:
// - net 基于 Chromium 网络栈,自动携带浏览器标准头,不受 CORS 限制
// - 不能用 netRequest它 chunk.toString() 会损坏二进制数据)
// - 不能用 Node.js https缺少浏览器头导致 CDN 返回 400
// ============================================================
import { net } from 'electron';
import fs from 'node:fs';
import path from 'node:path';
import { ensureDir } from './path';
/**
* 通过 Electron net 模块下载文件到磁盘。
*
* @param url - 下载地址
* @param destPath - 目标文件路径
* @param headers - 可选的自定义请求头
*/
export function downloadToFile(
url: string,
destPath: string,
headers?: Record<string, string>,
): Promise<void> {
return new Promise((resolve, reject) => {
const req = net.request({ method: 'GET', url });
// 自定义请求头
if (headers) {
for (const [key, value] of Object.entries(headers)) {
req.setHeader(key, value);
}
}
// 超时 120s
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
req.abort();
reject(new Error('下载超时120s'));
}, 120_000);
req.on('response', (response) => {
clearTimeout(timer);
const status = response.statusCode;
// 3xx 重定向
if (status >= 300 && status < 400) {
const location = response.headers['location']?.[0];
if (location && location !== url) {
downloadToFile(location, destPath, headers).then(resolve).catch(reject);
} else {
reject(new Error('重定向循环或缺少 Location 头'));
}
response.on('data', () => {}); // 消费响应体
return;
}
if (status >= 400) {
reject(new Error(`HTTP ${status}`));
response.on('data', () => {});
return;
}
// 收集二进制 chunks → 写入文件
const chunks: Buffer[] = [];
response.on('data', (chunk: Buffer) => chunks.push(chunk));
response.on('end', () => {
try {
ensureDir(path.dirname(destPath));
const buffer = Buffer.concat(chunks);
fs.writeFileSync(destPath, buffer);
resolve();
} catch (err) {
reject(err);
}
});
response.on('error', (err) => {
reject(new Error(`响应读取失败: ${err.message}`));
});
});
req.on('error', (err) => {
clearTimeout(timer);
if (timedOut) return;
reject(new Error(`网络请求失败: ${err.message}`));
});
req.end();
});
}