1. 程式人生 > >完美解決方案-雪花演算法ID到前端之後精度丟失問題

完美解決方案-雪花演算法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; } } ~~~ 小夥伴放下電話, 再次投來敬佩眼光。“走吧,一起下班!”我和小夥伴說,小夥伴一路上一直問我你是怎麼學習的?我冠冕堂皇的說了一些多想多學多問之類的話。 其實我心裡在想:我是一個懶人,但我不能說。能躺著絕不坐著,能自動絕不手動,能打車絕不自己開車。第一次就把事情做對,才是省時省力做好的方法!這麼多年的“懶”,決定了我需要去思考更多的“捷徑”,思考“捷徑”的過程是我不斷進階的訣竅! **勤奮的人是社會的生產力,而懶人是社會的創造力!** ## 歡迎關注我的部落格,裡面有很多精品合集 * 本文轉載註明出處(必須帶連線,不能只轉文字):[字母哥部落格](http://www.zimug.com)。 **覺得對您有幫助的話,幫我點贊、分享!您的支援是我不竭的創作動力!** 。另外,筆者最近一段時間輸出瞭如下的精品內容,期待您的關注。 * [《手摸手教你學Spring Boot2.0》]( https://www.kancloud.cn/hanxt/springboot2/content ) * [《Spring Security-JWT-OAuth2一本通》](https://www.kancloud.cn/hanxt/springsecurity/content) * [《實戰前後端分離RBAC許可權管理系統》](https://www.kancloud.cn/hanxt/vue-spring/content) * [《實戰SpringCloud微服務從青銅到王者》](https://www.kancloud.cn/hanxt/springcloud/content) * [《VUE深入淺出系列》](https://www.kancloud.cn/hanxt/vuejs2/content)