import { Application, Component, Configurable, Exception, Inject, InvalidMethodAcceptException, InvalidMethodReturnException, timeout } from '@lakutata/core' import {Logger} from '@lakutata/core/build/plugins/Logger' import {glob} from 'glob' import {TPattern} from '../../types/TPattern' import {CONTROLLER_PATTERN_MAP, IS_CONTROLLER} from '../../constants/MetadataKey' import {TInvokePattern} from '../../types/TInvokePattern' import {BaseController} from '../../lib/BaseController' import {IPatRun} from '../../interfaces/IPatRun' import {ServiceInvokeTimeoutException} from '../../exceptions/ServiceInvokeTimeoutException' import {Service} from '../../Service' import {ClientComponent} from './ClientComponent' import {SERVICE_INVOKE_EVENT} from '../../constants/SocketConstant' import {PatternNotFoundException} from '../../exceptions/PatternNotFoundException' import {format} from 'util' export class ProviderComponent extends Component { @Inject(Application) protected readonly app: Application @Inject('client') protected client: ClientComponent @Configurable() protected readonly service: Service @Configurable() protected readonly controllers: () => string[] @Configurable() protected readonly logger: () => Logger | undefined protected readonly patternManager: IPatRun = require('patrun')() protected readonly servicePatterns: TPattern[] = [] protected tasks: number = 0 /** * 初始化函数 * @protected */ protected async initialize(): Promise { await this.loadControllers(this.controllers()) this.client.onHttpRequest(SERVICE_INVOKE_EVENT, async (inp: string) => await this.invokeHandler(inp)) this.client.onSocketRequest(SERVICE_INVOKE_EVENT, async (inp: string) => await this.invokeHandler(inp)) } /** * 处理方法远程调用 * @param inp * @protected */ protected async invokeHandler(inp: string): Promise { this.tasks += 1 const startInvokeAt: number = Date.now() const packet: TInvokePattern = this.app.JSON.parse(inp) packet.chain.push(this.app.getID()) const object: TPattern = packet.object const fn = this.patternManager.find(object) if (fn) { try { packet.object = await fn(packet, object) } catch (e) { packet.error = e as Error let retryableError: boolean = true const unRetryableExceptionConstructors = [InvalidMethodAcceptException, InvalidMethodReturnException] unRetryableExceptionConstructors.forEach(exceptionConstructor => { if (e instanceof exceptionConstructor) { retryableError = false } }) packet.retryableError = retryableError } } else { packet.error = this.generateException(PatternNotFoundException, format(object)) packet.retryableError = false } packet.timeSpent = Date.now() - startInvokeAt packet.totalTimeSpent = packet.totalTimeSpent + packet.timeSpent this.tasks -= 1 return this.app.JSON.stringify(packet) } /** * 通过Glob加载服务控制器 * @param globStr * @protected */ protected async loadControllersByGlob(globStr: string): Promise { return new Promise((resolve, reject) => { glob(globStr, (err, files) => { if (err) { reject(err) } else { files.forEach(file => { import(file).then(exports => { for (let key in exports) { if (exports.hasOwnProperty(key) && typeof exports[key] === 'function') { const controllerConstructor = exports[key] if (Reflect.getMetadata(IS_CONTROLLER, controllerConstructor)) { this.service.bind(controllerConstructor, { logger: this.logger() }) const patternMap: Map = Reflect.getMetadata(CONTROLLER_PATTERN_MAP, controllerConstructor) patternMap.forEach((pattern: TPattern, funcName: string) => { this.patternManager.add(pattern, async (packet: TInvokePattern, inp?: { [key: string]: any }) => { const startHandleAt: number = Date.now() const controller: BaseController = this.service.get(controllerConstructor) let error: Error | Exception | undefined = undefined let timeSpent: number const invokeName: string = `${controllerConstructor.name}.${funcName}` const loggerFunction = function (this: BaseController) { const invokeInfo = { timeout: packet.timeout, retry: packet.retry } if (error) { let errorMessage: string if (error instanceof Exception) { errorMessage = `${error.err}:${error.errMsg}` } else { errorMessage = `${error.name}:${error.message}` } this.logger?.error(packet.id, '[FAILED]', invokeName, inp, errorMessage, `${timeSpent}ms`, packet.chain, invokeInfo) } else { this.logger?.info(packet.id, '[SUCCESS]', invokeName, inp, `${timeSpent}ms`, packet.chain, invokeInfo) } } try { const result = await timeout(new Promise((timeoutResolve, timeoutReject) => { controller[funcName](inp).then(timeoutResolve).catch(timeoutReject) }), packet.timeout, async () => { throw this.generateException(ServiceInvokeTimeoutException, invokeName, packet.timeout) }) timeSpent = Date.now() - startHandleAt loggerFunction.call(controller) return result } catch (e) { error = e as Error timeSpent = Date.now() - startHandleAt loggerFunction.call(controller) throw e } }) this.servicePatterns.push(pattern) }) } } } }).catch(importError => { reject(importError) }) }) resolve() } }) }) } /** * 加载服务控制器 * @param controllerDirectory * @protected */ protected async loadControllers(controllerDirectory: string | string[]): Promise { if (!controllerDirectory) return const controllerDirectories: string[] = Array.isArray(controllerDirectory) ? controllerDirectory : [controllerDirectory] const loadControllerPromises: Promise[] = [] controllerDirectories.forEach(globStr => { loadControllerPromises.push(new Promise((resolve, reject) => { this.loadControllersByGlob(globStr).then(resolve).catch(reject) })) }) await Promise.all(loadControllerPromises) } /** * 获取服务Patterns */ public getPatterns(): TPattern[] { return this.servicePatterns } /** * 获取当前正在执行的任务数 */ public getTasks(): number { return this.tasks } }