前端性能优化之大文件上传

大文件上传是前端开发中常见的需求之一,特别是在需要处理较大的Excel表格数据、高清图片、视频或其他大型文件时。优化大文件上传不仅可以提升用户体验,还能有效减轻服务器负担。本文将深入探讨大文件上传的几种常见优化技术,包括文件切片与并发上传、断点续传、后台处理优化、安全性考虑和用户体验优化。

一、前言

在现代Web应用中,用户上传大文件已成为常见需求。然而,直接上传大文件会面临诸多挑战,例如网络不稳定导致上传中断、长时间上传导致用户体验差、服务器压力大等。因此,优化大文件上传性能显得尤为重要。

二、优化方案 

1. 文件切片与并发上传

1.1 文件切片原理

文件切片(Chunking)是将大文件分成若干小片段,每个片段独立上传的方法。这样做可以有效减少单次上传的数据量,降低上传失败的概率。

这种方法可以提高上传效率和稳定性,并且支持断点续传

1.2 实现步骤
  1. 前端切片:利用Blob对象的slice方法将文件切片。
  2. 并发上传:使用Promise.all实现多个切片并发上传。
  3. 合并请求:上传完成后,通知服务器合并这些切片。

2. 断点续传

断点续传(Resumable Uploads)可以在上传过程中断(如网络故障、页面关闭等)时,从断点继续上传,避免重新上传整个文件。这通常通过记录已上传的分片索引来实现。

2.1 实现步骤
  1. 前端记录进度:使用localStorage记录已上传的切片信息。这种方式不依赖于服务端,实现起来也比较方便,缺点在于如果用户清除了本地文件,会导致上传记录丢失
  2. 断点续传:上传时检查哪些切片未上传,继续上传未完成的部分。

3. 秒传功能

  • 在服务端已经存在了上传的资源时,通过文件hash值快速判断文件是否存在,从而避免重复上传,节省时间和流量。

已经上传过的文件,并且在后端已经拼接完成,如果再次上传的话后端不做处理,直接返回拼接好的文件的信息即可,y一般主要后端实现 

4. 基于WebWorker的并行处理

  • 使用WebWorker来并行计算文件的分片hash值,可以显著提高大文件处理的速度。

WebWorker 实际上是运行在浏览器后台的一个单独的线程,因此可以执行一些耗时的操作而不会阻塞主线程。WebWorker 通过与主线程之间传递消息实现通信,这种通信是双向的。WebWorker不能直接访问 DOM,也不能使用像 window 对象这样的浏览器接口对象,但可以使用一些WebWorker 标准接口和 Navigator 对象的部分属性和方法。

主线程

主线程创建 worker 实例,向子线程通过 postMessage 发送消息,通过 onmessage 监听子线程返回的数据。

  const myWorker = new Worker('./worker.js')
 
  // 监听子线程返回的数据
  myWorker.onmessage = function (e) {
    console.log('Fibonacci result:', e.data)
  }
 
  // 向子线程发送消息
  myWorker.postMessage(40) // 请求计算斐波那契数列的第40项

5. 压缩传输数据

  • 对传输的数据进行压缩处理,减少传输时间和带宽消耗

6. 分布式存储

  • 使用分布式存储系统,提高文件存储和访问的性能和可扩展性,减轻单个服务器的负载压力。

7. 后台处理优化

  1. 分片接收与合并:服务器需要支持接收分片请求,并在所有分片上传完成后合并文件。可以利用中间件或服务端程序语言实现这一逻辑。

8. 安全性考虑

  1. 文件类型校验:在前端和后端都应对文件类型进行校验,确保上传的文件类型符合预期。
  2. 文件大小限制:限制单个文件和总上传文件的大小,防止恶意用户上传过大的文件造成服务器压力。

9. 用户体验优化

  1. 进度显示:通过显示上传进度条,让用户了解上传进度,提升用户体验。
  2. 网络波动处理:考虑到用户可能在网络不稳定的环境中上传文件,可以增加失败重试机制。

完整实例

后端代码(Node.js + Express)

安装依赖

npm init -y
npm install express multer fs

 创建服务器文件(server.js)

const express = require('express');
const multer = require('multer');
const fs = require('fs');
const path = require('path');
const bodyParser = require('body-parser');

const app = express();
const upload = multer({ dest: 'uploads/' });
app.use(bodyParser.json());

