npm.io
2.2.0 • Published 4 months ago

koatty_exception

Licence
BSD-3-Clause
Version
2.2.0
Deps
5
Size
375 kB
Vulns
0
Weekly
0

koatty_exception

npm version Build Status Test Coverage License

koatty_exception 是 Koatty 框架的异常处理模块,提供了统一的错误处理机制、响应格式化和多协议支持。

特性

  • 统一异常处理 - 提供标准化的异常处理机制
  • 链式调用 - 支持方法链式调用,代码更优雅
  • 多协议支持 - 支持 HTTP、gRPC、WebSocket 多种协议
  • 可观测性 - 集成 OpenTelemetry 链路追踪
  • 高度可配置 - 支持自定义日志格式、错误响应格式等
  • TypeScript 支持 - 完整的类型定义和类型安全
  • 零依赖核心 - 核心功能无外部依赖
  • 装饰器模式 - 使用 @ExceptionHandler 装饰器注册异常处理器

安装

npm install koatty_exception

# 或使用 yarn
yarn add koatty_exception

# 或使用 pnpm
pnpm add koatty_exception

快速开始

基础用法
import { Exception, Output, CommonErrorCode } from 'koatty_exception';

// 创建基础异常
const error = new Exception('用户未找到', CommonErrorCode.RESOURCE_NOT_FOUND, 404);

// 链式调用设置异常属性
const customError = new Exception('验证失败')
  .setCode(CommonErrorCode.VALIDATION_ERROR)
  .setStatus(400)
  .setContext({
    requestId: 'req-123',
    path: '/api/users',
    method: 'POST'
  });

// 使用 Output 类格式化响应
const successResponse = Output.ok('操作成功', { id: 1, name: '张三' });
const errorResponse = Output.fail('操作失败', null, 1001);
自定义异常处理器
import { Exception, ExceptionHandler } from 'koatty_exception';
import { KoattyContext } from 'koatty_core';

@ExceptionHandler()
export class ValidationException extends Exception {
  constructor(message: string, field?: string) {
    super(message, CommonErrorCode.VALIDATION_ERROR, 400);
    
    if (field) {
      this.setContext({ field });
    }
  }

  async handler(ctx: KoattyContext): Promise<any> {
    // 自定义处理逻辑
    const response = {
      error: 'VALIDATION_ERROR',
      message: this.message,
      field: this.context?.field,
      timestamp: new Date().toISOString()
    };
    
    ctx.status = this.status;
    ctx.type = 'application/json';
    return ctx.res.end(JSON.stringify(response));
  }
}

// 使用自定义异常
throw new ValidationException('邮箱格式不正确', 'email');
全局配置
import { setExceptionConfig } from 'koatty_exception';

// 配置异常处理行为
setExceptionConfig({
  enableStackTrace: process.env.NODE_ENV !== 'production',
  logFormat: 'json',
  maxStackLength: 1000,
  customErrorFormat: (error) => ({
    errorId: generateErrorId(),
    type: error.constructor.name,
    message: error.message,
    timestamp: new Date().toISOString()
  })
});

API 文档

Exception 类
构造函数
constructor(message: string, code?: number, status?: number, stack?: string, span?: Span)
方法
setCode(code: number): this

设置错误代码(必须为非负整数)

setStatus(status: number): this

设置 HTTP 状态码(必须在 100-599 范围内)

setMessage(message: string): this

设置错误消息

setContext(context: Partial): this

设置错误上下文信息

setSpan(span: Span): this

设置 OpenTelemetry 跟踪 span

async handler(ctx: KoattyContext): Promise

处理异常并返回响应

toJSON(): object

将异常转换为普通对象

Output 类
静态方法
ok(message: string | JsonResult, data?: T, code?: number): JsonResult

创建成功响应

// 基础用法
Output.ok('操作成功', { id: 1 });
// 结果: { code: 0, message: '操作成功', data: { id: 1 } }

// 自定义代码
Output.ok('创建成功', newUser, 201);
fail(err?: Error | CodeError | string | unknown, data?: T, code?: number): JsonResult

创建失败响应

// 使用错误对象
Output.fail(new Error('数据库连接失败'));

// 使用字符串
Output.fail('参数无效', null, 1001);

// 使用 CodeError 对象
Output.fail({ code: 1002, message: '权限不足', data: { required: 'admin' } });
paginate(items: T[], total: number, page: number, pageSize: number, message?: string): JsonResult

创建分页响应

