1. 程式人生 > >Nodejs學習筆記(4) 文件操作 fs 及 express 上傳

Nodejs學習筆記(4) 文件操作 fs 及 express 上傳

.cn 緩存 單元 填充 cep page imm idt mimetype

目錄

  • 參考資料
  • 1. fs 模塊
    • 1.1 讀取文件fs.readFile
    • 1.2 寫入文件fs.writeFile
    • 1.3 獲取文件信息fs.stat
    • 1.4 刪除文件fs.unlink
    • 1.5 清空指定文件夾 fs.unlink + fs.readdir
  • 2. 關於 HTTP 文件傳輸和 multer 控制文件上傳的幾個問題(寫在前面)
    • 2.1 文件選擇後(未提交前)放在哪裏?
    • 2.2 文件提交後的路徑是什麽?
    • 2.3 文件傳輸在HTTP協議中是如何進行的?
    • 2.4 multer([options])中有哪些鍵?分別有什麽用??

    • 2.5 multer.array()有什麽用??
    • 2.6 使用不同瀏覽器傳輸文件會有什麽不同效果??
  • 3. HTML 用於上傳文件的元素
    • 3.1 HTML < form > 標簽的 enctype 屬性
    • 3.2 HTML DOM FileUpload 對象
  • 4. express 文件上傳
    • 4.1 文件上傳的實例
    • 4.2 multer模塊是什麽?
    • 4.3 multer如何控制文件傳輸?
      • 4.3.1 控制編碼文件的位置
      • 4.3.2 給不同的文件響應規定字段名
      • 4.3.3 控制接收文件的類型

    • 4.3 設置本地存儲路徑(通過 req 對象的屬性)
    • 4.4 緩存管理


參考資料

Node.js 文件系統 | 菜鳥教程
HTML DOM FileUpload 對象 | W3school
HTML <form> 標簽 | W3school
HTML <input> 標簽 | W3school
HTML <input> 標簽的 accept 屬性
multer - npm
multer模塊的使用 +文件上傳+ 評論 | 維克多噗噗的博客


1. fs 模塊

?該模塊主要執行文件操作,操作的方法均有同步和異步版本,例如讀取文件內容的函數有異步的 fs.readFile() 和同步的 fs.readFileSync()。
?異步的方法函數最後一個參數為回調函數回調函數的第一個參數包含了錯誤信息(error)。其余參數根據不同的方法有所差異。
?比起同步,異步方法性能更高,速度更快,而且沒有阻塞。在此僅記錄我在 express 上傳文件操作時所用到的readFile方法、writeFile方法、stat方法和unlink方法,對其余方法僅作簡單描述,詳細使用方法和實例參照Node.js文件系統 | 菜鳥教程


1.1 讀取文件fs.readFile

fs.readFile(filename,[,options], callback(err, data));

回調函數的參數:

  • err - 錯誤信息;
  • data - buffer數據流對象,可用data.toString()轉換成字符串;
var fs = require(‘fs‘);

// 異步讀取
fs.readFile(‘./input.txt‘, function (err, data) {
    if (err) {
        return console.error(err);
    }
    console.log("異步讀取:" + data.toString());
});

1.2 寫入文件fs.writeFile

fs.writeFile(file, data[, options], callback(err))

?writeFile 直接打開文件默認是w模式,所以如果文件存在,該方法寫入的內容會覆蓋舊的文件內容。

?參數使用說明如下:

  • file - 文件名或文件描述符。
  • data - 要寫入文件的數據,可以是String(字符串)或Buffer(流)對象。
  • options - 該參數是一個對象,內容如下:
    • encoding - 編碼,默認值為utf8
    • mode - 模式(權限),默認值為0666(可讀、可寫);
    • flag - 文件打開行為,默認值為‘w‘
  • callback - 回調函數,回調函數只包含錯誤信息參數(err),在寫入失敗時返回。

?常見的打開文件的模式(mode)有以下幾種:

Flag描述
r以讀取模式打開文件。如果文件不存在拋出異常。
r+ 以讀寫模式打開文件。如果文件不存在拋出異常。
w以寫入模式打開文件,如果文件不存在則創建。
w+以讀寫模式打開文件,如果文件不存在則創建。

1.3 獲取文件信息fs.stat

fs.stat(path, callback(err, stats))

文件的狀態信息包含在回調函數的參數stats中,這是一個fs.Stats對象,其內容如下:

