文件分段上传

微信公众号中看到一篇分段上传的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模块的读写不支持直接操作,例子中是读了一遍其实是使用已有库方便些

 

原文链接:https://mp.weixin.qq.com/s/0rZ8KDukVuoxuT1R1z70Cg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值