分段上传文件

后端koa+formidable

const fs = require("mz/fs")
const path = require("path")
const Koa = require("koa")
const app = new Koa()
const formidable = require("formidable")

const static = require("./lib/koa-static")
const uploadPath = path.resolve(__dirname, "./temps")

app.use(static(__dirname))

app.use(async (ctx, next) => {
  if (ctx.path === "/upload" && ctx.method === "POST") {
    let isExit = fs.existsSync(uploadPath)
    //不存在上传文件夹则创建
    if (!isExit) fs.mkdirSync(uploadPath)
    let form = new formidable.IncomingForm()
    form.uploadDir = uploadPath;

    // 文件名不存在,则表示还没全部上传成功
    let filename = await parser(form, ctx.req)
    if (!filename) {
      ctx.body = `模块${num}上传成功`
    } else {
      console.log("complete")
      await mergeFiles()
      ctx.body = "complete"
    }
  } else {
    await next()
  }
})

let num = 0
let total = 0
let filename = ""
async function parser(form, req) {
  return await new Promise((resolve, rejects) => {
    let filepath = null
    form.parse(req, async (err, fields, files) => {
      if (err) rejects(error)
      filename = fields.filename
      total = fields.count * 1
      filepath = files.chunk.path
      await fs.rename(filepath, path.join(uploadPath, num + ""))
    })
    form.on("end", async () => {
      if (++num == total) {
        // 如果全部模块上传完成,返回文件名,上传模块重置为0
        num = 0
        setTimeout(() => {
          resolve(filename)
        })
      } else {
        // 还没全部上传完
        resolve(false)
      }
    })
  })
}

// 合并上传的文件
async function mergeFiles() {
  let dirs = await fs.readdir(uploadPath)
  dirs = dirs.sort((a, b) => a - b).map((dir) => path.join(uploadPath, dir))
  let ws = fs.createWriteStream(path.join(uploadPath, filename))
  for (let i = 0; i < dirs.length; i++) {
    ws.write(fs.readFileSync(dirs[i]))
  }
  ws.end()
  ws.on("close", () => {
    console.log("close")
  })
}

app.use(async (ctx, next) => {
  ctx.body = "Not Found"
})
app.listen(3000);

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>upload</title>
</head>
<body>
    <div><input type="file" id="input"></div>
    <button id="btn">submit</button>
<script>

    let inputel = document.getElementById("input");
    let submitBtn = document.getElementById("btn");

    let index = 0;
    // 每次只上传10mb
    let size = 1024*1024*10;
    let file,filename,totalSize,count;

    inputel.addEventListener("change",(e)=>{
        file = e.target.files[0];
        filename = file.name;
        totalSize = file.size;  // 总大小
        count = Math.ceil(totalSize/size); // 总上传次数
        console.log("count",count);
    });

    function upload(){
        let uploadSize = index*size;
        let min = Math.min(size,totalSize-uploadSize);
        if(min>=0){
            let fd = new FormData();
            fd.append("chunkIndex",index);
            fd.append("count",count);
            fd.append("filename",filename);
            // slice() 可以将file对象转化成 Blob 
            // Blob 对象表示一个不可变、原始数据的类文件对象
            fd.append("chunk",file.slice(uploadSize,uploadSize+min));
            uploadFile(fd);
            index++;
            upload();
        }
    }
    function uploadFile(fd){
        let request = new XMLHttpRequest();
        request.open("POST","/upload",true);
        request.onload = function(){
            console.log(request.response);
        }
        request.send(fd)
    }
    submitBtn.addEventListener("click",()=>{
        upload();
    })

</script>
</body>
</html>