在项目整体架构设计的时候,我们经常需要做以下工作:
- 返回值的统一封装处理,因为只有规范好统一的返回值格式,才能不会给接口使用者带来疑惑和方便前端对接口的统一处理。
- 对异常码进行严格规定,什么错误返回什么码制,这样前端就可以根据不同的码制进行错误提示,同时也方便三方调用者对异常进行处理。要注意,微服务架构,不同的微服务之间的码制前缀要做区分,这样我们就可以根据码制很快定位到是哪个微服务出现问题。
- 对异常的统一拦截,这一点非常重要,因为一旦对异常没有做统一处理,严重就会导致堆栈乃至sql信息暴露给前端,这是非常严重的事故。
所以今天我会给大家展示如何对返回值进行统一封装,如何对异常进行统一拦截,异常码定义,根据自己的业务自行定义即可。
1. 返回值的封装
分为两步,首先定义返回值类型和字段,然后定义BaseController。
1.1 响应信息主体
我定义的这个涵盖了我们要返回的各种情况,大家可以参考
/**
* 响应信息主体
*/
@Data
@NoArgsConstructor
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 成功
*/
public static final int SUCCESS = 200;
/**
* 失败
*/
public static final int FAIL = 500;
private int code;
private String msg;
private T data;
public static <T> R<T> ok() {
return restResult(null, SUCCESS, "操作成功");
}
public static <T> R<T> ok(T data) {
return restResult(data, SUCCESS, "操作成功");
}
public static <T> R<T> ok(String msg) {
return restResult(null, SUCCESS, msg);
}
public static <T> R<T> ok(String msg, T data) {
return restResult(data, SUCCESS, msg);
}
public static <T> R<T> fail() {
return restResult(null, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg) {
return restResult(null, FAIL, msg);
}
public static <T> R<T> fail(T data) {
return restResult(data, FAIL, "操作失败");
}
public static <T> R<T> fail(String msg, T data) {
return restResult(data, FAIL, msg);
}
public static <T> R<T> fail(int code, String msg) {
return restResult(null, code, msg);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static <T> R<T> warn(String msg) {
return restResult(null, HttpStatus.WARN, msg);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static <T> R<T> warn(String msg, T data) {
return restResult(data, HttpStatus.WARN, msg);
}
private static <T> R<T> restResult(T data, int code, String msg) {
R<T> r = new R<>();
r.setCode(code);
r.setData(data);
r.setMsg(msg);
return r;
}
public static <T> Boolean isError(R<T> ret) {
return !isSuccess(ret);
}
public static <T> Boolean isSuccess(R<T> ret) {
return R.SUCCESS == ret.getCode();
}
}
1.2 BaseController 的写法
**
* web层通用数据处理
*/
public class BaseController {
/**
* 响应返回结果
*
* @param rows 影响行数
* @return 操作结果
*/
protected R<Void> toAjax(int rows) {
return rows > 0 ? R.ok() : R.fail();
}
/**
* 响应返回结果
*
* @param result 结果
* @return 操作结果
*/
protected R<Void> toAjax(boolean result) {
return result ? R.ok() : R.fail();
}
//同时也可以将一些常用的方法放在这里,比如获取登录用户信息
}
1.3 示例
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/workflow/category")
public class WfCategoryController extends BaseController {
@GetMapping("/eventTest")
public R<Void> eventTest(){
applicationEventPublisher.publishEvent(new UserChangePasswordEvent("11111"));
return R.ok();
}
}
返回值:
2. 全局异常拦截
全局异常拦截的本质实际上采用的是spring的AOP拦截实现的,然后根据异常类型映射到不同的ExceptionHandler处理方法上。
2.1 引入AOP依赖
必须引入AOP依赖,我刚开始忘了依赖,怎么试都不对,所以一定要检查好,不要忘了。
<!-- SpringBoot 拦截器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 写异常拦截器
在写异常拦截器的时候,我们得要分为这么几个部分,首先是自定义的业务异常拦截,验证类异常,比如参数校验类异常拦截处理,还有未知的运行时异常拦截,如果使用到Mybatis,还得要注意Mybatis系统异常的通用拦截处理,还有就是请求不支持异常,最后再是统一的大异常拦截。
可以看我下面写的案例:
/**
* 全局异常处理器
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public R<Void> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return R.fail(e.getMessage());
}
/**
* Mybatis系统异常 通用处理
*/
@ExceptionHandler(MyBatisSystemException.class)
public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
String message = e.getMessage();
if (message.contains("CannotFindDataSourceException")) {
log.error("请求地址'{}', 未找到数据源", requestURI);
return R.fail("未找到数据源,请联系管理员确认");
}
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
return R.fail(message);
}
/**
* 业务异常
*/
@ExceptionHandler(ServiceException.class)
public R<Void> handleServiceException(ServiceException e, HttpServletRequest request) {
log.error(e.getMessage(), e);
Integer code = e.getCode();
return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public R<Void> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return R.fail(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public R<Void> handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return R.fail(e.getMessage());
}
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public R<Void> handleBindException(BindException e) {
log.error(e.getMessage(), e);
String message = StreamUtils.join(e.getAllErrors(), DefaultMessageSourceResolvable::getDefaultMessage, ", ");
return R.fail(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public R<Void> constraintViolationException(ConstraintViolationException e) {
log.error(e.getMessage(), e);
String message = StreamUtils.join(e.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
return R.fail(message);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public R<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return R.fail(message);
}
}
运行返回显示
异常统一处理和返回值封装就到此结束了,上面的稍微修改下,完全可以做生产用,希望大家多多点赞关注支持,后续给大家分享更多有用的知识点!!!