美文网首页
超过js的number类型最大值的解决办法

超过js的number类型最大值的解决办法

作者: 千年的心 | 来源:发表于2020-07-02 09:53 被阅读0次

在开发过程中,通常我们的主键会使用‘雪花算法’设置成长整型,但是当过长的长整型传到前端后会丢失精度。js的number类型有个最大值(安全值)。即2的53次方,为9007199254740992(16位)。如果超过这个值,那么js会出现不精确的问题。

解决方法

在传递给前端时,将Long转为String

1.在Spring Boot单体应用中,直接在序列化时配置规则


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * @author lieber
 */
@Configuration
@Component
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 配置模板资源路径
        registry.addResourceHandler("/templates/**").addResourceLocations("classpath:/templates/");
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }


    /**
     * 解决Jackson long经度丢失问题
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        // 序列化json时,将所有的Long变成String
        // 因为js中得数字类型不能包含所有的Java Long值
        SimpleModule simpleModule = new SimpleModule();
        simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
        simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(simpleModule);
        jackson2HttpMessageConverter.setObjectMapper(objectMapper);
        converters.add(jackson2HttpMessageConverter);
    }
}

2.在Spring Cloud Gateway中

在微服务环境时,每个微服务都需要去配置比较麻烦,所以我们在网关处统一处理。

2.1 新建一个Filter拦截响应

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;

/**
 * 响应long转换
 *
 * @author lieber
 */
@Component
@Slf4j
public class LongResponseFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String url = exchange.getRequest().getPath().value();
        if (!StringUtils.isEmpty(url) && notCover(url)) {
            return chain.filter(exchange);
        }
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        // 读取原有响应
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffers);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        // 释放掉内存
                        DataBufferUtils.release(join);
                        String str = new String(content, Charset.forName("UTF-8"));
                        // 处理原有响应
                        Object obj;
                        if (JsonUtil.INSTANCE.isJson(str)) {
                            obj = Long2StrUtil.INSTANCE.cover(JSONObject.parse(str));
                            // 如果是响应的ApiResult,那么只需要检查data中的内容即可
                            if (obj instanceof ApiResult) {
                                ApiResult result = (ApiResult) obj;
                                Object data = Long2StrUtil.INSTANCE.cover(result.getData());
                                result.setData(data);
                                obj = result;
                            } else {
                                // 否则检查所有内容
                                obj = Long2StrUtil.INSTANCE.cover(obj);
                            }
                        } else {
                            obj = Long2StrUtil.INSTANCE.cover(str);
                        }
                        str = JSONObject.toJSONString(obj, SerializerFeature.WriteMapNullValue);
                        originalResponse.getHeaders().setContentLength(str.getBytes().length);
                        return bufferFactory.wrap(str.getBytes());
                    }));

                }
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    @Override
    public int getOrder() {
        // 此处应该小于-1 既NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER之前执行
        return -10;
    }


    /**
     * 不需要转换
     *
     * @param url 路由信息
     * @return true/false
     */
    private boolean notCover(String url) {
        // 有其它条件可以加上
        return isSwaggerUrl(url);
    }

    /**
     * 判断是否是swagger路由
     *
     * @param url 当前路由
     * @return true/false
     */
    private boolean isSwaggerUrl(String url) {
        if (StringUtils.isEmpty(url)) {
            return false;
        }
        for (String str : SwaggerUrls.URLS) {
            if (url.contains(str)) {
                return true;
            }
        }
        return false;
    }
}

2.2 新建一个处理类

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

import java.util.Collection;
import java.util.Map;

/**
 * Long转字符串
 *
 * @author lieber
 */
public enum Long2StrUtil {


    /**
     * 实例
     */
    INSTANCE;

    private static final long JS_MAX_LONG_VALUE = 9007199254740992L;

    /**
     * 转换json对象中的Long数据
     *
     * @param jsonObject json对象
     * @return 转换后的json对象
     */
    private JSON coverObj(JSONObject jsonObject) {
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            Object obj = entry.getValue();
            if (obj == null) {
                continue;
            }
            obj = this.cover(obj);
            entry.setValue(obj);
        }
        return jsonObject;
    }


    /**
     * 转换json数组中的Long数据
     *
     * @param array json数组对象
     * @return 转换后的json数组对象
     */
    private JSON coverArr(JSONArray array) {
        for (int i = 0, size = array.size(); i < size; i++) {
            Object obj = array.get(i);
            obj = this.cover(obj);
            array.set(i, obj);
        }
        return array;
    }

    /**
     * 转换Long为字符串
     *
     * @param obj 带转换对象
     * @return 转换后对象
     */
    public Object cover(Object obj) {
        if (obj instanceof Long) {
            Long val = (Long) obj;
            if (val > JS_MAX_LONG_VALUE) {
                obj = val.toString();
            }
        } else if (obj instanceof Collection) {
            obj = this.coverArr(JSON.parseArray(JSONObject.toJSONString(obj, SerializerFeature.WriteMapNullValue)));
        } else if (JsonUtil.INSTANCE.isJsonObj(obj)) {
            obj = this.coverObj(JSON.parseObject(JSONObject.toJSONString(obj, SerializerFeature.WriteMapNullValue)));
        }
        return obj;
    }
}

