当前位置: 首页 > news >正文

大文件分片上传(前端TS实现)

大文件分片上传

内容

一般情况下,前端上传文件就是new FormData,然后把文件 append 进去,然后post发送给后端就完事了,但是文件越大,上传的文件也就越长,如果在上传过程中,突然网络故障,又或者请求超时,等待过久等等情况,就会导致错误而后又得重新传大文件。所以这时候就要使用分片上传了,就算断网了也能继续接着上传(断点上传),如果是之前上传过这个文件了(服务器还存着),就不需要做二次上传了(秒传)。

7.17实现方式

首先获取文件信息后设定分片大小对文件进行分片(slice函数),而后为文件生成一个hash对文件进行标注。在请求时先验证所上传的文件是否已经存在于服务器(若是则直接提示上传成功,即秒传功能),若不存在或部分存在则需要后端返回上传成功的分片标识数组,前端将使用成功分片数组与原文件分片数组进行处理得到未上传成功的分片,而后将未成功的分片以并发方式上传至后端。上传后即完成了整个文件的上传,向后端发送合并分片请求即完成大文件分片上传,断点续传功能。

7.19实现方式(即彻底完成功能)

步骤:

  1. 由于前端计算md5耗时过长,可能会导致页面卡死,因此考虑使用Web Worker来计算md5,即使用worker.js(使用spark-md5)文件来计算:分片数组,分片哈希数组,文件整体哈希。
  2. 在拿到web worker所得到的分片数组,分片哈希数组,文件整体哈希后,即可开始进行大文件上传工作,前端使用文件整体哈希、文件名以及分片数组作为请求参数调用后端/init接口初始化上传操作。【initSend函数】。
  3. 初始化操作完成后,前端使用文件整体哈希作为参数调用后端/status接口获取此文件分片的状态信息,前端根据后端所返回的状态信息使用filter以及every方法得到:文件是否已经上传的状态existFile、后端存在的文件分片existChunks。若existFile为true,则直接提示上传成功,即秒传功能。【verifyInfo函数】
  4. 若existFile为false,则使用后端返回的existChunks数组与分片数组进行对比,得到未上传成功的分片数组,并将其分片信息(分片哈希值,分片内容,分片序号以及文件整体哈希值)作为formData参数发送至后端/chunk接口(在发送分片时使用并发操作,并限制最大并发数为6)【uploadChunks函数】
  5. 当所有分片发送完成后,前端给后端以文件整体哈希做为参数调用后端/merge接口提示后端可以进行合并操作,后端返回成功消息即完成大文件分片上传,断点续传功能。【mergeFile函数】

演示图

  1. 选择文件:
    在这里插入图片描述

  2. 秒传:
    在这里插入图片描述

  3. 分片上传:
    在这里插入图片描述


代码内容

在后续操作中,完成了对请求的封装以及分片上传的hook的编写,主处理逻辑部分仅仅为下方所示:

const submitUpload = () => {const file = FileInfo.value;if(!file) {return;}fileName.value = file.name;const { mainDeal } = useUpload(file, fileName.value)mainDeal()
}

api的封装为下方所示:

import request from "@/utils/request";export async function initSend(uploadId, fileName, totalChunk) {return request({url: "/init",method: "POST",data: {uploadId,fileName,totalChunk,},});
}export async function verifyInfo(uploadId) {return request({url: "/status",method: "POST",data: {uploadId,},headers: { "Content-Type": "application/x-www-form-urlencoded" },});
}
export async function uploadSingle(formData) {return request({url: "/chunk",method: "POST",data: {formData,},headers: {"Content-Type": "multipart/form-data",},});
}
export async function mergeFile(uploadId) {return request({url: "/merge",method: "POST",data: {uploadId,},headers: {"Content-Type": "application/x-www-form-urlencoded",},});
}

useUpload钩子函数为:

