| | |
| | | import cn.hutool.core.map.MapUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.fasterxml.jackson.core.JsonParseException; |
| | | import com.fasterxml.jackson.databind.exc.InvalidFormatException; |
| | | import cn.lihu.jh.framework.apilog.core.service.ApiErrorLogFrameworkService; |
| | | import cn.lihu.jh.framework.common.exception.ServiceException; |
| | | import cn.lihu.jh.framework.common.exception.util.ServiceExceptionUtil; |
| | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.exception.ExceptionUtils; |
| | | import org.springframework.http.converter.HttpMessageNotReadableException; |
| | | import org.springframework.security.access.AccessDeniedException; |
| | | import org.springframework.util.Assert; |
| | | import org.springframework.validation.BindException; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 处理请求体(@RequestBody)反序列化失败。 |
| | | * |
| | | * <h3>为什么需要单独处理</h3> |
| | | * 之前没有这个处理器,JSON 里的字段类型不对(例如 {@code "bookCheckType": "常规心电图"}, |
| | | * 而实体字段是 {@code Integer})时会抛 {@link HttpMessageNotReadableException}, |
| | | * 一路落到 {@link #defaultExceptionHandler} 兜底 —— 前端只看到 <b>500「系统异常」</b>, |
| | | * 完全看不出是参数格式问题(实测 2026-10-28 排查 {@code /ecg/appointment/create} 即为此)。 |
| | | * |
| | | * <p>现在改为返回 <b>400</b> 并明确指出「哪个字段、收到什么值、期望什么类型」。 |
| | | */ |
| | | @ExceptionHandler(HttpMessageNotReadableException.class) |
| | | public CommonResult<?> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException ex) { |
| | | log.warn("[httpMessageNotReadableExceptionHandler]", ex); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "请求参数格式错误:" + buildMessageNotReadableDetail(ex)); |
| | | } |
| | | |
| | | /** |
| | | * 把反序列化异常翻译成人能看懂的说明:字段名 + 收到的值 + 期望的类型。 |
| | | */ |
| | | private String buildMessageNotReadableDetail(HttpMessageNotReadableException ex) { |
| | | Throwable cause = ex.getMostSpecificCause(); |
| | | // ① 字段类型不匹配(最常见):拿到了字段名 / 实际值 / 期望类型 |
| | | if (cause instanceof InvalidFormatException) { |
| | | InvalidFormatException ife = (InvalidFormatException) cause; |
| | | String field = ife.getPath().isEmpty() |
| | | ? "" : ife.getPath().get(ife.getPath().size() - 1).getFieldName(); |
| | | Object value = ife.getValue(); |
| | | Class<?> target = ife.getTargetType(); |
| | | return String.format("字段「%s」类型不正确(收到值:%s,期望类型:%s)", |
| | | StrUtil.blankToDefault(field, "未知字段"), value, null == target ? "未知" : target.getSimpleName()); |
| | | } |
| | | // ② 请求体缺失 / JSON 语法错误等:用最具体的原因 |
| | | if (cause instanceof JsonParseException) { |
| | | return "JSON 格式不正确:" + ((JsonParseException) cause).getOriginalMessage(); |
| | | } |
| | | String message = null == cause ? ex.getMessage() : cause.getMessage(); |
| | | return StrUtil.blankToDefault(message, "请求体无法解析"); |
| | | } |
| | | |
| | | /** |
| | | * 处理 SpringMVC 参数校验不正确 |
| | | */ |
| | | @ExceptionHandler(MethodArgumentNotValidException.class) |