export type ErrorType = | "validation" | "not_found" | "permission_denied" | "conflict" | "business" | "database" | "internal"; export interface ErrorDetails { [key: string]: unknown; } export abstract class ApplicationError extends Error { abstract readonly type: ErrorType; abstract readonly statusCode: number; readonly code: string; readonly details?: ErrorDetails; // FIX #1: traceId 改为可写,以便 GlobalErrorFilter 注入请求级 traceId traceId?: string; constructor(message: string, code: string, details?: ErrorDetails) { super(message); this.name = this.constructor.name; this.code = code; this.details = details; } toJSON(): Record { return { success: false, error: { code: this.code, message: this.message, details: this.details, traceId: this.traceId, }, }; } } export class ValidationError extends ApplicationError { readonly type = "validation" as const; readonly statusCode = 400; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_VALIDATION_ERROR", details); } } export class NotFoundError extends ApplicationError { readonly type = "not_found" as const; readonly statusCode = 404; constructor(resource: string, id: string) { super(`${resource} not found: ${id}`, "MSG_NOT_FOUND", { resource, id }); } } export class PermissionDeniedError extends ApplicationError { readonly type = "permission_denied" as const; readonly statusCode = 403; constructor(permission: string) { super(`Permission denied: ${permission}`, "MSG_PERMISSION_DENIED", { permission, }); } } export class ConflictError extends ApplicationError { readonly type = "conflict" as const; readonly statusCode = 409; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_CONFLICT", details); } } export class BusinessError extends ApplicationError { readonly type = "business" as const; readonly statusCode = 422; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_BUSINESS_ERROR", details); } } export class DatabaseError extends ApplicationError { readonly type = "database" as const; readonly statusCode = 500; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_DATABASE_ERROR", details); } } export class InternalError extends ApplicationError { readonly type = "internal" as const; readonly statusCode = 500; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_INTERNAL_ERROR", details); } } /** * ES 不可用且无降级路径(503)。 * 仲裁依据:02-architecture-design.md §6.2 */ export class EsUnavailableError extends ApplicationError { readonly type = "internal" as const; readonly statusCode = 503; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_ES_UNAVAILABLE", details); } } /** * Redis 不可用且无降级路径(503)。 * 仲裁依据:02-architecture-design.md §6.2 */ export class RedisUnavailableError extends ApplicationError { readonly type = "internal" as const; readonly statusCode = 503; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_REDIS_UNAVAILABLE", details); } } /** * Push Gateway 不可用(503)。 * 仲裁依据:02-architecture-design.md §6.2 + coord M4(软失败,仅必要时抛出) */ export class PushGatewayUnavailableError extends ApplicationError { readonly type = "internal" as const; readonly statusCode = 503; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_PUSH_GATEWAY_UNAVAILABLE", details); } } /** * 通知模板不存在(404)。 * 仲裁依据:02-architecture-design.md §6.2 */ export class TemplateNotFoundError extends ApplicationError { readonly type = "not_found" as const; readonly statusCode = 404; constructor(templateCode: string) { super( `Notification template not found: ${templateCode}`, "MSG_TEMPLATE_NOT_FOUND", { templateCode }, ); } } /** * 模板渲染失败(变量缺失等,422)。 * 仲裁依据:02-architecture-design.md §6.2 */ export class TemplateRenderError extends ApplicationError { readonly type = "business" as const; readonly statusCode = 422; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_TEMPLATE_RENDER_ERROR", details); } } /** * 通知频率超限(429)。 * 仲裁依据:02-architecture-design.md §6.2(NotificationPreference.frequency_limit 预留) */ export class RateLimitExceededError extends ApplicationError { readonly type = "business" as const; readonly statusCode = 429; constructor(message: string, details?: ErrorDetails) { super(message, "MSG_RATE_LIMIT_EXCEEDED", details); } }