iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 14
1
Modern Web

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

Node.js-Backend見聞錄(13):實作-會員系統(二)-會員註冊(二)

Node.js-Backend見聞錄(13):實作-會員系統(二)-會員註冊(二)

前言

實作-會員系統(一)-會員註冊(一)中,我們已經讓專案跟資料庫做聯動。在該篇分享將接續把資料寫入資料庫中,完成「會員註冊」這部分的功能。

會員註冊需求

  • 不允許相同帳號(email)重複註冊
  • email 格式要檢查是否為 email 格式
  • 密碼要加密再存,不可逆。

資料結構

.
├── app.js
├── bin
│   └── www
├── config
│   └── development_config.js
├── controllers
│   └── modify_controller.js
├── models
│   ├── connection_db.js
│   └── register_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

開始實作

整頓MVC架構

接下來,我們先將集中在routes資料夾的member.js檔案,其程式碼拆分成MVC的架構。並賦予這個API的url為/register

var express = require('express');
var router = express.Router();

const MemberModifyMethod = require('../controllers/modify_controller');

memberModifyMethod = new MemberModifyMethod();

router.post('/register', memberModifyMethod.postRegister);

module.exports = router;

並使其對應到controllers資料夾的modify_controller.js檔案。

const toRegister = require('../models/register_model');

module.exports = class Member {
    postRegister(req, res, next) {
        // 獲取client端資料
        const memberData = {
            name: req.body.name,
            email: req.body.email,
            password: req.body.password,
            create_date: onTime()
        }
        // 將資料寫入資料庫
        toRegister(memberData).then(result => {
            // 若寫入成功則回傳
            res.json({
                status: "註冊成功。",
                result: result 
            })
        }, (err) => {
            // 若寫入失敗則回傳
            res.json({
                result: err
            })
        })
    }
}

//取得現在時間,並將格式轉成YYYY-MM-DD HH:MM:SS
const onTime = () => {
    const date = new Date();
    const mm = date.getMonth() + 1;
    const dd = date.getDate();
    const hh = date.getHours();
    const mi = date.getMinutes();
    const ss = date.getSeconds();

    return [date.getFullYear(), "-" +
        (mm > 9 ? '' : '0') + mm, "-" +
        (dd > 9 ? '' : '0') + dd, " " +
        (hh > 9 ? '' : '0') + hh, ":" +
        (mi > 9 ? '' : '0') + mi, ":" +
        (ss > 9 ? '' : '0') + ss
    ].join('');
}

寫入資料庫

為了讓client端的資料傳入資料庫,我們在models資料夾的register_model.js檔案中寫入:

const db = require('./connection_db');

module.exports = function register(memberData) {
    let result = {};
    return new Promise((resolve, reject) => {
        // 將資料寫入資料庫
        db.query('INSERT INTO member_info SET ?', memberData, function (err, rows) {
            // 若資料庫部分出現問題,則回傳給client端「伺服器錯誤,請稍後再試!」的結果。
            if (err) {
                console.log(err);
                result.status = "註冊失敗。"
                result.err = "伺服器錯誤,請稍後在試!"
                reject(result);
                return;
            }
            result.registerMember = memberData;
            resolve(result);
        })
    })
}

之後,我們使用Postman來測試看看:

接著進入到MySQL資料庫,使用select * from member_info;指令來顯示看看是否有將資料寫入資料庫中。

實作需求-不允許相同帳號(email)重複註冊

我們在「寫入資料庫」的步驟中,只將資料輸入後就結束,還未達成「不允許相同帳號(email)重複註冊」的需求。若要達成這個需求,代表我們要把資料寫入資料庫前,應該要先確定資料庫是否有該筆資料。等同於我們需要先將資料庫的資料撈出來比對一次,如果真的沒有該筆資料才進行寫入動作。否則,就會跳出已有重複的Email。的回應。

在這部分我們只要將models資料夾中的register_model檔案,改寫成:

const db = require('./connection_db');

module.exports = function register(memberData) {
    let result = {};
    return new Promise((resolve, reject) => {
        // 尋找是否有重複的email
        db.query('SELECT email FROM member_info WHERE email = ?', memberData.email, function (err, rows) {
            // 若資料庫部分出現問題,則回傳給client端「伺服器錯誤,請稍後再試!」的結果。
            if (err) {
                console.log(err);
                result.status = "註冊失敗。"
                result.err = "伺服器錯誤,請稍後在試!"
                reject(result);
                return;
            }
            // 如果有重複的email
            if (rows.length >= 1) {
                result.status = "註冊失敗。";
                result.err = "已有重複的Email。";
                reject(result);
            } else {
                // 將資料寫入資料庫
                db.query('INSERT INTO member_info SET ?', memberData, function (err, rows) {
                    // 若資料庫部分出現問題,則回傳給client端「伺服器錯誤,請稍後再試!」的結果。
                    if (err) {
                        console.log(err);
                        result.status = "註冊失敗。";
                        result.err = "伺服器錯誤,請稍後在試!"
                        reject(result);
                        return;
                    }
                    // 若寫入資料庫成功,則回傳給clinet端下:
                    result.status = "註冊成功。"
                    result.registerMember = memberData;
                    resolve(result);
                })
            }
        })
    })
}

完成後,我們再透過Postman來做測試。接著email欄位的值也保持為test@gmail.com,並按send按鈕送出。在response的回傳就能會看到:

{
    "result": {
        "status": "註冊失敗。",
        "err": "已有重複的Email。"
    }
}

實作需求-不允許相同帳號(email)重複註冊

這邊我們在service資料夾的member_check.js檔案,先來撰寫判斷email的規則。這邊我們將使用正規表達示來解決:

module.exports = class CheckCustomer {
    //判斷email格式
    checkEmail(email) {
        const re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        const result = re.test(email);
        return result;
    }
}