import { ref } from "vue";
import axios from "axios";
import type { AxiosResponse } from "axios";
import {initSend,verifyInfo,mergeFile,uploadSingle,
} from "@/apis/uploadApi";
export function useUpload(fileInfo, filename) {const FileInfo = ref<File>(fileInfo);const fileName = ref(filename); // 文件名称const fileHash = ref(""); // 文件hashconst fileHashArr = ref([]);const chunks = ref([]);interface Verify {id?: Number;uploadId?: String;chunkIndex?: Number;status?: String;}const mainDeal = async () => {const worker = new Worker(new URL("@/utils/worker.js", import.meta.url), {type: "module",});const file = FileInfo.value;console.log(worker);console.log("file_info", file);worker.postMessage({ file: file });worker.onmessage = async (e) => {const { data } = e;chunks.value = data.fileChunkList;fileHashArr.value = data.fileChunkHashList;fileHash.value = data.fileMd5;console.log("uploadid", fileHash.value);const res_init = await initSend(fileHash.value,fileName.value,chunks.value.length);console.log("res_init", res_init);const { existFile, existChunks } = await verify(fileHash.value);if (existFile) return;uploadChunks(chunks.value, existChunks, fileHashArr.value);worker.terminate();};};// 控制请求并发const concurRequest = (taskPool: Array<() => Promise<Response>>,max: number): Promise<Array<Response | unknown>> => {return new Promise((resolve) => {if (taskPool.length === 0) {resolve([]);return;}console.log("taskPool", taskPool);const results: Array<Response | unknown> = [];let index = 0;let count = 0;console.log("results_before", results);const request = async () => {if (index === taskPool.length) return;const i = index;const task = taskPool[index];index++;try {results[i] = await task();console.log("results_try", results);} catch (err) {results[i] = err;} finally {count++;if (count === taskPool.length) {resolve(results);}request();}};const times = Math.min(max, taskPool.length);for (let i = 0; i < times; i++) {request();}});};// 合并分片请求const mergeRequest = async () => {return mergeFile(fileHash.value);};// 上传文件分片const uploadChunks = async (chunks: Array<Blob>,existChunks: Array<string>,md5Arr: Array<string>) => {const formDatas = chunks.map((chunk, index) => ({fileHash: fileHash.value,chunkHash: fileHash.value + "-" + index,chunkIndex: index,checksum: md5Arr[index],chunk,})).filter((item) => !existChunks.includes(item.chunkHash));console.log("formDatas", formDatas);const form_Datas = formDatas.map((item) => {console.log("!", item.chunkIndex);const formData = new FormData();formData.append("uploadId", item.fileHash);formData.append("chunkIndex", String(item.chunkIndex));formData.append("checksum", item.checksum);formData.append("file", item.chunk);return formData;});console.log("formDatas", form_Datas);const taskPool = form_Datas.map((formData) => () =>fetch("http://10.184.131.57:8101/ferret/upload/chunk", {method: "POST",body: formData,}));//控制请求并发const response = await concurRequest(taskPool, 6);console.log("response", response);// 合并分片请求const res_merge = await mergeRequest();console.log("res_merge", res_merge);};// 校验文件、文件分片是否存在const verify = async (uploadId: string) => {const res = await verifyInfo(uploadId);const { data } = res.data;console.log("verify", res);// 看服务器是不是已经有文件所有信息const existFile = data.every((item: Verify) => item.status === "Uploaded");const existChunks: string[] = [];data.filter((item: Verify) => {if (item.status === "Uploaded") {existChunks.push(`${item.uploadId}-${item.chunkIndex}`);}});console.log("existFile", existFile, "existChunks", existChunks);return {existFile,existChunks,};};return {mainDeal,};
}

web worker实现方式:

import SparkMD5 from 'spark-md5';
let DefaultChunkSize = 1024 * 1024 * 5; // 5MBself.onmessage = (e) => {console.log("!!>", e.data)if (e.data.file.size >= 1024 * 1024 * 100 && e.data.file.size < 1024 * 1024 * 512) {DefaultChunkSize = 1024 * 1024 * 10}else if (e.data.file.size >= 1024 * 1024 * 512) {DefaultChunkSize = 1024 * 1024 * 50}const { file, chunkSize = DefaultChunkSize } = e.data;let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,chunks = Math.ceil(file.size / chunkSize),currentChunk = 0,spark = new SparkMD5.ArrayBuffer(),fileChunkHashList = [],fileChunkList = [],fileReader = new FileReader();loadNext();function loadNext() {let start = currentChunk * chunkSize,end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;let chunk = blobSlice.call(file, start, end);fileChunkList.push(chunk);fileReader.readAsArrayBuffer(chunk);}function getChunkHash(e) {const chunkSpark = new SparkMD5.ArrayBuffer();chunkSpark.append(e.target.result);fileChunkHashList.push(chunkSpark.end());}// 处理每一块的分片fileReader.onload = function (e) {spark.append(e.target.result);currentChunk++;getChunkHash(e)if (currentChunk < chunks) {loadNext();} else {// 计算完成后,返回结果self.postMessage({fileMd5: spark.end(),fileChunkList,fileChunkHashList,});fileReader.abort();fileReader = null;}}// 读取失败fileReader.onerror = function () {self.postMessage({error: 'wrong'});}
};

