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

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

上一篇中我們編寫了使用者註冊登入、登入的程式碼,學習瞭如何進行使用者的認證(JWT),如何安全地儲存用的密碼(hash)。這一篇我們有以下2個任務:

  1. 獲取token中的資料;
  2. 通過model來同步資料庫。

獲取token中的資料

使用者登入的時候我們已經使用者的一些基本資訊加密儲存到token中,通過路由配置我們能簡單的去控制哪些介面需要登入,哪些介面不需要登入,但是如果再細化到使用者或者使用者角色,只是從路由層面就難以控制了,我們需要拿到使用者資訊,並將使用者資訊儲存在session中,方便我們隨時取用。那要怎麼做呢?這裡我們需要編寫一箇中間件來實現這個功能。程式碼如下:

// app/middleware/eggJwt
module.exports = options => {
    return async function jwt(ctx, next) {
        const token = ctx.request.header.authorization;
        if (token) {
            // 這裡有個坑,前端一般傳過來是Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoieGl...
            // 但是解碼時不需要Bearer,所以需要手動處理一下
            const tokenStr = token.split(' ')[1];
            try {
                // 解碼token並存儲到session中
                const decode = ctx.app.jwt.verify(tokenStr, options.secret);
                ctx.session.user = decode;
                await next();
            } catch (error) {
                ctx.status = 500;
                ctx.body = {
                    message: error.message
                };
                return;
            }
        } else {
            ctx.status = 401;
            ctx.body = {
                message: '使用者未登入'
            };
            return;
        }
    };
};

這裡說明一下eggjs的外掛機制,如果你在config.default.js中配置:

config.middleware = ['eggJwt'];

那麼,這個外掛將作用於所有的路由,除非有特殊配置,但是這裡我們不需要,所以不加這一行程式碼。我們按需配置:

// app/router.js
module.exports = app => {
    const { router, controller } = app;
    const jwt = app.middleware.eggJwt(app.config.jwt);
    router.get('/', controller.home.index);
    router.post('/createUser', jwt, controller.user.createUser);
    router.get('/getUsers', jwt, controller.user.getUsers);
    router.post('/register', controller.user.register);
    router.post('/login', controller.user.login);
};

我們手動引入後按需配置即可。

這個時候我們去訪問配置了jwt的介面,就會執行外掛,解析token,並將解析結果儲存到session裡。我們修改一下user controller的程式碼試一下:

async getUsers() {
    // 列印session
    console.log(this.ctx.session.user, '===========');
    const users = await this.ctx.model.User.findAll();
    this.ctx.body = {
        code: 200,
        data: users
    };
}

配置好token後請求localhost:7001/getUsers,資料能正常返回,同時控制檯打印出了:

{ name: 'xiaoming', iat: 1609055716, exp: 1609062916 } ===========

可以看到這時我們就拿到了token裡儲存的資料。

通過model來同步資料庫

按照我們現在的程式碼,如果我們需要調整model中的欄位,我們需要修改model檔案,然後再寫指令碼同步修改資料庫中的欄位,這樣太麻煩了。幸好,Sequelize提供了一個方法讓我們可以根據model去同步資料庫裡的欄位:

  • User.sync() - 如果表不存在,則建立該表(如果已經存在,則不執行任何操作)
  • User.sync({ force: true }) - 將建立表,如果表已經存在,則將其首先刪除
  • User.sync({ alter: true }) - 這將檢查資料庫中表的當前狀態(它具有哪些列,它們的資料型別等),然後在表中進行必要的更改以使其與模型匹配.

注意我們這裡只能用User.sync({ alter: true }),那麼要在什麼時候去執行呢?我們想在每次服務啟動時去同步資料庫欄位,同時eggjs也提供了生命週期供我們使用:

// app.js
class AppBootHook {
    constructor(app) {
        this.app = app;
    }

    configWillLoad() {
    // 此時 config 檔案已經被讀取併合並,但是還並未生效
    // 這是應用層修改配置的最後時機
    // 注意:此函式只支援同步呼叫
    }

    async didLoad() {
    // 所有的配置已經載入完畢
    // 可以用來載入應用自定義的檔案,啟動自定義的服務

    }

    async willReady() {
    // 所有的外掛都已啟動完畢,但是應用整體還未 ready
    // 可以做一些資料初始化等操作,這些操作成功才會啟動應用
		// 根據model同步資料庫
        await this.app.model.sync({ alter: true });
    }

    async didReady() {
    // 應用已經啟動完畢
    }

    async serverDidReady() {
    // http / https server 已啟動,開始接受外部請求
    // 此時可以從 app.server 拿到 server 的例項
    }
}

module.exports = AppBootHook;

這時,我們在user model中新增一個欄位:nickname: STRING(10),然後重啟一下服務,開啟資料庫看看user表,果然多了一個nickname的列。這個功能極大地方便了我們去同步model與資料庫欄位,但是操作的時候也要小心,不要誤刪欄位。

這一篇內容不多,但是都是很實用的功能,下一篇我們將通過Sequelize來實現表的關聯查詢。