From 88e9caddfa4f9c5e3a44f955b59cc3fd731a070a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Sat, 14 Jun 2025 17:42:31 +0800 Subject: [PATCH] feat: test-rpc-service --- src/common/event.ts | 4 +- src/common/proxy-handler.ts | 20 ++++ src/core/index.ts | 10 +- .../listeners/NodeIKernelProfileListener.ts | 36 +++--- src/core/services/index.ts | 2 + src/framework/proxy/service.ts | 106 ++++++++++++++++++ src/framework/proxy/session.ts | 0 7 files changed, 157 insertions(+), 21 deletions(-) create mode 100644 src/framework/proxy/service.ts create mode 100644 src/framework/proxy/session.ts diff --git a/src/common/event.ts b/src/common/event.ts index ca0e2405..52812123 100644 --- a/src/common/event.ts +++ b/src/common/event.ts @@ -10,9 +10,9 @@ interface InternalMapKey { checker: ((...args: any[]) => boolean) | undefined; } -type EnsureFunc = T extends (...args: any) => any ? T : never; +export type EnsureFunc = T extends (...args: any) => any ? T : never; -type FuncKeys = Extract< +export type FuncKeys = Extract< { [K in keyof T]: EnsureFunc extends never ? never : K; }[keyof T], diff --git a/src/common/proxy-handler.ts b/src/common/proxy-handler.ts index 3bcaf1a3..c18aa89d 100644 --- a/src/common/proxy-handler.ts +++ b/src/common/proxy-handler.ts @@ -20,3 +20,23 @@ export function proxyHandlerOf(logger: LogWrapper) { export function proxiedListenerOf(listener: T, logger: LogWrapper) { return new Proxy(listener, proxyHandlerOf(logger)); } + +export function proxyHandlerOfWithoutLogger() { + return { + get(target: any, prop: any, receiver: any) { + if (typeof target[prop] === 'undefined') { + // 如果方法不存在,返回一个函数,这个函数调用existentMethod + // eslint-disable-next-line @typescript-eslint/no-unused-vars + return (..._args: unknown[]) => { + console.log(`${target.constructor.name} has no method ${prop}`); + }; + } + // 如果方法存在,正常返回 + return Reflect.get(target, prop, receiver); + }, + }; +} + +export function proxiedListenerOfWithoutLogger(listener: T) { + return new Proxy(listener, proxyHandlerOfWithoutLogger()); +} \ No newline at end of file diff --git a/src/core/index.ts b/src/core/index.ts index dff28680..91744716 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -30,6 +30,7 @@ import os from 'node:os'; import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners'; import { proxiedListenerOf } from '@/common/proxy-handler'; import { NTQQPacketApi } from './apis/packet'; +import { createVirtualServiceClient, handleServiceServerOnce } from '@/framework/proxy/service'; export * from './wrapper'; export * from './types'; export * from './services'; @@ -99,7 +100,7 @@ export class NapCatCore { this.context = context; this.util = this.context.wrapper.NodeQQNTWrapperUtil; this.eventWrapper = new NTEventWrapper(context.session); - this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath,NapcatConfigSchema); + this.configLoader = new NapCatConfigLoader(this, this.context.pathWrapper.configPath, NapcatConfigSchema); this.apis = { FileApi: new NTQQFileApi(this.context, this), SystemApi: new NTQQSystemApi(this.context, this), @@ -168,6 +169,13 @@ export class NapCatCore { proxiedListenerOf(msgListener, this.context.logger), ); + let msgServiceClient = createVirtualServiceClient('NodeIKernelMsgService', async (ServiceCommand, ...args) => { + this.context.logger.log(`Client Outing->[${ServiceCommand}]`, ...args); + return handleServiceServerOnce(ServiceCommand, async (listenerCommand: string, ...args: any[]) => { + msgServiceClient.receiverListener(listenerCommand, ...args); + }, this.eventWrapper, ...args); + }); + console.log('msgServiceClient', await msgServiceClient.object.fetchFavEmojiList('', 50, true, true)); const profileListener = new NodeIKernelProfileListener(); profileListener.onProfileDetailInfoChanged = (profile) => { if (profile.uid === this.selfInfo.uid) { diff --git a/src/core/listeners/NodeIKernelProfileListener.ts b/src/core/listeners/NodeIKernelProfileListener.ts index 5ba11be0..18c08237 100644 --- a/src/core/listeners/NodeIKernelProfileListener.ts +++ b/src/core/listeners/NodeIKernelProfileListener.ts @@ -1,71 +1,71 @@ -import { User, UserDetailInfoListenerArg } from '@/core/types'; +import { SelfStatusInfo, User, UserDetailInfoListenerArg } from '@/core/types'; export class NodeIKernelProfileListener { - onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void { + onUserDetailInfoChanged(_arg: UserDetailInfoListenerArg): void { } - onProfileSimpleChanged(...args: unknown[]): any { + onProfileSimpleChanged(..._args: unknown[]): any { } - onProfileDetailInfoChanged(profile: User): any { + onProfileDetailInfoChanged(_profile: User): any { } - onStatusUpdate(...args: unknown[]): any { + onStatusUpdate(..._args: unknown[]): any { } - onSelfStatusChanged(...args: unknown[]): any { + onSelfStatusChanged(_info: SelfStatusInfo): any { } - onStrangerRemarkChanged(...args: unknown[]): any { + onStrangerRemarkChanged(..._args: unknown[]): any { } - onMemberListChange(...args: unknown[]): any { + onMemberListChange(..._args: unknown[]): any { } - onMemberInfoChange(...args: unknown[]): any { + onMemberInfoChange(..._args: unknown[]): any { } - onGroupListUpdate(...args: unknown[]): any { + onGroupListUpdate(..._args: unknown[]): any { } - onGroupAllInfoChange(...args: unknown[]): any { + onGroupAllInfoChange(..._args: unknown[]): any { } - onGroupDetailInfoChange(...args: unknown[]): any { + onGroupDetailInfoChange(..._args: unknown[]): any { } - onGroupConfMemberChange(...args: unknown[]): any { + onGroupConfMemberChange(..._args: unknown[]): any { } - onGroupExtListUpdate(...args: unknown[]): any { + onGroupExtListUpdate(..._args: unknown[]): any { } - onGroupNotifiesUpdated(...args: unknown[]): any { + onGroupNotifiesUpdated(..._args: unknown[]): any { } - onGroupNotifiesUnreadCountUpdated(...args: unknown[]): any { + onGroupNotifiesUnreadCountUpdated(..._args: unknown[]): any { } - onGroupMemberLevelInfoChange(...args: unknown[]): any { + onGroupMemberLevelInfoChange(..._args: unknown[]): any { } - onGroupBulletinChange(...args: unknown[]): any { + onGroupBulletinChange(..._args: unknown[]): any { } } diff --git a/src/core/services/index.ts b/src/core/services/index.ts index a36de20f..463b5d87 100644 --- a/src/core/services/index.ts +++ b/src/core/services/index.ts @@ -36,8 +36,10 @@ import type { NodeIKernelTicketService, NodeIKernelTipOffService, } from '.'; +import { NodeIKernelAlbumService } from './NodeIKernelAlbumService'; export type ServiceNamingMapping = { + NodeIKernelAlbumService: NodeIKernelAlbumService; NodeIKernelAvatarService: NodeIKernelAvatarService; NodeIKernelBuddyService: NodeIKernelBuddyService; NodeIKernelFileAssistantService: NodeIKernelFileAssistantService; diff --git a/src/framework/proxy/service.ts b/src/framework/proxy/service.ts new file mode 100644 index 00000000..ee55cbdd --- /dev/null +++ b/src/framework/proxy/service.ts @@ -0,0 +1,106 @@ +import { FuncKeys, NTEventWrapper } from "@/common/event"; +import { ServiceNamingMapping } from "@/core"; + +type ServiceMethodCommand = { + [Service in keyof ServiceNamingMapping]: `${Service}/${FuncKeys}` +}[keyof ServiceNamingMapping]; + +export const RegisterListenerCmd: Array = [ + 'NodeIKernelMsgService/addKernelMsgListener', + 'NodeIKernelGroupService/addKernelGroupListener', + 'NodeIKernelProfileLikeService/addKernelProfileLikeListener', + 'NodeIKernelProfileService/addKernelProfileListener', + 'NodeIKernelBuddyService/addKernelBuddyListener', +]; + +export function createVirtualServiceServer( + serviceName: T, + ntevent: NTEventWrapper, + callback: (command: ServiceMethodCommand, ...args: any[]) => Promise +): ServiceNamingMapping[T] { + return new Proxy(() => { }, { + get: (_target: any, functionName: string) => { + const command = `${serviceName}/${functionName}` as ServiceMethodCommand; + if (RegisterListenerCmd.includes(command as ServiceMethodCommand)) { + return async (..._args: any[]) => { + const listener = new Proxy(new class { }(), { + apply: (_target, _thisArg, _arguments) => { + return callback(command, ..._arguments); + } + }); + return await (ntevent.callNoListenerEvent as any)(command, listener); + }; + } + return async (...args: any[]) => { + return await (ntevent.callNoListenerEvent as any)(command, ...args); + }; + } + }); +} + +// 问题2: 全局状态管理可能导致内存泄漏和状态污染 +export const listenerCmdRegisted = new Map(); +export const clientCallback = new Map Promise>(); +export async function handleServiceServerOnce( + command: ServiceMethodCommand,// 服务注册命令 + recvListener: (command: string, ...args: any[]) => Promise,//listener监听器 + ntevent: NTEventWrapper,// 事件处理器 + ...args: any[]//实际参数 +) { + if (RegisterListenerCmd.includes(command)) { + if (!listenerCmdRegisted.has(command)) { + listenerCmdRegisted.set(command, true); + return (ntevent.callNoListenerEvent as any)(command, new Proxy(new class { }(), { + get: (_target: any, prop: string) => { + return async (..._args: any[]) => { + let listenerCmd = `${command.split('/')[0]}/${prop}`; + recvListener(listenerCmd, ..._args); + }; + } + })); + } + return 0; + } + console.log('handleServiceServerOnce', command, 'args', args); + console.log('params', args); + return await (ntevent.callNoListenerEvent as (command: ServiceMethodCommand, ...args: any[]) => Promise)(command, ...args); +} + +export function createVirtualServiceClient( + serviceName: T, + receiverEvent: (command: ServiceMethodCommand, ...args: any[]) => Promise +) { + const object = new Proxy(() => { }, { + get: (_target: any, functionName: string) => { + const command = `${serviceName}/${functionName}` as ServiceMethodCommand; + if (RegisterListenerCmd.includes(command as ServiceMethodCommand)) { + if (!clientCallback.has(command)) { + return async (listener: Record) => { + // 遍历 listener + for (const key in listener) { + if (typeof listener[key] === 'function') { + const listenerCmd = `${command.split('/')[0]}/${key}`; + clientCallback.set(listenerCmd, listener[key].bind(listener)); + } + } + return await receiverEvent(command); + }; + } + } + return async (...args: any[]) => { + return await receiverEvent(command, ...args); + }; + } + }); + + const receiverListener = function (command: string, ...args: any[]) { + return clientCallback.get(command)?.(command, ...args); + }; + return { receiverListener: receiverListener, object: object as ServiceNamingMapping[T] }; +} + +// 建议添加清理函数 +export function clearServiceState() { + listenerCmdRegisted.clear(); + clientCallback.clear(); +} \ No newline at end of file diff --git a/src/framework/proxy/session.ts b/src/framework/proxy/session.ts new file mode 100644 index 00000000..e69de29b