You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何开启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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 23:45:42