目录

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',    })  }}