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