相关文章:

大文件分片上传(前端TS实现)

大文件分片上传 内容 一般情况下&#xff0c;前端上传文件就是new FormData,然后把文件 append 进去&#xff0c;然后post发送给后端就完事了&#xff0c;但是文件越大&#xff0c;上传的文件也就越长&#xff0c;如果在上传过程中&#xff0c;突然网络故障&#xff0c;又或者…...

unity2D游戏开发02添加组件移动玩家

添加组件 给PlayGame和EnemyObject添加组件BoxCollider 2D碰撞器&#xff0c;不用修改参数 给PlayGame添加组件Rigibody 2D 设置数据 添加EnemyObject&#xff0c;属性如下 Edit->project setting->Physics 2D 将 y的值改为0 给playerObject添加标签 新建层 将PlayerObj…...

设计模式 之 —— 单例模式

目录 什么是单例模式&#xff1f; 定义 单例模式的主要特点 单例模式的几种设计模式 1.懒汉式&#xff1a;线程不安全 2.懒汉式&#xff1a;线程安全 3.饿汉式 4.双重校验锁 单例模式的优缺点 优点&#xff1a; 缺点&#xff1a; 适用场景&#xff1a; 什么是单例模…...

深入浅出WebRTC—ULPFEC

FEC 通过在发送端添加额外的冗余信息&#xff0c;使接收端即使在部分数据包丢失的情况下也能恢复原始数据&#xff0c;从而减轻网络丢包的影响。在 WebRTC 中&#xff0c;FEC 主要有两种实现方式&#xff1a;ULPFEC 和 FlexFEC&#xff0c;FlexFEC 是 ULPFEC 的扩展和升级&…...

Python从0到100(四十三):数据库与Django ORM 精讲

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...

Redis-主从模式

目录 前言 一.主从节点介绍 二.配置redis主从结构 二.主从复制 四.拓扑结构 五.数据同步 全量复制&#xff08;Full Sync Replication&#xff09; 局部复制&#xff08;Partial Replication&#xff09; Redis的学习专栏&#xff1a;http://t.csdnimg.cn/a8cvV 前言 …...

加速决策过程:企业级爬虫平台的实时数据分析

摘要 在当今数据驱动的商业环境中&#xff0c;企业如何才能在海量信息中迅速做出精准决策&#xff1f;本文将探讨企业级爬虫平台如何通过实时数据分析加速决策过程&#xff0c;实现数据到决策的无缝衔接。我们聚焦于技术如何赋能企业&#xff0c;提升数据处理效率&#xff0c;…...

字典树(前缀树)数组实现(只能查26个单词)

这段代码实现了一个基于 Trie 树的字典树&#xff08;Trie&#xff09;数据结构&#xff0c;用于存储和检索字符串。其中包含以下几个方法. insert(String word): 向 Trie 树中插入一个单词。首先将单词转换为字符数组&#xff0c;然后遍历字符数组&#xff0c;逐个字符在 Trie…...

CTF-pwn-虚拟化-vmmware 前置

文章目录 参考vmware逃逸简介虚拟机和主机通信机制(guest to host)共享内存&#xff08;弃用&#xff09;backdoor机制Message_Send和Message_RecvGuestRPC实例RpcOutSendOneRawWork实例 vmware-rpctool info-get guestinfo.ip各个步骤对应的backdoor操作Open RPC channelSend …...

thinkphp8结合layui2.9 图片上传验证

