前言
今天在进行前后端联调的时候发现,后端传给前端的Long类型的值,前端使用bigint接收之后会有精度丢失的问题。
后端字段:
前端接收字段:
问题描述
后端用户表有个user_id
字段表示用户Id,使用雪花算法生成一个Id填充。在测试删除用户的时候,发现前端传过来的userId字段精度有丢失,例如:1508622389376712704
变成了 1508622389376712700
。
解决方法
添加一个Http消息转换器 HttpMessageConverters
即可。
JsonHttpMessageConverter
/**
* 类型转换器
*
* @author luoweijie
* @date 2022/04/06
*/
@Configuration
public class JsonHttpMessageConverter {
private static final String ID_NAME = "id";
private static final String ID_SUFFIX = "Id";
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fastJsonConfig = new FastJsonConfig();
List<SerializerFeature> list = new ArrayList<>();
list.add(SerializerFeature.PrettyFormat);
list.add(SerializerFeature.WriteMapNullValue);
list.add(SerializerFeature.WriteNullStringAsEmpty);
list.add(SerializerFeature.WriteNullListAsEmpty);
list.add(SerializerFeature.QuoteFieldNames);
list.add(SerializerFeature.WriteDateUseDateFormat);
list.add(SerializerFeature.DisableCircularReferenceDetect);
list.add(SerializerFeature.WriteBigDecimalAsPlain);
fastJsonConfig.setSerializerFeatures(list.toArray(new SerializerFeature[0]));
fastConverter.setFastJsonConfig(fastJsonConfig);
fastJsonConfig.setSerializeFilters((ValueFilter) (object, name, value) -> {
// if ((name.endsWith(ID_SUFFIX) || ID_NAME.equals(name)) && value.getClass() == Long.class) {
// return String.valueOf(value);
// }
if (value != null && value.getClass() == Long.class) {
return String.valueOf(value);
}
return value;
});
return new HttpMessageConverters(fastConverter);
}
}
SnowFlakeUtils
/**
* 全局Id工具类,雪花算法
*
* @author luoweijie
* @date 2022/03/17
*/
public final class SnowFlakeUtils {
private final long workerId;
private final long datacenterId;
private long sequence = 0L;
/**
* 物理节点ID长度
*/
private final long workerIdBits = 5L;
/**
* 数据中心ID长度
*/
private final long datacenterIdBits = 5L;
private long lastTimestamp = -1L;
/**
* 单例SnowFlakeUtils
*/
private static class IdGenHolder {
private static final SnowFlakeUtils INSTANCE = new SnowFlakeUtils();
}
public static SnowFlakeUtils get() {
return IdGenHolder.INSTANCE;
}
public SnowFlakeUtils() {
this(0L, 0L);
}
public SnowFlakeUtils(long workerId, long datacenterId) {
// 最大支持机器节点数0~31,一共32个
long maxWorkerId = ~(-1L << workerIdBits);
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
// 最大支持数据中心节点数0~31,一共32个
long maxDatacenterId = ~(-1L << datacenterIdBits);
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
@SuppressWarnings("all")
public synchronized long nextId() {
//获取当前毫秒数
long timestamp = timeGen();
//如果服务器时间有问题(时钟后退) 报错。
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果上次生成时间和当前时间相同,在同一毫秒内
long sequenceBits = 12L;
if (lastTimestamp == timestamp) {
//sequence自增,因为sequence只有12bit,所以和sequenceMask相与一下,去掉高位
/**
* 用于和当前时间戳做比较,以获取最新时间
*/
long sequenceMask = ~(-1L << sequenceBits);
sequence = (sequence + 1) & sequenceMask;
//判断是否溢出,也就是每毫秒内超过4095,当为4096时,与sequenceMask相与,sequence就等于0
if (sequence == 0) {
//自旋等待到下一毫秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
//如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加,每个毫秒时间内,都是从0开始计数,最大4095
sequence = 0L;
}
lastTimestamp = timestamp;
// 最后按照规则拼出ID 64位
// 000000000000000000000000000000000000000000 00000 00000 000000000000
//1位固定整数 time datacenterId workerId sequence
long tewPoch = 1288834974657L;
long datacenterIdShift = sequenceBits + workerIdBits;
long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
return ((timestamp - tewPoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
| (workerId << sequenceBits) | sequence;
}
/**
* 比较当前时间和过去时间,防止时钟回退(机器问题),保证给的都是最新时间/最大时间
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 获取当前的时间戳(毫秒)
*/
private long timeGen() {
return System.currentTimeMillis();
}
/**
* 获取全局唯一编码
*/
public static Long getId() {
return SnowFlakeUtils.get().nextId();
}
}
评论区