news 2026/10/10 6:05:57

雪花 ID 传到前端就变了个数?我用全局 Long 转 String 一次收口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
雪花 ID 传到前端就变了个数?我用全局 Long 转 String 一次收口

先说结论

MyBatis-Plus 默认的雪花算法(IdType.ASSIGN_ID)生成的是19 位 Long,而 JS 的 Number 能精确表示的最大整数是2^53 - 1 = 9007199254740991(16 位)。

19 位 > 16 位 →id 以数字形式进 JSON 就必然丢精度,末几位会变。这不是后端"传错了",是数字在 JS 里存不下。

我们的收口方式:在全局 Jackson 配置里把所有Long序列化成 String——而不是在实体字段上逐个加注解,那条路一定会漏。


现象:id 传到前端就变了个数

起因是列表页点「详情」偶尔查不到数据,对着看才发现前端拿到的 id 和数据库里的不是一个数:

数据库 1934567890123456789 接口 JSON(原始) 1934567890123456789 浏览器里读到的 1934567890123456800 ← 末 3 位变了

用 node 复现更直白:

>String(Number("1934567890123456789"))'1934567890123456800'>Number("1934567890123456789")===1934567890123456789false

它不报错、不抛异常。前端拿着这个"差不多对"的 id 去请求详情、去提交更新,结果是查不到数据、或者更新到了别的行——数据量少的时候还可能一直撞不出来,一路带到线上才发现。

根因:19 位超出了 JS 的安全整数范围

JS 里所有数字都是双精度浮点(IEEE 754),能精确表示的整数上限是 2^53 - 1:

Number.MAX_SAFE_INTEGER// 9007199254740991 ← 16 位

超出这个范围,整数只剩 53 位精度,末位被舍入:

Number("9007199254740993")// 9007199254740992 ← 只差 1,也存不下

而雪花 id 的结构(时间戳 41 位 + 机器位 10 位 + 序列号 12 位)决定了它是 19 位十进制数,正好越线。所以只要主键用雪花,前端就一定不能用数字接 id。

我第一版方案:给实体字段加 @JsonSerialize(不够用)

第一反应很自然——既然是主键字段出问题,那就在字段上加注解:

@TableId(type=IdType.ASSIGN_ID)@JsonSerialize(using=ToStringSerializer.class)privateLongid;

单表查询确实好了。但很快发现还有漏网的——只要 id 不是从实体字段序列化出去的,注解就管不到:

  • 统一返回体里的裸 Long:R<Long>直接返回getId()的结果
  • Map<String, Object>拼装的返回值(统计、聚合、联表查询很常见)
  • List<Long>这类集合,以及没继承实体基类的 VO
  • 第三方/工具类里带 Long 字段的对象

这些都是"字段注解覆盖不到"的路径,照样按数字出去、照样变形。加注解的粒度是字段,丢精度的风险却是全链路的。

最终方案:全局把所有 Long 序列化成 String

不逐个字段加注解了,改成声明一条全局规则:所有 Long 类型都用ToStringSerializer:

