midwayjs / midway

🍔 A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 🌈
https://www.midwayjs.org/
MIT License
7.34k stars 573 forks source link

希望 extends继承时可以继承装饰器 #2364

Open yuntian001 opened 1 year ago

yuntian001 commented 1 year ago

比如

import { Controller } from '@midwayjs/decorator';
@Controller('/api')
export class APIController {

}
import {  Get, Query } from '@midwayjs/decorator';
import { APIController } from './api.controller';
export class UserController extends APIController {
  @Get('/user')
  async getUser(@Query('uid') uid) {
    return { success: true, message: 'OK', data: {aa:1} };
  }
}

这时候路由/api/user并不存在。 经测试在nestjs中这样写是可以访问/api/user的。

waitingsong commented 1 year ago

基类上不建议添加装饰器

yuntian001 commented 1 year ago

基类上不建议添加装饰器

我主要想实现分模块, 比如v1、v2两个版本的api,只要继承不同的基类,在基类上写上前缀和定义好模块的公共中间件, 就可以作用到模块下的所有控制器。

V1模块

import { Controller } from '@midwayjs/decorator';
import { ReportMiddlewareV1 } from '../middleware/report.v1.middlweare';
@Controller('/v1', { middleware: [ ReportMiddlewareV1 ] })
export class APIController {
 }
import {  Get, Query } from '@midwayjs/decorator';
import { APIController } from './api.controller';
import { ReportMiddlewareV1User } from '../middleware/report.v1.uer.middlweare';
@Controller('/user',{middleware: [ ReportMiddlewareV1User ]});
export class UserController extends APIController {
  @Get('/user')
  async getUser(@Query('uid') uid) {
    return { success: true, message: 'OK', data: {aa:1} };
  }
}

期望的访问地址为:/v1/user,期望应用的中间为["ReportMiddlewareV1","ReportMiddlewareV1User"]

V2模块

import { Controller } from '@midwayjs/decorator';
import { ReportMiddlewareV2 } from '../middleware/report.v2.middlweare';
@Controller('/v2', { middleware: [ ReportMiddlewareV2 ] })
export class APIController {
 }
import {  Get, Query } from '@midwayjs/decorator';
import { APIController } from './api.controller';
import { ReportMiddlewareV2User } from '../middleware/report.v2.user.middlweare';

@Controller('/user',{middleware: [ ReportMiddlewareV2User ]});
export class UserController extends APIController {
  @Get('/user')
  async getUser(@Query('uid') uid) {
    return { success: true, message: 'OK', data: {aa:1} };
  }
}

期望的访问地址为:/v2/user,期望应用的中间为["ReportMiddlewareV2","ReportMiddlewareV2User"]

思路

我看官方的 packages\core\src\service\webRouterService.ts下的路由注册部分

  protected analyzeController() {
    const controllerModules = listModule(CONTROLLER_KEY);

    for (const module of controllerModules) {
      const controllerOption: ControllerOption = getClassMetadata(
        CONTROLLER_KEY,
        module
      );
      this.addController(
        module,
        controllerOption,
        this.options.includeFunctionRouter
      );
    }
  }

改为获取到controllerOption后递归拿下原型的controllerOption,然后加个@Controller入参控制下是否合并祖先的属性,进行controllerOption合并后再注册,应该可以实现。

不知道官方有没有更好的实现方式和建议

waitingsong commented 1 year ago

可以试试把不同版本的中间件合并为一个入口,通过 ctx.path 来调用不同版本(中间件)逻辑

yuntian001 commented 1 year ago

可以试试把不同版本的中间件合并为一个入口,通过 ctx.path 来调用不同版本(中间件)逻辑

那样只解决了中间件的问题,前缀问题依然没有解决,而且每次请求都会进行判断,不像注册时只会执行一次。我还在自己封个装饰器,动态注册路由吧。

czy88840616 commented 1 year ago
import {
  Provide,
  Get,
  Configuration,
  App,
  Inject,
  Middleware,
  Init,
} from '@midwayjs/decorator';
import * as koa from '@midwayjs/koa';
import { join } from 'path';
import { MidwayWebRouterService } from '@midwayjs/core';

@Provide()
export class HomeController {
  @Inject()
  ctx;

  @Get('/')
  async home(): Promise<string> {
    return 'Hello Midwayjs!' + this.ctx.user;
  }
}

@Middleware()
export class CustomMiddleware {
  resolve() {
    return async (ctx, next) => {
      ctx.user = 'v1';
      await next();
    };
  }
}

@Middleware()
export class CustomMiddleware2 {
  resolve() {
    return async (ctx, next) => {
      ctx.user = 'v2';
      await next();
    };
  }
}

@Configuration({
  imports: [koa],
  importConfigs: [join(__dirname, './config')],
})
export class ContainerLifeCycle {
  @App()
  app: koa.Application;

  @Inject()
  webRouterService: MidwayWebRouterService;

  @Init()
  async init() {
    this.webRouterService.addController(HomeController, {
      prefix: '/v1',
      routerOptions: {
        middleware: [CustomMiddleware],
      },
    });
    this.webRouterService.addController(HomeController, {
      prefix: '/v2',
      routerOptions: {
        middleware: [CustomMiddleware2],
      },
    });
  }
}
yuntian001 commented 1 year ago
//packages\core\src\service\webRouterService.ts 
protected mergeControllerOption(controllerOption: ControllerOption, controllerClz: Object) {
    if(controllerOption.mergeAncestor){
      const prototype = Object.getPrototypeOf(controllerClz);
      const parentOption: ControllerOption = getClassMetadata(
        CONTROLLER_KEY,
        prototype
      )
      if(parentOption){
        controllerOption.prefix = (controllerOption.prefix+parentOption.prefix).replace(/\/\//g,'/');
        controllerOption.routerOptions = merge(controllerOption.routerOptions,parentOption.routerOptions);
        return this.mergeControllerOption(controllerOption,prototype)
      }
    }
    return controllerOption;
  }

  protected analyzeController() {
    const controllerModules = listModule(CONTROLLER_KEY);

    for (const module of controllerModules) {
      const controllerOption: ControllerOption = this.mergeControllerOption(getClassMetadata(
        CONTROLLER_KEY,
        module
      ),module);
      this.addController(
        module,
        controllerOption,
        this.options.includeFunctionRouter
      );
    }
  }

@czy88840616 这种方式是否合适,如果感觉合适我可以完善下提个PR

czy88840616 commented 1 year ago

暂时不建议动原型链,原型链会有不少悖论和延展的问题,比如重名的装饰器是组合还是继承,配置如何处理,swagger 扩展等等。

yuntian001 commented 1 year ago

暂时不建议动原型链,原型链会有不少悖论和延展的问题,比如重名的装饰器是组合还是继承,配置如何处理,swagger 扩展等等。

好的,希望官方能规划出更方便的分模化方案。