Vue
<template>
<div class="upload">
<el-upload
:before-upload="befUpload"
class="upload-demo"
drag
action="/"
:show-file-list="false"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">拖拽或<em>点击上传</em></div>
</el-upload>
</div>
</template>
<script setup>
import axios from "axios";
function befUpload(file) {
console.log(file);
const eachSize = 2 * 1024 * 1024; // 每个chunks的大小
const blockCount = Math.ceil(file.size / eachSize); // 分片总数
const axiosArray = []; // axiosPromise数组
let ext = file.name.split(".");
ext = ext[ext.length - 1]; // 获取文件后缀名
// 通过hash标识文件
let random = Math.random().toString();
random = random.split(".");
random = random[random.length - 1];
let hash = Date.now() + random + file.lastModified; // 文件 hash 实际应用时,hash需要更加复杂,确保唯一性,可以使用uuid
// 处理每个分片的上传操作
for (let i = 0; i < blockCount; i++) {
let start = i * eachSize,
end = Math.min(file.size, start + eachSize);
// 构建表单
const form = new FormData();
form.append("file", file.slice(start, end));
form.append("name", file.name);
form.append("total", blockCount);
form.append("ext", ext);
form.append("index", i);
form.append("size", file.size);
form.append("hash", hash);
// ajax提交 分片,此时 content-type 为 multipart/form-data
const axiosOptions = {
onUploadProgress: (e) => {
handleXhrProgressCallback(blockCount, i, e);
},
};
// 加入到 Promise 数组中
axiosArray.push(
axios.post("http://127.0.0.1:51413/v1/upload_chunks", form, axiosOptions)
);
}
// 所有分片上传后,请求合并分片文件
axios.all(axiosArray).then(() => {
// 合并chunks
const data = {
name: file.name,
total: blockCount,
ext,
hash,
resetname:"1"
};
// resetname: 后台做个判断是否自定义名字
axios
.post("http://127.0.0.1:51413/v1/merge_chunks", data)
.then((res) => {
handleXhrSuccessCallback(res.data);
})
.catch((err) => {
handleXhrErrorCallback(err);
});
});
}
function handleXhrProgressCallback(total, index, e) {
console.log(`当前上传第 ${index + 1} 个chunk,共计 ${total}`);
}
// 上传成功处理
function handleXhrSuccessCallback(data) {
console.log("上传成功处理");
console.log(data);
}
// 上传失败处理
function handleXhrErrorCallback(err) {
console.log(err);
}
</script>
<style>
</style>
Node
chunk.js
const multer = require('multer');
const chunksBasePath = '~uploads/';
const storage = multer.diskStorage({
destination:chunksBasePath,
});
const baseUpload = multer({storage});
const upload = baseUpload.single('file');
/**
* @name uploadMiddleware
* @description 文件上传中间件,upload 方法调用的时候 会有 err 进行错误判断
* @param {*} req
* @param {*} res
* @param {*} next
*/
const uploadMiddleware = (req,res,next)=>{
upload(req,res,(err)=>{
if(err){
// 进行错误捕获
res.json({code:-1,msg:err.toString()});
}else{
next();
}
});
};
module.exports = uploadMiddleware;
app.js
const express = require('express');
const app = express();
const port = 51413;
const bodyParser = require('body-parser');
app.use(require('cors')())
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(bodyParser.json())
app.use(express.static('./public'))
app.use(express.static('./uploads'))
app.use('/v1', require('./route/upload'))
app.listen(port,()=>{
console.log('sever run to http://127.0.0.1:' + port);
})
upload.js
const express = require('express')
const router = express.Router();
const uploadChunksMiddleware = require('../chunk.js');
const fs = require('fs');
const path = require('path');
const fileBasePath = 'uploads/';
const chunkBasePath = '~uploads/';
router.post('/upload_chunks', uploadChunksMiddleware, (req, res) => {
// 创建chunk的目录
const chunkTmpDir = chunkBasePath + req.body.hash + '/';
// 判断目录是否存在
if (!fs.existsSync(chunkTmpDir)) fs.mkdirSync(chunkTmpDir);
// 移动切片文件
fs.renameSync(req.file.path, chunkTmpDir + req.body.hash + '-' + req.body.index);
res.send(req.file);
})
// 文件分片
router.post('/merge_chunks', (req, res) => {
const total = req.body.total;
const hash = req.body.hash;
const resetname = req.body.resetname;
// const saveDir = '2344'
const saveDir = fileBasePath + new Date().getFullYear() + (new Date().getMonth() + 1) + new Date().getDate() + '/';
let savePath = '';
if (resetname == '1') {
savePath = saveDir + Date.now() + hash + '.' + req.body.ext;
}else {
savePath = saveDir + resetname + '.' + req.body.ext;
}
const chunkDir = chunkBasePath + '/' + hash + '/';
console.log(savePath);
try {
// 创建保存的文件夹(如果不存在)
if (!fs.existsSync(saveDir)) fs.mkdirSync(saveDir);
// 创建文件
fs.writeFileSync(savePath, '');
// 读取所有的chunks 文件名存放在数组中
const chunks = fs.readdirSync(chunkBasePath + '/' + hash);
// 检查切片数量是否正确
if (chunks.length !== total || chunks.length === 0) return res.send({ code: -1, msg: '切片文件数量不符合' });
for (let i = 0; i < total; i++) {
// 追加写入到文件中
fs.appendFileSync(savePath, fs.readFileSync(chunkDir + hash + '-' + i));
// 删除本次使用的chunk
fs.unlinkSync(chunkDir + hash + '-' + i);
}
// 删除chunk的文件夹
fs.rmdirSync(chunkDir);
// 返回uploads下的路径,不返回uploads
res.json({
code: 0, msg: '文件上传成功', data: {
path: savePath.split(fileBasePath)[savePath.split(fileBasePath).length - 1]
}
});
} catch (err) {
res.json({ code: -1, msg: '出现异常,上传失败' });
}
});
module.exports = router
Github: https://github.com/WishMelz/Express-chunk-upload