@AutoConfigurationpublicclassJacksonConfig{@BeanpublicJsonMapperBuilderCustomizerlongToStringCustomizer(){returnbuilder->builder.addModule(newJacksonModule(){@OverridepublicStringgetModuleName(){return"long-to-string";}@OverridepublicVersionversion(){returnVersion.unknownVersion();}@OverridepublicvoidsetupModule(SetupContextcontext){context.addSerializers(newSerializers(){@OverridepublicValueSerializer<?>findSerializer(SerializationConfigconfig,JavaTypetype,BeanDescription.SupplierbeanDesc,JsonFormat.Valueformat){if(type.isTypeOrSubTypeOf(Long.class)){returnToStringSerializer.instance;}returnnull;// 其余类型保持默认序列化}});}});}}

两个容易踩的点:

  1. 用JsonMapperBuilderCustomizer接入,别自己 new 一个 ObjectMapper。交给 Spring Boot 的构建链,spring.jackson.*的既有配置和项目里其它定制不会被顶掉。
  2. 注意包名是tools.jackson.*(Jackson 3),不是网上随手能搜到的com.fasterxml.jackson.*:模块基类从SimpleModule变成了JacksonModule,findSerializer的签名也多出BeanDescription.Supplier和JsonFormat.Value两个参数。照 Jackson 2 的博客抄会直接编译不过。

实体上原来那些@JsonSerialize(using = ToStringSerializer.class)我没删——不冲突,也算兜底;但**"不丢精度"这件事的责任,已经交给全局配置了**。

配套要注意的三件事

  1. 前端把 id 当字符串用:别parseInt(id)、别Number(id)、别+id转数字;路由参数和下拉选中值比较也用字符串(id === String(row.id))。前端"id 就该是数字"的直觉,是最容易埋雷的地方。
  2. 后端接口不用改:@PathVariable Long id、@RequestParam Long id收到字符串照样能自动转换,前端传字符串是安全的。
  3. Long → String 是"全量"的:分页total、计数、状态码这类 Long 也会变成字符串。需要数字的地方(分页组件、图表数据)要么前端Number(),要么在 VO 里改用Integer。这是改全局之后唯一需要额外留意的地方——我一开始也没想到total会跟着变。

其他几种方案,为什么没选

  • 前端上 BigInt / json-bigint 解析:能解决,但要动整条请求解析链路,第三方 UI/图表/富文本组件未必兼容,改动面和回归范围都压在前端。
  • 把主键真的改成 String 存:表结构、索引、数据迁移、外部系统对接全要动——为"展示"付"存储"的代价,不划算。
  • 每个 VO 手动加注解:就是上面那个漏网的方案。人一多、路径一杂,必然会漏,漏一次就是一个线上 bug。

一句话总结:19 位雪花 id 超出 JS 安全整数,丢精度是必然;最省事的收口点在后端序列化——全局把所有 Long 转成 String,再把"Long 会变字符串"这件事同步给前端。

你们项目的雪花 id 是怎么处理的?是字段级注解,还是也做了全局配置?评论区聊聊。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 6:05:24

PHP科技企业模板部署与安全加固实战指南

简介&#xff1a;这是一套开箱即用的PHP科技企业官网模板&#xff0c;专为IT、软件、科技产品及工作室类网站快速建站设计&#xff0c;适合前端初学者与中小型技术团队进行二次开发与部署。模板采用淡绿色主色调&#xff0c;视觉高端大气&#xff0c;内置PHP后台管理系统&#…

作者头像 李华
网站建设 2026/10/10 6:05:12

Beyond Compare高效使用指南:从文本比较到文件夹同步与三路合并

Beyond Compare到底怎么用才叫“高效”——我把它翻来覆去用了一遍之后先说个我自己的经历。有次处理一个发布包&#xff0c;上一个版本和这个版本之间文件改了几十个&#xff0c;靠肉眼去翻目录、逐个看修改时间&#xff0c;折腾一晚上&#xff0c;最后还是漏掉了两个配置文件…

作者头像 李华
网站建设 2026/10/10 6:05:06

使用 NVIDIA NemoClaw 构建内存驱动型智能体

2026年 9月 4日 作者&#xff1a;Xuan Wu, Alexia Huang, Yijie Lai, Joe Liu 和 Xiaowei Li 企业工作涵盖随时间变化的AI 智能体必须先对其进行重建&#xff0c;然后再进行贡献。 为了向智能体提供这种必要的环境&#xff0c;我们的团队使用 NVIDIA NemoClaw 构建了 内存驱…

作者头像 李华
网站建设 2026/10/10 6:05:05

看漫画学Python:零基础入门教程全解析与安装避坑指南

前阵子有个朋友跟我说&#xff0c;他已经第三次从“Python编程入门”这类书的第三章放弃了。每次拿起书&#xff0c;都能坚持到变量和数据类型那一节&#xff0c;然后就感觉大脑像被塞了一团棉花。我问他书长什么样&#xff0c;他给我拍了一张照片——厚厚一本&#xff0c;满页…

作者头像 李华