springboot统一响应实体封装+统一异常类管理

Posted 一只搬砖的小松鼠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot统一响应实体封装+统一异常类管理相关的知识,希望对你有一定的参考价值。

前言:

  在日常前后端分离的接口开发过程中,需要我们按相应的格式给前端返回响应的数据,常见的方式就是我们后端自己封装一个包装类,每次返回给前端数据的时候都需要我们自己手动构建一。

短时间内来看或许并没有什么,但是一旦接口量变大,我们每个接口都去构建返回值的话,那样就会浪费我们很多的开发时间,所以我们就可以对响应的内容进行统一的处理,在写Controller中的方法时我们也可以不用统一返回类型了。例如:

    @ApiOperation(value = "分页查询告警信息")
    @PostMapping("findAlarmByPage")
    public PageInfo<TowerAlarmInfo> selectAll(@RequestBody AlarmReqVo alarmReqVo) 
        return towerAlarmInfoService.findByPage(alarmReqVo);
    

这样可以让我们减少很多的工作量。 可能有的朋友就会问:那我们的非空验证可一些异常的处理怎么办呢?   下面介绍的就是统一的异常类型管理。把我们后端所有能发生的异常进行统一的封装(可以自定义一个异常类型),封装之后再返回给前端相应的提示,那么前端就会清晰的知道后端发生了什么错误,是不是前端的锅哈哈哈哈。

 

响应实体的封装:

  首先肯定我们还是需要建一个统一返回的类:

 package com.dlxx.tower.app.util;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
 //这个注解表示变量为空的时候构造json就不带上这个变量
 @JsonInclude(JsonInclude.Include.NON_NULL)
 public class FrontResult 
     /**
      * 结果状态码
      */
     private Integer code;
     /**
      * 响应结果描述
      */
     private String message;
     /**
      * 返回数据
      */
     private Object data;
 
     public FrontResult(Object data) 
         this.data = data;
         this.code = ResultEnum.SUCCESS.getCode();
         this.message = "操作成功";
     
 
     /**
      * 静态方法,返回前端实体结果
      *
      * @param code    状态码
      * @param message 消息
      * @param data    数据
      * @return 前端实体结果
      */
     public static FrontResult build(Integer code, String message, Object data) 
         return new FrontResult(code, message, data);
     
 
     /**
      * 返回成功的结果实体
      *
      * @param message 消息
      * @param data    数据
      * @return 实体
      */
     public static FrontResult getSuccessResult(String message, Object data) 
         FrontResult result = new FrontResult();
         result.code = ResultEnum.SUCCESS.getCode();
         result.message = message;
         result.data = data;
         return result;
     
 
     /**
      * 返回无需data的成功结果实体
      *
      * @param message 消息内容
      * @return 返回结果
      */
     public static FrontResult getSuccessResultOnlyMessage(String message) 
         FrontResult result = new FrontResult();
         result.code = ResultEnum.SUCCESS.getCode();
         result.message = message;
         result.data = null;
         return result;
     
 
     /**
      * 获取一个异常结果
      *
      * @param code    错误码
      * @param message 自定义异常信息
      * @return FrontResult
      */
     public static FrontResult getExceptionResult(Integer code, String message) 
         FrontResult result = new FrontResult();
         result.code = (code == null) ? ResultEnum.CODE_EXCEPTION.getCode() : code;
         result.message = message.isEmpty() ? ResultEnum.CODE_EXCEPTION.getMsg() : message;
         return result;
     
 
     /**
      * 得到异常结果
      *
      * @param resultEnum 枚举结果代码
      * @return @link FrontResult
      */
     public static FrontResult getExceptionResult(ResultEnum resultEnum) 
         FrontResult result = new FrontResult();
         Integer code = resultEnum.getCode();
         String msg = resultEnum.getMsg();
         result.code = (code == null) ? ResultEnum.CODE_EXCEPTION.getCode() : code;
         result.message = msg.isEmpty() ? ResultEnum.CODE_EXCEPTION.getMsg() : msg;
         return result;
     
 

 

