JavaScript大数ID精度丢失问题的解决方案
作者:当归1024
问题背景
在前后端分离的Web应用开发中,经常会遇到一个令人头疼的问题:JavaScript数字精度丢失。当后端使用Long类型生成的大数ID传递到前端时,会出现精度丢失,导致前端获取的ID与后端实际存储的ID不一致。
问题现象
在我们的若依(RuoYi)邮件服务器管理系统中,出现了以下问题:
- 数据库中的ID:
1965951881494065154
- Vue前端接收到的ID:
1965951881494065200
- 结果: 修改操作失败,因为前端发送的ID与数据库中的实际ID不匹配
问题根源分析
JavaScript数字精度限制
JavaScript中的数字都是以64位浮点数存储的,其中:
- 1位符号位
- 11位指数位
- 52位尾数位
这意味着JavaScript能够安全表示的最大整数是 2^53 - 1 = 9007199254740991
,即 Number.MAX_SAFE_INTEGER
。
雪花算法ID的特点
现代分布式系统中常用的雪花算法(Snowflake)生成的ID通常是19位的长整型数字,如:
1965951881494065154 (19位)
这个数字远远超过了JavaScript的安全整数范围,因此在JSON序列化传输到前端时会发生精度丢失。
解决方案
核心思路
将Long类型的ID在JSON序列化时转换为字符串,避免JavaScript的数字精度问题。
使用Jackson的ToStringSerializer
在Java实体类中,对ID字段添加 @JsonSerialize(using = ToStringSerializer.class)
注解。
实施步骤
1. 添加必要的导入
在实体类中添加Jackson相关的导入:
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
2. 在ID字段上添加注解
/** 服务器ID */ @JsonSerialize(using = ToStringSerializer.class) private Long id;
3. 完整的实体类示例
package com.ruoyi.email.domain; import java.io.Serializable; import java.util.Date; import javax.validation.constraints.*; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.annotation.Excel.ColumnType; import lombok.Data; import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode @TableName("tbl_email_server") public class EmailServer implements Serializable { private static final long serialVersionUID = 1L; /** 服务器ID */ @JsonSerialize(using = ToStringSerializer.class) private Long id; // ... 其他字段 }
解决方案的工作原理
序列化过程
- 后端序列化: Long类型的ID
1965951881494065154
被序列化为字符串"1965951881494065154"
- 前端接收: JavaScript接收到字符串格式的ID,不会有精度丢失
- 前端发送: 前端将字符串ID发送回后端
- 后端反序列化: Spring Boot自动将字符串转换回Long类型
JSON数据格式对比
修改前:
{ "id": 1965951881494065200, // 精度丢失! "serverCode": "EMAIL_001", "name": "主邮件服务器" }
修改后:
{ "id": "1965951881494065154", // 字符串格式,精度保持! "serverCode": "EMAIL_001", "name": "主邮件服务器" }
前端处理注意事项
HTML模板中的处理
由于ID现在是字符串格式,在前端模板中可以正常使用:
<template> <el-table :data="serverList"> <el-table-column prop="id" label="ID" /> <el-table-column label="操作"> <template #default="{ row }"> <!-- 字符串ID可以直接使用 --> <el-button @click="editServer(row.id)">编辑</el-button> </template> </el-table-column> </el-table> </template>
JavaScript中的处理
// 接收数据 const serverData = { id: "1965951881494065154", // 字符串格式 serverCode: "EMAIL_001", name: "主邮件服务器" }; // 发送请求 updateEmailServer({ id: serverData.id, // 直接使用字符串ID serverCode: "EMAIL_002" });
验证结果
修改完成后,验证效果:
- 数据库ID:
1965951881494065154
- 前端接收ID:
"1965951881494065154"
(字符串格式) - 前端发送ID:
"1965951881494065154"
- 后端接收ID:
1965951881494065154
(自动转换为Long)
结果: ID数据在前后端传输过程中保持完全一致,修改操作成功!
适用场景
这个解决方案适用于以下情况:
- 雪花算法生成的ID: 通常是18-19位的长整型
- 时间戳ID: 毫秒级时间戳生成的长整型ID
- 其他超过JavaScript安全范围的Long类型字段: 如订单号、交易流水号等
性能影响
- 序列化性能: 几乎无影响,字符串序列化很高效
- 网络传输: 略微增加传输字节数(引号)
- 前端处理: 字符串比较操作性能略优于数字比较
其他解决方案对比
方案一:前端使用BigInt
// 需要特殊处理 const id = BigInt("1965951881494065154");
缺点: 需要修改大量前端代码,兼容性问题
方案二:自定义JSON配置
@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() { return new Jackson2ObjectMapperBuilder() .simpleDateFormat("yyyy-MM-dd HH:mm:ss") .serializerByType(Long.class, ToStringSerializer.instance); } }
缺点: 影响全局所有Long类型字段
方案三:使用@JsonSerialize注解
@JsonSerialize(using = ToStringSerializer.class) private Long id;
优点:
- 精确控制,只影响特定字段
- 无需修改前端代码
- 性能影响最小
- 代码清晰易维护
总结
通过在Java实体类的ID字段上添加 @JsonSerialize(using = ToStringSerializer.class)
注解,我们成功解决了JavaScript大数精度丢失的问题。这个方案具有以下优势:
- 简单易实施: 只需添加一个注解
- 影响范围小: 只影响特定字段,不会波及其他功能
- 兼容性好: 前端代码无需修改
- 性能优秀: 几乎无性能损耗
这个问题在现代Web开发中非常常见,特别是在使用分布式ID生成算法的系统中。掌握这个解决方案能够帮助开发者快速解决类似的精度丢失问题,提高系统的稳定性和数据准确性。
以上就是JavaScript大数ID精度丢失问题的解决方案的详细内容,更多关于JavaScript大数ID精度丢失的资料请关注脚本之家其它相关文章!