一. 问题引出
前段时间上线遇到一个问题,TiDB表的主键使用的是bigint + auto_random,值类似于【2017612633061982209】,查询接口正常返回后,前端读取到的却是【2017612633061982000】,精度丢失了?
二. 本质原因
1. js数字的精度是有限的,Java的Long类型的数字超出了JavaScript的处理范围。
2. 内部只有一种数字类型Number,双精度64位格式存储,即使整数也是如此。
3. 最大的数值应该是2的53次方-1,十进制是【9007199254740991】,16位。
三. 怎么解决?
前端解决
在浏览器环境中,使用json-bigint库替换默认的JSON库,保证精度不丢失。详细细节留给前端解决了,这里不赘述。
后端解决
- 方案1. 后端的ID(Long)转成(String), 前端使用String类型的ID。
/**
* WebMvc配置
*/
@Configuration
@Slf4j
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
/**
*添加消息转化类
* @param list
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> list) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = jsonConverter.getObjectMapper();
//序列换成json时,将所有的long变成string
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
list.add(jsonConverter);
}
}
- 方案2. 增加配置(全局的)
spring:
jackson:
generator:
write_numbers_as_strings: true
优点:方便。
缺点:所有的数字都被转成字符串输出了,包括按照timestamp格式输出的时间,影响范围大。
- 方案3. 使用注解(字段级别)
@JsonSerialize(using=ToStringSerializer.class)
private Long bankcardHash;
优点:可在字段级别调整。
缺点:涉及字段多的话,就特别繁琐,改动比较多。
- 方案4:覆写类方法
@Bean("jackson2ObjectMapperBuilderCustomizer")
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
Jackson2ObjectMapperBuilderCustomizer customizer = new Jackson2ObjectMapperBuilderCustomizer() {
@Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance)
.serializerByType(Long.TYPE, ToStringSerializer.instance);
}
};
return customizer;
}
哪个解决方案好点?
抛出问题:总而言之,上面后端的方案可以全局解决Long转string的问题,但有些时候,前端是需要number类型的数据的,这个时候就只能单独再处理了。
????哪个方案?
四.结论
建议前端引入插件【json-bigint】解决Long精度损失的问题,永久解决问题。
五.参考
https://blog.youkuaiyun.com/xhdxhdxhd/article/details/109164469