跳到主要内容
版本:3.0.0

Web 路由表

从 v2.8.0 开始,Midway 提供了内置的路由表能力,所有的 Web 框架都将使用这份路由表注册路由。

从 v3.4.0 开始,路由服务将作为 Midway 内置服务提供。

在应用启动,onReady 生命周期以及之后可用。

获取路由表服务

已默认实例化,可以直接注入使用。

import { Configuration, Inject, MidwayWebRouterService, MidwayServerlessFunctionService } from '@midwayjs/core';

@Configuration({
// ...
})
export class MainConfiguration {
@Inject()
webRouterService: MidwayWebRouterService;

@Inject()
serverlessFunctionService: MidwayServerlessFunctionService;

async onReady() {
// Web 路由
const routes = await this.webRouterService.getFlattenRouterTable();

// serverless 函数
const routes = await this.serverlessFunctionService.getFunctionList();
}
}

MidwayServerlessFunctionService 仅在 Serverless 场景下生效,方法和 MidwayWebRouterService 几乎相同。

路由信息定义

每个路由信息由一个 RouterInfo 定义表示,包含一些属性。

定义如下:

export interface RouterInfo {
/**
* router prefix
*/
prefix: string;
/**
* router alias name
*/
routerName: string;
/**
* router path, without prefix
*/
url: string | RegExp;
/**
* request method for http, like get/post/delete
*/
requestMethod: string;
/**
* invoke function method
*/
method: string;
description: string;
summary: string;
/**
* router handler function key,for IoC container load
*/
handlerName: string;
/**
* serverless func load key
*/
funcHandlerName: string;
/**
* controller provideId
*/
controllerId: string;
/**
* router middleware
*/
middleware: any[];
/**
* controller middleware in this router
*/
controllerMiddleware: any[];
/**
* request args metadata
*/
requestMetadata: any[];
/**
* response data metadata
*/
responseMetadata: any[];
}
属性类型描述
prefixstring路由前缀,比如 / 或者 /api,用户写在 @Controller 装饰器上的部分
routerNamestring路由名
urlstring路由的去除路由前缀的部分,也是用户写在 @Get 等装饰器上的部分
requestMethodstringget/post/delete/put/all 等
methodstring实际调用的类上的方法名
descriptionstring描述,路由装饰器上的参数
summarystring摘要,路由装饰器上的参数
handlerNamestring等价于 controllerId.method
funcHandlerNamestring使用 @Func 写的 handler 名字
controllerIdstringcontroller 的依赖注入容器的 key(providerId)
middlewarestring[]路由中间件字符串数组
controllerMiddlewarestring[]控制器中间件字符串数组
requestMetadataany[]请求参数的元数据,@Query/@Body 等元数据
responseMetadataany[]响应参数的元数据,@SetHeader/@ContentType 等元数据

路由优先级

