如何优雅实现后端接口统一返回对象

238 阅读7分钟

一、业务场景****

目前主流开发方式都是前后端分离的,定义一种统一的返回格式,在前后端进行沟通时是非常有必要的,减少大家的沟通成本,大家基于这个约定去理解,出现问题能够快速定位。 格式统一规范,也是减少联调挨骂的基本保障。接下来就介绍一下如何优雅实现统一的响应对象。

二、统一的响应对象字段理解****

{

"code": 0,

"message": "成功",

"detailMessage": "成功",

"data": true

}

code: 返回结果的状态码,通常都是4位,比如A业务:1000-1999,B业务:2000-2999,C业务:3000-3999,不同的数字开头代表不同的业务。但是大多数项目中默认规定了几个固定的状态码:

 0    成功

 -1   服务发生了一点小故障,请稍后重试

 -2   socket 超时异常

 -3   io异常

1000  参数不合法

1001  请勿重复操作

1002  请求太频繁,请稍后重试

message: 返回结果的描述消息,展示给用户看的

detailMessage: 内部开发人员看的消息(详细的错误信息,主要方便问题定位)

data: 返回给调用方的业务数据(通常都是json数据)

三、前后端接口约束****

1. 所有查询都用get;

2. 添加、修改、删除post

3. 前端:get统一key value

4. 前端:如果是数组,前端传递数组结构: key[0]=value.........

5. 前端:post参数如果是1~2个,例如: http://192.168.2.1:8080/xxx/{id}/{name}

6. 前端:post多个参数,走body

7. 前端:上传统一fromdata

8. 前端:下载附件方式

9. 前端:新增、编辑分开写

10. 前端:表格初始加载+重置+搜索是同一个接口

11. 前端:所有下拉列表,数据量大要搜索

12. 前端:除登录之外所有接口在请求头中信息加token,单点登录

13. 前端数据加密:前端明文,后端返回结果均用AES加密(16位)

14. 前端批量导入加密:前端MD5加密

15. 前端查询: 单条数据/多条都用数组,根据id查询返回对象(单个展示)

四、实现方案及实现步骤****

4.1 定义一个通用的统一返回码接口:IRespResultCode.java,接口里面规范子类对象要实现的方法****

例如:getCode(),getMessage(),getDetailMessage()

代码如下:****

/***
*** 定义一个通用的统一返回码接口,接口里面规范子类对象要实现的方法**
/
public interface IRespResultCode {
/
*
*** 结果码**
*/
***Integer getCode();

     /***
*** 返回消息**
*/
***String getMessage();

     /***
*** 详细的错误消息(开发者看)**
*/
***String getDetailMessage();

}

4.2 定义一个通用的统一返回码对象:RespResultCode.java实现IRespResultCode接口 ,主要放通用的返回结果信息****

例如:请求成功、系统异常、网络异常、参数不合法等信息

代码如下:****

/***
*** 通用返回码对象(主要放各个模块的通用错误码)**
/
public enum RespResultCode implements IRespResultCode{
/
*
*** 成功**
/
OK(0, "成功"),
/

*** 系统异常
*
/
SYS_EXCEPTION(-1, "服务发生了一点小故障,请稍后重试"),
/

*** socket超时异常
*
/
SOCKET_TIMEOUT_EXCEPTION(-2, "socket 超时异常"),
/

*** io异常
*
/
IO_EXCEPTION(-3, "io 异常"),
/

*** 参数不合法
*
/
ERR_PARAM_NOT_LEGAL(1000, "参数不合法"),
/

*** 请勿重复操作
*
/
REPEATED_OPERATE(1001, "请勿重复操作"),
/

*** 请求太频繁,请稍后重试
*
/
REPEATED_BUSY(1002, "请求太频繁,请稍后重试");
/

*** 错误码
*
*/
***private Integer code;

     /***
*** 错误消息**
*/
***private String message;

     /***
*** 详细的错误消息(开发看)**
*/
***private String detailMessage;

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

    RespResultCode(Integer code, String message,String detailMessage) {
this.code = code;
this.message = message;
this.detailMessage = detailMessage;
}

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

    @Override
