Springboot 接收&返回 LocalDateTime 全自动时间戳互转 支持Swagger3

前言

LocalDateTime 就是一个更高效率的 Date , 从 Java 8 开始支持
Springboot 中前端传入和返回 LocalDateTime 默认的格式比较蛋疼
希望是传入和返回都是毫秒级的时间戳
例如 2023-04-25 12:25:47.442 对应 1682396747442
期望前端传入和返回时间都用Number类型的时间戳
后端自动序列化或反序列化为LocalDateTime

补充
这样做还有一个隐藏问题就是取值范围
JavaScript 中的 Number 整数取值范围是 ±2的53次方-1
对应到 JavaInteger 取值范围是 ±2的31次方-1 , JavaLong 是64次方
所以 Java 端必须用 LongString 来接收和返回数据


折腾

经过一番学习和折腾,基本了解了大概的逻辑
Springboot默认用Jackson处理JSON转换
理论上只要改Jackson处理LocalDateTime的方式

最简单的代码如下

  1. @Configuration
  2. public class LocalDateTimeConfig {
  3. @Bean
  4. public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
  5. return builder -> {
  6. DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  7. builder.deserializers(new LocalDateTimeDeserializer(dateTimeFormatter));
  8. builder.serializers(new LocalDateTimeSerializer(dateTimeFormatter));
  9. };
  10. }
  11. }

传入和返回的格式都为
yyyy-MM-dd HH:mm:ss

  1. {
  2. "startTime": "2023-04-25 12:25:47"
  3. }


然鹅,他自带的序列化都是改时间格式 YYMMDD,我这里需要的是long类型时间戳
那么延续这个思路,我重写了序列化和反序列化的方法
(错误示范)

  1. @Configuration
  2. public class LocalDateTimeConfig {
  3. @Bean
  4. public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
  5. return builder -> {
  6. builder.serializers(new LocalDateTimeSerializer() {
  7. @Override
  8. public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
  9. if (localDateTime != null) {
  10. jsonGenerator.writeNumber(localDateTime.atZone(ZoneId.of("UTC+8")).toInstant().toEpochMilli());
  11. }
  12. }
  13. });
  14. builder.deserializers(new LocalDateTimeDeserializer() {
  15. @Override
  16. public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException {
  17. return LocalDateTime.ofInstant(Instant.ofEpochMilli(parser.getValueAsLong()), ZoneId.of("UTC+8"));
  18. }
  19. });
  20. };
  21. }
  22. }

然后就不出意外的出意外了 😭
传入 1682481600000 正常接收
返回 2023-04-26T12:00:00 炸了… 成默认格式了

根据一番打断点调试读源码百度看文档等折腾后…
发现序列化方法 public void serialize 根本没执行
执行了重写之前的那个默认的序列化方法 就很蛋疼

最后折腾了2天,发现csdn这位老哥写的就是正确答案
SpringBoot–LocalDateTime格式转换(响应给前端) - CSDN

  1. @Configuration
  2. public class LocalDateTimeConfig {
  3. @Bean
  4. public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
  5. return builder -> {
  6. builder.serializerByType(LocalDateTime.class, new LocalDateTimeSerializer());
  7. builder.deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer());
  8. };
  9. }
  10. public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
  11. @Override
  12. public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
  13. throws IOException {
  14. if (value != null) {
  15. gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
  16. }
  17. }
  18. }
  19. public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
  20. @Override
  21. public LocalDateTime deserialize(JsonParser parser, DeserializationContext deserializationContext) throws IOException {
  22. long timestamp = parser.getValueAsLong();
  23. return timestamp < 0 ? null : LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.systemDefault());
  24. }
  25. }
  26. }

至此传入和返回都是 1682493795958 大功告成


最后兼容一下Swagger3
传入参数默认自动填Long类型数字

  1. @Bean
  2. public Docket createRestApi() {
  3. return new Docket(DocumentationType.OAS_30)
  4. ...
  5. .build()
  6. // 重点是这句
  7. .directModelSubstitute(LocalDateTime.class, Long.class);
  8. }


END

参考
SpringBoot–LocalDateTime格式转换(响应给前端) - CSDN
Formatting json Date/LocalDateTime/LocalDate in Spring Boot - Spring Cloud
spring boot 时间戳和LocalDateTime相互转换 - CSDN

送人玫瑰,手留余香
Smart-doc 仅需要注释即可全自动生成开发文档 Springboot 开发必备 代替Swagger
微信小程序 Java服务端 登录相关代码 工具类 2023最新版