From 5043a49779619514ef93206d927d5f369fc700c7 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: Fri, 14 Nov 2025 19:49:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A3=85=E9=A5=B0=E5=99=A8=E4=B8=8E?= =?UTF-8?q?=E8=A3=85=E9=A5=B0=E5=99=A8=E8=B7=AF=E7=94=B1=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 7 +- packages/napcat-framework/vite.config.ts | 8 +++ .../napcat-onebot/action/auto-register.ts | 6 ++ packages/napcat-onebot/action/index.ts | 54 ++++++++------- packages/napcat-onebot/action/router.ts | 2 + packages/napcat-onebot/action/test/Readme.md | 17 +++++ .../action/test/TestAutoRegister01.ts | 12 ++++ .../action/test/TestAutoRegister02.ts | 12 ++++ packages/napcat-shell/utils/test.ts | 31 +++++++++ packages/napcat-shell/utils/test2.ts | 31 +++++++++ packages/napcat-shell/vite.config.ts | 9 +++ packages/napcat-vite/tsconfig.json | 3 +- packages/napcat-vite/vite-auto-include.js | 69 +++++++++++++++++++ 13 files changed, 233 insertions(+), 28 deletions(-) create mode 100644 packages/napcat-onebot/action/auto-register.ts create mode 100644 packages/napcat-onebot/action/test/Readme.md create mode 100644 packages/napcat-onebot/action/test/TestAutoRegister01.ts create mode 100644 packages/napcat-onebot/action/test/TestAutoRegister02.ts create mode 100644 packages/napcat-shell/utils/test.ts create mode 100644 packages/napcat-shell/utils/test2.ts create mode 100644 packages/napcat-vite/vite-auto-include.js diff --git a/package.json b/package.json index e647d763..a2655f9f 100644 --- a/package.json +++ b/package.json @@ -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" } } \ No newline at end of file diff --git a/packages/napcat-framework/vite.config.ts b/packages/napcat-framework/vite.config.ts index 5bc42b58..c51539de 100644 --- a/packages/napcat-framework/vite.config.ts +++ b/packages/napcat-framework/vite.config.ts @@ -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 }, diff --git a/packages/napcat-onebot/action/auto-register.ts b/packages/napcat-onebot/action/auto-register.ts new file mode 100644 index 00000000..90a3fa61 --- /dev/null +++ b/packages/napcat-onebot/action/auto-register.ts @@ -0,0 +1,6 @@ +import { OneBotAction } from './OneBotAction'; +export const AutoRegisterRouter: Array OneBotAction> = []; + +export function ActionHandler(target: new (...args: any[]) => OneBotAction) { + AutoRegisterRouter.push(target); +} diff --git a/packages/napcat-onebot/action/index.ts b/packages/napcat-onebot/action/index.ts index b0c41b8e..1c80890b 100644 --- a/packages/napcat-onebot/action/index.ts +++ b/packages/napcat-onebot/action/index.ts @@ -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(); + const _map = new Map(); - 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(key: K): MapType[K]; - // function get(key: K): null; - // function get(key: K): HandlerUnion | null | MapType[K] - function get (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(key: K): MapType[K]; + // function get(key: K): null; + // function get(key: K): HandlerUnion | null | MapType[K] + function get(key: K): MapType[K] | undefined { + return _map.get(key as keyof MapType) as MapType[K] | undefined; + } + return { get }; } export type ActionMap = ReturnType; diff --git a/packages/napcat-onebot/action/router.ts b/packages/napcat-onebot/action/router.ts index 9f5fa768..c9125466 100644 --- a/packages/napcat-onebot/action/router.ts +++ b/packages/napcat-onebot/action/router.ts @@ -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', diff --git a/packages/napcat-onebot/action/test/Readme.md b/packages/napcat-onebot/action/test/Readme.md new file mode 100644 index 00000000..fb6cf9b1 --- /dev/null +++ b/packages/napcat-onebot/action/test/Readme.md @@ -0,0 +1,17 @@ +# 自动注册路由测试 + +## 参与工具 +vite-auto-include 自动化收集所有文件并引入 +@vitejs/plugin-react-swc 包含对装饰器的支持 + +## 支持示例 +```typescript +@ActionHandler +export default class TestAutoRegister02 extends OneBotAction { + override actionName = ActionName.TestAutoRegister02; + + async _handle (_payload: void): Promise { + return 'AutoRegister Router Test'; + } +} +``` \ No newline at end of file diff --git a/packages/napcat-onebot/action/test/TestAutoRegister01.ts b/packages/napcat-onebot/action/test/TestAutoRegister01.ts new file mode 100644 index 00000000..3b44f099 --- /dev/null +++ b/packages/napcat-onebot/action/test/TestAutoRegister01.ts @@ -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 { + override actionName = ActionName.TestAutoRegister01; + + async _handle (_payload: void): Promise { + return 'AutoRegister Router Test'; + } +} diff --git a/packages/napcat-onebot/action/test/TestAutoRegister02.ts b/packages/napcat-onebot/action/test/TestAutoRegister02.ts new file mode 100644 index 00000000..63cbd10f --- /dev/null +++ b/packages/napcat-onebot/action/test/TestAutoRegister02.ts @@ -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 { + override actionName = ActionName.TestAutoRegister02; + + async _handle (_payload: void): Promise { + return 'AutoRegister Router Test'; + } +} diff --git a/packages/napcat-shell/utils/test.ts b/packages/napcat-shell/utils/test.ts new file mode 100644 index 00000000..ce82df29 --- /dev/null +++ b/packages/napcat-shell/utils/test.ts @@ -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); // 调用乘法 \ No newline at end of file diff --git a/packages/napcat-shell/utils/test2.ts b/packages/napcat-shell/utils/test2.ts new file mode 100644 index 00000000..803ed478 --- /dev/null +++ b/packages/napcat-shell/utils/test2.ts @@ -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); // 调用乘法 diff --git a/packages/napcat-shell/vite.config.ts b/packages/napcat-shell/vite.config.ts index 9b0371e4..c9aac813 100644 --- a/packages/napcat-shell/vite.config.ts +++ b/packages/napcat-shell/vite.config.ts @@ -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 }, diff --git a/packages/napcat-vite/tsconfig.json b/packages/napcat-vite/tsconfig.json index 9e2d7942..18530ede 100644 --- a/packages/napcat-vite/tsconfig.json +++ b/packages/napcat-vite/tsconfig.json @@ -40,7 +40,8 @@ "allowJs": true }, "include": [ - "vite-plugin-version.js" + "*.js", + "**/*.js" ], "exclude": [ "node_modules", diff --git a/packages/napcat-vite/vite-auto-include.js b/packages/napcat-vite/vite-auto-include.js new file mode 100644 index 00000000..4a0914d8 --- /dev/null +++ b/packages/napcat-vite/vite-auto-include.js @@ -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; +}