以往我们需要关心路由的加载顺序,比如通配的 /* 比如在实际的 /abc 之后,否则会加载到错误的路由。在新版本中,我们对此种情况做了自动排序。

规则如下:

    1. 绝对路径规则优先级最高如 /ab/cb/e
    1. 星号只能出现最后且必须在/后面,如 /ab/cb/**
    1. 如果绝对路径和通配都能匹配一个路径时,绝对规则优先级高
    1. 有多个通配能匹配一个路径时,最长的规则匹配,如 /ab/**/ab/cd/** 在匹配 /ab/cd/f 时命中 /ab/cd/**
    1. 如果 //* 都能匹配 / ,但 / 的优先级高于 /*

此规则也与 Serverless 下函数的路由规则保持一致。

简单理解为,“明确的路由优先级最高,长的路由优先级高,通配的优先级最低”。

比如,排序完的优先级如下(高到低):

/api/invoke/abc
/api/invoke/*
/api/abc
/api/*
/abc
/*

当前匹配的路由

通过 getMatchedRouterInfo 方法,我们可以知道当前的路由,匹配到哪个路由信息(RouterInfo),从而进一步处理,这个逻辑在鉴权等场景很有用。

比如,在中间件中,我们可以在进入控制器之前提前判断。

import { Middleware, Inject, httpError, MidwayWebRouterService } from '@midwayjs/core';

@Middleware()
export class AuthMiddleware {
@Inject()
webRouterService: MidwayWebRouterService;

resolve() {
return async (ctx, next) => {
// 查询当前路由是否在路由表中注册
const routeInfo = await this.webRouterService.getMatchedRouterInfo(ctx.path, ctx.method);
if (routeInfo) {
await next();
} else {
throw new httpError.NotFoundError();
}
}
}
}

路由信息

获取扁平化路由列表

获取当前所有可注册到 HTTP 服务的路由列表(包括 @Func/@Controller,以及一切按照标准信息注册的自定义装饰器)。

会按照优先级从高到低自动排序。

定义:

async getFlattenRouterTable(): Promise<RouterInfo[]>

获取路由表 API。

const result = await this.webRouterService.getFlattenRouterTable();

输出示例:

[
{
"prefix": "/",
"routerName": "",
"url": "/set_header",
"requestMethod": "get",
"method": "homeSet",
"description": "",
"summary": "",
"handlerName": "apiController.homeSet",
"funcHandlerName": "apiController.homeSet",
"controllerId": "apiController",
"middleware": [],
"controllerMiddleware": [],
"requestMetadata": [],
"responseMetadata": [
{
"type": "web:response_header",
"setHeaders": {
"ccc": "ddd"
}
},
{
"type": "web:response_header",
"setHeaders": {
"bbb": "aaa"
}
}
],
},
{
"prefix": "/",
"routerName": "",
"url": "/ctx-body",
"requestMethod": "get",
"method": "getCtxBody",
"description": "",
"summary": "",
"handlerName": "apiController.getCtxBody",
"funcHandlerName": "apiController.getCtxBody",
"controllerId": "apiController",
"middleware": [],
"controllerMiddleware": [],
"requestMetadata": [],
"responseMetadata": [],
},
// ...
]

获取 Router 信息列表

在 Midway 中,每个 Controller 对应一个 Router 对象,每个 Router 都会有一个路由前缀(prefix),在此之中的所有路由都会按照上面的规则进行排序。

Router 本身也会按照 prefix 进行排序。

定义:

export interface RouterPriority {
prefix: string;
priority: number;
middleware: any[];
routerOptions: any;
controllerId: string;
}

async getRoutePriorityList(): Promise<RouterPriority[]>

Router 的数据相对简单。

属性类型描述
prefixstring路由前缀,比如 / 或者 /api,用户写在 @Controller 装饰器上的部分
prioritynumberRouter 的优先级,@Priority 装饰器填写的值,/ 根 Router 默认优先级最低,为 -999
middlewarestring[]控制器中间件字符串数组
controllerIdstringcontroller 的依赖注入容器的 key(providerId)
routerOptionsany@Controller 装饰器的 options

获取路由表 API。

const list = await collector.getRoutePriorityList();

输出示例:

[
{
"prefix": "/case",
"priority": 0,
"middleware": [],
"routerOptions": {
"middleware": [],
"sensitive": true
},
"controllerId": "caseController"
},
{
"prefix": "/user",
"priority": 0,
"middleware": [],
"routerOptions": {
"middleware": [],
"sensitive": true
},
"controllerId": "userController"
},
{
"prefix": "/",
"priority": -999,
"middleware": [],
"routerOptions": {
"middleware": [],
"sensitive": true
},
"controllerId": "apiController"
}
]

获取带层级的路由

某些情况下,我们需要拿到带层级的路由,包括哪些路由在哪个控制器(Controller)下,这样能更好的创建路由。

Midway 也提供了获取带层级的路由表方法。层级内会按照优先级从高到低自动排序。

定义:

async getRouterTable(): Promise<Map<string, RouterInfo[]>>

获取层级路由表 API,返回的是个 Map,key 为控制器的路由前缀 prefix 字符串。未明确写明路由前缀的(比如函数或者其他场景),都将归为 / 路由前缀下。

const result = await collector.getRouterTable();

输出示例:

Map(3) {
'/' => [
{
prefix: '/',
routerName: '',
url: '/set_header',
requestMethod: 'get',
method: 'homeSet',
description: '',
summary: '',
handlerName: 'apiController.homeSet',
funcHandlerName: 'apiController.homeSet',
controllerId: 'apiController',
middleware: [],
controllerMiddleware: [],
requestMetadata: [],
responseMetadata: [Array],
},
{
prefix: '/',
routerName: '',
url: '/ctx-body',
requestMethod: 'get',
method: 'getCtxBody',
description: '',
summary: '',
handlerName: 'apiController.getCtxBody',
funcHandlerName: 'apiController.getCtxBody',
controllerId: 'apiController',
middleware: [],
controllerMiddleware: [],
requestMetadata: [],
responseMetadata: [],
},
// ...
]
}

获取所有函数信息

getFlattenRouterTable 相同,只是返回的内容多了函数部分的信息。

定义:

async getFunctionList(): Promise<RouterInfo[]>

获取函数路由表 API。

const result = await this.serverlessFunctionService.getFunctionList();

添加路由

动态添加 Web 控制器

有些时候我们希望根据某些条件动态的添加一个控制器,就可以使用这个方法。

首先,我们需要有一个控制器类,但是不使用 @Controller 装饰器修饰。

import { Get, Provide } from '@midwayjs/core';

// 注意这里未使用 @Controller 修饰
@Provide()
export class DataController {
@Get('/query_data')
async getData() {
return 'hello world';
}
}

我们可以通过 addController 方法动态添加它。

// src/configuration.ts
import { Configuration, Inject, MidwayWebRouterService } from '@midwayjs/core';
import { DataController } from './controller/data.controller';

@Configuration({
// ...
})
export class MainConfiguration {
@Inject()
webRouterService: MidwayWebRouterService;

async onReady() {
if (process.env.NODE_ENV === 'test') {
this.webRouterService.addController(DataController, {
prefix: '/test',
routerOptions: {
middleware: [
// ...
]
}
});
}
// ...
}
}

addController 的方法,第一个参数为类本身,第二个参数和 @Controller 装饰器参数相同。

动态添加 Web 路由函数

在某些场景下,用户可以直接动态添加方法。

// src/configuration.ts
import { Configuration, Inject, MidwayWebRouterService } from '@midwayjs/core';

@Configuration({
// ...
})
export class MainConfiguration {
@Inject()
webRouterService: MidwayWebRouterService;

async onReady() {
// koa/egg 格式
this.webRouterService.addRouter(async (ctx) => {
return 'hello world';
}, {
url: '/api/user',
requestMethod: 'GET',
});
// ...

// express 格式
this.webRouterService.addRouter(async (req, res) => {
return 'hello world';
}, {
url: '/api/user',
requestMethod: 'GET',
});
}
}

addRouter 的方法,第一个参数为路由方法体,第二个参数为路由的元数据。

动态添加 Serverless 函数

和添加动态 Web 路由类似,使用内置的 MidwayServerlessFunctionService 服务来添加。

比如,添加一个 http 函数。

// src/configuration.ts
import { Configuration, Inject, MidwayServerlessFunctionService } from '@midwayjs/core';

@Configuration({
// ...
})
export class MainConfiguration {
@Inject()
serverlessFunctionService: MidwayServerlessFunctionService;

async onReady() {
this.serverlessFunctionService.addServerlessFunction(async (ctx, event) => {
return 'hello world';
}, {
type: ServerlessTriggerType.HTTP,
metadata: {
method: 'get',
path: '/api/hello'
},
functionName: 'hello',
handlerName: 'index.hello',
});
}
}

metadata 的信息和 @ServerlessTrigger 的参数相同。