处理Long类型长度超长导致前端精度丢失问题
1,问题场景
后端返回的Long类型的数据,超10000000000000000,前端处理的时候,数据被截断了。比如tchId: 11073477511443988481, 前端根据tchId获取下一环节信息的时候,传的tchId变成了11073477511443988400,导致报错。
主要是因为JavaScript中的Number类型无法精确表示超过其安全整数范围(即-9007199254740991(-2^53 + 1)到9007199254740991(2^53 - 1))的整数值。因此,当后端Java的Long类型数据超过这个范围时,前端JavaScript在解析这些数据时就会丢失精度。
因此当Long类型超过的时候,需要把Long类型,转成字符串返回给前端。
2,处理:
1,处理方法1:字段加注解
可以使用Jackson库的@JsonFormat注解来将Long类型字段序列化为字符串类型。这样,在数据传输到前端时,就会以字符串的形式进行传输,从而避免了精度丢失的问题,如代码字段返回转换成字符串。
import com.fasterxml.jackson.annotation.JsonFormat;
public class Tache {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private Long tchld;
}
这种就只适合指定的字段,不通用。如果要设置未全局要怎么做?
2,处理方法2:全局处理
为了更加便捷地解决这个问题,使用全局配置,将所有Long类型的数据都转换为字符串类型。通过配置Jackson的ObjectMapper来实现
引用:
implementation 'com.fasterxml.jackson.core:jackson-core:2.13.5'
判断处理:
public class StringSerializer extends StdSerializer<Object> {
private static Logger logger = LoggerFactory.getLogger(StringSerializer.class);
public final static StringSerializer instance = new StringSerializer();
public StringSerializer() {
super(Object.class);
}
public StringSerializer(Class<?> handledType) {
super(handledType, false);
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value != null && value instanceof Long) {
if (((Long) value).longValue() >= 10000000000000000L) {
gen.writeString(value.toString());
} else {
gen.writeNumber((Long) value);
}
}
}
}
配置:
@Configuration
@EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/**").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
@Bean
public ObjectMapper jacksonObjectMapperCustomization() {
TimeZone timeZone = TimeZone.getTimeZone("Asia/Shanghai");
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder().timeZone(timeZone);
return builder.build();
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(c -> c instanceof MappingJackson2HttpMessageConverter);
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(
jacksonObjectMapperCustomization());
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, StringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, StringSerializer.instance);
objectMapper.registerModule(simpleModule);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(jackson2HttpMessageConverter);
converters.add(new ByteArrayHttpMessageConverter());
converters.add(new FormHttpMessageConverter());
converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
}
@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("LOCALE");
return localeChangeInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
@Bean
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
return new RequestMappingHandlerMapping() {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class)
|| (AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)
&& !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class)));
}
};
}
@Bean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier, ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier, EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties, WebEndpointProperties webEndpointProperties, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = this.shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes, corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath), shouldRegisterLinksMapping, null);
}
private boolean shouldRegisterLinksMapping(WebEndpointProperties webEndpointProperties, Environment environment, String basePath) {
return webEndpointProperties.getDiscovery().isEnabled() && (StringUtils.hasText(basePath) || ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT));
}
}
处理的代码:
simpleModule.addSerializer(Long.class, StringSerializer.instance);
simpleModule.addSerializer(Long.TYPE, StringSerializer.instance);
总结:
如果是只有某些指定数据进行转换,就直接加注解的方式,要全局的话,就用全局的配置去处理。