JavaScript大文件上传的处理方法之切片上传

目录
  • 前言
  • 切片后上传
  • 生成hash
  • 文件秒传
  • 暂停上传
    • 中断请求示例
    • 添加暂停上传功能
  • 恢复上传
  • 添加功能总结

前言

本篇介绍了切片上传的基本实现方式(前端),以及实现切片上传后的一些附加功能,切片上传原理较为简单,代码注释比较清晰就不多赘述了,后面的附加功能介绍了实现原理,并贴出了在原本代码上的改进方式。有什么错误希望大佬可以指出,感激不尽。

切片后上传

切片上传的原理较为简单,即获取文件后切片,切片后整理好每个切片的参数并发请求即可。

下面直接上代码:

HTML

<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <el-button @click="handleUpload">上传</el-button>
  </div>
</template>

JavaScript

<script>
const SIZE = 10 * 1024 * 1024; // 切片大小

export default {
  data: () => ({
    // 存放文件信息
    container: {
      file: null
      hash: null
    },
    data: [] // 用于存放加工好的文件切片列表
    hashPercentage: 0 // 存放hash生成进度
  }),
  methods: {
    // 获取上传文件
    handleFileChange(e) {
      const [file] = e.target.files;
      if (!file) {
        this.container.file = null;
        return;
      }
      this.container.file = file;
    },

    // 生成文件切片
    createFileChunk(file, size = SIZE) {
     const fileChunkList = [];
      let cur = 0;
      while (cur < file.size) {
        fileChunkList.push({ file: file.slice(cur, cur + size) });
        cur += size;
      }
      return fileChunkList;
    },

    // 生成文件hash
    calculateHash(fileChunkList) {
      return new Promise(resolve => {
        this.container.worker = new Worker("/hash.js");
        this.container.worker.postMessage({ fileChunkList });
        this.container.worker.onmessage = e => {
          const { percentage, hash } = e.data;
          // 可以用来显示进度条
          this.hashPercentage = percentage;
          if (hash) {
            resolve(hash);
          }
        };
      });
    },

   	// 切片加工(上传前预处理 为文件添加hash等)
    async handleUpload() {
      if (!this.container.file) return;
      // 切片生成
      const fileChunkList = this.createFileChunk(this.container.file);
      // hash生成
      this.container.hash = await this.calculateHash(fileChunkList);
      this.data = fileChunkList.map(({ file },index) => ({
        chunk: file,
        // 这里的hash为文件名 + 切片序号,也可以用md5对文件进行加密获取唯一hash值来代替文件名
        hash: this.container.hash + "-" + index
      }));
      await this.uploadChunks();
    }

    // 上传切片
   	async uploadChunks() {
     const requestList = this.data
     	// 构造formData
       .map(({ chunk,hash }) => {
         const formData = new FormData();
         formData.append("chunk", chunk);
         formData.append("hash", hash);
         formData.append("filename", this.container.file.name);
         return { formData };
       })
     	// 发送请求 上传切片
       .map(async ({ formData }) =>
       	request(formData)
       );
     await Promise.all(requestList); // 等待全部切片上传完毕
     await merge(this.container.file.name) // 发送请求合并文件
   	},
  }
};
</script>

生成hash

无论是前端还是服务端,都必须要生成文件和切片的 hash,之前我们使用文件名 + 切片下标作为切片 hash,这样做文件名一旦修改就失去了效果,而事实上只要文件内容不变,hash 就不应该变化,所以正确的做法是根据文件内容生成 hash,所以我们修改一下 hash 的生成规则

这里用到另一个库 spark-md5,它可以根据文件内容计算出文件的 hash 值,另外考虑到如果上传一个超大文件,读取文件内容计算 hash 是非常耗费时间的,并且会引起 UI 的阻塞,导致页面假死状态,所以我们使用 web-worker 在 worker 线程计算 hash,这样用户仍可以在主界面正常的交互

