小架构step系列32:统一结果

1 概述

HTTP请求的返回值如果没有规范,则可能返回的形式是五花八门的,这样使用这些返回值的地方就比较麻烦,需要写出不同的代码来处理这些形式各异的结果,这种代码冗长且毫无技术含量,维护起来却成本比较高。应该对返回的结果给出统一的结构。

2 规范

2.1 统一为带错误码的结果

请求的结果,从大的方向上有两种情况:
  • 一是HTTP请求状态码为失败的,这个时候是没有业务结果的。这种需要根据HTTP的状态码来判断。
  • 二是HTTP请求状态是成功的,里面包含了业务结果的。
这里说的“带错误码的结果”专指第二种,针对业务结果规范为带错误码的,这个错误码是业务的错误码。
import com.qqian.stepfmk.srvpro.base.error.ErrorCode;
import com.qqian.stepfmk.srvpro.base.error.SystemErrorCode;
import lombok.Value;

@Value
public class CodeResult<T> {
    ErrorCode code;
    T data;

    public CodeResult() {
        this(SystemErrorCode.SUCCESS, null);
    }

    public CodeResult(ErrorCode code) {
        this(code, null);
    }

    public CodeResult(ErrorCode code, T data) {
        this.code = code;
        this.data = data;
    }
}

其中,code为错误码,里面包含具体的错误码和错误描述,应该用常量进行定义,便于统一管理所有错误码。data则是具体的业务数据。

如果业务也是成功的,则使用系统级的成功状态码:

public class SystemErrorCode {
    public static final long SCOPE_SYSTEM = 0L;
    public static final int CODE_SUCCESS = 0;

    public static final ErrorCode SUCCESS = ErrorCode.withSystem(SCOPE_SYSTEM, CODE_SUCCESS,
            "OK", "成功");
}

2.2 校验错误

前面文章讲了使用hibernate-validator包来进行请求参数校验,校验的结果会抛出异常,需要对这里异常也转为统一结果形式。

1) 有校验错误的时候,抛出BindException

// 源码位置:org.springframework.web.method.annotation.ModelAttributeMethodProcessor
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 省略部分代码

    if (bindingResult == null) {
        WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
        if (binder.getTarget() != null) {
            if (!mavContainer.isBindingDisabled(name)) {
                bindRequestParameters(binder, webRequest);
            }
            validateIfApplicable(binder, parameter);
            
            // 如果有校验错误,抛出BindException,参数为BindingResult对象,里面包含了具体的错误信息
            if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                throw new BindException(binder.getBindingResult());
            }
        }
        if (!parameter.getParameterType().isInstance(attribute)) {
            attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
        }
        bindingResult = binder.getBindingResult();
    }

    Map<String, Object> bindingResultModel = bindingResult.getModel();
    mavContainer.removeAttributes(bindingResultModel);
    mavContainer.addAllAttributes(bindingResultModel);

    return attribute;
}

2) 统一处理这类异常,转为带错误码的结果

@ControllerAdvice
public class ParamValidateExceptionHandler {
    @ExceptionHandler(BindException.class)
    public CodeResult<Object> handleBindException(BindException e) {
        BindingResult result = e.getBindingResult();
        ErrorCode code = ErrorCode.withSystem(SystemErrorCode.SCOPE_SYSTEM,
                SystemErrorCode.CODE_INVALID_PARAMETER, "", "");

        return new CodeResult<Object>(code);
    }
}

由于hibernate-validator包并没有留扩展点供扩展校验结果信息,从头扩展工作量又比较大,所以用@ExceptionHandler处理这一类异常更方便一些,缺点是错误码不够具体化。如果实在要具体化,可以考虑扩展hibernate-validator提供的消息模版,在定义模版的时候规定一个带错误码的格式,到了统一处理异常的时候,再解释模版得到具体的信息。

2.3 业务错误

如果在业务处理的过程中,需要抛出异常中断处理流程,此时应该定义具体的错误码,并抛出有错误码的异常。

@Value
public class ErrorCodeException extends RuntimeException {
    ErrorCode code;
    public ErrorCodeException(ErrorCode code) {
        super(code.getMessage());
        this.code = code;
    }

    public static ErrorCodeException with(ErrorCode code) {
        return new ErrorCodeException(code);
    }
}

统一把带错误码异常转成带错误码的结果:

@ControllerAdvice
public class ErrorCodeExceptionHandler {
    @ExceptionHandler(ErrorCodeException.class)
    public CodeResult<Object> handleBindException(ErrorCodeException e) {
        return new CodeResult<Object>(e.getCode());
    }
}

2.4 未知错误

还有一类是预料不到的错误,比如数据库连接异常等。这类需要给个默认的异常处理,避免服务器的堆栈信息暴露到了服务器端。

public class SystemErrorCode {
    public static final long SCOPE_SYSTEM = 0L;
    public static final int CODE_SUCCESS = 0;
    public static final int CODE_UNKNOWN = 1;
    public static final int CODE_INVALID_PARAMETER = 101;

    public static final ErrorCode SUCCESS = ErrorCode.withSystem(SCOPE_SYSTEM, CODE_SUCCESS,
            "OK", "成功");
    public static final ErrorCode UNKNOWN = ErrorCode.withSystem(SCOPE_SYSTEM, CODE_UNKNOWN,
            "未知错误", "请联系系统管理处理");
    public static final ErrorCode INVALID_PARAMETER = ErrorCode.withSystem(SCOPE_SYSTEM, CODE_INVALID_PARAMETER,
            "非法参数错误", "请检查参数");
}

@ControllerAdvice
public class DefaultExceptionHandler {
    @ExceptionHandler(Exception.class)
    public CodeResult<Object> handleException(Exception ex) {
        return new CodeResult<Object>(SystemErrorCode.UNKNOWN);
    }
}

3 架构一小步

统一返回带错误码的结果。成功的结果,错误码为0,其它情况定义具体的错误码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值