tyt-api-nest/src/common/http-exception.filter.ts
2026-03-13 02:40:21 +08:00

121 lines
3.5 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 {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
HttpStatus,
Logger,
} from '@nestjs/common';
import { Response } from 'express';
import { Prisma } from '../generated/prisma/client.js';
import { MESSAGES } from './messages.js';
/**
* 全局异常过滤器:统一异常返回结构,并保证 msg 为中文。
*/
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(HttpExceptionFilter.name);
catch(exception: unknown, host: ArgumentsHost): void {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
// 非 HttpException 统一记录堆栈,便于定位 500 根因。
if (!(exception instanceof HttpException)) {
const error = exception as { message?: string; stack?: string };
this.logger.error(
error?.message ?? 'Unhandled exception',
error?.stack,
);
}
const status = this.resolveStatus(exception);
const msg = this.resolveMessage(exception, status);
response.status(status).json({
code: status,
msg,
data: null,
});
}
/**
* 解析 HTTP 状态码,非 HttpException 统一按 500 处理。
*/
private resolveStatus(exception: unknown): number {
if (exception instanceof Prisma.PrismaClientInitializationError) {
return HttpStatus.SERVICE_UNAVAILABLE;
}
if (exception instanceof Prisma.PrismaClientKnownRequestError) {
switch (exception.code) {
case 'P2002':
return HttpStatus.CONFLICT;
case 'P2025':
return HttpStatus.NOT_FOUND;
default:
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
if (exception instanceof HttpException) {
return exception.getStatus();
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}
/**
* 解析异常消息:优先使用业务抛出的 message否则按状态码兜底中文。
*/
private resolveMessage(exception: unknown, status: number): string {
if (exception instanceof Prisma.PrismaClientInitializationError) {
return MESSAGES.DB.CONNECTION_FAILED;
}
if (exception instanceof Prisma.PrismaClientKnownRequestError) {
switch (exception.code) {
case 'P2021':
return MESSAGES.DB.TABLE_MISSING;
case 'P2022':
return MESSAGES.DB.COLUMN_MISSING;
case 'P2002':
return MESSAGES.DEFAULT_CONFLICT;
case 'P2025':
return MESSAGES.DEFAULT_NOT_FOUND;
default:
return MESSAGES.DEFAULT_INTERNAL_ERROR;
}
}
if (exception instanceof HttpException) {
const payload = exception.getResponse();
if (typeof payload === 'string') {
return payload;
}
if (payload && typeof payload === 'object') {
const body = payload as Record<string, unknown>;
const message = body.message;
if (Array.isArray(message)) {
return message.join('');
}
if (typeof message === 'string' && message.trim()) {
return message;
}
}
}
switch (status) {
case HttpStatus.BAD_REQUEST:
return MESSAGES.DEFAULT_BAD_REQUEST;
case HttpStatus.UNAUTHORIZED:
return MESSAGES.DEFAULT_UNAUTHORIZED;
case HttpStatus.FORBIDDEN:
return MESSAGES.DEFAULT_FORBIDDEN;
case HttpStatus.NOT_FOUND:
return MESSAGES.DEFAULT_NOT_FOUND;
case HttpStatus.CONFLICT:
return MESSAGES.DEFAULT_CONFLICT;
default:
return MESSAGES.DEFAULT_INTERNAL_ERROR;
}
}
}