public String getMessage() {
return message;
}

    public String getDetailMessage() {
return detailMessage;
}

}

4.3 定义一个处理返回结果的工具类RespResult.java,定义一些通用的返回结果的方法****

例如:返回成功结果的success方法、返回失败结果的error方法。


代码如下:****


import java.io.Serializable;
/***
*** 定义一些通用的返回结果的方法,例如返回成功结果的success方法、返回失败结果的error方法。**
*/
*public class RespResult implements Serializable {

     /***
*** 结果码**
*/
***private Integer code = RespResultCode.OK.getCode();

     /***
*** 结果信息**
*/
***private String message = RespResultCode.OK.getMessage();

     /***
*** 详细的错误消息(开发看)**
*/
***private String detailMessage = RespResultCode.OK.getDetailMessage();

    public Integer getCode() {
return code;
}

    public void setCode(Integer code) {
this.code = code;
}

    public String getMessage() {
return message;
}

    public void setMessage(String message) {
this.message = message;
}

    public T getData() {
return data;
}

    public String getDetailMessage() {
return detailMessage;
}

    public void setDetailMessage(String detailMessage) {
this.detailMessage = detailMessage;
}

     /***
*** 返回结果的数据对象**
*/
***private T data;

    public RespResult setData(T data) {
this.data = data;
return this;
}

    public RespResult() {

    }

    public RespResult(Integer code) {
this.code = code;
}

    public RespResult(Integer code, String message) {
this.code = code;
this.message = message;
}

    public RespResult(IRespResultCode respResultCode) {
this.code = respResultCode.getCode();
this.message = respResultCode.getMessage();
}

    public RespResult(Integer code, String message, String detailMessage) {
this.code = code;
this.message = message;
this.detailMessage = detailMessage;
}

    public static RespResult success() {
return new RespResult<>(RespResultCode.OK);
}

    public static RespResult success(T data) {
RespResult ar = new RespResult(RespResultCode.OK);
ar.setData(data);
return ar;
}

    public static RespResult error(String msg) {
return new RespResult(RespResultCode.SYS_EXCEPTION.getCode(), msg);
}

    public static RespResult error(Integer code, String msg) {
return new RespResult(code, msg);
}

    public static RespResult error(Integer code, String msg, String detailMessage) {
return new RespResult(code, msg, detailMessage);
}

    public static RespResult error(Integer code, String msg, T data) {
return new RespResult(code, msg).setData(data);
}
}

4.4 封装业务异常类BusinessException.java,继承运行时异常,业务层出现一些逻辑的错误可以抛出BusinessException异常****

代码如下:****

/***
*** 业务异常类**
*/
*public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;

   public BusinessException(String msg) {
super(msg);
}

    public BusinessException(Throwable cause) {
super(cause);
}

    public BusinessException(String message, Throwable cause) {
super(message,cause);
}

    public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writeableStackTrace) {
super(message,cause, enableSuppression, writeableStackTrace);
}

}

4.5 封装参数校验异常ParamException.java,继承运行时异常,接口层出现一些参数校验错误可以抛出ParamException异常****

代码如下:****

/***
*** 参数校验异常**
*/
*@NoArgsConstructor
public class ParamException extends RuntimeException {
private static final long serialVersionUID = 1L;

    public ParamException(String message) {
super(message);
}

    public ParamException(int code, String message) {
super(message);
}

    public ParamException(Throwable cause) {
super(cause);
}

    public ParamException(String message, Throwable cause) {
super(message, cause);
}

    public ParamException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

4.6 定义通过全局异常拦截器GlobalExceptionHandler拦截各类异常,对返回结果的code和message做二次加工****

Springboot的全局异常查是通过两个注解@RestControllerAdvice和@ExceptionHandler来实现的。只有代码出错或者throw出来的异常才会被捕捉处理,如果被catch的异常,就不会被捕捉,除非catch之后再throw异常。

@RestControllerAdvice:增强型控制器,对于控制器的全局配置放在同一个位置,全局异常的注解,放在类上。

@RestControllerAdvice默认只会处理controller层抛出的异常,如果需要处理service层的异常,需要定义一个自定义的MyException来继承RuntimeException类,然后@ExceptionHandler(MyException.class)即可。

@ExceptionHandler:指明需要处理的异常类型以及子类。注解放在方法上面。

代码如下:****


import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
/***
*** 自定义全局异常处理类**
*/
*@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
public RespResult handleError(MethodArgumentTypeMismatchException e) {
log.error("Method Argument Type Mismatch:{}", e.getMessage(), e);
return RespResult.error("Method Argument Type Mismatch");
}

