.Net中关于Json序列化Long类型数据的解决办法
在分布式的项目中,我们的数据库Id不能再像以前一样使用int类型自动增长,这时候我们需要一个在网络当中都要能够保持唯一的值,通常情况我们会使用Guid来解决这个问题,但是作为string类型,他并不适合作为主键。尤其是在查询等需要索引操作的时候显得尤为重要。
1. 这时候我们通常会选择使用雪花Id来解决这个问题,他是一个能在网络当中能够保证唯一的数值number类型的数字,对应在csharp中是long类型。具体雪花Id的原理网上都有,这里直接上生成雪花Id的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
| public class OdinSnowFlake : IOdinSnowFlake { private readonly long twepoch; private const int workerIdBits = 5; private const int datacenterIdBits = 5; private const long maxWorkerId = -1L ^ (-1L << workerIdBits); private const long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private const int sequenceBits = 12; private const int datacenterIdShift = sequenceBits + workerIdBits; private const int workerIdShift = sequenceBits; private const int timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private const long sequenceMask = -1L ^ (-1L << sequenceBits); public long datacenterId { get; private set; } public long workerId { get; private set; } public long sequence { get; private set; } public long lastTimestamp { get; private set; } private static Dictionary<long, long> dicContainer = null;
public OdinSnowFlake(long datacenterId, long workerId) { this.twepoch = (long)((new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc) - Jan1st1970).TotalMilliseconds); if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new Exception(string.Format("datacenter Id can't be greater than {0} or less than 0", maxDatacenterId)); } if (workerId > maxWorkerId || workerId < 0) { throw new Exception(string.Format("worker Id can't be greater than {0} or less than 0", maxWorkerId)); } this.workerId = workerId; this.datacenterId = datacenterId; this.sequence = 0L; this.lastTimestamp = -1L; if (dicContainer == null) dicContainer = new Dictionary<long, long>(); }
public void InitDic() { if (dicContainer == null) dicContainer = new Dictionary<long, long>(); }
public void ClearDic() { if (dicContainer != null) dicContainer.Clear(); }
public long NextId() { lock (this) { long timestamp = GetCurrentTimestamp(); if (timestamp > lastTimestamp) { sequence = 0L; } else if (timestamp == lastTimestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = GetNextTimestamp(lastTimestamp); } } else { sequence = (sequence + 1) & sequenceMask; if (sequence > 0) { timestamp = lastTimestamp; } else { timestamp = lastTimestamp + 1; } }
lastTimestamp = timestamp;
var id = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; if (!dicContainer.ContainsKey(id)) { dicContainer.Add(id, id); return id; } else { Thread.Sleep(1); return NextId(); } } }
public string AnalyzeId(long Id) { StringBuilder sb = new StringBuilder(); var timestamp = (Id >> timestampLeftShift); var time = Jan1st1970.AddMilliseconds(timestamp + twepoch); sb.Append(time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss:fff")); var datacenterId = (Id ^ (timestamp << timestampLeftShift)) >> datacenterIdShift; sb.Append("_" + datacenterId); var workerId = (Id ^ ((timestamp << timestampLeftShift) | (datacenterId << datacenterIdShift))) >> workerIdShift; sb.Append("_" + workerId); var sequence = Id & sequenceMask; sb.Append("_" + sequence); return sb.ToString(); }
private static long GetNextTimestamp(long lastTimestamp) { long timestamp = GetCurrentTimestamp(); while (timestamp <= lastTimestamp) { timestamp = GetCurrentTimestamp(); } return timestamp; }
private static long GetCurrentTimestamp() { return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds; }
private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
}
|
代码解释:
提供了构造函数,其中datacenterId为当前数据中心Id:一般从1开始。workerId是机器Id,需要注意的是在网络节点当中的服务器,这个Id不能重复
代码的 NextId() 方法将会生成一个 18位长的long类型的雪花Id。
AnalyzeId() 方法可以简单的解析一个long的数值是不是符合雪花Id的规范。这个解析不精准,只能判断格式大致是否正确具体解析规则可以看代码。
有了这个Id,我们通常可以开心的在代码当中以application/json格式返回一个对象,比如
输出的结果是
这是因为 JavaScript 数值精度是32位,如果整数数度超过32位,就会被当作浮点数处理。换句话说,如果从服务端生成的JSON,某个值是64位整数,传到前端JavaScript,再传回服务端,不做任何运算,都可能出现失真。
解决问题的办法:将long作为string类型序列化输出 代码如下:
1 2 3 4 5 6 7
| public class Stu { [JsonConverter(typeof(JsonConverterLong))] public long id { get; set; } public string name { get; set; } public int age { get; set; } }
|
JsonConverterLong 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class JsonConverterLong : JsonConverter { public override bool CanConvert(Type objectType) { return true; }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if ((reader.ValueType == null || reader.ValueType == typeof(long?)) && reader.Value == null) { return null; } else { long.TryParse(reader.Value != null ? reader.Value.ToString() : "", out long value); return value; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) writer.WriteValue(value); else writer.WriteValue(value + ""); } }
|