Jackson自定义序列化注解解决JS接收数值精度丢失问题

1 背景

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

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

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

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

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

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

2 @ JsonSerialize注解

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

2.1 基本介绍

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

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

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

2.2 使用

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

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

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

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

2.3 自定义序列化器

对于雪花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);
    }
}

具体使用:

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

3 全局配置

每个实体类的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));
    }
}

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

猜你喜欢

转载自blog.csdn.net/huangjhai/article/details/128430840