bfchengnuo / MyRecord

平时充电做的笔记,一个程序猿的自我修养.
https://bfchengnuo.com/MyRecord/
33 stars 8 forks source link

SpringBoot中的日期序列化转换 #32

Closed bfchengnuo closed 5 years ago

bfchengnuo commented 5 years ago

最常用的两个注解: @DateTimeFormat@JsonFormat

@DateTimeFormat 转换前端 String 类型到后端 Date 类型。

@JsonFormat 转换后端 Date 类型到前端 String 类型,如果只用到此注解,加到属性上或者方法上都可以; 如果跟 @DateTimeFormat 配合使用,此注解添加到 getter 方法上面,注意加 timezone="GMT+8"

示例:

class Main{
  @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
  private Date updateTime;

  // 一般配合 controller 使用
  public void test(@DateTimeFormat(pattern = "yyyy-MM-dd")Date date){}

  @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8")
  public Date getUpdateTime() {
    return updateTime;
  }

}
bfchengnuo commented 5 years ago

补充,SpringBoot 中对 Jackson 的一些使用:

spring:
  jackson:
    # 全局格式化标准
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    # 忽略 null 值的输出
    default-property-inclusion: non_null

    # 返回时间戳
    # serialization:
    #  write-dates-as-timestamps: true

使用 @JsonSerialize(using = Date2LongSerializer.class) 来指定转换规则,例如毫秒转换为秒:

public class Date2LongSerializer extends JsonSerializer<Date> {
    @Override
    public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeNumber(date.getTime() / 1000);
    }
}
bfchengnuo commented 5 years ago

如果使用的是 @RequestBody 的情况下时间戳是 yyyy-MM-dd 格式的日期,Jackson 会自动识别并且转换成功,若是 yyyy-MM-dd HH:mm:ss 这种格式的日期字符串的话,Jackson 无法自动转换成 Date 类型。

解决方案可以在字段上使用 @JsonFormat ,但如果要兼容多种格式,则需要使用 @JsonDeserialize 来自定义转换规则。

当然,最小耦合的方法是重写 Jackson 的日期转换器,适应各种格式。

参见:https://my.oschina.net/u/2608182/blog/2877624

bfchengnuo commented 5 years ago

方案汇总

现在,整理下最常用的几种方案:

  1. 用 String 接收,自己转换,太 low 基本没人用
  2. 使用 @DateTimeFormat(pattern = “yyyy-MM-dd HH:mm:ss”) 注解在实体字段上
  3. 如果使用了 SB,那么可以在 properties/yml 文件中配置
  4. 写一个 BaseController,每一个需要处理的 controller 继承这个 BaseController,在 BaseController 里使用 @InitBinder 写一个全局转换日期方法
  5. 自定义 DateConverterConfig,实现一下 Spring 提供的 Converter,重写里面的 convert 方法

附:

# 方案 3 配置示例:
spring.jackson.date-format = yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone = GMT+8
spring.mvc.date-format = yyyy-MM-dd HH:mm:ss

// 方案 4,写在 controller 中
@InitBinder
public void initBinder(ServletRequestDataBinder binder) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    binder.registerCustomEditor(Date.class, new CustomDateEditor(sdf, true));        
}

// 方案 5
@Component
public class DateConverterConfig implements Converter<String, Date> {

    private static final List<String> formarts = new ArrayList<>(4);
    static{
        formarts.add("yyyy-MM");
        formarts.add("yyyy-MM-dd");
        formarts.add("yyyy-MM-dd hh:mm");
        formarts.add("yyyy-MM-dd hh:mm:ss");
    }

    @Override
    public Date convert(String source) {
        String value = source.trim();
        if ("".equals(value)) {
            return null;
        }
        if(source.matches("^\\d{4}-\\d{1,2}$")){
            return parseDate(source, formarts.get(0));
        }else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")){
            return parseDate(source, formarts.get(1));
        }else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")){
            return parseDate(source, formarts.get(2));
        }else if(source.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")){
            return parseDate(source, formarts.get(3));
        }else {
            throw new IllegalArgumentException("Invalid boolean value '" + source + "'");
        }
    }

    /**
     * 格式化日期
     * @param dateStr String 字符型日期
     * @param format String 格式
     * @return Date 日期
     */
    public  Date parseDate(String dateStr, String format) {
        Date date=null;
        try {
            DateFormat dateFormat = new SimpleDateFormat(format);
            date = dateFormat.parse(dateStr);
        } catch (Exception e) {

        }
        return date;
    }
}

需要注意的是,在某些情况下,例如使用 feign 调用、MQ 传输,某些方式的配置可能会失效,例如配置文件中配置的就可能会失效,这个需要自行确认。

参考

https://www.jianshu.com/p/2a8c31480c5d https://blog.csdn.net/eumenides_/article/details/79033505

bfchengnuo commented 5 years ago

SpringBoot 中可能存在的问题

因为 SpringBoot 的黑箱问题,如果不熟悉 SB 的源码或者流程,会比较蛋疼,比如我。。。

因为用到了 Swagger,我通过继承 WebMvcConfigurationSupport 的 addResourceHandlers 方法进行了对静态资源文件的过滤,保证其可访问; 然而,这导致了配置文件中配置的日期转换失效。。。 修正方法:

@Configuration
public class WebConfig extends WebMvcConfigurationSupport {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        super.addResourceHandlers(registry);
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
                // JDK8 新特性,可选择多个模块
                .modulesToInstall(new ParameterNamesModule());
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
    }

    /**
     * 自动转换时间格式
     *
     * @param registry date
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new DateFormatter("yyyy-MM-dd HH:mm:ss"));
    }
}

参考:https://blog.csdn.net/qq_25610165/article/details/78696060

原文是使用 @EnableWebMvc 后、implements WebMvcConfigurer 后导致的失效。

bfchengnuo commented 5 years ago

其他格式化

Spring 提供的数据格式化有很多,例如数字相关的 @NumberFormat(pattern=“#.##”) 就是有两位小数的。

参考:https://www.cnblogs.com/liukemng/p/3748137.html