雪花演算法ID在前端丟失精度解決方案

勝金發表於2021-08-14

  首先說一下背景,目前筆者的工作是物聯網方面的,裝置有對應的智慧運營平臺,平臺開發中建表的主鍵用的是Mybatis plus預設的雪花演算法來生成的,也就是分散式系統比較常用的雪花ID,技術棧就是常用的Spring boot+Spring could Alibaba,json工具用的是FastJson。

  在開發的過程中遇到了一個問題:前端接收到的資料在回傳給後端的時候ID總是不對,仔細排查發現,前端接收到的資料的ID末尾兩到三位數字都變成了0。雪花ID的長度是19位數字,系統在bean中的ID用的是Long型別,資料庫建表用的是bigint,接收雪花ID自然沒有問題,但是前端的number型別只能接收16位數字,準確的說是:2的53次方減1,即為9007199254740991,所以回傳的ID不對是數字精度丟失的原因造成的。

  知道了原因,解決方案也很簡單,後端傳給前端時把ID轉換位字串型別,前端接收字串就不會丟失精度了,前端把ID回傳給後端的時候,Spring的反序列化會自動為我們轉成Long型別,這麼一來就解決問題了。針對這一思路,樓主想到了兩種解決方案。

1、@JsonSerialize註解

  JsonSerialize註解可以幫我們實現欄位值的序列化和反序列話,@JsonSerialize(using = ToStringSerializer.class),程式碼如下:

public class Device{

    @ApiModelProperty(value = "物聯終端id")
    @TableId(type = IdType.ASSIGN_ID)
    @JsonSerialize(using = ToStringSerializer.class)
    private Long deviceId;

    ...  
}

  在需要解決數字過長的欄位上新增sonSerialize註解就可以完美解決這一問題,但是開發的時候一定要注意,萬一漏掉很容易踩坑,所以在員工培訓的時候一定要有所交待。

2、過濾器

  過濾器是一種一勞永逸的方法,筆者的專案引入的是fastjson依賴,fastjson可以通過SerializeFilters定製序列化,非常方便,先上程式碼:

package com.johanChan.app.config;

import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import java.util.ArrayList;
import java.util.List;

/**
 * @author JohanChan
 * @ProjectName Demo
 * @Description 與前端互動時對實體類中Long型別的ID欄位序列號
 * @time 2021/6/23 11:30
 */
@Configuration
public class CustomFastJsonHttpMessageConverter {
    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();

        List<SerializerFeature> list = new ArrayList<>();
        list.add(SerializerFeature.PrettyFormat);
        list.add(SerializerFeature.WriteMapNullValue);
        list.add(SerializerFeature.WriteNullStringAsEmpty);
        list.add(SerializerFeature.WriteNullListAsEmpty);
        list.add(SerializerFeature.QuoteFieldNames);
        list.add(SerializerFeature.WriteDateUseDateFormat);
        list.add(SerializerFeature.DisableCircularReferenceDetect);
        list.add(SerializerFeature.WriteBigDecimalAsPlain);

        fastJsonConfig.setSerializerFeatures(list.toArray(new SerializerFeature[list.size()]));

        fastConverter.setFastJsonConfig(fastJsonConfig);
        HttpMessageConverter<?> converter = fastConverter;
        fastJsonConfig.setSerializeFilters(new ValueFilter() {
            @Override
            public Object process(Object object, String name, Object value) {
                /*if ((StringUtils.endsWith(name, "Id") || StringUtils.equals(name,"id")) && value != null
                        && value.getClass() == Long.class) {*/
                if (value != null && value.getClass() == Long.class ) {
                    Long v = (Long) value;
                    if (v.toString().length() > 15) {
                        return String.valueOf(value);
                    }
                }
                return value;
            }
        });
        return new HttpMessageConverters(converter);
    }
}

  在ValueFilter中自定義規則,long型別的變數值如果超過15位數則轉化成字串,前端的number型別可以接收16位數字,為什麼不用16位判斷呢?前面已經說過,前端雖然可以接收16位的數字,但最大是9007199254740991,如果用16位做判斷,就會有漏網之魚了。這種方法省心省力,基本上開發人員不需要再注意這種數字過大的問題,但是使用的時候也要有所考量,根據實際業務考慮系統中有沒有其他需求需要用較長的數字,統一用過濾器會不會受到影響。

相關文章