[Typescript]用装饰器封装Express服务(6)-Websocket服务实现

79 阅读1分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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服务