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

Express的response.download接口对相同请求返回非标准数据问题求助

问题根因

你遇到的偶发异常是典型的异步竞态问题:文件流写入是异步操作,你当前的createReportFile函数在创建完写入流、还没等所有内容写入完成、文件关闭的情况下就直接返回了文件ID,此时调用response.download读取文件时,文件可能处于以下几种状态:

  • 还没创建完成:返回File not found
  • 只写完了表头:仅返回表头内容
  • 所有内容写入完成:正常返回完整文件
    刚好对应你遇到的所有异常表现。

修复方案

第一步:改造createReportFile为异步函数,等待文件写入完成

把原来的同步返回改成返回Promise,监听流的写入完成事件和错误事件,同时记得在所有内容写完后主动结束流:

export const createReportFile = ({ reportLines, fromDate, toDate }: CreateReportProps): Promise<string> => {
  return new Promise((resolve, reject) => {
    const startDate = format(fromDate, 'dd-MM-yyyy')
    const endDate = format(toDate, 'dd-MM-yyyy')
    const aksReportDate = Date.now()

    const reportFileId = `${startDate}-to-${endDate}Report${aksReportDate}`
    const filePath = path.join(__dirname, '..', '..', '..', 'tmp', 'uploads', `${reportFileId}.csv`)

    const reportFileStream = fs.createWriteStream(filePath)
    const fieldsLine = 'usuário,primeiro_nome,ultimo_nome,ativo,completou_o_treinamento'

    reportFileStream.once('open', () => {
      reportFileStream.write(fieldsLine + '\r\n')
      reportLines.forEach(({ user_id, first_name, last_name, active, completed }) => {
        reportFileStream.write(`${user_id},${first_name},${last_name},${active},${completed}\r\n`)
      })
      // 所有内容写入完成后主动结束流
      reportFileStream.end()
    })

    // 流写入完成、文件关闭后返回文件ID
    reportFileStream.on('finish', () => {
      resolve(reportFileId)
    })

    // 写入出错时抛出错误
    reportFileStream.on('error', (err) => {
      reject(err)
    })
  })
}

第二步:修改下载逻辑,等待文件生成完成后再触发下载

因为现在createReportFile返回的是Promise,需要加await等待执行完成:

// ...
const usersReport = // ....generate users report

// 加await等待文件生成完成
const reportFileId = await createReportFile({ fromDate, toDate, reportLines: usersReport })

const filePath = path.join(__dirname, '..', '..', '..', 'tmp', 'uploads', `${reportFileId}.csv`)

console.log(filePath)

return response.download(filePath)
// ...

可选优化方案

如果不需要持久化存储报表文件,可以直接在内存生成CSV内容返回,省去本地文件读写、后续清理临时文件的步骤:

const fieldsLine = 'usuário,primeiro_nome,ultimo_nome,ativo,completou_o_treinamento'
const csvContent = [
  fieldsLine,
  ...usersReport.map(({ user_id, first_name, last_name, active, completed }) => 
    `${user_id},${first_name},${last_name},${active},${completed}`
  )
].join('\r\n')

// 直接设置响应头返回CSV
response.setHeader('Content-Type', 'text/csv')
response.setHeader('Content-Disposition', `attachment; filename="report-${Date.now()}.csv"`)
return response.send(csvContent)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 16:39:03