atime: Wed Jul 25 2018 21:11:59 GMT+0800 (GMT+08:00) {}
atimeMs: 1532524319921.0476
birthtime: Wed Jul 25 2018 21:11:59 GMT+0800 (GMT+08:00) {}
birthtimeMs: 1532524319921.0476
blksize: undefined
blocks: undefined
ctime: Wed Jul 25 2018 21:11:59 GMT+0800 (GMT+08:00) {}
ctimeMs: 1532524319922.0476
dev: 6533005
gid: 0
ino: 1407374884234476
mode: 33206
mtime: Wed Jul 25 2018 21:11:59 GMT+0800 (GMT+08:00) {}

?其中有四個時間值得我們關註:

  • atime - 訪問時間(access time);
  • birthtime - 創建時間;
  • ctime - 狀態修改時間(change time),顯示的是文件的權限、擁有者、所屬的組、鏈接數發生改變時的時間;
  • mtime - 修改時間(modify time),顯示的是文件內容被修改的最後時間。

?每一個時間都是一個JavaScript Date()對象的實例,因此有些方法是可以通用的,例如獲取日期、月份、年份:

stats.birthtime.getDate()
25
stats.birthtime.getMonth() // js的月份從0開始算
6
stats.birthtime.getFullYear()
2018

fs.unlink(path, callback(err))

?直接刪除path對應的文件,若文件不存在會通過err報錯。


?其他方法

方法 作用
fs.open(path, flags[, mode], callback(err, fd)) 打開文件
fs.read(fd, buffer, offset, length, position, callback) 讀取文件
fs.close(fd, callback) 關閉文件
fs.ftruncate(fd, len, callback) 截取文件
fs.mkdir(path[, mode], callback) 創建目錄
fs.readdir(path, callback) 讀取目錄
fs.rmdir(path, callback) 刪除目錄

fs.readdir(path, callback(err, files))

?files為 目錄下的文件數組列表。


2. 關於 HTTP 文件傳輸和 multer 控制文件上傳的幾個問題(寫在前面)

2.1 文件選擇後(未提交前)放在哪裏?

?哪都沒放,還在原先的磁盤上,只是根據選擇文件信息填充了HTML DOM FileUpload的屬性。


2.2 文件提交後的路徑是什麽?

?由服務器設定,在express中由multer({dest: ‘‘})指定。


2.3 文件傳輸在HTTP協議中是如何進行的?

?將文件編碼後存儲在請求體中,且一旦發送請求(包含請求體和請求頭),就向服務器指定接收文件的位置發送一個編碼文件(存放在multer({dest: ‘‘})指定的路徑中);

?服務器可以根據請求頭的信息,對編碼文件進行操作(解析、讀取等);

?若直接修改編碼文件的後綴名,可以直接獲得原始文件,例如我發送一個png圖片,在服務器收到了一個名稱為7d5931b2f95ce2cb93e647c6d64f5326的文件,將其後綴名修改為.png,打開,完美還原。


2.4 multer([options])中有哪些鍵?分別有什麽用??

  • dest:指定接收編碼文件的路徑;(用的最多)
  • fileFilter:控制接收的文件類型;(偶爾用用,在控制文件類型時用到)
  • limits:Limits of the uploaded data;(基本沒用)
  • preservePath:Keep the full path of files instead of just the base name;(基本沒用)

2.5 multer.array()有什麽用??

?array()的作用是規定接受的一系列文件共有的字段名(類似於將文件分類)。其可以使用app.use()命名為一個全局中間件,但這並不理想,因為在一個腳本文件中可能需要響應不同類型的文件上傳,有圖片、文檔、XML、JSON等。

?所以更理想的方式是在全局先創建一個multer實例:

var upload = multer({ dest: ‘./tmp/‘});

然後在對每個不同的POST請求響應中,將upload.array(‘‘)作為第二個參數寫入:

// 響應請求
app.post(‘/image_upload‘, upload.array(‘image‘), function (req, res) {
    // code...
})

?一般來說,一個app.post()只能響應一個表單元素的提交,因此對提交的不同類型的表單元素數據設置不同的字段名(fieldname),是最理想的選擇


2.6 使用不同瀏覽器傳輸文件會有什麽不同效果??

?沒有不同效果,都可以成功傳輸文件,並且都能將編碼文件存儲到指定文件夾中。只是請求頭的user-agent信息會有不同。


3. HTML 用於上傳文件的元素

3.1 HTML < form > 標簽的 enctype 屬性