// 路由:处理文件切片上传
app.post('/upload', upload.single('chunk'), (req, res) => {
  const { index, fileName } = req.body;
  const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${index}`);
  fs.renameSync(req.file.path, chunkPath);
  res.status(200).send('Chunk uploaded');
});

// 路由:合并切片
app.post('/merge', (req, res) => {
  const { totalChunks, fileName } = req.body;
  const filePath = path.join(__dirname, 'uploads', fileName);
  const writeStream = fs.createWriteStream(filePath);

  for (let i = 0; i < totalChunks; i++) {
    const chunkPath = path.join(__dirname, 'uploads', `${fileName}-${i}`);
    const data = fs.readFileSync(chunkPath);
    writeStream.write(data);
    fs.unlinkSync(chunkPath);
  }

  writeStream.end();
  res.status(200).send('File merged');
});

app.listen(3000, () => {
  console.log('Server started on http://localhost:3000');
});

前端代码(index.html + script.js)

1.创建HTML文件(index.html)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>大文件上传</title>
</head>
<body>
  <input type="file" id="fileInput">
  <progress id="progressBar" value="0" max="100"></progress>
  <button onclick="uploadFile()">上传文件</button>
  <script src="script.js"></script>
</body>
</html>

2.创建JavaScript文件(script.js)

const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar');
const chunkSize = 5 * 1024 * 1024; // 5MB 每次最大切片的长度

// 每个切片要发送的ajax
const uploadChunk = async (chunk, index, fileName) => {
  const formData = new FormData();
  formData.append('chunk', chunk);
  formData.append('index', index);
  formData.append('fileName', fileName);

  // 发送切片上传请求
  await fetch('/upload', {
    method: 'POST',
    body: formData
  });

  updateProgressBar(index);
};

const updateProgressBar = (index) => {
  const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || [];
  if (!uploadedChunks.includes(index)) {
    uploadedChunks.push(index);
    progressBar.value = (uploadedChunks.length / totalChunks) * 100;
    localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks));
  }
};

// 上传文件按钮事件
const uploadFile = async () => {
  const file = fileInput.files[0];
  const totalChunks = Math.ceil(file.size / chunkSize);
  const uploadedChunks = JSON.parse(localStorage.getItem('uploadedChunks')) || [];
  const promises = [];

  for (let i = 0; i < totalChunks; i++) {
    if (!uploadedChunks.includes(i)) {
      const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
      promises.push(uploadChunk(chunk, i, file.name));
    }
  }

  // 多个切片并发上传
  await Promise.all(promises);

  await fetch('/merge', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ totalChunks, fileName: file.name })
  });

  localStorage.removeItem('uploadedChunks');
  alert('文件上传成功');
};

启动后端服务器

3.在浏览器中打开前端页面

index.html文件在浏览器中打开,选择文件并点击“上传文件”按钮即可看到文件上传进度。

node server.js

 

三、插件

目前成熟的大文件上传方案 目前社区已经存在一些成熟的大文件上传解决方案,也许并不需要我们手动去实现一个简陋的大文件上传库,但是了解其原理还是十分有必要的。

推荐的前端vue组件:vue-simple-uploader,支持vue2,vue3

vue-simple-uploader是基于simple-Uploader.js封装的大文件上传组件,具有以下优点:

  1. 支持单文件、多文件、文件夹上传;支持拖拽文件、文件夹上传
  2. 可暂停、继续上传
  3. 错误处理
  4. 支持“秒传”,通过文件判断服务端是否已存在从而实现“秒传”
  5. 分块上传
  6. 支持进度、预估剩余时间、出错自动重试、重传等操作

 安装与配置

npm install vue-simple-uploader --save

然后在你的main.js中引入并使用它:

import Vue from 'vue';
import uploader from 'vue-simple-uploader';
Vue.use(uploader);

接下来,配置上传选项,这些选项可以根据你的后端接口和业务需求进行调整:

options: {
  target: ' http://localhost:8080', // SpringBoot后台接收文件夹数据的接口
  simultaneousUploads: 10,          // 支持同时上传数量
  autoStart: false,                 // 自动上传
  panelShow: false,
  allowDuplicateUploads: false,    // 上传过得文件不可以再上传
  testChunks: false,               // 是否分片-不分片
  chunkSize: '102400000000',       // 块大小
  // query参数是带有数据的post的额外参数,policy、OSSAccessKeyId和signature是获取到的后端签名返回,
  query: (file) => {
    return {
      name: file.name,
      key: file.key,
      policy,
      OSSAccessKeyId: accessId,
      signature,
      success_action_status: 200,  // success_action_status需设置为 200
    };
  },
}

常用方法与事件

vue-simple-uploader提供了多种方法和事件,以便于开发者根据需要进行自定义处理:

  • assignBrowse:将非组件按钮绑定为上传按钮。
  • getSize:获取上传文件的总大小。
  • progress:获取上传进度。
  • addFile:手动添加文件到上传队列。

事件处理包括但不限于:

  • fileAdded:文件添加到上传队列时触发。
  • filesAdded:多文件添加时触发。
  • fileSuccess:文件上传成功时触发。
  • complete:所有文件上传完成时触发。
  • fileError:文件上传失败时触发。

代码实现

以下是vue-simple-uploader组件的一个基本使用示例,包括组件声明、事件绑定和样式配置:

<template>
  <!-- 定义Uploader组件 -->
  <uploader
    :key="uploader_key"            <!-- 使用key确保组件在数据更新时重新渲染 -->
    :options="options"             <!-- 绑定配置项 -->
    class="uploader-example"       <!-- 添加自定义类名 -->
    @file-added="onFileAdded"      <!-- 文件添加时触发的事件 -->
    @file-success="onFileSuccess"  <!-- 文件上传成功时触发的事件 -->
    @upload-start="uploadStr"      <!-- 开始上传时触发的事件 -->
    @complete="uploadEnd"          <!-- 所有文件上传完成时触发的事件 -->
    @file-error="fileError"        <!-- 文件上传失败时触发的事件 -->
  >
    <!-- 定义不支持上传的提示 -->
    <uploader-unsupport></uploader-unsupport>
    <!-- 定义拖拽区域 -->
    <uploader-drop>
      <!-- 定义上传按钮,使用Element UI的按钮组件 -->
      <el-button class="uploaders-btn">
        <uploader-btn class="btn" :directory="true">  <!-- 设置为目录上传 -->
          <el-icon><Notification /></el-icon>         <!-- 使用Element UI的图标组件 -->
          <span>上传文件夹</span>                      <!-- 按钮文本 -->
        </uploader-btn>
      </el-button>
    </uploader-drop>
  </uploader>
</template>
 
<script>
import md5 from "js-md5";
export default {
  data() {
    return {
      // 用于刷新组件的key,每次上传时更改其值以刷新组件状态
      uploader_key: new Date().getTime(),
      // 配置项,根据后端接口和业务需求进行配置
      options: {
        //目标上传 URL,默认POST
        target: "/api/file/uploadFile", // 后端接收数据的接口
        //上传文件时文件内容的参数名,对应chunk里的Multipart对象名,默认对象名为file
        ileParameterName: 'upfile',
        //失败后最多自动重试上传次数
        maxChunkRetries: 3,
        query: (file, res, status) => {
          // 返回上传所需的额外参数
          return {
            filePath: "",
            identifier: md5(file.uniqueIdentifier),
            parentUserFileId: this.firstId,
            sourceMenuId: this.findId,
            uuid: this.uuid,
          };
        },
        headers: {
          "Blade-Auth": "bearer " + getToken(), // 认证信息
        },
        testChunks: true, // 不分片上传
        //分块大小(单位:字节)
        chunkSize: '2048000',
      },

      fileStatusText: {
        success: '上传成功',
        error: '上传失败',
        uploading: '上传中',
        paused: '暂停',
        waiting: '等待上传'
      }
    };
  },
  created() {
    // 组件创建时初始化options
    this.options = {
      // ... 具体配置
    };
  },
  methods: {
    // 文件添加到上传队列时的处理函数
    onFileAdded(file) {
      console.log("文件添加到队列:", file);
      // 每次添加文件时生成新的uuid
      this.uuid = new Date().getTime();
    },
    // 文件上传成功时的处理函数
    onFileSuccess(rootFile, file, response, chunk) {
      console.log("文件上传成功:", file, response);
      // 根据服务器返回的response处理业务逻辑
    },
    // 文件上传失败时的处理函数
    fileError(rootFile, file, response, chunk) {
      console.error("文件上传失败:", file, response);
      // 显示错误信息
      this.$message.error("文件夹上传失败");
    },
    // 开始上传时的处理函数
    uploadStr() {
      this.loadingFile = true; // 设置加载状态
    },
    // 所有文件上传完成时的处理函数
    uploadEnd() {
      this.loadingFile = false; // 重置加载状态
    },
  },
};
</script>
 
<style lang="scss" scoped>
/* 自定义样式 */
.uploader-example {
  .uploaders-btn {
    /* 按钮样式 */
  }
  .btn {
    /* 上传按钮内的图标和文本样式 */
  }
}
</style>

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/942581.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构之线性表之顺序表

定义&#xff1a; 由n&#xff08;n>0&#xff09;个数据特性相同的元素构成的有限序列称为线性表 简单来说n个相同数据类型的数据组wsw合在一起的这么一个集合就是一个线性表 线性表包括顺序表和链表 1. 顺序表&#xff08;我们所有的代码实现都用函数来封装&#xff09…

Matlab 和 R 语言的数组索引都是从 1 开始,并且是左闭右闭的

文章目录 一、前言二、主要内容三、小结 &#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、前言 在早期的计算机科学中&#xff0c;数组索引从 1 开始是很常见的。例如&#xff0c;Fortran 和 Pascal 等编程语言也采用了从 1 开始的索引。 这种索引…

突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除

GitLab停止为中国大陆、香港和澳门地区提供服务&#xff0c;要求用户在60天内迁移账号&#xff0c;否则将被删除。这一事件即将引起广泛的关注和讨论。以下是对该事件的扩展信息&#xff1a; 1. 背景介绍&#xff1a;GitLab是一家全球知名的软件开发平台&#xff0c;提供代码托…

瑞吉外卖项目学习笔记(八)修改菜品信息、批量启售/停售菜品

瑞吉外卖项目学习笔记(一)准备工作、员工登录功能实现 瑞吉外卖项目学习笔记(二)Swagger、logback、表单校验和参数打印功能的实现 瑞吉外卖项目学习笔记(三)过滤器实现登录校验、添加员工、分页查询员工信息 瑞吉外卖项目学习笔记(四)TableField(fill FieldFill.INSERT)公共字…

C++小碗菜之五:关键字static

“一个人的命运啊&#xff0c;当然要靠自我奋斗&#xff0c;但也要考虑到历史的行程。” ——2009年4月23日在视察中国联合工程公司时的讲话 目录 ​编辑 前言 static在局部作用域中的作用 给出例子&#xff1a; 修改上面给出的例子&#xff1a; 为什么不使用全局变量…

开源云原生数据仓库ByConity ELT 的测试体验

ByConity 是分布式的云原生SQL数仓引擎&#xff0c;擅长交互式查询和即席查询&#xff0c;具有支持多表关联复杂查询、集群扩容无感、离线批数据和实时数据流统一汇总等特点。 ByConity 是一个云原生的、高性能的实时数据仓库&#xff0c;而 ELT&#xff08;Extract&#xff0c…

Linux -- 线程的优点、pthread 线程库

目录 线程的优点 pthread 线程库 前言 认识线程库 简单验证线程的独立栈空间 线程的优点 与进程之间的切换相比&#xff0c;线程之间的切换需要操作系统做的工作要少得多。 调度进程时&#xff0c;CPU 中有一个 cache&#xff08;缓存&#xff0c;提高运行效率&#xff0…

数字IC前端学习笔记:脉动阵列的设计方法学(四)

相关阅读 数字IC前端https://blog.csdn.net/weixin_45791458/category_12173698.html?spm1001.2014.3001.5482 引言 脉动结构&#xff08;也称为脉动阵列&#xff09;表示一种有节奏地计算并通过系统传输数据的处理单元(PEs)网络。这些处理单元有规律地泵入泵出数据以保持规则…

观察者模式和发布-订阅模式有什么异同?它们在哪些情况下会被使用?

大家好&#xff0c;我是锋哥。今天分享关于【观察者模式和发布-订阅模式有什么异同&#xff1f;它们在哪些情况下会被使用&#xff1f;】面试题。希望对大家有帮助&#xff1b; 观察者模式和发布-订阅模式有什么异同&#xff1f;它们在哪些情况下会被使用&#xff1f; 1000道 …

【LeetCode】726、原子的数量

【LeetCode】726、原子的数量 文章目录 一、递归: 嵌套类问题1.1 递归: 嵌套类问题 二、多语言解法 一、递归: 嵌套类问题 1.1 递归: 嵌套类问题 遇到 ( 括号, 则递归计算子问题 遇到大写字母, 或遇到 ( 括号, 则清算历史, 并开始新的记录 记录由两部分组成: 大写字母开头的 …

【Select 语法全解密】.NET开源ORM框架 SqlSugar 系列

系列文章目录 &#x1f380;&#x1f380;&#x1f380; .NET开源 ORM 框架 SqlSugar 系列 &#x1f380;&#x1f380;&#x1f380; 文章目录 系列文章目录前言一、Select 执行位置二、返回一个字段和多个字段三、单表返回DTO四、多表返回DTO4.1 手动DTO4.2 实体自动映射14.…

WebRTC服务质量(11)- Pacer机制(03) IntervalBudget

WebRTC服务质量&#xff08;01&#xff09;- Qos概述 WebRTC服务质量&#xff08;02&#xff09;- RTP协议 WebRTC服务质量&#xff08;03&#xff09;- RTCP协议 WebRTC服务质量&#xff08;04&#xff09;- 重传机制&#xff08;01) RTX NACK概述 WebRTC服务质量&#xff08;…

分布式协同 - 分布式事务_2PC 3PC解决方案

文章目录 导图Pre2PC&#xff08;Two-Phase Commit&#xff09;协议准备阶段提交阶段情况 1&#xff1a;只要有一个事务参与者反馈未就绪&#xff08;no ready&#xff09;&#xff0c;事务协调者就会回滚事务情况 2&#xff1a;当所有事务参与者均反馈就绪&#xff08;ready&a…

计算机图形学知识点汇总

一、计算机图形学定义与内容 1.图形 图形分为“图”和“形”两部分。 其中&#xff0c;“形”指形体或形状&#xff0c;存在于客观世界和虚拟世界&#xff0c;它的本质是“表示”&#xff1b;而图则是包含几何信息与属性信息的点、线等基本图元构成的画面&#xff0c;用于表达…

Nginx区分PC端和移动端访问

在使用Nginx时&#xff0c;可以通过$http_user_agent变量来判断用户访问的客户端类型&#xff0c;从而提供不同的内容或服务。下面是一个基于$http_user_agent变量来判断是否为PC访问的Nginx配置示例。 1. 理解$http_user_agent变量的含义及其在Nginx中的用途 $http_user_agen…

方法。。。

1. 方法概述 1.1 方法的概念 ​** 方法&#xff08;method&#xff09;是程序中最小的执行单元** 注意&#xff1a; 方法必须先创建才可以使用&#xff0c;该过程成为方法定义方法创建后并不是直接可以运行的&#xff0c;需要手动使用后&#xff0c;才执行&#xff0c;该过程…

jasypt原理

jasypt原理 一、背景知识二、原理分析1、(uml中蓝色)加载Encryptor、Detector和Resolver2、(uml中红色)加载EnableEncryptablePropertiesBeanFactoryPostProcessor3、(uml中绿色)解密过程 以jasypt 1.14为例 一、背景知识 需要了解spring的加载顺序&#xff1a; step1:主要是…

【UE5 C++课程系列笔记】13——GameInstanceSubsystem的简单使用

目录 概念 基本使用案例 效果 步骤 概念 UGameInstanceSubsystem 类继承自 USubsystem&#xff0c;它与 GameInstance 紧密关联&#xff0c;旨在为游戏提供一种模块化、可方便扩展和管理的功能单元机制。在整个游戏运行期间&#xff0c;一个 GameInstance 可以包含多个 UGa…

SpringCloud 系列教程:微服务的未来(二)Mybatis-Plus的条件构造器、自定义SQL、Service接口基本用法

本篇博客将深入探讨 MyBatis-Plus 的三个核心功能&#xff1a;条件构造器、自定义 SQL 和 Service 接口的基本用法。通过对这些功能的学习和掌握&#xff0c;开发者能够更加高效地使用 MyBatis-Plus 进行业务开发。 目录 前言 条件构造器 自定义SQL Service接口基本用法 总结…

我的 2024 年终总结

2024 年&#xff0c;我离开了待了两年的互联网公司&#xff0c;来到了一家聚焦教育机器人和激光切割机的公司&#xff0c;没错&#xff0c;是一家硬件公司&#xff0c;从未接触过的领域&#xff0c;但这还不是我今年最重要的里程碑事件 5 月份的时候&#xff0c;正式提出了离职…