1. 程式人生 > 其它 >使用typescript的裝飾器將express的router路由註冊的操作進行簡化處理

使用typescript的裝飾器將express的router路由註冊的操作進行簡化處理

最近想利用React + Node開發一個前後端分離的個人網站,其中服務端首選了express框架+TypeScript。簡單地瞭解了一下express是通過router來寫介面的,例如:

 1 import express, {Request, Response} from 'express'
 2 const app = express();
 3 const router = express.Router();
 4 ​
 5 // 隨便寫一個get請求的介面
 6 router.get('/api/login/loginByPhone', (req:Request, res:Response) => {
 7     //...
 8 })
 9 // 在將路由註冊到app中
10 app.use(router)

 

但如果有N多個路由,不同的路由肯定會按照需求的情況寫在不同的ts檔案中,例如:

先將router放在單獨的一箇中間件檔案中進行匯出,其他路由檔案引入的都是這一個router物件

// middleware/router.ts
​
const express = require('express')
// 建立路由物件
const router = express.Router()
​
// 匯出的路由物件專門用於建立不同的路由: router.post('/api/...')
export default router;

然後新建路由

//LoginRouter.ts
import router from './middleware/router'
router.get('/api/login/loginByPhone', (req:Request, res:Response) => {
    //...
})
router.post('/api/login/selectxxx', (req:Request, res:Response) => {
    //...
})
//...
// 最後匯出LoginRouter
export default LoginRouter;
​
//UserRouter.ts import router from './middleware/router' router.get('/api/user/selectUserInformation', (req:Request, res:Response) => { //... }) router.post('/api/user/saveInformation', (req:Request, res:Response) => { //... }) // 匯出UserRouter路由 export default UserRouter;

最後再統一引入到一個檔案中進行註冊:

import LoginRouter from './Router/LoginRouter'
import UserRouter from './Router/UserRouter'
import express from 'express'
const app = express()
// 註冊路由
app.use(LoginRouter);
app.use(UserRouter);

 

但是如果路由檔案特別多的話(假如有100個),那是不是得在一個檔案裡面引入100個路由檔案挨個進行註冊...

import xxx1 from './Router/xxx1'
import xxx2 from './Router/xxx2'
import xxx3 from './Router/xxx3'
//...
import xxx100 from './Router/xxx100'
​
app.use(xxx1)
app.use(xxx2)
app.use(xxx3)
//...
app.use(xxx100)

這也太麻煩了,那我豈不是成跪著敲程式碼的了麼? 所以必須想到一個偷懶的辦法!經過深思熟慮反覆斟酌,再憑藉著我對Java的一絲經驗,總算是有了一些思路,於是順著思路馬上開寫:

1)首先就是修改路由檔案中匯出的內容,不再匯出router物件,而是匯出一個類

// ./router/LoginRouter.ts
import router from './middle/router';
export default class LoginRouter{
    // 類物件中的屬性用來存放router路由
    selectInforByPhone = router.post('/api/login/loginByPassWord', (req: Request, resp: Response) => {
        //...
    })
​
    registerByPhone = router.post('/api/register/registerByPhone', (req: Request, resp: Response) => {
       //...
    })
}

 

2)第二步就是利用typescript寫一個@controller裝飾器放在路由類上,裝飾器的功能很簡單,就是獲取到類物件中的路由

// ./decorators/controller.ts
// 專門儲存類物件中路由的陣列
const routerArray:any = [];
​
function Controller(target: any): void {
    // 例項化當前類
    let obj = new target();
    // 然後將物件中的每一個router變數儲存到routerArray陣列中
    for (let route in obj) {
        routerArray.push(obj[route]);
    }
}
​
// Controller裝飾器, 儲存路由的陣列
export {Controller, routerArray};

現在的路由類就變成了這樣

import {Controller} from './decorators/controller'
import router from './middleware/router'
// 現在路由類不叫xxxRouter了,改叫xxxController
@Controller   // 加上裝飾器
export default class LoginController{
    // 類物件中的變數用來存放router路由
    selectInforByPhone = router.post('/api/login/loginByPassWord', (req: Request, resp: Response) => {
        //...
    })
​
    registerByPhone = router.post('/api/register/registerByPhone', (req: Request, resp: Response) => {
       //...
    })
}

 

接下來每次例項化這個類時,就會執行裝飾器函式,將類物件中的路由變數存入到routerArray陣列中。然後只需要對陣列進行遍歷,就可以註冊所有的路由。但是相比於最開始的引入 + 註冊,現在好像更麻煩了。。。不僅要引入路由類,還得例項化路由類,最後需要遍歷陣列才能進行註冊。因此接下來才是改變一切的關鍵:

const path = require('path');
const fs = require('fs');
​
export default class routerConfig {
​
    // 1.1 獲取controller資料夾的絕對路徑
    filePath = `${path.resolve()}\\src\\controller`;
​
    registerRouters(app: any) {
        return new Promise((resolve, reject) => {
            //1.2 讀取到controller資料夾下的檔名
            fs.readdir(this.filePath, (err: any, files: any) => {
                if(err){
                    reject('Controller file path error!');
                }
                files.forEach((file: any) => {
                    // 2. 引入當前檔案中的類物件
                    const nowClass = require(this.filePath + "\\" + file);     
                    if(typeof nowClass === 'object'){
                        // 3. 每次new當前類時,都會觸發controller裝飾器
                        new nowClass['default']();
                    }
                })
                // 4.1 獲取到存放路由的陣列
                const { routerArray } = require('../decorators/controller');
                //4.2 遍歷陣列,將路由註冊到app中
                routerArray.forEach((route: string) => {
                    app.use(route);
                })
                resolve(true);
            })
        })
    }
}

上面的類中的registerRouters()方法的作用就是:

  1. 獲取到src資料夾下的controller資料夾下的每一個檔名

  2. 通過const xx = require(path)的方式引入controller資料夾下的檔案中匯出的類物件

  3. 例項化xx類,在這個過程中,就會觸發@Controller裝飾器,將當前類中的路由存到同一個陣列中

  4. 迴圈完每一個路由檔案後,再將陣列中的路由遍歷一遍,註冊到app中。

我們最後在index.ts中執行這個操作

// index.ts
const express = require('express');
const app = express();
​
// 引入關鍵的那個類
import RouteConfig from './src/config/routerConfig'
​
let routerConfig = new RouteConfig();
// 呼叫routerConfig物件下的registerRouters()方法,並將app物件傳入,路由註冊完畢之後再對8888埠進行監聽
routerConfig.registerRouters(app).then(_ => {
    app.listen(8888, () => {
        console.log('note-station Server running at http://127.0.0.1');
    })
}).catch(err => {
    console.log(err);
});

 

測試:

先啟動服務端,通過nodemon執行以下index.ts檔案

然後隨便啟動一個React專案,隨便訪問一下已有的router,看一下注冊是否成功

即將被訪問的router

 

訪問/api/user/selectUser 

 

 

再看看服務端控制檯是否有前端傳來的資料

可以看到資料互動完全沒問題,因此路由的註冊肯定也是成功了的!!

總結:

通過@Controller裝飾器和RouterConfig物件的registerRouters()方法對controller資料夾下的類物件進行處理後,我們不需要再每次新建一個router後都要進行路由的引入和註冊操作。後續只需要在新建的xxxController類上加一個@Controller,那麼最後在啟動服務時就會將這個類中的路由自動註冊到app中。

 

對於以上方法,若有覺得方法有不足之處或是值得改進的地方,又或者有更好的方式,歡迎隨時指出。各位的批評與指點就是在下進步的良藥。