乐趣区

关于spring:完美解决方案雪花算法ID到前端之后精度丢失问题

最近公司的一个项目组要把以前的单体利用进行为服务拆分,表的 ID 主键应用 Mybatis plus 默认 的雪花算法来生成。

快下班的时候,小伙伴跑过来找我,:“快给我看看这问题,卡这卡了小半天了!”。连拉带拽,连哄带骗的把我拉到他的电脑前面。这位小伙伴在我看来技术不算是大牛,但教训也很丰盛了。他都卡了半天的问题,应该不是小问题,如果我一时半会搞不定,真的是耽搁我上班了,所以我很不愿意的在他的地位坐了下来。

一、景象是这样的

上面我把异样的景象给大家形容一下,小伙伴建了一张表,表的主键是 id BigINT,用来存储雪花算法生成的 ID,嗯,这个没有问题!

CREATE TABLE user
(id BIGINT(20) NOT NULL COMMENT '主键 ID',
        #其余字段省略
);

应用 Long 类型对应数据库 ID 数据。嗯,也没有问题,雪花算法生成的就是一串数字,Long 类型属于标准答案!

@Data
public class User {
    private Long id;
// 其余成员变量省略 

在后端下断点。看到数据响应以 JSON 响应给前端,失常

{
id:1297873308628307970,
// 其余属性省略
}

最初,这条数据返回给前端,前端接管到之后,批改这条数据,后端再次接管回来。奇怪的问题呈现了: 后端从新接管回来的 id 变成了:12978733086283000000,不再是 1297873308628307970

二、剖析问题

我的第一感觉是,开发小伙伴把数据给搞混了,张冠李戴了,把 XXX 的对象 ID 放到了 YYY 对象的 ID 上。所以,就依照代码从前端到后端、从后端到前端调试跟踪了一遍。

从代码的逻辑角度上没有任何问题。这时,我有点焦躁了,真的是耽搁我上班了!但动工没有回头箭,既然坐下来了就得帮他解决,不然当前这队伍怎么带?想到这我又静下心来,开始思考。

1297873308628300000 ---> 1297873308628307970

这两个数长得还挺像的,仿佛是被四舍五入了。此时脑袋外面冒出一个想法,是精度失落了么?哪里能导致精度失落?

  • 服务端都是 Long 类型的 id,不可能失落
  • 前端是什么类型,JSON 字符串转 js 对象,接管 Long 类型的是 number

上网查了一下 Number 精度是 16 位(雪花 ID 是 19 位的),So:JS 的 Number 数据类型导致的精度失落。问题是找到了!
小伙伴投来钦佩的眼光,5 分钟就把这问题发现了。可是发现了有什么用?得解决问题啊!

三、解决问题

开发小伙伴说:那我把所有的数据库表设计,id 字段由 Long 类型改成 String 类型吧。我问他你有多少张表?他说 100 多张吧。

  • 100 多张表还有 100 多个实体类须要改
  • 还有各种应用到实体类的 Service 层要改
  • Service 等改完 Controller 层要改
  • 要害的是 String 和 Long 都是罕用类型,他还不敢批量替换

小伙伴拿起电话打算订餐,说今晚的加班是无奈防止了。我想了想说:你最好别改,String 做 ID 查问性能会降落 ,我再想想!后端 A 到前端 B 呈现精度失落,要么改前端,要么改后端,要么……。“哎哎,你等等先别订餐,后端 A 到前端 B 你用的什么做的序列化?”小伙伴通知我说应用的是 Jackson,这就好办了,Jackson 我相熟啊!


解决思路:后端的 ID(Long) ==> Jackson(Long 转 String) ==> 前端应用 String 类型的 ID, 前端应用 js string 精度就不会失落了。 那前端再把 String 类型的 19 位数字传回服务端的时候,能够用 Long 接管么?当然能够,这是 Spring 反序列化参数接管默认反对的行为。


最终计划就是: 前端用 String 类型的雪花 ID 放弃精度,后端及数据库持续应用 Long(BigINT) 类型不影响数据库查问执行效率。

剩下的问题就是:在 Spring Boot 利用中,应用 Jackson 进行 JSON 序列化的时候怎么将 Long 类型 ID 转成 String 响应给前端。计划如下:

@Configuration
public class JacksonConfig {

  @Bean
  @Primary
  @ConditionalOnMissingBean(ObjectMapper.class)
  public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder)
  {ObjectMapper objectMapper = builder.createXmlMapper(false).build();

    // 全局配置序列化返回 JSON 解决
    SimpleModule simpleModule = new SimpleModule();
    //JSON Long ==> String
    simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
    objectMapper.registerModule(simpleModule);
    return objectMapper;
  }

}

小伙伴放下电话,再次投来钦佩眼光。“走吧,一起上班!”我和小伙伴说,小伙伴一路上始终问我你是怎么学习的?我冠冕堂皇的说了一些多想多学多问之类的话。
其实我心里在想:我是一个懒人,但我不能说。能躺着绝不坐着,能主动绝不手动,能打车绝不本人开车。第一次就把事件做对,才是省时省力做好的办法!这么多年的“懒”,决定了我须要去思考更多的“捷径”,思考“捷径”的过程是我一直进阶的窍门!
怠惰的人是社会的生产力,而懒人是社会的创造力!

欢送关注我的博客,外面有很多精品合集

  • 本文转载注明出处(必须带连贯,不能只转文字):字母哥博客。

感觉对您有帮忙的话,帮我点赞、分享!您的反对是我不竭的创作能源!。另外,笔者最近一段时间输入了如下的精品内容,期待您的关注。

  • 《手摸手教你学 Spring Boot2.0》
  • 《Spring Security-JWT-OAuth2 一本通》
  • 《实战前后端拆散 RBAC 权限管理系统》
  • 《实战 SpringCloud 微服务从青铜到王者》
  • 《VUE 深入浅出系列》
退出移动版