前言
前端请求的日期格式的参数,你还在挨个配置@DateTimeFormat注解进行接受吗?
后端返回给前端的json响应中的时间格式,你还在挨个用@JsonFormat配置时间格式化吗?
本文教大家如何在spring boot下进行全局的日期格式化配置。
一、全局属性配置
#json格式化全局配置 spring.jackson.time-zone=GMT+8 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.default-property-inclusion=NON_NULL spring.mvc.date-format=yyyy-MM-dd HH:mm:ss
说明:
spring.jackson.time-zone 指定将响应结果进行json序列化时采用的默认时区
spring.jackson.date-format 指定json序列化时 时间格式化采用的默认格式
spring.jackson.default-property-inclusion 指定默认包含的熟悉,NON_NULL表示只序列化非空属性
spring.mvc.date-format
指定前端请求参数中日期格式参数和后端时间类型字段绑定时默认的格式,相当于@DateTimeFormat的全局配置
注意⚠️:
这里的spring.jackson.time-zone指定的时区一定要和mysql数据库连接中时区保持一致。
二、自定义全局格式化配置
经过上面的全局配置,已经能满足大部分场景,个别特殊的时间格式化的场景,我们可以单独采用@JsonFormat和@DateTimeFormat的实体中的日期字段进行配置。
如果用户希望自定义控制json格式化,可参考如下配置:
1、自定义格式化类CustomDateFormat
/** * @Description JSON形式的全局时间类型转换器 * 自定义的格式化类一定要继承SimpleDateFormat */ public class CustomDateFormat extends SimpleDateFormat { private static final long serialVersionUID = -3201781773655300201L; public String defaultDateFormat; public String defaultTimeZone; public CustomDateFormat(String pattern,String defaultTimeZone){ this.defaultDateFormat = pattern; this.defaultTimeZone = defaultTimeZone; } /** * 只要覆盖parse(String)这个方法即可 */ @Override public Date parse(String dateStr, ParsePosition pos) { return getDate(dateStr, pos); } /** * 前端传的日期字符串转Date * @param dateStr * @return */ @Override public Date parse(String dateStr) { ParsePosition pos = new ParsePosition(0); return getDate(dateStr, pos); } //可以根据前端传递的时间格式自动匹配格式化 private Date getDate(String dateStr, ParsePosition pos) { SimpleDateFormat sdf = null; if (StringUtils.isBlank(dateStr)) { return null; } else if (dateStr.matches("^\\d{4}-\\d{1,2}$")) { sdf = new SimpleDateFormat("yyyy-MM"); return sdf.parse(dateStr, pos); } else if (dateStr.matches("^\\d{4}-\\d{1,2}-\\d{1,2}$")) { sdf = new SimpleDateFormat("yyyy-MM-dd"); return sdf.parse(dateStr, pos); } else if (dateStr.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}$")) { sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm"); return sdf.parse(dateStr, pos); } else if (dateStr.matches("^\\d{4}-\\d{1,2}-\\d{1,2} {1}\\d{1,2}:\\d{1,2}:\\d{1,2}$")) { sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.parse(dateStr, pos); } else if (dateStr.length() == 23) { sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); return sdf.parse(dateStr, pos); } return super.parse(dateStr, pos); } /** * 后端返回的日期格式化指定字符串 * @param date * @param toAppendTo * @param fieldPosition * @return */ @Override public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition){ return new StringBuffer(DateFormatUtils.format(date, defaultDateFormat, TimeZone.getTimeZone(defaultTimeZone))); } }
2、自定义表单日期转化器
/** * @Description 表单形式的全局时间类型转换器 */ @Configuration public class DateConverter implements Converter<String, Date> { private static final List<String> FORMARTS = new ArrayList<String>(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 = (Date) dateFormat.parse(dateStr); } catch (Exception e) { } return date; } }
3、注册自定义的日期转化器
@Slf4j @Configuration public class WebMvcConfigurer implements WebMvcConfigurer { private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss"); @Value("${spring.jackson.date-format:yyyy-MM-dd HH:mm:ss}") private String defaultDateFormat; @Value("${spring.jackson.time-zone:UTC}") private String defaultTimeZone; /** * JSON全局日期转换器 */ public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() { MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); //设置日期格式 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setDateFormat(new CustomDateFormat(defaultDateFormat,defaultTimeZone)); //支持LocalDateTime、LocalDate、LocalTime的序列化 JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER)); javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DATE_FORMATTER)); javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(TIME_FORMATTER)); objectMapper.registerModule(javaTimeModule); // 设置时区 objectMapper.setTimeZone(TimeZone.getTimeZone(defaultTimeZone)); mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper); //忽略不能识别的字段 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); //忽略非空字段 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //设置中文编码格式 List<MediaType> list = new ArrayList<MediaType>(); list.add(MediaType.APPLICATION_JSON); mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list); return mappingJackson2HttpMessageConverter; } /** * 注册转化器 * 注意:List<HttpMessageConverter>的转化器是按顺序生效,前面的执行了,后面的就不会执行了 * @param converters */ @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //将fastJson添加到视图消息转换器列表内 converters.add(0,getMappingJackson2HttpMessageConverter()); } /** * 表单全局日期转换器 */ @Bean public ConversionService getConversionService(DateConverter dateConverter){ ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean(); Set<Converter> converters = new HashSet<>(); converters.add(dateConverter); factoryBean.setConverters(converters); return factoryBean.getObject(); } }
这样采用自定义的方式,主要完成了自动根据时间参数的格式去匹配时间格式化完成Date类型的参数绑定。
四、补充
前端传递到后端的时间,一般会带有时区,这就导致传递的是CST时区,保存到数据库自动转化成了UTC时区。出现传入时间和保存到数据库的时间不一致的问题。
注意:
保证数据库连接的时区也采用GMT+8能解决大部分时间不一致的问题。
解决方案如下:
方案一: (经验证,解决我的问题)
将以下行添加到 application.properties 文件:
spring.jackson.deserialization.ADJUST_DATES_TO_CONTEXT_TIME_ZONE = false
原理主要是不将前端的时区传到后端。
方式二:(参考,待验证)
在 Application.java(带有 main 方法的类)中设置全局时区:
@PostConstruct void started() { TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC")); }
总结
本文主要介绍了spring boot项目中,如何进行全局日期格式化的配置。
并更进一步介绍了通过自定义格式化类,实现自动根据时间参数的格式去匹配时间格式化完成Date类型的参数绑定,
帮助提高日常开发效率。