HTML <form> 標簽 | W3school
HTML <input> 標簽 | W3school

?enctype 屬性規定在發送到服務器之前應該如何對表單數據進行編碼;
?默認地,表單數據會編碼為 application/x-www-form-urlencoded。就是說,在發送到服務器之前,所有字符都會進行編碼(空格轉換為 "+" 加號,特殊符號轉換為 ASCII HEX 值);
?當我們使用文件上傳功能時,enctype的值必須設置為multipart/form-data


語法

<form enctype="value">

屬性值

描述
application/x-www-form-urlencoded 在發送前編碼所有字符(默認)
multipart/form-data

不對字符編碼。

在使用包含文件上傳控件的表單時,必須使用該值。

text/plain 空格轉換為 "+" 加號,但不對特殊字符編碼。

3.2 HTML DOM FileUpload 對象

HTML DOM FileUpload 對象 | W3school

?在 HTML 文檔中 標簽每出現一次,一個 FileUpload 對象就會被創建。我們可以通過使用document.getElementById()來訪問 FileUpload 對象:
.html:

<form id="uplodaFile" action="/file_upload" method="POST" enctype="multipart/form-data">
    <input type="file" name="image" size="50"><br>
    <input type="submit" value="上傳圖片">
</form>

.js(獲取 FileUpload 對象):

var element = document.getElementById("uplodaFile");

FileUpload 對象的屬性:

屬性 描述
accept

設置或返回指示文件傳輸的 MIME 類型的列表(逗號分隔)。

Content-Type

accessKey 設置或返回訪問 FileUpload 對象的快捷鍵。
alt 設置或返回不支持 <input type="file"> 時顯示的替代文字。
defaultValue 設置或返回 FileUpload 對象的初始值。
disabled 設置或返回是否禁用 FileUpload 對象。
form 返回對包含 FileUpload 對象的表單的引用。
id 設置或返回 FileUpload 對象的 id。
name 設置或返回 FileUpload 對象的名稱。
tabIndex 設置或返回定義 FileUpload 對象的 tab 鍵控制次序的索引號。
type 返回表單元素的類型。對於 FileUpload ,則是 "file" 。
value 返回由用戶輸入設置的文本後,FileUpload 對象的文件名。

4. express 文件上傳

4.1 文件上傳的實例

upload.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Upload Page</title>
</head>
<body>
    <h2>UPLOAD IMAGE FILE</h2><br>
    <form id="uploadImg" action="/image_upload" method="POST" enctype="multipart/form-data" >
        <input type="file" name="image" accept="image/*"><br>
        <input type="submit" value="上傳圖片">
    </form>
    <script type="text/javascript">
        var element = document.getElementById("uploadImg");
    </script>
</body>
</html>

upload.js:

/**
 * 上傳圖片文件測試腳本
 */
// 依賴
var express = require(‘express‘);
var app = express();
var fs = require(‘fs‘);

var bodyParser = require(‘body-parser‘);
var multer = require(‘multer‘);

// 中間件
app.use(express.static(‘./uploads/‘));
app.use(bodyParser.urlencoded({ extended: false }));

// 控制允許接收的文件類型(4.3.3)
var fileFilter = function fileFilter (req, file, cd) {
    if (file.mimetype == "image/png" || file.mimetype == "image/jpeg"){
        cd(null, true);
    }else{
        req.error = "不允許上傳" + file.mimetype + "類型的文件!";
        cd(null, false);
    }
}

// 設置路徑和文件過濾器(4.3)
var upload = multer({ dest: ‘./uploadFiles/tmp/‘, fileFilter: fileFilter});

// 首頁
app.get(‘/‘, function (req, res) {
    res.sendFile(__dirname + "/" + "upload.html");
})

// 響應請求
app.post(‘/image_upload‘, upload.array(‘image‘), function (req, res) {
    // 文件信息
    if(!req.files[0]){
        console.log(req.error);
        res.send(req.error);
        return;
    }else{
        console.log(req.files[0]);
    }

    // 存儲並響應客戶端
    var des_file = __dirname + "/uploadFiles/" + req.files[0].fieldname + "/" + req.files[0].originalname;
    fs.readFile(req.files[0].path, function (err, data) {
        fs.writeFile(des_file, data, function (err) {
            if(err){
                console.log(err);
            }else{
                var response = {
                    message: ‘File uploaded successfully‘,
                    filename: req.files[0].originalname
                };
                console.log(response);
                res.json(response);
            }
        });
    });
})

