diff --git a/mhd-common/mhd-common-core/src/main/java/com/mhd/common/core/jackson/FlexibleDateDeserializer.java b/mhd-common/mhd-common-core/src/main/java/com/mhd/common/core/jackson/FlexibleDateDeserializer.java new file mode 100644 index 000000000..91dc9d344 --- /dev/null +++ b/mhd-common/mhd-common-core/src/main/java/com/mhd/common/core/jackson/FlexibleDateDeserializer.java @@ -0,0 +1,112 @@ +package com.mhd.common.core.jackson; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; + +import java.io.IOException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 宽松日期反序列化:兼容 yyyy-MM-dd(纯日期)等常见格式。 + * + * 背景:库存查询等接口日期只保留年月日后,前端可能把 "2026-07-06" 这类纯日期回传, + * 而实体/DTO 上 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 是严格格式,反序列化直接报 + * InvalidFormatException。本反序列化器解析顺序: + * 1) 字段 @JsonFormat 指定的模式(严格全匹配,保持原行为); + * 2) 兜底常见格式:yyyy-MM-dd HH:mm:ss → yyyy-MM-dd HH:mm → yyyy-MM-dd; + * 3) 纯数字按毫秒时间戳; + * 4) 交给 Jackson 默认解析(ISO-8601 等)。 + * 仅放宽接受范围,不影响任何既有格式的解析结果;由各服务 DateCompatibilityConfig 显式注册生效。 + * + * @author rcx + * @date 2026-09-05 + */ +public class FlexibleDateDeserializer extends JsonDeserializer implements ContextualDeserializer { + + /** 兜底格式按长度降序排列,避免长文本被短格式部分解析 */ + private static final String[] FALLBACK_PATTERNS = { + "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd" + }; + + /** 字段 @JsonFormat 指定的模式,可为空 */ + private final String pattern; + + public FlexibleDateDeserializer() { + this(null); + } + + public FlexibleDateDeserializer(String pattern) { + this.pattern = pattern; + } + + @Override + public Date deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException { + String value = parser.getText(); + if (value == null || value.trim().isEmpty()) { + return null; + } + value = value.trim(); + + // 纯数字按毫秒时间戳(10位秒级时间戳也兼容) + if (value.matches("\\d{10,}")) { + return new Date(Long.parseLong(value)); + } + + // 1) 字段 @JsonFormat 指定的模式优先 + Date parsed = tryParse(value, pattern); + // 2) 兜底常见格式 + if (parsed == null) { + for (String fallback : FALLBACK_PATTERNS) { + parsed = tryParse(value, fallback); + if (parsed != null) { + break; + } + } + } + // 3) 交给 Jackson 默认解析(ISO-8601 等) + if (parsed == null) { + try { + parsed = ctxt.parseDate(value); + } catch (IllegalArgumentException ignore) { + // 走不到默认解析时统一在下方抛出友好异常 + } + } + if (parsed == null) { + throw ctxt.weirdStringException(value, Date.class, + "支持格式:yyyy-MM-dd HH:mm:ss / yyyy-MM-dd HH:mm / yyyy-MM-dd"); + } + return parsed; + } + + /** + * 严格全匹配解析:解析失败或文本未完整消费(如按 yyyy-MM-dd 解析 "2026-07-06 10:00:00") + * 都视为不匹配返回 null,交由下一个格式尝试 + */ + private Date tryParse(String value, String pattern) { + if (pattern == null || pattern.isEmpty()) { + return null; + } + SimpleDateFormat sdf = new SimpleDateFormat(pattern); + sdf.setLenient(false); + ParsePosition pos = new ParsePosition(0); + Date date = sdf.parse(value, pos); + return (date != null && pos.getIndex() == value.length()) ? date : null; + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) { + if (property != null) { + JsonFormat jsonFormat = property.getAnnotation(JsonFormat.class); + if (jsonFormat != null && jsonFormat.pattern() != null && !jsonFormat.pattern().isEmpty()) { + return new FlexibleDateDeserializer(jsonFormat.pattern()); + } + } + return this; + } +} diff --git a/mhd_oms/src/main/java/com/mhd/oms/config/DateCompatibilityConfig.java b/mhd_oms/src/main/java/com/mhd/oms/config/DateCompatibilityConfig.java new file mode 100644 index 000000000..714c4c851 --- /dev/null +++ b/mhd_oms/src/main/java/com/mhd/oms/config/DateCompatibilityConfig.java @@ -0,0 +1,33 @@ +package com.mhd.oms.config; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.mhd.common.core.jackson.FlexibleDateDeserializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Date; + +/** + * JSON 日期反序列化兼容配置:Date 字段兼容 yyyy-MM-dd(纯日期)等格式。 + * + * 背景:库存查询日期只保留年月日后,前端回传纯日期(如 createTime=2026-07-06)时, + * 实体/DTO 上 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 严格格式反序列化报 + * InvalidFormatException。注册 FlexibleDateDeserializer 后优先按字段 @JsonFormat + * 严格解析,失败时兼容纯日期等常见格式(与 WMS 库存查询一致)。 + * + * Spring Boot 会自动把容器中的 Module Bean 注册进 MVC 使用的 ObjectMapper。 + * + * @author rcx + * @date 2026-09-05 + */ +@Configuration +public class DateCompatibilityConfig { + + @Bean + public Module flexibleDateModule() { + SimpleModule module = new SimpleModule("oms-flexible-date-module"); + module.addDeserializer(Date.class, new FlexibleDateDeserializer()); + return module; + } +} diff --git a/mhd_wms/src/main/java/com/mhd/wms/config/DateCompatibilityConfig.java b/mhd_wms/src/main/java/com/mhd/wms/config/DateCompatibilityConfig.java new file mode 100644 index 000000000..8763207b7 --- /dev/null +++ b/mhd_wms/src/main/java/com/mhd/wms/config/DateCompatibilityConfig.java @@ -0,0 +1,33 @@ +package com.mhd.wms.config; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.mhd.common.core.jackson.FlexibleDateDeserializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.Date; + +/** + * JSON 日期反序列化兼容配置:Date 字段兼容 yyyy-MM-dd(纯日期)等格式。 + * + * 背景:库存查询日期只保留年月日后,前端回传纯日期(如 createTime=2026-07-06)时, + * 实体/DTO 上 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") 严格格式反序列化报 + * InvalidFormatException。注册 FlexibleDateDeserializer 后优先按字段 @JsonFormat + * 严格解析,失败时兼容纯日期等常见格式(与 OMS 库存查询一致)。 + * + * Spring Boot 会自动把容器中的 Module Bean 注册进 MVC 使用的 ObjectMapper。 + * + * @author rcx + * @date 2026-09-05 + */ +@Configuration +public class DateCompatibilityConfig { + + @Bean + public Module flexibleDateModule() { + SimpleModule module = new SimpleModule("wms-flexible-date-module"); + module.addDeserializer(Date.class, new FlexibleDateDeserializer()); + return module; + } +}