feat: test-rpc-service

This commit is contained in:
手瓜一十雪
2025-06-14 17:42:31 +08:00
parent f576cd9417
commit 88e9caddfa
7 changed files with 157 additions and 21 deletions

View File

@@ -10,9 +10,9 @@ interface InternalMapKey {
checker: ((...args: any[]) => boolean) | undefined; checker: ((...args: any[]) => boolean) | undefined;
} }
type EnsureFunc<T> = T extends (...args: any) => any ? T : never; export type EnsureFunc<T> = T extends (...args: any) => any ? T : never;
type FuncKeys<T> = Extract< export type FuncKeys<T> = Extract<
{ {
[K in keyof T]: EnsureFunc<T[K]> extends never ? never : K; [K in keyof T]: EnsureFunc<T[K]> extends never ? never : K;
}[keyof T], }[keyof T],

View File

@@ -20,3 +20,23 @@ export function proxyHandlerOf(logger: LogWrapper) {
export function proxiedListenerOf<T extends object>(listener: T, logger: LogWrapper) { export function proxiedListenerOf<T extends object>(listener: T, logger: LogWrapper) {
return new Proxy<T>(listener, proxyHandlerOf(logger)); return new Proxy<T>(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<T extends object>(listener: T) {
return new Proxy<T>(listener, proxyHandlerOfWithoutLogger());
}

View File

@@ -30,6 +30,7 @@ import os from 'node:os';
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners'; import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners';
import { proxiedListenerOf } from '@/common/proxy-handler'; import { proxiedListenerOf } from '@/common/proxy-handler';
import { NTQQPacketApi } from './apis/packet'; import { NTQQPacketApi } from './apis/packet';
import { createVirtualServiceClient, handleServiceServerOnce } from '@/framework/proxy/service';
export * from './wrapper'; export * from './wrapper';
export * from './types'; export * from './types';
export * from './services'; export * from './services';
@@ -99,7 +100,7 @@ export class NapCatCore {
this.context = context; this.context = context;
this.util = this.context.wrapper.NodeQQNTWrapperUtil; this.util = this.context.wrapper.NodeQQNTWrapperUtil;
this.eventWrapper = new NTEventWrapper(context.session); 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 = { this.apis = {
FileApi: new NTQQFileApi(this.context, this), FileApi: new NTQQFileApi(this.context, this),
SystemApi: new NTQQSystemApi(this.context, this), SystemApi: new NTQQSystemApi(this.context, this),
@@ -168,6 +169,13 @@ export class NapCatCore {
proxiedListenerOf(msgListener, this.context.logger), 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(); const profileListener = new NodeIKernelProfileListener();
profileListener.onProfileDetailInfoChanged = (profile) => { profileListener.onProfileDetailInfoChanged = (profile) => {
if (profile.uid === this.selfInfo.uid) { if (profile.uid === this.selfInfo.uid) {

View File

@@ -1,71 +1,71 @@
import { User, UserDetailInfoListenerArg } from '@/core/types'; import { SelfStatusInfo, User, UserDetailInfoListenerArg } from '@/core/types';
export class NodeIKernelProfileListener { 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 {
} }
} }

View File

@@ -36,8 +36,10 @@ import type {
NodeIKernelTicketService, NodeIKernelTicketService,
NodeIKernelTipOffService, NodeIKernelTipOffService,
} from '.'; } from '.';
import { NodeIKernelAlbumService } from './NodeIKernelAlbumService';
export type ServiceNamingMapping = { export type ServiceNamingMapping = {
NodeIKernelAlbumService: NodeIKernelAlbumService;
NodeIKernelAvatarService: NodeIKernelAvatarService; NodeIKernelAvatarService: NodeIKernelAvatarService;
NodeIKernelBuddyService: NodeIKernelBuddyService; NodeIKernelBuddyService: NodeIKernelBuddyService;
NodeIKernelFileAssistantService: NodeIKernelFileAssistantService; NodeIKernelFileAssistantService: NodeIKernelFileAssistantService;

View File

@@ -0,0 +1,106 @@
import { FuncKeys, NTEventWrapper } from "@/common/event";
import { ServiceNamingMapping } from "@/core";
type ServiceMethodCommand = {
[Service in keyof ServiceNamingMapping]: `${Service}/${FuncKeys<ServiceNamingMapping[Service]>}`
}[keyof ServiceNamingMapping];
export const RegisterListenerCmd: Array<ServiceMethodCommand> = [
'NodeIKernelMsgService/addKernelMsgListener',
'NodeIKernelGroupService/addKernelGroupListener',
'NodeIKernelProfileLikeService/addKernelProfileLikeListener',
'NodeIKernelProfileService/addKernelProfileListener',
'NodeIKernelBuddyService/addKernelBuddyListener',
];
export function createVirtualServiceServer<T extends keyof ServiceNamingMapping>(
serviceName: T,
ntevent: NTEventWrapper,
callback: (command: ServiceMethodCommand, ...args: any[]) => Promise<any>
): 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<ServiceMethodCommand, boolean>();
export const clientCallback = new Map<string, (command: string, ...args: any[]) => Promise<any>>();
export async function handleServiceServerOnce(
command: ServiceMethodCommand,// 服务注册命令
recvListener: (command: string, ...args: any[]) => Promise<any>,//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<any>)(command, ...args);
}
export function createVirtualServiceClient<T extends keyof ServiceNamingMapping>(
serviceName: T,
receiverEvent: (command: ServiceMethodCommand, ...args: any[]) => Promise<any>
) {
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<string, any>) => {
// 遍历 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();
}

View File