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

如何从后端向前端分块传数据?网关(Node.js)流式传输实现问询

解决方案:Node.js网关通过流分块返回CSV数据

核心思路

网关循环调用内部服务(每次带limit和offset参数分页拿数据),把每批数据转成CSV格式后,通过Node.js的可读流(Readable)分块推给前端;前端直接接收流数据生成CSV文件,不用再合并多请求结果。

网关(Node.js/Express)实现步骤

1. 依赖准备

先装需要的包:

npm install express csv-stringify axios

2. 完整流处理代码

自定义一个可读流,负责分批拉取数据、转CSV、推流;再用Express暴露接口把流挂到响应上:

const express = require('express');
const { Readable } = require('stream');
const stringify = require('csv-stringify').stringify;
const axios = require('axios');
const app = express();

// 自定义流:分批获取内部服务数据并转CSV
class CSVDataStream extends Readable {
  constructor(options) {
    super({ encoding: 'utf8' });
    this.batchSize = options.batchSize || 1000; // 每批拿多少条数据
    this.currentOffset = 0;
    this.internalApiUrl = options.internalApiUrl;
    this.hasSentHeader = false; // 标记是否发过CSV表头
  }

  async _read() {
    try {
      // 调用内部服务拿一批数据
      const res = await axios.get(this.internalApiUrl, {
        params: {
          limit: this.batchSize,
          offset: this.currentOffset
        }
      });
      const dataBatch = res.data;

      if (dataBatch.length === 0) {
        // 没数据了,结束流
        this.push(null);
        return;
      }

      // 第一次推数据时先发送CSV表头
      if (!this.hasSentHeader) {
        const headers = Object.keys(dataBatch[0]);
        const headerLine = stringify([headers], { header: false });
        this.push(headerLine);
        this.hasSentHeader = true;
      }

      // 把当前批次数据转成CSV行
      const csvLines = stringify(dataBatch, { header: false });
      this.push(csvLines);

      // 更新偏移量,准备拿下一批
      this.currentOffset += this.batchSize;
    } catch (err) {
      this.emit('error', err);
    }
  }
}

// 网关导出CSV接口
app.get('/export-csv', (req, res) => {
  // 设置响应头,告诉浏览器这是可下载的CSV文件
  res.setHeader('Content-Type', 'text/csv');
  res.setHeader('Content-Disposition', 'attachment; filename="data-export.csv"');
  res.setHeader('Transfer-Encoding', 'chunked'); // 启用分块传输

  const csvStream = new CSVDataStream({
    batchSize: 1000,
    internalApiUrl: 'http://your-internal-service/api/get-data' // 替换成你的内部服务地址
  });

  // 把流接到响应上,自动分块返回
  csvStream.pipe(res);

  // 处理流错误
  csvStream.on('error', () => {
    res.status(500).send('数据导出失败');
  });
});

app.listen(3001, () => {
  console.log('网关服务运行在3001端口');
});

关键细节

  • 分块传输:通过Transfer-Encoding: chunked让浏览器逐步接收数据,不用等全量数据加载完
  • 背压处理:Node.js流会自动处理前端接收慢的情况,暂停拉取数据,避免网关内存爆掉
  • 表头去重:只在第一次推数据时发表头,避免重复写入

前端(React.js)接收流实现

用fetch直接读流,逐步写入Blob生成下载链接,不用把全量数据放内存:

const handleExportCSV = async () => {
  try {
    const response = await fetch('http://your-gateway:3001/export-csv');
    if (!response.ok) throw new Error('导出失败');

    const reader = response.body.getReader();
    const blobChunks = [];

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      blobChunks.push(value);
    }

    // 生成Blob并触发下载
    const csvBlob = new Blob(blobChunks, { type: 'text/csv' });
    const downloadUrl = URL.createObjectURL(csvBlob);
    const aTag = document.createElement('a');
    aTag.href = downloadUrl;
    aTag.download = 'data-export.csv';
    aTag.click();
    URL.revokeObjectURL(downloadUrl);
  } catch (err) {
    alert(err.message);
  }
};

可选:添加下载进度提示

如果需要显示进度,可通过已接收数据量和总长度计算:

const handleExportWithProgress = async () => {
  try {
    const response = await fetch('http://your-gateway:3001/export-csv');
    if (!response.ok) throw new Error('导出失败');

    const totalLength = parseInt(response.headers.get('Content-Length') || '0', 10);
    let receivedLength = 0;
    const blobChunks = [];

    const reader = response.body.getReader();
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      blobChunks.push(value);
      receivedLength += value.length;
      // 更新进度条,比如 setProgress(Math.round((receivedLength / totalLength) * 100))
    }

    // 后续下载逻辑同上
  } catch (err) {
    alert(err.message);
  }
};

注意事项

  • 内部服务必须支持limit和offset的分页参数
  • 可在网关的流逻辑里加重试机制,处理内部服务偶尔的请求失败
  • 超大数据量场景下,前端别把全量数据存内存,用流逐步写入Blob是最优解

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 15:50:26