为什么会出现前端精度丢失?
当我们后端 (Java 为例) 使用 Long/long 类型输出数据到前端,且保存的数据是超过 16 位的数字,那么前端就会出现精度丢失现象。
通俗易懂的说就是前端 JavaScript 的数字类型是 Number,其支持的最大范围就是 16 位数字;而如果我们后端使用雪花算法生成 ID 的话则是 19 位,前端的精度没有后端的大,后端传递到前端时就会出现精度丢失现象。
比如:后端传递的 ID 是 xxxxxxxxxxxxxxx5678
,而前端接收到的却是 xxxxxxxxxxxxxxx5700
,这就是所谓的精度丢失现象。
解决方案
对于精度问题造成的数据丢失现象,我们的最佳思路就是 将后端传递的 Long/long 类型的数据以字符串的形式进行传递,这样就能够有效地避免精度丢失问题。
主要有两种解决方案:局部解决、自定义序列化器全局解决。
以 Spring Boot 项目 + MyBatis-Plus 持久层框架为例。
MyBatis-Plus
MyBatis-Plus 官方文档
局部解决
在对应的主键 ID 字段上添加 @JsonSerialize(using = ToStringSerializer.class)
注解即可,这样就能把 Long 类型的 ID 转化为 String。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@TableName(value = "user") @Data public class User implements Serializable {
@TableId(type = IdType.ASSIGN_ID) @JsonSerialize(using = ToStringSerializer.class) private Long id; }
|
不过该方案存在的问题就是:如果项目中有成百上千个类都需要传递 ID 字段,那我们就需要一个一个的去添加注解,非常的不方便,因此大型项目并不推荐此种方式。
全局解决
全局解决又分为两种方式。
⚠️ 注意:这样做之后,前端接收到的所有 Long/long 类型字段都是字符串,前端需要按照字符串处理,不能直接当作数字进行运算。
第一种方式:使用 ObjectMapper
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 28
|
@JsonComponent public class JsonConfig {
@Bean public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { ObjectMapper objectMapper = builder.createXmlMapper(false).build();
SimpleModule module = new SimpleModule();
module.addSerializer(Long.class, ToStringSerializer.instance);
module.addSerializer(Long.TYPE, ToStringSerializer.instance);
objectMapper.registerModule(module);
return objectMapper; } }
|
第二种方式:使用 Jackson2ObjectMapperBuilder。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Configuration public class JacksonConfiguration {
@Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> { builder.serializerByType(Long.class, ToStringSerializer.instance) .serializerByType(Long.TYPE, ToStringSerializer.instance); }; } }
|
总结
方式二使用 Jackson2ObjectMapperBuilderCustomizer
提供的链式 API,较为简单、高层抽象,编码更简洁,适用于常规配置,且与 Spring Boot 高度集成。
方式一是手动创建 ObjectMapper
,通过 registerModule()
等 API 或直接设置属性,更灵活也更底层。适用于复杂定制 (如多个自定义序列化器、混合配置等),但是编码也相对要复杂一些。
拓展
我们也可以通过上述的方式自定义统一的时间类型进行返回。
通常情况下我们是这样格式化时间的:
1 2 3 4 5 6 7 8 9 10 11
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date updateTime;
|
这往往需要我们对每个进行传递的时间执行格式化操作,而现在我们可以全局配置时间的格式了:
1 2 3 4 5 6 7 8 9
| @Bean public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { return builder -> { builder.serializerByType(Long.class, ToStringSerializer.instance) .serializerByType(Long.TYPE, ToStringSerializer.instance) .timeZone(TimeZone.getTimeZone("Asia/Shanghai")) .dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); }; }
|