后端的Long型参数,让阿粉踩了个大坑

开发 后端
最近几天一直在改造工程,采用雪花算法生成主键ID,突然踩到一个天坑,前端 JavaScript 在取 Long 型参数时,参数值有点不太对!

最近几天一直在改造工程,采用雪花算法生成主键ID,突然踩到一个天坑,前端 JavaScript 在取 Long 型参数时,参数值有点不太对!

[[340323]]

一、问题描述

最近在改造内部管理系统的时候, 发现了一个巨坑,就是前端 JS 在获取后端 Long 型参数时,出现精度丢失!

起初,用 postman 模拟接口请求,都很正常,但是用浏览器请求的时候,就出现问题了!

问题复现:

  1. @RequestMapping("/queryUser") 
  2. public List<User> queryUser(){ 
  3.     List<User> resultList = new ArrayList<>(); 
  4.     User user = new User(); 
  5.     //赋予一个long型用户ID 
  6.     user.setId(123456789012345678L); 
  7.     resultList.add(user); 
  8.     return resultList; 

打开浏览器,请求接口,结果如下!

用 postman 模拟接口请求,结果如下!

刚开始的时候,还真没发现这个坑,结果当进行测试的时候,才发现前端传给后端的ID,与数据库中存的ID不一致,才发现 JavaScript 还有这个天坑!

由于 JavaScript 中 Number 类型的自身原因,并不能完全表示 Long 型的数字,在 Long 长度大于17位时会出现精度丢失的问题。

当我们把上面的用户 ID 改成 19 位的时候,我们再来看看浏览器请求返回的结果。

  1. //设置用户ID,位数为19位 
  2. user.setId(1234567890123456789l); 

浏览器请求结果!

当返回的结果超过17位的时候,后面的全部变成0!

二、解决办法

遇到这种情况,应该怎么办呢?

  • 第一种办法:在后台把 long 型改为String类型,但是代价有点大,只要涉及到的地方都需要改
  • 第二种办法:使用工具进行转化把 long 型改为String类型,这种方法可以实现全局转化(推荐)
  • 第三种办法:前端进行处理(目前没有很好的办法,不推荐)

因为项目涉及到的代码非常多,所以不可能把 long 型改为 String 类型,而且使用 Long 类型的方法非常多,改起来风险非常大,所以不推荐使用!

最理想的方法,就是使用aop代理拦截所有的方法,对返回参数进行统一处理,使用工具进行转化,过程如下!

1. Jackson 工具序列化对象

我们可以使用Jackson工具包来实现对象序列化。

首先在 maven 中添加必须的依赖:

  1. <!--jackson依赖--> 
  2. <dependency> 
  3.     <groupId>com.fasterxml.jackson.core</groupId> 
  4.     <artifactId>jackson-core</artifactId> 
  5.     <version>2.9.8</version> 
  6. </dependency> 
  7. <dependency> 
  8.     <groupId>com.fasterxml.jackson.core</groupId> 
  9.     <artifactId>jackson-annotations</artifactId> 
  10.     <version>2.9.8</version> 
  11. </dependency> 
  12. <dependency> 
  13.     <groupId>com.fasterxml.jackson.core</groupId> 
  14.     <artifactId>jackson-databind</artifactId> 
  15.     <version>2.9.8</version> 
  16. </dependency> 

编写一个转化工具类JsonUtil:

  1. public class JsonUtil { 
  2.  
  3.     private static final Logger log = LoggerFactory.getLogger(JsonUtil.class); 
  4.  
  5.     private static ObjectMapper objectMapper = new ObjectMapper(); 
  6.     private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"
  7.  
  8.     static { 
  9.         // 对象的所有字段全部列入 
  10.         objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); 
  11.         // 取消默认转换timestamps形式 
  12.         objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); 
  13.         // 忽略空bean转json的错误 
  14.         objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); 
  15.         //设置为东八区 
  16.         objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); 
  17.         // 统一日期格式 
  18.         objectMapper.setDateFormat(new SimpleDateFormat(DATE_FORMAT)); 
  19.         // 反序列化时,忽略在json字符串中存在, 但在java对象中不存在对应属性的情况, 防止错误 
  20.         objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 
  21.         // 序列换成json时,将所有的long变成string 
  22.         objectMapper.registerModule(new SimpleModule().addSerializer(Long.class, ToStringSerializer.instance).addSerializer(Long.TYPE, ToStringSerializer.instance)); 
  23.     } 
  24.  
  25.     /** 
  26.      * 对象序列化成json字符串 
  27.      * @param obj 
  28.      * @param <T> 
  29.      * @return 
  30.      */ 
  31.     public static <T> String objToStr(T obj) { 
  32.         if (null == obj) { 
  33.             return null; 
  34.         } 
  35.  
  36.         try { 
  37.             return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); 
  38.         } catch (Exception e) { 
  39.             log.warn("objToStr error: ", e); 
  40.             return null; 
  41.         } 
  42.     } 
  43.  
  44.     /** 
  45.      * json字符串反序列化成对象 
  46.      * @param str 
  47.      * @param clazz 
  48.      * @param <T> 
  49.      * @return 
  50.      */ 
  51.     public static <T> T strToObj(String str, Class<T> clazz) { 
  52.         if (StringUtils.isBlank(str) || null == clazz) { 
  53.             return null; 
  54.         } 
  55.  
  56.         try { 
  57.             return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz); 
  58.         } catch (Exception e) { 
  59.             log.warn("strToObj error: ", e); 
  60.             return null; 
  61.         } 
  62.     } 
  63.  
  64.     /** 
  65.      * json字符串反序列化成对象(数组) 
  66.      * @param str 
  67.      * @param typeReference 
  68.      * @param <T> 
  69.      * @return 
  70.      */ 
  71.     public static <T> T strToObj(String str, TypeReference<T> typeReference) { 
  72.         if (StringUtils.isBlank(str) || null == typeReference) { 
  73.             return null; 
  74.         } 
  75.  
  76.         try { 
  77.             return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference)); 
  78.         } catch (Exception e) { 
  79.             log.warn("strToObj error", e); 
  80.             return null; 
  81.         } 
  82.     } 

