前言
现在流行的基本前后端交互都是通过Json格式,所以有一定规模的项目都会这样子做,往往后端定义统一规范给前端进行交互。 (基本的代码片在这,不提供额外的github地址)
响应处理
结果返回格式
{
"success": false,
"code": "G000000",
"message": "环境:[dev], 微服务: [content-center], 接口 [/exception4] 业务异常",
"resultTime": "2020-05-07T16:30:27.072",
"data": null
}
结果包含内容
- 是否响应成功标识;
- 响应状态码;
- 响应描述;
结果枚举
- 返回字段:success、code、message
- 枚举值可以根据自身增加
@Getter
@AllArgsConstructor
public enum ResultCodeEnum {
/**
* 成功
*/
SUCCESS(
true,
"G200000",
"成功"),
/**
* 失败 (错误请求)
*/
FAIL(
false,
"G40000",
"失败"),
/**
* 未认证(签名错误)
*/
UNAUTHORIZED(
false,
"G401000",
"未认证"),
/**
* 接口不存在
*/
NOT_FOUND(
false,
"G404000",
"接口不存在"),
/**
* 服务器内部错误
*/
INTERNAL_SERVER_ERROR(
false,
"G500000",
"服务器内部错误")
;
/**
* 是否成功
*/
private Boolean success;
/**
* 状态码
*/
private String code;
/**
* 返回信息
*/
private String message;
}
结果类
- 返回给前端的类
- T 是任何对象形式传入返回
- return this 为了链式编程
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1573334432703774091L;
private Boolean success;
private String code;
private String message;
private LocalDateTime resultTime;
private T data;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Map<String, Object> param = new HashMap<>();
public static Result<?> ok() {
return Result.builder()
.success(ResultCodeEnum.SUCCESS.getSuccess())
.code(ResultCodeEnum.SUCCESS.getCode())
.message(ResultCodeEnum.SUCCESS.getMessage())
.resultTime(LocalDateTime.now())
.build();
}
public static Result<?> error() {
return Result.builder()
.success(ResultCodeEnum.FAIL.getSuccess())
.code(ResultCodeEnum.FAIL.getCode())
.message(ResultCodeEnum.FAIL.getMessage())
.resultTime(LocalDateTime.now())
.build();
}
public static Result<?> setResult(ResultCodeEnum resultCodeEnum) {
return Result.builder()
.success(resultCodeEnum.getSuccess())
.code(resultCodeEnum.getCode())
.message(resultCodeEnum.getMessage())
.resultTime(LocalDateTime.now())
.build();
}
public Result<?> message(String message) {
this.setMessage(message);
return this;
}
public Result<?> code(String code) {
this.setCode(code);
return this;
}
public Result<?> success(Boolean success) {
this.setSuccess(success);
return this;
}
public Result<?> data(Object data) {
this.setData((T) data);
return this;
}
}
异常处理
自定义全局异常类
- 还有一种方式继承Exception,作为父类,提供给子类继承 (有经验一定Java基础必须懂)
@Data
public class BusinessException extends RuntimeException {
private String code;
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(ResultCodeEnum resultCodeEnum) {
super(resultCodeEnum.getMessage());
this.code = resultCodeEnum.getCode();
}
@Override
public String toString() {
return "BusinessException{" + "code=" + code + ", message=" + this.getMessage() + '}';
}
}
统一异常处理器
- @ControllerAdvice 和 RestControllerAdvice 的区别就是返现Json格式,不然就需要加 @RequestBody
- 除了全局异常HttpStatus=500,其余 HttpStatus=200
- 加入了环境和服务名称
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdapter {
@Value("${spring.profiles.active}")
String profile;
@Value("${spring.application.name}")
String appName;
/**
* 全局异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<?> exception(Exception e) {
log.info("全局异常信息 ex={}", e.getMessage(), e);
HttpServletRequest request = getHttpServletRequest();
return Result.error().message("环境:[" + profile + "], 微服务: [" + appName + "], 接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
}
/**
* 方法接收到非法参数异常
*/
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.OK)
public Result<?> illegalArgumentException(IllegalArgumentException e) {
log.error("方法接收到非法参数异常 ex={}", e.getMessage(), e);
HttpServletRequest request = getHttpServletRequest();
return Result.error().message("环境:[" + profile + "], 微服务: [" + appName + "], 接口 [" + request.getRequestURI() + "] 非法参数异常");
}
/**
* 处理文件上传大小过大异常
*/
@ExceptionHandler(MultipartException.class)
@ResponseStatus(HttpStatus.OK)
public Result<?> multipartException(MultipartException e) {
log.error("上传文件大小超过最大限制 最大文件大小限制为:10M ex={}", e.getMessage(), e);
HttpServletRequest request = getHttpServletRequest();
return Result.error().message("环境:[" + profile + "], 微服务: [" + appName + "], 接口 [" + request.getRequestURI() + "] 上传大小过大异常");
}
/**
* 方法无效异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
public Result<?> businessException(MethodArgumentNotValidException e) {
log.error(CommonConstants.GLOBAL_EXCEPTION, e.getMessage(), e);
HttpServletRequest request = getHttpServletRequest();
List<String> messages = e.getBindingResult().getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList()
);
return Result.error().message("环境:[" + profile + "], 微服务: [" + appName + "], 接口 [" + request.getRequestURI() + "] 方法无效异常:");
}
/**
* 业务异常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public Result<?> businessException(BusinessException e) {
log.error(CommonConstants.GLOBAL_EXCEPTION, e.getMessage(), e);
HttpServletRequest request = getHttpServletRequest();
return Result.error().code(e.getCode()).message("环境:[" + profile + "], 微服务: [" + appName + "], 接口 [" + request.getRequestURI() + "] 业务异常");
}
/**
* 方法参数异常
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.OK)
public Result<?> businessException(BindException e) {
log.error(CommonConstants.GLOBAL_EXCEPTION, e.getMessage(), e);
HttpServletRequest request = getHttpServletRequest();
List<String> messages = e.getBindingResult().getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList()
);
return Result.error().message("环境:[" + profile + "], 微服务: [" + appName + "], 接口 [" + request.getRequestURI() + "] 方法参数异常");
}
public static HttpServletRequest getHttpServletRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
return Objects.requireNonNull(attributes).getRequest();
}
}
TIPS
- MethodArgumentNotValidException拦截问题
那么还需要在控制层添加BingdingResult吗? 答案是需要的。不然你所有的错误都会给 MethodArgumentNotValidException.class拦截,原因是@Validated需要和 BingdingResult绑定使用。详情可以看底层源码了解!