<?php declare (strict_types 1);namespace app\index\validate;use think\Validate;class Upload extends Validate {/*** 定义验证规则* 格式&#xff1a;字段名 > [规则1,规则2...]** var array*/protected $rule [image > fileExt:jpg,png|fileSize:204800|fi…...

农村污水处理难题:探索低成本高效解决方案

农村污水处理难题&#xff1a;探索低成本高效解决方案 农村污水处理作为国家生态文明建设的重要一环&#xff0c;面临着诸多挑战&#xff0c;尤其是技术落后、管理分散、资源匮乏等问题。物联网技术的引入&#xff0c;为解决这些痛点提供了创新途径&#xff0c;实现了对污水处…...

lightningcss介绍及使用

lightningcss介绍及使用 一款使用 rust 编写的 css 解析器&#xff0c;转换器、及压缩器。 特性 特别快&#xff1a;可以在毫秒级别解析、压缩大量的 css 文件&#xff0c;而且比其他工具的打包结果更小给值添加类型&#xff1a;许多其他css解析器会将值解析成一个无类型的 …...

HTTP服务的应用

1、编辑json请求参数&#xff1b; 2、把json发送到服务url&#xff0c;接收服务的返回参数&#xff1b; 3、解析返回参数。 procedure TfrmCustomQuery.btnFullUpdateClick(Sender: TObject); varfrm: TfrmInputQueryConditionEX;b_OK: Boolean;sBeginDate, sEndDate, sJSON…...

uni-app:踩坑路---scroll-view内使用fixed定位,无效的问题

前言&#xff1a; emmm&#xff0c;说起来这个问题整得还挺好笑的&#xff0c;本人在公司内&#xff0c;奋笔疾书写代码&#xff0c;愉快的提交测试的时候&#xff0c;测试跟我说&#xff0c;在苹果手机上你这个样式有bug&#xff0c;我倒是要看看&#xff0c;是什么bug。 安卓…...

MySQL4.索引及视图

1.建库 create database mydb15_indexstu; use mydb15_indexstu;2.建表 2.1 student表学&#xff08;sno&#xff09;号为主键&#xff0c;姓名&#xff08;sname&#xff09;不能重名&#xff0c;性别&#xff08;ssex&#xff09;仅能输入男或女&#xff0c;默认所在系别&a…...

MongoDB - 聚合阶段 $match、$sort、$limit

文章目录 1. $match 聚合阶段1. 构造测试数据2. $match 示例3. $match 示例 2. $sort 聚合阶段1. 排序一致性问题2. $sort 示例 3. $limit 聚合阶段 1. $match 聚合阶段 $match 接受一个指定查询条件的文档。 $match 阶段语法&#xff1a; { $match: { <query> } }$ma…...

ModuleNotFoundError: No module named ‘scrapy.utils.reqser‘

在scrapy中使用scrapy-rabbitmq-scheduler会出现报错 ModuleNotFoundError: No module named scrapy.utils.reqser原因是新的版本的scrapy已经摒弃了该方法,但是scrapy-rabbitmq-scheduler 没有及时的更新,所以此时有两种解决方法 方法一.将scrapy回退至旧版本,找到对应的旧版…...

vue3+ts+vite+electron+electron-packager打包成exe文件

目录 1、创建vite项目 2、添加需求文件 3、根据package.json文件安装依赖 4、打包 5、electron命令运行 6、electron-packager打包成exe文件 Build cross-platform desktop apps with JavaScript, HTML, and CSS | Electron 1、创建vite项目 npm create vitelatest 2、添…...

使用脚本搭建MySQL数据库基础环境

数据库的基本概念 数据&#xff08;Data&#xff09; 描述事物的符号记录 包括数字&#xff0c;文字&#xff0c;图形。图像&#xff0c;声音&#xff0c;档案记录等。 以记录形式按统一格式进行存储 表 将不同的记录组织在一起 用来储存具体数据 数据库 表的集合&#xff0c;是…...

Parameter index out of range (2 > number of parameters, which is 1【已解决】

文章目录 1、SysLogMapper.xml添加注释导致的2、解决方法3、总结 1、SysLogMapper.xml添加注释导致的 <!--定义一个查询方法&#xff0c;用于获取日志列表--><!--方法ID为getLogList&#xff0c;返回类型com.main.server.api.model.SysLogModel,参数类型为com.main.se…...

rk3588s 定制版 USB adb , USB2.0与USB3.0 区别,adb 由typeC 转换到USB3.0(第二部分)

硬件资源&#xff1a; rk3588s 核心板定制的地板 软件资源&#xff1a; 网盘上的 android12 源码 1 硬件上 客户只想使用 type c 接口中的 usb2.0 OTG 。在硬件上&#xff0c;甚至连 CC芯片都没有连接。 关于一些前置的知识。 1 USB2.0 与 USB3.0 的区别。 usb3.0 兼容2.0 …...

Cookie与Session 实现登录操作

Cookie Cookie 是网络编程中使用最广泛的一项技术&#xff0c;主要用于辨识用户身份。 客户端&#xff08;浏览器&#xff09;与网站服务端通讯的过程如下图所示&#xff1a; 从图中看&#xff0c;服务端既要返回 Cookie 给客户端&#xff0c;也要读取客户端提交的 Cookie。所…...

通过IEC104转MQTT网关轻松接入阿里云平台

随着智能电网和物联网技术的飞速发展&#xff0c;电力系统中的传统IEC 104协议设备正面临向现代化、智能化转型的迫切需求。阿里云作为全球领先的云计算服务提供商&#xff0c;其强大的物联网平台为IEC 104设备的接入与数据处理提供了强大的支持。本文将深入探讨钡铼网关在MQTT…...

lua 游戏架构 之 游戏 AI (五)ai_autofight_find_way

这段Lua脚本定义了一个名为 ai_autofight_find_way 的类&#xff0c;继承自 ai_base 类。 lua 游戏架构 之 游戏 AI &#xff08;一&#xff09;ai_base-CSDN博客文章浏览阅读238次。定义了一套接口和属性&#xff0c;可以基于这个基础类派生出具有特定行为的AI组件。例如&…...

vue3+openLayers点击标记事件

<template><!--地图--><div class"distributeMap" id"distributeMap"></div> </template> <script lang"ts" setup> import { onMounted, reactive } from "vue"; import { Feature, Map, View }…...

深入分析 Android ContentProvider (三)

文章目录 深入分析 Android ContentProvider (三)ContentProvider 的高级使用和性能优化1. 高级使用场景1.1. 数据分页加载示例&#xff1a;分页加载 1.2. 使用 Loader 实现异步加载示例&#xff1a;使用 CursorLoader 加载数据 1.3. ContentProvider 与权限管理示例&#xff1…...

养宠浮毛异味双困扰?性价比高的宠物空气净化器推荐

家里养了两只银渐层&#xff0c;谁懂啊&#xff01;一下班打开家门就看到家里飘满了猫浮毛雪&#xff0c;空气中还传来隐隐约约的异味。每天不是在吸毛的路上&#xff0c;就是在洗猫砂盆的路上&#xff0c;而且空气中的浮毛还很难清理干净&#xff0c;这是最让人头疼的问题。 …...

maven项目容器化运行之3-优雅的利用Jenkins和maven使用docker插件调用远程docker构建服务并在1Panel中运行

一.背景 在《maven项目容器化运行之1》中&#xff0c;我们开启了1Panel环境中docker构建服务给到了局域网。在《maven项目容器化运行之2》中&#xff0c;我们基本实现了maven工程创建、远程调用docker构建镜像、在1Panel选择镜像运行容器三大步骤。 但是&#xff0c;存在一个问…...

docker 打包orbbec

docker pull humble容器 sudo docker run -it osrf/ros:humble-desktop docker 启动容器 sudo docker run -u root --device/dev/bus/usb:/dev/bus/usb -it -v /home/wl:/share --name wl4 osrf/ros:humble-desktop /bin/bash新开一个终端 查看本地存在的容器&#xff1a;…...

无涯·问知财报解读,辅助更加明智的决策

财报解读就像是给公司做一次全面的体检&#xff0c;是理解公司内部运作机制和市场表现的一把钥匙&#xff0c;能够有效帮助投资者、分析师、管理层以及所有市场参与者判断一家公司的健康程度和发展潜力。 星环科技无涯问知的财经库内置了企业年报及财经类信息&#xff0c;并对…...