大文件的断点续传再次理解

<template>
  <div id="app">
    <h1>App3</h1>
    <el-upload drag action :auto-upload="false" :show-file-list="false" :on-change="changeFile" :on-success="handleSuccess">
      <i class="el-icon-upload"></i>
      <div class="el-upload__text">
        大文件的断点续传将文件拖到此处,或
        <em>点击上传</em>
      </div>
    </el-upload>

    <!-- PROGRESS -->
    <div class="progress">
      <span>上传进度:{{total|totalText}}%</span>
      <el-link type="primary" v-if="total>0 && total<100" @click="handleBtn">{{btn|btnText}}</el-link>
    </div>

    <!-- VIDEO -->
    <div class="uploadImg" v-if="video">
      <video :src="video" controls />
    </div>
  </div>
</template>

<script>
import { fileParse } from "./assets/utils";
import axios from "axios";
import SparkMD5 from "spark-md5";

export default {
  name: "App",
  data() {
    return {
      total: 0,
      video: null,
      btn: false,//false显示暂停     true显示继续
      abort :false
    };
  },
  filters: {
    btnText(btn) {//这个是过滤器filters
      return btn ? "继续" : "暂停";
    },
    totalText(total) {
      return total > 100 ? 100 : total;
    },
  },
  methods: {
    handleSuccess(){
      console.log(1)
    },
    async changeFile(file) {
      console.log('file',file)
      if (!file) return;
      file = file.raw;
      console.log('file→',file)
      // 解析为BUFFER数据
      // 我们会把文件切片处理:把一个文件分割成为好几个部分(固定数量/固定大小)
      // 每一个切片有自己的部分数据和自己的名字
      // HASH_1.mp4
      // HASH_2.mp4
      // ...
      let buffer = await fileParse(file, "buffer"),
        spark = new SparkMD5.ArrayBuffer(),
        hash,
        suffix;
      spark.append(buffer);
      hash = spark.end();
      suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.name)[1];//拿到文件名的后缀

      // 创建100个文件切片的(集合)
      let partList = [],
        partsize = file.size / 100,//partsize是一个切片的大小
        cur = 0;//cur这个变量很关键作用是切片化记录当前切片切刀哪里
        // 文件可以使用slice方法去切
        // slice方法表示拿到cur到cur+partsize之间的文件切片
      for (let i = 0; i < 100; i++) {
        let item = {
          chunk: file.slice(cur, cur + partsize),
          filename: `${hash}_${i}.${suffix}`,
        };
        cur += partsize;
        partList.push(item);
      }
      this.partList = partList;
      this.hash = hash;//这是文件名
      this.sendRequest();
    },
    // /single3这个接口是用来上传切片的/merge是用来合并切片的接口
    async sendRequest() {
      // 根据100个切片创造100个请求函数的(集合)
      let requestList = [];
      this.partList.forEach((item, index) => {
        // 每一个函数都是发送一个切片的请求
        let fn = () => {
          let formData = new FormData();
          formData.append("chunk", item.chunk);
          formData.append("filename", item.filename);
          return axios
            .post("/single3", formData, {
              headers: { "Content-Type": "multipart/form-data" },
            })
            .then((result) => {
              result = result.data;
              if (result.code == 0) {
                this.total += 1;
                // 传完的切片我们把它移除掉
                this.partList.splice(index, 1);
              }
            });
        };//这里只是写了一个函数,并没有调用
        requestList.push(fn);
      });

      // 传递:并行(只能用ajax.abort()去终止请求发送)【并发的话浏览器各个浏览器限值请求的并发量不一样有所限制也不好处理】,/串行(基于标志控制不发送)传完一个切片在传下一个
      let i = 0;//i是记录传完切片的个数的
      let complete = async () => {
        let result = await axios.get("/merge", {
          params: {
            hash: this.hash,//传个hash值给后端让后端去合并文件切片
          },
        });
        result = result.data;
        if (result.code == 0) {
          this.video = result.path;//合并成功后返回一个合并后文件的地址并且显示在页面上
        }
        alert("文件上传完毕!")
      };
      let send = async () => {
        // 已经中断则不再上传
        if (this.abort) return;
        if (i >= requestList.length) {
          // 当i>=requestList.length的时候表示切片都传完了。
          complete();
          return;
        }
        await requestList[i]();
        i++;
        send();
      };
      send();
    },
    handleBtn() {
      if (this.btn) {
        //断点续传
        this.abort = false;//abort为false的时候表示取消终止,目前是正在上传的状态
        this.btn = false;//已经开始传了就让按钮变成暂停
        this.sendRequest();
        return;
      }
      //让文件暂停上传
      this.btn = true;//已经终止了上传就让按钮变成继续
      this.abort = true;//abort为true的时候表示已经终止上传了
    },
  },
};
</script>

serverjs

/*-CREATE SERVER-*/
const express = require('express'),
    app = express(),
    bodyParser = require('body-parser'),
    fs = require('fs'),
    SparkMD5 = require('spark-md5'),
    PORT = 8888;
app.listen(PORT, () => {
    console.log(`THE WEB SERVICE IS CREATED SUCCESSFULLY AND IS LISTENING TO THE PORT:${PORT}`);
});
app.use(bodyParser.urlencoded({
    extended: false,
    limit: '1024mb'
}));

/*-API-*/
const multiparty = require("multiparty"),//这个模块用来处理form-data数据
    uploadDir = `${__dirname}/upload`;