统一封装一下枚举类型ResultEnum

import lombok.AllArgsConstructor;

/**
 * 结果枚举
 *
 * @author longjun
 * @date 2023/04/11
 */
@AllArgsConstructor
public enum ResultEnum 
    /**
     * 成功
     */
    SUCCESS(200, "操作成功"),
    /**
     * 代码异常
     */
    CODE_EXCEPTION(500, "后端代码内部异常"),
    /**
     * 参数错误
     */
    PARAMETER_ERROR(999, "前端入参异常"),
    /**
     * 失败
     */
    FAIL(1111, "后端代码异常异常"),
    /**
     * 空点
     */
    NULL_POINT(1000, "空指针异常"),
    /**
     * 指数误差
     */
    OUT_OF_INDEX_ERROR(1001, "索引越界异常"),
    /**
     * 模型零
     */
    MODEL_NULL(1002, "前端入参实体的实体为空"),
    /**
     * 数据库错误
     */
    DATABASE_ERROR(1003, "数据库异常"),
    /**
     * 身份验证错误
     */
    AUTHENTICATION_ERROR(1004, "身份验证异常"),
    /**
     * 逻辑错误
     */
    LOGIC_ERROR(1005, "业务逻辑异常"),
    /**
     * 类没有找到
     */
    CLASS_NOT_FOUND(1006, "类未找到异常"),
    /**
     * sql异常
     */
    SQL_EXCEPTION(1007, "sql语句异常"),
    /**
     * io例外
     */
    IO_EXCEPTION(1008, "io异常"),
    /**
     * json解析错误
     */
    JSON_PARSE_ERROR(1009, "json转换异常"),

    NUMBER_FORMAT_ERROR(1010, "String转换为数字错误"),
    /**
     * 更新失败
     */
    UPDATE_FAIL(1011, "更新失败"),

    /**
     * 发送POST错误
     */
    SEND_POST_ERROR(1012, "发送POST请求异常"),
    /**
     * 短信发送错误
     */
    SMS_SEND_ERROR(1013, "短信发送失败");


    /**
     * 状态码
     */
    private Integer code;

    public Integer getCode() 
        return code;
    

    ResultEnum(Integer code) 
        this.code = code;
    

    private String msg;

    public String getMsg() 
        return msg;
    


 
 

基本的类型封装好了,接下来就是重头戏,关键代码也在这里。主要就是实现了ResponseBodyAdvice接口,其实是对加了@RestController(也就是@Controller+@ResponseBody)注解的处理器将要返回的值进行增强处理。

其实也就是采用了AOP的思想,对返回值进行一次修改。

 

  该接口一共有两个方法:

(1)supports  —— 判断是否要执行beforeBodyWrite方法,true为执行,false不执行  ——  通过supports方法,我们可以选择哪些类或哪些方法要对response进行处理,其余的则不处理。

(2)beforeBodyWrite  ——  对 response 处理的具体执行方法。

 package com.dlxx.tower.app.config;
 
 import com.dlxx.tower.app.exception.BizException;
 import com.dlxx.tower.app.util.FrontResult;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import org.springframework.core.MethodParameter;
 import org.springframework.http.MediaType;
 import org.springframework.http.converter.HttpMessageConverter;
 import org.springframework.http.server.ServerHttpRequest;
 import org.springframework.http.server.ServerHttpResponse;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
 
 /**
  * 响应控制器建议
  *
  * @author longjun
  * @description: 全局处理增强版Controller,避免Controller里返回数据每次都要用响应体来包装
  *
  * @date 2023/04/04
  */
 @RestControllerAdvice(basePackages = "com.dlxx.tower.app.controller")
 public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> 
     @Override
     public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) 
         // 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作,返回false
         return !returnType.getGenericParameterType().equals(FrontResult.class);
     
 
     @Override
     public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) 
         // String类型不能直接包装,所以要进行些特别的处理
         if (returnType.getGenericParameterType().equals(String.class)) 
             ObjectMapper objectMapper = new ObjectMapper();
             try 
                 // 将数据包装在ResultVO里后,再转换为json字符串响应给前端
                 return objectMapper.writeValueAsString(new FrontResult(data));
              catch (JsonProcessingException e) 
                 throw new BizException();
             
         
         // 将原本的数据包装在ResultVO里
         return new FrontResult(data);
     
 