    @ExceptionHandler(MissingServletRequestParameterException.class)
public RespResult handleError(MissingServletRequestParameterException e) {
log.error("Method Request Parameter :{}", e.getMessage(), e);
return RespResult.error("Method Request Parameter");
}

    @ExceptionHandler(MethodArgumentNotValidException.class)
public RespResult handleError(MethodArgumentNotValidException e) {
log.error("Method Request Not Valid :{}", e.getMessage(), e);
BindingResult bindingResult = e.getBindingResult();
FieldError fieldError = bindingResult.getFieldError();
String message = String.format("%s:%s", fieldError.getField(), fieldError.getDefaultMessage());
return RespResult.error(RespResultCode.ERR_PARAM_NOT_LEGAL.getCode(),
RespResultCode.ERR_PARAM_NOT_LEGAL.getMessage(),
message);
}

    @ExceptionHandler(BindException.class)
public RespResult handleError(BindException e) {
log.error("Bind Exception :{}", e.getMessage(), e);
FieldError fieldError = e.getFieldError();
String message = String.format("%s:%s", fieldError.getField(), fieldError.getDefaultMessage());
return RespResult.error(RespResultCode.ERR_PARAM_NOT_LEGAL.getCode(), message);
}

    @ExceptionHandler(HttpMessageNotReadableException.class)
public RespResult handleError(HttpMessageNotReadableException e) {
log.error("Http Message Not Readable Exception :{}", e.getMessage(), e);
return RespResult.error("请求的参数类型与方法接收的参数类型不匹配, 请检查!");
}

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public RespResult handleError(HttpRequestMethodNotSupportedException e) {
log.error("Http Request Method Not Support Exception :{}", e.getMessage(), e);
String message = String.format("%s方法类型不支持,请检查!", e.getMethod());
return RespResult.error(message);
}

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public RespResult handleError(HttpMediaTypeNotSupportedException e) {
log.error("Http Media Type Not Support Exception :{}", e.getMessage(), e);
String message = String.format("不支持%s content-type,请检查!", e.getContentType());
return RespResult.error(message);
}

    @ExceptionHandler(ParamException.class)
public RespResult handleError(ParamException e) {
log.error("参数不合法 :{}", e.getMessage(), e);
return RespResult.error(RespResultCode.ERR_PARAM_NOT_LEGAL.getCode(),
RespResultCode.ERR_PARAM_NOT_LEGAL.getMessage(), e.getMessage());
}

    @ExceptionHandler(BusinessException.class)
public RespResult handleError(BusinessException e) {
log.error("业务出现异常 :{}", e.getMessage(), e);
return RespResult.error(RespResultCode.SYS_EXCEPTION.getCode(),
RespResultCode.SYS_EXCEPTION.getMessage(), e.getMessage());
}

    @ExceptionHandler(Exception.class)
public RespResult handleError(Exception e) {
log.error("全局异常信息,异常堆栈信息 :{}", e.getMessage(), e);
return RespResult.error(RespResultCode.SYS_EXCEPTION.getCode(),
RespResultCode.SYS_EXCEPTION.getMessage(), e.getMessage());
}

    @ExceptionHandler(Throwable.class)
public RespResult handleError(Throwable e) {
log.error("Throwable Server Exception :{}", e.getMessage(), e);
return RespResult.error(RespResultCode.SYS_EXCEPTION.getCode(),
RespResultCode.SYS_EXCEPTION.getMessage(), e.getMessage());
}

}

 

4.7 一般对业务方法,比如controller层返回http code 200,对所有异常进行捕获,返回的message二次加工展示给调用方。****