利用OOP+装饰器重写express服务

242 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情

面向过程编程的express接口

// router.js
import { Router } from 'express'
const router = Router()

router.get('/', (req, res, next) => {})
router.get('/logout', (req, res, next) => {})

export default router

// app.js
import express from 'express'

const app = express()
app.use(router)

app.listen('7001', () => {
  console.log('listen at 7001')
})

这就是面向过程编程的写法,如果你用过egg.js框架,它是这么写的:

export default class UserController extends Controller {
    async login() {}
}

这种写法就是面向对象OOP的写法。现在我们有了typescript,其实typescript的优势不仅仅是类型提示,它的更大的威力在于它完全是一门面向对象编程的语言,所以使用typescript就需要利用它的优势,把面向过程编程改写为面向对象编程。

创建控制器和装饰器

import 'reflect-metadata'

// 在类的装饰器上面获取类方法的元数据
function controller(target: any) {
  for (let key in target.prototype) {
    console.log('key', Reflect.getMetadata('path', target.prototype, key))
  }
}

// 通过方法装饰器给类的方法设置元数据,即path
function get(path: string) {
  return function (target: any, key: string) {
    Reflect.defineMetadata('path', path, target, key)
  }
}

@controller
class LoginController {
  constructor() {}
  @get('/login')
  login() {
    ...
  }

  @get('/')
  home(req: Request, res: Response) {
    ...
  }
}

export default LoginController
  • 我们对类的方法使用装饰器,装饰器的作用是利用reflect-metadata来给方法添加元数据path,即路由。
  • 在类的装饰器controller上,获取方法的元数据path

通过装饰器实现项目路由功能

1. 存储元数据path和method

function getRequestDecorator(type: string) {
  return function (path: string) {
    return function (target: any, key: string) {
      Reflect.defineMetadata('path', path, target, key)
      Reflect.defineMetadata('method', type, target, key)
    }
  }
}

export const get = getRequestDecorator(Method.get)
export const post = getRequestDecorator(Method.post)

2. 读取元数据并生成路由

import { Router } from 'express'
export const router = Router()

export function controller(target: any) {
  for (let key in target.prototype) {
    const path = Reflect.getMetadata('path', target.prototype, key)
    const method: Method = Reflect.getMetadata('method', target.prototype, key)
    const handle = target.prototype[key]
    if (path && method && handle) {
      // 生成路由
      router[method](path, handle)
    }
  }
}

3. 执行装饰器

现在装饰器是设置在类上,想让装饰器执行就必须要执行一次,也就是在入口文件引入对应的类即可:

// 执行一遍LoginController文件,就当详单于执行了装饰器,装饰器里面又生成了路由
import './controller/LoginController'

import { router } from './controller/decorator'
app.use(router)

中间件装饰器

现在我们要利用装饰器来绑定中间件:

function use(middleware: RequestHandler) {
    return function (target: any, key: string) {
        const originMiddlewares = Reflect.getMetadata('middlewares', target, key) || []      
        originMiddlewares.push(middleware)
        // 可以绑定多个中间件
        Reflect.defineMetadata('middlewares', originMiddlewares, target, key)
    }
}

使用中间件:

@controller
export class CrowllerController {
    @get('/getData')
    @use(checkLogin)
    @use(test)
    getData(req: BodyRequest, res: Response): void {  
        const analyzer = DellAnalyzer.getInstance()
        new Crowller(url, analyzer)
        res.json(getResponseData<responseResult.getData>(true))
    }
}