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

发布时间 2023-04-18 11:45:25作者: 阿龙er

前言:

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

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

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

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

 

响应实体的封装:

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

  1 package com.dlxx.tower.app.util;
  2 
  3 import com.fasterxml.jackson.annotation.JsonInclude;
  4 import lombok.AllArgsConstructor;
  5 import lombok.Data;
  6 import lombok.NoArgsConstructor;
  7 
  8 @Data
  9 @AllArgsConstructor
 10 @NoArgsConstructor
 11 //这个注解表示变量为空的时候构造json就不带上这个变量
 12 @JsonInclude(JsonInclude.Include.NON_NULL)
 13 public class FrontResult {
 14     /**
 15      * 结果状态码
 16      */
 17     private Integer code;
 18     /**
 19      * 响应结果描述
 20      */
 21     private String message;
 22     /**
 23      * 返回数据
 24      */
 25     private Object data;
 26 
 27     public FrontResult(Object data) {
 28         this.data = data;
 29         this.code = ResultEnum.SUCCESS.getCode();
 30         this.message = "操作成功";
 31     }
 32 
 33     /**
 34      * 静态方法,返回前端实体结果
 35      *
 36      * @param code    状态码
 37      * @param message 消息
 38      * @param data    数据
 39      * @return 前端实体结果
 40      */
 41     public static FrontResult build(Integer code, String message, Object data) {
 42         return new FrontResult(code, message, data);
 43     }
 44 
 45     /**
 46      * 返回成功的结果实体
 47      *
 48      * @param message 消息
 49      * @param data    数据
 50      * @return 实体
 51      */
 52     public static FrontResult getSuccessResult(String message, Object data) {
 53         FrontResult result = new FrontResult();
 54         result.code = ResultEnum.SUCCESS.getCode();
 55         result.message = message;
 56         result.data = data;
 57         return result;
 58     }
 59 
 60     /**
 61      * 返回无需data的成功结果实体
 62      *
 63      * @param message 消息内容
 64      * @return 返回结果
 65      */
 66     public static FrontResult getSuccessResultOnlyMessage(String message) {
 67         FrontResult result = new FrontResult();
 68         result.code = ResultEnum.SUCCESS.getCode();
 69         result.message = message;
 70         result.data = null;
 71         return result;
 72     }
 73 
 74     /**
 75      * 获取一个异常结果
 76      *
 77      * @param code    错误码
 78      * @param message 自定义异常信息
 79      * @return FrontResult
 80      */
 81     public static FrontResult getExceptionResult(Integer code, String message) {
 82         FrontResult result = new FrontResult();
 83         result.code = (code == null) ? ResultEnum.CODE_EXCEPTION.getCode() : code;
 84         result.message = message.isEmpty() ? ResultEnum.CODE_EXCEPTION.getMsg() : message;
 85         return result;
 86     }
 87 
 88     /**
 89      * 得到异常结果
 90      *
 91      * @param resultEnum 枚举结果代码
 92      * @return {@link FrontResult}
 93      */
 94     public static FrontResult getExceptionResult(ResultEnum resultEnum) {
 95         FrontResult result = new FrontResult();
 96         Integer code = resultEnum.getCode();
 97         String msg = resultEnum.getMsg();
 98         result.code = (code == null) ? ResultEnum.CODE_EXCEPTION.getCode() : code;
 99         result.message = msg.isEmpty() ? ResultEnum.CODE_EXCEPTION.getMsg() : msg;
100         return result;
101     }
102 }

 

统一封装一下枚举类型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 处理的具体执行方法。

 1 package com.dlxx.tower.app.config;
 2 
 3 import com.dlxx.tower.app.exception.BizException;
 4 import com.dlxx.tower.app.util.FrontResult;
 5 import com.fasterxml.jackson.core.JsonProcessingException;
 6 import com.fasterxml.jackson.databind.ObjectMapper;
 7 import org.springframework.core.MethodParameter;
 8 import org.springframework.http.MediaType;
 9 import org.springframework.http.converter.HttpMessageConverter;
