mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-10 06:46:04 +08:00
fix(json): 注册 OffsetDateTime query/form 参数 Converter 修复时间范围查询绑定失败; 抽取 OffsetDateTimeParseUtil 工具类(@UtilityClass)
This commit is contained in:
@@ -4,6 +4,7 @@ import jakarta.annotation.PostConstruct;
|
||||
import cn.daxpay.open.platform.common.json.jdk.Java8TimeFormatModule;
|
||||
import cn.daxpay.open.platform.common.json.jdk.JavaLongTypeModule;
|
||||
import cn.daxpay.open.platform.common.json.util.JacksonUtil;
|
||||
import cn.daxpay.open.platform.common.json.util.OffsetDateTimeParseUtil;
|
||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||
@@ -11,15 +12,18 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.boot.jackson.autoconfigure.JsonMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import tools.jackson.databind.DeserializationFeature;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.SerializationFeature;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/// # jackson 序列化自动配置
|
||||
/// # jackson 序列化与时间参数自动配置
|
||||
///
|
||||
/// 使用 Spring Boot 4 标准的 JsonMapperBuilderCustomizer 定制机制, 不手动创建 ObjectMapper
|
||||
/// 使用 Spring Boot 4 标准的 JsonMapperBuilderCustomizer 定制机制, 不手动创建 ObjectMapper.
|
||||
/// 另注册 OffsetDateTime 的 query/form 参数 [Converter], 使前端时间范围无时区字符串可绑定 (见 [#offsetDateTimeConverter])
|
||||
@Configuration
|
||||
public class JacksonConfiguration {
|
||||
|
||||
@@ -64,4 +68,12 @@ public class JacksonConfiguration {
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
||||
/// OffsetDateTime query/form 参数转换 (Spring ConversionService, 非 jackson)
|
||||
/// 前端时间范围选择器提交 `yyyy-MM-dd HH:mm:ss` 无时区字符串, 默认无法绑定 OffsetDateTime,
|
||||
/// 复用 [OffsetDateTimeParseUtil] 与 JSON 反序列化保持两条绑定路径解析一致
|
||||
@Bean
|
||||
Converter<String, OffsetDateTime> offsetDateTimeConverter() {
|
||||
return OffsetDateTimeParseUtil::parse;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,45 +1,19 @@
|
||||
package cn.daxpay.open.platform.common.json.deserializer;
|
||||
|
||||
import cn.daxpay.open.platform.common.json.util.OffsetDateTimeParseUtil;
|
||||
import tools.jackson.core.JacksonException;
|
||||
import tools.jackson.core.JsonParser;
|
||||
import tools.jackson.databind.DeserializationContext;
|
||||
import tools.jackson.databind.ValueDeserializer;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/// # OffsetDateTime 反序列化器, 支持三种格式:
|
||||
/// ## - ISO 8601 带偏移: 2026-06-13T08:30:00Z 或 2026-06-13T16:30:00+08:00
|
||||
/// ## - 旧格式: 2026-06-13 16:30:00 (按 Asia/Shanghai 时区解析后转 UTC)
|
||||
/// ## - 旧格式毫秒: 2026-06-13 16:30:00.123
|
||||
///
|
||||
/// # OffsetDateTime 反序列化器
|
||||
/// 解析逻辑详见 [OffsetDateTimeParseUtil], 支持三种格式: ISO 带偏移 / `yyyy-MM-dd HH:mm:ss` / `yyyy-MM-dd HH:mm:ss.SSS`
|
||||
public class DaxpayOffsetDateTimeDeserializer extends ValueDeserializer<OffsetDateTime> {
|
||||
|
||||
private static final DateTimeFormatter FORMATTER_OLD = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final DateTimeFormatter FORMATTER_OLD_MS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
private static final ZoneOffset SHANGHAI_OFFSET = ZoneOffset.ofHours(8);
|
||||
|
||||
@Override
|
||||
public OffsetDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
|
||||
String text = p.getString();
|
||||
if (text == null || text.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return OffsetDateTime.parse(text);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
try {
|
||||
if (text.contains(".")) {
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(text, FORMATTER_OLD_MS);
|
||||
return localDateTime.atOffset(SHANGHAI_OFFSET).withOffsetSameInstant(ZoneOffset.UTC);
|
||||
}
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(text, FORMATTER_OLD);
|
||||
return localDateTime.atOffset(SHANGHAI_OFFSET).withOffsetSameInstant(ZoneOffset.UTC);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("无法解析日期时间: " + text, e);
|
||||
}
|
||||
return OffsetDateTimeParseUtil.parse(p.getString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package cn.daxpay.open.platform.common.json.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/// # OffsetDateTime 字符串解析工具
|
||||
/// 供 Jackson 反序列化器与 Spring query/form 参数 [Converter] 共用,
|
||||
/// 保证 JSON body 与 GET query / form 表单两条绑定路径的解析行为一致.
|
||||
///
|
||||
/// 支持三种格式:
|
||||
/// ## - ISO 8601 带偏移: 2026-06-13T08:30:00Z 或 2026-06-13T16:30:00+08:00
|
||||
/// ## - 旧格式: 2026-06-13 16:30:00 (按东八区解析后转 UTC)
|
||||
/// ## - 旧格式毫秒: 2026-06-13 16:30:00.123
|
||||
@UtilityClass
|
||||
public class OffsetDateTimeParseUtil {
|
||||
|
||||
private static final DateTimeFormatter FORMATTER_OLD = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private static final DateTimeFormatter FORMATTER_OLD_MS = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
private static final ZoneOffset SHANGHAI_OFFSET = ZoneOffset.ofHours(8);
|
||||
|
||||
/// 将字符串解析为 OffsetDateTime
|
||||
public OffsetDateTime parse(String text) {
|
||||
if (text == null || text.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
// 优先按 ISO 带偏移解析
|
||||
try {
|
||||
return OffsetDateTime.parse(text);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
// 回退: 旧格式(无时区字面量), 先用 LocalDateTime 接住, 再附加东八区偏移转为 UTC
|
||||
try {
|
||||
DateTimeFormatter formatter = text.contains(".")
|
||||
? FORMATTER_OLD_MS
|
||||
: FORMATTER_OLD;
|
||||
LocalDateTime localDateTime = LocalDateTime.parse(text, formatter);
|
||||
return localDateTime.atOffset(SHANGHAI_OFFSET).withOffsetSameInstant(ZoneOffset.UTC);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("无法解析日期时间: " + text, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user