问题说明
浏览器Long长度大于17会精度丢失,解决办法是后台将Long转为String,原因是js number的局限。
问题剖析
JavaScript 的 Number 类型使用 64 位双精度浮点数(遵循 IEEE 754 标准),这种表示方式具有一定的精度限制。这种精度限制在处理大整数时尤为明显。例如,当一个 Long 值的位数超过 17 位时,可能会遇到精度丢失的问题。在这种情况下,通常建议在后台将 Long 转换为 String,以确保在传输和处理过程中不会丢失精度。
JavaScript 数值表示的局限性
- IEEE 754 标准:
JavaScript 的 Number 类型采用 IEEE 754 双精度浮点数格式来表示数字。这个格式有一个 52 位的有效数字位(即尾数),加上一个 11 位的指数位,以及一个符号位。 - 精度限制:
由于尾数部分只有 52 位有效位,因此 JavaScript 的 Number 类型可以精确表示的最大整数是 2 53 − 1 2^{53} - 1 253−1(即 9,007,199,254,740,991)。当整数超出这个范围时,JavaScript 无法保持其精度,可能导致数值的精度丢失或不准确的结果。
问题验证
当你在浏览器中处理一个超过 17 位的 Long 整数时,可能会遇到精度丢失的问题:
let largeNumber = 1234567890123456789; // 超过 17 位的数字
console.log(largeNumber); // 输出可能会不准确
总结
在 JSON 数据交换中,JavaScript 无法安全地传输超过 17 位的整数。因此,使用字符串来传输这些整数是可靠的做法,因为字符串在传输过程中不会丢失精度。
解决办法
为了避免精度丢失,推荐在后台将 Long 类型转换为 String 类型,并在前端以字符串形式传递和处理这些大整数。这可以确保整数在传输和操作过程中不会丢失精度。
通用配置
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
simpleModule.addSerializer(long.class, ToStringSerializer.instance);
simpleModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(jackson2HttpMessageConverter);
}
}
泛类型特殊配置
对于接口直接将Object、Map等泛类型形式返回的场景,前面配的Serializer是不生效的,为解决这个问题,需手动处理该场景的转换,考虑到可复用性,采用注解切面的方式实现。
在编程中,“Advice” 通常与面向切面编程(Aspect-Oriented Programming,AOP)相关。AOP 是一种编程范式,它允许你将关注点(如日志、事务、安全等)从业务逻辑中分离出来。这些关注点被称为“横切关注点”,而这些关注点的实现则通过“Advice”来完成。
使用 Advice 的场景
- 日志记录:使用 Before 和 After Advice 记录方法的调用及其结果,帮助调试和监控应用程序的行为。
- 事务管理:在方法执行之前开始事务,在方法执行之后提交事务,或在出现异常时回滚事务。
- 权限检查:在方法执行之前检查用户是否具有执行该方法的权限。
- 性能监控:记录方法的执行时间,用于性能分析和优化。
1.编写自定义注解
/**
* 数据返回有long,且是泛类型,使用@DataBaseResultAdvice来做返回结果格式化
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataBaseResultAdvice {
}
2.编写注解拦截器实现类,的
@ControllerAdvice
public class DataBaseResultBodyAdvice implements ResponseBodyAdvice<Object> {
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// Check if the method has the @ApplyResponseAdvice annotation
return returnType.getMethod().isAnnotationPresent(DataBaseResultAdvice.class);
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
JSONObject jsonObject = (JSONObject) JSON.toJSON(body);
// 遍历 JsonObject
convertLongToString(jsonObject);
return jsonObject;
}
/**
* 将JSON对象或数组中的Long或BigInteger类型的值转换为字符串。
*
* @param object 需要转换的对象,可以是JSONObject或JSONArray
*/
private static void convertLongToString(Object object) {
// 如果对象是JSONObject类型
if (object instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) object;
// 遍历JSONObject的所有键
for (String key : jsonObject.keySet()) {
Object value = jsonObject.get(key);
// 如果值是Long或BigInteger类型,将其转换为字符串
if (value instanceof Long || value instanceof BigInteger) {
jsonObject.put(key, value.toString());
}
// 如果值是JSONObject或JSONArray类型,递归调用convertLongToString方法
else if (value instanceof JSONObject || value instanceof JSONArray) {
convertLongToString(value);
}
}
}
// 如果对象是JSONArray类型
else if (object instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) object;
// 遍历JSONArray的所有元素
for (int i = 0; i < jsonArray.size(); i++) {
Object value = jsonArray.get(i);
// 如果元素是Long或BigInteger类型,将其转换为字符串
if (value instanceof Long || value instanceof BigInteger) {
jsonArray.set(i, value.toString());
}
// 如果元素是JSONObject或JSONArray类型,递归调用convertLongToString方法
else if (value instanceof JSONObject || value instanceof JSONArray) {
convertLongToString(value);
}
}
}
}
}
3.在返回泛类型接口的方法上加上注解@DataBaseResultAdvice
@DataBaseResultAdvice
@ApiOperation(value = "这是一个接口")
@PostMapping("/page/{code}")
public BaseResult<IPage<Map>> page(@PathVariable("code") String cod) {
IPage<Map> result = dataService.getDataPage(code, query);
return BaseResult.ok(result);
}