並在controllers資料夾的modify_controller.js中,加入這層的判斷:

const toRegister = require('../models/register_model');
const Check = require('../service/member_check');

check = new Check();

module.exports = class Member {
    postRegister(req, res, next) {
        // 獲取client端資料
        const memberData = {
            name: req.body.name,
            email: req.body.email,
            password: req.body.password,
            create_date: onTime()
        }

        const checkEmail = check.checkEmail(memberData.email);
        // 不符合email格式
        if (checkEmail === false) {
            res.json({
                result: {
                    status: "註冊失敗。",
                    err: "請輸入正確的Eamil格式。(如1234@email.com)"
                }
            })
        // 若符合email格式
        } else if (checkEmail === true) {
            // 將資料寫入資料庫
            toRegister(memberData).then(result => {
                // 若寫入成功則回傳
                res.json({
                    result: result
                })
            }, (err) => {
                // 若寫入失敗則回傳
                res.json({
                    err: err
                })
            })
        }
    }
}

接著,我們一樣使用Postman來測試,並將email欄位的值改成任何非正常mail格式的值。並按send按鈕送出。在response的回傳就能會看到:

{
    "result": {
        "status": "註冊失敗。",
        "err": "請輸入正確的Eamil格式。(如1234@email.com)"
    }
}

實作需求-密碼要加密再存,不可逆。

這邊我們會需要用到Node.js的內置模組crypto中的hash部分。

讀者可能會問「為什麼我們需要做到密碼加密呢?」。試想著,如果我們寫入資料庫的密碼沒有進行加密,等同於是明碼的狀態。假設公司負責資料庫的工程師心懷不軌,或是資料庫遭受攻擊,導致整個資料外洩,那不就有可能造成很嚴重的損失。所以,我們才需要將密碼做個加密的動作,且不可逆。至於,加密的方式有很多,而這邊先展示最簡單(安全性最低)的加密方式。

註記:不可逆表示密碼不會隨著我們加密的方式而反轉回來。

首先,我們到models資料夾的encryption.js檔案中寫入:

const crypto = require('crypto');

module.exports = function getRePassword(password) {
    //加密
    let hashPassword = crypto.createHash('sha1');
    hashPassword.update(password);
    const rePassword = hashPassword.digest('hex');
    //   console.log('rePassword: ' + rePassword);
    return rePassword;
}

並回到controllers資料夾的modify_controller.js檔案中加入加密部分的判斷:

const toRegister = require('../models/register_model');
const Check = require('../service/member_check');
const encryption = require('../models/encryption');

check = new Check();

module.exports = class Member {
    postRegister(req, res, next) {

        // 進行加密
        const password = encryption(req.body.password);

        // 獲取client端資料
        const memberData = {
            name: req.body.name,
            email: req.body.email,
            password: password,
            create_date: onTime()
        }

        const checkEmail = check.checkEmail(memberData.email);
        // 不符合email格式
        if (checkEmail === false) {
            res.json({
                status: "註冊失敗。",
                err: "請輸入正確的Eamil格式。(如1234@email.com)"
            })
        // 若符合email格式
        } else if (checkEmail === true) {
            // 將資料寫入資料庫
            toRegister(memberData).then(result => {
                // 若寫入成功則回傳
                res.json({
                    result: result
                })
            }, (err) => {
                // 若寫入失敗則回傳
                res.json({
                    err: err
                })
            })
        }
    }
}

//取得現在時間,並將格式轉成YYYY-MM-DD HH:MM:SS
const onTime = () => {
    const date = new Date();
    const mm = date.getMonth() + 1;
    const dd = date.getDate();
    const hh = date.getHours();
    const mi = date.getMinutes();
    const ss = date.getSeconds();

    return [date.getFullYear(), "-" +
        (mm > 9 ? '' : '0') + mm, "-" +
        (dd > 9 ? '' : '0') + dd, " " +
        (hh > 9 ? '' : '0') + hh, ":" +
        (mi > 9 ? '' : '0') + mi, ":" +
        (ss > 9 ? '' : '0') + ss
    ].join('');
}

這時,我們一樣再透過Postman來進行測試。並將email欄位的值改成另外個不會重複的email。按send按鈕送出後,在response的回傳就能會看到我們的password已經被加密了。

{
    "result": {
        "status": "註冊成功。",
        "registerMember": {
            "name": "test",
            "email": "test123@gmail.com",
            "password": "7110eda4d09e062aa5e4a390b0a572ac0d2c0220",
            "create_date": "2018-01-02 23:23:27"
        }
    }
}

小結

這在我們已經完成了「會員註冊」的功能了,接下來會往「會員登入」部分前進。


上一篇
Node.js-Backend見聞錄(12):實作-會員系統(一)-會員註冊(一)
下一篇
Node.js-Backend見聞錄(14):實作-會員系統(三)-會員登入
系列文
Node JS-Back end見聞錄31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
oliver
iT邦新手 5 級 ‧ 2018-01-02 20:01:00

加油加油/images/emoticon/emoticon08.gif

好的~~/images/emoticon/emoticon02.gif

0
小不釘
iT邦新手 2 級 ‧ 2019-05-20 10:08:29

好文推推
最近才踏入後端正需要這樣的一篇文章讓我知道有哪些事要處理

感謝推推 XD

0
fustarky
iT邦新手 5 級 ‧ 2021-04-21 14:11:04

不好意思,想請問中間一段

寫入資料庫
為了讓client端的資料傳入資料庫,我們在models資料夾的member_model.js檔案中寫入

這裡的資料夾是否改為 register_model.js ?

對,沒錯。這部分要改為 register_model.js 才對。

感謝 fustarky 幫忙校正。

我要留言

立即登入留言