基于JavaScript实现文件秒传功能

 更新时间:2024年01月08日 08:28:46   作者:码窝酱  
在互联网高速发展的今天,文件上传已经成为网页应用中的一个基本功能,随着用户上传文件尺寸的不断增大、对质量清晰度的要求也越来越高,所以本文给大家介绍了如何使用JavaScript实现文件秒传功能,需要的朋友可以参考下

背景

上传一个100MB的视频文件,只需要1~3秒,是真的吗?靠谱吗?

此前,经常有用户反馈在正常网络下上传一个1GB的视频大约需要10分钟,我们也只能回复:“其实这种情况和上传的时间、网络以及文件大小有关”。

在互联网高速发展的今天,文件上传已经成为网页应用中的一个基本功能。随着用户上传文件尺寸的不断增大、对质量清晰度的要求也越来越高。如何提高上传速度、优化用户体验成为了前端开发者必须面对的问题。

在此之前,最常见的优化方案就是分块上传、断点续传,在用户因异常断开上传 或 刷新页面后,能继续在上一次的基础上继续上传。这的确能较好的提升用户上传体验,也是很有必要的优化手段,但无法实现文件秒传。

什么是文件秒传?

文件秒传指的是当用户上传文件时,如果服务器已存在完全相同的文件,那么无需用户再次上传,直接使用服务器上的文件副本,实现瞬间完成上传的过程。这种技术可以显著减少不必要的数据传输,节省时间和带宽资源。(服务器会根据有无hash的情况返回3种状态,下面会有详细说明)

文件秒传的原理

文件秒传的核心原理是“文件指纹”。即文件唯一ID,通常是指文件的哈希值(如MD5、SHA-1等),它是通过哈希算法计算得出的一串固定长度的字符串,可以唯一标识文件的内容。即使文件非常庞大,其哈希值也能迅速计算出来,并且即便只是文件中的一个字节发生变化,所得到的哈希值也会完全不同。

秒传的三种状态处理说明

三种状态都需要将前端计算得出的文件hash传递给服务端查询获得

状态一(notHash):文件在服务器不存在
此时正常分块上传,上传结束后返回上传结果

状态二(hasHash):文件在服务器存在
根据文件hash查询到上传结果,直接返回(实现秒传)

状态三(hashIng):新文件第一次上传完,但后端任务未完成, 而该文件在前端又被上传。此时轮询后端接口,等待后端任务完成后直接上传结果(该文件第二次或后续上传均已实现秒传)

状态三中说的后端任务主要是:新文件上传完后,后端并不会直接拿前端的hash存到数据库,而是会自己在服务端根据上传完的视频生成hash(生成hash规则和前端一样)再和前端比对,以确保数据的准确性及唯一性。

如何在前端页面实现文件秒传?

关键技术点

  • 文件分块hash计算
  • 拿得到的hash到后端查询文件状态(确定文件是否在服务端存在)
  • 根据服务端返回的上传状态,处理上传。

1. 计算文件hash

计算hash的方法封装,使用md5会有相对较大的概率出现重复hash,建议至少使用sha1的方式计算。

/**
 * @description: 分块计算文件hash
 * @param {*} file 文件对象
 * @param {*} chunkSize 分块计算的文件大小,默认10MB
 * @return {*}
 */
export function calculateSliceFileHash({ file, chunkSize = 10 * 1024 * 1024 }) {
  let currentChunkIndex = 0;
  const maxChunkCount = Math.ceil(file.size / chunkSize);
  let sha1WordArray = CryptoJS.algo.SHA1.create();
  const startTime = Date.now()
  return new Promise((resolve, reject) => {
    function loadNextChunk() {
      const start = currentChunkIndex * chunkSize;
      const end = Math.min(start + chunkSize, file.size);
      const reader = new FileReader();
      reader.onload = function (e) {
        const arrayBuffer = e.target.result;
        const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
        sha1WordArray.update(wordArray);
        const diffTime = Date.now() - startTime
        if (currentChunkIndex < maxChunkCount - 1) {
          currentChunkIndex++;
          loadNextChunk()
        } else {
          const sha1 = sha1WordArray.finalize().toString(CryptoJS.enc.Hex); // CryptoJS.enc.Hex
          resolve({ sha1, diffTime }); // 返回计算出的hash值
        }
      };
      reader.onerror = function (error) {
        console.error('Error reading file chunk:', error);
        reject({ error: parseError(error) }); // 处理错误
      };
      const blobSlice = file.slice(start, end);
      reader.readAsArrayBuffer(blobSlice);
    }
    loadNextChunk();
  })
}

在需要计算的时候直接调用const {sha1} = calculateSliceFileHash({file})即可

2. 根据hash查询后端状态

  async uploadFile(params) {
    // 获取hash
    const {sha1} = await calculateSliceFileHash({ file })
    // 根据hash查询文件状态
    const {data} = await this.axios.post(`/api/xxx`, params)
    const { key, bucket, region, state, url } = data.data
    // state='hasHash' | 'hashIng' | 'notHash'
 ...
 }

3. 根据服务端的状态,返回上传结果

