本文由 本人CSDN转码, 原文地址 警惕!Long类型ID精度丢失——从一次诡异的“00”尾巴Bug说起(long类型属性前端展示最后几位都变成0)_long类型精度丢失-CSDN博客
一、问题现场:消失的数字
在一次再普通不过的消息发送功能中,我们遇到了一个bug:
Map<String, Object> Map = new HashMap<>();
Map.put("ecId", ecOrder.getId()); // 源值:123456789012345678
Map.put("doctorId", ecOrder.getDoctorId()); // 源值:987654321098765432
// 经过JSON序列化传输后,另一端收到的值变成了:
ecId: 123456789012345000
doctorId: 987654321098765000
数字的末尾几位莫名其妙地变成了 "00",像是被什么东西 "咬掉" 了一样。
二、真相大白:JavaScript 的数字之殇
问题的根源不在 Java,而在 JavaScript。
1. JavaScript 的数字精度限制
JavaScript 遵循 IEEE 754 标准,所有数字都以 64 位双精度浮点数存储。这意味着:
-
安全整数范围:
-2^53 + 1到2^53 - 1(即-9007199254740991到9007199254740991) -
超过这个范围的数字将无法精确表示,会导致精度丢失
2. Java Long 类型 vs JavaScript Number
| 类型 | 范围 | 精度 |
|---|---|---|
| Java Long | -2^63 到 2^63-1 | 精确表示 19 位数字 |
| JavaScript Number | ±2^53-1 | 精确表示 16 位数字 |
当 19 位的 Long 类型 ID(如123456789012345678)传到前端时,JavaScript 无法精确表示,于是末几位就变成了 "000"。
三、问题重现:一个简单的测试
public class LongPrecisionTest {
public static void main(String[] args) {
Long bigId = 123456789012345678L;
String json = JSON.toJSONString(Collections.singletonMap("id", bigId));
System.out.println("序列化前: " + bigId);
System.out.println("JSON字符串: " + json);
// 模拟前端JavaScript处理
// 在JS中: JSON.parse('{"id":123456789012345678}')
// 会得到: {id: 123456789012345000}
}
}
四、解决方案:四重防护策略
方案 1:字符串化传输(推荐)
后端处理:
// 在序列化前将Long转为String
Map<String, Object> data = new HashMap<>();
data.put("id", String.valueOf(123456789012345678L));
data.put("name", "会诊订单");
String json = JSON.toJSONString(data);
前端处理:
// 收到数据后,如果需要数值运算,使用BigInt
const data = JSON.parse(response);
const id = BigInt(data.id); // 使用BigInt处理大整数
**方案 2:**使用 @JsonFormat 注解
可以使用@JsonFormat(shape = JsonFormat.Shape.STRING)将字段转换为 String 类型
@Data
@AllArgsConstructor
public class Student {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private long id;
private String name;
}
方案 3:配置 JSON 序列化器
使用 Jackson:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// 将Long类型序列化为字符串
SimpleModule module = new SimpleModule();
module.addSerializer(Long.class, ToStringSerializer.instance);
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(module);
return objectMapper;
}
}
使用 FastJSON:
SerializeConfig config = new SerializeConfig();
config.put(Long.class, ToStringSerializer.instance);
config.put(Long.TYPE, ToStringSerializer.instance);
String json = JSON.toJSONString(data, config);
方案 4:自定义序列化方案
public class LongToStringSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) {
gen.writeString(String.valueOf(value));
}
}
// 在实体类中使用
public class Order {
@JsonSerialize(using = LongToStringSerializer.class)
private Long id;
// other fields
}
方案 5:统一响应体封装
@Data
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;
// 自动处理Long类型转String
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setSuccess(true);
response.setData(processLongTypes(data));
return response;
}
private static Object processLongTypes(Object data) {
if (data instanceof Long) {
return String.valueOf(data);
}
if (data instanceof Map) {
// 遍历Map处理Long类型
}
if (data instanceof Collection) {
// 遍历集合处理Long类型
}
return data;
}
}
五、实战修复:我们的代码优化
修改前:
Map<String, Object> Map = new HashMap<>();
Map.put("ecId", ecOrder.getId()); // 可能精度丢失
修改后:
Map<String, Object> Map= new HashMap<>();
// 所有Long类型字段都转为String
Map.put("ecId", String.valueOf(ecOrder.getId()));
Map.put("doctorId", String.valueOf(ecOrder.getDoctorId()));
Map.put("toDoctorId", String.valueOf(ecOrder.getToDoctorId()));
// ...其他字段
六、预防措施:建立开发规范
-
API 设计规范:所有 ID 字段在接口中均以 String 类型传输
-
代码审查:重点检查 Long 类型在前后端交互处的处理
-
测试用例:添加大整数精度测试用例
-
文档注释:在相关代码处添加注释说明精度问题
/**
* 注意:Long类型ID直接JSON序列化到前端可能会导致精度丢失
* 必须转换为String类型传输
*/
public static final String convertLongToString(Long value) {
return value != null ? String.valueOf(value) : null;
}
七、总结
这次 "00 尾巴"Bug 给我们上了深刻的一课:在分布式系统和前后端分离架构中,数据类型的一致性至关重要。
-
根本原因:JavaScript 的数字精度限制与 Java Long 类型的范围不匹配
-
解决方案:字符串化传输 + 序列化配置
-
核心建议:在系统设计初期就考虑数据精度问题,建立统一的数据传输规范
记住这个数字:2^53 = 9007199254740992。超过这个值的整数,在前端都会面临精度丢失的风险!