function handleMultiparty(req, res, temp) {
    return new Promise((resolve, reject) => {
        // multiparty的配置
        let options = {
            maxFieldsSize: 200 * 1024 * 1024//这个配置表示你支持的文件最大上传多大
        };
        !temp ? options.uploadDir = uploadDir : null;//options.uploadDir表示你上传的地址是什么
        let form = new multiparty.Form(options);//把options对象放进去表示开始传文件了
        // multiparty解析
        form.parse(req, function (err, fields, files) {
            if (err) {
                res.send({
                    code: 1,
                    reason: err
                });
                reject(err);
                return;
            }
            resolve({
                fields,
                files
            });
        });
    });
}

// 基于FORM-DATA上传数据
app.post('/single1', async (req, res) => {
    console.log("req",req)
    let {
        files
    } = await handleMultiparty(req, res);//拿到的files是你上传的多个文件的信息,files.file[0]就是你上传的那个文件的信息对象
    let file = files.file[0];
    console.log('file.originalFilename→',file.originalFilename);
    console.log('file.path→',file.path);
    res.send({
        code: 0,
        originalFilename: file.originalFilename,//originalFilename表示你当初上传的原始文件名
        path: file.path.replace(__dirname, `http://127.0.0.1:${PORT}`)//返回去带服务器端口号和ip的文件地址
        //把file.path地址当中表示__dirname的替换成服务器地址
    });
});

// 上传BASE64
app.post('/single2', (req, res) => {
    let {
        chunk,
        filename
    } = req.body;

    // chunk的处理:转换为buffer解码
    chunk = decodeURIComponent(chunk);
    chunk = chunk.replace(/^data:image\/\w+;base64,/, "");//把base64开头去掉
    chunk = Buffer.from(chunk, 'base64');//把base64转换成刚Buffer数据

    // 存储文件到服务器
    let spark = new SparkMD5.ArrayBuffer(), //SparkMD5可以通过文件内容生成一个文件名的hash值
        suffix = /\.([0-9a-zA-Z]+)$/.exec(filename)[1],//这个接口是获得传进来的文件的后缀名
        path;
    spark.append(chunk);//然后把解析后的buffer数据放到Spark中
    // 通过spark.end()可以拿到文件解析后的hash文件名
    // 使用SparkMD5这个模块是根据文件内容生成文件名的hash值,而写入文件使用,fs.writeFileSync(path, chunk),时地址一样会覆盖的重新写入所以不会出现上传多次同一张图片/文件后端同时保存多张同一图片/文件
    path = `${uploadDir}/${spark.end()}.${suffix}`;//拿到写入文件夹的地址
    const dir=`${uploadDir}`
    const files=fs.readdirSync(dir)
    console.log("files→",files)
    var arr=[]
    files.forEach(function(item){
        if(item==`${spark.end()}.${suffix}`){
            arr.push(item)
        }
    })
    if(arr.length==0){
        fs.writeFileSync(path, chunk);//然后把文件写入到相应的文件夹
    }//如果文件夹内有同样的文件就不写入而是直接返回图片地址。
    res.send({
        code: 0,
        originalFilename: filename,
        path: path.replace(__dirname, `http://127.0.0.1:${PORT}`)
    });
});

// 切片上传 && 合并
app.post('/single3', async (req, res) => {
    let {
        fields,
        files
    } = await handleMultiparty(req, res, true);

    let [chunk] = files.chunk,
        [filename] = fields.filename;
    let hash = /([0-9a-zA-Z]+)_\d+/.exec(filename)[1],//因为我们传过来的每一个切片的filename都是hash_索引+文件格式, 左边代码作用是我们拿到前端传过来文件的hash名
        // suffix = /\.([0-9a-zA-Z]+)$/.exec(file.name)[1],//如左是拿到传过来文件的后缀名
        path = `${uploadDir}/${hash}`;//会临时创建一个文件夹,文件夹是以hash值命名的,后面传过来的同hash名的切片块都放在这个文件夹里
    !fs.existsSync(path) ? fs.mkdirSync(path) : null;
    // fs.existsSync(path)这个api是为了检测path这个文件目录是否存在的,如果存在就会检测到返回true否则返回false
    // fs.mkdirSync(path)创建文件夹
    path = `${path}/${filename}`;
    // 判断文件是否存在,如果存在的话那么就直接返回文件地址,实现秒传
    fs.access(path, async err => {
        // 存在的则不再进行任何的处理
        if (!err) {
            res.send({
                code: 0,
                path: path.replace(__dirname, `http://127.0.0.1:${PORT}`)
            });
            return;
        }

        // // 为了测试出效果,延迟1秒钟
        // await new Promise(resolve => {
        //     setTimeout(_ => {
        //         resolve();
        //     }, 200);
        // });

        // 不存在的再创建,写入对应带hash名的地址下
        let readStream = fs.createReadStream(chunk.path),
            writeStream = fs.createWriteStream(path);
        readStream.pipe(writeStream);
        readStream.on('end', function () {
            fs.unlinkSync(chunk.path);
            res.send({
                code: 0,
                path: path.replace(__dirname, `http://127.0.0.1:${PORT}`)
            });
        });
    });
});

app.get('/merge', (req, res) => {
    let {
        hash
    } = req.query;

    let path = `${uploadDir}/${hash}`,
        fileList = fs.readdirSync(path),
        suffix;
    fileList.sort((a, b) => {//把文件切片排序
        let reg = /_(\d+)/;
        return reg.exec(a)[1] - reg.exec(b)[1];
    }).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: 0,
        path: `http://127.0.0.1:${PORT}/upload/${hash}.${suffix}`
    });
});

app.use(express.static('./'));
app.use((req, res) => {
    res.status(404);
    res.send('NOT FOUND!');
});
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值