原生 JavaScript 实现大文件分片并发上传

本文介绍了一种使用原生JavaScript实现大文件分片并发上传的方法,包括分片上传的优势、验证秒传的策略(如计算文件hash值)、断点续传的实现,以及前端和后端的具体处理流程。通过并发上传切片,提高文件上传效率,并通过服务端验证已上传的切片来避免重复上传。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原生 JavaScript 实现大文件分片并发上传

首先放上github链接,代码注释的比较清除,上层使用React进行测试,基本的功能都实现了

技术栈 TypeScript node

模块 sprak-md5 express fs fromidadle

实现的功能

  • 分片上传
  • 验证秒传
  • 断点续传
  • 并发上传

前言

此项目本来是作为一个react上传组件编写,编写后发现该组件内部相对比较复杂,于是将该组件抽离出来写成了一个上传工具类

由于分片上传需要后端支持,所以本片会涉及到一点node的知识

为什么需要文件分片上传?

在上传小文件时,分片上传和普通上传的效果体验并不大,但是在上传大文件时,普通的一次性上传会存在以下缺点

  • 文件上传时间较长,会长时间持续占用服务器的连接端口
  • 如果断网或者页面不小心关闭,已上传的文件会全部丢失,需要重新上传
分片上传的优点
  • 充分利用浏览器多进程的特性,并发的上传文件,加快文件的上传速度
  • 服务端可以将已经上传的文件切片保存起来,若文件上传过程中出现了意外,再下次上传的时候可以过滤掉已经上传的切片

核心思想

利用H5提供的原生File对象,由于File对象是特殊类型的Blob, File接口也继承了Blob接口的属性,分片上传的核心思想就是利用File继承Blob接口的Blob.slice方法

Blob.slice方法可以将我们的文件切分为多个单个的切片,分片上传的思想就是利用slice APi将文件分割成多个切片,然后利用浏览器多进程的特性进行并发上传

如何实现验证秒传?

思路

在上传切片前将文件的基本信息发送至服务端进行验证,判断该文件是否需要重新上传

