1. 程式人生 > 實用技巧 >如何用Eggjs從零開始開發一個專案(2)

如何用Eggjs從零開始開發一個專案(2)

在上一篇文章,我們已經使用Sequelize連線上了資料庫,並能進行簡單的資料庫操作,在此基礎上,我們試著來開發一個完整的專案。這篇文章我們從使用者的註冊、登入著手,試著開發使用者模組的相關的程式碼。

使用者註冊

1. 註冊邏輯

使用者註冊的邏輯很簡單:

  • 客戶端:使用者輸入輸入賬號,密碼等資訊進行使用者註冊;
  • 服務端:接收到客戶端提交的註冊資訊後,進行欄位的檢驗(是否必填、欄位長度等),欄位符合要求後,根據使用者註冊的賬號查詢資料庫,根據返回結果判斷該使用者是否是新使用者,如果是新使用者,將使用者資訊寫入到資料庫,完成註冊流程。

2. 使用者密碼處理

客戶端使用者提交資料後,服務端驗證通過進行資料庫寫入,但是其中使用者密碼是敏感資訊,為了服務安全考慮,不能直接將明文密碼寫入到資料庫,防止資料庫被攻擊,使用者密碼洩露。所以一般在儲存使用者密碼時,會先對使用者密碼進行加鹽加密處理,這樣哪怕資料庫儲存的密碼洩露,其他人也無法通過處理後的密碼進行登入。這裡使用雜湊演算法對密碼進行加密,因為雜湊的特性是不可逆,具體的細節可以參考

為什麼說 MD5 是不可逆的?。說明:雜湊可以被暴力破解,加鹽可以很大程度上增加破解難度。

廢話不多說,我們下面來寫程式碼。

首先,我們安裝一下bcryptjs,我們使用它對密碼進行加鹽加密和比對:

npm install bcryptjs --save

然後我們把這兩個方法寫到app/extend/helper.js

const bcrypt = require('bcryptjs');
module.exports = {
    encrypt(password) {
        const salt = bcrypt.genSaltSync(5);	//加鹽
        const hash = bcrypt.hashSync(password, salt);	//雜湊(同步呼叫)
        return hash;
    },
    compare(password, hash) {
        return bcrypt.compareSync(password, hash);	//比對
    }
};

這樣我們在專案裡就可以通過this.ctx.helop.encryptthis.ctx.helop.compare的方式去使用這些公用的方法了。

然後,在UserController中新增一個register方法:

async register() {
    const params = this.ctx.request.body;
    // 引數校驗
    if (!params.name || !params.password || !params.phone || !params.email) {
        this.ctx.body = {
            code: '500',
            msg: '引數不合法'
        };
    }
    // 查詢該使用者是否已經註冊
    const user = await this.ctx.model.User.findOne({ where: {
        name: params.name
    } });

    if (user) {
        this.ctx.body = {
            code: '500',
            msg: '該使用者已存在'
        };
    }
    // 插入資料庫
    const result = await this.ctx.model.User.create({
        ...params,
        password: this.ctx.helper.encrypt(params.password)
    });
    if (result) {
        this.ctx.body = {
            code: '200',
            msg: '註冊成功'
        };
    }
}

最後,新增路由:

// app/router.js
router.post('/register', controller.user.register);

3.功能測試

測試一下,用postman建立一個post請求到localhost:7001/register,傳入註冊使用者資訊:

{
	"name":"xiaoming",
	"password":"test1234",
	"phone":"13412341234",
	"email":"[email protected]"
}

服務端返回“註冊成功”的提示語,這時候我們去資料庫就能看到這條資料了,而且密碼是一坨看不懂的密文,這個時候我們用同樣的資料再次發起請求,服務端返回“該使用者已註冊”,說明我們的註冊功能已經完成了!

使用者登入

使用者登入的邏輯很簡單,就是一個客戶端傳入的使用者名稱密碼與伺服器儲存的相比較,匹配就登入成功,不然就是使用者名稱密碼錯誤。但是,我們還需要額外考慮一個問題,http請求是無狀態的,那我們怎麼去記住使用者的登入狀態和登入資訊呢,並且每次向服務端發起請求都帶上這些資訊呢?

常用的使用者認證方式有兩種,一種是通過Cookie實現,一種是Token的實現方式。關於使用者授權認證可以看看這篇文章:授權認證登入之 Cookie、Session、Token、JWT 詳解,Eggjs官網也有一個章節講了Cookie和Session相關的知識Cookie 與 Session,學習服務端,掌握這些知識還是很必要的。

在這裡,我們選擇JWT作為我們的解決方案。關於JWT,這裡提供兩篇文章作為參考,JSON Web Token 入門教程Introduction to JSON Web Tokens。OK,原理看完以後我們來寫程式碼。

首先我們安裝egg-jwt外掛:

npm install egg-jwt --save

引入外掛:

// app/config/plugin.js
jwt: {
    enable: true,
    package: "egg-jwt"
}

配置jwt secret:

// app/config/config.default.js
config.jwt = {
    secret: '12312456
};

OK,我們下面編寫程式碼的程式碼:

// app/controller/user.js
async login() {
    const params = this.ctx.request.body;
    if (!params.name || !params.password) {
        this.ctx.body = {
            code: '500',
            msg: '引數不合法'
        }
    }

    const user = await this.ctx.model.User.findOne({ where: {
        name: params.name
    } });

    if (!user) {
        this.ctx.body= {
            code: '500',
            msg: '使用者名稱密碼錯誤'
        }
    }

    //校驗密碼
    const checkPassword = this.ctx.helper.compare(
        params.password,
        user.password
    );

    if (checkPassword) {
        // 根據使用者名稱稱建立token,過期時間為2小時
        const token = this.app.jwt.sign({
            name: user.name
        }, this.app.config.jwt.secret, { expiresIn: '2h' });
        this.ctx.body = {
            code: '200',
            data: token
        }
    } else {
        this.ctx.body = {
            code: '500',
            msg: '使用者名稱密碼錯誤'
        }
    }
}

配置路由:

// app/router.js
router.post('/login', controller.user.login);

程式碼完畢,讓我們測試一下,建立一個post請求到localhost:7001/login,輸入使用者名稱密碼:

{
	"name":"xiaoming",
	"password":"test1234"
}

結果如下:

{
    "code": "200",
    "data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGlhb21pbmciLCJpYXQiOjE2MDg1NTkzMTYsImV4cCI6MTYwODU2NjUxNn0.SyXAhVvrwAql-4FzaZrlEs6dsEJ4wXbdjQsHv43CSOI"
}

成功了,這一大坨就是我們建立的token。

嗯……我們是拿到了token,但是怎麼用呢?還記得我們最開始寫的兩個介面嗎?我們現在給他配置jwt驗證,然後試試不登入能不能訪問。

// app/router.js
router.post('/createUser', app.jwt, controller.user.createUser);
router.get('/getUsers', app.jwt, controller.user.getUsers);

然後我們重新測試一下這兩個介面,服務端返回401 Unauthorized,這說明驗證生效了。那我們在請求的時候把剛才登入介面獲取到的token設定在AuthorizationBearer Token欄位中,再試一次,成功了!

如果你跟我走到了這一步,那說明你已經距離一個合格的服務端開發者更近了一步,但是這裡我們好像並沒有用到token裡面攜帶的資訊,下一篇我們將會解析token攜帶的資訊,知道每次都是哪個使用者在訪問我們的服務,而且我們將優化我們的程式碼,讓它看起來更簡潔、合理。