Skip to content

Commit

Permalink
feat: add router collector and export router table (#852)
Browse files Browse the repository at this point in the history
  • Loading branch information
czy88840616 authored Feb 23, 2021
1 parent 49f568f commit 3641ac9
Show file tree
Hide file tree
Showing 27 changed files with 696 additions and 389 deletions.
5 changes: 3 additions & 2 deletions packages/core/src/context/applicationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
import { ObjectProperties } from '../definitions/properties';
import { ManagedResolverFactory } from './managedResolverFactory';
import { NotFoundError } from '../common/notFoundError';
import { parsePrefix, isPathEqual } from '../util/';
import { parsePrefix } from '../util/';
import { PathFileUtil } from '../util/pathFileUtil';

const PREFIX = '_id_default_';

Expand Down Expand Up @@ -244,7 +245,7 @@ export class BaseApplicationContext
) {
if (!this.disableConflictCheck && this.registry.hasDefinition(identifier)) {
const def = this.registry.getDefinition(identifier);
if (!isPathEqual(definition.srcPath, def.srcPath)) {
if (!PathFileUtil.isPathEqual(definition.srcPath, def.srcPath)) {
throw new Error(
`${identifier} path = ${definition.srcPath} already exist (${def.srcPath})!`
);
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/context/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
import { dirname, isAbsolute, join } from 'path';
import { MAIN_MODULE_KEY, generateProvideId } from '@midwayjs/decorator';
import { IContainerConfiguration, IMidwayContainer } from '../interface';
import { isPath, safeRequire } from '../util/';
import { safeRequire } from '../util/';
import { PathFileUtil } from '../util/pathFileUtil';
import * as util from 'util';
import { FunctionalConfiguration } from '../functional/configuration';

Expand Down Expand Up @@ -121,7 +122,7 @@ export class ContainerConfiguration implements IContainerConfiguration {

private resolvePackageBaseDir(packageName: string, baseDir?: string) {
// 把相对路径转为绝对路径
if (isPath(packageName)) {
if (PathFileUtil.isPath(packageName)) {
if (!isAbsolute(packageName)) {
packageName = join(baseDir || this.container.baseDir, packageName);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ export { BaseFramework } from './baseFramework';
export * from './context/providerWrapper';
export * from './common/constants';
export { safelyGet, safeRequire } from './util/';
export * from './util/pathFileUtil';
export * from './features';
export * from './util/webRouterParam';
export * from './util/webRouterCollector';
export { plainToClass, classToPlain } from 'class-transformer';
export * from './logger';
export { createConfiguration } from './functional/configuration';
Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/util/emptyFramework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { BaseFramework } from '../baseFramework';
import {
IMidwayApplication,
IMidwayBootstrapOptions,
MidwayFrameworkType,
} from '../interface';

export class EmptyFramework extends BaseFramework<any, any, any> {
getApplication(): any {
return this.app;
}

getFrameworkType(): MidwayFrameworkType {
return MidwayFrameworkType.CUSTOM;
}

async run(): Promise<void> {}

async applicationInitialize(options: IMidwayBootstrapOptions) {
this.app = {} as IMidwayApplication;
}

getDefaultContextLoggerClass() {
return super.getDefaultContextLoggerClass();
}
}
18 changes: 1 addition & 17 deletions packages/core/src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dirname, resolve, sep, extname } from 'path';
import { dirname, resolve, sep } from 'path';
import { readFileSync } from 'fs';

export const isDevelopmentEnvironment = env => {
Expand All @@ -23,14 +23,6 @@ export const safeRequire = (p, enabledCache = true) => {
}
};

export const isPath = (p): boolean => {
// eslint-disable-next-line no-useless-escape
if (/(^[\.\/])|:|\\/.test(p)) {
return true;
}
return false;
};

/**
* safelyGet(['a','b'],{a: {b: 2}}) // => 2
* safelyGet(['a','b'],{c: {b: 2}}) // => undefined
Expand Down Expand Up @@ -76,14 +68,6 @@ export function parsePrefix(provideId: string) {
return provideId;
}

export function isPathEqual(one: string, two: string) {
if (!one || !two) {
return false;
}
const ext = extname(one);
return one.replace(ext, '') === two;
}

export function getUserHome() {
return process.env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'];
}
26 changes: 26 additions & 0 deletions packages/core/src/util/pathFileUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { extname } from 'path';
import { readFileSync } from 'fs';

export const PathFileUtil = {
isPath(p): boolean {
// eslint-disable-next-line no-useless-escape
if (/(^[\.\/])|:|\\/.test(p)) {
return true;
}
return false;
},

isPathEqual(one: string, two: string) {
if (!one || !two) {
return false;
}
const ext = extname(one);
return one.replace(ext, '') === two;
},

getFileContentSync(filePath: any, encoding?: string) {
return typeof filePath === 'string'
? readFileSync(filePath, encoding)
: filePath;
},
};
227 changes: 227 additions & 0 deletions packages/core/src/util/webRouterCollector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
import { EmptyFramework } from './emptyFramework';
import {
CONTROLLER_KEY,
ControllerOption,
getClassMetadata,
getPropertyDataFromClass,
getPropertyMetadata,
getProviderId,
listModule,
PRIORITY_KEY,
RouterOption,
WEB_RESPONSE_KEY,
WEB_ROUTER_KEY,
WEB_ROUTER_PARAM_KEY,
} from '@midwayjs/decorator';
import { MidwayContainer } from '../context/midwayContainer';

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;

/**
* controller provideId
*/
controllerId: string;
/**
* router middleware
*/
middleware: any[];

/**
* request args metadata
*/
requestMetadata: any[];

/**
* response data metadata
*/
responseMetadata: any[];
}

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

export class WebRouterCollector {
private readonly baseDir: string;
private routes = new Map<string, RouterInfo[]>();
private routesPriority: RouterPriority[] = [];
private isReady = false;

constructor(baseDir?: string) {
this.baseDir = baseDir || '';
}

protected async analyze() {
if (!MidwayContainer.parentDefinitionMetadata) {
const framework = new EmptyFramework();
await framework.initialize({
baseDir: this.baseDir,
});
}

const controllerModules = listModule(CONTROLLER_KEY);

for (const module of controllerModules) {
this.collectRoute(module);
}

// sort router
for (const prefix of this.routes.keys()) {
const routerInfo = this.routes.get(prefix);
this.routes.set(prefix, this.sortRouter(routerInfo));
}

// sort prefix
this.routesPriority = this.routesPriority.sort((routeA, routeB) => {
return routeB.priority - routeA.priority;
});
}

protected collectRoute(module) {
const controllerId = getProviderId(module);
const controllerOption: ControllerOption = getClassMetadata(
CONTROLLER_KEY,
module
);

// sort for priority
let priority = getClassMetadata(PRIORITY_KEY, module);
// implement middleware in controller
const middleware = controllerOption.routerOptions.middleware;

const prefix = controllerOption.prefix || '/';
if (prefix === '/' && priority === undefined) {
priority = -999;
}

if (!this.routes.has(prefix)) {
this.routes.set(prefix, []);
this.routesPriority.push({
prefix,
priority: priority || 0,
middleware,
routerOptions: controllerOption.routerOptions,
controllerId,
});
}

const webRouterInfo: RouterOption[] = getClassMetadata(
WEB_ROUTER_KEY,
module
);

if (webRouterInfo && typeof webRouterInfo[Symbol.iterator] === 'function') {
for (const webRouter of webRouterInfo) {
const routeArgsInfo =
getPropertyDataFromClass(
WEB_ROUTER_PARAM_KEY,
module,
webRouter.method
) || [];

const routerResponseData =
getPropertyMetadata(WEB_RESPONSE_KEY, module, webRouter.method) || [];

this.routes.get(prefix).push({
prefix,
routerName: webRouter.routerName || '',
url: webRouter.path,
requestMethod: webRouter.requestMethod,
method: webRouter.method,
description: webRouter.description || '',
summary: webRouter.summary || '',
handlerName: `${controllerId}.${webRouter.method}`,
controllerId,
middleware: webRouter.middleware,
requestMetadata: routeArgsInfo,
responseMetadata: routerResponseData,
});
}
}
}

protected sortRouter(urlMatchList: RouterInfo[]) {
// 1. 绝对路径规则优先级最高如 /ab/cb/e
// 2. 星号只能出现最后且必须在/后面,如 /ab/cb/**
// 3. 如果绝对路径和通配都能匹配一个路径时,绝对规则优先级高
// 4. 有多个通配能匹配一个路径时,最长的规则匹配,如 /ab/** 和 /ab/cd/** 在匹配 /ab/cd/f 时命中 /ab/cd/**
// 5. 如果 / 与 /* 都能匹配 / ,但 / 的优先级高于 /*
return urlMatchList
.map(item => {
return {
...item,
pureRouter: item.url.toString().replace(/\**$/, ''),
level: item.url.toString().split('/').length - 1,
};
})
.sort((handlerA, handlerB) => {
if (handlerA.level === handlerB.level) {
if (handlerB.pureRouter === handlerA.pureRouter) {
return (
handlerA.url.toString().length - handlerB.url.toString().length
);
}
return handlerB.pureRouter.length - handlerA.pureRouter.length;
}
return handlerB.level - handlerA.level;
});
}

async getRoutePriorityList(): Promise<RouterPriority[]> {
if (!this.isReady) {
await this.analyze();
this.isReady = true;
}
return this.routesPriority;
}

async getRouterTable(): Promise<Map<string, RouterInfo[]>> {
if (!this.isReady) {
await this.analyze();
this.isReady = true;
}
return this.routes;
}

async getFlattenRouterTable(): Promise<RouterInfo[]> {
if (!this.isReady) {
await this.analyze();
this.isReady = true;
}
let routeArr = [];
for (const routerInfo of this.routes.values()) {
routeArr = routeArr.concat(routerInfo);
}
return routeArr;
}
}
Loading

0 comments on commit 3641ac9

Please sign in to comment.