如何开启Rest API请求数据转换错误日志及配置Jackson多日期序列化
解决方案
一、开启数据转换错误日志
方式1:调整日志配置
在application.yml中添加以下日志级别配置,捕获Spring MVC消息转换和参数绑定的详细日志:
logging: level: # 捕获JSON消息转换的详细日志 org.springframework.http.converter.json.MappingJackson2HttpMessageConverter: DEBUG # 捕获请求处理时的参数绑定/校验异常日志 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor: DEBUG # 更宽泛的配置,覆盖整个Spring MVC模块日志 # org.springframework.web: DEBUG
方式2:自定义全局异常处理器
通过全局异常处理器精准捕获并打印JSON解析错误、参数校验错误的详细日志,同时返回友好提示:
import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.http.converter.HttpMessageNotReadableException; import java.util.stream.Collectors; @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { // 捕获JSON解析/数据转换错误 @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity<String> handleJsonParseError(HttpMessageNotReadableException e) { log.error("JSON解析失败: ", e); String detailMsg = e.getMostSpecificCause() != null ? e.getMostSpecificCause().getMessage() : e.getMessage(); return ResponseEntity.badRequest().body("请求数据格式错误: " + detailMsg); } // 捕获@Valid参数校验错误 @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<String> handleValidationError(MethodArgumentNotValidException e) { log.error("参数校验失败: ", e); BindingResult result = e.getBindingResult(); String errorMsg = result.getFieldErrors().stream() .map(fieldError -> String.format("%s: %s", fieldError.getField(), fieldError.getDefaultMessage())) .collect(Collectors.joining("; ")); return ResponseEntity.badRequest().body("参数校验错误: " + errorMsg); } }
二、配置Jackson支持多日期格式自动适配
步骤1:自定义多格式LocalDateTime反序列化器
编写反序列化器,支持你需要的多种日期格式(包括字符串格式和毫秒时间戳):
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import java.io.IOException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.Arrays; import java.util.List; public class MultiFormatLocalDateTimeDeserializer extends StdDeserializer<LocalDateTime> { // 定义支持的日期格式列表 private static final List<DateTimeFormatter> SUPPORTED_FORMATTERS = Arrays.asList( DateTimeFormatter.ISO_LOCAL_DATE_TIME, // 适配2021-08-02T03:00:00格式 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS Z"), // 适配2022-10-22 10:17:37.402150 +00:00格式 DateTimeFormatter.ISO_INSTANT // 适配UTC时间格式(可选) ); public MultiFormatLocalDateTimeDeserializer() { super(LocalDateTime.class); } @Override public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException { switch (parser.getCurrentToken()) { case VALUE_NUMBER_INT: // 处理Postman{{$timestamp}}生成的毫秒级时间戳 long timestamp = parser.getLongValue(); return LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault()); case VALUE_STRING: String dateStr = parser.getText().trim(); // 依次尝试所有支持的格式 for (DateTimeFormatter formatter : SUPPORTED_FORMATTERS) { try { return LocalDateTime.parse(dateStr, formatter); } catch (DateTimeParseException ignored) { // 格式不匹配,继续尝试下一种 } } // 所有格式都不匹配,抛出异常 throw new IOException(String.format("无法解析日期: %s,支持的格式包括:ISO_LOCAL_DATE_TIME、yyyy-MM-dd HH:mm:ss.SSSSSS Z、毫秒时间戳", dateStr)); default: throw context.mappingException(handledType()); } } }
步骤2:配置Jackson使用自定义反序列化器
方式A:局部配置(针对单个字段)
在TDto的tDate字段上替换原有的@JsonSerialize,添加自定义反序列化注解:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; // 移除原有的@JsonSerialize(using = LocalDateTimeSerializer.class),替换为自定义反序列化器 @JsonDeserialize(using = MultiFormatLocalDateTimeDeserializer.class) private LocalDateTime tDate;
方式B:全局配置(所有LocalDateTime字段生效)
通过配置类全局注册自定义反序列化器,无需在每个字段上添加注解:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); JavaTimeModule timeModule = new JavaTimeModule(); // 注册自定义LocalDateTime反序列化器 timeModule.addDeserializer(LocalDateTime.class, new MultiFormatLocalDateTimeDeserializer()); // 如果需要序列化时统一格式,可以添加默认序列化器 // timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); objectMapper.registerModule(timeModule); // 关闭默认时间戳序列化,使用指定格式 objectMapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 开启严格模式,不支持的格式直接抛出异常 objectMapper.enable(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); return objectMapper; } }
内容的提问来源于stack exchange,提问作者skyho
相关产品推荐
相关产品推荐

