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

    技术2022-07-12  78

    在开发过程中,通常我们的主键会使用‘雪花算法’设置成长整型,但是当过长的长整型传到前端后会丢失精度。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); } }

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

    Processed: 0.009, SQL: 9