- 新增装饰器支持(@Get, @Post, @Put, @Delete, @Patch, @Controller) - 实现路由自动注册机制(RouteScanner) - 添加全局异常处理中间件(Exception) - 实现统一响应体格式(ApiResponse) - 新增请求体解析中间件(BodyParser) - 重构控制器为类模式,支持装饰器路由 - 添加示例用户控制器(UserController) - 更新TypeScript配置支持装饰器 - 添加reflect-metadata依赖 - 完善项目文档 Breaking Changes: - 控制器现在返回数据而不是直接设置ctx.body - 新增统一的API响应格式
167 lines
4.7 KiB
TypeScript
167 lines
4.7 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}`);
|
||
}
|
||
|
||
console.log(`注册路由: ${route.method} ${fullPath} -> ${ControllerClass.name}.${route.propertyKey}`);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 构建完整的路由路径
|
||
*/
|
||
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);
|
||
|
||
// 如果控制器返回了数据,则包装成统一响应格式
|
||
if (result !== undefined) {
|
||
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;
|
||
}
|
||
}
|