由于实例化 web-worker 时,参数是一个 js 文件路径且不能跨域,所以我们单独创建一个 hash.js 文件放在 public 目录下,另外在 worker 中也是不允许访问 dom 的,但它提供了importScripts`函数用于导入外部脚本,通过它导入 spark-md5

// /public/hash.js
self.importScripts("/spark-md5.min.js"); // 导入脚本
// 生成文件 hash
self.onmessage = e => {
  const { fileChunkList } = e.data;
  const spark = new self.SparkMD5.ArrayBuffer();
  let percentage = 0;
  let count = 0;
  const loadNext = index => {
    // 新建读取器
    const reader = new FileReader();
    // 设定读取数据格式并开始读取
    reader.readAsArrayBuffer(fileChunkList[index].file);
    // 监听读取完成
    reader.onload = e => {
      count++;
      // 获取读取结果并交给spark计算hash
      spark.append(e.target.result);
      if (count === fileChunkList.length) {
        self.postMessage({
          percentage: 100,
          // 获取最终hash
          hash: spark.end()
        });
        self.close();
      } else {
        percentage += 100 / fileChunkList.length;
        self.postMessage({
          percentage
        });
        // 递归计算下一个切片
        loadNext(count);
      }
    };
  };
  loadNext(0);
};

小结

  • 获取上传文件
  • 文件切片后存入数组 fileChunkList.push({ file: file.slice(cur, cur + size) });
  • 生成文件hash(非必须)
  • 根据文件切片列表生成请求列表
  • 并发请求
  • 待全部请求完成后发送合并请求

文件秒传

实际是障眼法,用来欺骗用户的。

原理:在文件上传之前先计算出文件的hash,然后发送给后端进行验证,看后端是否存在这个hash,如果存在,则证明这个文件上传过,则直接提示用户秒传成功

// 切片加工(上传前预处理 为文件添加hash等)
async handleUpload() {
  if (!this.container.file) return;
  // 切片生成
  const fileChunkList = this.createFileChunk(this.container.file);
  // hash生成
  this.container.hash = await this.calculateHash(fileChunkList);

  // hash验证 (verify为后端验证接口请求)
  const { haveExisetd } = await verify(this.container.hash)
  // 判断
  if(haveExisetd) {
  	this.$message.success("秒传:上传成功")
    return
  } 

  this.data = fileChunkList.map(({ file },index) => ({
    chunk: file,
    // 这里的hash为文件名 + 切片序号,也可以用md5对文件进行加密获取唯一hash值来代替文件名
    hash: this.container.hash + "-" + index
  }));
  await this.uploadChunks();
}

暂停上传

原理:将所有的切片存在一个数组中,每当一个切片上传完毕,从数组中移除,这样就可以实现用一个数组只保存上传中的文件。此外,因为要暂停上传,所以需要中断请求 axios中断请求可以利用AbortController

中断请求示例

const controller = new AbortController()
axios({
  signal: controller.signal
}).then(() => {});
// 取消请求
controller.abort()

添加暂停上传功能

// 上传切片
async uploadChunks() {
 // 需要把requestList放到全局,因为要通过操控requestList来实现中断
 this.requestList = this.data
 	// 构造formData
   .map(({ chunk,hash }) => {
     const formData = new FormData();
     formData.append("chunk", chunk);
     formData.append("hash", hash);
     formData.append("filename", this.container.file.name);
     return { formData };
   })
 	// 发送请求 上传切片
   .map(async ({ formData }, index) =>
     request(formData).then(() => {
       // 将请求成功的请求剥离出requestList
       this.requestList.splice(index, 1)
     })
   );
 await Promise.all(this.requestList); // 等待全部切片上传完毕
 await merge(this.container.file.name) // 发送请求合并文件
},
// 暂停上传
handlePause() {
	this.requestList.forEach((req) => {
        // 为每个请求新建一个AbortController实例
     	const controller = new AbortController();
        req.signal = controller.signal
        controller.abort()
    })
}

恢复上传

原理:上传切片之前,向后台发送请求,接口将已上传的切片列表返回,通过切片hash将后台已存在的切片过滤,只上传未存在的切片

// 切片加工(上传前预处理 为文件添加hash等)
async handleUpload() {
  if (!this.container.file) return;
  // 切片生成
  const fileChunkList = this.createFileChunk(this.container.file);
  // 文件hash生成
  this.container.hash = await this.calculateHash(fileChunkList);
  // hash验证 (verify为后端验证接口请求)
  const { haveExisetd, uploadedList } = await verify(this.container.hash)
  // 判断
  if(haveExisetd) {
  	this.$message.success("秒传:上传成功")
    return
  }
  this.data = fileChunkList.map(({ file },index) => ({
    chunk: file,
    // 注:这个是切片hash   这里的hash为文件名 + 切片序号,也可以用md5对文件进行加密获取唯一hash值来代替文件名
    hash: this.container.hash + "-" + index
  }));
  await this.uploadChunks(uploadedList);
}
// 上传切片
async uploadChunks(uploadedList = []) {
 // 需要把requestList放到全局,因为要通过操控requestList来实现中断
 this.requestList = this.data
    // 过滤出来未上传的切片
   .filter(({ hash }) => !uploadedList.includes(hash))
 	// 构造formData
   .map(({ chunk,hash }) => {
     const formData = new FormData();
     formData.append("chunk", chunk);
     formData.append("hash", hash);
     formData.append("filename", this.container.file.name);
     return { formData };
   })
 	// 发送请求 上传切片
   .map(async ({ formData }, index) =>
     request(formData).then(() => {
       // 将请求成功的请求剥离出requestList
       this.requestList.splice(index, 1)
     })
   );
 await Promise.all(this.requestList); // 等待全部切片上传完毕
 // 合并之前添加一层验证 验证全部切片传送完毕
 if(uploadedList.length + this.requestList.length == this.data.length){
	await merge(this.container.file.name) // 发送请求合并文件
 }
},
// 暂停上传
handlePause() {
	this.requestList.forEach((req) => {
        // 为每个请求新建一个AbortController实例
     	const controller = new AbortController();
        req.signal = controller.signal
        controller.abort()
    })
}
// 恢复上传
async handleRecovery() {
    //获取已上传切片列表 (verify为后端验证接口请求)
	const { uploadedList } = await verify(this.container.hash)
    await uploadChunks(uploadedList)
}

添加功能总结

  • 1.文件秒传其实就是一个简单的验证,把文件的hash发送给后端,后端验证是否存在该文件后将结果返回,如果存在则提示文件秒传成功
  • 2.断点传送分为两步,暂停上传和恢复上传。暂停上传是通过获取到未上传完毕切片列表(完整切片列表剥离请求已完成的切片后形成),对列表请求进行请求中断实现的。恢复上传实质也是一层验证,在上传文件之前,将文件的hash发送给后端,后端返回已经上传完毕的切片列表,然后根据切片hash将后端返回的切片列表中的切片过滤出去,只上传未上传完成的切片。

到此这篇关于JavaScript大文件上传的处理方法之切片上传的文章就介绍到这了,更多相关JS切片上传内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript进阶之前端文件上传和下载示例详解

    目录 文件下载 1.通过a标签点击直接下载 2.open或location.href 3.Blob和Base64 文件上传 文件上传思路 File文件 上传单个文件-客户端 上传文件-服务端 多文件上传-客户端 大文件上传-客户端 大文件上传-服务端 文件下载 1.通过a标签点击直接下载 <a href="https:xxx.xlsx" rel="external nofollow" download="test">下载文件</

  • JS实现可恢复的文件上传示例详解

    目录 正文 不太实用的进度事件 算法 server.js uploader.js index.html 正文 使用 fetch 方法来上传文件相当容易. 连接断开后如何恢复上传?这里没有对此的内建选项,但是我们有实现它的一些方式. 对于大文件(如果我们可能需要恢复),可恢复的上传应该带有上传进度提示.由于 fetch 不允许跟踪上传进度,我们将会使用 XMLHttpRequest. 不太实用的进度事件 要恢复上传,我们需要知道在连接断开前已经上传了多少. 我们有 xhr.upload.onpro

  • JS文件上传时如何使用MD5加密

    目录 JS文件上传使用MD5加密 什么是MD5? MD5怎么用? 前端中md5的用法 md5的使用方法 JS文件上传使用MD5加密 什么是MD5? MD5信息摘要算法(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致.(MD5 百度百科) 简而言之,就是对上传文件进行加密. MD5怎么用? 在页面中使用需下载依赖包,yarn spark-md5或npm in

  • JavaScript文件上传的常见问题整理

    文件上传:<input type="file" /> (IE9及以下不支持下面这些功能,其它浏览器最新版本均已支持.) 1.允许上传文件数量 允许选择多个文件: <input type="file" multiple> 只允许上传一个文件: <input  type="file" single> 2.上传指定的文件格式 <input type="file" accept="i

  • JavaScript实现大文件上传的示例代码

    下面就是JavaScript实现大文件上传功能的代码 bigFileUpload.js const path = require('path') import axios from 'axios' import { resolve } from 'path'; import { promised } from 'q'; // 递归调用请求 async function dg(requestMargreList, options, key = 0) { let index = key const

  • 原生JS实现文件上传

    本文实例为大家分享了JS实现文件上传的具体代码,供大家参考,具体内容如下 一.目的: 实现上传图片功能 二.效果: 三.思路: 用input标签自带的上传,先隐藏掉,给上传按钮添加点击事件,绑定input的点击事件 四.代码: //html <input ref="img-upload-input" class="img-upload-input" type="file" accept=".png, .jpg" @cha

  • JavaScript大文件上传的处理方法之切片上传

    目录 前言 切片后上传 生成hash 文件秒传 暂停上传 中断请求示例 添加暂停上传功能 恢复上传 添加功能总结 前言 本篇介绍了切片上传的基本实现方式(前端),以及实现切片上传后的一些附加功能,切片上传原理较为简单,代码注释比较清晰就不多赘述了,后面的附加功能介绍了实现原理,并贴出了在原本代码上的改进方式.有什么错误希望大佬可以指出,感激不尽. 切片后上传 切片上传的原理较为简单,即获取文件后切片,切片后整理好每个切片的参数并发请求即可. 下面直接上代码: HTML <template> &

  • Linux 中清空或删除大文件内容的五种方法

    在 Linux 终端下处理文件时,有时我们想直接清空文件的内容但又不必使用任何Linux命令行编辑器 去打开这些文件.那怎样才能达到这个目的呢?在这篇文章中,我们将介绍几种借助一些实用的命令来清空文件内容的方法. 注意: 由于再Linux中一切皆文件,你需要时刻注意,确保你将要清空的文件不是重要的用户文件或者系统文件.清空重要的系统文件或者配置文件可能会引发严重的应用失败或者系统错误. 提示:在下面的示例中,我们将使用名为 access.log 的文件来作为示例样本. 1. 通过重定向到 Nul

  • linux查找大文件指定内容的实现方法

    以大划小思想,然后重定向. linux有时候会遇到文件很大,关键字查找都要超过整屏,无法查看到所有内容.比如一个非常大的日志文件info.log,我们要查看某段字符所有日志,可以同 cat info.log | grep '1711178968' ,如果显示过多,此时可以加时间,行数控制.也可以通过 '>>'指令. cat info.log | grep '1711178968'   >> temp.log 将中间结果暂存下来,通过more ,less等工具一页页查看temp.lo

  • 通过Nginx服务器获取大文件MD5值的配置方法

    HTTP协议新增了Content-MD5 HTTP头,但是nginx并不支持这个功能,而且官方也明确表示不会增加这项功能,为什么呢?因为每次请求都需要读取整个文件来计算MD5值,以性能著称的nginx绝对不愿意干出违背软件宗旨的事情.但是有些应用中,需要验证文件的正确性,有些人通过下载当前文件,然后计算MD5值来比对当前文件是否正确.不仅仅浪费带宽资源也浪费了大把的时间.有需求就有解决方案,网友开发了file-md5模块. 1. 下载模块file-md5 # cd /usr/local/src

  • 详解在Linux中清空或删除大文件内容的5种方法

    有时,在处理Linux终端中的文件时,您可能希望清除文件的内容,而无需使用任何Linux命令行编辑器打开它.怎么能实现这一目标?在本文中,我们将借助一些有用的命令,通过几种不同的方式清空文件内容. 警告:在我们继续查看各种方法之前,请注意,因为在Linux中一切都是文件,所以必须始终确保要清空的文件不是重要的用户或系统文件.清除关键系统或配置文件的内容可能会导致致命的应用程序/系统错误或故障. 就像刚刚说的,下面是从命令行清除文件内容的方法. 重要说明:出于本文的目的,我们access.log在

  • Asp.Net上传文件并配置可上传大文件的方法

    ASP.NET 包含两个控件可以使用户向网页服务器上传文件.一旦服务器接受了上传的文件数据,那么应用程序就可以进行保存,进行检查或者忽略它. HtmlInputFile - HTML 服务器控件 FileUpload - ASP.NET 网页控件 两种控件都允许文件上传,但是 FileUpload 控件自动设置编码格式,然而 HtmlInputFile 控件并不会这样. 1.使用HtmlInputFile文件上传 前台 <form enctype="multipart/form- data

  • PHP文件上传问题汇总(文件大小检测、大文件上传处理)

    由于涉及到本地和服务器两方面的安全问题,所以基于input type="file"形式的页面文件上传一直处于一个很尴尬的位置.一方面,用户不希望隐私泄露,所以浏览器无法对用户在上传时选择的文件做有效的判 断.另一方面,为了服务器端的安全,减轻传输负担,系统又希望能在用户开始上传之前就将非法的文件拒之门外. 一来一去,基于原始input方式的上传,成为网络存储网站避之唯恐不及的遗留性问题,也造就了现在千奇百怪的插件.上传客户端. input方式的上传就如此之差么?当然不是.上传文件不大的

  • 如何基于js管理大文件上传及断点续传详析

    目录 前言 前端结构 后端结构(node + express) 基于FormData实现文件上传 基于BASE64实现文件上传 BASE64具体方法 前端生成文件名传给后端 上传进度管控 大文件上传 服务端代码(大文件上传+断点续传) 总结 前言 前端小伙伴们平常在开发过程中文件上传是经常遇到的一个问题,也许你能够实现相关的功能,但是做完后回想代码实现上是不是有点"力不从心"呢?你真的了解文件上传吗?如何做到大文件上传以及断电续传呢,前后端通讯常用的格式,文件上传进度管控,服务端是如何

  • Vue+Node实现大文件上传和断点续传

    目录 源代码 Blob.slice 切片 初始化文件内容 FormData.append() 大文件上传 断点续传 代码 创建切片 源码 worker 线程通讯的逻辑 断点续传 秒传 源代码 断点续传.分片上传.秒传.重试机制 文件上传是开发中的难点, 大文件上传及断点续传 难点中的细节及核心技术点. element-ui 框架的上传组件,是默认基于文件流的. 数据格式:form-data: 传递的数据: file 文件流信息:filename 文件名字 通过 fileRead.readAsDa

  • JavaScript 中如何实现大文件并行下载

    目录 一.HTTP 范围请求 1.1 Range 语法 二.如何实现大文件下载 2.1 定义辅助函数 2.2 大文件下载使用示例 三.总结 相信有些小伙伴已经了解大文件上传的解决方案,在上传大文件时,为了提高上传的效率,我们一般会使用 Blob.slice 方法对大文件按照指定的大小进行切割,然后在开启多线程进行分块上传,等所有分块都成功上传后,再通知服务端进行分块合并. 那么对大文件下载来说,我们能否采用类似的思想呢?在服务端支持 Range 请求首部的条件下,我们也是可以实现多线程分块下载的

随机推荐