NapCatQQ source snapshot
This commit is contained in:
commit
f954252a34
1111 files changed
+139867
No files matched your search
@@ -0,0 +1,315 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import path from 'path';
|
||||
import {
|
||||
PluginRouterRegistry,
|
||||
PluginRequestHandler,
|
||||
PluginApiRouteDefinition,
|
||||
PluginPageDefinition,
|
||||
PluginHttpRequest,
|
||||
PluginHttpResponse,
|
||||
HttpMethod,
|
||||
MemoryStaticFile,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 包装 Express Request 为 PluginHttpRequest
|
||||
*/
|
||||
function wrapRequest (req: Request): PluginHttpRequest {
|
||||
return {
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
query: req.query as Record<string, string | string[] | undefined>,
|
||||
body: req.body,
|
||||
headers: req.headers as Record<string, string | string[] | undefined>,
|
||||
params: req.params,
|
||||
raw: req,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装 Express Response 为 PluginHttpResponse
|
||||
*/
|
||||
function wrapResponse (res: Response): PluginHttpResponse {
|
||||
const wrapped: PluginHttpResponse = {
|
||||
status (code: number) {
|
||||
res.status(code);
|
||||
return wrapped;
|
||||
},
|
||||
json (data: unknown) {
|
||||
res.json(data);
|
||||
},
|
||||
send (data: string | Buffer) {
|
||||
res.send(data);
|
||||
},
|
||||
setHeader (name: string, value: string) {
|
||||
res.setHeader(name, value);
|
||||
return wrapped;
|
||||
},
|
||||
sendFile (filePath: string) {
|
||||
res.sendFile(filePath);
|
||||
},
|
||||
redirect (url: string) {
|
||||
res.redirect(url);
|
||||
},
|
||||
raw: res,
|
||||
};
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插件路由注册器实现
|
||||
* 为每个插件创建独立的路由注册器,收集路由定义
|
||||
*/
|
||||
/** 内存静态路由定义 */
|
||||
interface MemoryStaticRoute {
|
||||
urlPath: string;
|
||||
files: MemoryStaticFile[];
|
||||
}
|
||||
|
||||
export class PluginRouterRegistryImpl implements PluginRouterRegistry {
|
||||
private apiRoutes: PluginApiRouteDefinition[] = [];
|
||||
private apiNoAuthRoutes: PluginApiRouteDefinition[] = [];
|
||||
private pageDefinitions: PluginPageDefinition[] = [];
|
||||
private staticRoutes: Array<{ urlPath: string; localPath: string; }> = [];
|
||||
private memoryStaticRoutes: MemoryStaticRoute[] = [];
|
||||
|
||||
constructor (
|
||||
private readonly pluginId: string,
|
||||
private readonly pluginPath: string
|
||||
) { }
|
||||
|
||||
// ==================== API 路由注册 ====================
|
||||
|
||||
api (method: HttpMethod, routePath: string, handler: PluginRequestHandler): void {
|
||||
this.apiRoutes.push({ method, path: routePath, handler });
|
||||
}
|
||||
|
||||
get (routePath: string, handler: PluginRequestHandler): void {
|
||||
this.api('get', routePath, handler);
|
||||
}
|
||||
|
||||
post (routePath: string, handler: PluginRequestHandler): void {
|
||||
this.api('post', routePath, handler);
|
||||
}
|
||||
|
||||
put (routePath: string, handler: PluginRequestHandler): void {
|
||||
this.api('put', routePath, handler);
|
||||
}
|
||||
|
||||
delete (routePath: string, handler: PluginRequestHandler): void {
|
||||
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 {
|
||||
this.pageDefinitions.push(pageDef);
|
||||
}
|
||||
|
||||
pages (pageDefs: PluginPageDefinition[]): void {
|
||||
this.pageDefinitions.push(...pageDefs);
|
||||
}
|
||||
|
||||
// ==================== 静态资源 ====================
|
||||
|
||||
static (urlPath: string, localPath: string): void {
|
||||
// 如果是相对路径,则相对于插件目录
|
||||
const absolutePath = path.isAbsolute(localPath)
|
||||
? localPath
|
||||
: path.join(this.pluginPath, localPath);
|
||||
this.staticRoutes.push({ urlPath, localPath: absolutePath });
|
||||
}
|
||||
|
||||
staticOnMem (urlPath: string, files: MemoryStaticFile[]): void {
|
||||
this.memoryStaticRoutes.push({ urlPath, files });
|
||||
}
|
||||
|
||||
// ==================== 构建路由 ====================
|
||||
|
||||
/**
|
||||
* 构建 Express Router(用于 API 路由)
|
||||
* 注意:静态资源路由不在此处挂载,由 webui-backend 直接在不需要鉴权的路径下处理
|
||||
*/
|
||||
buildApiRouter (): Router {
|
||||
const router = Router();
|
||||
|
||||
// 注册 API 路由
|
||||
for (const route of this.apiRoutes) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 包装处理器,添加错误处理和请求/响应包装
|
||||
*/
|
||||
private wrapHandler (handler: PluginRequestHandler): (req: Request, res: Response, next: NextFunction) => void {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const wrappedReq = wrapRequest(req);
|
||||
const wrappedRes = wrapResponse(res);
|
||||
await handler(wrappedReq, wrappedRes, next);
|
||||
} catch (error: any) {
|
||||
console.error(`[Plugin: ${this.pluginId}] Route error:`, error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({
|
||||
code: -1,
|
||||
message: `Plugin error: ${error.message || 'Unknown error'}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== 查询方法 ====================
|
||||
|
||||
/**
|
||||
* 检查是否有注册的 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有注册的静态资源路由
|
||||
*/
|
||||
hasStaticRoutes (): boolean {
|
||||
return this.staticRoutes.length > 0 || this.memoryStaticRoutes.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有注册的页面
|
||||
*/
|
||||
hasPages (): boolean {
|
||||
return this.pageDefinitions.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的页面定义
|
||||
*/
|
||||
getPages (): PluginPageDefinition[] {
|
||||
return [...this.pageDefinitions];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件 ID
|
||||
*/
|
||||
getPluginId (): string {
|
||||
return this.pluginId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取插件路径
|
||||
*/
|
||||
getPluginPath (): string {
|
||||
return this.pluginPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的静态路由
|
||||
*/
|
||||
getStaticRoutes (): Array<{ urlPath: string; localPath: string; }> {
|
||||
return [...this.staticRoutes];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有注册的内存静态路由
|
||||
*/
|
||||
getMemoryStaticRoutes (): MemoryStaticRoute[] {
|
||||
return [...this.memoryStaticRoutes];
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空路由(用于插件卸载)
|
||||
*/
|
||||
clear (): void {
|
||||
this.apiRoutes = [];
|
||||
this.apiNoAuthRoutes = [];
|
||||
this.pageDefinitions = [];
|
||||
this.staticRoutes = [];
|
||||
this.memoryStaticRoutes = [];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user