最近做项目任务时, 因前端请求接口时未进行参数验证, 因此一些数值类型参数传递数据被录入了String类型, 服务端提示"参数解析失败", 测试提了个提示信息不准确的bug, 虽然前端加一下验证, 后端接口也拦截掉了异常, 但既然提出最好可以处理一下, 当然要看看能不能解决此问题, 将最符合的提示返回给前端.
首先, 如果需要最准确的提示信息, 那么就需要用到自定义注解了, 在自定义注解中填入相应的提示信息
/**
* 数据绑定异常提示语
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DataBindErrorTips {
String value();
}
使用方法: 直接在请求实体类的字段上添加注解并给予自定义提示信息
/**
* 商品编号
*/
@DataBindErrorTips("商品编号格式不正确")
private Integer spuCode;
现在基本的内容已经准备好, 剩下的就是处理异常了, 首先既然是绑定的注解首先想到BindException异常处理,如下编写
@ExceptionHandler(BindException.class)
@ResponseStatus(value = HttpStatus.OK)
public Result<?> validExceptionHandler(BindException e, HttpServletRequest request) {
BindingResult bindingResult = e.getBindingResult();
FieldError fieldError = bindingResult.getFieldErrors().stream().min(Comparator.comparing(FieldError::getField)).orElse(null);
if (fieldError == null){
return Response.build(ILLEGALA_RGUMENT_CODE, "参数格式不正确");
}
String fieldName = fieldError.getField();
Class<?> targetCalss = Objects.requireNonNull(bindingResult.getTarget()).getClass();
String erroMessage = getDataBindErrorTips(targetCalss, fieldName);
if (StringUtils.isEmpty(erroMessage)) {
erroMessage = fieldError.getDefaultMessage();
if (erroMessage != null && erroMessage.startsWith("Failed to convert property")) {
erroMessage = "参数格式不正确";
}
}
String rejectedValue = Objects.toString(fieldError.getRejectedValue());
if (rejectedValue.length() > 30) {
rejectedValue = rejectedValue.substring(0, 30) + "...";
}
log.error("BindException URI={}, field={}, rejectedValue={}, errorMessage={}", request.getRequestURI(), fieldName, rejectedValue, erroMessage);
return Response.build(ILLEGALA_RGUMENT_CODE, erroMessage);
}
//获取字段所在实体类中所对应的自定义提示信息
private String getDataBindErrorTips(Class<?> targetCalss, String fieldName) {
if (targetCalss == null || StringUtils.isEmpty(fieldName)) {
return null;
}
while (targetCalss != Object.class) {
Field field;
try {
field = targetCalss.getDeclaredField(fieldName);
DataBindErrorTips dataBindErrorTips = field.getDeclaredAnnotation(DataBindErrorTips.class);
return dataBindErrorTips == null ? null : dataBindErrorTips.value();
} catch (NoSuchFieldException e) {
targetCalss = targetCalss.getSuperclass();
}
}
return null;
}
测试了一下, 发现get请求可以准确提示,而post请求未进入该异常拦截中, 查看工作台异常信息
org.springframework.http.converter.HttpMessageNotReadableException: Invalid JSON input: Cannot deserialize value of type java.lang.Integer
from String “abc”: not a valid Integer value; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type java.lang.Integer
from String “abc”: not a valid Integer value
从中可以看到报错异常是InvalidFormatException, 那么好了, 直接在此异常中处理即可
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(InvalidFormatException.class)
public Result<?> handInvalidFormatException(InvalidFormatException e){
String erroMessage = "";
List<JsonMappingException.Reference> path = e.getPath();
Reference reference;
if (!CollectionUtils.isEmpty(path) && (reference = path.get(0)) != null) {
erroMessage = getDataBindErrorTips(reference.getFrom().getClass(), reference.getFieldName());
if (erroMessage == null) {
erroMessage = "参数名:"+reference.getFieldName()+" 输入不合法,需要的是 "+invalidFormatException.getTargetType().getName() + " 类型,"+"提交的值是:"+invalidFormatException.getValue().toString();
}
}else{
erroMessage = "参数解析失败";
}
return Response.build(ILLEGALA_RGUMENT_CODE, erroMessage);
}
测试运行, 没有被异常拦截到, 不应该啊, 到底哪里出现问题了, 回到上面再仔细看看报错异常信息原来在InvalidFormatException前面还有一个上层异常HttpMessageNotReadableException那好办了, 直接将上述代码中的InvalidFormatException.class改为HttpMessageNotReadableException.class, 这样不就可以拦截到了吗?测试后发现果然可以! 虽然可以拦截并且提示信息正确但拦截的异常类与处理的异常类不统一,为了以后的扩展性, 最好还是拦截异常与参数统一都用HttpMessageNotReadableException,那么就需要将HttpMessageNotReadableException 转化为我们需要的InvalidFormatException代码如下
@ResponseStatus(HttpStatus.OK)
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
String erroMessage = null;
Throwable rootCause = e.getRootCause();
if (rootCause instanceof JsonMappingException) {
erroMessage = getDataBindErrorTips(((JsonMappingException) rootCause).getPath());
}
if (erroMessage == null) {
erroMessage = "参数解析失败";
}
return Response.build(ILLEGALA_RGUMENT_CODE, erroMessage);
}
private String getDataBindErrorTips(List<Reference> path) {
if (CollectionUtils.isEmpty(path)) {
return null;
}
for (int i = path.size() - 1; i >= 0; i--) {//基本只需要最后一条数据
Reference reference = path.get(i);
if (reference == null) {
continue;
}
Object target = reference.getFrom();
if (target == null) {
continue;
}
Class<?> targetCalss = target.getClass();
String result = this.getDataBindErrorTips(target.getClass(), reference.getFieldName());
if (!StringUtils.isEmpty(result)) {
return result;
}
}
return null;
}
现在无论是gat请求或者post请求, 都已经能返回准确的提示信息了