feat: 装饰器与装饰器路由注册

This commit is contained in:
手瓜一十雪 2025-11-14 19:49:13 +08:00
parent 36aa08a8f5
commit 5043a49779
13 changed files with 233 additions and 28 deletions

View File

@ -12,13 +12,14 @@
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^16.0.3",
"@vitejs/plugin-react-swc": "^4.2.2",
"typescript": "^5.3.0",
"vite": "^6.4.1",
"vite-plugin-cp": "^6.0.3",
"typescript": "^5.3.0"
"vite-plugin-cp": "^6.0.3"
},
"dependencies": {
"silk-wasm": "^3.6.1",
"express": "^5.0.0",
"silk-wasm": "^3.6.1",
"ws": "^8.18.3"
}
}

View File

@ -2,7 +2,9 @@ import cp from 'vite-plugin-cp';
import { defineConfig, PluginOption, UserConfig } from 'vite';
import path, { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve';
import { autoIncludeTSPlugin } from "napcat-vite/vite-auto-include.js";
import { builtinModules } from 'module';
import react from '@vitejs/plugin-react-swc';
//依赖排除
const external = [
'silk-wasm',
@ -11,6 +13,12 @@ const external = [
];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
const FrameworkBaseConfigPlugin: PluginOption[] = [
autoIncludeTSPlugin({
entries: [
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') }
]
}),
react({ tsDecorators: true }),
cp({
targets: [
{ src: '../napcat-napi-loader/', dest: 'dist', flatten: true },

View File

@ -0,0 +1,6 @@
import { OneBotAction } from './OneBotAction';
export const AutoRegisterRouter: Array<new (...args: any[]) => OneBotAction<unknown, unknown>> = [];
export function ActionHandler(target: new (...args: any[]) => OneBotAction<unknown, unknown>) {
AutoRegisterRouter.push(target);
}

View File

@ -136,8 +136,9 @@ import { DownloadFileRecordStream } from './stream/DownloadFileRecordStream';
import { DownloadFileImageStream } from './stream/DownloadFileImageStream';
import { TestDownloadStream } from './stream/TestStreamDownload';
import { UploadFileStream } from './stream/UploadFileStream';
import { AutoRegisterRouter } from './auto-register';
export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
const actionHandlers = [
new CleanStreamTempFile(obContext, core),
new DownloadFileStream(obContext, core),
@ -288,30 +289,35 @@ export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatC
new GetCollectionList(obContext, core),
];
type HandlerUnion = typeof actionHandlers[number];
type MapType = {
[H in HandlerUnion as H['actionName']]: H;
} & {
[H in HandlerUnion as `${H['actionName']}_async`]: H;
} & {
[H in HandlerUnion as `${H['actionName']}_rate_limited`]: H;
};
type HandlerUnion = typeof actionHandlers[number];
type MapType = {
[H in HandlerUnion as H['actionName']]: H;
} & {
[H in HandlerUnion as `${H['actionName']}_async`]: H;
} & {
[H in HandlerUnion as `${H['actionName']}_rate_limited`]: H;
};
const _map = new Map<keyof MapType, HandlerUnion>();
const _map = new Map<keyof MapType, HandlerUnion>();
actionHandlers.forEach(h => {
_map.set(h.actionName as keyof MapType, h);
_map.set(`${h.actionName}_async` as keyof MapType, h);
_map.set(`${h.actionName}_rate_limited` as keyof MapType, h);
});
// function get<K extends keyof MapType>(key: K): MapType[K];
// function get<K extends keyof MapType>(key: K): null;
// function get<K extends keyof MapType>(key: K): HandlerUnion | null | MapType[K]
function get<K extends keyof MapType> (key: K): MapType[K] | undefined {
return _map.get(key as keyof MapType) as MapType[K] | undefined;
}
return { get };
actionHandlers.forEach(h => {
_map.set(h.actionName as keyof MapType, h);
_map.set(`${h.actionName}_async` as keyof MapType, h);
_map.set(`${h.actionName}_rate_limited` as keyof MapType, h);
});
AutoRegisterRouter.forEach((ActionClass) => {
const handlerInstance = new ActionClass(obContext, core); // 延迟实例化
const actionName = handlerInstance.actionName; // 获取 actionName
_map.set(actionName as keyof MapType, handlerInstance as HandlerUnion);
_map.set(`${actionName}_async` as keyof MapType, handlerInstance as HandlerUnion);
_map.set(`${actionName}_rate_limited` as keyof MapType, handlerInstance as HandlerUnion);
});
// function get<K extends keyof MapType>(key: K): MapType[K];
// function get<K extends keyof MapType>(key: K): null;
// function get<K extends keyof MapType>(key: K): HandlerUnion | null | MapType[K]
function get<K extends keyof MapType>(key: K): MapType[K] | undefined {
return _map.get(key as keyof MapType) as MapType[K] | undefined;
}
return { get };
}
export type ActionMap = ReturnType<typeof createActionMap>;

View File

@ -10,6 +10,8 @@ export interface InvalidCheckResult {
}
export const ActionName = {
TestAutoRegister01: 'test_auto_register_01',
TestAutoRegister02: 'test_auto_register_02',
// 所有 Normal Stream Api 表示并未流传输 表示与流传输有关
CleanStreamTempFile: 'clean_stream_temp_file',

View File

@ -0,0 +1,17 @@
# 自动注册路由测试
## 参与工具
vite-auto-include 自动化收集所有文件并引入
@vitejs/plugin-react-swc 包含对装饰器的支持
## 支持示例
```typescript
@ActionHandler
export default class TestAutoRegister02 extends OneBotAction<void, string> {
override actionName = ActionName.TestAutoRegister02;
async _handle (_payload: void): Promise<string> {
return 'AutoRegister Router Test';
}
}
```

View File

@ -0,0 +1,12 @@
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
import { ActionName } from '@/napcat-onebot/action/router';
import { ActionHandler } from '../auto-register';
@ActionHandler
export default class TestAutoRegister01 extends OneBotAction<void, string> {
override actionName = ActionName.TestAutoRegister01;
async _handle (_payload: void): Promise<string> {
return 'AutoRegister Router Test';
}
}

View File

@ -0,0 +1,12 @@
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
import { ActionName } from '@/napcat-onebot/action/router';
import { ActionHandler } from '../auto-register';
@ActionHandler
export default class TestAutoRegister02 extends OneBotAction<void, string> {
override actionName = ActionName.TestAutoRegister02;
async _handle (_payload: void): Promise<string> {
return 'AutoRegister Router Test';
}
}

View File

@ -0,0 +1,31 @@
// 日志装饰器
function log2(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
}
// 示例类
class MathOperations2 {
@log2
add(a: number, b: number): number {
return a + b;
}
@log2
multiply(a: number, b: number): number {
return a * b;
}
}
// 创建实例并调用方法
const math2 = new MathOperations2();
math2.add(1, 2); // 调用加法
math2.multiply(3, 4); // 调用乘法

View File

@ -0,0 +1,31 @@
// 日志装饰器
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
}
// 示例类
class MathOperations {
@log
add(a: number, b: number): number {
return a + b;
}
@log
multiply(a: number, b: number): number {
return a * b;
}
}
// 创建实例并调用方法
const math = new MathOperations();
math.add(1, 2); // 调用加法
math.multiply(3, 4); // 调用乘法

View File

@ -4,6 +4,8 @@ import path, { resolve } from 'path';
import nodeResolve from '@rollup/plugin-node-resolve';
import { builtinModules } from 'module';
import napcatVersion from "napcat-vite/vite-plugin-version.js";
import { autoIncludeTSPlugin } from "napcat-vite/vite-auto-include.js";
import react from '@vitejs/plugin-react-swc';
//依赖排除
const external = [
@ -11,8 +13,15 @@ const external = [
'ws',
'express'
];
const nodeModules = [...builtinModules, builtinModules.map((m) => `node:${m}`)].flat();
const ShellBaseConfigPlugin: PluginOption[] = [
react({ tsDecorators: true }),
autoIncludeTSPlugin({
entries: [
{ entry: 'napcat.ts', dir: path.resolve(__dirname, '../napcat-onebot/action/test') }
]
}),
cp({
targets: [
{ src: '../napcat-native/', dest: 'dist/native', flatten: false },

View File

@ -40,7 +40,8 @@
"allowJs": true
},
"include": [
"vite-plugin-version.js"
"*.js",
"**/*.js"
],
"exclude": [
"node_modules",

View File

@ -0,0 +1,69 @@
import path from 'path';
import fs from 'fs';
export function autoIncludeTSPlugin(options) {
// options: { entries: [{ entry: 'napcat.ts', dir: './utils' }, ...] }
const { entries } = options;
let tsFilesMap = {};
return {
name: 'vite-auto-include',
async buildStart() {
tsFilesMap = {};
for (const { entry, dir } of entries) {
const fullDir = path.resolve(dir);
const allTsFiles = await findTSFiles(fullDir);
const validFiles = [];
allTsFiles.forEach((filePath) => {
try {
const source = fs.readFileSync(filePath, 'utf-8');
if (source && source.trim() !== '') {
validFiles.push(filePath);
} else {
// Skipping empty file: ${filePath}
}
} catch (error) {
console.error(`Error reading file: ${filePath}`, error);
}
});
tsFilesMap[entry] = validFiles;
}
},
transform(code, id) {
for (const [entry, tsFiles] of Object.entries(tsFilesMap)) {
// 检查id是否匹配entry支持完整路径或相对路径
const isMatch = id.endsWith(entry) || id.includes(entry);
if (isMatch && tsFiles.length > 0) {
const imports = tsFiles.map(filePath => {
const relativePath = path.relative(path.dirname(id), filePath).replace(/\\/g, '/');
return `import './${relativePath}';`;
}).join('\n');
return imports + '\n' + code;
}
}
return code;
},
};
}
// 辅助函数:查找所有 .ts 文件
async function findTSFiles(dir) {
const files = [];
const items = await fs.promises.readdir(dir, { withFileTypes: true });
for (let item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory()) {
files.push(...await findTSFiles(fullPath)); // 递归查找子目录
} else if (item.isFile() && fullPath.endsWith('.ts')) {
files.push(fullPath); // 收集 .ts 文件
}
}
return files;
}