使用Dicer处理异步multipart数据时如何将分片归类到对应文件
解决方法
Dicer本身的事件模型已经天然解决了分片归属的问题,不需要额外的全局标识字段,核心逻辑如下:
- 每一次
part事件回调拿到的p对象,对应一个独立的multipart分段(要么是普通表单字段,要么是一个完整的上传文件) - 同一个
p对象触发的所有data事件返回的数据分片,都属于当前这个分段,不同分段的事件回调完全独立,不会出现分片交叉混乱的情况 - 只需要利用JavaScript的函数作用域隔离每个
part的上下文,就能把分片正确归类到所属文件
实现步骤
- 在
part事件的回调作用域内,声明当前分段的专属存储变量(比如文件写入流、数据缓冲区、文件元信息等) - 在
header事件中解析Content-Disposition请求头,提取字段名、文件名,判断当前分段是否为上传文件 - 在当前
part的data事件中,把收到的数据分片直接写入当前作用域对应的存储介质中 - 在当前
part的end事件中完成文件收尾操作
示例代码
var inspect = require('util').inspect, http = require('http'), fs = require('fs'), path = require('path'); // 可使用content-disposition模块解析请求头,也可自行编写正则解析 var contentDisposition = require('content-disposition'); var Dicer = require('dicer'); var RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i, HTML = Buffer.from('<html><head></head><body>\ <form method="POST" enctype="multipart/form-data">\ <input type="text" name="textfield"><br />\ <input type="file" name="filefield" multiple><br />\ <input type="submit">\ </form>\ </body></html>'), PORT = 8080, UPLOAD_DIR = path.join(__dirname, 'uploads'); // 上传文件存储目录 // 提前创建上传目录 if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true }); http.createServer(function(req, res) { var m; if (req.method === 'POST' && req.headers['content-type'] && (m = RE_BOUNDARY.exec(req.headers['content-type']))) { var d = new Dicer({ boundary: m[1] || m[2] }); d.on('part', function(p) { // 当前part的专属变量,作用域隔离不会和其他part冲突 let writeStream = null; let currentFileInfo = null; p.on('header', function(header) { // 解析content-disposition头 const disposition = contentDisposition.parse(header['content-disposition'][0]); // 判断是文件类型的分段 if (disposition.parameters.filename) { currentFileInfo = { fieldName: disposition.parameters.name, fileName: disposition.parameters.filename, savePath: path.join(UPLOAD_DIR, Date.now() + '_' + disposition.parameters.filename) }; // 创建当前文件的写入流 writeStream = fs.createWriteStream(currentFileInfo.savePath); } }); p.on('data', function(data) { // 只有当前part是文件的时候才写入数据 if (writeStream) writeStream.write(data); // 如果是普通表单字段可以把data拼到缓冲区自行处理 }); p.on('end', function() { if (writeStream) { writeStream.end(); console.log('文件上传完成:', currentFileInfo); } }); }); d.on('finish', function() { console.log('所有分段处理完成'); res.writeHead(200); res.end('Form submission successful!'); }); req.pipe(d); } else if (req.method === 'GET' && req.url === '/') { res.writeHead(200); res.end(HTML); } else { res.writeHead(404); res.end(); } }).listen(PORT, function() { console.log('Listening for requests on port ' + PORT); });
注意事项
- 大文件上传不要用Buffer拼接数据,直接用文件流写入可以避免内存溢出
- 文件名要做重名处理,避免同名文件覆盖,示例中用时间戳做前缀就是这个目的
- 生产环境建议增加文件大小、格式校验,避免恶意上传
内容的提问来源于stack exchange,提问作者Gary
相关产品推荐
相关产品推荐

