
spring rest api 中,当 json 输入的 `epoch` 字段为毫秒级时间戳字符串(如 `"683124845000"`)时,jackson 默认按秒解析导致时间严重错乱(如误为公元23617年),需通过自定义序列化器/反序列化器或类型调整解决。
在基于 Spring Boot 的 REST 服务中,Instant 类型字段默认由 Jackson 使用 InstantDeserializer 处理,而该反序列化器期望输入为秒级数值(如 1717020000)或 ISO-8601 字符串(如 "2024-05-30T02:00:00Z")。但若前端传入的是毫秒级时间戳字符串(例如 "683124845000",对应 1991-10-26T02:20:45Z),Jackson 会错误地将其解释为秒级值(≈ 21677 年),从而造成时间逻辑崩溃。
✅ 正确解决方案(推荐)
最健壮且语义清晰的方式是:为 Instant 字段显式注册毫秒级反序列化逻辑,保持领域模型使用 Instant(不可变、时区安全),同时兼容毫秒精度输入。
1. 编写自定义反序列化器(核心)
public class MillisInstantDeserializer extends JsonDeserializer{ @Override public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // 支持字符串或数字输入 if (p.getCurrentToken() == JsonToken.VALUE_STRING) { try { long millis = Long.parseLong(p.getText().trim()); return Instant.ofEpochMilli(millis); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid epoch millis string: " + p.getText(), e); } } else if (p.getCurrentToken() == JsonToken.VALUE_NUMBER_INT || p.getCurrentToken() == JsonToken.VALUE_NUMBER_FLOAT) { return Instant.ofEpochMilli(p.getLongValue()); } else { throw new IllegalArgumentException("Expected numeric or string epoch millis, but got: " + p.getCurrentToken()); } } }
2. 编写配套序列化器(可选,确保输出一致)
public class MillisInstantSerializer extends JsonSerializer{ @Override public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeNumber(value.toEpochMilli()); } }
3. 应用到目标字段
public class Booking {
@JsonSerialize(using = MillisInstantSerializer.class)
@JsonDeserialize(using = MillisInstantDeserializer.class)
private Instant epoch;
private String email;
// constructors, getters, setters...
}✅ 效果验证:输入 {"epoch": "683124845000", "email": "user@example.com"} → epoch 正确解析为 1991-10-26T02:20:45Z。
⚠️ 其他可行方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 改用 long 存储毫秒值 | 简单、零配置、无依赖 | 失去 Instant 的语义表达力(如 plusDays()、时区转换),业务层需手动封装 | 快速原型、内部 DTO |
| 统一改为秒级 API | 符合 Jackson 默认行为 | 需前端配合改造,可能破坏现有客户端;秒级精度丢失(无法表示毫秒事件) | 新建系统、全栈可控环境 |
| 全局配置 Jackson Instant 反序列化器 | 一劳永逸 | 影响所有 Instant 字段,存在副作用风险(如 ISO 字符串被误处理) | 全项目严格统一毫秒约定 |
? 注意事项
- 永远避免 @JsonFormat(pattern="...") 用于时间戳数字:该注解仅适用于日期字符串格式(如 "yyyy-MM-dd"),对纯数字无效。
- 输入校验不可少:毫秒值应介于 0L 和 Instant.MAX.toEpochMilli()(约 31556889864403199L)之间,建议在反序列化器中增加范围检查。
-
测试覆盖关键路径:
@Test void shouldParseMillisStringCorrectly() throws Exception { String json = "{\"epoch\":\"683124845000\",\"email\":\"test@x.com\"}"; Booking booking = new ObjectMapper().readValue(json, Booking.class); assertEquals(Instant.parse("1991-10-26T02:20:45Z"), booking.getEpoch()); }
通过上述方式,你既能保留 Instant 的类型安全与时间语义,又能精准支持毫秒级时间戳的 JSON 输入,彻底规避因单位误解引发的时间灾难。










