Spring boot统一包装返回包装类、捕获异常(处理)、记录请求和响应日志

该文主要介绍了如何在Springboot应用中使用Hutool工具库创建一个全局的返回包装类,用于统一处理异常并生成相应日志。定义了一个`Result`类来封装返回结果,包含成功和失败的状态码以及数据。同时,创建了一个`ControllerAdvice`类作为切面处理,处理所有非特定注解的返回结果,将非`Result`类型的返回对象转化为`Result`。此外,还包含了各种异常的处理器,如参数校验异常、HTTP方法不支持异常等,确保了系统对异常的统一管理和日志记录。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring boot统一包装返回包装类、捕获异常(处理)、记录请求和响应日志

说明:工具类用的hutool

  1. 先定义不需要包装的注解和返回类
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreResponseAdvice {
}
  1. 返回类
@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);
    }
//省略部分构造函数,根据需要自行添加
  1. 定义全局处理类
@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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值