Skip to content

Commit

Permalink
feat: @Middleware support Advice
Browse files Browse the repository at this point in the history
  • Loading branch information
killagu committed Aug 22, 2024
1 parent d7127d3 commit 1cfb249
Show file tree
Hide file tree
Showing 20 changed files with 399 additions and 34 deletions.
4 changes: 3 additions & 1 deletion core/controller-decorator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@
},
"dependencies": {
"@eggjs/core-decorator": "^3.39.5",
"@eggjs/aop-decorator": "^3.39.5",
"@eggjs/tegg-common-util": "^3.39.5",
"@eggjs/tegg-metadata": "^3.39.5",
"@eggjs/tegg-types": "^3.39.5",
"path-to-regexp": "^1.8.0",
"reflect-metadata": "^0.1.13",
"undici": "^5.26.5"
"undici": "^5.26.5",
"is-type-of": "^1.2.1"
},
"devDependencies": {
"@types/mocha": "^10.0.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type {
ControllerMetaBuilder,
ControllerMetaBuilderCreator,
ControllerMetaBuilderCreator, ControllerMetadata,
ControllerTypeLike,
EggProtoImplClass,
} from '@eggjs/tegg-types';
import ControllerInfoUtil from '../util/ControllerInfoUtil';
import MethodInfoUtil from '../util/MethodInfoUtil';
import { Pointcut } from '@eggjs/aop-decorator';

export class ControllerMetaBuilderFactory {
private static builderCreatorMap: Map<ControllerTypeLike, ControllerMetaBuilderCreator> = new Map();
Expand All @@ -26,4 +28,23 @@ export class ControllerMetaBuilderFactory {
}
return creator(clazz);
}

static build(clazz: EggProtoImplClass, controllerType?: ControllerTypeLike): ControllerMetadata | undefined {
const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(clazz, controllerType);
if (!builder) return;
const metadata = builder.build();
if (!metadata) return;
const controllerAopMws = ControllerInfoUtil.getControllerAopMiddlewares(clazz);
for (const { name } of metadata.methods) {
const methodAopMws = MethodInfoUtil.getMethodAopMiddlewares(clazz, name);
if (MethodInfoUtil.shouldRegisterAopMiddlewarePointCut(clazz, name)) {
for (const mw of [ ...methodAopMws, ...controllerAopMws ].reverse()) {
Pointcut(mw)(clazz.prototype, name);
}
MethodInfoUtil.registerAopMiddlewarePointcut(clazz, name);
}
}

return metadata;
}
}
57 changes: 51 additions & 6 deletions core/controller-decorator/src/decorator/Middleware.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
import assert from 'node:assert';
import type { EggProtoImplClass, MiddlewareFunc } from '@eggjs/tegg-types';
import type { IAdvice, EggProtoImplClass, MiddlewareFunc } from '@eggjs/tegg-types';
import is from 'is-type-of';
import ControllerInfoUtil from '../util/ControllerInfoUtil';
import MethodInfoUtil from '../util/MethodInfoUtil';
import { AdviceInfoUtil } from '@eggjs/aop-decorator';

