大文件分片上传
-
前端:
利用 Blob.prototype.slice 方法对大文件进行分片,和数组的slice类似,调用之后可以返回大文件中对应的分片文件。
这样我们就可以把大文件切分成一个个的小片段,利用
http的可并发性
,同时上传多个分片,这样从上传一个大文件变成了同时上传多个小文件,大大的减少了上传时间。- 分片顺序
由于http的可并发性,多个文件可以同时上传,这时记录
每个分片的顺序
是尤为重要的,否则之后合并的时候会出错。解决:将传给后端的文件名按照:文件的名称+index+后缀命名
后端处理:文件的名称作为当前分片的整个文件夹名称,index作为分片的名称
保证了分片的顺序
- 何时合并分片
index * chunkSize : index当前切片的顺序, chunkSize 每片饭店大小 如果这个大小大于当前文件总大小,说明所有分片已经全部上传完成,进行合并分片
<template>
<div>
<input type="file" ref="btnFile" />
<button @click="uploadFile(0)">上传</button>
</div>
</template>
<script setup>
import { upphoto, uploadSlice, merge } from "@/api/items";
import { ref } from "vue";
const btnFile = ref(null);
const chunkSize = 1024 * 1024;
const uploadFile = (index) => {
let file = btnFile.value.files[0];
let fileNameArr = file.name.split(".");
let fname = fileNameArr[0];
let fext = fileNameArr[1];
let start = index * chunkSize;
if (start > file.size) {
handlemerge(file.name);
return;
}
let blob = file.slice(start, start + chunkSize);
let blobName = `${fname}.${index}.${fext}`; // 文件名:文件的名称+index+后缀命名
let blobFile = new File([blob], blobName);
let formData = new FormData();
formData.append("file", blobFile);
uploadSlice(formData).then((res) => {
uploadFile(++index);
});
};
const handlemerge = (name) => {
merge({ name }).then((res) => {
console.log(res);
});
};
</script>
<style lang='scss' scoped>
</style>
-
node端
利用appendFileSync将分片数据合并,appendFileSync()方法用于将给定数据同步追加到文件中。如果新文件不存在,则会创建一个新文件。
module.exports = (app) => {
const express = require('express');
const router = express.Router();
const multiparty = require('multiparty')
const fs = require('fs')
const fse = require('fs-extra')
const path = require('path')
const upload_dir = __dirname + '/../../uploadsMultipart/slice'
router.post('/uploadSlice', (req, res) => {
const form = new multiparty.Form({ uploadDir: upload_dir })
form.parse(req)
form.on('file', async (name, chunk) => {
console.log(chunk);
// 存放切片的目录
let chunkDir = `${upload_dir}/${chunk.originalFilename.split('.')[0]}`
// 是否存在 chunkDir 这个目录,不存在就创建一个
if (!fse.existsSync(chunkDir)) {
await fse.mkdirs(chunkDir)
}
// 原文件名
let dPath = path.join(chunkDir, chunk.originalFilename.split('.')[1])
// 移动文件
await fse.move(chunk.path, dPath, { overwrite: true })
res.send('文件上传成功')
})
})
router.post('/merge', async (req, res) => {
let name = req.body.name
let fname = name.split('.')[0]
let chunkDir = path.join(upload_dir, fname)
let chunks = await fse.readdir(chunkDir)
// 进行排序合并
chunks.sort((a, b) => a - b).map(chunkPath => {
fs.appendFileSync(
path.join(upload_dir, name),
fs.readFileSync(`${chunkDir}/${chunkPath}`)
)
})
// 删除文件夹
fse.removeSync(chunkDir)
res.send({
msg: '合并成功',
url: `http://localhost:9000/uploadsMultipart/slice/${name}`
})
})
app.use('/admin/api', router);
}
- uploadsMultipart/slice 文件目录图示