大文件上传、切片上传、断点续传

概要

大文件上传、切片上传、断点续传

整体架构流程

后端:技术express、express-fileupload、fs、path等
导入所需的模块:express、express-fileupload、fs、path等。
创建Express应用程序实例。
设置CORS(跨域资源共享)允许跨域访问。
设置静态资源访问,将public文件夹作为静态资源目录。
使用express.json()中间件解析请求体中的JSON数据。
使用express-fileupload中间件处理文件上传。
定义文件上传的路由,包括上传切片文件和合并切片文件的接口。
在上传切片文件的接口中,根据切片文件的名称创建临时存储目录,并将切片文件保存到对应目录中。如果文件已存在,则返回文件已存在的提示。
在合并切片文件的接口中,读取所有切片文件,按照切片文件名中的序号排序,将切片文件内容追加到合并文件中,最后删除切片文件所在的目录。
在查询已上传切片文件的接口中,根据传入的hash值读取对应目录下的所有切片文件,按照序号排序后返回给客户端。

前端:基于Vue和Element UI的大文件断点续传功能。主要的组件是el-upload和el-progress。
在data中定义了一些需要用到的数据,包括上传进度percentage、文件列表fileList、已上传的文件列表already等。
handleRemove、handlePreview、handleExceed、beforeRemove等方法是对上传组件进行操作的回调函数。
handleUpload方法是处理文件上传的逻辑。首先将文件加入fileList,并检查是否已经存在相同hash值的文件,如果有则从localStorage中读取之前的上传进度。然后根据文件大小和指定的chunkSize将文件切片成多个chunk,分别上传每个chunk。在上传过程中通过onUploadProgress回调更新当前的上传进度。
handlePause方法是暂停上传的操作,将isPaused设置为true,同时设置uploading为false以停止上传。
handleResume方法是恢复上传的操作,将isPaused设置为false,如果之前有上传过的文件且上传进度不是100%,则从上次中断的地方继续上传;否则从fileList中最后一个文件开始上传。
clearFiles方法用于清空文件列表和上传进度,并清空localStorage中的数据。

内联代码片

  1. 后台
let path = "";
    //创建存储大文件的临时目录
    let [, HASH] = /^([^_]+)_(\d+)/.exec(filename);
    path = `${uploadDir}/${HASH}`;
    !fs.existsSync(path) ? fs.mkdirSync(path) : null;
    path = `${uploadDir}/${HASH}/${filename}`;
    let isExists = await exists(path);
    if (isExists) {
      res.send({
        code: 0,
        msg: "文件已存在",
      });
      return;
    }
    fs.writeFile(path, file.data, async function (err) {
      console.log('-------写文件------------');
      if (err) {
        res.send({ code: 0, msg: "failure" });
      } else {

        res.send({ code: 1, msg: "success" });
      }
    });
  } catch (error) {
    res.send({ code: 0, msg: "failure" });
  }
//读取所有切片文件
  const { hash } = req.query;
  let path = `${uploadDir}/${hash}`;
  let suffix;
  const chunksName = await fs.readdirSync(`${uploadDir}/${hash}`);
  //进行排序
  chunksName.sort((a, b) => {
    let reg = /_(\d+)/;
    return reg.exec(a)[1] - reg.exec(b)[1];
  });
  chunksName.forEach((item) => {
    !suffix ? (suffix = /\.([0-9a-zA-Z]+)$/.exec(item)[1]) : null;
    fs.appendFileSync(
      `${uploadDir}/${hash}.${suffix}`,
      fs.readFileSync(`${path}/${item}`)
    );
    fs.unlinkSync(`${path}/${item}`);
  });
  //删除分片所在的文件夹
  fs.rmdirSync(path);
  res.send({
    code: 200,
    msg: "上传完毕",
  });
const { hash } = req.query;
  let path = `${uploadDir}/${hash}`;
  let fileList = [];
  console.log('path', path);
  try {
    let chunksName = await fs.readdirSync(path);
    console.log('chunksName before', chunksName);
    //进行排序
    chunksName.sort((a, b) => {
      let reg = /_(\d+)/;
      return reg.exec(a)[1] - reg.exec(b)[1];
    });
    console.log('chunksName afeter', chunksName);
    res.send({
      code: 1,
      msg: "",
      fileList: chunksName,
    });
  } catch (error) {
    res.send({
      code: 0,
      msg: "",
      fileList,
    });
  }
  • 前端代码
 data() {
    return {
      percentage: 0, // 上传进度
      fileList: [], // 文件列表
      already: [], // 已上传文件列表
      isPaused: false, // 是否暂停上传
      uploading: false, // 是否正在上传
      currentFile: null, // 当前上传的文件
      currentProgress: 0 // 当前上传的进度
    }
  },