10 import org.springframework.http.server.ServerHttpRequest;
11 import org.springframework.http.server.ServerHttpResponse;
12 import org.springframework.web.bind.annotation.RestControllerAdvice;
13 import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
14 
15 /**
16  * 响应控制器建议
17  *
18  * @author longjun
19  * @description: 全局处理增强版Controller,避免Controller里返回数据每次都要用响应体来包装
20  *
21  * @date 2023/04/04
22  */
23 @RestControllerAdvice(basePackages = {"com.dlxx.tower.app.controller"})
24 public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
25     @Override
26     public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> aClass) {
27         // 如果接口返回的类型本身就是ResultVO那就没有必要进行额外的操作,返回false
28         return !returnType.getGenericParameterType().equals(FrontResult.class);
29     }
30 
31     @Override
32     public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
33         // String类型不能直接包装,所以要进行些特别的处理
34         if (returnType.getGenericParameterType().equals(String.class)) {
35             ObjectMapper objectMapper = new ObjectMapper();
36             try {
37                 // 将数据包装在ResultVO里后,再转换为json字符串响应给前端
38                 return objectMapper.writeValueAsString(new FrontResult(data));
39             } catch (JsonProcessingException e) {
40                 throw new BizException();
41             }
42         }
43         // 将原本的数据包装在ResultVO里
44         return new FrontResult(data);
45     }
46 }
View Code

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

统一异常处理:

首先自定义一个异常类型

 1 package com.dlxx.tower.app.exception;
 2 
 3 import com.dlxx.tower.app.util.FrontResult;
 4 import com.dlxx.tower.app.util.ResultEnum;
 5 
 6 /**
 7  * 业务异常
 8  * 自定义一个异常类,用于处理我们发生的业务异常
 9  *
10  * @author longjun
11  * @version 1.0.0
12  * @date 2023/04/04
13  */
14 public class BizException extends RuntimeException {
15 
16     private static final long serialVersionUID = 1L;
17 
18     /**
19      * 错误码
20      */
21     protected Integer errorCode;
22     /**
23      * 错误信息
24      */
25     protected String errorMsg;
26 
27     public BizException() {
28         super();
29     }
30 
31     public BizException(FrontResult errorInfoInterface) {
32         super(errorInfoInterface.getCode().toString());
33         this.errorCode = errorInfoInterface.getCode();
34         this.errorMsg = errorInfoInterface.getMessage();
35     }
36 
37     public BizException(FrontResult errorInfoInterface, Throwable cause) {
38         super(errorInfoInterface.getCode().toString(), cause);
39         this.errorCode = errorInfoInterface.getCode();
40         this.errorMsg = errorInfoInterface.getMessage();
41     }
42 
43     public BizException(String errorMsg) {
44         super(errorMsg);
45         this.errorMsg = errorMsg;
46     }
47 
48     public BizException(Integer errorCode, String errorMsg) {
49         super(String.valueOf(errorCode));
50         this.errorCode = errorCode;
51         this.errorMsg = errorMsg;
52     }
53 
54     public BizException(ResultEnum resultEnum) {
55         super(String.valueOf(resultEnum.getCode()));
56         this.errorCode = resultEnum.getCode();
57         this.errorMsg = resultEnum.getMsg();
58     }
59 
60     public BizException(Integer errorCode, String errorMsg, Throwable cause) {
61         super(String.valueOf(errorCode), cause);
62         this.errorCode = errorCode;
63         this.errorMsg = errorMsg;
64     }
65 
66 
67     public Integer getErrorCode() {
68         return errorCode;
69     }
70 
71     public void setErrorCode(Integer errorCode) {
72         this.errorCode = errorCode;
73     }
74 
75     public String getErrorMsg() {
76         return errorMsg;
77     }
78 
79     public void setErrorMsg(String errorMsg) {
80         this.errorMsg = errorMsg;
81     }
82 
83     @Override
84     public String getMessage() {
85         return errorMsg;
86     }
87 
88     @Override
89     public Throwable fillInStackTrace() {
90         return this;
91     }
92 
93 }
View Code

 

