封装ResultVO实现统一返回结果

2,052 阅读7分钟

背景

为了在开发中,返回到前端的数据内容格式趋于一致,我们在开发过程中最好能够将返回数据对象的格式进行约定,以便于开发对接过程中的约定速成;本章将带你了解如何设计统一返回对象,以及与其相关的知识内容。

封装返回结果对象

ResultVO对象封装

通过RestFul接口开发的接口,一般含有接口执行状态(成功、失败、失败描述、成功的数据返回对象)

因此我们可以将返回结果对象结构定义如下如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "统一请求的返回对象")
public class ResultVO<T> {
    @ApiModelProperty(value = "错误代码")
    private Integer code;
    @ApiModelProperty(value = "消息")
    private String msg;
    @ApiModelProperty(value = "对应返回数据")
    private T data;
}

定义了统一的的返回对象,那么一般我们需要考虑不同场景的输出和调用;比如成功,失败,或其他异常等情况的便捷调用。如果是因为某种业务原因需要返回失败操作,一般包含有错误码和错误信息,更有胜者包含对应的错误堆栈异常明细;那么就需要我们对于错误码做比较好的规划和设计了;

错误代码结构设计

这里我们设计了如下的错误码规划,首先定义一个IErrorCode(错误代码的接口类),里面定义两个方法,即获取错误码和错误消息的接口方法,如下

public interface IErrorCode {
    /**
     * 描述:得到错误码
     * @date 2020/11/21
     **/
    Integer getCode();
    /**
     * 描述:得到错误消息
     * @Author Hank
     **/
    String getMsg();
}

定义了接口类,然后我们再定义ErrorCode的接口实现枚举;如下

public enum  ErrorCode implements IErrorCode {
    /***
     * 1. 以下错误码的定义,需要提前与前端沟通
     * 2. 错误码按模块进行错误码规划
     * 3. 所有错误码枚举类均需要实现错误码接口类
     */
    SUCCESS(0,"操作成功"),
    SYSTEM_BUSY(10000,"系统繁忙,请稍后再试!"),
    FORM_VALIDATION_ERROR(10001,"表单验证错误"),
	// 用户登录方面错误码
    LOGIN_ERROR(101001, "你还未登陆,请及时登陆"),
    TOKEN_ERROR(101002, "登录凭证已过期,请重新登录");

   private Integer code;
   private String msg;

   ErrorCode(Integer code,String message){
       this.code=code;
       this.msg=message;
   }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }
}

小试牛刀

做了如上定义,我们就可以在对应的方法中去进行使用了,使用过程中,我们可以直接 return ResultVO;如下:

@RestController
@RequestMapping(value = "/test")
@Api(tags = "基础模块接口")
public class IndexCtrl {

    @ApiOperation(value = "hello 接口",notes = "hello接口的描述")
    @GetMapping(value = "/index")
    public ResultVO hello(){
        ResultVO rv=new ResultVO();
        rv.setCode(ErrorCode.SUCCESS.getCode());
        rv.setMsg(ErrorCode.SUCCESS.getMsg());
        return rv;
    }
}

效果增强

从上面的代码中我们可以看到,要做这样的返回,貌似还是比较繁琐,基本上要四行代码才能有一个完成的返回,并且ErrorCode类是固定的。

我们尝试将上面代码再做一次修改,希望达到如下效果:

  • 在使用返回对象的时候能够尽量简单,或开发中无感
  • 各个模块可以定义自己的错误码实现类,融入到基础框架中

ResultVO对象增强

需要达到如上两点,我们首先修改ResultVO类,丰富构造函数和支持枚举方法的传值,代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "统一请求的返回对象")
public class ResultVO<T> {
    @ApiModelProperty(value = "错误代码")
    private Integer code;
    @ApiModelProperty(value = "消息")
    private String msg;
    @ApiModelProperty(value = "对应返回数据")
    private T data;

    public ResultVO(int code, String mesage) {
        setCode(code);
        setMsg(mesage);
    }
    
    public ResultVO(IErrorCode errorCode, T data) {
        setCodeMessage(errorCode);
        setData(data);
    }

    public ResultVO setCodeMessage(IErrorCode codeMessage) {
        setCode(codeMessage.getCode());
        setMsg(codeMessage.getMsg());
        return this;
    }
}

封装助手工具

完成如上,我们还可以封装一个工具类RV,方便使用:

public class RV {

    /***
     * 成功的返回对象
     * @param data
     * @return
     */
    public static ResultVO success(Object data) {
        return new ResultVO(ErrorCode.SUCCESS,data);
    }

    /**
     * 失败的返回对象
     * @Param: ErrCodeInterface
     * @return: [ResultVO]
     *
     **/
    public static ResultVO fail(IErrorCode errorCode) {
        return new ResultVO().setCodeMessage(errorCode);
    }

    /**
     * 描述: 通过errorCode和数据对象参数,构建一个新的对象
     * @param [errorCode, data]
     * @return: [ResultVO]
     **/
    public static ResultVO result(IErrorCode errorCode,Object data){
        return new ResultVO(errorCode,data);
    }
}

简洁性验证