this.uploading = true; // 开始上传
        const { name, size } = file;

        // 读取文件内容
        const content = await read(file);

        // 使用SparkMD5库计算文件内容的哈希值
        let spark = new SparkMD5.ArrayBuffer();
        spark.append(content);
        const hash = spark.end();

        // 检查本地存储是否有相同哈希值的文件已经上传过
        if (localStorage.getItem(`${hash}`)) {
          this.percentage = ~~localStorage.getItem(`${hash}`);
        }

        // 检查是否已经上传过一些片段
        const result = await axios.get("http://127.0.0.1:3000/api/upload_already", { params: { hash } });
        this.already = result.data.fileList;

        const chunkSize = 1 * 1024 * 1024;
        const chunkList = [];
        const chunkListLength = Math.ceil(size / chunkSize);
        const suffix = /\.([0-9A-z]+)$/.exec(name)[1];
        let index = 0;

        // 将文件切片成多个chunk
        while (index < chunkListLength) {
          chunkList.push({
            chunk: file.slice(index * chunkSize, (index + 1) * chunkSize),
            fileName: `${hash}_${index + 1}.${suffix}`
          });
          index++;
        }

        let count = 0;
        for (let i = 0; i < chunkListLength; i++) {
          if (this.isPaused) {
            break;
          }

          let item = chunkList[i];

          // 如果该chunk已经在已上传的部分文件列表中,则跳过该chunk继续上传下一个
          if (this.already.length > 0 && this.already.includes(item.fileName)) {
            count++;
            this.percentage = Math.floor((count / chunkListLength) * 100);
            continue;
          }

          const formData = new FormData();
          formData.append("file", item.chunk);
          formData.append("filename", item.fileName);

          // 发送POST请求上传chunk,并监听上传进度
          await axios.post("http://127.0.0.1:3000/api/upload_chunk", formData, {
            headers: {
              "Content-Type": "multipart/form-data",
            },
            onUploadProgress: (progressEvent) => {
              this.currentFile = file;
              this.currentProgress = Math.floor(((i * chunkSize + progressEvent.loaded) / size) * 100);
              this.percentage = this.currentProgress;
            },
          });

          count++;
          this.percentage = Math.floor((count / chunkListLength) * 100);
          localStorage.setItem(`${hash}`, this.percentage.toString());

          if (count === chunkListLength) {
            // 完成所有chunk的上传后,发送请求进行文件合并
            await axios.get("http://127.0.0.1:3000/api/upload_merge", { params: { hash } });
            MessageBox({
              type: "success",
              message: "上传成功!",
            });
            this.percentage = 0
            this.uploading = false;
            return;
          }
        }
      }
 this.isPaused = true;
      // 停止上传
  this.uploading = false;

项目地址:https://gitee.com/wang_fan123/bigFile-upload

技术细节

演示视频

在这里插入图片描述

小结

代码比较简单,存在些许bug,欢迎指出共同进步

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前端文件上传指的是上传文件大小较大,超过了常规的文件上传限制(例如2MB)。这时需要使用特殊的上传方式,常见的有两种: 1. 分片上传:将大文件分成多个小文件,分别上传到服务器,并在服务器端将这些小文件合并成一个完整的文件。 2. 断点续传:将大文件分成多个小文件,分别上传到服务器,上传过程中若出现网络中断或其他原因导致上传失败,可以从失败的位置继续上传,避免重新上传整个文件。 实现前端文件上传断点续传需要使用一些第三方库或框架,例如: 1. Plupload:一个基于Flash和HTML5的文件上传库,支持分片上传断点续传。 2. Resumable.js:一个基于HTML5的文件上传库,支持断点续传。 3. jQuery-File-Upload:一个基于jQuery的文件上传插件,支持分片上传断点续传。 在使用这些库或框架实现大文件上传断点续传时,需要注意以下几点: 1. 服务器端需要支持分片上传断点续传,否则无法实现这些功能。 2. 分片上传断点续传需要对文件进行切片,这可能会影响上传速度和文件完整性,需要做好相应的处理。 3. 断点续传需要记录上传进度,以便在上传失败时能够从失败的位置继续上传。 总之,前端文件上传断点续传是一个比较复杂的问题,需要使用一些专门的库或框架来实现,同时需要注意一些细节问题,才能保证上传效率和上传成功率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值