这样基本上就可以对返回值进行统一封装了,下面就开始介绍全局异常;

统一异常处理:

首先自定义一个异常类型

 package com.dlxx.tower.app.exception;
 
 import com.dlxx.tower.app.util.FrontResult;
 import com.dlxx.tower.app.util.ResultEnum;
 
 /**
  * 业务异常
  * 自定义一个异常类,用于处理我们发生的业务异常
  *
  * @author longjun
  * @version 1.0.0
  * @date 2023/04/04
  */
 public class BizException extends RuntimeException 
 
     private static final long serialVersionUID = 1L;
 
     /**
      * 错误码
      */
     protected Integer errorCode;
     /**
      * 错误信息
      */
     protected String errorMsg;
 
     public BizException() 
         super();
     
 
     public BizException(FrontResult errorInfoInterface) 
         super(errorInfoInterface.getCode().toString());
         this.errorCode = errorInfoInterface.getCode();
         this.errorMsg = errorInfoInterface.getMessage();
     
 
     public BizException(FrontResult errorInfoInterface, Throwable cause) 
         super(errorInfoInterface.getCode().toString(), cause);
         this.errorCode = errorInfoInterface.getCode();
         this.errorMsg = errorInfoInterface.getMessage();
     
 
     public BizException(String errorMsg) 
         super(errorMsg);
         this.errorMsg = errorMsg;
     
 
     public BizException(Integer errorCode, String errorMsg) 
         super(String.valueOf(errorCode));
         this.errorCode = errorCode;
         this.errorMsg = errorMsg;
     
 
     public BizException(ResultEnum resultEnum) 
         super(String.valueOf(resultEnum.getCode()));
         this.errorCode = resultEnum.getCode();
         this.errorMsg = resultEnum.getMsg();
     
 
     public BizException(Integer errorCode, String errorMsg, Throwable cause) 
         super(String.valueOf(errorCode), cause);
         this.errorCode = errorCode;
         this.errorMsg = errorMsg;
     
 
 
     public Integer getErrorCode() 
         return errorCode;
     
 
     public void setErrorCode(Integer errorCode) 
         this.errorCode = errorCode;
     
 
     public String getErrorMsg() 
         return errorMsg;
     
 
     public void setErrorMsg(String errorMsg) 
         this.errorMsg = errorMsg;
     
 
     @Override
     public String getMessage() 
         return errorMsg;
     
 
     @Override
     public Throwable fillInStackTrace() 
         return this;
     
 
 

 

