系统异常模块设计思考

叨叨

  1. 周末闲来无事,一直觉得当前项目的异常模块的结构设计不合适,经常有人问:这个异常提示放在哪里合适? 这个异常和这个异常是一样的啊! 异常提示放的乱七八糟…来梳理下吧.
  2. 项目中通常是自定义一个异常类,并继承RuntimeException.通过提供构造方法来传入自定义异常信息.这种设计的结果就是代码中随处可见的throw new XXException("xxxx");魔法值,相同的异常提示也没法复用;
  3. 为了异常消息能复用(即字符串异常提示),定义一些接口类,将这些提示信息作为常量.当前项目就是这样做的.不知道什么时候开始,接口类中各种重复的异常提示,可能是数量太多,大家也不再去找有没有现成的,直接重新定义就是了.

项目代码就不贴了,反正就是异常类型的维护是"你中有我,我中有你".总之就是很乱.跟项目管理和开发规范也有关系吧.

设计异常

  1. 定义系统模块枚举类,功能模块区分出来.
  2. 异常中最好能体现异常所在的功能模块,防止异常提示信息重复时查找问题不方便
  3. 取消自定义异常类传入字符串的构造函数,传入指定类型,需要先维护
  4. 异常信息的维护类加入模块属性(强制),作为命名空间,同一模块的异常信息维护在一起,方便查找,一定程度上也能防重复定义,也可以通过类文件进行划分,当然这个属于项目工程和编码规范
定义异常提示枚举

通过接口维护异常提示信息,无法强制已提示信息中添加模块属性,此处使用枚举类
通过定义多个枚举类对模块进行分类,再通过模块字段分类,比如此处demo定义两个枚举类: 业务处理异常BizProcessErrorEnum, 请求参数异常RequestParamErrorEnum(使用validation则忽略),BizProcessErrorEnum中又有用户模块,合同模块,但这些都是在进行业务逻辑处理时发生的异常,所以放在一个类中.

// 业务处理异常枚举类
public enum BizProcessErrorEnum implements BizExceptionType{
    // 用户模块
    USER_NOT_FOUND(SysModuleEnums.USER,"用户不存在"),
	
	// 合同模块
	DATE_FORMAT_ERROR(SysModuleEnums.CONTRACT,"时间格式错误"),
	
	// other
    ;


    private SysModuleEnums module;
    private String msg;
    BizProcessErrorEnum(SysModuleEnums module, String msg) {
        this.module = module;
        this.msg = msg;
    }
    @Override
    public String getMessage() {
        return msg;
    }
    @Override
    public SysModuleEnums getModuleEnum() {
        return module;
    }
}

// 请求参数错误枚举类
public enum RequestParamErrorEnum implements BizExceptionType{
    DEMO(SysModuleEnums.CONFIG,"字典项重复")

	// other
    ;
    
    
    private SysModuleEnums module;
    private String msg;
    RequestParamErrorEnum(SysModuleEnums module, String msg) {
        this.module = module;
        this.msg = msg;
    }
    @Override
    public String getMessage() {
        return msg;
    }
    @Override
    public SysModuleEnums getModuleEnum() {
        return module;
    }
}
异常归类

定义接口,所有定义的枚举实现该接口,限定自定义异常的传参

public interface BizExceptionType {

    /**
     * 获取描述信息
     * @return
     */
    String getMessage();

    /**
     * 获取模块枚举
     * @return
     */
    SysModuleEnums getModuleEnum();
}
自定义异常类
public class BaseBizException extends RuntimeException{
	// 模块
    private SysModuleEnums module;
    // 异常信息
    private String message;
    // 异常提示枚举类
    private BizExceptionType errorEnum;

	// 构造只能传入BizExceptionType,需要在对应模块提前维护
    public BaseBizException(BizExceptionType errorEnum) {
        this.module = errorEnum.getModuleEnum();
        this.errorEnum = errorEnum;
        this.message = errorEnum.getMessage();
    }

    public SysModuleEnums getModule() {
        return module;
    }

    @Override
    public String getMessage() {
        return message;
    }
	
    public String getErrorEnum() {
        return errorEnum.getModuleEnumString();
    }
}
全局异常处理
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(BaseBizException.class)
    public ResponseEntity<BaseResponse<Void>> forBizException(BaseBizException e){
        log.error("异常模块:{},异常内容:{}",e.getModule().name(),e.getMessage());
        return new ResponseEntity<>(new BaseResponse<>(BizStatus.ERROR,null,e.getMessage()),HttpStatus.OK);
    }

    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<BaseResponse<Void>> forRuntimeException(RuntimeException e){
        log.error("异常内容:{}",e.getMessage());
        return new ResponseEntity<>(new BaseResponse<>(BizStatus.ERROR, null, e.getMessage()), HttpStatus.OK);
    }
}
测试
@RestController
@RequestMapping("/exception")
public class DemoController{

    @PostMapping("/create")
    public ResponseEntity<Void> demo(@RequestBody SysUser user){
        if (user.getAge() == 0) {
            throw new BaseBizException(BizProcessErrorEnum.USER_NOT_FOUND);
        }
        return new ResponseEntity<>(HttpStatus.OK);
    }
    
    @PostMapping("/create1")
    public ResponseEntity<Void> demo1(@RequestBody SysUser user){
        if (user.getAge() == 0) {
            int i = 1/0;
        }
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

@Data
class SysUser{
    private String name;
    private int age;
}
# 请求
POST http://localhost:8080/exception/create
Content-Type: application/json

{
  "name": "wyy",
  "age": 0
}
# 日志输出
2021-08-21 19:12:39.423 ERROR 68955 --- [nio-8080-exec-1] demo.wyy.config.GlobalExceptionHandler   : 异常模块:USER,异常内容:用户不存在
# 响应
{
  "status": "ERROR",
  "data": null,
  "msg": "用户不存在"
}
-------------------------------------------------------------------------------------------------
# 请求
POST http://localhost:8080/exception/create1
Content-Type: application/json

{
  "name": "wyy",
  "age": 0
}
# 日志输出
2021-08-21 19:26:09.583 ERROR 68955 --- [nio-8080-exec-3] demo.wyy.config.GlobalExceptionHandler   : 异常内容:/ by zero
# 响应
{
  "status": "ERROR",
  "data": null,
  "msg": "/ by zero"
}

小记

按照这种设计,在业务层面对异常提示做大致的划分,从而确定几个枚举类,每个枚举类中通过系统的功能模块进行标识分组,清晰了一些.水平有限,如果哪位大佬项目中有好的实践方案,交流一下,谢谢.

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值