概述
项目中用雪花算法生成分布式ID,这些ID是Long型的。
当这些ID在前端通过JavaScript处理时,会存在精度丢失问题。
本文不过多讨论为什么会丢失精度。
本文只以后端(Java端)角度,提供几种解决方案。
整体说明
后台将Long转换成String类型有两大类解决方案:
- 全局处理:通过编写全局转换器,对查询结果中所有的Long都进行转换。
- 优点:对于后端来说,开发成本很低。
- 缺点:所有Long型字段都会被转换,可能导致前端的代码逻辑。
- 局部处理:编写序列号和反序列化器,通过@JsonSerialize和@JsonDeserialize指定需要转换的字段。
- 优点:对于后端来说,需要针对每一个需要转换的字段进行注解,开发成本较高,
- 缺点:只转换指定的字段,对前端代码逻辑影响较小。
全局处理
根据当前项目类型,分为三种方式。
Spring MVC
添加配置类,自定义的配置类提供的转换器会替代默认的转换器。
/**
* <p>Description: 自定义json转换器,取代默认的转换器</p>
* @author hanchao
* @date 2018/7/23 下午4:31
*/
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
/**
* <p>Description: 解决js处理long型丢失精度问题,将long转换成long</p>
* @author hanchao
* @date 2018/7/23 下午4:31
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//定义Json转换器
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);
//将对象映射器添加至Json转换器
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
//在转换器列表中添加自定义的Json转换器
converters.add(jackson2HttpMessageConverter);
//添加utf-8的默认String转换器
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
}
}
Spring Boot
其实Spring Boot的修改方式与Spring MVC一致,只是由于项目主类本身就继承了WebMvcConfigurerAdapter
。所以不需要自定义配置类,直接修改主类,添加@Override
方法configureMessageConverters
:
/**
* <p>Description: 解决js处理long型丢失精度问题,将long转换成long</p>
* @author hanchao
* @date 2018/7/23 下午4:31
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//定义Json转换器
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);
//将对象映射器添加至Json转换器
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
//在转换器列表中添加自定义的Json转换器
converters.add(jackson2HttpMessageConverter);
//添加utf-8的默认String转换器
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
}
没有Spring MVC
首先,定义一个由Long转换成String的工具方法:
/**
* <p>Description: 将long转换成String</p>
* @author hanchao
* @date 2018/7/23 下午5:07
*/
public static <T> T longToString(T body,String... field) throws IOException {
//定义对象映射器
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);
//将查询结果转换成Json字符串
String json = objectMapper.writeValueAsString(body);
//将Json字符串转换成查询结果
return (T) objectMapper.readValue(json,body.getClass());
}
然后,将查询结果在返回前端之前进行转换,具体代码就不多贴了。
局部处理
局部处理通过注解解决,无需关系项目类型。
首先,编写序列号和反序列化器:
package pers.hanchao.demo.util;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
/**
* <p>Description: 类型字段序列化时转为字符串,避免js丢失精度</P>
*
* @author hanchao
* @date 2018/7/23 下午5:34
*/
public class LongJsonSerializer extends JsonSerializer<Long> {
@Override
public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
String text = (value == null ? null : String.valueOf(value));
if (text != null) {
jsonGenerator.writeString(text);
}
}
}
package pers.hanchao.demo.util;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import org.apache.log4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
/**
* <p>Description: 将字符串转为Long</P>
*
* @author hanchao
* @date 2018/7/23 下午5:35
*/
public class LongJsonDeserializer extends JsonDeserializer<Long> {
private static final Logger logger = (Logger) LoggerFactory.getLogger(LongJsonDeserializer.class);
@Override
public Long deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String value = jsonParser.getText();
try {
return value == null ? null : Long.parseLong(value);
} catch (NumberFormatException e) {
logger.error("解析长整形错误", e);
return null;
}
}
}
然后,在需要进行转换的字段上,添加注解:
@JsonSerialize(using = LongJsonSerializer.class)
@JsonDeserialize(using = LongJsonDeserializer.class)
private Long id;