开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情
设计
在加载express路由中间件之前,就需要加载Websocket服务,否则,可能会使Websocket端点不可用。
这里,我们设计一个类装饰器@WsService,以标记此类包含Ws端点路由服务。
@WsService
然后,设计方法装饰器@EndPoint,用以标记,ws服务的端点处理函数
@EndPoint
在扫描组件环节,实例化服务类,并添加到缓存map中,并在完成ioc注入环节后,对map依次读取并用express-ws进行加载
实现
首先,定义相关的装饰器:
1.类装饰器
export const WsService_METADATA = 'WsService';
export const WsService = (path: string = '', componentName?: string): ClassDecorator => {
return (constructor: any) => {
let id = getTargetId(constructor)
if (!componentName) {
componentName = constructor.name + '_' + id
}
Reflect.defineMetadata(WsService_METADATA, path, constructor);
log(`[WsService]- add WsService: ${constructor.name}`,)
let instance = new constructor();
application.WebsocketManager.addWsControllers(componentName, instance)
application.componentManager.addBean(componentName, constructor, instance)
};
}
2.方法装饰器
export const EndPoint = (path = '/'): MethodDecorator => {
return (target: object, name: string, descriptor: any) => {
// target:当前类实例,name:当前函数名,descriptor:当前属性(函数)的描述符
Reflect.defineMetadata(
'info',
{ type: 'ws', path },
descriptor.value,
);
}
}
接着,定义一个manager类,用以管理ws服务相关的处理方法
import { WsService_METADATA } from "./WsService";
export class WebsocketManager {
wsControllers?: Map<string, ComponentInfo> = new Map(); // controller 结合
public async addWsControllers(name: string, con: any) {
this.wsControllers.set(name, con)
}
public LoadWsController(app): void {
log(`========================= Load WsController========================`)
this.registerWs(app)
}
public registerWs(
app: any,
) {
let wsApp = expressWS(app).app;
let wsArr = []
this.wsControllers.forEach((instance: any, key: string, map: Map<string, any>) => {
// 获取Controller注解的入参--路径
const controllerRootPath: string = Reflect.getMetadata(
WsService_METADATA,
instance.constructor,
);
log(`[registerWs]-WsService: ${controllerRootPath}`)
// 实例属性
const proto = Object.getPrototypeOf(instance);
// 方法数组
const functionNameArr = Object.getOwnPropertyNames(proto).filter(
n => n !== 'constructor' && typeof proto[n] === 'function',
);
functionNameArr.forEach(functionName => {
const routeMetadata: WsRouteType = Reflect.getMetadata(
'info',
proto[functionName],
);
if (!routeMetadata) return;
const { type, path } = routeMetadata;
log(`[registerWs]-load ${type.toUpperCase()}:${path}`)
wsArr[wsArr.length] = {
path: controllerRootPath + path, handler: this.createWsHandler(instance,
functionName)
};
});
});
for (let i = 0; i < wsArr.length; i++) {
let ws = wsArr[i];
wsApp.ws(ws.path, ws.handler)
}
}
createWsHandler(instance,
functionName,): any {
return async (ws, req, next) => {
try {
await instance[functionName](ws, req, next)
} catch (e) {
console.error(e)
}
}
}
}
经manager类实例化并挂载到application上下文中,并调用LoadWsController方法,以加载ws服务