• Long类型字段前端获取精度丢失的解决方案


    背景

    分布式项目中广泛使用雪花算法生成ID作为数据库表的主键,Long类型的雪花ID有19位,而前端接收Long类型用的是number类型,但是number类型的精度只有16位。这就导致雪花ID传到前端会出现精度丢失的问题。如下:

    数据库的值:1461642036950462475 
    前端接收的值:1461642036950462500
    
    • 1
    • 2

    (拓展)
    使用雪花算法ID的优点:

    1. 算法简单,计算效率高。高并发分布式环境下生成递增不重复 id,每秒可生成百万个不重复 id。
    2. 基于时间戳,以及同一时间戳下序列号自增,基本保证 id 有序递增,有利于数据库存储。
    3. 不依赖第三方库和中间件(中间件方式生成ID的方式,依赖数据库或Redis缓存实现全局ID的递增)

    缺点:
    1.依赖服务器时间,服务器时钟回拨时可能会生成重复 id。算法中可通过记录最后一个生成 id 时的时间戳来解决,每次生成 id 之前比较当前服务器时钟是否被回拨,避免生成重复 id。

    UUID方式明显的缺点是因为其无序的规则,导致数据库插入效率低,自增方式明显的缺点是不适合分表分库场景,分布式存储的场景会出现主键冲突。而且安全性低,因为ID增长有规律的,容易被非法获取数据。所以综合考虑下,使用雪花算法生成ID的方式更为推荐。

    @ JsonSerialize注解

    如果要将id字段从Long类型改成String类型,涉及到表,实体类等等代码的改动,影响面比较大所以这个做法是不提倡的。

    基本介绍

    Jackson是Spring框架默认的序列化框架,使用Jackson的@JsonSerialize注解可以完美解决这个问题。具体做法:后端的ID(Long) ==> Jackson(Long转String) ==> 前端使用String类型接收,并且Jackson反序列化默认支持的前端穿回来的String类型的19位数字字段用Long类型接收。

    使用方面,将@JsonSerialize注解定义在字段上即可。Jackson提供了许多Json序列化器。另外我们也可以自定义序列化器。

    需要注意不要再引入Gson,在我使用过程中发现同时引入Gson包的话,会导致@JsonSerialize注解失效。

    使用

    在项目中都是将注解标注在对应字段上,在Json序列化的时候把Long自动转为String。

        @JsonSerialize(using = ToStringSerializer.class)
        private Long id;
    
    • 1
    • 2

    需要注意的是被转换的字段必须是包装类类型,否则会转换失败。

    @JsonSerialize(using = ToStringSerializer.class)
    private Long parentId;    //转化成功
    @JsonSerialize(using = ToStringSerializer.class)
    private long parentId;    //转化失败
    
    • 1
    • 2
    • 3
    • 4

    自定义序列化器

    对于雪花ID集合,我们继续使用 ToStringSerializer,前端解析接收到到之后,还需要特殊处理。因此需要针对List类型的雪花ID集合自定义序列化器,实现将Long转换成String类型传递给前端。

    自定义序列化器ListLongToStringArrayJsonSerializer,继承JsonSerializer

    public class ListLongToStringArrayJsonSerializer extends JsonSerializer<List<Long>> {
    
        @Override
        public void serialize(List<Long> values, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            String[] newValues =
                    ObjectUtil.defaultIfNull(values, Collections.emptyList()).stream()
                            .map(String::valueOf)
                            .toArray(String[]::new);
            gen.writeArray(newValues, 0, newValues.length);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    具体使用:

        @JsonSerialize(using = ListLongToStringArrayJsonSerializer.class)
        private List<Long> idList;
    
    • 1
    • 2

    全局配置

    每个实体类的id字段都需要加@JsonSerialize注解有些繁琐,我们可以通过先修改Jackson转换器,实现全局统一处理Long类型字段。如下所示:

    @EnableWebMvc
    @Configuration
    public class MvcConfig implements WebMvcConfigurer {
        /**
         * 重写Jackson转换器
         * Long类型转String类型
         *
         * 解决前端Long类型精度丢失问题(js解析只能解析到16位)
         *
         * @param converters
         */
        @Override
        public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
            MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
                    new MappingJackson2HttpMessageConverter();
    
            ObjectMapper objectMapper = new ObjectMapper();
            SimpleModule simpleModule = new SimpleModule();
            simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
            simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
            simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(simpleModule);
            jackson2HttpMessageConverter.setObjectMapper(objectMapper);
            converters.add(jackson2HttpMessageConverter);
            converters.add(new StringHttpMessageConverter(StandardCharsets.UTF_8));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    但是因为这里的全局配置是将所有Long类型的字段都转换成String传给前端,可能导致前端界面存在有类型不兼容的情况,所以这里的全局配置需要谨慎使用。

    更多: http://www.lzhpo.com/article/106

  • 相关阅读:
    kinetic: 编译navgation包遇到的问题
    Confluence 可以用哪些开源知识库替换?盘点主流的11款
    【目标检测】雷达目标CFAR检测算法
    docker从零部署jenkins保姆级教程(上)
    【RF预测】基于matlab随机森林算法数据回归预测【含Matlab源码 2047期】
    idea快捷键
    数据结构学习系列之顺序表的查找与排序以及去重
    SOLIDWORKS软件提供了哪些特征造型方法?硕迪科技
    Android平台下奔溃Crash和无响应ANR日志抓取分析
    【2023高教社杯】D题 圈养湖羊的空间利用率 问题分析、数学模型及MATLAB代码
  • 原文地址:https://blog.csdn.net/huangjhai/article/details/125414032