为了在开发中,返回到前端的数据内容格式趋于一致,我们在开发过程中最好能够将返回数据对象的格式进行约定,以便于开发对接过程中的约定速成;本章将带你了解如何设计统一返回对象,以及与其相关的知识内容。
通过RestFul接口开发的接口,一般含有接口执行状态(成功、失败、失败描述、成功的数据返回对象)
因此我们可以将返回结果对象结构定义如下如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "统一请求的返回对象")
public class ResultVO {
@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类,丰富构造函数和支持枚举方法的传值,代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(value = "统一请求的返回对象")
public class ResultVO {
@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包中定义的这些类,没有特殊情况不用高频的修改,那我们在其他业务模块要定义自己的错误码可以怎么做呢。
如下,我们只需要在对应的业务模块,定义自己的错误码枚举类即可,比如在设备管理模块
/**
* new-retail
*
* 错误码定义范围 10101-10200
*
* @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
*
* 自定义业务异常类
*
* @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;
}
}
完成了上面的基础工作,我们接下来看下在编码中能够如何使用;
@PostMapping(value = "/restart")
public ResultVO restart(@RequestBody DeviceInfo deviceInfo) {
load(deviceInfo);
if (!deviceInfo.online()) {
throw new BusinessException(DeviceErrorCode.DEVICE_OFFLINE);
}
return RV.success(deviceInfo);
}
当然在异常处理部分,我们可以根据场景需要,定义不同类型的异常,结构与上面类似,即可达到相同的效果
上面我们主要介绍了
以上为本章介绍的所有内容,希望对你有帮助,如果你在统一返回对象封装和异常封装方面有更好的不同实践,也欢迎在留言区进行留言。
想要了解更多信息,可关注本公众号(一起学开源);或请长按以下二维码添加助手。将拉你加入社区进行更多交流