前言
"这个时间范围的查询怎么一条数据都查不出来?"
接到异常时,检查了入参日志,日期参数看起来没有问题。又检查了接口的请求日志和响应日志,API 返回了 200,数据为空。没有异常、没有错误码。
排查了两个小时后发现------Java 的 Date 对象传给外部 API 时,毫秒值精度没有做转换,对方的接口期望的是秒级 10 位时间戳,我们传了 13 位。
问题现象
跨系统调用示意图:
scss
[Java 服务] ---[Date 对象]---> [外部数据平台 API]
Date.getTime() = 1715000000000 (13位毫秒)
外部API期望 = 1715000000 (10位秒)
外部接口收到时间参数后,正常返回了状态码 200,响应体也没有任何"参数错误"的提示,只是结果集为空。
为什么 Date 对象不"通用"
Java Date 的精度底层
java
Date now = new Date();
System.out.println(now.getTime());
// 输出:1715000000123 (13 位,毫秒精度)
Java 的 Date 类底层是用毫秒值(long)存储的,getTime() 返回的是从 1970-01-01 UTC 到现在的毫秒数,一定是 13 位(当前时间范围内)。
跨系统的精度差异
很多系统(特别是非 Java 生态的 API、部分 Python/Go 服务)使用秒级时间戳(10 位)。直接传 Java Date 对象时,序列化框架(如 Jackson、Fastjson、Forest)的默认行为可能是:
- 序列化为毫秒值(13 位)
- 或序列化为时间字符串("2024-05-06T10:30:00.000+08:00")
- 或序列化为时间戳对象结构
每个框架的行为不同,而目标 API 期望的格式只有查文档才能确认。
最坑的点:没有任何异常
如果传错了参数类型或格式,大多数 API 会返回 400 或明确的错误提示。但时间戳精度差异的惩罚是------API 正常处理请求,根据错误的时间范围查询,结果为空,返回 200 + 空数组/空对象。从调用方的角度看,"一切正常,只是没有数据"。
正确做法
方案一:接口参数用 Long 类型,强制调用方显式转换
java
// ❌ 避免:Date 类型参数,隐式序列化
@ForestClient
public interface DataApiClient {
@PostRequest("/api/query")
Result query(@Body("startDate") Date startDate,
@Body("endDate") Date endDate);
}
// ✅ 推荐:Long 类型参数,强制调用方做精度转换
@ForestClient
public interface DataApiClient {
@PostRequest("/api/query")
Result query(@Body("startDate") Long startTime,
@Body("endDate") Long endTime);
}
调用方在传入前显式转换精度:
java
// 确认外部 API 文档要求的是秒级时间戳
Date startDate = ...;
Long startTime = startDate.getTime() / 1000; // 毫秒 → 秒
service.query(startTime, endTime);
方案二:如果必须传 Date,加注解指定序列化格式
java
@ForestClient
public interface DataApiClient {
@PostRequest("/api/query")
Result query(@Body("startDate") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date startDate,
@Body("endDate") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date endDate);
}
但这种方式依然有风险------如果目标 API 期望的是秒级 Unix 时间戳而不是格式化字符串,还是不对。
最稳妥的做法
查 API 文档 → 显式转换 → 集成测试验证
- 确认目标 API 文档中的时间参数格式(秒戳/毫秒戳/格式化字符串)
- 在调用代码中显式转换并注释精度选择的原因
- 集成测试中包括一个已知时间范围的查询用例,验证返回数据的正确性
java
// 在集成测试中验证时间戳精度
@Test
void testTimeRangeQuery() {
// 选定一个已知有数据的日期范围
Long startTime = date("2024-01-01").getTime() / 1000; // 秒级
Long endTime = date("2024-01-31").getTime() / 1000; // 秒级
Result result = client.query(startTime, endTime);
assertThat(result.getData()).isNotEmpty(); // 确认能查到数据
}
总结
这个坑的本质是 Java Date 毫秒精度与其他系统的默认对齐问题。跨系统传时间参数时:
- 不要默认 Java Date 的序列化行为与目标 API 一致
- 优先用 Long 类型显式控制精度
- 明确 10 位(秒)还是 13 位(毫秒),在代码注释中标明
- 集成测试覆盖时间范围查询的场景
- 这类错误静默发生,日志里看不出异常,是最难排查的一类问题
你遇到过类似的静默数据错误吗?欢迎分享你排查这类问题的经验。