node进阶之用流实现上传文件
内容:
1.文件上传基础
2.node文件处理机制
3.用流实现文件上传
1.文件上传基础
前端代码:
1 <form action="localhost:8080/" method="post" enctype="multipart/form-data"> 2 <input type="file" name="f1"> 3 <input type="submit" value="上传文件"> 4 </form> 5 6 注意: 7 上传文件时表单中的enctype="multipart/form-data"必须要写 8 input(file)必须要有name
后端代码:
1 const http = require('http'); 2 const uuid = require('uuid/v4'); 3 const fs = require('fs') 4 5 let server_post = http.createServer((req, res) => { 6 let arr = []; 7 8 req.on('data', data => { 9 arr.push(data); 10 }); 11 req.on('end', () => { 12 let data = Buffer.concat(arr); 13 // console.log(data) 14 15 //data 16 //解析二进制文件上传数据 17 let post = {}; 18 let files = {}; 19 if (req.headers['content-type']) { 20 let str = req.headers['content-type'].split('; ')[1]; 21 if (str) { 22 let boundary = '--' + str.split('=')[1]; 23 24 //1.用"分隔符切分整个数据" 25 let arr = (data.toString()).split(boundary); 26 27 //2.丢弃头尾两个数据 28 arr.shift(); 29 arr.pop(); 30 31 //3.丢弃掉每个数据头尾的"\r\n" 32 arr = arr.map(buffer => buffer.slice(2, buffer.length - 2)); 33 34 //4.每个数据在第一个"\r\n\r\n"处切成两半 35 arr.forEach(buffer => { 36 let n = buffer.indexOf('\r\n\r\n'); 37 38 let disposition = buffer.slice(0, n); 39 let content = buffer.slice(n + 4); 40 41 disposition = disposition.toString(); 42 43 if (disposition.indexOf('\r\n') === -1) { 44 //普通数据 45 //Content-Disposition: form-data; name="user" 46 content = content.toString(); 47 48 let name = disposition.split('; ')[1].split('=')[1]; 49 name = name.substring(1, name.length - 1); 50 51 post[name] = content; 52 } else { 53 //文件数据 54 /*Content-Disposition: form-data; name="f1"; filename="a.txt"\r\n 55 Content-Type: text/plain*/ 56 let [line1, line2] = disposition.split('\r\n'); 57 let [, name, filename] = line1.split('; '); 58 let type = line2.split(': ')[1]; 59 60 name = name.split('=')[1]; 61 name = name.substring(1, name.length - 1); 62 filename = filename.split('=')[1]; 63 filename = filename.substring(1, filename.length - 1); 64 65 let path = `upload/${uuid().replace(/\-/g, '')}`; 66 67 fs.writeFile(path, content, err => { 68 if (err) { 69 console.log('文件写入失败', err); 70 } else { 71 files[name] = {filename, path, type}; 72 console.log(files); 73 } 74 }); 75 } 76 }); 77 78 79 //5.完成 80 console.log(post); 81 } 82 } 83 84 85 res.end(); 86 }); 87 }); 88 server_post.listen(8080);
2.node文件处理机制
node文件上传从根本上来说就两种方法:
(1)最基础原始的方法
使用fs中的readFile和writeFile实现(读取完上传的文件后保存)
这样做有弊端:
- 只能等到所有数据都到达了才开始处理
- readFile先把所有数据全读到内存中,然后回调:
- 1.极其占用内存
- 2.资源利用极其不充分
(2)更好的方法
使用流,收到一部分数据就直接解析一部分,实例见后面的文件上传实例
3.用流实现文件上传
(1)流
三种流:
- 读取流 --> fs.createReadStream、req
- 写入流 --> fs.createWriteStream、res
- 读写流 --> 压缩、加密
(2)流实现读写文件
1 const fs = require('fs') 2 3 let rs = fs.createReadStream('1.txt') // 读取流 4 let ws = fs.createWriteStream('2.txt') // 写入流 5 6 rs.pipe(ws) 7 8 // 异常处理 9 rs.on('error', function (error) { 10 console.log('读取失败!') 11 }) 12 13 // 读取完成 及 写入完成 14 rs.on('end', function () { 15 console.log('读取完成!') 16 }) 17 18 ws.on('finish', function () { 19 console.log('写入完成!') 20 })
注:1.txt应该在同级目录下
(3)用流实现上传文件核心代码
1 /** 2 * [saveFileWithStream description] 3 * @param {String} filePath [文件路径] 4 * @param {Buffer} readData [Buffer 数据] 5 */ 6 static saveFile(filePath, fileData) { 7 return new Promise((resolve, reject) => { 8 // 块方式写入文件 9 const wstream = fs.createWriteStream(filePath); 10 11 wstream.on('open', () => { 12 const blockSize = 128; 13 const nbBlocks = Math.ceil(fileData.length / (blockSize)); 14 for (let i = 0; i < nbBlocks; i += 1) { 15 const currentBlock = fileData.slice( 16 blockSize * i, 17 Math.min(blockSize * (i + 1), fileData.length), 18 ); 19 wstream.write(currentBlock); 20 } 21 22 wstream.end(); 23 }); 24 wstream.on('error', (err) => { reject(err); }); 25 wstream.on('finish', () => { resolve(true); }); 26 }); 27 } 28 29 // 实际调用的时候,如下: 30 try { 31 await saveFileWithStream(filePath, fileData); // 这里的fileData是Buffer类型 32 } catch (err) { 33 console.log(err.stack); 34 }
too young too simple sometimes native!