Vue3 + ElementPlus 前端实现分片上传

目录

1. 什么是分片上传

2. 上传组件模板

3. 上传组件逻辑

3.1 基本思路

3.2 选择上传文件

3.3 校验文件是否合法

3.4 文件加密

3.5 合并文件

3.6 文件切片上传

4. 参考文章

4.1 文章链接

4.2 参考文章提到的注意事项

4.2.1 nginx 上传大小限制

4.2.2 大文件下载


1. 什么是分片上传

将 一个文件 切割为 一系列特定大小的 数据片段,将这些 数据片段 分别上传到服务端;

全部上传完成后,再由服务端将这些 数据片段 合并成为一个完整的资源;

上传过程中,由于外部因素(比如网络波动)导致上传中断,下次上传时会保留该文件的上传进度(断点续传);

2. 上传组件模板

包含三部分:

  • 上传组件,使用 el-upload
  • 进度条组件,使用 el-progress
  • 上传完成状态组件,使用 el-input 自定义
 
   
     
       
     

     
Drop file here or click to upload
// 进度条
// 上传成功之后隐藏上传文件组件

3. 上传组件逻辑

3.1 基本思路

使用 el-upload 选择文件

选择成功的 回调函数 可以读取文件信息,用于前端校验文件的合法性

前端校验文件合法后,将文件进行切片

通过 请求轮询 把切片传递给后端

3.2 选择上传文件

在这一步,可以获得文件信息

根据文件信息,对文件进行合法性校验

校验成功后,调用文件切片方法

/**
 * @description: 选择上传文件
 * @param file el-upload 返回的参数
 */
const handleFileUpload = async (file: any) => {
  console.log('el-upload 返回的参数 === ', file.file);

  // 如果文件合法,则进行分片上传
  if (await checkMirrorFile(file)) {
    // 文件信息
    const files = file.file;
    // 从 0 开始的切片
    const shardIndex = 0;
    // 调用 文件切片 方法
    uploadFileSilce(files, shardIndex);
  // 文件非法,则进行提示
  } else {
    ElMessage.error('请检查文件是否合法!');
  }
};

3.3 校验文件是否合法

校验文件格式

校验文件大小

调用接口,校验磁盘剩余空间大小

/**
 * @description: 校验文件合法性
 */
const checkMirrorFile = async (file) => {
    // 校验文件格式,支持.zip/.tar
    const fileType = file.file.name.split('.')
    if (fileType[fileType.length - 1] !== 'zip' && fileType[fileType.length - 1] !== 'tar') {
        ElMessage.warning('文件格式错误,仅支持 .zip/.tar')
        return false
    }

    // 校验文件大小
    const fileSize = file.file.size;
    // 文件大小是否超出 2G
    if (fileSize > 2 * 1024 * 1024 * 1024) {
        ElMessage.warning('上传文件大小不能超过 2G')
        return false
    }

    // 调用接口校验文件合法性,比如判断磁盘空间大小是否足够
    const res = await checkMirrorFileApi()
    if (res.code !== 200) {
        ElMessage.warning('暂时无法查看磁盘可用空间,请重试')
        return false
    }
    // 查看磁盘容量大小
    if (res.data.diskDevInfos && res.data.diskDevInfos.length > 0) {
        let saveSize = 0
        res.data.diskDevInfos.forEach(i => {
            // 磁盘空间赋值
            if (i.devName === '/dev/mapper/centos-root') {
                // 返回值为GB,转为字节B
                saveSize = i.free * 1024 * 1024 * 1024
            }
        })
        // 上传的文件大小没有超出磁盘可用空间
        if (fileSize < saveSize) {
            return true
        } else {
            ElMessage.warning('文件大小超出磁盘可用空间容量')
            return false
        }
    } else {
        ElMessage.warning('文件大小超出磁盘可用空间容量')
        return false
    }
}

3.4 文件加密

此处文件上传用 MD5 进行加密,需要安装依赖 spark-md5

npm i spark-md5

/**
 * @description: 文件加密处理
 */
const getMD5 = (file: any): Promise => new Promise((resolve, reject) => {
  const spark = new SparkMD5.ArrayBuffer();
  // 获取文件二进制数据
  const fileReader = new FileReader();
  fileReader.readAsArrayBuffer(file); // file 就是获取到的文件
  // 异步执行函数
  fileReader.addEventListener('load', (e: any) => {
    spark.append(e.target.result);
    const md5: string = spark.end();
    resolve(md5);
  });
  fileReader.addEventListener('error', (e) => {
    reject(e);
  });
});

3.5 合并文件

