<
>

SpringBoot雪花算法主键ID传到前端后精度丢失问题的解决

2022-08-09 16:44:46 来源:易采站长站 作者:

目录
简介问题描述项目场景问题描述问题复现解决方案法1:全局处理法2:局部处理

简介

本文用示例介绍SpringBoot如何解决雪花算法主键ID传到前端后精度丢失问题。

问题描述

Java后端Long类型的范围

    -2^63~2^63,即:-9223372036854775808~9223372036854775807,它是19位的。这个数字可以通过方法获得:Long.MAX_VALUE、Long_MIN_VALUE。

    前端JS的数字类型的范围

      -2^53~2^53,即:-9007199254740991~9007199254740991,它是16位的。这个数字可以通过方法获得:Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER。

      结论

      可见,Java后端的Long宽度大于前端的。雪花算法一般会生成18位或者19位宽度的数字,那么这时就会出问题。

      项目场景

      1.表结构

      主键类型是BIGINT,存储雪花算法生成的ID。

      CREATE TABLE `user` (
        `id` BIGINT(32) NOT NULL COMMENT '用户id',
      	...
        PRIMARY KEY (`id`) USING BTREE
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

      2.Entity

      用Long 类型对应数据库ID的BIGINT类型。

      这里使用 MybatisPlus 的雪花算法自动生成19位长度的纯数字作为主键ID。(当然也可以手动用雪花算法生成ID)

      import lombok.Data;
       
      @Data
      public class User {
          @TableId(type = IdType.ASSIGN_ID)
          private Long id;
      	
          //其他成员
      }

      3.响应给前端

      以JSON数据响应给前端正常

      {
        "id": 1352166380631257089,
         ...
      }

      问题描述

      实例

      Controller

      package com.knife.controller;
       
      import com.knife.entity.UserVO;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
       
      @RestController
      @RequestMapping("user")
      public class UserController {
       
          @GetMapping("find")
          public UserVO find(Long id) {
              UserVO userVO = new UserVO();
              userVO.setId(id);
              userVO.setUsername("Tony");
       
              return userVO;
          }
      }

      Entity

      package com.knife.entity;
       
      import lombok.Data;
       
      @Data
      public class UserVO {
          private Long id;
       
          private String username;
      }

      测试

      访问:http://localhost:8080/user/find?id=1352213368413982722

      结果

      问题复现

      从上边可以看到,并没有问题。

      为什么没有出问题?

      前端传入后端:SpingMVC会自动将String类型的ID转为Long类型,不会出问题后端响应给前端:是JSON格式,与JS没有关系,不会出问题

      什么时候会出问题?

      前端接收到JSON之后,将其序列化为JS对象,然后进行其他操作。在JSON转JS对象时就会出问题,如下:

      可以看到,原来id为1352213368413982722,序列化为JS对象后变成了 1352213368413982700

      代码为:

      const json = '{"id": 1352213368413982722, "name": "Tony"}';
      const obj = JSON.parse(json);
       
      console.log(obj.id);
      console.log(obj.name);

      解决方案

      有如下两种方案

        将数据库表设计的id字段由>前端用String类型来保存ID保持精度,后端及数据库继续使用Long(BigINT)类型

        方案1使用String 类型做数据库ID,查询性能会大幅度下降。所以应该采用方案2。本文介绍方案2。

        法1:全局处理

        简介

        自定义ObjectMapper。

        方案1:ToStringSerializer(推荐)

        package com.knife.config;
         
        import com.fasterxml.jackson.databind.ObjectMapper;
        import com.fasterxml.jackson.databind.module.SimpleModule;
        import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
         
        @Configuration
        public class JacksonConfig {
         
            @Bean
            public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
                ObjectMapper objectMapper = builder.createXmlMapper(false).build();
         
                // 全局配置序列化返回 JSON 处理
                SimpleModule simpleModule = new SimpleModule();
                // 将使用String来序列化Long类型
                simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
                simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
                objectMapper.registerModule(simpleModule);
                return objectMapper;
            }
        }

        测试 

        访问:http://localhost:8080/user/find?id=1352213368413982722

        方案2:自定义序列化器(不推荐)

        序列化器

        package com.knife.config;
         
        import com.fasterxml.jackson.core.JsonGenerator;
        import com.fasterxml.jackson.databind.SerializerProvider;
        import com.fasterxml.jackson.databind.annotation.JacksonStdImpl;
        import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
         
        import java.io.IOException;
         
        /**
         * 超出 JS 最大最小值 处理
         */
        @JacksonStdImpl
        public class BigNumberSerializer extends NumberSerializer {
         
        	/**
        	 * 根据 JS Number.MAX_SAFE_INTEGER 与 Number.MIN_SAFE_INTEGER 得来
        	 */
        	private static final long MAX_SAFE_INTEGER = 9007199254740991L;
        	private static final long MIN_SAFE_INTEGER = -9007199254740991L;
         
        	/**
        	 * 提供实例
        	 */
        	public static final BigNumberSerializer instance = new BigNumberSerializer(Number.class);
         
        	public BigNumberSerializer(Class<? extends Number> rawType) {
        		super(rawType);
        	}
         
        	@Override
        	public void serialize(Number value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        		// 超出范围 序列化位字符串
        		if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {
        			super.serialize(value, gen, provider);
        		} else {
        			gen.writeString(value.toString());
        		}
        	}
        }

        ObjectMapper配置

        package com.knife.config;
         
        import com.fasterxml.jackson.databind.ObjectMapper;
        import com.fasterxml.jackson.databind.module.SimpleModule;
        import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
         
        @Configuration
        public class JacksonConfig {
         
            @Bean
            public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
                ObjectMapper objectMapper = builder.createXmlMapper(false).build();
         
                // 全局配置序列化返回 JSON 处理
                SimpleModule simpleModule = new SimpleModule();
                // 将使用自定义序列化器来序列化Long类型
                simpleModule.addSerializer(Long.class, BigNumberSerializer.instance);
                simpleModule.addSerializer(Long.TYPE, BigNumberSerializer.instance);
                objectMapper.registerModule(simpleModule);
                return objectMapper;
            }
        }

        测试 

        访问:http://localhost:8080/user/find?id=1352213368413982722

        法2:局部处理

        说明

        在字段上加:@JsonSerialize(using=>

        实例

        package com.knife.entity;
         
        import com.fasterxml.jackson.databind.annotation.JsonSerialize;
        import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
        import lombok.Data;
         
        @Data
        public class UserVO {
            @JsonSerialize(using= ToStringSerializer.class)
            private Long id;
         
            private String username;
        }

        测试 

        访问:http://localhost:8080/user/find?id=1352213368413982722

         到此这篇关于SpringBoot雪花算法主键ID传到前端后精度丢失问题的解决的文章就介绍到这了,更多相关SpringBoot雪花算法主键ID精度丢失内容请搜索易采站长站以前的文章或继续浏览下面的相关文章希望大家以后多多支持易采站长站!

暂时禁止评论

微信扫一扫

易采站长站微信账号