项目亮点!一文带你轻松搞懂前端大文件切片上传、断点续传思路

一、为什么?

面试官:"项目中有遇到什么难点吗?",相信有许多小伙伴在面试过程中都被问过这个问题,虽然我们大部分人的工作无非就是 CRUD,但是在我们的日常开发当中,还有许多的优化之处可以挖掘,而且这些优化点很多都能充项目亮点,并且完全可以作为我们简历和面试的加分项。

而文件上传就是我们在平时开发中经常会遇到的业务,如果只是简单的文件上传那还不足以作为项目亮点,而当我们给它加上切片、续传的功能,就不一样了。本文会带大家搞明白这些功能的实现思路,主要聚焦于 前端 部分,基于 Vue3来实现,暂时不涉及后端代码。

这些功能的实现也不只是为了应付面试,对于业务功能的提升也是显而易见的:当用户有一个超大的文件需要上传,而又无法保证浏览器或网络等能一直流畅运行,如果有续传功能,这个时候就算断开连接,下次继续上传也能恢复之前的下载进度。

二、流程图

文件切片上传的简单理解:前端先将文件分割成多份,再进行上传,由后端进行切片合并操作。

三、具体实现

1. HTML结构

vue 复制代码
<template>
  <div>
    <div class="btn" @click="openFileSelected">
      选择文件
    </div>
    <div class="btn" @click="upload">
      上传
    </div>
    <input ref="inputFileRef" class="input_file" type="file"multiple  @change="inputFileChange">
  </div>
</template>

2. 打开文件选择菜单

我们的页面通常是隐藏 input 标签,然后通过点击自己创建的按钮来间接触发 input 标签的 click 事件,从而打开选择文件菜单。

实现代码:

typescript 复制代码
// 获取文件选择器实例
const inputFileRef = ref<HTMLInputElement>()

// 打开文件选择器
const openFileSelected = () => {
  if (inputFileRef.value) {
    inputFileRef.value.click()
  }
}

3. 监听文件选择事件

typescript 复制代码
// 等待上传文件,支持多文件上传
const inputFilesList = reactive<any[]>([])

// 文件选择触发
const inputFileChange = (e: Event) => {
  // 获取选中的文件
  const files = (e.target as HTMLInputElement).files
  
  // 如果没有选择文件不做任何操作
  if (!files) return
  
  // 可能存在选中多文件,将其存入 inputFilesList 数组中,后续遍历上传。
  for (let i = 0; i < files?.length; i++) {
    inputFilesList.push(files[i])
  }
}

4.获取文件HASH值工具函数

如果采用 typescript 开发需要下载 @types/spark-md5

bash 复制代码
pnpm i spark-md5 @types/spark-md5
typescript 复制代码
import SparkMD5 from 'spark-md5'

export function getHASH(file: File): Promise<any> {
  return new Promise((resolve, reject) => {
    // 要通过 spark-md5 得到 hash 需要先获取文件的buffer值
    const fileReader = new FileReader()
    fileReader.readAsArrayBuffer(file)
    fileReader.onload = (e) => {
      const BUFFER = e.target?.result
      if (!BUFFER) reject()
      const HASH = new SparkMD5.ArrayBuffer().append(BUFFER as ArrayBuffer).end()
      // 获取文件后缀,后续单独拼接切片文件名
      const suffix = (/\.([a-zA-Z0-9]+)$/.exec(file.name) as any)[1]
      resolve({
        HASH,
        suffix
      })
    }
  })
}

5. 进行文件切片并获取已上传过的切片

该小节是本文内容的核心,采取自带的slice 方法进行文件切割,然后进行上传,通过 complete 函数进行是否合并判断。

typescript 复制代码
/**
 *  上传 
 */
const upload = async () => {
  // 判断是否有待上传的文件
  if (inputFilesList.length === 0) {
    alert('请选择文件')
    return
  }
 
  // 遍历文件数组
  for (let i = 0; i < inputFilesList.length; i++) {
    // 每个切片的固定大小
    let maxSize = 1024 * 1024 * 1
    
    const {
      HASH,
      suffix
    } = await changeBuffer(inputFilesList[i])
    let count = Math.ceil(inputFilesList[i].size / maxSize)
    // 当切片数量大于最大限制时候,采用固定数量,防止请求过多
    if (count > 100) {
      count = 100
      maxSize = inputFilesList[i].size / count
    }

    // 开始切片操作
    let index = 0
    //用来存储当前文件的切片
    let chunks = []
    while (index < count) {
      const sliceFile = inputFilesList[i].slice(index * maxSize, (index + 1) * maxSize)
      chunks.push({
        file: sliceFile,
        name: `${HASH}_${index}.${suffix}`
      })
      index++
    }
      
    // 在此处进行请求,获取该文件是否有已上传切片
    const uploadedFileList = await axios.get('/file')
    
    // 筛选出未上传的切片
    if (uploadedFileList.length) {
       	chunks = chunks.filter(chunk => !uploadedFileList.includes(chunk))
    }
    
    // 用于判断是否上传完成
    let uplopedCount = 0
    const uploped = () => {
        uplopedCount++
        // 当所有切片都上传完成,像后端发送请求合并操作
        if (uplopedCount > count) {
			const result = axois.post('/merge', {
                hash: HASH
            })
        }
    }
      
    // 进行切片上传
    for (let i = 0; i < chunks.length; i++) {
      const fm = new formData()
      fm.append('file', chunks[i].file)
      fm.append('name', chunks[i].name)
      axios.post('/file', fm).then((result) => {
         // 如果当前切片上传成功,进入uploped函数,用于判断该文件切片是否全部上传完成。
         if (result.code === 200) uploped()
      })
    }
  }
}

四、总结

通过上述的代码,我们已经基本实现了前端大文件上传、续传的功能,这其中还有许多可以完善的点,就交给小伙伴们自己挖掘了,感谢大家阅读。

相关推荐
掘金者阿豪24 分钟前
把业务数据变成共享仪表盘:Metabase可视化与远程访问实践
前端·后端
kyriewen1 小时前
折腾了半年 AI 编程工作流,最后发现效率瓶颈是桌上那块屏幕
前端·javascript·ai编程
蜗牛前端1 小时前
codex 全流程开发上线的高颜值礼簿小程序
前端·微信小程序
大龄秃头程序员2 小时前
我在图文流 App 里落地双层缓存、弱网降级与 OOM 治理
前端
老王以为2 小时前
React Renderer 分离的多平台架构
前端·react native·react.js
hunterandroid2 小时前
Kotlin Coroutines 与 Flow:让异步任务更清晰
前端
Bigger3 小时前
从零搭建 AI 代码审查服务:一份前端也能看懂的 Python 学习笔记
前端·ci/cd·ai编程
lichenyang4533 小时前
JSAPI、NAPI、Biz、Imp:ASCF Demo 如何真正调用系统能力和 C++ 能力
前端
lichenyang4533 小时前
IPC、JSVM、UIThread、libuv:ASCF 架构图里最容易混的几个词
前端
用户059540174463 小时前
Redis记忆存储故障恢复测试踩坑实录:手动测试让我漏掉了2个一致性Bug
前端·css