Add service handler registration and DI support

Introduces dependency injection via Inversify and reflect-metadata, adds a service handler registry for packet handling, and updates core initialization to auto-register and bind service handlers. Also updates Vite configs and auto-include logic to support protocol service files.
This commit is contained in:
手瓜一十雪 2025-11-14 22:20:33 +08:00
parent a2a73ce2dd
commit f04ffa5dc6
8 changed files with 62 additions and 13 deletions

View File

@ -14,6 +14,8 @@
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.3", "@rollup/plugin-node-resolve": "^16.0.3",
"@vitejs/plugin-react-swc": "^4.2.2", "@vitejs/plugin-react-swc": "^4.2.2",
"inversify": "^7.10.4",
"reflect-metadata": "^0.2.2",
"typescript": "^5.3.0", "typescript": "^5.3.0",
"vite": "^6.4.1", "vite": "^6.4.1",
"vite-plugin-cp": "^6.0.3" "vite-plugin-cp": "^6.0.3"

View File

@ -25,18 +25,18 @@ export class NTEventWrapper {
private readonly listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); // ListenerName-Unique -> Listener实例 private readonly listenerManager: Map<string, ListenerClassBase> = new Map<string, ListenerClassBase>(); // ListenerName-Unique -> Listener实例
private readonly EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); // tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func} private readonly EventTask = new Map<string, Map<string, Map<string, InternalMapKey>>>(); // tasks ListenerMainName -> ListenerSubName-> uuid -> {timeout,createtime,func}
constructor ( constructor(
wrapperSession: NodeIQQNTWrapperSession wrapperSession: NodeIQQNTWrapperSession
) { ) {
this.WrapperSession = wrapperSession; this.WrapperSession = wrapperSession;
} }
createProxyDispatch (ListenerMainName: string) { createProxyDispatch(ListenerMainName: string) {
const dispatcherListenerFunc = this.dispatcherListener.bind(this); const dispatcherListenerFunc = this.dispatcherListener.bind(this);
return new Proxy( return new Proxy(
{}, {},
{ {
get (target: any, prop: any, receiver: any) { get(target: any, prop: any, receiver: any) {
if (typeof target[prop] === 'undefined') { if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod // 如果方法不存在返回一个函数这个函数调用existentMethod
return (...args: any[]) => { return (...args: any[]) => {
@ -54,7 +54,7 @@ export class NTEventWrapper {
Service extends keyof ServiceNamingMapping, Service extends keyof ServiceNamingMapping,
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>, ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]> T extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>
> (eventName: `${Service}/${ServiceMethod}`): T | undefined { >(eventName: `${Service}/${ServiceMethod}`): T | undefined {
const eventNameArr = eventName.split('/'); const eventNameArr = eventName.split('/');
type eventType = { type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>>; }; [key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>>; };
@ -79,7 +79,7 @@ export class NTEventWrapper {
} }
createListenerFunction<T, Listener extends keyof ListenerNamingMapping, createListenerFunction<T, Listener extends keyof ListenerNamingMapping,
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>> (listenerMainName: string, uniqueCode: string = ''): T { ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>>(listenerMainName: string, uniqueCode: string = ''): T {
const existListener = this.listenerManager.get(listenerMainName + uniqueCode); const existListener = this.listenerManager.get(listenerMainName + uniqueCode);
if (!existListener) { if (!existListener) {
const Listener = this.createProxyDispatch(listenerMainName); const Listener = this.createProxyDispatch(listenerMainName);
@ -97,7 +97,7 @@ export class NTEventWrapper {
} }
// 统一回调清理事件 // 统一回调清理事件
async dispatcherListener (ListenerMainName: string, ListenerSubName: string, ...args: any[]) { async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
this.EventTask.get(ListenerMainName) this.EventTask.get(ListenerMainName)
?.get(ListenerSubName) ?.get(ListenerSubName)
?.forEach((task, uuid) => { ?.forEach((task, uuid) => {
@ -115,7 +115,7 @@ export class NTEventWrapper {
Service extends keyof ServiceNamingMapping, Service extends keyof ServiceNamingMapping,
ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>, ServiceMethod extends FuncKeys<ServiceNamingMapping[Service]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]> EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>
> ( >(
serviceAndMethod: `${Service}/${ServiceMethod}`, serviceAndMethod: `${Service}/${ServiceMethod}`,
...args: Parameters<EventType> ...args: Parameters<EventType>
): Promise<Awaited<ReturnType<EventType>>> { ): Promise<Awaited<ReturnType<EventType>>> {
@ -126,7 +126,7 @@ export class NTEventWrapper {
Listener extends keyof ListenerNamingMapping, Listener extends keyof ListenerNamingMapping,
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>, ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]> ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
> ( >(
listenerAndMethod: `${Listener}/${ListenerMethod}`, listenerAndMethod: `${Listener}/${ListenerMethod}`,
checker: (...args: Parameters<ListenerType>) => boolean, checker: (...args: Parameters<ListenerType>) => boolean,
waitTimes = 1, waitTimes = 1,
@ -140,7 +140,7 @@ export class NTEventWrapper {
let complete = 0; let complete = 0;
let retData: Parameters<ListenerType> | undefined; let retData: Parameters<ListenerType> | undefined;
function sendDataCallback () { function sendDataCallback() {
if (complete === 0) { if (complete === 0) {
reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout')); reject(new Error(' ListenerName:' + listenerAndMethod + ' timeout'));
} else { } else {
@ -180,7 +180,7 @@ export class NTEventWrapper {
ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>, ListenerMethod extends FuncKeys<ListenerNamingMapping[Listener]>,
EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>, EventType extends (...args: any) => any = EnsureFunc<ServiceNamingMapping[Service][ServiceMethod]>,
ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]> ListenerType extends (...args: any) => any = EnsureFunc<ListenerNamingMapping[Listener][ListenerMethod]>
> ( >(
serviceAndMethod: `${Service}/${ServiceMethod}`, serviceAndMethod: `${Service}/${ServiceMethod}`,
listenerAndMethod: `${Listener}/${ListenerMethod}`, listenerAndMethod: `${Listener}/${ListenerMethod}`,
args: Parameters<EventType>, args: Parameters<EventType>,
@ -194,7 +194,7 @@ export class NTEventWrapper {
let retData: Parameters<ListenerType> | undefined; let retData: Parameters<ListenerType> | undefined;
let retEvent: any = {}; let retEvent: any = {};
function sendDataCallback (resolve: any, reject: any) { function sendDataCallback(resolve: any, reject: any) {
if (complete === 0) { if (complete === 0) {
reject( reject(
new Error( new Error(

View File

@ -31,6 +31,7 @@ import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/napcat-cor
import { proxiedListenerOf } from 'napcat-common/src/proxy-handler'; import { proxiedListenerOf } from 'napcat-common/src/proxy-handler';
import { NTQQPacketApi } from './apis/packet'; import { NTQQPacketApi } from './apis/packet';
import { NativePacketHandler } from './packet/handler/client'; import { NativePacketHandler } from './packet/handler/client';
import { container, ReceiverServiceRegistry } from './packet/handler/serviceRegister';
export * from './wrapper'; export * from './wrapper';
export * from './types/index'; export * from './types/index';
export * from './services/index'; export * from './services/index';
@ -118,6 +119,15 @@ export class NapCatCore {
UserApi: new NTQQUserApi(this.context, this), UserApi: new NTQQUserApi(this.context, this),
GroupApi: new NTQQGroupApi(this.context, this), GroupApi: new NTQQGroupApi(this.context, this),
}; };
container.bind(NapCatCore).toConstantValue(this);
ReceiverServiceRegistry.forEach((ServiceClass, serviceName) => {
container.bind(ServiceClass).toSelf();
console.log(`Registering service handler for: ${serviceName}`);
this.context.packetHandler.onCmd(serviceName, ({ seq, hex_data }) => {
const serviceInstance = container.get(ServiceClass);
return serviceInstance.handler(seq, hex_data);
});
});
} }
async initCore () { async initCore () {

View File

@ -0,0 +1,24 @@
import "reflect-metadata";
import { Container, injectable } from "inversify";
import { NapCatCore } from "../..";
export const container = new Container();
export const ReceiverServiceRegistry = new Map<string, new (...args: any[]) => ServiceBase>();
export abstract class ServiceBase {
get core(): NapCatCore {
return container.get(NapCatCore);
}
abstract handler(seq: number, hex_data: string): Promise<void> | void;
}
export function ReceiveService(serviceName: string) {
return function <T extends new (...args: any[]) => ServiceBase>(constructor: T) {
injectable()(constructor);
ReceiverServiceRegistry.set(serviceName, constructor);
return constructor;
};
}

View File

@ -0,0 +1,8 @@
import { ReceiveService, ServiceBase } from "../packet/handler/serviceRegister";
// @ReceiveService('trpc.msg.olpush.OlPushService.MsgPush')
// export class OlPushService extends ServiceBase {
// async handler(seq: number, hex_data: string) {
// console.log(`OlPushService handler called with seq: ${seq} and data: ${hex_data}`);
// }
// }

View File

@ -16,6 +16,7 @@ const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].
const FrameworkBaseConfigPlugin: PluginOption[] = [ const FrameworkBaseConfigPlugin: PluginOption[] = [
autoIncludeTSPlugin({ autoIncludeTSPlugin({
entries: [ entries: [
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-core/protocol') },
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') } { entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') }
] ]
}), }),

View File

@ -19,6 +19,7 @@ const ShellBaseConfigPlugin: PluginOption[] = [
react({ tsDecorators: true }), react({ tsDecorators: true }),
autoIncludeTSPlugin({ autoIncludeTSPlugin({
entries: [ entries: [
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-core/protocol') },
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') } { entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') }
] ]
}), }),

View File

@ -12,6 +12,9 @@ export function autoIncludeTSPlugin(options) {
async buildStart() { async buildStart() {
tsFilesMap = {}; tsFilesMap = {};
for (const { entry, dir } of entries) { for (const { entry, dir } of entries) {
if (!tsFilesMap[entry]) {
tsFilesMap[entry] = [];
}
const fullDir = path.resolve(dir); const fullDir = path.resolve(dir);
const allTsFiles = await findTSFiles(fullDir); const allTsFiles = await findTSFiles(fullDir);
@ -29,7 +32,7 @@ export function autoIncludeTSPlugin(options) {
} }
}); });
tsFilesMap[entry] = validFiles; tsFilesMap[entry].push(...validFiles);
} }
}, },