上传文件大家应该都做过,前端直接把file文件传给后端就ok了,但是大文件这样传就会造成页面假死,体验极差。如果遇到网络不稳定的时候,中途上传失败的话,又要从头开始传,本来文件就大,还慢。所以今天我们用一种新方法-切片上传+断点续传
前端实现:
页面上很简单,我就放了进度条和一个上传文件组件,你可以按照三方ui来进行替换
html
<div class="box">
<progress value="0"></progress>
<br />
<input type="file" text="选择文件"/>
</div>
主要看逻辑实现
1、获取页面元素,初始化变量(vue中获取元素使用ref即可)
javascript
let ipt = doc.querySelector("input");
let progress = doc.querySelector("progress");
const chunkSize = 64 * 1024; //切片大小
let uploaded = 0; //已上传多少
2、 input绑定change事件(如果有专门的上传按钮,就给按钮绑定点击事件,如下图2)
思路:
- 通过read函数,把blob文件转为字符串,然后打成MD5
- 然后设置进度条的max为文件的size
- 判断缓存中是否有上传的进度,有就把切片初始值设置缓存中的值
- 设置循环,当前上传的大小 < 文件size时,进行切片,使用file.slice(起始值,起始值 + 切片大小)
- 把当前切片append进formdata,然后传给后端
- 然后把修改切片起始值,设置缓存进度,设置进度条当前值
javascript
// blob转为为字符串
const read = (file) => {
let reader = new FileReader();
return new Promise((resolve, reject) => {
reader.onload = function (event) {
const arrayBuffer = event.target.result;
resolve(arrayBuffer);
};
reader.onerror = reject;
reader.readAsBinaryString(file);
});
};
ipt.addEventListener("change", async (event) => {
let file = event.target.files[0];
if (!file) {
return;
}
ipt.value = null;
let content = await read(file);
let hash = md5(content);//转成hash,保证文件唯一
let { size, name, type } = file;
const local = localStorage.getItem(uploaded);//断点重传标志
progress.max = size;
if (local) {
uploaded = Number(local);
}
while (uploaded < size) {
//切片大小
const chunk = file.slice(uploaded, uploaded + chunkSize, type);
let fd = new FormData();
fd.append("name", name);
fd.append("size", size);
fd.append("type", type);
fd.append("file", chunk);
fd.append("offset", uploaded);
fd.append("hash", hash);
try {
await axios.post("http://localhost:5454/upload", fd);
} catch (error) {
console.log("上传失败");
}
uploaded += chunkSize;
localStorage.setItem(uploaded, uploaded);//设置传递进度
progress.value = uploaded;
}
console.log("上传成功");
});
javascript
btn.addEventListener('click',async()=>{
let file = ipt.files[0]
})
后端实现:
思路:
- 利用express-fileupload插件后,file在req.files中取,其他数据在req.body取
- 设置文件名,利用extreme获取传来的文件的后缀名,拼接在跟目录的upload文件夹下
- 判断offset != 0,就是已上传但是本地没有文件,创建文件并写入,否则写入到已经存在的文件中
javascript
//app.js
const express = require("express");
const bodyParser = require("body-parser");
const fileUpload = require("express-fileupload");
const { resolve, extname } = require("path");
const { existsSync, appendFileSync, writeFileSync } = require("fs");
const app = express();
const PORT = "5454";
// 跨域
app.all("*", (req, res, next) => {
res.header("Access-Control-Allow-origin", "*");
res.header("Access-Control-Allow-Methods", "POST,GET");
next();
});
app.use(bodyParser.json());
app.use(fileUpload());
app.use('/',express.static('upload'))
// app.get("/", (req, res) => {
// res.send("Hello World!");
// });
app.post("/upload", (req, res) => {
const { name, size, type, offset, hash } = req.body;
const { file } = req.files;
// console.log(name, size, type, offset, hash);
//取后缀
let ext = extname(name);
let fileName = resolve(__dirname, "./upload/" + hash + ext);
if (offset != 0) {
if (!existsSync(fileName)) {
res.status(400).send({
msg:'文件不存在'
})
return
}
appendFileSync(fileName,file.data)
res.send({
code:200,
data:'http://localhost:5454/' + fileName,
msg:'文件写入成功'
})
return
}
writeFileSync(fileName, file.data);
res.send({
code: 200,
msg: "文件创建并写入成功",
});
});
app.listen(PORT, () => {
console.log(`server running in ${PORT}`);
});
以上就实现了大文件上传的基本方法,像文件类型校验啥的可自行扩展,本demo只是基本示例,如有更好的方法,可在评论区留下讨论,共同进步~
源码地址:大文件上传: 大文件上传demo