You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在调用fs.createWriteStream前确认链接对应文件真实存在

Node.js 文件下载前有效性校验实现方案

核心逻辑说明

你原有代码的问题是未对HTTP响应结果做有效性校验,只要请求能收到回包(哪怕是404、500等错误状态)都会直接将内容写入本地文件,才会导致错误页面被保存为xls格式的问题。我们只需要在收到响应后先做两层校验,再执行写入操作即可解决问题:

  • 校验HTTP响应状态码为2xx成功状态
  • 可选校验Content-Type响应头匹配Excel文件类型,进一步避免返回网页内容的场景
  • 所有异常场景(网络错误、状态码错误、类型不匹配)统一触发Slack告警

完整修改后代码

const https = require('https');
const fs = require('fs');

// 抽离Slack告警公共方法
async function sendSlackAlert(url, dest, error) {
  try {
    await fetch(
      `https://hooks.slack.com/services/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`,
      {
        method: 'post',
        body: JSON.stringify({
          text: `文件下载失败!url: ${url}, 存储路径: ${dest}, 错误信息: ${error}`,
        }),
        headers: { 'Content-Type': 'application/json' },
      }
    );
  } catch (e) {
    console.error('Slack告警发送失败:', e);
  }
}

async function downloadFile() {
  function switchCase(value) {
    switch (value) {
      case '1':
        return 'jan';
      case '2':
        return 'fev';
      case '3':
        return 'mar';
      case '4':
        return 'abr';
      case '5':
        return 'mai';
      case '6':
        return 'jun';
      case '7':
        return 'jul';
      case '8':
        return 'ago';
      case '9':
        return 'set';
      case '10':
        return 'out';
      case '11':
        return 'nov';
      case '12':
        return 'dez';
      default:
        return;
    }
  }

  await (async function (url, dest) {
    return new Promise((resolve, reject) => {
      const request = https
        .get(url, async function (response) {
          // 第一步:校验HTTP状态码
          if (response.statusCode < 200 || response.statusCode >= 300) {
            // 销毁响应流,避免内存泄漏
            response.resume();
            const errMsg = `HTTP请求错误,状态码: ${response.statusCode}`;
            await sendSlackAlert(url, dest, errMsg);
            return reject(new Error(errMsg));
          }

          // 第二步:可选校验Content-Type为Excel类型,可根据站点实际返回调整匹配规则
          const contentType = response.headers['content-type'] || '';
          if (!contentType.includes('application/vnd.ms-excel') && !contentType.includes('application/octet-stream')) {
            response.resume();
            const errMsg = `返回内容类型错误,实际类型: ${contentType}`;
            await sendSlackAlert(url, dest, errMsg);
            return reject(new Error(errMsg));
          }

          // 校验通过再创建写入流,避免提前生成无效文件
          const file = fs.createWriteStream(dest);
          response.pipe(file);

          file.on('finish', async function () {
            file.close(async () => {
              await closeDB();
              resolve(true);
            });
          });

          // 监听文件写入错误
          file.on('error', async (err) => {
            fs.unlink(dest, () => {});
            await sendSlackAlert(url, dest, err.message);
            reject(err);
          });
        })
        .on('error', async function (err) {
          await sendSlackAlert(url, dest, err.message);
          reject(err);
        });
    });
  })(
    `https://www.anbima.com.br/informacoes/merc-sec-debentures/arqs/d${
      new Date().getFullYear().toString().slice(2) +
      switchCase((new Date().getMonth() + 1).toString()) +
      (new Date().getDate() - 1).toString().padStart(2, '0')
    }.xls`,
    'd' +
      new Date().getFullYear().toString().slice(2) +
      (new Date().getMonth() + 1).toString() +
      (new Date().getDate() - 1).toString().padStart(2, '0') +
      '.xls'
  );
}

关键优化点说明

  • 把响应校验逻辑提到创建写入流之前,避免无效场景下生成空文件
  • 统一了所有错误场景的Slack告警逻辑,无需重复编写代码
  • 异常场景下主动销毁响应流,避免内存泄漏
  • 调整了文件关闭、数据库关闭的执行顺序,避免异步时序问题
  • 新增了文件写入错误的监听,覆盖本地磁盘满、无权限等本地异常场景

内容的提问来源于stack exchange,提问作者Breno

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 21:54:09