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;
}
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;
}[keyof T],

View File

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

View File

@@ -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 {
}
}

View File

@@ -36,8 +36,10 @@ import type {
NodeIKernelTicketService,
NodeIKernelTipOffService,
} from '.';
import { NodeIKernelAlbumService } from './NodeIKernelAlbumService';
export type ServiceNamingMapping = {
NodeIKernelAlbumService: NodeIKernelAlbumService;
NodeIKernelAvatarService: NodeIKernelAvatarService;
NodeIKernelBuddyService: NodeIKernelBuddyService;
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