SpringBoot雪花算法主键ID传到前端后精度丢失问题的解决
作者:IT利刃出鞘 发布时间:2022-07-18 02:30:47
简介
本文用示例介绍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字段由 Long 类型改成 String 类型。
前端用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= ToStringSerializer.class)。
实例
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
来源:https://knife.blog.csdn.net/article/details/124884023


猜你喜欢
- 前言Elasticsearch是一个非常流行的搜索引擎,已经成为了许多企业的首选解决方案。然而,我们要想成为一个优秀的程序员,就必须掌握各种
- 先上效果图这个效果来自于三星S5的充电界面,当然有些细节差别,主要看思路.本文目的是技术交流,不要将效果直接运用于商业产品和项目.电池背景因
- 一、前期准备我们要在IDEA上创建一个新的项目,创建好一个项目后,我们需要创建4个包,分别是英雄包,装备包,铭文包,野怪包,皮肤包然后我们就
- 1:普通实现99乘法表太简单,是个程序员都会,实现如下:package test.ms;public class Test99 {publi
- 1.SQLite的特点SQLite是一个轻量级数据库,它设计目标是嵌入式的,而且占用资源非常低SQLite没有服务器进程,通过文件保存数据,
- 项目前端由于采用Extjs4,列表分页需要返回三个参数:totalCount(记录总数)、start(开始位置)、limit(每页条数)。由
- 对于跨域,相信同学们都有所了解。前端的跨域的若干种方式,大家也都知道,什么 JSONP,iframe+domain 等等。但是我们今天的主题
- 最近因为赶项目进度,因此将本来要用原生控件实现的界面,自己做了H5并嵌入webview中。发现点击H5中 input type="
- 最近有个需求是这样的,人民币的符号“¥”因为安卓手机系统的不一致导致符号不是完全一样,所以用美工的给的图片代替,考虑到用的地
- 使用版本:spring-boot: 2.1.6.RELEASEsping: 5.1.8.RELEASEjava: openjdk 11.0.
- 一、判断一个字符串str不为空的方法有:1、str == null;2、"".equals(str);3、str.len
- 本文实例为大家分享了Java实现斗地主游戏的具体代码,供大家参考,具体内容如下原理图:斗地主过程: 1、组合牌&nb
- 1. 新建项目引入web和security包完整的pom.xml文件如下<?xml version="1.0" e
- 最大堆最大堆的特点是父元素比子元素大,并且是一棵完全二叉树。data[1]开始存,data[0]空着不用。也可以把data[0]当成size
- Java 判断字符串是否为IP地址,供大家参考,具体内容如下1、代码主要就是这么几个条件非空长度符合 0.0.0.0 - 255.255.2
- 前言使用Java8的新特性Stream流式处理,可以提高对于集合的一些操作效率,再配合lambda表达式,可以极致的简化代码,尤其还有并行流
- 本文实例讲述了Java编程调用微信分享功能。分享给大家供大家参考,具体如下:这篇文章介绍如何使用java开发微信分享功能,因为工作,已经开发
- AbstractHandlerMapping实现HandlerMapping接口定的getHandler1. 提供getHandlerInt
- 前面照着android系统的裁剪图片的功能自己写了一个相似的工具。功能是大体上实现了,但留下了一个调用的问题:如何从我的程序调用这个裁剪工具
- 入门Springboot项目创建在IDEA中创建即可。注意点:1、所有文件都需要放在 :Application文件的同级或下级目录中2、ap