export function Middleware(...middlewares: MiddlewareFunc []) {
function classMiddleware(constructor: EggProtoImplClass) {
enum MiddlewareType {
AOP = 'AOP',
MiddlewareFunc = 'MiddlewareFunc',
}

function isAop(mw: MiddlewareFunc | EggProtoImplClass<IAdvice>) {
return is.class(mw) && AdviceInfoUtil.isAdvice(mw);
}

function isAopTypeOrMiddlewareType(middlewares: Array<MiddlewareFunc> | Array<EggProtoImplClass<IAdvice>>): MiddlewareType {
const adviceCount = middlewares.filter(t => isAop(t)).length;
if (adviceCount) {
if (adviceCount === middlewares.length) {
return MiddlewareType.AOP;
}
throw new Error('AOP and MiddlewareFunc can not be mixed');
}
return MiddlewareType.MiddlewareFunc;
}

export function Middleware(...middlewares: Array<MiddlewareFunc> | Array<EggProtoImplClass<IAdvice>>) {
function functionTypeClassMiddleware(constructor: EggProtoImplClass) {
middlewares.forEach(mid => {
ControllerInfoUtil.addControllerMiddleware(mid, constructor);
});
}

function methodMiddleware(target: any, propertyKey: PropertyKey) {
function aopTypeClassMiddleware(constructor: EggProtoImplClass) {
for (const aopAdvice of middlewares as EggProtoImplClass<IAdvice>[]) {
ControllerInfoUtil.addControllerAopMiddleware(aopAdvice, constructor);
}
}

function functionTypeMethodMiddleware(target: any, propertyKey: PropertyKey) {
assert(typeof propertyKey === 'string',
`[controller/${target.name}] expect method name be typeof string, but now is ${String(propertyKey)}`);
const controllerClazz = target.constructor as EggProtoImplClass;
Expand All @@ -20,11 +48,28 @@ export function Middleware(...middlewares: MiddlewareFunc []) {
});
}

function aopTypeMethodMiddleware(target: any, propertyKey: PropertyKey) {
const controllerClazz = target.constructor as EggProtoImplClass;
const methodName = propertyKey as string;
for (const aopAdvice of middlewares as EggProtoImplClass<IAdvice>[]) {
MethodInfoUtil.addMethodAopMiddleware(aopAdvice, controllerClazz, methodName);
}
}

return function(target: any, propertyKey?: PropertyKey) {
const type = isAopTypeOrMiddlewareType(middlewares);
if (propertyKey === undefined) {
classMiddleware(target);
if (type === MiddlewareType.AOP) {
aopTypeClassMiddleware(target);
} else {
functionTypeClassMiddleware(target);
}
} else {
methodMiddleware(target, propertyKey);
if (type === MiddlewareType.AOP) {
aopTypeMethodMiddleware(target, propertyKey);
} else {
functionTypeMethodMiddleware(target, propertyKey);
}
}
};
}
13 changes: 11 additions & 2 deletions core/controller-decorator/src/util/ControllerInfoUtil.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
CONTROLLER_ACL,
CONTROLLER_ACL, CONTROLLER_AOP_MIDDLEWARES,
CONTROLLER_HOST,
CONTROLLER_MIDDLEWARES,
CONTROLLER_NAME,
CONTROLLER_TYPE,
CONTROLLER_TYPE, IAdvice,
} from '@eggjs/tegg-types';
import type { ControllerTypeLike, EggProtoImplClass, MiddlewareFunc } from '@eggjs/tegg-types';
import { MetadataUtil } from '@eggjs/core-decorator';
Expand All @@ -14,10 +14,19 @@ export default class ControllerInfoUtil {
middlewares.push(middleware);
}

static addControllerAopMiddleware(middleware: EggProtoImplClass<IAdvice>, clazz: EggProtoImplClass) {
const middlewares = MetadataUtil.initOwnArrayMetaData<EggProtoImplClass<IAdvice>>(CONTROLLER_AOP_MIDDLEWARES, clazz, []);
middlewares.push(middleware);
}

static getControllerMiddlewares(clazz: EggProtoImplClass): MiddlewareFunc[] {
return MetadataUtil.getMetaData(CONTROLLER_MIDDLEWARES, clazz) || [];
}

static getControllerAopMiddlewares(clazz: EggProtoImplClass): EggProtoImplClass<IAdvice>[] {
return MetadataUtil.getMetaData(CONTROLLER_AOP_MIDDLEWARES, clazz) || [];
}

static setControllerType(clazz: EggProtoImplClass, controllerType: ControllerTypeLike) {
MetadataUtil.defineMetaData(CONTROLLER_TYPE, controllerType, clazz);
}
Expand Down
6 changes: 1 addition & 5 deletions core/controller-decorator/src/util/ControllerMetadataUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@ export class ControllerMetadataUtil {
if (metadata) {
return metadata;
}
const builder = ControllerMetaBuilderFactory.createControllerMetaBuilder(clazz);
if (!builder) {
return;
}
metadata = builder.build();
metadata = ControllerMetaBuilderFactory.build(clazz);
if (metadata) {
ControllerMetadataUtil.setControllerMetadata(clazz, metadata);
}
Expand Down
31 changes: 30 additions & 1 deletion core/controller-decorator/src/util/MethodInfoUtil.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MetadataUtil } from '@eggjs/core-decorator';
import { MapUtil } from '@eggjs/tegg-common-util';
import {
METHOD_ACL,
IAdvice,
METHOD_ACL, METHOD_AOP_MIDDLEWARES, METHOD_AOP_REGISTER_MAP,
METHOD_CONTEXT_INDEX,
METHOD_CONTROLLER_HOST,
METHOD_CONTROLLER_TYPE_MAP,
Expand All @@ -10,8 +11,10 @@ import {
import type { ControllerTypeLike, EggProtoImplClass, MiddlewareFunc } from '@eggjs/tegg-types';

type METHOD_MAP = Map<string, ControllerTypeLike | string[]>;
type MethodAopRegisterMap = Map<string, boolean>;
type MethodContextIndexMap = Map<string, number>;
type MethodMiddlewareMap = Map<string, MiddlewareFunc[]>;
type MethodAopMiddlewareMap = Map<string, EggProtoImplClass<IAdvice>[]>;
type MethodAclMap = Map<string, string | undefined>;

export default class MethodInfoUtil {
Expand Down Expand Up @@ -46,6 +49,17 @@ export default class MethodInfoUtil {
return methodMiddlewareMap?.get(methodName) || [];
}

static addMethodAopMiddleware(middleware: EggProtoImplClass<IAdvice>, clazz: EggProtoImplClass, methodName: string) {
const methodMiddlewareMap: MethodAopMiddlewareMap = MetadataUtil.initOwnMapMetaData(METHOD_AOP_MIDDLEWARES, clazz, new Map());
const methodMiddlewares = MapUtil.getOrStore(methodMiddlewareMap, methodName, []);
methodMiddlewares.push(middleware);
}

static getMethodAopMiddlewares(clazz: EggProtoImplClass, methodName: string): EggProtoImplClass<IAdvice>[] {
const methodMiddlewareMap: MethodAopMiddlewareMap | undefined = MetadataUtil.getMetaData(METHOD_AOP_MIDDLEWARES, clazz);
return methodMiddlewareMap?.get(methodName) || [];
}

static setMethodAcl(code: string | undefined, clazz: EggProtoImplClass, methodName: string) {
const methodAclMap: MethodAclMap = MetadataUtil.initOwnMapMetaData(METHOD_ACL, clazz, new Map());
methodAclMap.set(methodName, code);
Expand All @@ -70,4 +84,19 @@ export default class MethodInfoUtil {
const methodControllerMap: METHOD_MAP | undefined = MetadataUtil.getMetaData(METHOD_CONTROLLER_HOST, clazz);
return methodControllerMap?.get(methodName) as string[] | undefined;
}

static getMethods(clazz: EggProtoImplClass): string[] {
const methodControllerMap: METHOD_MAP | undefined = MetadataUtil.getMetaData(METHOD_CONTROLLER_TYPE_MAP, clazz);
return Array.from(methodControllerMap?.keys() || []);
}

static shouldRegisterAopMiddlewarePointCut(clazz: EggProtoImplClass, methodName: string): boolean {
const methodControllerMap: MethodAopRegisterMap | undefined = MetadataUtil.getMetaData(METHOD_AOP_REGISTER_MAP, clazz);
return !(methodControllerMap && methodControllerMap.get(methodName));
}

static registerAopMiddlewarePointcut(clazz: EggProtoImplClass, methodName: string) {
const methodControllerMap: MethodAopRegisterMap = MetadataUtil.initOwnMapMetaData(METHOD_AOP_REGISTER_MAP, clazz, new Map());
methodControllerMap.set(methodName, true);
}
}
16 changes: 16 additions & 0 deletions core/controller-decorator/test/Middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import assert from 'node:assert';
import { MiddlewareController, MiddlewaresController } from './fixtures/MiddlewareController';
import ControllerInfoUtil from '../src/util/ControllerInfoUtil';
import MethodInfoUtil from '../src/util/MethodInfoUtil';
import {
AopMiddlewareController,
BarAdvice,
BarMethodAdvice,
FooAdvice,
FooMethodAdvice,
} from './fixtures/AopMiddlewareController';

describe('test/Middleware.test.ts', () => {
it('should work', () => {
Expand All @@ -16,4 +23,13 @@ describe('test/Middleware.test.ts', () => {
assert(controllerMws.length === 1);
assert(methodMws.length === 2);
});

it('controller Aop Middleware should work', () => {
const controllerAopMws = ControllerInfoUtil.getControllerAopMiddlewares(AopMiddlewareController);
const helloMethodMws = MethodInfoUtil.getMethodAopMiddlewares(AopMiddlewareController, 'hello');
const byeMethodMws = MethodInfoUtil.getMethodAopMiddlewares(AopMiddlewareController, 'bye');
assert.deepStrictEqual(controllerAopMws, [ FooAdvice, BarAdvice ]);
assert.deepStrictEqual(helloMethodMws, [ FooMethodAdvice, BarMethodAdvice ]);
assert.deepStrictEqual(byeMethodMws, []);
});
});
63 changes: 63 additions & 0 deletions core/controller-decorator/test/fixtures/AopMiddlewareController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { HTTPMethodEnum, IAdvice, ObjectInitType } from '@eggjs/tegg-types';
import { Middleware } from '../../src/decorator/Middleware';
import { Advice } from '@eggjs/aop-decorator';
import { HTTPController } from '../../src/decorator/http/HTTPController';
import { HTTPMethod } from '../../src/decorator/http/HTTPMethod';

@Advice({
initType: ObjectInitType.SINGLETON,
})
export class FooAdvice implements IAdvice {
async beforeCall(): Promise<void> {
// ...
}
}

@Advice({
initType: ObjectInitType.SINGLETON,
})
export class BarAdvice implements IAdvice {
async beforeCall(): Promise<void> {
// ...
}
}

@Advice({
initType: ObjectInitType.SINGLETON,
})
export class FooMethodAdvice implements IAdvice {
async beforeCall(): Promise<void> {
// ...
}
}

@Advice({
initType: ObjectInitType.SINGLETON,
})
export class BarMethodAdvice implements IAdvice {
async beforeCall(): Promise<void> {
// ...
}
}

@Middleware(FooAdvice, BarAdvice)
@HTTPController()
export class AopMiddlewareController {

@Middleware(FooMethodAdvice, BarMethodAdvice)
@HTTPMethod({
method: HTTPMethodEnum.GET,
path: '/hello',
})
async hello(): Promise<void> {
return;
}

@HTTPMethod({
method: HTTPMethodEnum.GET,
path: '/bye',
})
async bye(): Promise<void> {
return;
}
}
Loading

0 comments on commit 1cfb249

Please sign in to comment.