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

咨询:React前端流式传输文件至Express后端处理及回传方案

前端到Express后端的流式CSV传输与处理方案

当然有这种实践经验,针对大体积CSV的流式传输+逐块处理需求,下面是一套完整的前后端实现方案,全程无需加载整个文件到内存:

前端实现(React为例:流式上传)

利用浏览器的File对象和ReadableStreamAPI,逐块读取文件并发送,避免一次性加载大文件:

import { useState } from 'react';

const CSVStreamHandler = () => {
  const [uploadProgress, setUploadProgress] = useState(0);
  const [processProgress, setProcessProgress] = useState(0);
  const [downloadLink, setDownloadLink] = useState(null);

  const handleFileSelect = async (e) => {
    const file = e.target.files[0];
    if (!file || file.type !== 'text/csv') return;

    // 构造可读流,分块读取文件
    const uploadStream = new ReadableStream({
      async start(controller) {
        const chunkSize = 2 * 1024 * 1024; // 2MB块,可根据网络调整
        let currentOffset = 0;

        while (currentOffset < file.size) {
          const chunkBlob = file.slice(currentOffset, currentOffset + chunkSize);
          const chunkBuffer = await chunkBlob.arrayBuffer();
          controller.enqueue(new Uint8Array(chunkBuffer));

          currentOffset += chunkSize;
          setUploadProgress(Math.min(Math.round((currentOffset / file.size) * 100), 100));
        }
        controller.close();
      }
    });

    try {
      const response = await fetch('/api/stream-process-csv', {
        method: 'POST',
        body: uploadStream,
        headers: {
          'Content-Type': 'text/csv',
          'Content-Length': file.size.toString()
        }
      });

      if (!response.ok) throw new Error('文件处理失败');

      // 接收后端流式回传的处理结果
      const reader = response.body.getReader();
      const receivedChunks = [];
      const totalSize = parseInt(response.headers.get('Content-Length') || '0');
      let receivedBytes = 0;

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

        receivedChunks.push(value);
        receivedBytes += value.length;
        setProcessProgress(Math.min(Math.round((receivedBytes / totalSize) * 100), 100));
      }

      // 生成下载链接
      const resultBlob = new Blob(receivedChunks, { type: 'text/csv' });
      const url = URL.createObjectURL(resultBlob);
      setDownloadLink(url);
    } catch (err) {
      console.error('处理出错:', err);
      alert('文件传输或处理失败,请重试');
    }
  };

  return (
    <div className="csv-handler">
      <input type="file" accept=".csv" onChange={handleFileSelect} />
      {uploadProgress > 0 && <p>上传进度: {uploadProgress}%</p>}
      {processProgress > 0 && <p>处理回传进度: {processProgress}%</p>}
      {downloadLink && (
        <a href={downloadLink} download="processed_data.csv">
          下载处理后的CSV
        </a>
      )}
    </div>
  );
};

export default CSVStreamHandler;

后端实现(Express:流式接收、处理、回传)

Express的req是可读流,结合csv-parser做流式解析,处理后直接通过响应流回传,全程无大内存占用:

先安装依赖:

npm install csv-parser csv-writer

然后编写API路由:

const express = require('express');
const csvParser = require('csv-parser');
const { createObjectCsvWriter } = require('csv-writer');
const { PassThrough } = require('stream');

const router = express.Router();

router.post('/stream-process-csv', (req, res) => {
  // 设置响应头,支持流式下载
  res.setHeader('Content-Type', 'text/csv');
  res.setHeader('Content-Disposition', 'attachment; filename="processed_data.csv"');
  res.setHeader('Transfer-Encoding', 'chunked');

  // 中间转换流,用于传递处理后的行数据
  const transformStream = new PassThrough({ objectMode: true });

  // 1. 流式解析前端传来的CSV
  req.pipe(csvParser())
    .on('data', (rawRow) => {
      // 这里写你的业务处理逻辑:比如数据清洗、转换、计算
      const processedRow = {
        ...rawRow,
        // 示例:将数值字段翻倍
        adjusted_value: rawRow.value ? (parseFloat(rawRow.value) * 2).toString() : ''
      };
      // 将处理后的行写入中间流
      transformStream.write(processedRow);
    })
    .on('end', () => {
      // 解析完成,关闭中间流
      transformStream.end();
    })
    .on('error', (err) => {
      console.error('CSV解析错误:', err);
      res.status(500).send('CSV格式错误或解析失败');
      transformStream.destroy();
    });

  // 2. 将处理后的数据流式转换为CSV并回传
  const csvWriter = createObjectCsvWriter({
    path: res, // 直接写入响应流
    header: [
      // 需与处理后的字段对应,可根据实际表头动态生成
      { id: 'id', title: 'ID' },
      { id: 'name', title: 'NAME' },
      { id: 'value', title: 'ORIGINAL_VALUE' },
      { id: 'adjusted_value', title: 'ADJUSTED_VALUE' }
    ]
  });

  // 从中间流读取数据,写入CSV响应
  csvWriter.writeRecords(transformStream)
    .then(() => {
      res.end();
    })
    .catch((err) => {
      console.error('CSV生成错误:', err);
      res.status(500).send('处理结果生成失败');
    });
});

module.exports = router;

核心注意点

  • 块大小优化:前端块大小建议1-4MB,太小会增加请求开销,太大易导致内存波动。
  • 错误处理:必须监听所有流的error事件,及时销毁流并返回错误,避免资源泄漏。
  • 进度计算:如果后端无法提前确定处理后文件大小,可去掉Content-Length头,前端只显示“处理中”状态,或通过已接收字节数估算进度。
  • 跨域配置:若前后端跨域,Express需配置CORS,允许Content-Length等自定义头,以及POST请求方法。
  • 内存控制:全程所有操作基于流,即使处理GB级CSV也不会导致内存溢出,适合百万级行数据场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 07:36:23