2.3 其它处理类

import com.alibaba.fastjson.JSONObject;
import org.springframework.util.StringUtils;

/**
 * JSON处理工具类
 *
 * @author lieber
 */
public enum JsonUtil {

    /**
     * 实例
     */
    INSTANCE;

    /**
     * json对象字符串开始标记
     */
    private final static String JSON_OBJECT_START = "{";

    /**
     * json对象字符串结束标记
     */
    private final static String JSON_OBJECT_END = "}";

    /**
     * json数组字符串开始标记
     */
    private final static String JSON_ARRAY_START = "[";

    /**
     * json数组字符串结束标记
     */
    private final static String JSON_ARRAY_END = "]";


    /**
     * 判断字符串是否json对象字符串
     *
     * @param val 字符串
     * @return true/false
     */
    public boolean isJsonObj(String val) {
        if (StringUtils.isEmpty(val)) {
            return false;
        }
        val = val.trim();
        if (val.startsWith(JSON_OBJECT_START) && val.endsWith(JSON_OBJECT_END)) {
            try {
                JSONObject.parseObject(val);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }

    /**
     * 判断字符串是否json数组字符串
     *
     * @param val 字符串
     * @return true/false
     */
    public boolean isJsonArr(String val) {
        if (StringUtils.isEmpty(val)) {
            return false;
        }
        val = val.trim();
        if (StringUtils.isEmpty(val)) {
            return false;
        }
        val = val.trim();
        if (val.startsWith(JSON_ARRAY_START) && val.endsWith(JSON_ARRAY_END)) {
            try {
                JSONObject.parseArray(val);
                return true;
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }

    /**
     * 判断对象是否是json对象
     *
     * @param obj 待判断对象
     * @return true/false
     */
    public boolean isJsonObj(Object obj) {
        String str = JSONObject.toJSONString(obj);
        return this.isJsonObj(str);
    }

    /**
     * 判断字符串是否json字符串
     *
     * @param str 字符串
     * @return true/false
     */
    public boolean isJson(String str) {
        if (StringUtils.isEmpty(str)) {
            return false;
        }
        return this.isJsonObj(str) || this.isJsonArr(str);
    }

}

但是这种方式会对网关造成压力。

相关文章

  • 超过js的number类型最大值的解决办法

    在开发过程中,通常我们的主键会使用‘雪花算法’设置成长整型,但是当过长的长整型传到前端后会丢失精度。js的numb...

  • JS的数据类型

    一、Number 在JS中所有的数值都是number类型 最大值Number.MAX_VALUE 最小值Numbe...

  • 类型转换、运算符

    JS中所有的数值都是Number类型,包括整数和浮点数(小数)JS中可以表示的数字的最大值如果使用Number表示...

  • JS----Number、Boolean 、类型转换

    在JS中所有的数值都是Number类型,包括整数和浮点数(小数) JS中可以表示的数字的最大值 Number.MA...

  • 二.number类型易错点

    number类型最大值: number类型可以正确计算的范围是最小值-9007199254740992到最大值+9...

  • js数据类型转换

    在JS中所有的数值都是Number类型,包括整数和浮点数(小数) JS中可以表示的数字的最大值 N...

  • Springboot解决雪花算法ID到前端精度丢失

    JS的数字类型目前支持的最大值为:9007199254740992(16位),一旦数字超过这个值,JS将会丢失精度...

  • js入门之路------类型转换

    js 中两个类型相加 1、number类型+其他类型: I、number+string,例如: II、number...

  • ## JS初识

    ## JS初识 # js初识 # js注释 # 变量 # 变量的命名 JS数值的类型 # Number类型 # S...

  • 2019-07-27 前端面试题

    1.js的基础类型 js的基础类型包括 Undefined , Null , String ,Number ,...

网友评论

      本文标题:超过js的number类型最大值的解决办法

      本文链接:https://www.haomeiwen.com/subject/wkocqktx.html