diff --git a/src/main/java/com/yunhai/userbase/common/GlobalExceptionHandler.java b/src/main/java/com/yunhai/userbase/common/GlobalExceptionHandler.java index 2e740f9..6c26903 100644 --- a/src/main/java/com/yunhai/userbase/common/GlobalExceptionHandler.java +++ b/src/main/java/com/yunhai/userbase/common/GlobalExceptionHandler.java @@ -1,7 +1,9 @@ package com.yunhai.userbase.common; +import com.fasterxml.jackson.databind.JsonMappingException; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -13,6 +15,7 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -66,6 +69,27 @@ public class GlobalExceptionHandler { return paramError("参数类型不匹配: " + ex.getName()); } + /** + * 请求体反序列化失败(Jackson 在进 Controller 前抛出,@Valid 兜不到)。典型如 birthday 等非空字段格式非法: + * 从异常最内层 {@link JsonMappingException} 的 path 取失败字段名,birthday 给点名提示,其余走通用文案。 + * 空串/null 由 Jackson JSR-310 归一为 null、不抛,故不会误入此分支(满足「空不管」)。 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public Map handleNotReadable(HttpMessageNotReadableException ex) { + String field = null; + Throwable cause = ex.getMostSpecificCause(); + if (cause instanceof JsonMappingException) { + List path = ((JsonMappingException) cause).getPath(); + if (path != null && !path.isEmpty()) { + field = path.get(path.size() - 1).getFieldName(); + } + } + if ("birthday".equals(field)) { + return paramError("birthday 字段类型需为yyyy-MM-dd"); + } + return paramError("请求参数格式错误"); + } + private String fieldErrorsMessage(java.util.List fieldErrors) { if (fieldErrors == null || fieldErrors.isEmpty()) { return "参数校验失败"; diff --git a/src/test/java/com/yunhai/userbase/common/GlobalExceptionHandlerTest.java b/src/test/java/com/yunhai/userbase/common/GlobalExceptionHandlerTest.java new file mode 100644 index 0000000..539fdd0 --- /dev/null +++ b/src/test/java/com/yunhai/userbase/common/GlobalExceptionHandlerTest.java @@ -0,0 +1,49 @@ +package com.yunhai.userbase.common; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpInputMessage; +import org.springframework.http.converter.HttpMessageNotReadableException; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link GlobalExceptionHandler#handleNotReadable} 单测:请求体反序列化失败的友好提示。 + * 纯单元(无 DB/Spring 上下文),直接构造异常断言返回信封。 + */ +class GlobalExceptionHandlerTest { + + private final GlobalExceptionHandler handler = new GlobalExceptionHandler(); + + @Test + void birthdayParseErrorReturnsFriendlyParamError() throws Exception { + JsonParser p = new ObjectMapper().createParser("{}"); + JsonMappingException jme = JsonMappingException.from(p, "cannot deserialize LocalDate"); + jme.prependPath(new JsonMappingException.Reference(null, "birthday")); + HttpMessageNotReadableException ex = + new HttpMessageNotReadableException("JSON parse error", jme, (HttpInputMessage) null); + + Map body = handler.handleNotReadable(ex); + + assertEquals(4, body.get("code")); + String msg = (String) body.get("msg"); + assertTrue(msg.contains("birthday"), "msg 应点名 birthday 字段: " + msg); + assertTrue(msg.contains("yyyy-MM-dd"), "msg 应含期望格式 yyyy-MM-dd: " + msg); + } + + @Test + void otherParseErrorReturnsGenericMessage() { + HttpMessageNotReadableException ex = + new HttpMessageNotReadableException("malformed JSON", (HttpInputMessage) null); + + Map body = handler.handleNotReadable(ex); + + assertEquals(4, body.get("code")); + assertEquals("请求参数格式错误", body.get("msg")); + } +}