# 日期处理

# 使用 Java8 日期

Java8 新的日期类型对时区和类型之间转换都能友好支持,所以我们采用 LocalDateTime

注意

如果 mybatis core 低于 3.4.5 版本,需要手动引入 mybatis-typehandlers-jsr310 用于映射转换
本项目为高版本,无需引入

数据库连接池也能也会导致映射失败,例如 druid@1.1.17 映射失败,升级到 druid@1.1.22 即可

# 国际化情况下的日期使用

前后端之间传递的都是 unix 时间戳,unix 时间戳指的是零时区 1970-1-1 起所经过的秒数,所以后端接收到时间戳后,把它转换为一个东八区的 LocalDateTime ,这样后台系统就不用再考虑时区的问题,因为在系统的最前方反序列化的过程中已经完成了时区的转换,系统内部所有的时间都是东八区的,包括数据库。

后端给前端反数据时,将 LocalDateTime 转换为时间戳,前端获取到时间戳后,根据时区转换成相应的时间展示给用户。

建议

在不需要国际化的情况下,后端可以以 yyyy-MM-dd HH:mm:ss 的形式返给前端,方便前端展示

# 代码实现

对于通过 @requestBody 反序列化的对象,可以使用 Jackson 提供的全局解析方式:

@JsonComponent
public class DateFormatConfig {

    private DateFormatConfig() {}

    private static final DateTimeFormatter dateTimeFormatter =
            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /** 日期格式化为字符串 */
    public static class DateJsonSerializer extends JsonSerializer<LocalDateTime> {

        @Override
        public void serialize(
                LocalDateTime localDateTime,
                JsonGenerator jsonGenerator,
                SerializerProvider serializerProvider)
                throws IOException {
            jsonGenerator.writeString(dateTimeFormatter.format(localDateTime));
        }
    }

    /** 解析日期字符串 */
    public static class DateJsonDeserializer extends JsonDeserializer<LocalDateTime> {

        @Override
        public LocalDateTime deserialize(
                JsonParser jsonParser, DeserializationContext deserializationContext)
                throws IOException {
            String value = jsonParser.getText();

            String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
            String timeStampFormat = "^\\d+$";

            if (StringUtils.isEmpty(value)) {
                return null;
            }
            if (value.matches(dateTimeFormat)) {
                return LocalDateTime.parse(value, dateTimeFormatter);
            }
            if (value.matches(timeStampFormat)) {
                return LocalDateTime.ofInstant(
                        Instant.ofEpochSecond(Long.parseLong(value)),
                        ZoneId.of(GlobalConst.TIME_ZONE_ID));
            }
            return null;
        }
    }
}

对于直接 param 映射的对象,需要配合 @InitBinder 和 @ControllerAdvice 注解重写反序列化逻辑,如下:

// ParamFormatConfig.java
@Component
public class ParamFormatConfig implements Converter<String, LocalDateTime> {

    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final String DATE_FORMAT_REGEXP = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}";
    private static final String TIME_STAMP_FORMAT_REGEXP = "^\\d+$";
    private static final DateTimeFormatter dateTimeFormatter =
            DateTimeFormatter.ofPattern(DATE_FORMAT);

    // param格式化(http url)
    @Override
    public LocalDateTime convert(@Nullable String value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        if (value.matches(DATE_FORMAT_REGEXP)) {
            return LocalDateTime.parse(value, dateTimeFormatter);
        }
        if (value.matches(TIME_STAMP_FORMAT_REGEXP)) {
            return LocalDateTime.ofInstant(
                    Instant.ofEpochSecond(Long.parseLong(value)),
                    ZoneId.of("+8"));
        }
        throw new ServiceException(ResultStatus.FORMAT_ERROR);
    }
}

// ControllerHandler.java
@ControllerAdvice
public class ControllerHandler {

    @Resource private ParamFormatConfig paramFormatConfig;

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        GenericConversionService genericConversionService =
                (GenericConversionService) binder.getConversionService();
        if (genericConversionService != null) {
            genericConversionService.addConverter(paramFormatConfig);
        }
    }
}

本项目把他们合并写在 DateFormatConfig.javaControllerHandler.java 中,具体见代码详情