async uploadFile(params) {
...
    // 文件存在,直接返回结果
    if (state === 'hasHash') {
      return Promise.resolve({url})
    }
    // 文件已上传,后端处理中
    if (state === 'hashIng') {
      // 设置轮询开始时间
      if (!params.pollStartTs) {
        params.pollStartTs = Date.now()
      } else if (Date.now() - params.pollStartTs >= 60000) {
        // 轮询超过1分钟认定为超时
        return Promise.reject({ error: '文件加载超时,请稍后再试' })
      }
      await sleep(1000) // 每隔1s轮询
      await this.uploadFile(params)
    }
  	// state==='notHash'时,执行下面的上传
    return new Promise(async (resolve, reject) => {
      const cos = new Cos({
        getAuthorization: this.cosAuthorization({
          resolve,
          reject,
          bucket,
          key
        }).bind(this)
      })
      cos.sliceUploadFile(
        {
          Bucket: bucket,
          Region: region,
          Key: key,
          Body: file,
          SliceSize: 1024 * 1024 * 10, // 超10M使用分块(cos单个块最大不超过5GB)
          onProgress,
          onTaskReady
        },
        (error, data) => {
          if (error) {
            return reject({ error })
          }
          resolve(data)
        }
      )
    })
  }

批量上传中应用秒传

批量上传也同样适应,遍历调用uploadFile({file: singleFile}),逐个处理。也可使用Promise.all(...),等待所有文件处理完后再返回结果集合。 注:建议使用遍历逐个上传,能有更好的用户体验。

存在的主要问题

上传大文件并在浏览器中进行SHA1或MD5哈希计算时可能会导致浏览器崩溃,原因通常是在处理大文件时所需的计算和内存资源超过了浏览器的能力

如上所示,通常的处理办法包括: 通过setTimeoutrequestAnimationFrame分时段计算、切割合适的块、使用Web Workers、使用Stream Processing优化、优化算法,内存管理、在服务端计算等。
但最佳的处理办法,是在浏览器中使用webworker多线程计算hash,同时需要兼顾其兼容性、线程数量(需根据实际应用调整),目前项目已做优化、整体体验尚佳。webworker在后面的文章中会有详细的介绍。

总结

实现文件秒传能够显著提升用户的上传体验,特别是在处理大文件上传时(上传1GB大概20秒左右)
通过文件哈希比对、分块上传和断点续传等技术,可以让用户感受到上传速度的极大提升。
当然,文件秒传的具体实现还是有一定的复杂性,需要前后端紧密协作,确保整个上传过程的稳定性和安全性。随着技术的不断进步,相信未来的文件上传体验将会更加流畅,让用户真正体验到“秒传”的魔力。

以上就是基于JavaScript实现文件秒传功能的详细内容,更多关于JavaScript实现文件秒传的资料请关注脚本之家其它相关文章!

相关文章

  • javascript开发中因空格引发的错误

    javascript开发中因空格引发的错误

    最近写一个关于用JavaScript做图片自动切换问题发现一个非常奇特的问题,除了空格和换行外完全相同的代码,在Firefox下却有截然不同的运行结果,今天记录以提供他人留意及自我备查。
    2010-11-11
  • js实现鼠标悬浮给图片加边框的方法

    js实现鼠标悬浮给图片加边框的方法

    这篇文章主要介绍了js实现鼠标悬浮给图片加边框的方法,涉及jquery.insetborder.js中borderEffect方法的使用技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-01-01
  • javascript写的简单的计算器,内容很多,方法实用,推荐

    javascript写的简单的计算器,内容很多,方法实用,推荐

    最近用javascript写了一个简单的计算器,自己测试感觉还好,代码都给了注释,非常不错,推荐大家学习。
    2011-12-12
  • Bootstrap3.0学习教程之JS折叠插件

    Bootstrap3.0学习教程之JS折叠插件

    这篇文章主要介绍了Bootstrap3.0学习教程之JS折叠插件的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-05-05
  • JavaScript indexOf的第二个参数用法

    JavaScript indexOf的第二个参数用法

    indexOf 是我们非常熟悉的一个方法,它可以用来获取某一个元素在一个数组里的位置,我们一般就会使用 array.indexOf(element) 的方法来进行使用,但是,大家有没有使用过 indexOf 的第二个参数呢?本文将给大家介绍一下indexOf的第二个参数用法,需要的朋友可以参考下
    2024-02-02
  • Bootstrap框架动态生成Web页面文章内目录的方法

    Bootstrap框架动态生成Web页面文章内目录的方法

    这篇文章主要介绍了Bootstrap框架动态生成Web页面文章内目录的方法,利用Bootstrap中的Affix和ScrollSpy插件便可以实现,需要的朋友可以参考下
    2016-05-05
  • svg插入foreignObject无法响应事件解决

    svg插入foreignObject无法响应事件解决

    这篇文章主要为大家介绍了svg插入foreignObject无法响应事件解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • js实现tab切换效果

    js实现tab切换效果

    本文主要分享了js封装一个tab切换效果的示例代码,具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • D3.js实现绘制和弦图的教程详解

    D3.js实现绘制和弦图的教程详解

    弦图,是一种表示实体之间相互关系的图形方法。这篇文章主要为大家详细介绍了如何通过D3.js实现绘制和弦图,文中的示例代码讲解详细,对我们学习D3.js有一定的帮助,需要的可以参考一下
    2022-11-11
  • JS脚本加载后执行相应回调函数的操作方法

    JS脚本加载后执行相应回调函数的操作方法

    本文主要讲解怎么在成功加载 js 文件后再执行相应回调任务,对JS脚本加载后执行相应回调函数的操作方法感兴趣的朋友,通过本文学习下吧
    2018-02-02

最新评论