Files
foka-ci/apps/server/middlewares/exception.ts
hurole 63c1e4df63 feat: 添加路由装饰器系统和全局异常处理
- 新增装饰器支持(@Get, @Post, @Put, @Delete, @Patch, @Controller)
- 实现路由自动注册机制(RouteScanner)
- 添加全局异常处理中间件(Exception)
- 实现统一响应体格式(ApiResponse)
- 新增请求体解析中间件(BodyParser)
- 重构控制器为类模式,支持装饰器路由
- 添加示例用户控制器(UserController)
- 更新TypeScript配置支持装饰器
- 添加reflect-metadata依赖
- 完善项目文档

Breaking Changes:
- 控制器现在返回数据而不是直接设置ctx.body
- 新增统一的API响应格式
2025-09-01 00:14:17 +08:00

139 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type Koa from 'koa';
import type { Middleware } from './types.ts';
/**
* 统一响应体结构
*/
export interface ApiResponse<T = any> {
code: number; // 状态码0表示成功其他表示失败
message: string; // 响应消息
data?: T; // 响应数据
timestamp: number; // 时间戳
}
/**
* 自定义业务异常类
*/
export class BusinessError extends Error {
public code: number;
public httpStatus: number;
constructor(message: string, code = 1000, httpStatus = 400) {
super(message);
this.name = 'BusinessError';
this.code = code;
this.httpStatus = httpStatus;
}
}
/**
* 全局异常处理中间件
*/
export class Exception implements Middleware {
apply(app: Koa): void {
app.use(async (ctx, next) => {
try {
await next();
// 如果没有设置响应体则返回404
if (ctx.status === 404 && !ctx.body) {
this.sendResponse(ctx, 404, '接口不存在', null, 404);
}
} catch (error) {
console.error('全局异常捕获:', error);
this.handleError(ctx, error);
}
});
}
/**
* 处理错误
*/
private handleError(ctx: Koa.Context, error: any): void {
if (error instanceof BusinessError) {
// 业务异常
this.sendResponse(ctx, error.code, error.message, null, error.httpStatus);
} else if (error.status) {
// Koa HTTP 错误
const message = error.status === 401 ? '未授权访问' :
error.status === 403 ? '禁止访问' :
error.status === 404 ? '资源不存在' :
error.status === 422 ? '请求参数错误' :
error.message || '请求失败';
this.sendResponse(ctx, error.status, message, null, error.status);
} else {
// 系统异常
const isDev = process.env.NODE_ENV === 'development';
const message = isDev ? error.message : '服务器内部错误';
const data = isDev ? { stack: error.stack } : null;
this.sendResponse(ctx, 500, message, data, 500);
}
}
/**
* 发送统一响应
*/
private sendResponse(
ctx: Koa.Context,
code: number,
message: string,
data: any = null,
httpStatus = 200
): void {
const response: ApiResponse = {
code,
message,
data,
timestamp: Date.now()
};
ctx.status = httpStatus;
ctx.body = response;
ctx.type = 'application/json';
}
}
/**
* 创建成功响应的辅助函数
*/
export function createSuccessResponse<T>(data: T, message = '操作成功'): ApiResponse<T> {
return {
code: 0,
message,
data,
timestamp: Date.now()
};
}
/**
* 创建成功响应的辅助函数(自动设置消息)
*/
export function createAutoSuccessResponse<T>(data: T): ApiResponse<T> {
let message = '操作成功';
// 根据数据类型自动生成消息
if (Array.isArray(data)) {
message = `获取列表成功,共${data.length}条记录`;
} else if (data && typeof data === 'object') {
message = '获取数据成功';
} else if (data === null || data === undefined) {
message = '操作完成';
}
return createSuccessResponse(data, message);
}
/**
* 创建失败响应的辅助函数
*/
export function createErrorResponse(code: number, message: string, data?: any): ApiResponse {
return {
code,
message,
data,
timestamp: Date.now()
};
}