紧接着,编写一个实体类Person,用于测试:

  1. @Data 
  2. public class Person implements Serializable { 
  3.  
  4.     private static final long serialVersionUID = 1L
  5.  
  6.     private Integer id; 
  7.  
  8.     //Long型参数 
  9.     private Long uid; 
  10.     private String name; 
  11.     private String address; 
  12.     private String mobile; 
  13.  
  14.     private Date createTime; 

最后,我们编写一个测试类测试一下效果:

  1. public static void main(String[] args) { 
  2.     Person person = new Person(); 
  3.     person.setId(1); 
  4.     person.setUid(1111L); 
  5.     person.setName("hello"); 
  6.     person.setAddress(""); 
  7.     System.out.println(JsonUtil.objToStr(person)); 

输出结果如下:

其中最关键一行代码,是注册了这个转换类,从而实现将所有的 long 变成 string。

  1. // 序列换成json时,将所有的long变成string 
  2. SimpleModule simpleModule = new SimpleModule(); 
  3. simpleModule.addSerializer(Long.class, ToStringSerializer.instance); 
  4. simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); 
  5. objectMapper.registerModule(simpleModule); 

如果想对某个日期进行格式化,可以全局设置。

  1. //全局统一日期格式 
  2. objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 

也可以,单独对某个属性进行设置,例如对createTime属性格式化为yyyy-MM-dd,只需要加上如下注解即可。

  1. @JsonFormat(pattern="yyyy-MM-dd"timezone="GMT+8"
  2. private Date createTime; 

工具转化类写好之后,就非常简单了,只需要对 aop 拦截的方法返回的参数,进行序列化就可以自动实现将所有的 long 变成 string。

2. SpringMVC 配置

如果是 SpringMVC 项目,操作也很简单。

自定义一个实现类,继承自ObjectMapper:

  1. package com.example.util; 
  2.  
  3. /** 
  4.  * 继承ObjectMapper 
  5.  */ 
  6. public class CustomObjectMapper extends ObjectMapper { 
  7.  
  8.     public CustomObjectMapper() { 
  9.         super(); 
  10.         SimpleModule simpleModule = new SimpleModule(); 
  11.         simpleModule.addSerializer(Long.class, ToStringSerializer.instance); 
  12.         simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); 
  13.         registerModule(simpleModule); 
  14.     } 

在 SpringMVC 的配置文件中加上如下配置:

  1. <mvc:annotation-driven > 
  2.     <mvc:message-converters> 
  3.         <bean class="org.springframework.http.converter.StringHttpMessageConverter"> 
  4.             <constructor-arg index="0" value="utf-8" /> 
  5.             <property name="supportedMediaTypes"> 
  6.                 <list> 
  7.                     <value>application/json;charset=UTF-8</value> 
  8.                     <value>text/plain;charset=UTF-8</value> 
  9.                 </list> 
  10.             </property> 
  11.         </bean>           
  12.         <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> 
  13.             <property name="objectMapper"> 
  14.                 <bean class="com.example.util.CustomObjectMapper"> 
  15.                     <property name="dateFormat"> 
  16.                         <-对日期进行统一转化-> 
  17.                         <bean class="java.text.SimpleDateFormat"> 
  18.                             <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" /> 
  19.                         </bean> 
  20.                     </property> 
  21.                 </bean> 
  22.             </property> 
  23.         </bean> 
  24.     </mvc:message-converters> 
  25. </mvc:annotation-driven> 

3. SpringBoot 配置

如果是 SpringBoot 项目,操作也类似。

编写一个WebConfig配置类,并实现自WebMvcConfigurer,重写configureMessageConverters方法:

  1. /** 
  2.  * WebMvc配置 
  3.  */ 
  4. @Configuration 
  5. @Slf4j 
  6. @EnableWebMvc 
  7. public class WebConfig implements WebMvcConfigurer { 
  8.  
  9.     /** 
  10.      *添加消息转化类 
  11.      * @param list 
  12.      */ 
  13.     @Override 
  14.     public void configureMessageConverters(List<HttpMessageConverter<?>> list) { 
  15.         MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(); 
  16.         ObjectMapper objectMapper = jsonConverter.getObjectMapper(); 
  17.         //序列换成json时,将所有的long变成string 
  18.         SimpleModule simpleModule = new SimpleModule(); 
  19.         simpleModule.addSerializer(Long.class, ToStringSerializer.instance); 
  20.         simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); 
  21.         objectMapper.registerModule(simpleModule); 
  22.         list.add(jsonConverter); 
  23.     } 

三、总结

在实际的项目开发中,很多服务都是纯微服务开发,没有用到SpringMVC,在这种情况下,使用JsonUtil工具类实现对象序列化,可能是一个非常好的选择。

 

责任编辑:赵宁宁 来源: Java极客技术
相关推荐

2024-09-24 13:31:33

2023-10-26 07:29:06

mongodb十六进制ID

2024-01-24 12:09:33

代码Lodash前端

2024-12-09 08:25:47

Springsave方法

2022-08-28 20:07:17

Docker后端

2020-06-05 07:42:16

参数验证合法

2021-01-08 07:38:15

代码功能调用

2020-03-27 10:20:05

安全众测渗透测试网络安全

2015-05-11 10:39:19

2024-08-30 11:40:19

2021-05-07 07:59:52

WebFluxSpring5系统

2022-03-15 17:35:20

电商系统架构

2020-03-09 10:21:12

Java集合类 Guava

2021-08-19 07:34:55

RabbitMQLinuxWindows

2020-09-06 10:02:32

项目管理战略目标CIO

2020-10-30 07:43:35

Jenkins配置前端

2020-07-09 07:54:35

ThreadPoolE线程池

2020-10-19 06:47:05

爬虫数据Jsoup

2020-11-06 07:35:09

微信支付支付宝

2022-03-08 13:08:45

数据库异构数据库
点赞
收藏

51CTO技术栈公众号