會員系統的部分,已經接續完成「會員註冊」、「會員登入」及「修改會員資料」的功能。接續,我們嘗試做做看若會員要「上傳大頭貼」的功能。
form-data格式的資料。$ npm install formidable
.
├── app.js
├── bin
│   └── www
├── config
│   └── development_config.js
├── controllers
│   └── modify_controller.js
├── models
│   ├── connection_db.js
│   ├── encryption_model.js
│   ├── login_model.js
│   ├── register_model.js
│   ├── update_model.js
│   └── verification_model.js
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── member.js
│   └── users.js
├── sevice
│   └── member_check.js
├── views
    ├── error.ejs
    └── index.ejs
├── .env
└── .gitignore
根據需求,我們必須先嘗試讓server端來可以接收圖片,且依據需求中「同一個會員重複上傳不同大頭照只留最新的那張。」這部分就能分三種做法:
「將圖片轉成Base64的格式放置在DB」
「在server端新增一個暫存資料夾並放置」
「使用第三方服務來儲存圖片,並將圖片位置存放在DB」
這部分我們將會使用到formidable套件。首先,我們先開立另外支API URL給這部分的功能。開啟routes資料夾的member.js檔案,修改成:
var express = require('express');
var router = express.Router();
const MemberModifyMethod = require('../controllers/modify_controller');
memberModifyMethod = new MemberModifyMethod();
router.post('/register', memberModifyMethod.postRegister);
router.post('/login', memberModifyMethod.postLogin);
router.put('/update', memberModifyMethod.putUpdate);
router.put('/updateimg', memberModifyMethod.putUpdateImage);
module.exports = router;
接著,我們繼續修改controllers資料夾的modify_controller.js檔案,新增一個名為putUpdateImage的function:
putUpdateImage(req, res, next) {
    const form = new formidable.IncomingForm();
    form.parse(req, function (err, fields, files) {
        res.json({
            name: fields.name,
            password: fields.password,
            file: files.file
        })
    })
}
讀者可以先找個圖片,檔案大小越小越好。因為在這個階段我們需要透過這個圖片來進行上傳的動作。在找好圖片後,我們使用Postman來進行測試:
註記:
file就是讀者自己上傳的圖片。

在圖中可以看到所得到的資料分別是我們在Postman上面輸入的key跟value,其中較為特別的是file的部分,透過formidable套件來幫我們把檔案剖析出來的資料為:
    "file": {
        "size": 13428,
        "path": "/var/folders/q7/nxs9l4lx78bbc_6w5d8wyn5w0000gn/T/upload_5bbd398a624a1331c932b0a311f29789",
        "name": "test.jpg",
        "type": "image/jpeg",
        "mtime": "2018-01-05T11:51:51.275Z"
    }
由於在上個步驟發現可以透過formidable套件來取得檔案的大小及型態,等同於我們可以藉由它來限制只能傳多少kb的檔案及特定副檔名。且需求中也有提到需要我們去定義這部分的規則:
開啟service資料夾中的member_check.js檔案,新增兩個規則:
    //判斷檔案大小
    checkFileSize(fileSize) {
        var maxSize = 1 * 1024 * 1024; //1MB
        if (fileSize > maxSize) {
            return true;
        }
        return false;
    }
    //判斷型態是否符合jpg, jpeg, png
    checkFileType(fileType) {
        if (fileType === 'image/png' || fileType === 'image/jpg' || fileType === 'image/jpeg') {
            return true;
        }
        return false;
    }
之後,將這部分的規則加進去controllers資料夾中的modify_controller其putUpdateImage function中。
putUpdateImage(req, res, next) {
    const form = new formidable.IncomingForm();
    form.parse(req, async function (err, fields, files) {
        // 確認檔案大小是否小於1MB
        if (check.checkFileSize(files.file.size) === true) {
            res.json({
                result: {
                    status: "上傳檔案失敗。",
                    err: "請上傳小於1MB的檔案"
                }
            })
            return;
        }
        // 確認檔案型態是否為png, jpg, jpeg
        if (check.checkFileType(files.file.type) === true) {
            res.json({
                name: fields.name,
                password: fields.password,
                file: files.file
            })
        } else {
            res.json({
                result: {
                    status: "上傳檔案失敗。",
                    err: "請選擇正確的檔案格式。如:png, jpg, jpeg等。"
                }
            })
            return;
        }
    })
}
將資料存進去資料庫前,我們還得需要將圖片轉成Base64的格式。而這部分需要Node.js的內置套件fs來協助我們達成。先額外新增一個function在controllers資料夾中的modify_controller.js檔案下:
const fileToBase64 = (filePath) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'base64', function (err, data) {
            resolve(data);
        })
    })
}
資料庫部分的程式碼,我們已經在昨天完成了。接續我們將該model加進去controllers資料夾中的modify_controller.js。並加入會員登入部分的功能進去:
putUpdateImage(req, res, next) {
    const form = new formidable.IncomingForm();
    const token = req.headers['token'];
    //確定token是否有輸入
    if (check.checkNull(token) === true) {
        res.json({
            err: "請輸入token!"
        })
    } else if (check.checkNull(token) === false) {
        verify(token).then(tokenResult => {
            if (tokenResult === false) {
                res.json({
                    result: {
                        status: "token錯誤。",
                        err: "請重新登入。"
                    }
                })
            } else {
                form.parse(req, async function (err, fields, files) {
                    // 確認檔案大小是否小於1MB
                    if (check.checkFileSize(files.file.size) === true) {
                        res.json({
                            result: {
                                status: "上傳檔案失敗。",
                                err: "請上傳小於1MB的檔案"
                            }
                        })
                        return;
                    }
                    // 確認檔案型態是否為png, jpg, jpeg
                    if (check.checkFileType(files.file.type) === true) {
                        // 將圖片轉成base64編碼
                        const image = await fileToBase64(files.file.path);
                        const id = tokenResult;
                        // 進行加密
                        const password = encryption(fields.password);
                        const memberUpdateData = {
                            img: image,
                            name: fields.name,
                            password: password,
                            update_date: onTime()
                        }
                        updateAction(id, memberUpdateData).then(result => {
                            res.json({
                                result: result
                            })
                        }, (err) => {
                            res.json({
                                result: err
                            })
                        })
                    } else {
                        res.json({
                            result: {
                                status: "上傳檔案失敗。",
                                err: "請選擇正確的檔案格式。如:png, jpg, jpeg等。"
                            }
                        })
                        return;
                    }
                })
            }
        })
    }
}
修改routes資料夾的member.js檔案。
var express = require('express');
var router = express.Router();
const MemberModifyMethod = require('../controllers/modify_controller');
memberModifyMethod = new MemberModifyMethod();
// # Node.js-Backend見聞錄(16):實作-會員系統(五)-添加部分功能更動成:
// 註冊新會員
router.post('/member', memberModifyMethod.postRegister);
// 會員登入
router.post('/member/login', memberModifyMethod.postLogin);
// 更新會員資料
router.put('/member', memberModifyMethod.putUpdate);
// 更新會員資料(檔案上傳示範,可直接取代/member的PUT method)
router.put('/updateimage', memberModifyMethod.putUpdateImage);
module.exports = router;
當前端的夥伴接收到Base64後,其實是可以將它反推成圖片並顯示。使用html中的img即可:
<img alt="Embedded Image" src="data:image/jpg;base64, /9j/4AAQSkZJRgABAQ... />
會員系統-完整的code
我們終於將會員系統給完成了,不知道讀者在做完該練習後對Node.js的運行及使用方式有沒有多點了解。接續我們將探討如何讓資料庫來進行自動備份。