首页 > 其他分享 >面试常考--前端性能优化之大文件上传

面试常考--前端性能优化之大文件上传

时间:2024-07-24 11:51:46浏览次数:8  
标签:文件 const index -- 常考 切片 uploadedChunks 上传

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

1. 前言

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

2. 文件切片与并发上传

2.1 文件切片原理

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

2.2 实现步骤

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

3. 断点续传

断点续传(Resumable Uploads)可以在上传过程中断时,从断点继续上传,避免重新上传整个文件。

3.1 实现步骤

  1. 前端记录进度:使用localStorage记录已上传的切片信息。
  2. 断点续传:上传时检查哪些切片未上传,继续上传未完成的部分。

4. 后台处理优化

4.1 分片接收与合并

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

 

5. 安全性考虑

5.1 文件类型校验

在前端和后端都应对文件类型进行校验,确保上传的文件类型符合预期。

5.2 文件大小限制

限制单个文件和总上传文件的大小,防止恶意用户上传过大的文件造成服务器压力。

6. 用户体验优化

6.1 进度显示

通过显示上传进度条,让用户了解上传进度,提升用户体验。

 

6.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>

 

  1. 创建JavaScript文件(script.js)
const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar');
const chunkSize = 5 * 1024 * 1024; // 5MB

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('文件上传成功');
};

 

启动后端服务器

 

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

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

node server.js

 

 

标签:文件,const,index,--,常考,切片,uploadedChunks,上传
From: https://www.cnblogs.com/zx618/p/18320558

相关文章

  • 2024暑假集训总结
    2024暑假集训总结知识点清单:树状数组拓展:(1)k维前缀和(2)树状数组+倍增没码过,小慌线段树:(1)线段树不仅仅是一个维护区间和、区间最值或者类似于方差那道题,维护区间的平方等等信息,它的深层是将区间拆分为\(O(logn)\)个子区间从而将修改与查询降为\(O(logn)\)级别,因此对于线......
  • [OI] 容斥原理拓展
    10.容斥原理拓展10.1二项式反演\[P.10.1(1)\]设\(U=\{S_1,S_2,S_3...S_n\}\),且任意\(i\)个元素的交集都相等定义\(g(x)\)为\(x\)个集合的交集,\(f(x)\)为\(x\)个集合补集的交集(定义\(f(0)=g(0)=U\)),则:\[\mid\bigcap^{n}_{i}S_{i}\mid=\midU\mid+\sum_{i}\{(-1)^{......
  • [十万个为什么] [lua] 自定义byte_buffer
    #include"lprefix.h"#include"lua.h"#include"lauxlib.h"#include"lualib.h"#defineBYTE_BUFFER_META_TABLE_NAME"byte_buffer*"#defineGET_BYTE_BUFFER(L)((byte_buffer_t*)luaL_checkudata(L,1,BYTE_......
  • 如何使用 C# 检查用户是否安装了最低 Python 版本并且可以访问我的代码?
    我正在开发一个C#程序,该程序必须为一项特定任务运行一些Python代码。(Python代码很复杂,是由另一个团队开发的。无法在C#中重现其功能。)我正在尝试更新我的程序的安装程序文件以解决此问题:我希望它检查用户是否(谁正在安装我的程序)已安装Python并且它满足我的最低版......
  • 如何优雅地将复杂的Python对象和SQLAlchemy对象模型类结合起来?
    我有一个相当复杂的类,具有从提供的df到init计算的复杂属性,这些属性可能是最终可以序列化为字符串的其他类类型。在Python中,我想处理对象而不是原始类型,但也想使用SQLAlchemy与数据库交互。表中的列与许多类属性相同,如何优雅地组合这两个类?我可以使用组合并将数据......
  • CF547D Mike and Fish 题解
    Description给定\(n\)个整点。你要给每个点染成红色或蓝色。要求同一水平线或垂直线上两种颜色的数量最多相差\(1\)。\(n,x_i,y_i\le2\times10^5\)。Solution考虑给每条水平线和垂直线建一个点,对于每个整点就把它对应的两条线连一条边。题目就转化为了给每条边定......
  • Ubuntu搭建Vulhub靶场
    Step1首先安装docker和docker-compose,参考其他教程安装完成后查看当前版本判断是否安装成功Step21.安装vulhub靶场选择一个合适的位置执行如下代码(我放在/root/vulhub),进行克隆下载gitclonehttps://gitee.com/puier/vulhub.git下载成功2.编译并运行靶场随便选择......
  • 2024牛客多校第三场
    磨合上升期,爽!B队友做的#include<bits/stdc++.h>usingnamespacestd;#defineintlonglonginlineintread(){intx=0;boolf=1;charch=getchar();for(;ch<'0'||ch>'9';ch=getchar())f^=(ch=='-');for(;ch>=&#......
  • Sentinel使用
    Sentinel的作用用于流量控制、熔断降级等安装与简单使用git下载Sentinel控制台https://github.com/alibaba/Sentinel/releases/tag/1.8.3,下载后直接使用java-jar命令启动项目引入sentinel依赖<dependency><groupId>com.alibaba.cloud</groupId><artifact......
  • JavaScript中的new map()和new set()使用详细(new map()和new set()的区别)
    简介:newMap():在JavaScript中,newMap()用于创建一个新的Map对象。Map对象是一种键值对的集合,其中的键是唯一的,值可以重复。newSet():在JavaScript中,newSet()是用来创建一个新的Set对象的语法。Set对象是一种集合,其中的值是唯一的,没有重复的值。newSet()可以用......