通过接口合并上传文件,接口需要的参数:

  • 文件名
  • 文件唯一 hash 值

接口合并完成后,前端展示已上传的文件名称

/**
 * @description: 合并文件
 * @param name 文件名
 * @param hash 文件唯一 hash 值
 * @return 命名名称
 */
const composeFile = async (name: string, hash: string) => {
  console.log('开始文件合并');
  const res = await uploadFileMerge({
    applicationId: props.applicationId,
    applicationVersion: props.applicationVersion,
    bucketName: 'app',
    fileName: name,
    hash,
  });
  console.log('后端接口合并文件 ===', res);
  if (res.status === 200 && res.data.code) {
    // 合并成功后,调整已上传的文件名称
    state.editForm.inlineAppVersionModel.fileName = name;
  }
};

3.6 文件切片上传

接口轮询 —— 每次携带一个文件切片给后端;后端接受到切片 并 返回成功状态码后,再进行下一次切片上传

/**
 * @description: 分片函数
 * @param file 文件
 * @param shardIndex 分片数量
 */
const uploadFileSilce = async (file: File, shardIndex: number) => {
      // 文件名
      const { name } = file;
      // 文件大小
      const { size } = file;
      // 分片大小
      const shardSize = 1024 * 1024 * 5;
      // 文件加密
      const hash: string = await getMD5(file);
      // 分片总数
      const shardTotal = Math.ceil(size / shardSize);

      // 如果 当前分片索引 大于 总分片数
      if (shardIndex >= shardTotal) {
        isAlive.value = false;
        progress.value = 100;
        // 合并文件
        composeFile(name, hash);
        return;
      }

      // 文件开始结束的位置
      const start = shardIndex * shardSize;
      const end = Math.min(start + shardSize, size);
      // 开始切割
      const packet = file.slice(start, end);
      
      // 拼接请求参数
      const formData = new FormData();
      formData.append('file', packet);
      formData.append('applicationId', props.applicationId);
      formData.append('applicationVersion', props.applicationVersion);
      formData.append('bucketName', 'app');
      formData.append('hash', hash);
      formData.append('shardSize', shardSize as unknown as string);
      formData.append('seq', shardIndex as unknown as string);

      // 如果 当前分片索引 小于 总分片数
      if (shardIndex < shardTotal) {
        // 进度条保留两位小数展示
        progress.value = Number(((shardIndex / shardTotal) * 100).toFixed(2)) * 1;
        // 调用文件上传接口
        const res = await uploadFile(formData);
        if (res.status !== 200) {
          ElMessage.error('上传失败');
          progress.value = 0;
          return;
        }
        if (res.status === 200 && res.data.code === 200) {
          // 这里为所有切片上传成功后进行的操作
          console.log('上传成功');
        }
        // eslint-disable-next-line no-param-reassign
        shardIndex++;
        // 递归调用 分片函数
        uploadFileSilce(file, shardIndex);
      }
    };

4. 参考文章

4.1 文章链接

前端大文件上传和下载(分片上传)_BreenCL的博客-CSDN博客_前端分片上传前端大文件上传(分片上传)一、问题日常业务中难免出现前端需要向后端传输大型文件的情况,这时单次的请求不能满足传输大文件的需求,就需要用到分片上传业务需求为:用户可以上传小于20G的镜像文件,并进显示当前上传进度前端:vue3.x+Element Plus组件+axios二、解决解决思路简单为前端选择文件后读取到文件的基本信息,包括:文件的大小、文件格式等信息,用于前端校验,校验完成后将文件进行切片并通过请求轮询把切片传递给后端Vue的元素代码如下,主要借助el-upload组件:&lhttps://blog.csdn.net/baoyin0822/article/details/123922628

4.2 参考文章提到的注意事项

4.2.1 nginx 上传大小限制

nginx 默认上传大小为 1MB,若超过 1MB,则需要修改 nginx 配置 解除上传限制

4.2.2 大文件下载

/**
 * @description: 动态创建 a 标签,实现大文件下载
 */
const downloadMirror = async (item) => {
  let t = {
    id: item.id,
  }
  const res = await downloadMirrorApi(t)
  if (res.headers["content-disposition"]) {
    let temp = res.headers["content-disposition"].split(";")[1].split("filename=")[1]
    let fileName = decodeURIComponent(temp)
    // 通过创建a标签实现文件下载
    let link = document.createElement('a')
    link.download = fileName
    link.style.display = 'none'
    link.href = res.data.msg
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
  } else {
    ElMessage({
      message: '该文件不存在',
      type: 'warning',
    })
  }
}

你可能感兴趣的:(组件库,分片上传)