llm-chat/backend/middleware/errorHandler.js

274 lines
6.2 KiB
JavaScript
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.

const { Logger } = require('./logger');
/**
* 404错误处理中间件
*/
const notFoundHandler = (req, res, next) => {
const error = new Error(`找不到请求的资源: ${req.method} ${req.originalUrl}`);
error.status = 404;
error.code = 'NOT_FOUND';
Logger.warn('404错误', {
method: req.method,
url: req.originalUrl,
ip: req.ip,
userAgent: req.get('User-Agent')
});
next(error);
};
/**
* 全局错误处理中间件
*/
const globalErrorHandler = (err, req, res, next) => {
// 默认错误状态码
let statusCode = err.status || err.statusCode || 500;
let message = err.message || '服务器内部错误';
let code = err.code || 'INTERNAL_ERROR';
// 记录错误日志
Logger.error('全局错误处理', err, {
method: req.method,
url: req.originalUrl,
ip: req.ip,
userAgent: req.get('User-Agent'),
body: req.body,
params: req.params,
query: req.query
});
// 根据错误类型设置不同的响应
switch (err.name) {
case 'ValidationError':
statusCode = 400;
message = '请求参数验证失败';
code = 'VALIDATION_ERROR';
break;
case 'UnauthorizedError':
statusCode = 401;
message = '未授权访问';
code = 'UNAUTHORIZED';
break;
case 'JsonWebTokenError':
statusCode = 401;
message = 'Token无效';
code = 'INVALID_TOKEN';
break;
case 'TokenExpiredError':
statusCode = 401;
message = 'Token已过期';
code = 'TOKEN_EXPIRED';
break;
case 'CastError':
statusCode = 400;
message = '无效的ID格式';
code = 'INVALID_ID';
break;
case 'SyntaxError':
if (err.message.includes('JSON')) {
statusCode = 400;
message = 'JSON格式错误';
code = 'INVALID_JSON';
}
break;
}
// 数据库相关错误
if (err.message.includes('SQLITE_')) {
statusCode = 500;
message = '数据库操作失败';
code = 'DATABASE_ERROR';
// 具体的SQLite错误处理
if (err.message.includes('UNIQUE')) {
statusCode = 409;
message = '资源已存在';
code = 'RESOURCE_CONFLICT';
} else if (err.message.includes('FOREIGN KEY')) {
statusCode = 400;
message = '外键约束错误';
code = 'FOREIGN_KEY_ERROR';
}
}
// LLM API相关错误
if (err.message.includes('LLM') || err.message.includes('API')) {
if (err.message.includes('密钥')) {
statusCode = 503;
message = 'AI服务配置错误';
code = 'LLM_CONFIG_ERROR';
} else if (err.message.includes('网络') || err.message.includes('连接')) {
statusCode = 503;
message = 'AI服务连接失败';
code = 'LLM_CONNECTION_ERROR';
} else if (err.message.includes('频率')) {
statusCode = 429;
message = '请求过于频繁';
code = 'RATE_LIMIT_EXCEEDED';
}
}
// 构建错误响应
const errorResponse = {
success: false,
error: message,
code: code,
timestamp: new Date().toISOString(),
path: req.originalUrl,
method: req.method
};
// 开发环境包含更多错误信息
if (process.env.NODE_ENV === 'development') {
errorResponse.stack = err.stack;
errorResponse.details = {
name: err.name,
originalMessage: err.message
};
}
// 特殊处理:如果是流式请求,需要特殊处理
if (req.headers.accept && req.headers.accept.includes('text/plain')) {
res.status(statusCode).write(JSON.stringify({
type: 'error',
data: errorResponse
}) + '\n');
res.end();
return;
}
res.status(statusCode).json(errorResponse);
};
/**
* 异步错误包装器
*/
const asyncHandler = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
/**
* 验证错误处理器
*/
const validationErrorHandler = (errors) => {
const formattedErrors = errors.map(error => ({
field: error.param,
message: error.msg,
value: error.value
}));
const validationError = new Error('请求参数验证失败');
validationError.status = 400;
validationError.code = 'VALIDATION_ERROR';
validationError.details = formattedErrors;
return validationError;
};
/**
* 业务逻辑错误类
*/
class BusinessError extends Error {
constructor(message, statusCode = 400, code = 'BUSINESS_ERROR') {
super(message);
this.name = 'BusinessError';
this.status = statusCode;
this.code = code;
}
}
/**
* 资源未找到错误类
*/
class NotFoundError extends Error {
constructor(resource = '资源') {
super(`${resource}不存在`);
this.name = 'NotFoundError';
this.status = 404;
this.code = 'NOT_FOUND';
}
}
/**
* 验证错误类
*/
class ValidationError extends Error {
constructor(message, field = null) {
super(message);
this.name = 'ValidationError';
this.status = 400;
this.code = 'VALIDATION_ERROR';
this.field = field;
}
}
/**
* 权限错误类
*/
class ForbiddenError extends Error {
constructor(message = '权限不足') {
super(message);
this.name = 'ForbiddenError';
this.status = 403;
this.code = 'FORBIDDEN';
}
}
/**
* 服务不可用错误类
*/
class ServiceUnavailableError extends Error {
constructor(service = '服务') {
super(`${service}暂时不可用`);
this.name = 'ServiceUnavailableError';
this.status = 503;
this.code = 'SERVICE_UNAVAILABLE';
}
}
/**
* 进程退出前的清理处理
*/
const gracefulShutdown = () => {
process.on('SIGTERM', () => {
Logger.info('收到SIGTERM信号开始优雅关闭...');
process.exit(0);
});
process.on('SIGINT', () => {
Logger.info('收到SIGINT信号开始优雅关闭...');
process.exit(0);
});
process.on('uncaughtException', (err) => {
Logger.error('未捕获的异常', err);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
Logger.error('未处理的Promise拒绝', new Error(reason), { promise });
process.exit(1);
});
};
module.exports = {
notFoundHandler,
globalErrorHandler,
asyncHandler,
validationErrorHandler,
gracefulShutdown,
// 错误类
BusinessError,
NotFoundError,
ValidationError,
ForbiddenError,
ServiceUnavailableError
};