然后就是异常处理类

 package com.dlxx.tower.app.config;
 
 import com.dlxx.tower.app.exception.BizException;
 import com.dlxx.tower.app.util.FrontResult;
 import com.dlxx.tower.app.util.ResultEnum;
 import com.fasterxml.jackson.core.JsonParseException;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 
 import javax.servlet.http.HttpServletRequest;
 import java.io.IOException;
 import java.sql.SQLException;
 
 /**
  * 全局异常处理程序
  * <p>
  * 统一异常处理
  * 使用该注解表示开启了全局异常的捕获
  *
  * @author longjun
  * @version 1.0.0
  * @date 2023/04/04
  */
 @RestControllerAdvice
 @Slf4j
 public class GlobalExceptionHandler 
 
     /**
      * 处理自定义的业务异常
      *
      * @param req
      * @param e
      * @return
      */
     @ExceptionHandler(value = BizException.class)
     @ResponseBody
     public FrontResult bizExceptionHandler(HttpServletRequest req, BizException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生业务异常!原因是:", e.getErrorMsg());
         return FrontResult.getExceptionResult(e.getErrorCode(), e.getErrorMsg());
     
 
     /**
      * 处理空指针的异常
      *
      * @param req
      * @param e
      * @return
      */
     @ExceptionHandler(value = NullPointerException.class)
     @ResponseBody
     public FrontResult exceptionHandler(HttpServletRequest req, NullPointerException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生空指针异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.NULL_POINT);
     
 
 
     /**
      * 处理索引越界异常
      *
      * @param req
      * @param e
      * @return
      */
     @ExceptionHandler(value = IndexOutOfBoundsException.class)
     @ResponseBody
     public FrontResult exceptionHandler(HttpServletRequest req, IndexOutOfBoundsException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("索引越界异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.OUT_OF_INDEX_ERROR);
     
 
     /**
      * 处理类未找到异常
      *
      * @param req
      * @param e
      * @return
      */
     @ExceptionHandler(value = ClassNotFoundException.class)
     @ResponseBody
     public FrontResult exceptionHandler(HttpServletRequest req, ClassNotFoundException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生类未找到异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.CLASS_NOT_FOUND);
     
 
 
     /**
      * 处理SQL异常
      *
      * @param req
      * @param e
      * @return
      */
     @ExceptionHandler(value = SQLException.class)
     @ResponseBody
     public FrontResult exceptionHandler(HttpServletRequest req, SQLException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生SQL异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.SQL_EXCEPTION);
     
 
     /**
      * 处理IO异常
      *
      * @param req
      * @param e
      * @return
      */
     @ExceptionHandler(value = IOException.class)
     @ResponseBody
     public FrontResult exceptionHandler(HttpServletRequest req, IOException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生IO异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.IO_EXCEPTION);
     
 
 
     /**
      * json转换异常处理程序
      *
      * @param req 要求事情
      * @param e   e
      * @return @link FrontResult
      */
     @ExceptionHandler(value = JsonParseException.class)
     @ResponseBody
     public FrontResult exceptionHandler(HttpServletRequest req, JsonParseException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生JSON转换异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.JSON_PARSE_ERROR);
     
 
     /**
      * String转数字异常处理程序
      *
      * @param req 要求事情
      * @param e   e
      * @return @link FrontResult
      */
     @ExceptionHandler(value = NumberFormatException.class)
     @ResponseBody
     public FrontResult exceptionsHandler(HttpServletRequest req, NumberFormatException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生String转数字异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.NUMBER_FORMAT_ERROR);
     
 
     /**
      * 前端参数不匹配异常处理程序
      *
      * @param req 要求事情
      * @param e   e
      * @return @link FrontResult
      */
     @ExceptionHandler(value = HttpMessageNotReadableException.class)
     @ResponseBody
     public FrontResult exceptionsHandler(HttpServletRequest req, HttpMessageNotReadableException e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("发生前端参数不匹配异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.PARAMETER_ERROR);
     
 
     /**
      * 处理其他异常
      *
      * @param req
      * @param e
      * @return
      */
     @ExceptionHandler(value = Exception.class)
     @ResponseBody
     public FrontResult exceptionHandler(HttpServletRequest req, Exception e) 
         log.error("URL : " + req.getRequestURL().toString());
         log.error("HTTP_METHOD : " + req.getMethod());
         log.error("未知异常!原因是:", e);
         return FrontResult.getExceptionResult(ResultEnum.FAIL);
     
 
 
 

 

这里就已经可以实现这两个功能了

需要我们触发异常的时候就可以直接这样用

 throw new BizException(ResultEnum.AUTHENTICATION_ERROR.getCode(), "验证码校验失败。");

第一个参数就是我们封装的枚举,第二个就是自定义的信息,当然也可以直接用枚举,这些都可以自己修改的。

 

springboot统一处理返回实体与异常抛出


当返回异常时,是这样子的


    "timestamp": "2019-12-11T05:37:10.096+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "报错了",
    "path": "/test/testException"

但是,可能有时前台需要一个code值来判断抛出的错误,这样就需要我们封装去返回code值和message值。同时还可以拦截异常进行输出,是市面架构的通用做法。下文将介绍如何统一处理返回实体与异常抛出。

1.自定义返回对象

1.定义一个返回实体类,如果不进行处理,直接输出200和成功码。在接口输出最外层都使用该实体,这样前端就能有规范的获取数据与信息值。

public enum CommonEnum 

    SC_SUCCESS(200, "成功"),

    SC_NO_JURISDICTION(401, "没有权限,请联系管理员授权"),

    SC_INTERNAL_SERVER_ERROR_500(500, "接口异常");



public class Result<T> implements Serializable 

    /**
     * 返回编码
     */
    private Integer code = 200;

    /**
     * 返回信息
     */
    private String message = "操作成功";

    /**
     * 返回数据
     */
    private T data;

    /**
     * 成功标志
     */
    private boolean success = true;

    public Result() 
    

    public Result(Integer code, String Message) 
        this.code = code;
        this.message = Message;
    

    public int getCode() 
        return code;
    

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

    public String getMessage() 
        return this.message;
    

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

    public T getData() 
        return data;
    

    public void setData(T data) 
        this.data = data;
    

    public void SetResult(Integer code, String msg, T data) 
        this.setMessage(msg);
        this.setData(data);
        this.setCode(code);
    

    public static Result<Object> error(String msg) 
        return error(CommonEnum.SC_INTERNAL_SERVER_ERROR_500.getCode(), msg);
    

    public static Result<Object> error(int code, String msg) 
        Result<Object> r = new Result<Object>();
        r.setCode(code);
        r.setMessage(msg);
        return r;
    

2.异常的统一处理

  1. 自定义一个TemplateException ,需要继承Exception (或其他子类)。
public class TemplateException extends Exception 

    /**
     * 服务器状态码
     */
    private Integer code;

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

    public Integer getCode() 
        return code;
    

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

    @Override
    public String getMessage() 
        return message;
    

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

    public TemplateException(String message) 
        this.message = message;
    

    public TemplateException(Integer code, String message) 
        this.message = message;
        this.code = code;
    

  1. 定义一个ExceptionHandle,在异常时会进行逻辑处理。所以不需要在写try-catch了,直接在该类配置即可。
@RestControllerAdvice
public class DemoExceptionHandler 

    private final static Logger logger = LoggerFactory.getLogger(DemoExceptionHandler.class);

    /**
     * 自定义异常处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(TemplateException.class)
    public Result<?> handleTemplateException(TemplateException e) 
        logger.info(e.getMessage());
        e.printStackTrace();
        return Result.error(e.getCode(), e.getMessage());
    

    /**
     * exception异常处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Result<?> handleException(Exception e) 
        logger.info(e.getMessage());
        e.printStackTrace();
        return Result.error("接口异常,错误信息为" + e.getMessage());
    

    /**
     * 权限异常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(UnauthorizedException.class, AuthorizationException.class)
    public Result<?> handleAuthorizationException(AuthorizationException e) 
        logger.info(e.getMessage());
        e.printStackTrace();
        return Result.error(CommonEnum.SC_NO_JURISDICTION.getCode(), CommonEnum.SC_NO_JURISDICTION.getMessage());
    

这时候在抛出异常时,返回值为 。


    "code": 10066,
    "status": 0,
    "msg": "报错了",
    "data": null

以上是关于springboot统一响应实体封装+统一异常类管理的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot异常处理统一封装我来做-使用篇

项目封装:统一结果,统一异常,统一日志

springboot统一处理返回实体与异常抛出

springboot统一处理返回实体与异常抛出

springboot 实体参数校验 validate 抛出javax.validation.ConstraintViolationException异常 统一处理

SpringBoot返回统一的JSON标准格式