Spring boot统一包装返回包装类、捕获异常(处理)、记录请求和响应日志
说明:工具类用的hutool
- 先定义不需要包装的注解和返回类
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
}
- 返回类
@Data
@AllArgsConstructor
public class Result<T> implements Serializable {
/**
* 返回错误提示枚举
*/
@Getter
@AllArgsConstructor
public enum CodeEnum {
/**
* 操作成功
*/
SUCCESS(0, "操作成功"),
/**
* 操作异常
*/
EXCEPTION(-1, "操作异常");
//省略其他
private final Integer code;
private final String desc;
}
public static <T> Result<T> success() {
return new Result<T>(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getDesc(), null);
}
public static <T> Result<T> of(CodeEnum codeEnum) {
return new Result<T>(codeEnum.getCode(), codeEnum.getDesc(), null);
}
public static <T> Result<T> success(T data) {
if (data instanceof Boolean) {
CodeEnum codeEnum = (Boolean) data ? CodeEnum.SUCCESS : CodeEnum.FAIL;
return new Result<T>(codeEnum.getCode(), codeEnum.getDesc(), data);
}
return new Result<T>(CodeEnum.SUCCESS.getCode(), CodeEnum.SUCCESS.getDesc(), data);
}
public static <T> Result<T> fail(Exception e) {
if (e == null) {
return new Result<T>(Result.CodeEnum.EXCEPTION.getCode(), CodeEnum.EXCEPTION.desc, null);
}
return new Result<T>(CodeEnum.EXCEPTION.code,
StrUtil.isEmpty(e.getMessage())
? e.toString()
: e.getMessage()
, null);
}
//省略部分构造函数,根据需要自行添加
- 定义全局处理类
@Component
@Aspect
@RestControllerAdvice
@Slf4j
public class ControllerAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> clazz) {
Method method = methodParameter.getMethod();
if (method == null
|| method.getReturnType().equals(void.class)
//请求为404的接口method.getName()为error
|| "error".equals(method.getName())
//swagger包路径
|| methodParameter.getDeclaringClass().getName().contains("springfox.documentation")) {
return false;
}
return !
(methodParameter.getParameterType().isAssignableFrom(Result.class)
||
//不需要包装的注解 methodParameter.hasMethodAnnotation(IgnoreResponseAdvice.class));
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> clazz, ServerHttpRequest request, ServerHttpResponse response) {
if (body == null) {
return JSON.toJSON(null).toString();
}
//String 需要特殊处理
if (body instanceof String) {
try {
ObjectMapper om = new ObjectMapper();
return om.writeValueAsString(Result.success(body));
} catch (JsonProcessingException e) {
return Result.fail(e.getMessage());
}
}
//已经是Result类,直接返回
if (body instanceof Result) {
return body;
}
return Result.success(body);
}
/**
* 切点 注解拦截
*/
@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
public void controllerAspect() {
}
/**
* 后置通知 用于拦截Controller层记录异常返回
*
* @param point 切点
*/
@Around("controllerAspect()")
public Object around(final ProceedingJoinPoint point) throws Throwable {
Principal currentUser = AuthorizeUtil.getCurrentUser();
String userName = "获取当前操作用户";
//获取方法
MethodSignature methodSignature = (MethodSignature) point.getSignature();
ApiOperation api = methodSignature.getMethod().getAnnotation(ApiOperation.class);
List<Object> args = Arrays.stream(point.getArgs()).filter(f -> !(f instanceof HttpServletResponse) && !(f instanceof HttpServletRequest)).collect(Collectors.toList());
//todo若需要对敏感数据进行脱敏,自行处理
try {
Object proceed = point.proceed();
log.info("[用户:{}请求{}{}]请求参数:{} ;响应数据:{}", userName, api.value(), methodSignature.toShortString(), args, JSON.toJSONString(proceed));
return proceed;
} catch (Exception e) {
log.error("[用户:{}请求{}{}]请求参数:{} ", userName, api.value(), methodSignature.toShortString(), args);
throw e;
}
}
@ExceptionHandler
@ResponseBody
public Result httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e) {
return Result.fail(e.getMessage());
}
@ExceptionHandler
@ResponseBody
public Result methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
//快速失败,取默认的错误
for (ObjectError error : e.getBindingResult().getAllErrors()) {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
return Result.fail(Result.CodeEnum.PARAM_ERROR.getCode(), fieldError.getField() + fieldError.getDefaultMessage());
}
return Result.fail(Result.CodeEnum.PARAM_ERROR.getCode(), error.getObjectName() + error.getDefaultMessage());
}
return Result.fail(Result.CodeEnum.PARAM_ERROR);
}
/**
* 拦截单个参数校验异常捕获 @RequestParam 参数校验失败
*
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(ConstraintViolationException.class)
public Result constraintViolationException(ConstraintViolationException e) {
log.error("出现异常:{}", e);
List<String> errorArr = Lists.newArrayList();
for (ConstraintViolation constraint : e.getConstraintViolations()) {
errorArr.add(constraint.getInvalidValue() + "错误" + constraint.getMessage());
}
return Result.fail(Result.CodeEnum.PARAM_ERROR.getCode(), String.join(";", errorArr.toArray(new String[]{})));
}
/**
* validation 报错捕捉
*
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public Result exceptionHandler(BindException e) {
log.error(e.getMessage(), e);
return Result.fail(Result.CodeEnum.PARAM_ERROR.getCode(), Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
}
@ResponseBody
@ExceptionHandler(HttpMessageConversionException.class)
public Result parameterTypeConvertException(HttpMessageConversionException e) {
log.error(e.getMessage(), e);
return Result.fail(Result.CodeEnum.PARAM_ERROR);
}
@ExceptionHandler
@ResponseBody
public Result exceptionHandler(IllegalArgumentException e) {
log.error(e.getMessage(), e);
if (StrUtil.isNotBlank(e.getMessage()) && Validator.hasChinese(e.getMessage())) {
return Result.fail(Result.CodeEnum.EXCEPTION.getCode(), e.getMessage(), null);
}
return Result.fail(Result.CodeEnum.PARAM_ERROR.getCode(), e.getMessage());
}
@ExceptionHandler
@ResponseBody
public Result exceptionHandler(Exception e) {
log.error(e.getMessage(), e);
return Result.fail(e);
}
}