/** * 根据 url 下载文件到 savePath(支持 http/https) * 入参:url, savePath(相对路径时基于 folderPath) */ const path = require('path') const fs = require('fs') const https = require('https') const http = require('http') function buildSavePath(savePath, folderPath) { if (!savePath || typeof savePath !== 'string') return null const trimmed = savePath.trim() if (path.isAbsolute(trimmed) || trimmed.match(/^[A-Za-z]:/)) return trimmed return folderPath ? path.join(folderPath, trimmed) : path.resolve(trimmed) } function downloadToFile(url, filePath) { return new Promise((resolve, reject) => { const protocol = url.startsWith('https') ? https : http const request = protocol.get(url, { timeout: 60000 }, (response) => { if (response.statusCode === 301 || response.statusCode === 302) { const redirect = response.headers.location if (redirect) return downloadToFile(redirect, filePath).then(resolve).catch(reject) } if (response.statusCode !== 200) { reject(new Error(`HTTP ${response.statusCode}`)) return } const dir = path.dirname(filePath) if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }) const file = fs.createWriteStream(filePath) response.pipe(file) file.on('finish', () => { file.close(); resolve({ success: true, path: filePath }) }) file.on('error', (err) => { fs.unlink(filePath, () => {}); reject(err) }) }) request.on('error', reject) request.setTimeout(60000, () => { request.destroy(); reject(new Error('timeout')) }) }) } async function executeDownload({ url, savePath, folderPath }) { if (!url || typeof url !== 'string' || !url.trim()) return { success: false, error: 'download 缺少 url 参数' } if (!savePath && savePath !== '') return { success: false, error: 'download 缺少 savePath 参数' } const absolutePath = buildSavePath(String(savePath).trim(), folderPath) if (!absolutePath) return { success: false, error: 'download 缺少 savePath 参数' } try { await downloadToFile(url.trim(), absolutePath) return { success: true, path: absolutePath } } catch (e) { return { success: false, error: e && (e.message || String(e)) || 'download failed' } } } module.exports = { executeDownload }