前端流式下载


前端流式下载

1. 概述

在文件下载场景中,后端通常会将文件内容发送给前端,前端再将其保存为本地文件。常见的实现方式有两种:

  1. base64 编码下载:后端将文件内容编码为 base64 字符串,前端接收到后解码并保存为文件。
  2. 流式下载:后端直接将文件内容以流的形式发送给前端,前端接收到后直接保存为文件。

本文将对比这两种方法的实现方式、性能和适用场景。


2. 原始代码(base64 编码下载)

2.1 后端接口

后端接口返回一个包含 base64 编码数据的 JSON 对象。

Node.js 示例

const express = require('express');
const fs = require('fs');
const path = require('path');

const app = express();
const PORT = 3000;

app.get('/api/download', (req, res) => {
  const filePath = path.join(__dirname, 'data/example.csv');
  fs.readFile(filePath, 'utf8', (err, data) => {
    if (err) {
      return res.status(500).send({ error: 'File not found' });
    }
    const base64Data = Buffer.from(data).toString('base64');
    res.json({ resultData: base64Data });
  });
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

2.2 前端代码

前端代码需要对返回的 base64 数据进行解码,并将其转换为文件。

const downloadFile = async (type) => {
  try {
    const { data } = await api.aaaGetResult({
      aaa: route.params.aaa,
      id: id.value,
      fileType: type,
    });
    if (data && data.resultData) {
      const decodedData = decodeURIComponent(escape(window.atob(data.resultData)));
      const blob = new Blob([decodedData], {
        type: "application/json",
      });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.style.display = "none";
      a.href = url;
      a.download = "aaa_result_0." + type; //
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }
  } catch (error) {
    console.error("结果下载失败:", error);
  }
};

3. 新代码(流式下载)

3.1 后端接口

后端接口直接返回文件流,而不是 base64 编码的数据。

Node.js 示例

const express = require('express');
const fs = require('fs');
const path = require('path');

const app = express();
const PORT = 3000;

app.get('/api/download', (req, res) => {
  const filePath = path.join(__dirname, 'data/example.csv');

  // 设置响应头
  res.setHeader('Content-Type', 'text/csv');
  res.setHeader('Content-Disposition', 'attachment; filename="example.csv"');

  // 读取文件并返回流
  const stream = fs.createReadStream(filePath);
  stream.pipe(res);
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

3.2 前端代码

前端代码直接处理返回的文件流,无需解码。

const downloadFile = async (type) => {
  try {
    const response = await api.aaaGetResult({
      aaa: route.params.,
      id: id.value,
      fileType: type,
      responseType: "blob" // 设置响应类型为 blob
    });

    // 检查响应状态
    if (response.status === 200) {
      // 创建一个 Blob 对象
      const blob = new Blob([response.data], { type: "application/octet-stream" });
      // 创建一个 URL 对象
      const url = URL.createObjectURL(blob);
      // 创建一个 a 标签用于下载
      const a = document.createElement("a");
      a.style.display = "none";
      a.href = url;
      a.download = `aaa_result_0.${type}`; // 设置下载文件名
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
      URL.revokeObjectURL(url); // 释放 URL 对象
    } else {
      ElMessage.error("下载失败,服务器返回错误");
    }
  } catch (error) {
    console.error("结果下载失败:", error);
    ElMessage.error("下载失败,请稍后重试");
  }
};

4. 对比总结

4.1 数据格式

特性base64 编码下载流式下载
后端返回格式base64 编码的字符串直接返回文件流
前端处理需要解码 base64 数据直接处理文件流,无需解码
性能数据量较大时效率低数据量较大时效率高
安全性数据在传输过程中被编码,可能增加复杂性数据直接传输,减少中间处理步骤
易用性需要额外的解码逻辑简化了前端逻辑,直接处理文件

4.2 适用场景

  • base64 编码下载

    • 适用于数据量较小的场景。
    • 适用于需要对数据进行额外处理的场景。
  • 流式下载

    • 适用于数据量较大的文件。
    • 适用于需要高效传输文件的场景。
    • 适用于直接将文件保存到本地的场景。

5. 推荐使用流式下载

流式下载在处理大文件时具有显著的性能优势,并且简化了前端的处理逻辑。推荐在文件下载场景中优先使用流式下载。


前端实现下载流式主要有以下几种方法和方案: ### 使用 Fetch API Fetch API 可用于流式处理响应数据。其关键点在于处理响应的 `body`,它是一个 `ReadableStream` 对象。可以逐块读取响应数据并进行处理,实现流式输出。例如在流式处理时,可通过不断读取数据块来实现数据的逐步展示或下载 [^1]。 ```javascript fetch('your-streaming-url') .then(response => { const reader = response.body.getReader(); return new ReadableStream({ start(controller) { function read() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } controller.enqueue(value); read(); }); } read(); } }); }) .then(stream => new Response(stream)) .then(response => response.blob()) .then(blob => { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'filename'; a.click(); URL.revokeObjectURL(url); }); ``` ### 处理 SSE(Server - Sent Events) SSE 允许服务器向客户端发送实时更新。客户端通过 `EventSource` 对象建立与服务器的连接,服务器可以持续向客户端发送事件流。在下载流式场景中,服务器可将数据分成多个事件发送给客户端,客户端逐个处理这些事件实现流式下载 [^1]。 ```javascript const eventSource = new EventSource('your-sse-url'); eventSource.onmessage = function(event) { // 处理接收到的事件数据 const data = event.data; // 可以将数据进行处理或存储,用于后续下载 }; eventSource.onerror = function(error) { console.error('EventSource failed:', error); }; ``` ### 使用 WebSocket WebSocket 提供了全双工通信通道,可用于实现流式传输。客户端和服务器可以随时向对方发送数据。在下载流式场景中,服务器可以将数据分块发送给客户端,客户端接收并处理这些数据块 [^2]。 ```javascript const socket = new WebSocket('ws://your-websocket-url'); socket.onmessage = function(event) { const data = event.data; // 处理接收到的数据块,可用于下载 }; socket.onerror = function(error) { console.error('WebSocket error:', error); }; socket.onclose = function() { console.log('WebSocket connection closed'); }; ``` ### 框架实现方案 - **React 实现方案**:在 React 中可结合上述流式处理方法,将其封装在组件中。通过状态管理来更新界面,实现流式数据的展示和下载 [^1]。 ```jsx import React, { useEffect, useState } from 'react'; const StreamingDownload = () => { const [data, setData] = useState(''); useEffect(() => { fetch('your-streaming-url') .then(response => { const reader = response.body.getReader(); return new ReadableStream({ start(controller) { function read() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } controller.enqueue(value); read(); }); } read(); } }); }) .then(stream => new Response(stream)) .then(response => response.text()) .then(text => { setData(text); // 可在此处实现下载逻辑 }); }, []); return ( <div> <p>{data}</p> </div> ); }; export default StreamingDownload; ``` - **Vue 实现方案**:在 Vue 中同样可以使用上述流式处理方法,结合 Vue 的响应式原理更新界面。可以封装成组件,方便使用 [^1]。 ```vue <template> <div> <p>{{ data }}</p> </div> </template> <script> export default { data() { return { data: '' }; }, mounted() { fetch('your-streaming-url') .then(response => { const reader = response.body.getReader(); return new ReadableStream({ start(controller) { function read() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } controller.enqueue(value); read(); }); } read(); } }); }) .then(stream => new Response(stream)) .then(response => response.text()) .then(text => { this.data = text; // 可在此处实现下载逻辑 }); } }; </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值