diff --git a/server/packages/sdk/src/const.ts b/server/packages/sdk/src/const.ts index a6ccd26b..a8e70f27 100644 --- a/server/packages/sdk/src/const.ts +++ b/server/packages/sdk/src/const.ts @@ -2,3 +2,8 @@ * 系统用户id */ export const SYSTEM_USERID = '000000000000000000000000'; + +/** + * 配置项 + */ +export const CONFIG_GATEWAY_AFTER_HOOK = '$gatewayAfterHooks'; diff --git a/server/packages/sdk/src/services/base.ts b/server/packages/sdk/src/services/base.ts index 4078ca12..44434012 100644 --- a/server/packages/sdk/src/services/base.ts +++ b/server/packages/sdk/src/services/base.ts @@ -18,6 +18,8 @@ import type { TFunction } from 'i18next'; import { t } from './lib/i18n'; import type { ValidationRuleObject } from 'fastest-validator'; import type { BuiltinEventMap } from '../structs/events'; +import { CONFIG_GATEWAY_AFTER_HOOK } from '../const'; +import _ from 'lodash'; type ServiceActionHandler = ( ctx: TcPureContext @@ -68,6 +70,12 @@ interface TcServiceBroker extends ServiceBroker { groups?: string | string[] ): Promise; emit(eventName: string): Promise; + broadcast( + eventName: K, + data: K extends keyof BuiltinEventMap ? BuiltinEventMap[K] : unknown, + groups?: string | string[] + ): Promise; + broadcast(eventName: string): Promise; } /** @@ -87,6 +95,11 @@ export abstract class TcService extends Service { private _settings: ServiceSchema['settings'] = {}; private _events: ServiceSchema['events'] = {}; + /** + * 全局的配置中心 + */ + public globalConfig: Record = {}; + private _generateAndParseSchema() { this.parseServiceSchema({ name: this.serviceName, @@ -104,6 +117,8 @@ export abstract class TcService extends Service { this.onInit(); // 初始化服务 + this.initBuiltin(); // 初始化内部服务 + this._generateAndParseSchema(); this.logger = this.buildLoggerWithPrefix(this.logger); @@ -119,6 +134,17 @@ export abstract class TcService extends Service { protected async onStop() {} + protected initBuiltin() { + this.registerEventListener('config.updated', (payload) => { + this.logger.info('Update global config with:', payload.config); + if (payload.config) { + this.globalConfig = { + ...payload.config, + }; + } + }); + } + registerMixin(mixin: Partial): void { this._mixins.push(mixin); } @@ -257,6 +283,29 @@ export abstract class TcService extends Service { return super.waitForServices(serviceNames, timeout, interval, logger); } + getGlobalConfig(key: string): any { + return _.get(this.globalConfig, key); + } + + /** + * 设置全局配置信息 + */ + async setGlobalConfig(key: string, value: any): Promise { + await this.waitForServices('config'); + await this.broker.call('config.set', { + key, + value, + }); + } + + async registryAfterActionHook(actionName: string, callbackAction: string) { + await this.waitForServices(['gateway', 'config']); + await this.broker.call('config.addToSet', { + key: `${CONFIG_GATEWAY_AFTER_HOOK}.${actionName}`, + value: `${this.serviceName}.${callbackAction}`, + }); + } + /** * 清理action缓存 * NOTICE: 这里使用Redis作为缓存管理器,因此不需要通知所有的service diff --git a/server/packages/sdk/src/structs/events.ts b/server/packages/sdk/src/structs/events.ts index e1ec3b22..9134b99f 100644 --- a/server/packages/sdk/src/structs/events.ts +++ b/server/packages/sdk/src/structs/events.ts @@ -21,4 +21,5 @@ export interface BuiltinEventMap { messageId: string; meta: MessageMetaStruct; }; + 'config.updated': { config: Record }; } diff --git a/server/services/core/config.service.ts b/server/services/core/config.service.ts index 05afa43a..0a3e789c 100644 --- a/server/services/core/config.service.ts +++ b/server/services/core/config.service.ts @@ -1,10 +1,11 @@ +import _ from 'lodash'; import { TcService, TcPureContext, config } from 'tailchat-server-sdk'; /** * 配置服务器 */ class ConfigService extends TcService { - config = {}; + config = {}; // 自管理的配置项,globalConfig是同步过来的 get serviceName(): string { return 'config'; @@ -33,6 +34,13 @@ class ConfigService extends TcService { value: 'any', }, }); + this.registerAction('addToSet', this.addToSet, { + visibility: 'public', + params: { + key: 'string', + value: 'any', + }, + }); this.registerAuthWhitelist(['/config/client']); } @@ -56,11 +64,23 @@ class ConfigService extends TcService { return this.config[ctx.params.key] ?? null; } - async set(ctx: TcPureContext<{ key: string; value: string }>) { + async set(ctx: TcPureContext<{ key: string; value: any }>) { const { key, value } = ctx.params; - this.config[key] = value; - await this.broker.broadcast('config.updated', this.config); + _.set(this.config, key, value); + await this.broker.broadcast('config.updated', { config: this.config }); + } + + /** + * 添加到设置但不重复 + */ + async addToSet(ctx: TcPureContext<{ key: string; value: any }>) { + const { key, value } = ctx.params; + + const originConfig = _.get(this.config, key) ?? []; + _.set(this.config, key, _.uniq([...originConfig, value])); + + await this.broker.broadcast('config.updated', { config: this.config }); } } diff --git a/server/services/core/gateway.service.ts b/server/services/core/gateway.service.ts index 10db1e1f..b5b383b3 100644 --- a/server/services/core/gateway.service.ts +++ b/server/services/core/gateway.service.ts @@ -10,6 +10,7 @@ import { parseLanguageFromHead, builtinAuthWhitelist, PureContext, + CONFIG_GATEWAY_AFTER_HOOK, } from 'tailchat-server-sdk'; import { TcHealth } from '../../mixins/health.mixin'; import type { Readable } from 'stream'; @@ -169,6 +170,20 @@ export default class ApiService extends TcService { // Async function which return with Promise res.setHeader('X-Node-ID', ctx.nodeID); + if (ctx.action.name) { + const afterHooks = this.getGlobalConfig( + `${CONFIG_GATEWAY_AFTER_HOOK}.${ctx.action.name}` + ); // TODO: 这里actionName可能不对。需要调试 + if (Array.isArray(afterHooks) && afterHooks.length > 0) { + for (const action of afterHooks) { + this.broker.call(String(action), { + route, + data, + }); + } + } + } + if (data && data['__raw']) { if (data['header']) { Object.entries(data['header']).forEach(([key, value]) => {