download.js 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. /**
  2. * 根据 url 下载文件到 savePath(支持 http/https)
  3. * 入参:url, savePath(相对路径时基于 folderPath)
  4. */
  5. const path = require('path')
  6. const fs = require('fs')
  7. const https = require('https')
  8. const http = require('http')
  9. function buildSavePath(savePath, folderPath) {
  10. if (!savePath || typeof savePath !== 'string') return null
  11. const trimmed = savePath.trim()
  12. if (path.isAbsolute(trimmed) || trimmed.match(/^[A-Za-z]:/)) return trimmed
  13. return folderPath ? path.join(folderPath, trimmed) : path.resolve(trimmed)
  14. }
  15. function downloadToFile(url, filePath) {
  16. return new Promise((resolve, reject) => {
  17. const protocol = url.startsWith('https') ? https : http
  18. const request = protocol.get(url, { timeout: 60000 }, (response) => {
  19. if (response.statusCode === 301 || response.statusCode === 302) {
  20. const redirect = response.headers.location
  21. if (redirect) return downloadToFile(redirect, filePath).then(resolve).catch(reject)
  22. }
  23. if (response.statusCode !== 200) {
  24. reject(new Error(`HTTP ${response.statusCode}`))
  25. return
  26. }
  27. const dir = path.dirname(filePath)
  28. if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
  29. const file = fs.createWriteStream(filePath)
  30. response.pipe(file)
  31. file.on('finish', () => { file.close(); resolve({ success: true, path: filePath }) })
  32. file.on('error', (err) => { fs.unlink(filePath, () => {}); reject(err) })
  33. })
  34. request.on('error', reject)
  35. request.setTimeout(60000, () => { request.destroy(); reject(new Error('timeout')) })
  36. })
  37. }
  38. async function executeDownload({ url, savePath, folderPath }) {
  39. if (!url || typeof url !== 'string' || !url.trim()) return { success: false, error: 'download 缺少 url 参数' }
  40. if (!savePath && savePath !== '') return { success: false, error: 'download 缺少 savePath 参数' }
  41. const absolutePath = buildSavePath(String(savePath).trim(), folderPath)
  42. if (!absolutePath) return { success: false, error: 'download 缺少 savePath 参数' }
  43. try {
  44. await downloadToFile(url.trim(), absolutePath)
  45. return { success: true, path: absolutePath }
  46. } catch (e) {
  47. return { success: false, error: e && (e.message || String(e)) || 'download failed' }
  48. }
  49. }
  50. module.exports = { executeDownload }