雪花 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 和数据库里的不是一个数:

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

用 node 复现更直白:

js 复制代码
> String(Number("1934567890123456789"))
'1934567890123456800'
> Number("1934567890123456789") === 1934567890123456789
false

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

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

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

js 复制代码
Number.MAX_SAFE_INTEGER   // 9007199254740991   ← 16 位

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

js 复制代码
Number("9007199254740993")  // 9007199254740992   ← 只差 1,也存不下

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

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

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

java 复制代码
@TableId(type = IdType.ASSIGN_ID)
@JsonSerialize(using = ToStringSerializer.class)
private Long id;

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

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

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

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

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

java 复制代码
@AutoConfiguration
public class JacksonConfig {

    @Bean
    public JsonMapperBuilderCustomizer longToStringCustomizer() {
        return builder -> builder.addModule(new JacksonModule() {

            @Override
            public String getModuleName() {
                return "long-to-string";
            }

            @Override
            public Version version() {
                return Version.unknownVersion();
            }

            @Override
            public void setupModule(SetupContext context) {
                context.addSerializers(new Serializers() {
                    @Override
                    public ValueSerializer<?> findSerializer(SerializationConfig config, JavaType type,
                                                             BeanDescription.Supplier beanDesc,
                                                             JsonFormat.Value format) {
                        if (type.isTypeOrSubTypeOf(Long.class)) {
                            return ToStringSerializer.instance;
                        }
                        return null;   // 其余类型保持默认序列化
                    }
                });
            }
        });
    }
}

两个容易踩的点:

  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 是怎么处理的?是字段级注解,还是也做了全局配置?评论区聊聊。

相关推荐
SimonKing1 小时前
一只离线鼠鼠,干翻了一堆在线格式转换网站
java·后端·程序员
武子康1 小时前
两台 A6000,LingBot 应该先验哪条路径?
人工智能·后端·agent
Thneonl1 小时前
值班第一年:最先要学的不是排障,是叫人
后端·程序员
codigger1 小时前
Redis 正式接入 AI:当"最懂速度的数据库"开始解决"记忆问题"
redis·分布式·后端·ai·向量检索
沐言人生1 小时前
82.4k 星!把十几万行代码变成知识图谱,新人终于不用硬啃了
前端·后端·github
Thneonl1 小时前
让 LLM 值第一班岗:只读的活放手交,变更的活一条别给
后端·架构
溪语流沙1 小时前
【Web全栈进阶】JWT无状态认证:签发、校验、刷新
前端·git·python·github
在繁华处1 小时前
2.1 上下文:决定 Agent 能力上限的关键
前端·人工智能·microsoft
ndsc_d1 小时前
2026年有哪些好用的AI UI设计工具?主流工具功能和适用场景对比
前端·人工智能·ui·ai·设计师·ai ui·ai ui工具