然后就是异常处理类

  1 package com.dlxx.tower.app.config;
  2 
  3 import com.dlxx.tower.app.exception.BizException;
  4 import com.dlxx.tower.app.util.FrontResult;
  5 import com.dlxx.tower.app.util.ResultEnum;
  6 import com.fasterxml.jackson.core.JsonParseException;
  7 import lombok.extern.slf4j.Slf4j;
  8 import org.springframework.http.converter.HttpMessageNotReadableException;
  9 import org.springframework.web.bind.annotation.ExceptionHandler;
 10 import org.springframework.web.bind.annotation.ResponseBody;
 11 import org.springframework.web.bind.annotation.RestControllerAdvice;
 12 
 13 import javax.servlet.http.HttpServletRequest;
 14 import java.io.IOException;
 15 import java.sql.SQLException;
 16 
 17 /**
 18  * 全局异常处理程序
 19  * <p>
 20  * 统一异常处理
 21  * 使用该注解表示开启了全局异常的捕获
 22  *
 23  * @author longjun
 24  * @version 1.0.0
 25  * @date 2023/04/04
 26  */
 27 @RestControllerAdvice
 28 @Slf4j
 29 public class GlobalExceptionHandler {
 30 
 31     /**
 32      * 处理自定义的业务异常
 33      *
 34      * @param req
 35      * @param e
 36      * @return
 37      */
 38     @ExceptionHandler(value = BizException.class)
 39     @ResponseBody
 40     public FrontResult bizExceptionHandler(HttpServletRequest req, BizException e) {
 41         log.error("URL : " + req.getRequestURL().toString());
 42         log.error("HTTP_METHOD : " + req.getMethod());
 43         log.error("发生业务异常!原因是:{}", e.getErrorMsg());
 44         return FrontResult.getExceptionResult(e.getErrorCode(), e.getErrorMsg());
 45     }
 46 
 47     /**
 48      * 处理空指针的异常
 49      *
 50      * @param req
 51      * @param e
 52      * @return
 53      */
 54     @ExceptionHandler(value = NullPointerException.class)
 55     @ResponseBody
 56     public FrontResult exceptionHandler(HttpServletRequest req, NullPointerException e) {
 57         log.error("URL : " + req.getRequestURL().toString());
 58         log.error("HTTP_METHOD : " + req.getMethod());
 59         log.error("发生空指针异常!原因是:", e);
 60         return FrontResult.getExceptionResult(ResultEnum.NULL_POINT);
 61     }
 62 
 63 
 64     /**
 65      * 处理索引越界异常
 66      *
 67      * @param req
 68      * @param e
 69      * @return
 70      */
 71     @ExceptionHandler(value = IndexOutOfBoundsException.class)
 72     @ResponseBody
 73     public FrontResult exceptionHandler(HttpServletRequest req, IndexOutOfBoundsException e) {
 74         log.error("URL : " + req.getRequestURL().toString());
 75         log.error("HTTP_METHOD : " + req.getMethod());
 76         log.error("索引越界异常!原因是:", e);
 77         return FrontResult.getExceptionResult(ResultEnum.OUT_OF_INDEX_ERROR);
 78     }
 79 
 80     /**
 81      * 处理类未找到异常
 82      *
 83      * @param req
 84      * @param e
 85      * @return
 86      */
 87     @ExceptionHandler(value = ClassNotFoundException.class)
 88     @ResponseBody
 89     public FrontResult exceptionHandler(HttpServletRequest req, ClassNotFoundException e) {
 90         log.error("URL : " + req.getRequestURL().toString());
 91         log.error("HTTP_METHOD : " + req.getMethod());
 92         log.error("发生类未找到异常!原因是:", e);
 93         return FrontResult.getExceptionResult(ResultEnum.CLASS_NOT_FOUND);
 94     }
 95 
 96 
 97     /**
 98      * 处理SQL异常
 99      *
100      * @param req
101      * @param e
102      * @return
103      */
104     @ExceptionHandler(value = SQLException.class)
105     @ResponseBody
106     public FrontResult exceptionHandler(HttpServletRequest req, SQLException e) {
107         log.error("URL : " + req.getRequestURL().toString());
108         log.error("HTTP_METHOD : " + req.getMethod());
109         log.error("发生SQL异常!原因是:", e);
110         return FrontResult.getExceptionResult(ResultEnum.SQL_EXCEPTION);
111     }
112 
113     /**
114      * 处理IO异常
115      *
116      * @param req
117      * @param e
118      * @return
119      */
120     @ExceptionHandler(value = IOException.class)
121     @ResponseBody
122     public FrontResult exceptionHandler(HttpServletRequest req, IOException e) {
123         log.error("URL : " + req.getRequestURL().toString());
124         log.error("HTTP_METHOD : " + req.getMethod());
125         log.error("发生IO异常!原因是:", e);
126         return FrontResult.getExceptionResult(ResultEnum.IO_EXCEPTION);
127     }
128 
129 
130     /**
131      * json转换异常处理程序
132      *
133      * @param req 要求事情
134      * @param e   e
135      * @return {@link FrontResult}
136      */
137     @ExceptionHandler(value = JsonParseException.class)
138     @ResponseBody
139     public FrontResult exceptionHandler(HttpServletRequest req, JsonParseException e) {
140         log.error("URL : " + req.getRequestURL().toString());
141         log.error("HTTP_METHOD : " + req.getMethod());
142         log.error("发生JSON转换异常!原因是:", e);
143         return FrontResult.getExceptionResult(ResultEnum.JSON_PARSE_ERROR);
144     }
145 
146     /**
147      * String转数字异常处理程序
148      *
149      * @param req 要求事情
150      * @param e   e
151      * @return {@link FrontResult}
152      */
153     @ExceptionHandler(value = NumberFormatException.class)
154     @ResponseBody
155     public FrontResult exceptionsHandler(HttpServletRequest req, NumberFormatException e) {
156         log.error("URL : " + req.getRequestURL().toString());
157         log.error("HTTP_METHOD : " + req.getMethod());
158         log.error("发生String转数字异常!原因是:", e);
159         return FrontResult.getExceptionResult(ResultEnum.NUMBER_FORMAT_ERROR);
160     }
161 
162     /**
163      * 前端参数不匹配异常处理程序
164      *
165      * @param req 要求事情
166      * @param e   e
167      * @return {@link FrontResult}
168      */
169     @ExceptionHandler(value = HttpMessageNotReadableException.class)
170     @ResponseBody
171     public FrontResult exceptionsHandler(HttpServletRequest req, HttpMessageNotReadableException e) {
172         log.error("URL : " + req.getRequestURL().toString());
173         log.error("HTTP_METHOD : " + req.getMethod());
174         log.error("发生前端参数不匹配异常!原因是:", e);
175         return FrontResult.getExceptionResult(ResultEnum.PARAMETER_ERROR);
176     }
177 
178     /**
179      * 处理其他异常
180      *
181      * @param req
182      * @param e
183      * @return
184      */
185     @ExceptionHandler(value = Exception.class)
186     @ResponseBody
187     public FrontResult exceptionHandler(HttpServletRequest req, Exception e) {
188         log.error("URL : " + req.getRequestURL().toString());
189         log.error("HTTP_METHOD : " + req.getMethod());
190         log.error("未知异常!原因是:", e);
191         return FrontResult.getExceptionResult(ResultEnum.FAIL);
192     }
193 
194 
195 }
View Code

 

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

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

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

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