【问题】浏览器JS对超过17位字段精度失真问题

问题说明

浏览器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 2531(即 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);
    }
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值