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.异常的统一处理
- 自定义一个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;
- 定义一个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 实体参数校验 validate 抛出javax.validation.ConstraintViolationException异常 统一处理