Env: Typescript5.2 + RxJs + Node16
Git: github.com/chelizichen...
File: github.com/chelizichen...
想象一下,@UsePipe该如何实现?
大多数TypeScript玩家可能都写过类似的,我简单讲下思路:
因为构建装饰器的参数中包含了该成员函数(新版和老版都保留了)。所以我们只需要根据拥有的装饰器依次堆加新的方法即可。说起来可能有些抽象,我写个简单的示例。
ts
/*
* @desc pipe 装饰器
*/
function UsePipe(pipe){
return function(method){
const originMethod = method;
method = async function(...args){
try{
await pipe(...args)
await originMethod(...args)
}catch(error){
handleError(error)
}
}
return method;
}
}
这样就可以实现管道验证而看起来不侵入业务代码,同理,拦截器还有其他各种各样的AOP概念都可以这样写。
但是上面那样写有个问题,由于实际上我们修改的是原成员函数,所以也算侵入了业务代码,并且在开发人员随意编写装饰器的位置,我们没办法保证pipe和interceptor哪个先执行。
如下面代码所示:
ts
@Controller("validate")
class ValidateController {
@Post('/changeStatus')
@UsePipe(new TestValidatePipe())
@UseInterceptor(new TestInerceptor())
async changeStatus(){
return {
code:1
}
}
}
如果装饰器是从上往下依次执行,那么必定是先执行管道再执行拦截器。但是开发的同学可不会这么想。
拦截器就是要比管道先执行啊,哪有身份都还没验证就直接进管道的
现在你有两种选择
- 告诉开发同学检查他的代码并修正,逻辑从上往下的话,代码也要从上往下不要乱掉。
- 重新构建自己写的的代码。
所幸这整个库都是我一个人练手玩玩的,所以我没有历史包袱的重构自己的代码。
接下来我将引入RxJS来重构我的代码。
我所要的效果是:写上去了就会执行,而且执行的先后顺序都是事先定义好的
于是我先定义了一个代表从前往后依次响应的数组。
ts
const httphandleStream = [
{key:"__rx__interceptor__",value:'handle.call'},
{key:"__rx__pipe__",value:"handle.call"},
{key:"__rx__router__",value:undefined}
];
const handleHttpObserve = from(httphandleStream);
然后就开始快乐的重构了
先创建响应流
ts
// func 是原本的成员函数
// context.metadata跟以前metadata差不多东西
const stream = handleHttpObserve.pipe(
concatMap(async (Obj) => {
try{
const {key,value} = Obj;
if(key === "__rx__router__"){
const data = await func(req,res)
_.set(context.metadata,'__rx__resp__',data)
return data;
}else{
const fn = (_.get(context.metadata,key) || EmptyFunction) as Function
await _.invoke(fn,value,...[this,req,res]);
}
}catch(error){
_.set(context.metadata,'__rx__resp__',error)
return error
}
}),
takeWhile(result => !(_.isError(result))), // 期间发生错误直接退出
scan((accumulator, currentValue) => [...accumulator, currentValue], []), // 使用 scan 累积结果
catchError(error => { // 抛出异常至 error
return throwError(()=>{
_.set(context.metadata,'__rx__resp__',error)
return error
});
})
)
再执行
其实这个时候next已经不重要了,他就是个打印每个执行的AOP函数返回值的东西,后续拓展可以用他来输出日志。 当有异常时,会进入到error中,并且RxJS管道关闭。否则一直执行,直到完成complete方法。
ts
stream.subscribe({
next(v){
console.log(v);
},
error(data){
const RESP = data || _.get(context.metadata,'__rx__resp__')
if((RESP instanceof TarsusError || RESP instanceof Error)){
(RESP as TarsusError).iCode = (RESP as TarsusError).iCode || -19;
(RESP as TarsusError).iMessage = (RESP as TarsusError).iMessage || 'uncaught error'
}
res.send(RESP)
},
complete(){
const iDATA = RxDone();
const iRESP = _.get(context.metadata,'__rx__resp__')
res.send(Object.assign({},iDATA,iRESP))
}
});
最后到@UsePipe装饰器上,我们只需要在meta中定义有这么一个Pipe管道就行了。
同理换成其他的,UseInterceptor,CheckStatus,CheckRole,之类的也会很好的处理。
ts
const UsePipe = (tarsuPipe:TarsusPipe) =>{
return function (value:any,context:ClassMethodDecoratorContext){
_.set(context.metadata,"__rx__pipe__",tarsuPipe)
}
}