Files
2026-06-14 11:19:15 +08:00

129 lines
4.8 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Threading;
namespace StandardScene.CommonTools
{
public sealed class SnowflakeIdGenerator
{
// 默认起始时间戳:2026-01-01T00:00:00.000ZUnix 毫秒)
// 如果你希望生成的数字更短,可以在构造函数里传入更“近”的 _epochMs(建议全系统统一)。
private const long DefaultEpochMs = 1767225600000L;
private const int WorkerIdBits = 5; // 机器ID所占的位数
private const int DatacenterIdBits = 5; // 数据中心ID所占的位数
private const int MaxWorkerId = -1 ^ (-1 << WorkerIdBits); // 最大机器ID
private const int MaxDatacenterId = -1 ^ (-1 << DatacenterIdBits); // 最大数据中心ID
private const int SequenceBits = 12; // 序列号所占的位数
private const int WorkerIdShift = SequenceBits; // 机器ID左移的位数
private const int DatacenterIdShift = SequenceBits + WorkerIdBits; // 数据中心ID左移的位数
private const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits; // 时间戳左移的位数
private const long SequenceMask = -1L ^ (-1L << SequenceBits); // 序列号的最大值
private const string Base62Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private readonly object _syncRoot = new object();
private readonly long _epochMs;
private readonly long _workerId; // 机器ID
private readonly long _datacenterId; // 数据中心ID
private long _sequence; // 序列号
private long _lastTimestamp = -1L; // 上次生成ID的时间戳
public SnowflakeIdGenerator(long workerId, long datacenterId, long? epochMs = null)
{
this._epochMs = epochMs ?? DefaultEpochMs;
if (this._epochMs > TimeGen())
{
throw new ArgumentException("_epochMs cannot be in the future.");
}
if (workerId is > MaxWorkerId or < 0)
{
throw new ArgumentException($"worker Id can't be greater than {MaxWorkerId} or less than 0");
}
if (datacenterId is > MaxDatacenterId or < 0)
{
throw new ArgumentException($"{datacenterId} can't be greater than {MaxDatacenterId} or less than 0");
}
this._workerId = workerId;
this._datacenterId = datacenterId;
}
public long NextId()
{
lock (_syncRoot)
{
long timestamp = TimeGen();
if (timestamp < _lastTimestamp)
{
// 容忍系统时钟回拨:等待到追上 _lastTimestamp,避免直接抛异常把业务打崩。
timestamp = TilNextMillis(_lastTimestamp);
}
if (_lastTimestamp == timestamp)
{
_sequence = (_sequence + 1) & SequenceMask;
if (_sequence == 0)
{
timestamp = TilNextMillis(_lastTimestamp);
}
}
else
{
_sequence = 0;
}
_lastTimestamp = timestamp;
long id = ((timestamp - _epochMs) << TimestampLeftShift)
| (_datacenterId << DatacenterIdShift)
| (_workerId << WorkerIdShift)
| _sequence;
return id;
}
}
/// <summary>
/// 生成更短的字符串形式 ID(Base62 编码),便于显示/存储。
/// </summary>
public string NextIdBase62()
{
ulong value = unchecked((ulong)NextId());
return ToBase62(value);
}
private static long TilNextMillis(long lastTimestamp)
{
var spin = new SpinWait();
long timestamp;
do
{
spin.SpinOnce();
timestamp = TimeGen();
}
while (timestamp <= lastTimestamp);
return timestamp;
}
private static long TimeGen()
{
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
private static string ToBase62(ulong value)
{
if (value == 0)
{
return "0";
}
// 2^64-1 的 base62 最大长度为 11(因为 62^11 > 2^64)。
char[] buffer = new char[11];
int pos = buffer.Length;
while (value > 0)
{
ulong rem = value % 62;
value /= 62;
buffer[--pos] = Base62Alphabet[(int)rem];
}
return new string(buffer, pos, buffer.Length - pos);
}
}
}