Typescript自定义异常类和枚举业务状态码

前言

最近在Midwayjs框架上搭建服务端项目,一个请求进来,执行链比较长,中间一旦出现校验不通过,需要进行异常处理,如果要在业务代码中进行异常处理十分麻烦且难以维护,从而引申出如何优雅地处理异常。

最简单的方式就是需要处理异常时,直接抛出异常,在全局异常处理中间件中进行捕获、处理、返回给前端。

我的期望是在抛出异常的同时,可以传递一些参数,比如业务状态码、http请求状态码、错误明细等参数。显然直接throw new Error(msg:string)是没办法做到的。所以我们需要自定义异常类,继承Error类,构造器中允许传入各种参数。

http请求状态与业务状态码

由于早期某些运营商会拦截非200的http请求状态码,导致有一批开发者http请求状态码只用200,然后定义了一大堆业务状态码在body中进行传递。
万年姨妈贴之一

但其实我觉得自定义业务状态码是有必要的。至少我接触的其他比较规范的项目都有自己的一套业务状态码。http请求状态码主要表示的请求的处理状态,也没办法跟业务挂钩,但现在也没必要全部只用200,以下是我挑出来比较常用的http请求状态码:

  1. 200 (成功)服务器已成功处理了请求。
  2. 401(未授权)请求要求身份验证。一旦出现这个状态,需要重新登陆
  3. 403 (无权限)请求的资源不允许访问。比如说,你使用普通用户的 Token 去请求管理员才能访问的资源。
  4. 404(未找到)服务器找不到请求的网页。
  5. 408(请求超时)服务器等候请求时发生超时 。
  6. 500(服务器内部错误)服务器遇到错误,无法完成请求。

如果需要更多请求状态码可以参照以下帖子自行挑选:
关于 RESTful API 中 HTTP 状态码的定义的疑问?

至于是否只用http请求状态200,自行斟酌,没必要引战。

封装自定异常类:

先定义一个基础异常类

//业务状态码
export const BasicExceptionCode = {
     
  PARAM_COUNT: 'BASIC0001',
  PARAM_TYPE: 'BASIC0002',
  PARAM_NULL: 'BASIC0003'
} as const;

export class BasicException extends Error {
     
  protected map: Map<string, string>;
  protected code: string = '';
  protected msg: string | undefined = '';
  protected detail: string = '';
  protected httpCode: number = 500;

  /**
   * 构造器函数 如果子类继承了该基类,请在子类构造器中依次执行super()、this.appendMap(map)、this.check(code,detail,httpCode)
   * @param code 业务状态码
   * @param detail 错误明细
   * @param httpCode 请求状态码
   */
  constructor(code: string = 'BASIC9999', detail: string = '', httpCode = 500) {
     
    super();
    this.map = new Map([
      ['BASIC0001', '参数数量错误'],
      ['BASIC0002', '参数类型错误'],
      ['BASIC0003', '参数为空'],
      ['BASIC9999', '未知错误']
    ]);
    //进行检查赋值
    this.check(code, detail, httpCode);
  }

  /**
   * 追加错误码Map,用于子类继承基类后,在构造器中执行super()后调用
   * @param map
   */
  protected appendMap(map: Map<string, string>) {
     
    this.map = new Map<string, string>([...this.map, ...map]);
  }

  /**
   * 检查错误码是否存在,存在提取错误状态码明细并赋值,如果不存在,则为未处理的错误。如果是子类,请在构造器中执行super()、super.setMap(map)后调用
   * @param code 业务状态码
   * @param detail 错误明细
   * @param httpCode 请求状态码
   */
  protected check(
    code: string = 'BASIC9999',
    detail: string = '',
    httpCode = 500
  ) {
     
    this.detail = detail;
    this.httpCode = httpCode;
    if (this.map.has(code)) {
     
      this.code = code;
      this.msg = this.map.get(code);
    } else {
     
      this.code = 'BASIC9999';
      this.msg = this.map.get(code);
    }
  }

  /**
   * 获取错误状态码
   */
  public getCode() {
     
    return this.code;
  }

  //获取错误码中文描述
  public getMsg() {
     
    return this.msg;
  }

  //获取错误明细(错误明细是抛出错误时手动传入的)
  public getDetail() {
     
    return this.detail;
  }

  //获取请求状态码
  public getHttpCode() {
     
    return this.httpCode;
  }

