- 修复路由配置,实现根路径自动重定向到/project - 新增Gitea OAuth认证系统和相关组件 - 完善日志系统实现,包含pino日志工具和中间件 - 重构页面结构,分离项目管理和环境管理页面 - 新增CORS、Session等关键中间件 - 优化前端请求封装和类型定义 - 修复TypeScript类型错误和参数传递问题
162 lines
4.5 KiB
TypeScript
162 lines
4.5 KiB
TypeScript
import type Koa from 'koa';
|
||
import KoaRouter from '@koa/router';
|
||
import { getRouteMetadata, getControllerPrefix, type RouteMetadata } from '../decorators/route.ts';
|
||
import { createAutoSuccessResponse } from '../middlewares/exception.ts';
|
||
|
||
/**
|
||
* 控制器类型
|
||
*/
|
||
export interface ControllerClass {
|
||
new (...args: any[]): any;
|
||
}
|
||
|
||
/**
|
||
* 路由扫描器,用于自动注册装饰器标注的路由
|
||
*/
|
||
export class RouteScanner {
|
||
private router: KoaRouter;
|
||
private controllers: ControllerClass[] = [];
|
||
|
||
constructor(prefix: string = '/api') {
|
||
this.router = new KoaRouter({ prefix });
|
||
}
|
||
|
||
/**
|
||
* 注册控制器类
|
||
*/
|
||
registerController(ControllerClass: ControllerClass): void {
|
||
this.controllers.push(ControllerClass);
|
||
this.scanController(ControllerClass);
|
||
}
|
||
|
||
/**
|
||
* 注册多个控制器类
|
||
*/
|
||
registerControllers(controllers: ControllerClass[]): void {
|
||
controllers.forEach(controller => this.registerController(controller));
|
||
}
|
||
|
||
/**
|
||
* 扫描控制器并注册路由
|
||
*/
|
||
private scanController(ControllerClass: ControllerClass): void {
|
||
// 创建控制器实例
|
||
const controllerInstance = new ControllerClass();
|
||
|
||
// 获取控制器的路由前缀
|
||
const controllerPrefix = getControllerPrefix(ControllerClass);
|
||
|
||
// 获取控制器的路由元数据
|
||
const routes: RouteMetadata[] = getRouteMetadata(ControllerClass);
|
||
|
||
// 注册每个路由
|
||
routes.forEach(route => {
|
||
const fullPath = this.buildFullPath(controllerPrefix, route.path);
|
||
const handler = this.wrapControllerMethod(controllerInstance, route.propertyKey);
|
||
|
||
// 根据HTTP方法注册路由
|
||
switch (route.method) {
|
||
case 'GET':
|
||
this.router.get(fullPath, handler);
|
||
break;
|
||
case 'POST':
|
||
this.router.post(fullPath, handler);
|
||
break;
|
||
case 'PUT':
|
||
this.router.put(fullPath, handler);
|
||
break;
|
||
case 'DELETE':
|
||
this.router.delete(fullPath, handler);
|
||
break;
|
||
case 'PATCH':
|
||
this.router.patch(fullPath, handler);
|
||
break;
|
||
default:
|
||
console.warn(`未支持的HTTP方法: ${route.method}`);
|
||
}
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 构建完整的路由路径
|
||
*/
|
||
private buildFullPath(controllerPrefix: string, routePath: string): string {
|
||
// 清理和拼接路径
|
||
const cleanControllerPrefix = controllerPrefix.replace(/^\/+|\/+$/g, '');
|
||
const cleanRoutePath = routePath.replace(/^\/+|\/+$/g, '');
|
||
|
||
let fullPath = '';
|
||
if (cleanControllerPrefix) {
|
||
fullPath += '/' + cleanControllerPrefix;
|
||
}
|
||
if (cleanRoutePath) {
|
||
fullPath += '/' + cleanRoutePath;
|
||
}
|
||
|
||
// 如果路径为空,返回根路径
|
||
return fullPath || '/';
|
||
}
|
||
|
||
/**
|
||
* 包装控制器方法,统一处理响应格式
|
||
*/
|
||
private wrapControllerMethod(instance: any, methodName: string) {
|
||
return async (ctx: Koa.Context, next: Koa.Next) => {
|
||
try {
|
||
// 调用控制器方法
|
||
const method = instance[methodName];
|
||
if (typeof method !== 'function') {
|
||
throw new Error(`控制器方法 ${methodName} 不存在或不是函数`);
|
||
}
|
||
|
||
// 绑定this并调用方法
|
||
const result = await method.call(instance, ctx, next);
|
||
|
||
// 如果控制器返回了数据,则包装成统一响应格式
|
||
ctx.body = createAutoSuccessResponse(result);
|
||
} catch (error) {
|
||
// 错误由全局异常处理中间件处理
|
||
throw error;
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取Koa路由器实例,用于应用到Koa应用中
|
||
*/
|
||
getRouter(): KoaRouter {
|
||
return this.router;
|
||
}
|
||
|
||
/**
|
||
* 应用路由到Koa应用
|
||
*/
|
||
applyToApp(app: Koa): void {
|
||
app.use(this.router.routes());
|
||
app.use(this.router.allowedMethods());
|
||
}
|
||
|
||
/**
|
||
* 获取已注册的路由信息(用于调试)
|
||
*/
|
||
getRegisteredRoutes(): Array<{ method: string; path: string; controller: string; action: string }> {
|
||
const routes: Array<{ method: string; path: string; controller: string; action: string }> = [];
|
||
|
||
this.controllers.forEach(ControllerClass => {
|
||
const controllerPrefix = getControllerPrefix(ControllerClass);
|
||
const routeMetadata = getRouteMetadata(ControllerClass);
|
||
|
||
routeMetadata.forEach(route => {
|
||
routes.push({
|
||
method: route.method,
|
||
path: this.buildFullPath(controllerPrefix, route.path),
|
||
controller: ControllerClass.name,
|
||
action: route.propertyKey
|
||
});
|
||
});
|
||
});
|
||
|
||
return routes;
|
||
}
|
||
}
|