內容:
1.文件上傳基礎
2.node文件處理機制
3.用流實現文件上傳
1.文件上傳基礎
前端代碼:
<form action="localhost:8080/" method="post" enctype="multipart/form-data">
<input type="text" name="user">
<input type="password" name="pass">
<input type="file" name="f1">
<input type="submit" value="上傳文件">
</form>
注意:
上傳文件時表單中的enctype="multipart/form-data"必須要寫
input(file)必須要有name
後端代碼:
const http = require('http');
const uuid = require('uuid/v4');
const fs = require('fs')
let server_post = http.createServer((req, res) => {
let arr = [];
req.on('data', data => {
arr.push(data);
});
req.on('end', () => {
let data = Buffer.concat(arr);
// console.log(data)
//data
//解析二進制文件上傳數據
let post = {};
let files = {};
if (req.headers['content-type']) {
let str = req.headers['content-type'].split('; ')[1];
if (str) {
let boundary = '--' + str.split('=')[1];
//1.用"分隔符切分整個數據"
let arr = (data.toString()).split(boundary);
//2.丟棄頭尾兩個數據
arr.shift();
arr.pop();
//3.丟棄掉每個數據頭尾的"\r\n"
arr = arr.map(buffer => buffer.slice(2, buffer.length - 2));
//4.每個數據在第一個"\r\n\r\n"處切成兩半
arr.forEach(buffer => {
let n = buffer.indexOf('\r\n\r\n');
let disposition = buffer.slice(0, n);
let content = buffer.slice(n + 4);
disposition = disposition.toString();
if (disposition.indexOf('\r\n') === -1) {
//普通數據
//Content-Disposition: form-data; name="user"
content = content.toString();
let name = disposition.split('; ')[1].split('=')[1];
name = name.substring(1, name.length - 1);
post[name] = content;
} else {
//文件數據
/*Content-Disposition: form-data; name="f1"; filename="a.txt"\r\n
Content-Type: text/plain*/
let [line1, line2] = disposition.split('\r\n');
let [, name, filename] = line1.split('; ');
let type = line2.split(': ')[1];
name = name.split('=')[1];
name = name.substring(1, name.length - 1);
filename = filename.split('=')[1];
filename = filename.substring(1, filename.length - 1);
let path = `upload/${uuid().replace(/\-/g, '')}`;
fs.writeFile(path, content, err => {
if (err) {
console.log('文件寫入失敗', err);
} else {
files[name] = {filename, path, type};
console.log(files);
}
});
}
});
//5.完成
console.log(post);
}
}
res.end();
});
});
server_post.listen(8080);
2.node文件處理機制
node文件上傳從根本上來說就兩種方法:
(1)最基礎原始的方法
使用fs中的readFile和writeFile實現(讀取完上傳的文件後保存)
這樣做有弊端:
只能等到所有數據都到達了纔開始處理
readFile先把所有數據全讀到內存中,然後回調:
1.極其佔用內存
2.資源利用極其不充分
(2)更好的方法
使用流,收到一部分數據就直接解析一部分,實例見後面的文件上傳實例
3.用流實現文件上傳
(1)流
三種流:
讀取流 --> fs.createReadStream、req
寫入流 --> fs.createWriteStream、res
讀寫流 --> 壓縮、加密
(2)流實現讀寫文件
const fs = require('fs')
let rs = fs.createReadStream('1.txt') // 讀取流
let ws = fs.createWriteStream('2.txt') // 寫入流
rs.pipe(ws)
// 異常處理
rs.on('error', function (error) {
console.log('讀取失敗!')
})
// 讀取完成 及 寫入完成
rs.on('end', function () {
console.log('讀取完成!')
})
ws.on('finish', function () {
console.log('寫入完成!')
})
注:1.txt應該在同級目錄下
(3)用流實現上傳文件核心代碼
/**
* [saveFileWithStream description]
* @param {String} filePath [文件路徑]
* @param {Buffer} readData [Buffer 數據]
*/
static saveFile(filePath, fileData) {
return new Promise((resolve, reject) => {
// 塊方式寫入文件
const wstream = fs.createWriteStream(filePath);
wstream.on('open', () => {
const blockSize = 128;
const nbBlocks = Math.ceil(fileData.length / (blockSize));
for (let i = 0; i < nbBlocks; i += 1) {
const currentBlock = fileData.slice(
blockSize * i,
Math.min(blockSize * (i + 1), fileData.length),
);
wstream.write(currentBlock);
}
wstream.end();
});
wstream.on('error', (err) => { reject(err); });
wstream.on('finish', () => { resolve(true); });
});
}
// 實際調用的時候,如下:
try {
await saveFileWithStream(filePath, fileData); // 這裏的fileData是Buffer類型
} catch (err) {
console.log(err.stack);
}