微信公众号中看到一篇分段上传的demo,闲来没事自己也撸一遍
// client
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<div>
<input type="file" id="file">
<button id="btn">上传</button>
</div>
<script>
function request({
url,
method,
data
}) {
return new Promise((resolve) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.send(data);
xhr.onload = e => {
resolve({
data: e.target.response
});
};
})
}
function createFileChunk(file, length) {
if (!file || !length) {
console.log('invalid file or invalid arguments');
return;
}
let fileChunkList = [];
let size = 0;
while (size < file.size) {
fileChunkList.push({
file: file.slice(size, size + length)
});
size = size + length;
}
return fileChunkList;
}
function uploadChunks(chunkList) {
const requestList = chunkList.map(item => {
const formData = new FormData();
formData.append('filename', item.name);
formData.append('hash', item.hash);
formData.append('chunk', item.chunk);
return request({
url: 'http://localhost:3000/upload',
method: 'POST',
data: formData
});
})
const formData = new FormData();
formData.append('filename', chunkList[0].name);
Promise.all(requestList).then(() => {
request({
url: 'http://localhost:3000/upload/merge',
method: 'POST',
data: formData
})
});
}
const btnDom = document.getElementById('btn');
// 切片大小为3M
const EACH_FILE_CHUNK_SIZE = 3 * 1024 * 1024;
btnDom.onclick = async function () {
const fileDom = document.getElementById('file');
const file = fileDom.files[0];
if (!file) return;
const fileChunkList = createFileChunk(file, EACH_FILE_CHUNK_SIZE);
const chunkList = fileChunkList.map((item, index) => ({
name: file.name,
hash: file.name + '_' + index,
chunk: item.file
}));
uploadChunks(chunkList);
}
</script>
</body>
</html>
// server
const http = require('http');
const path = require('path');
const fs = require('fs');
const multiparty = require('multiparty');
const server = http.createServer();
const UPLOAD_DIR = path.resolve(__dirname, 'target');
server.on('request', async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
if (req.method === 'OPTIONS') {
res.status = 200;
res.end();
return;
}
const multipart = new multiparty.Form();
if (req.url === '/upload') {
multipart.parse(req, async (err, fields, files) => {
if (err) return;
const [chunk] = files.chunk;
const [filename] = fields.filename;
const [hash] = fields.hash;
const chunkDir = `${UPLOAD_DIR}/${filename}`;
if (!fs.existsSync(chunkDir)) {
fs.mkdirSync(chunkDir);
}
fs.writeFileSync(`${chunkDir}/${hash}`, fs.readFileSync(chunk.path));
res.end('received file chunk');
})
}
if (req.url === '/upload/merge') {
multipart.parse(req, async (err, fields, files) => {
if (err) return;
const [filename] = fields.filename;
const chunkDir = `${UPLOAD_DIR}/${filename}`;
const fileDir = `${UPLOAD_DIR}/file/${filename}`;
const chunkItems = fs.readdirSync(chunkDir);
fs.writeFileSync(fileDir, '');
chunkItems.forEach(item => {
fs.appendFileSync(fileDir, fs.readFileSync(`${chunkDir}/${item}`));
fs.unlinkSync(`${chunkDir}/${item}`);
})
fs.rmdirSync(chunkDir);
})
res.end('received file');
}
});
server.listen(3000, () => console.log('server is listening at port 3000'));
这里需要注意:formdata中的file对象是blob对象,在node中fs模块的读写不支持直接操作,例子中是读了一遍其实是使用已有库方便些