1,问题提出
js中的Long类型数据仅支持2的53次方长度的数值,如果长度超过这个数值,就会出现精度丢失,数值不一致的问题。
如果后台传来一个64位的Long型整数,因为超过了53位,所以后台返回的值和前台获取的值会不一样。17位以后的数字变成了0,第17位四舍五入。
如:164379764419858435,前端会变成164379764419858430。在浏览器中做测试可知,这就是一个精度丢失的问题。
2,两种方法
- :最常用的办法就是将Long类型字段统一转成String类型。在后台使用工具进行转化把 long 型改为String类型,这种方法可以实现全局转化(推荐)
- :最理想的方法,就是使用aop代理拦截所有的方法,对返回参数进行统一处理,使用工具进行转化,过程如下!
如何避免精度丢失呢?
在springboot项目中解决上述问题,我们需要添加配置类,手动修改springmvc默认的消息转换器类
3,第一种方法
首先创建一个json对象映射器类
package com.tigerhhzz.wuaimai.common; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import java.math.BigInteger; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; /** * 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象 * 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象] * 从Java对象生成JSON的过程称为 [序列化Java对象到JSON] */ public class JacksonObjectMapper extends ObjectMapper { public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss"; public JacksonObjectMapper() { super(); //收到未知属性时不报异常 this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false); //反序列化时,属性不存在的兼容处理 this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); SimpleModule simpleModule = new SimpleModule() .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))) .addSerializer(BigInteger.class, ToStringSerializer.instance) .addSerializer(Long.class, ToStringSerializer.instance) .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT))) .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT))) .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT))); //注册功能模块 例如,可以添加自定义序列化器和反序列化器 this.registerModule(simpleModule); } }
上述代码中,其中:
.addSerializer(Long.class, ToStringSerializer.instance)
就是将带有Long类型的类对象,序列化为字符串类型的json数据。
- 重写extendMessageConverters方法
然后重写extendMessageConverters方法,设置对象转换器到springmvc配置类中,并将其索引顺序修改为第一位置;
package com.tigerhhzz.wuaimai.config; import com.tigerhhzz.wuaimai.common.JacksonObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.validation.Validator; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import java.util.List; /** * @author tigerhhzz * @date 2023/3/11 19:45 */ @Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { /** * 扩展mvc框架的消息转换器 * @param converters */ @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器..."); //创建消息转换器对象 MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); //设置对象转换器,底层使用Jackson将Java对象转为json messageConverter.setObjectMapper(new JacksonObjectMapper()); //将上面的消息转换器对象追加到mvc框架的转换器集合中 converters.add(0,messageConverter); } }
4,第二种方法
Jackson 工具序列化对象
我们可以使用Jackson工具包来实现对象序列化。
首先在 maven 中添加必须的依赖
<!--jackson依赖--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.8</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency>
编写一个转化工具类JsonUtil
public class JsonUtil { private static final Logger log = LoggerFactory.getLogger(JsonUtil.class); private static ObjectMapper objectMapper = new ObjectMapper(); private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; static { // 对象的所有字段全部列入 objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS); // 取消默认转换timestamps形式 objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); // 忽略空bean转json的错误 objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); //设置为东八区 objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8")); // 统一日期格式 objectMapper.setDateFormat(new SimpleDateFormat(DATE_FORMAT)); // 反序列化时,忽略在json字符串中存在, 但在java对象中不存在对应属性的情况, 防止错误 objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 序列换成json时,将所有的long变成string objectMapper.registerModule(new SimpleModule().addSerializer(Long.class, ToStringSerializer.instance).addSerializer(Long.TYPE, ToStringSerializer.instance)); } /** * 对象序列化成json字符串 * @param obj * @param <T> * @return */ public static <T> String objToStr(T obj) { if (null == obj) { return null; } try { return obj instanceof String ? (String) obj : objectMapper.writeValueAsString(obj); } catch (Exception e) { log.warn("objToStr error: ", e); return null; } } /** * json字符串反序列化成对象 * @param str * @param clazz * @param <T> * @return */ public static <T> T strToObj(String str, Class<T> clazz) { if (StringUtils.isBlank(str) || null == clazz) { return null; } try { return clazz.equals(String.class) ? (T) str : objectMapper.readValue(str, clazz); } catch (Exception e) { log.warn("strToObj error: ", e); return null; } } /** * json字符串反序列化成对象(数组) * @param str * @param typeReference * @param <T> * @return */ public static <T> T strToObj(String str, TypeReference<T> typeReference) { if (StringUtils.isBlank(str) || null == typeReference) { return null; } try { return (T) (typeReference.getType().equals(String.class) ? str : objectMapper.readValue(str, typeReference)); } catch (Exception e) { log.warn("strToObj error", e); return null; } } }
紧接着,编写一个实体类Person,用于测试
@Data public class Person implements Serializable { private static final long serialVersionUID = 1L; private Integer id; //Long型参数 private Long uid; private String name; private String address; private String mobile; private Date createTime; }
最后,我们编写一个测试类测试一下效果
public static void main(String[] args) { Person person = new Person(); person.setId(1); person.setUid(1111L); person.setName("hello"); person.setAddress(""); System.out.println(JsonUtil.objToStr(person)); }
输出结果如下:
其中最关键一行代码,是注册了这个转换类,从而实现将所有的 long 变成 string。
// 序列换成json时,将所有的long变成string SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule);
如果想对某个日期进行格式化,可以全局设置。
//全局统一日期格式 objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
也可以,单独对某个属性进行设置,例如对createTime属性格式化为yyyy-MM-dd,只需要加上如下注解即可。
@JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8") private Date createTime;
工具转化类写好之后,就非常简单了,只需要对 aop 拦截的方法返回的参数,进行序列化就可以自动实现将所有的 long变成 string。
- SpringBoot 配置
编写一个WebConfig配置类,并实现自WebMvcConfigurer,重写configureMessageConverters方法
/** * 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); } }
5,总结
最终,json数据中Long类型的数值都转换成字符串在请求与响应之间传递。