// 監聽
var server = app.listen(3333, function () {
    var host = server.address().address;
    var port = server.address().port;

    console.log("應用實例,訪問地址為:http://%s:%s", host, port);
})

4.2 multer模塊是什麽?

multer - npm

?Multer是一個專門用於處理multipart/form-data編碼類型數據流的node.js中間件,在進行文件上傳操作時常用到。

?需要註意的是,當表單元素的編碼類型不是multipart/form-data時,Multer不會對請求進行解析。

?我們一般通過如下方法使用multer模塊:

var multer = require(‘multer‘);
var upload = multer({ dest: ‘./tmp/‘});

// 響應請求
app.post(‘/image_upload‘, upload.array(‘image‘), function (req, res) {
    // code...
})

4.3 multer如何控制文件傳輸?

multer - npm


4.3.1 控制編碼文件的位置

multer({ dest: ‘./uploadFiles/tmp/‘ });

4.3.2 給不同的文件響應規定字段名

multer.array(‘image‘);
multer.array(‘myType‘);

4.3.3 控制接收文件的類型

IMME文件類型:Content-Type


前端控制

HTML <input> 標簽的 accept 屬性

?為表單元素<input type="file">設置屬性accept,限定文件選擇對話框中允許選擇的文件類型(多種類型用逗號分隔):

<input type="file" name="image" accept="image/png, application/pdf"><br>

服務端控制

multer模塊的使用 +文件上傳+ 評論 | 維克多噗噗的博客
multer(fileFilter) - npm

?在服務端控制接收文件的類型,主要依靠multer([options])中的fileFilter鍵(multer的鍵值)。fileFilter鍵的使用方法是:創建一個函數fileFilter(req, file, cd){},來對請求進行解析,進而通過參數cd決定是否接收發送的文件。

?錯誤的使用:

// 不能直接規定fileFilter的鍵值
var upload = multer({ dest: ‘.upload‘, fileFilter: ‘image/png, image/jpeg‘});

?正確的使用:

// 控制允許接收的文件類型
function fileFilter (req, file, cd) {
    if (file.mimetype == "image/png" || file.mimetype == "image/jpeg"){
        cd(null, true); // 同意接收文件
    }else{
        req.error = "不允許上傳" + file.mimetype + "類型的文件!";
        cd(null, false); // 拒絕接收文件
    }
}

var upload = multer({ dest: ‘./uploadFiles/tmp/‘, fileFilter: fileFilter});
  • file包含以下字段
    • encoding:"7bit";
    • fieldname:"image";(字段名:由upload.array(‘image‘)定義的)
    • mimetype:"image/jpeg";
    • originalname:"540ff7cddc29e.jpg";
  • cd的用法
    • cd(null, true) - To accept the file pass true
    • cd(null, false) - To reject this file pass false

4.3 設置本地存儲路徑(通過 req 對象的屬性)

?在文件目錄下創建uploadFiles文件夾,同時根據upload.array()中規定的字段名創建文件夾(一定不能創建出錯,不然會提示無法打開相應的文件夾);

?例如upload.array(‘image‘),創建uploadFiles/image,使用下面方法可以將文件存入到image文件夾中:

var des_file = __dirname + "/uploadFiles/" + req.files[0].fieldname + "/" + req.files[0].originalname;

fs.readFile(req.files[0].path, function (err, data) {
    fs.writeFile(des_file, data, function (err) {
        //callback...
    });
});

4.4 緩存管理

?在接收文件時,服務器將受到大量的編碼文件,當完成文件接收後,這些編碼的文件仍然存放在服務器主機磁盤上。這些文件的存在有利於數據的恢復,但當其數量達到一定規模時,會對磁盤空間造成較大的壓力,因此,應該采取合適的手段進行編碼文件的數量控制,來保證磁盤空間的可用性。

// 刪除傳輸文件時的臨時文件
var fs = require(‘fs‘);

var desDir = "D:/nodejs/my-sql/uploadFiles/tmp/";

// 先獲取該文件夾下所有文件名
fs.readdir(desDir, function (err, files) {
    if (err) {
        return console.error(err);
    }
    for (var i=0; i<files.length; i++) {
        // 使用 unlink 刪除
        fs.unlink(desDir + files[i], function (err){
            if (err) {
                return console.error(err);
            }
            console.log("Successfully delete file " + files[i].toString()); // 註意應轉換成字符串
        })
    }
})

Nodejs學習筆記(4) 文件操作 fs 及 express 上傳