验证方法

  • 文件名
  • 文件最后修改的时间(File文件对象的一个属性 lastModified
  • 文件hash值

如何选择

每一验证的方法都有其优缺点,文件名最后修改的时间方法相较于计算文件hash值可以在前端逻辑中快速的判断该文件是否需要上传,但是文件名最后修改的时间无法准确的判断该文件是否需要重传,因为文件名的修改对文件内容无影响,而lastModified又过于“敏感”,即对文件内容无实质修改的操作也会被记录,从而导致该已上传的文件需要重新上传。

所以我们选用计算文件hash值的方法判断文件是否需要上传,每次上传文件前首先将文件的hash值发送至服务端判断该文件是否需要重新上传,hash的计算我们选用spark-md5,别问为啥,因为spark-md5已经算是比较快的一个计算hash值的库了

spark-md5

spark-md5是一个计算文件hash值的工具

spark-md5可以帮助我们相对比较快速的计算出文件的hash值,我们可以在服务端存储文件的hash的

spark-md5基本用法

// 传入的参数为文件切片数组
// 首先递归的将每一个切片可利用FileReader实例读取文件中的内容,在成功的回调函数中将文件添加到spark-md实例中,在递归结束时计算文件hash,并返回文件hash,考虑到该过程比较耗,所以使用promise进行包裹

const calculatehash = (fileChunkList: Array<chunkListsFile>) => (
    new Promise(reslove => {
   
        const spark = new SparkMD5.ArrayBuffer()
        let count = 0
        const loadNext = (index: number) => {
   
            const reader = new FileReader()
            reader.readAsArrayBuffer(fileChunkList[index].file)
            reader.onload = (e: any) => {
   
                count++
                spark.append(e.target.result)
                // 如果文件处理完成则发送发送请求
                if (count === fileChunkList.length) {
   
                    reslove(spark.end())
                    return
                }
                loadNext(count)
            }
        }
        loadNext(0)
    })
)

实现

前端

因为我们是作为一个组件去写的,所以我们默认不处理html获取文件对象的步骤,直接从传入文件对象列表开始写,在文件的处理过程中调用传入的参数上报当前的文件处理/上传进度

接口定义

由于开发过程是由ts进行开发,所以我们首先要先定义接口

接口名 描述
fileBasicMessage 文件基本类型
chunkListsFile 每个文件切片
IwaitUploadFile 待上传文件数组
IwaitCalculateFile 待计算hash文件数组
IuploadedFile 上传完成的文件数组

interface fileBasicMessage {
   
    file: File,
    id?: string
}
    

export interface chunkListsFile {
   
    file: Blob
    hash: string
    fileName: string
    index?: number
}

export interface IwaitUploadFile extends fileBasicMessage {
   
    hash?: string
    uploadProcess?: number
    uploadPercentArr: Array<number> | []
    chunkList: Array<chunkListsFile> | []
    uploadedSize: number
}

export interface IwaitCalculateFile {
   
    id: string
    file: File
}

export interface IuploadedFile {
   
    url: string
    fileName: string
}

UML图

未命名文件

工具类大体框架实现

构造一个文件上传类并编写添加文件方法
//  函数需要的参数
export interface Iprops {
   
    // 每个切片的大小
    chunkSize?: number
    //  每个文件切片列表允许并发上传的个数
    concurrency: number
    //  上报文件处理上传进度回调函数
    updateWaitCalculateFile: (files: Array<IwaitCalculateFile>) => void
    updateWaitUploadFile: (files: Array<IwaitUploadFile>) => void
    updateUploadedFiles: (files: Array<IuploadedFile>) => void
}

class UploadTool {
   
    
    // 首先定义我们需要的信息
    constructor(props: IProps) {
   
        // 是否正在计算文件hash
        this.isCalculating = false
        // 切片大小默认4M
        this.chunkSize = props.chunkSize ? props.chunkSize : 4 * 1024 * 1024
        this.concurrency = props.concurrency ? props.concurrency : 3
        this.updateWaitCalculateFile = props.updateWaitCalculateFile
        this.updateWaitUploadFile = props.updateWaitUploadFile
        this.updateUploadedFiles = props.updateUploadedFiles
        this.chunksConcurrenceUploadNum = parseInt(String(10 / this.waitUploadFiles.length))
    }
    
    // 添加文件方法 (使用typescript写法)
    /**
     * @function 对外暴露的添加文件方法
     * @param newFiles 新添加的文件数组
     */
    public addNewFiles(newFiles: FileList) {
   
        
    }
    
    /**
     * @function 获取文件切片以及hash
     */
    private async calculateFilesMessage() {
   
        
    }
    
    
    /**
     * @function 添加已上传文件并上报
     * @param fileName 上传成功的文件名
     * @param url 返回的url
     */
    private addUploadedFiles(fileName: string, url: string): void {
   
      
    }
    
    /**
     * @function 增加计算hash完成文件并上报 调用上传方法
     * @param newWaitUploadFile 计算hash完成的文件
     */
    private async addCalculatedFile(newWaitUploadFile: IwaitUploadFile): Promise<any> {
   
      	
    }
    
    /**
     * @function 执行验证以及上传逻辑 
     * @param waitUploadFile 待上传文件信息(内部的参数在接口中已经定义)
     */
    private async upload(waitUploadFile: IwaitUploadFile) {
   
        
    }
    
    /**
     * @function 计算已上传的size
     * @param AlreadyUploadList 服务端返回的已上传hash列表
     * @param waitUploadFile 待上传文件
     * @returns 已上传的切片大小
     */
    private calculeateAlreadyUploadSize(AlreadyUploadList: Array<any>, waitUploadFile: IwaitUploadFile) {
   
        
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值