iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 17
0
Modern Web

Node JS-Back end見聞錄系列 第 17

Node.js-Backend見聞錄(16):實作-會員系統(五)-添加部分功能

Node.js-Backend見聞錄(16):實作-會員系統(五)-添加部分功能

前言

會員系統的部分,已經接續完成「會員註冊」、「會員登入」及「修改會員資料」的功能。接續,我們嘗試做做看若會員要「上傳大頭貼」的功能。

上傳大頭貼需求

  • 一個編輯會員資料的 API,除了可以更改「姓名」、「密碼」,還要能上傳「大頭照」。照片限制只能上傳png, jpg及jpeg格式、大小1MB、不同會員上傳相同檔名大頭照各自不受影響、同一個會員重複上傳不同大頭照只留最新的那張。

套件

  • formidable:用來剖析form-data格式的資料。
$ npm install formidable
  • fs:Node.js的內置套件,用來處理跟檔案相關的應用。

資料結構

.
├── 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端來可以接收圖片,且依據需求中「同一個會員重複上傳不同大頭照只留最新的那張。」這部分就能分三種做法:

  1. 「將圖片轉成Base64的格式放置在DB」

    • 待會我們會使用第1種方式來進行實作,其缺點是會佔用DB的儲存空間,若會員資料越多造成DB負荷也會越大,但這部分會做些處理來補救。比如限定上傳檔案的大小...等。
  2. 「在server端新增一個暫存資料夾並放置」

    • 會間接造成server空間的負擔,並不推薦這個做法。
  3. 「使用第三方服務來儲存圖片,並將圖片位置存放在DB」

    • 目前絕大多數的網站都使用這做法,但若要來實作這個做法還需要額外尋找第三方服務來使用。因此先不使用這方式來實作。

將圖片轉成Base64

這部分我們將會使用到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來進行測試:

  • API URL: localhost:3000/updateimg
  • HTTP method: PUT
  • headers
    • Content-Type: application/form-data
  • body
    • name: test
    • password: 1234
    • file: test.jpg

註記:file就是讀者自己上傳的圖片。

在圖中可以看到所得到的資料分別是我們在Postman上面輸入的key跟value,其中較為特別的是file的部分,透過formidable套件來幫我們把檔案剖析出來的資料為:

  • size: 檔案大小(kb),
  • path: 檔案在我們主機中的路徑。
  • name: 檔案名稱
  • type: 檔案類型
  • mtime: 上傳的時間(UTC+0)
    "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的檔案及特定副檔名。且需求中也有提到需要我們去定義這部分的規則:

  • 1MB以下的檔案
  • png, jpg, jpeg的副檔名

開啟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_controllerputUpdateImage 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

將資料存進去資料庫前,我們還得需要將圖片轉成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;
                    }
                })
            }
        })
    }
}

讓API URL更直覺點

修改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反推成圖片

當前端的夥伴接收到Base64後,其實是可以將它反推成圖片並顯示。使用html中的img即可:

<img alt="Embedded Image" src="data:image/jpg;base64, /9j/4AAQSkZJRgABAQ... />

Demo

補充

會員系統-完整的code

member_system

小結

我們終於將會員系統給完成了,不知道讀者在做完該練習後對Node.js的運行及使用方式有沒有多點了解。接續我們將探討如何讓資料庫來進行自動備份。


上一篇
Node.js-Backend見聞錄(15):實作-會員系統(四)-會員資料
下一篇
Node.js-Backend見聞錄(17):實作-會員系統(六)-延伸-關於資料庫備份
系列文
Node JS-Back end見聞錄31

尚未有邦友留言

立即登入留言