还是以上面应用代码为例,最终代码修改如下

@RestController
@RequestMapping(value = "/test")
@Api(tags = "基础模块接口")
public class IndexCtrl {

    @ApiOperation(value = "hello 接口",notes = "hello接口的描述")
    @GetMapping(value = "/index")
    public ResultVO hello(){
        return RV.success(null);;
    }
}

扩展性验证

前面我们描述了那么多,这里所谓的扩展性怎么理解呢,这里所谓的扩展性,更多是在不同业务系统,对于错误码的定义的扩展,比如一个我们项目里面,分成了多个不同的模块,但每个模块的实现都依赖于基础common包中封装的工具;对于错误码,我们不可能一次性在common中定义出所有模块的错误码;因此我们在设计的时候,特意定义了IErrorCode接口库类,默认由ErrorCode做了实现;

也就意味着,在common包中定义的这些类,没有特殊情况不用高频的修改,那我们在其他业务模块要定义自己的错误码可以怎么做呢。

如下,我们只需要在对应的业务模块,定义自己的错误码枚举类即可,比如在设备管理模块

/**java
 * new-retail
 * <p>
 * 错误码定义范围 10101-10200
 * </p>
 * @author Hank
 * @since 2020-11-21
 */
public enum DeviceErrorCode implements IErrorCode {
    DEVICE_OFFLINE(10101,"设备已离线"),
    COMMAND_ERROR(10102,"指令错误");

    private Integer code;
    private String msg;
    DeviceErrorCode(Integer code,String message){
        this.code=code;
        this.msg=message;
    }

    @Override
    public Integer getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }
}

在Controller中的应用

我们根据设备管理模块定义了对应的错误枚举类,也就意味着,在做该模块实际业务的时候,我们可以直接对外抛出对应的错误码,而不必考虑与其他模块的适配问题;其应用如下;

@PostMapping(value = "/restart")
public ResultVO restart(@RequestBody DeviceInfo deviceInfo) {
    load(deviceInfo);
    if (!deviceInfo.online()) {
        return RV.result(DeviceErrorCode.DEVICE_OFFLINE, deviceInfo);
    }
    return RV.success(deviceInfo);
}

在异常中的应用

在前章节我们讲到异常BusinessException的封装,但是我们只是做了简单的继承RuntimeException而已,没有继续深入;那我们再结合本章所讲到的错误代码进行完善增强

/**
 * new-retail-lesson
 * <p>
 * 自定义业务异常类
 * </p>
 * @author Hank
 * @since 2020-10-31
 */
public class BusinessException extends RuntimeException {
    private int code;
    private String detailMessage;

    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
        this.detailMessage = message;
    }

    public BusinessException(IErrorCode errorCode) {
        this(errorCode.getCode(),errorCode.getMsg());
    }

    public int getCode() {
        return code;
    }

    public String getDetailMessage() {
        return detailMessage;
    }
}

从上面代码中,我们可以看出,我们在BusinessException中增加了两个变量code,detailMessage;并对构造函数做了多样性处理,值得注意的是我们在构造函数中,增加了IErrorCode接口的参数;

既然在异常做了增强,那我们的异常拦截处也需要做响应的处理,找到我们前面定义的全局异常类GlobalExceptionHander,在对应的拦截BusinessException处做对应的处理,如下

@RestControllerAdvice
public class GlobalException {
    /**
     * 描述:业务异常拦截
     * @param
     * @date 2020/10/31
     * @Author Hank
     **/
    @ExceptionHandler(value = BusinessException.class)
    public ResultVO businessException(BusinessException e){
        ResultVO rv= new ResultVO(e.getCode(),e.getDetailMessage());
        return rv;
    }
}

完成了上面的基础工作,我们接下来看下在编码中能够如何使用;

  1. 在Controller中有业务异常抛出时,拿我们刚才的例子,代码调整如下,不用直接return,可以直接抛出对应的业务异常,由GlobalExceptionHander来兜底就行
@PostMapping(value = "/restart")
public ResultVO restart(@RequestBody DeviceInfo deviceInfo) {
    load(deviceInfo);
    if (!deviceInfo.online()) {
        throw new BusinessException(DeviceErrorCode.DEVICE_OFFLINE);
    }
    return RV.success(deviceInfo);
}
  1. 在业务代码中,可以直接抛出对应的业务异常,最终由GlobalException来兜底

当然在异常处理部分,我们可以根据场景需要,定义不同类型的异常,结构与上面类似,即可达到相同的效果

小结

上面我们主要介绍了

  1. ResultVO对象的封装
  2. 然后介绍了统一错误代码接口类和错误枚举类的设计
  3. 以及结合ResultVO对象和IErrorCode接口做了整合
  4. 通过IErrorCode与自定义异常结合再结合全局异常拦截与ResultVO对象结合做全局异常拦截;
  5. 最后还介绍了结合定义的ResultVO对象和IErrorCode接口类,可以如何做到异常方面的扩展。

以上为本章介绍的所有内容,希望对你有帮助,如果你在统一返回对象封装和异常封装方面有更好的不同实践,也欢迎在留言区进行留言。