如何在调用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
相关产品推荐
相关产品推荐

