mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-18 20:30:08 +08:00
feat: 装饰器与装饰器路由注册
This commit is contained in:
parent
36aa08a8f5
commit
5043a49779
@ -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"
|
||||
}
|
||||
}
|
||||
@ -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 },
|
||||
|
||||
6
packages/napcat-onebot/action/auto-register.ts
Normal file
6
packages/napcat-onebot/action/auto-register.ts
Normal 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);
|
||||
}
|
||||
@ -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>;
|
||||
|
||||
@ -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',
|
||||
|
||||
|
||||
17
packages/napcat-onebot/action/test/Readme.md
Normal file
17
packages/napcat-onebot/action/test/Readme.md
Normal 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';
|
||||
}
|
||||
}
|
||||
```
|
||||
12
packages/napcat-onebot/action/test/TestAutoRegister01.ts
Normal file
12
packages/napcat-onebot/action/test/TestAutoRegister01.ts
Normal 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';
|
||||
}
|
||||
}
|
||||
12
packages/napcat-onebot/action/test/TestAutoRegister02.ts
Normal file
12
packages/napcat-onebot/action/test/TestAutoRegister02.ts
Normal 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';
|
||||
}
|
||||
}
|
||||
31
packages/napcat-shell/utils/test.ts
Normal file
31
packages/napcat-shell/utils/test.ts
Normal 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); // 调用乘法
|
||||
31
packages/napcat-shell/utils/test2.ts
Normal file
31
packages/napcat-shell/utils/test2.ts
Normal 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); // 调用乘法
|
||||
@ -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 },
|
||||
|
||||
@ -40,7 +40,8 @@
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"vite-plugin-version.js"
|
||||
"*.js",
|
||||
"**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
69
packages/napcat-vite/vite-auto-include.js
Normal file
69
packages/napcat-vite/vite-auto-include.js
Normal 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user