• Hibernate如何把校验错误的信息按给定格式抛给前端?
  • 发布于 11小时前
  • 15 热度
    0 评论
  • 怅忘归
  • 0 粉丝 29 篇博客
  •   
概述
使用Hibernate校验进行参数校验时,如果参数不符合校验规则,就会抛出异常。对于这些异常,我们不可能将完整的异常信息直接抛出去给前端,都是需要处理成一定格式给到前端,例如说哪个属性是什么错误

示例
package wxw.mengyuan.tool.exception;

import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.support.WebExchangeBindException;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * Hibernate校验的全局异常处理器
 */
@ControllerAdvice(annotations = { Controller.class, RestController.class })
@Slf4j
@ResponseBody
public class HibernateGlobalExceptionHandler2 {

  /**
   * hibernate-validator校验普通参数异常的处理
   * 注意:快速失败返回模式下,参数名称为arg0、arg1...,并不是真实的形参名称
   */
  @ExceptionHandler(value = { ConstraintViolationException.class })
  private Object hibernateNormalValidExceptionHandler(Exception e) throws Exception {
    // 打印日志
    log.error("校验普通参数异常", e);

    // 获取异常信息
    Map<String, String> errorMessage = new HashMap<>();
    for (ConstraintViolation<?> cv : ((ConstraintViolationException) e).getConstraintViolations()) {
      // cv.getPropertyPath():得到"方法名.参数名称"
      errorMessage.put(cv.getPropertyPath().toString().split("\\.")[1], cv.getMessage());
    }

    // 返回结果
    return errorMessage;
  }

  /**
   * hibernate-validator校验对象属性异常的处理
   */
  @ExceptionHandler(value = { WebExchangeBindException.class, MethodArgumentNotValidException.class })
  private Object hibernateObjectValidExceptionHandler(Exception e) throws Exception {
    // 打印日志
    log.error("校验对象属性异常", e);

    // 获取异常信息
    Map<String, String> errorMessage = new HashMap<>();
    for (FieldError fieldError : ((BindingResult) e).getFieldErrors()) {
      errorMessage.put(fieldError.getField(), fieldError.getDefaultMessage());
    }
    // 返回结果
    return errorMessage;
  }

  /**
   * 堆代码 duidaima.com
   * hibernate-validator校验其他情况异常的处理
   */
  @ExceptionHandler(value = { HttpMessageNotReadableException.class })
  private Object hibernateEnumValidExceptionHandler(Exception e) throws Exception {
    // 打印日志
    log.error("校验枚举属性异常", e);

    // 未传递参数
    if (e.getCause() == null) {
      return "参数不能为空";
    }

    // 获取异常信息
    Map<String, String> errorMessage = new HashMap<>();
    // 枚举值校验失败
    if (e.getCause() instanceof InvalidFormatException cause) {
      StringBuilder value = new StringBuilder("【" + cause.getValue()  + "】不在【");
      // 获取枚举类
      Class<?> targetType = cause.getTargetType();
      // 获取枚举类的所有字面量
      Object[] enumConstants = targetType.getEnumConstants();
      // 获取非静态的字段对象
      Field[] declaredFields = Arrays.stream(targetType.getDeclaredFields()).filter(field -> !Modifier.isStatic(field.getModifiers())).toArray(Field[]::new);
      // 获取其中标注了@JsonValue的字段
      Field targetField = null;
      for (Field field : declaredFields) {
        if (field.isAnnotationPresent(JsonValue.class)) {
          field.setAccessible(true);
          targetField = field;
        }
      }
      if (targetField == null) {
        targetField = declaredFields[0];
        targetField.setAccessible(true);
      }
      // 遍历字面量,获取对应的字段的值
      for (int i = 0; i < enumConstants.length; i++) {
        value.append(targetField.get(enumConstants[i]));
        if (i < enumConstants.length - 1) {
          value.append(", ");
        }
      }
      value.append("】范围内");
      errorMessage.put(cause.getPath().getFirst().getFieldName(), value.toString());
    }
    // 非对象属性名称注入失败
    if (e.getCause() instanceof UnrecognizedPropertyException cause) {
      errorMessage.put(cause.getPropertyName(), "属性名称不存在");
    }
    // 返回结果
    return errorMessage;
  }

}
处理后的错误信息
普通参数校验异常处理返回信息
{
    "password": "密码不能为空",
    "username": "账号不能为空"
}
对象参数校验异常处理返回信息
{
    "password": "密码不能为空",
    "phone": "手机号不能为空",
    "username": "账号不能为空"
}
枚举属性校验异常
{
    "gender": "【AA】不在【男, 女】范围内"
}

用户评论