HTTP 异常过滤器层与对应的 WebSocket 层唯一区别在于:不应抛出 HttpException
,而应使用 WsException
。
throw new WsException('Invalid credentials.');
info 提示
WsException
类需从@nestjs/websockets
包导入。
基于上述示例,Nest 将处理抛出的异常并以如下结构发出 exception
消息:
{
status: 'error',
message: 'Invalid credentials.'
}
WebSocket 异常过滤器的行为与 HTTP 异常过滤器完全等效。以下示例使用了手动实例化的方法作用域过滤器。与基于 HTTP 的应用相同,您也可以使用网关作用域过滤器(即在网关类前添加 @UseFilters()
装饰器)。
@UseFilters(new WsExceptionFilter())
@SubscribeMessage('events')
onEvent(client, data: any): WsResponse<any> {
const event = 'events';
return { event, data };
}
通常,您会创建完全定制的异常过滤器来满足应用程序需求。但是,在某些情况下,您可能希望简单地扩展核心异常过滤器,并基于某些因素覆盖行为。
为了将异常处理委托给基础过滤器,您需要扩展 BaseWsExceptionFilter
并调用继承的 catch()
方法。
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseWsExceptionFilter } from '@nestjs/websockets';
@Catch()
export class AllExceptionsFilter extends BaseWsExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
super.catch(exception, host);
}
}
上述实现只是演示该方法的一个外壳。您的扩展异常过滤器实现将包括您定制的业务逻辑(例如,处理各种条件)。
const errorResponse = {
status: 'error',
timestamp: new Date().toISOString(),
event: 'exception',
data: {
...details,
originalData: data,
},
};
// 发送错误消息给客户端
client.emit('exception', errorResponse);
// 记录错误
console.error(`[WS Exception] 客户端 ${client.id}:`, errorResponse);
} }
#### 全局异常过滤器
处理所有类型的异常:
```typescript
import { Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
@Catch() // 捕获所有异常
export class GlobalWsExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const client: Socket = host.switchToWs().getClient();
let errorResponse: any;
if (exception instanceof WsException) {
errorResponse = this.handleWsException(exception);
} else if (exception instanceof HttpException) {
errorResponse = this.handleHttpException(exception);
} else if (exception instanceof Error) {
errorResponse = this.handleGenericError(exception);
} else {
errorResponse = this.handleUnknownException(exception);
}
// 添加通用字段
errorResponse.timestamp = new Date().toISOString();
errorResponse.clientId = client.id;
// 发送错误响应
client.emit('error', errorResponse);
// 记录错误
this.logError(client.id, exception, errorResponse);
}
private handleWsException(exception: WsException) {
const error = exception.getError();
return {
type: 'ws_exception',
status: 'error',
message: typeof error === 'string' ? error : error.message,
details: typeof error === 'object' ? error : undefined,
};
}
private handleHttpException(exception: HttpException) {
return {
type: 'http_exception',
status: 'error',
statusCode: exception.getStatus(),
message: exception.message,
details: exception.getResponse(),
};
}
private handleGenericError(exception: Error) {
return {
type: 'generic_error',
status: 'error',
message: exception.message,
stack: process.env.NODE_ENV === 'development' ? exception.stack : undefined,
};
}
private handleUnknownException(exception: unknown) {
return {
type: 'unknown_exception',
status: 'error',
message: '发生未知错误',
details: process.env.NODE_ENV === 'development' ? exception : undefined,
};
}
private logError(clientId: string, exception: unknown, response: any) {
console.error(`[WS Global Exception] 客户端 ${clientId}:`, {
exception: exception instanceof Error ? exception.message : exception,
response,
stack: exception instanceof Error ? exception.stack : undefined,
});
}
}
专门处理验证错误:
import { Catch, ArgumentsHost } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
export class ValidationException extends WsException {
constructor(public validationErrors: any[]) {
super('Validation failed');
}
}
@Catch(ValidationException)
export class WsValidationExceptionFilter {
catch(exception: ValidationException, host: ArgumentsHost) {
const client: Socket = host.switchToWs().getClient();
const errorResponse = {
status: 'validation_error',
message: '数据验证失败',
timestamp: new Date().toISOString(),
errors: this.formatValidationErrors(exception.validationErrors),
};
client.emit('validation_error', errorResponse);
console.warn(`[WS Validation] 客户端 ${client.id} 验证失败:`, errorResponse.errors);
}
private formatValidationErrors(errors: any[]): any[] {
return errors.map(error => ({
field: error.property,
value: error.value,
constraints: error.constraints,
}));
}
}
处理特定的业务逻辑异常:
import { Catch, ArgumentsHost } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
export class BusinessException extends WsException {
constructor(
public code: string,
message: string,
public statusCode: number = 400,
) {
super(message);
}
}
@Catch(BusinessException)
export class WsBusinessExceptionFilter {
catch(exception: BusinessException, host: ArgumentsHost) {
const client: Socket = host.switchToWs().getClient();
const errorResponse = {
status: 'business_error',
code: exception.code,
message: exception.message,
statusCode: exception.statusCode,
timestamp: new Date().toISOString(),
suggestions: this.getSuggestions(exception.code),
};
client.emit('business_error', errorResponse);
console.info(`[WS Business] 客户端 ${client.id} 业务异常 [${exception.code}]: ${exception.message}`);
}
private getSuggestions(code: string): string[] {
const suggestionMap: Record<string, string[]> = {
'INSUFFICIENT_BALANCE': ['请充值后重试', '检查账户余额'],
'UNAUTHORIZED_ACCESS': ['请先登录', '检查访问权限'],
'RESOURCE_NOT_FOUND': ['确认资源是否存在', '检查访问路径'],
'RATE_LIMIT_EXCEEDED': ['请降低请求频率', '稍后重试'],
};
return suggestionMap[code] || ['请联系客服'];
}
}
处理连接相关的异常:
import { Catch, ArgumentsHost } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
export class ConnectionException extends WsException {
constructor(
message: string,
public shouldDisconnect: boolean = false,
) {
super(message);
}
}
@Catch(ConnectionException)
export class WsConnectionExceptionFilter {
catch(exception: ConnectionException, host: ArgumentsHost) {
const client: Socket = host.switchToWs().getClient();
const errorResponse = {
status: 'connection_error',
message: exception.message,
timestamp: new Date().toISOString(),
reconnect: !exception.shouldDisconnect,
};
if (exception.shouldDisconnect) {
// 发送错误消息后断开连接
client.emit('connection_error', errorResponse);
setTimeout(() => {
client.disconnect(true);
}, 100);
console.warn(`[WS Connection] 强制断开客户端 ${client.id}: ${exception.message}`);
} else {
client.emit('connection_error', errorResponse);
console.warn(`[WS Connection] 客户端 ${client.id} 连接异常: ${exception.message}`);
}
}
}
收集和统计异常信息:
import { Catch, ArgumentsHost } from '@nestjs/common';
import { WsException } from '@nestjs/websockets';
import { Socket } from 'socket.io';
interface ExceptionStats {
count: number;
lastOccurrence: Date;
clients: Set<string>;
}
@Catch()
export class WsExceptionStatsFilter {
private stats = new Map<string, ExceptionStats>();
private readonly maxStatsEntries = 1000;
catch(exception: unknown, host: ArgumentsHost) {
const client: Socket = host.switchToWs().getClient();
const exceptionType = this.getExceptionType(exception);
// 更新统计信息
this.updateStats(exceptionType, client.id);
// 生成错误响应
const errorResponse = {
status: 'error',
type: exceptionType,
message: this.getExceptionMessage(exception),
timestamp: new Date().toISOString(),
frequency: this.getExceptionFrequency(exceptionType),
};
client.emit('error', errorResponse);
// 如果某种异常频率过高,记录警告
const stats = this.stats.get(exceptionType);
if (stats && stats.count > 100) {
console.warn(`[WS Exception Stats] 高频异常 ${exceptionType}: ${stats.count} 次, 影响 ${stats.clients.size} 个客户端`);
}
}
private getExceptionType(exception: unknown): string {
if (exception instanceof WsException) return 'WsException';
if (exception instanceof Error) return exception.constructor.name;
return 'UnknownException';
}
private getExceptionMessage(exception: unknown): string {
if (exception instanceof WsException) {
const error = exception.getError();
return typeof error === 'string' ? error : error.message || 'WebSocket异常';
}
if (exception instanceof Error) return exception.message;
return '未知异常';
}
private updateStats(exceptionType: string, clientId: string): void {
if (!this.stats.has(exceptionType)) {
this.stats.set(exceptionType, {
count: 0,
lastOccurrence: new Date(),
clients: new Set(),
});
}
const stats = this.stats.get(exceptionType);
stats.count++;
stats.lastOccurrence = new Date();
stats.clients.add(clientId);
// 限制统计条目数量
if (this.stats.size > this.maxStatsEntries) {
const oldestEntry = Array.from(this.stats.entries())
.sort(([,a], [,b]) => a.lastOccurrence.getTime() - b.lastOccurrence.getTime())[0];
this.stats.delete(oldestEntry[0]);
}
}
private getExceptionFrequency(exceptionType: string): string {
const stats = this.stats.get(exceptionType);
if (!stats) return 'low';
if (stats.count > 100) return 'high';
if (stats.count > 10) return 'medium';
return 'low';
}
// 获取异常统计
getStats(): Map<string, ExceptionStats> {
return new Map(this.stats);
}
// 重置统计
resetStats(): void {
this.stats.clear();
}
}
通常,您会创建完全自定义的异常过滤器来满足应用程序需求。但在某些情况下,您可能希望直接扩展核心异常过滤器 ,并根据特定因素覆盖其行为。
为了将异常处理委托给基础过滤器,您需要扩展 BaseWsExceptionFilter
并调用继承的 catch()
方法。
@@filename()
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseWsExceptionFilter } from '@nestjs/websockets';
@Catch()
export class AllExceptionsFilter extends BaseWsExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
// 添加自定义逻辑
const client = host.switchToWs().getClient();
console.log(`[WS Exception] 客户端 ${client.id} 发生异常:`, exception);
// 调用基础过滤器
super.catch(exception, host);
}
}
在网关中组合使用多个异常过滤器:
@WebSocketGateway()
@UseFilters(
new WsValidationExceptionFilter(),
new WsBusinessExceptionFilter(),
new WsConnectionExceptionFilter(),
new GlobalWsExceptionFilter(),
)
export class SecureGateway {
@SubscribeMessage('risky-operation')
async handleRiskyOperation(@MessageBody() data: any) {
// 可能抛出各种异常的操作
if (!data.userId) {
throw new ValidationException([
{ property: 'userId', constraints: { isNotEmpty: 'userId不能为空' } }
]);
}
if (data.amount > 1000) {
throw new BusinessException('INSUFFICIENT_BALANCE', '余额不足');
}
if (Math.random() > 0.8) {
throw new ConnectionException('网络不稳定', false);
}
return { success: true, data };
}
}
设置全局 WebSocket 异常过滤器:
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { GlobalWsExceptionFilter } from './filters/global-ws-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 设置全局 WebSocket 异常过滤器
app.useGlobalFilters(new GlobalWsExceptionFilter());
await app.listen(3000);
}
bootstrap();
上述实现仅是一个展示方法的框架。您对扩展异常过滤器的实现将包含您定制的业务逻辑 (例如处理各种条件)。
通过这些异常过滤器,您可以为 WebSocket 应用提供全面的错误处理、统计和监控功能。