mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-03 22:21:13 +00:00
Add plugin no-auth API routes and WebUI handling
Introduce support for plugin API routes that do not require WebUI authentication. Updates include:
- napcat-onebot: Add apiNoAuth route storage and helpers (apiNoAuth, getNoAuth, postNoAuth, putNoAuth, deleteNoAuth), hasApiNoAuthRoutes, buildApiNoAuthRouter, and clear handling in PluginRouterRegistryImpl.
- napcat-onebot types: Extend PluginRouterRegistry interface with no-auth API methods and document that authenticated APIs remain separate.
- napcat-webui-backend: Mount a new unauthenticated plugin route handler at /plugin/:pluginId/api that looks up the plugin router and dispatches requests to the plugin's no-auth router, returning appropriate errors when context or routes are missing.
- napcat-plugin-builtin: Add example no-auth endpoints (public/info and health) and update logger messages to reflect both auth and no-auth API paths.
- Bump napcat-types version to 0.0.16 and update napcat-plugin-builtin dependency accordingly.
These changes enable plugins to expose public endpoints (e.g. health checks or public metadata) under /plugin/{pluginId}/api/ while keeping existing authenticated APIs under /api/Plugin/ext/{pluginId}/.
This commit is contained in:
parent
74781fda0a
commit
78ac36f670
@ -68,6 +68,7 @@ interface MemoryStaticRoute {
|
||||
|
||||
export class PluginRouterRegistryImpl implements PluginRouterRegistry {
|
||||
private apiRoutes: PluginApiRouteDefinition[] = [];
|
||||
private apiNoAuthRoutes: PluginApiRouteDefinition[] = [];
|
||||
private pageDefinitions: PluginPageDefinition[] = [];
|
||||
private staticRoutes: Array<{ urlPath: string; localPath: string; }> = [];
|
||||
private memoryStaticRoutes: MemoryStaticRoute[] = [];
|
||||
@ -99,6 +100,28 @@ export class PluginRouterRegistryImpl implements PluginRouterRegistry {
|
||||
this.api('delete', routePath, handler);
|
||||
}
|
||||
|
||||
// ==================== 无认证 API 路由注册 ====================
|
||||
|
||||
apiNoAuth (method: HttpMethod, routePath: string, handler: PluginRequestHandler): void {
|
||||
this.apiNoAuthRoutes.push({ method, path: routePath, handler });
|
||||
}
|
||||
|
||||
getNoAuth (routePath: string, handler: PluginRequestHandler): void {
|
||||
this.apiNoAuth('get', routePath, handler);
|
||||
}
|
||||
|
||||
postNoAuth (routePath: string, handler: PluginRequestHandler): void {
|
||||
this.apiNoAuth('post', routePath, handler);
|
||||
}
|
||||
|
||||
putNoAuth (routePath: string, handler: PluginRequestHandler): void {
|
||||
this.apiNoAuth('put', routePath, handler);
|
||||
}
|
||||
|
||||
deleteNoAuth (routePath: string, handler: PluginRequestHandler): void {
|
||||
this.apiNoAuth('delete', routePath, handler);
|
||||
}
|
||||
|
||||
// ==================== 页面注册 ====================
|
||||
|
||||
page (pageDef: PluginPageDefinition): void {
|
||||
@ -184,12 +207,52 @@ export class PluginRouterRegistryImpl implements PluginRouterRegistry {
|
||||
// ==================== 查询方法 ====================
|
||||
|
||||
/**
|
||||
* 检查是否有注册的 API 路由
|
||||
* 检查是否有注册的 API 路由(需要认证)
|
||||
*/
|
||||
hasApiRoutes (): boolean {
|
||||
return this.apiRoutes.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有注册的无认证 API 路由
|
||||
*/
|
||||
hasApiNoAuthRoutes (): boolean {
|
||||
return this.apiNoAuthRoutes.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建无认证 Express Router(用于 /plugin/{pluginId}/api/ 路径)
|
||||
*/
|
||||
buildApiNoAuthRouter (): Router {
|
||||
const router = Router();
|
||||
|
||||
for (const route of this.apiNoAuthRoutes) {
|
||||
const handler = this.wrapHandler(route.handler);
|
||||
switch (route.method) {
|
||||
case 'get':
|
||||
router.get(route.path, handler);
|
||||
break;
|
||||
case 'post':
|
||||
router.post(route.path, handler);
|
||||
break;
|
||||
case 'put':
|
||||
router.put(route.path, handler);
|
||||
break;
|
||||
case 'delete':
|
||||
router.delete(route.path, handler);
|
||||
break;
|
||||
case 'patch':
|
||||
router.patch(route.path, handler);
|
||||
break;
|
||||
case 'all':
|
||||
router.all(route.path, handler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有注册的静态资源路由
|
||||
*/
|
||||
@ -244,6 +307,7 @@ export class PluginRouterRegistryImpl implements PluginRouterRegistry {
|
||||
*/
|
||||
clear (): void {
|
||||
this.apiRoutes = [];
|
||||
this.apiNoAuthRoutes = [];
|
||||
this.pageDefinitions = [];
|
||||
this.staticRoutes = [];
|
||||
this.memoryStaticRoutes = [];
|
||||
|
||||
@ -140,24 +140,42 @@ export interface MemoryStaticFile {
|
||||
|
||||
/** 插件路由注册器 */
|
||||
export interface PluginRouterRegistry {
|
||||
// ==================== API 路由注册 ====================
|
||||
// ==================== API 路由注册(需要认证) ====================
|
||||
|
||||
/**
|
||||
* 注册单个 API 路由
|
||||
* 注册单个 API 路由(需要认证,挂载到 /api/Plugin/ext/{pluginId}/)
|
||||
* @param method HTTP 方法
|
||||
* @param path 路由路径
|
||||
* @param handler 请求处理器
|
||||
*/
|
||||
api (method: HttpMethod, path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 GET API */
|
||||
/** 注册 GET API(需要认证) */
|
||||
get (path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 POST API */
|
||||
/** 注册 POST API(需要认证) */
|
||||
post (path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 PUT API */
|
||||
/** 注册 PUT API(需要认证) */
|
||||
put (path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 DELETE API */
|
||||
/** 注册 DELETE API(需要认证) */
|
||||
delete (path: string, handler: PluginRequestHandler): void;
|
||||
|
||||
// ==================== 无认证 API 路由注册 ====================
|
||||
|
||||
/**
|
||||
* 注册单个无认证 API 路由(挂载到 /plugin/{pluginId}/api/)
|
||||
* @param method HTTP 方法
|
||||
* @param path 路由路径
|
||||
* @param handler 请求处理器
|
||||
*/
|
||||
apiNoAuth (method: HttpMethod, path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 GET API(无认证) */
|
||||
getNoAuth (path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 POST API(无认证) */
|
||||
postNoAuth (path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 PUT API(无认证) */
|
||||
putNoAuth (path: string, handler: PluginRequestHandler): void;
|
||||
/** 注册 DELETE API(无认证) */
|
||||
deleteNoAuth (path: string, handler: PluginRequestHandler): void;
|
||||
|
||||
// ==================== 页面注册 ====================
|
||||
|
||||
/**
|
||||
|
||||
@ -129,6 +129,34 @@ const plugin_init: PluginModule['plugin_init'] = async (ctx) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 无认证 API 路由示例 ====================
|
||||
// 路由挂载到 /plugin/{pluginId}/api/,无需 WebUI 登录即可访问
|
||||
|
||||
// 获取插件公开信息(无需鉴权)
|
||||
ctx.router.getNoAuth('/public/info', (_req, res) => {
|
||||
const uptime = Date.now() - startTime;
|
||||
res.json({
|
||||
code: 0,
|
||||
data: {
|
||||
pluginName: ctx.pluginName,
|
||||
uptime,
|
||||
uptimeFormatted: formatUptime(uptime),
|
||||
platform: process.platform
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 健康检查接口(无需鉴权)
|
||||
ctx.router.getNoAuth('/health', (_req, res) => {
|
||||
res.json({
|
||||
code: 0,
|
||||
data: {
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ==================== 插件互调用示例 ====================
|
||||
// 演示如何调用其他插件的导出方法
|
||||
ctx.router.get('/call-plugin/:pluginId', (req, res) => {
|
||||
@ -178,7 +206,8 @@ const plugin_init: PluginModule['plugin_init'] = async (ctx) => {
|
||||
});
|
||||
|
||||
logger.info('WebUI 路由已注册:');
|
||||
logger.info(' - API 路由: /api/Plugin/ext/' + ctx.pluginName + '/');
|
||||
logger.info(' - API 路由(需认证): /api/Plugin/ext/' + ctx.pluginName + '/');
|
||||
logger.info(' - API 路由(无认证): /plugin/' + ctx.pluginName + '/api/');
|
||||
logger.info(' - 扩展页面: /plugin/' + ctx.pluginName + '/page/dashboard');
|
||||
logger.info(' - 静态资源: /plugin/' + ctx.pluginName + '/files/static/');
|
||||
logger.info(' - 内存资源: /plugin/' + ctx.pluginName + '/mem/dynamic/');
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
"description": "NapCat 内置插件",
|
||||
"author": "NapNeko",
|
||||
"dependencies": {
|
||||
"napcat-types": "0.0.15"
|
||||
"napcat-types": "0.0.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.0.1"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "napcat-types",
|
||||
"version": "0.0.15",
|
||||
"version": "0.0.16",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"types": "./napcat-types/index.d.ts",
|
||||
|
||||
@ -332,6 +332,28 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra
|
||||
return res.status(404).json({ code: -1, message: 'Memory file not found' });
|
||||
});
|
||||
|
||||
// 插件无认证 API 路由(不需要鉴权)
|
||||
// 路径格式: /plugin/:pluginId/api/*
|
||||
app.use('/plugin/:pluginId/api', (req, res, next) => {
|
||||
const { pluginId } = req.params;
|
||||
if (!pluginId) return res.status(400).json({ code: -1, message: 'Plugin ID is required' });
|
||||
|
||||
const ob11 = WebUiDataRuntime.getOneBotContext() as NapCatOneBot11Adapter | null;
|
||||
if (!ob11) return res.status(503).json({ code: -1, message: 'OneBot context not available' });
|
||||
|
||||
const pluginManager = ob11.networkManager.findSomeAdapter('plugin_manager') as OB11PluginMangerAdapter | undefined;
|
||||
if (!pluginManager) return res.status(503).json({ code: -1, message: 'Plugin manager not available' });
|
||||
|
||||
const routerRegistry = pluginManager.getPluginRouter(pluginId);
|
||||
if (!routerRegistry || !routerRegistry.hasApiNoAuthRoutes()) {
|
||||
return res.status(404).json({ code: -1, message: `Plugin '${pluginId}' has no registered no-auth API routes` });
|
||||
}
|
||||
|
||||
// 构建并执行插件无认证 API 路由
|
||||
const pluginRouter = routerRegistry.buildApiNoAuthRouter();
|
||||
return pluginRouter(req, res, next);
|
||||
});
|
||||
|
||||
// 插件页面路由(不需要鉴权)
|
||||
// 路径格式: /plugin/:pluginId/page/:pagePath
|
||||
app.get('/plugin/:pluginId/page/:pagePath', (req, res) => {
|
||||
|
||||
@ -232,8 +232,8 @@ importers:
|
||||
packages/napcat-plugin-builtin:
|
||||
dependencies:
|
||||
napcat-types:
|
||||
specifier: 0.0.15
|
||||
version: 0.0.15
|
||||
specifier: 0.0.16
|
||||
version: 0.0.16
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^22.0.1
|
||||
@ -5457,8 +5457,8 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
napcat-types@0.0.15:
|
||||
resolution: {integrity: sha512-uOkaQPO3SVgkO/Rt0cQ+02wCI9C9jzdYVViHByHrr9sA+2ZjT1HV5nVSgNNQXUaZ9q405LUu45xQ4lysNyLpBA==}
|
||||
napcat-types@0.0.16:
|
||||
resolution: {integrity: sha512-y3qhpdd16ATsMp4Jf88XwisFBVKqY+XSfvGX1YqMEasVFTNXeKr1MZrIzhHMkllW1QJZXAI8iNGVJO1gkHEtLQ==}
|
||||
|
||||
napcat.protobuf@1.1.4:
|
||||
resolution: {integrity: sha512-z7XtLSBJ/PxmYb0VD/w+eYr/X3LyGz+SZ2QejFTOczwt6zWNxy2yV1mTMTvJoc3BWkI3ESVFRxkuT6+pj1tb1Q==}
|
||||
@ -12783,7 +12783,7 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
napcat-types@0.0.15:
|
||||
napcat-types@0.0.16:
|
||||
dependencies:
|
||||
'@sinclair/typebox': 0.34.41
|
||||
'@types/node': 22.19.1
|
||||
|
||||
Loading…
Reference in New Issue
Block a user