const users = [/* 用户列表 */];
const response = Output.paginate(users, 100, 2, 10, '用户列表');
// 包含 pagination 信息:{ total, page, pageSize, totalPages, hasNext, hasPrev }
withMeta(message: string, data: T, meta?: Record<string, unknown>, code?: number): JsonResult

创建带元数据的响应

Output.withMeta('查询成功', userData, {
  version: '1.0',
  executionTime: 120,
  cached: true
});
工具函数
prevent(): Promise

创建一个用于阻止下一步处理的 Promise

isPrevent(err: unknown): err is Error

检查是否为阻止处理的错误

isException(err: unknown): err is Exception

类型保护函数,检查是否为 Exception 实例

toSafeError(err: unknown): Error

将任意值转换为安全的 Error 对象

isNetworkError(err: unknown): boolean

检查是否为网络相关错误

配置选项

interface ExceptionConfig {
  enableStackTrace?: boolean;    // 是否启用堆栈跟踪(默认:非生产环境启用)
  logFormat?: 'json' | 'text';  // 日志格式(默认:json)
  customErrorFormat?: (error: Exception) => any; // 自定义错误格式化函数
  maxStackLength?: number;       // 最大堆栈长度(默认:1000)
}

高级用法

1. 自定义业务异常
@ExceptionHandler()
export class BusinessException extends Exception {
  constructor(message: string, businessCode: string) {
    super(message, CommonErrorCode.GENERAL_ERROR, 400);
    this.setContext({ businessCode });
  }
}

@ExceptionHandler()
export class AuthenticationException extends Exception {
  constructor(message: string = '身份验证失败') {
    super(message, CommonErrorCode.AUTHENTICATION_ERROR, 401);
  }
  
  async handler(ctx: KoattyContext): Promise<any> {
    // 清除认证信息
    ctx.cookies.set('token', null);
    return super.handler(ctx);
  }
}
2. 错误聚合和统计
export class ErrorCollector {
  private static errors: Map<string, number> = new Map();
  
  static record(error: Exception): void {
    const key = `${error.constructor.name}:${error.code}`;
    this.errors.set(key, (this.errors.get(key) || 0) + 1);
  }
  
  static getStats(): Record<string, number> {
    return Object.fromEntries(this.errors);
  }
}

// 在异常处理器中使用
@ExceptionHandler()
export class MonitoredException extends Exception {
  async handler(ctx: KoattyContext): Promise<any> {
    ErrorCollector.record(this);
    return super.handler(ctx);
  }
}
3. 异步错误处理
import { prevent, isPrevent } from 'koatty_exception';

async function processData() {
  try {
    const shouldStop = await checkCondition();
    if (shouldStop) {
      await prevent(); // 阻止后续处理
    }
    
    // 继续处理...
  } catch (error) {
    if (isPrevent(error)) {
      console.log('处理被阻止');
      return;
    }
    throw error; // 重新抛出其他错误
  }
}

测试

# 运行所有测试
npm test

# 运行测试并生成覆盖率报告
npm run test:cov

# 运行 ESLint 检查
npm run eslint

错误代码参考

export enum CommonErrorCode {
  SUCCESS = 0,                    // 成功
  GENERAL_ERROR = 1,              // 通用错误
  VALIDATION_ERROR = 1001,        // 验证失败
  AUTHENTICATION_ERROR = 1002,    // 身份验证失败
  AUTHORIZATION_ERROR = 1003,     // 权限不足
  RESOURCE_NOT_FOUND = 1004,      // 资源未找到
  RESOURCE_CONFLICT = 1005,       // 资源冲突
  RATE_LIMIT_EXCEEDED = 1006,     // 请求频率超限
  INTERNAL_SERVER_ERROR = 1007,   // 内部服务器错误
  SERVICE_UNAVAILABLE = 1008,     // 服务不可用
  TIMEOUT_ERROR = 1009,           // 超时错误
  NETWORK_ERROR = 1010            // 网络错误
}

贡献

欢迎贡献代码!请查看 贡献指南 了解详情。

开发步骤
  1. Fork 项目
  2. 创建特性分支 (git checkout -b feature/amazing-feature)
  3. 提交更改 (git commit -m 'Add some amazing feature')
  4. 推送到分支 (git push origin feature/amazing-feature)
  5. 创建 Pull Request

许可证

本项目基于 BSD-3-Clause 许可证开源。

相关链接

致谢

感谢所有为这个项目做出贡献的开发者!

Keywords