  /**
   * 转字符串
   */
  public toString() {
     
    return `httpCode:${
       this.httpCode},code:${
       this.code},msg:${
       this.msg},detail:${
       this.detail}`;
  }
}

在定义一个测试类来测试我们的基础异常类

import {
      BasicException, BasicExceptionCode } from '../exception/basic';

class BasicTest {
     
  constructor(...args: any[]) {
     
    this.main(...args);
  }

  main(...args: any[]) {
     
    try {
     
      if (Object.keys(args).length !== 2) {
     
        throw new BasicException(BasicExceptionCode.PARAM_COUNT);
      }

      if (args[0] === undefined || args[0] === null || args[0] === '') {
     
        throw new Error(BasicExceptionCode.PARAM_NULL);
      }

      if (args[0] instanceof String === false) {
     
        throw new BasicException(BasicExceptionCode.PARAM_TYPE);
      }

      throw new BasicException();
    } catch (err) {
     
      if (err instanceof BasicException) {
     
        console.log(err.toString());
      } else {
     
        console.log(err.toString());
      }
    }
  }
}

new BasicTest('helloWorld');

编译后执行的输出结果:
Typescript自定义异常类和枚举业务状态码_第1张图片
有了基础异常类还不够,我们还需要根据业务功能自定义功能异常类。
我们再定义一个继承BasicException的UserException

import {
      BasicException, BasicExceptionCode } from './basic';
//合并业务状态码
export const UserExceptionCode = {
     
  USERNAME_ERR: 'USER0001',
  USERNAME_LEN: 'USER0002',
  PASSWORD_ERR: 'USER0003',
  PASSWORD_LEN: 'USER0004',
  ...BasicExceptionCode
} as const;

export class UserException extends BasicException {
     
  constructor(code: string = 'BASIC9999', detail: string = '', httpCode = 500) {
     
    super(code, detail, httpCode);
    //追加错误状态的错误描述信息
    super.appendMap(
      new Map([
        ['USER0001', '账号错误'],
        ['USER0002', '账号长度不符合要求'],
        ['USER0003', '密码错误'],
        ['USER0004', '密码长度不符合要求']
      ])
    );
    this.check(code, detail, httpCode);
  }
}

写完之后,同样的,我们再创建一个测试类来验证我们的UserException

import {
      BasicException } from '../exception/basic';
import {
      UserException, UserExceptionCode } from '../exception/user';

class UserTest {
     
  constructor(username: string, password: string) {
     
    this.main(username, password);
  }

  main(username: string, password: string) {
     
    try {
     
      if (username.length < 6) {
     
        throw new UserException(UserExceptionCode.USERNAME_LEN, username);
      }

      if (username !== '123456') {
     
        throw new UserException(UserExceptionCode.USERNAME_ERR, username);
      }

      if (password.length < 6) {
     
        throw new UserException(UserExceptionCode.PASSWORD_LEN, password);
      }

      if (password !== '123456') {
     
        throw new UserException(UserExceptionCode.PASSWORD_ERR, password);
      }

      throw new UserException(UserExceptionCode.PARAM_COUNT);
    } catch (err) {
     
      if (err instanceof BasicException) {
     
        console.log(err.toString());
      } else {
     
        console.log(err.toString());
      }
    }
  }
}

new UserTest('123455', '123456');

编译后执行结果如下:
执行结果

枚举状态码

export const UserExceptionCode = {
     
  USERNAME_ERR: 'USER0001',
  USERNAME_LEN: 'USER0002',
  PASSWORD_ERR: 'USER0003',
  PASSWORD_LEN: 'USER0004',
  ...BasicExceptionCode
} as const;

以上代码使用的是Typescript的 Const Assertions语法,并没有选择使用Typescript的枚举类型。原因是Typescript的枚举类型虽然可以定义常量,使用时可以枚举属性,但我需要为每个功能异常类追加不同的业务状态码,而Typescript枚举类型并不能很好地合并或继承,所以选择使用Const Assertions,同样可以枚举里面的属性,并且可以进行合并,而且属性值不可以修改且无法在表达式以外新增属性(最起码在开发时提示无此属性,Typecript只是编译时检查语法,编译后就会把这些语法检查去掉)。

你可能感兴趣的:(Typescript,Midwayjs,Nodejs,node.js,后端)