我想创建一个装饰器,它可以获取类的所有方法并用某些功能包装它们,对于这个例子,只需像这样记录:
export function CustDec<T extends new (...args: any[]) => any>(Target: T) {
return class extends Target {
constructor(...args: any[]) {
super(...args);
console.log('@--------------------@');
(Target as any).prototype.alphaMethod = async (args: any[]) => {
console.log('@-before-@');
await (Target as any).alphaMethod();
console.log('@-after-@');
};
}
};
}
问题是当我将此装饰器应用到我的班级时:
import { Controller, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Firewall } from 'src/auth/decorators/firewall.decorator';
import { CustDec } from './feedback.decorator';
@CustDec
@ApiTags('feedback')
@Controller('feedback')
export class FeedbackController {
constructor() {
setTimeout(async () => {
await this.alphaMethod(); // <--- here
}, 3000);
}
@Firewall()
@Post('')
async alphaMethod() { // <-- and here
return 'some promised result';
}
}
当我尝试在控制器上调用
/feedback
端点时,我收到错误,就好像 Firewall
和 Post
装饰器在我的 CustDec
更改 alphaMethod
后停止工作一样。即使我从 FeedbackController 构造方法内部调用 alphaMethod
,我也会收到另一个错误:
@-before-@
This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason:
TypeError: Target.alphaMethod is not a function
at FeedbackController.Target.alphaMethod (/home/zagrava/workspace/test-nest/backend/src/feedback/feedback.decorator.ts:8:31)
at Timeout._onTimeout (/home/zagrava/workspace/test-nest/backend/src/feedback/feedback.controller.ts:12:18)
at listOnTimeout (node:internal/timers:564:17)
at processTimers (node:internal/timers:507:7)
如何让我的 CustDec 使用日志记录正确包装类的所有方法?
类装饰器可以像下面这样实现:
function intercept<T extends { new(...args: any[]): {} }>(target: T) {
const methods = getMethods(target.prototype);
for (const method of methods) {
const currentMethod = target.prototype[method]
target.prototype[method] = async (...args: any[]) => {
console.log("intercepted", new Date());
const result = currentMethod(args)
if (result instanceof Promise) {
await result
}
console.log("executed", new Date());
return result;
}
}
}
这个想法是获取类的所有函数(构造函数本身除外)并将它们包装在自定义函数中。 获取如下类函数的辅助方法对于这种情况很有用:
const getMethods = (obj: any) => Object.getOwnPropertyNames(obj).filter(item => typeof obj[item] === 'function' && item !== "constructor")
问题很简单,POST和Firewall等装饰器使用反射包创建元数据,因此只需将原始方法的元数据复制粘贴到目标方法即可。
function copyMetadata(
originalMethod: any,
targetMethod: any,
) {
const metadataKeys = Reflect.getOwnMetadataKeys(originalMethod);
for (const key of metadataKeys) {
const metadata = Reflect.getMetadata(key, originalMethod);
Reflect.defineMetadata(key, metadata, targetMethod);
}
}