init commit
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using SimpleCore.Library;
|
||||
|
||||
namespace StandardScene.CommonTools
|
||||
{
|
||||
/// <summary>
|
||||
/// 按文件路径串行化读写,保证高并发下对同一文件的更新原子、不覆盖其他记录。
|
||||
/// 调用方在委托中完成“读当前内容 → 修改 → 返回新内容”,由本类负责加锁与写回。
|
||||
/// </summary>
|
||||
public static class AtomicFileUpdateHelper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, object> PathLocks = new ConcurrentDictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// 对指定路径执行原子更新:在持锁下读取当前文件内容,调用 update 得到新内容并写回。
|
||||
/// 不修改其他记录时,应在 update 中仅变更需要变更的条目后返回完整内容。
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件完整路径</param>
|
||||
/// <param name="update">接收当前文件文本(若文件不存在则为 null),返回要写回的新文本;返回 null 表示不写入</param>
|
||||
public static void ExecuteAtomicUpdate(string filePath, Func<string, string> update)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
throw new ArgumentNullException(nameof(filePath));
|
||||
if (update == null)
|
||||
throw new ArgumentNullException(nameof(update));
|
||||
|
||||
var lockObj = PathLocks.GetOrAdd(filePath, _ => new object());
|
||||
lock (lockObj)
|
||||
{
|
||||
string current = null;
|
||||
try
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
current = File.ReadAllText(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Diagnosis.Log($"AtomicFileUpdate read error: {filePath}, {ex.Message}", "AtomicFileUpdate", true);
|
||||
throw;
|
||||
}
|
||||
|
||||
string newContent = update(current);
|
||||
if (newContent == null)
|
||||
return;
|
||||
|
||||
var dir = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
try
|
||||
{
|
||||
var tempPath = filePath + ".tmp";
|
||||
File.WriteAllText(tempPath, newContent);
|
||||
if (File.Exists(filePath))
|
||||
File.Replace(tempPath, filePath, null);
|
||||
else
|
||||
File.Move(tempPath, filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Diagnosis.Log($"AtomicFileUpdate write error: {filePath}, {ex.Message}", "AtomicFileUpdate", true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace StandardScene.CommonTools
|
||||
{
|
||||
public sealed class SnowflakeIdGenerator
|
||||
{
|
||||
// 默认起始时间戳:2026-01-01T00:00:00.000Z(Unix 毫秒)
|
||||
// 如果你希望生成的数字更短,可以在构造函数里传入更“近”的 _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user