You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
593 lines
19 KiB
593 lines
19 KiB
using System; |
|
using System.Diagnostics; |
|
using System.Runtime.CompilerServices; |
|
using System.Security; |
|
using System.Threading; |
|
|
|
namespace AX.FireTrainingSys |
|
{ |
|
/// <summary> |
|
/// 表示一个对象 Id。 |
|
/// </summary> |
|
/// <remarks>来自 MongoDB 的 ObjectId 算法。</remarks> |
|
public struct ObjectId : IComparable<ObjectId>, IEquatable<ObjectId> |
|
{ |
|
private static readonly ObjectId emptyInstance = default; |
|
private static readonly int staticMachine = (GetMachineHash() + GetAppDomainId()) & 0x00ffffff; |
|
private static readonly short staticPid = GetPid(); |
|
private static int staticIncrement = (new Random()).Next(); |
|
|
|
private readonly int a; |
|
private readonly int b; |
|
private readonly int c; |
|
|
|
/// <summary> |
|
/// 创建新的 ObjectId 实例。 |
|
/// </summary> |
|
/// <param name="bytes"></param> |
|
public ObjectId(byte[] bytes) |
|
{ |
|
if (bytes == null) |
|
throw new ArgumentNullException(nameof(bytes)); |
|
|
|
if (bytes.Length != 12) |
|
throw new ArgumentException("Byte array must be 12 bytes long", nameof(bytes)); |
|
|
|
FromByteArray(bytes, 0, out a, out b, out c); |
|
} |
|
|
|
/// <summary> |
|
/// 创建新的 ObjectId 实例。 |
|
/// </summary> |
|
/// <param name="bytes"></param> |
|
/// <param name="index"></param> |
|
internal ObjectId(byte[] bytes, int index) |
|
{ |
|
FromByteArray(bytes, index, out a, out b, out c); |
|
} |
|
|
|
/// <summary> |
|
/// 创建新的 ObjectId 实例。 |
|
/// </summary> |
|
/// <param name="timestamp">The timestamp (expressed as a DateTime).</param> |
|
/// <param name="machine">The machine hash.</param> |
|
/// <param name="pid">The PID.</param> |
|
/// <param name="increment">The increment.</param> |
|
public ObjectId(DateTime timestamp, int machine, short pid, int increment) |
|
: this(GetTimestampFromDateTime(timestamp), machine, pid, increment) |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// 创建新的 ObjectId 实例。 |
|
/// </summary> |
|
/// <param name="timestamp">The timestamp.</param> |
|
/// <param name="machine">The machine hash.</param> |
|
/// <param name="pid">The PID.</param> |
|
/// <param name="increment">The increment.</param> |
|
public ObjectId(int timestamp, int machine, short pid, int increment) |
|
{ |
|
if ((machine & 0xff000000) != 0) |
|
throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); |
|
|
|
if ((increment & 0xff000000) != 0) |
|
throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); |
|
|
|
a = timestamp; |
|
b = (machine << 8) | (((int)pid >> 8) & 0xff); |
|
c = ((int)pid << 24) | increment; |
|
} |
|
|
|
/// <summary> |
|
/// 创建新的 ObjectId 实例。 |
|
/// </summary> |
|
/// <param name="value">The value.</param> |
|
public ObjectId(string value) |
|
{ |
|
if (value == null) |
|
throw new ArgumentNullException(nameof(value)); |
|
|
|
var bytes = ParseHexString(value); |
|
FromByteArray(bytes, 0, out a, out b, out c); |
|
} |
|
|
|
|
|
/// <summary> |
|
/// 获得空实例。 |
|
/// </summary> |
|
public static ObjectId Empty |
|
{ |
|
get { return emptyInstance; } |
|
} |
|
|
|
/// <summary> |
|
/// 获得时间戳。 |
|
/// </summary> |
|
public int Timestamp |
|
{ |
|
get { return a; } |
|
} |
|
|
|
/// <summary> |
|
/// 获得机器码。 |
|
/// </summary> |
|
public int Machine |
|
{ |
|
get { return (b >> 8) & 0xffffff; } |
|
} |
|
|
|
/// <summary> |
|
/// 获得 PID。 |
|
/// </summary> |
|
public short Pid |
|
{ |
|
get { return (short)(((b << 8) & 0xff00) | ((c >> 24) & 0x00ff)); } |
|
} |
|
|
|
/// <summary> |
|
/// 获得增量值。 |
|
/// </summary> |
|
public int Increment |
|
{ |
|
get { return c & 0xffffff; } |
|
} |
|
|
|
/// <summary> |
|
/// 获得创建时间。 |
|
/// </summary> |
|
public DateTime CreationTime |
|
{ |
|
get { return DateTime.UnixEpoch.AddSeconds(Timestamp); } |
|
} |
|
|
|
|
|
public static bool operator <(ObjectId lhs, ObjectId rhs) |
|
{ |
|
return lhs.CompareTo(rhs) < 0; |
|
} |
|
|
|
public static bool operator <=(ObjectId lhs, ObjectId rhs) |
|
{ |
|
return lhs.CompareTo(rhs) <= 0; |
|
} |
|
|
|
public static bool operator ==(ObjectId lhs, ObjectId rhs) |
|
{ |
|
return lhs.Equals(rhs); |
|
} |
|
|
|
public static bool operator !=(ObjectId lhs, ObjectId rhs) |
|
{ |
|
return !(lhs == rhs); |
|
} |
|
|
|
public static bool operator >=(ObjectId lhs, ObjectId rhs) |
|
{ |
|
return lhs.CompareTo(rhs) >= 0; |
|
} |
|
|
|
public static bool operator >(ObjectId lhs, ObjectId rhs) |
|
{ |
|
return lhs.CompareTo(rhs) > 0; |
|
} |
|
|
|
/// <summary> |
|
/// 生成一个新的 ObjectId。 |
|
/// </summary> |
|
public static ObjectId NewId() |
|
{ |
|
return NewId(GetTimestampFromDateTime(DateTime.UtcNow)); |
|
} |
|
|
|
/// <summary> |
|
/// 根据指定的时间戳,生成一个新的 ObjectId。 |
|
/// </summary> |
|
public static ObjectId NewId(DateTime timestamp) |
|
{ |
|
return NewId(GetTimestampFromDateTime(timestamp)); |
|
} |
|
|
|
/// <summary> |
|
/// 根据指定的时间戳,生成一个新的 ObjectId。 |
|
/// </summary> |
|
public static ObjectId NewId(int timestamp) |
|
{ |
|
int increment = Interlocked.Increment(ref staticIncrement) & 0x00ffffff; // 只使用低位 3 字节 |
|
return new ObjectId(timestamp, staticMachine, staticPid, increment); |
|
} |
|
|
|
/// <summary> |
|
/// 把 ObjectId 的各组件打包成一个字节数组。 |
|
/// </summary> |
|
/// <param name="timestamp">The timestamp.</param> |
|
/// <param name="machine">The machine hash.</param> |
|
/// <param name="pid">The PID.</param> |
|
/// <param name="increment">The increment.</param> |
|
/// <returns>A byte array.</returns> |
|
public static byte[] Pack(int timestamp, int machine, short pid, int increment) |
|
{ |
|
if ((machine & 0xff000000) != 0) |
|
throw new ArgumentOutOfRangeException("machine", "The machine value must be between 0 and 16777215 (it must fit in 3 bytes)."); |
|
|
|
if ((increment & 0xff000000) != 0) |
|
throw new ArgumentOutOfRangeException("increment", "The increment value must be between 0 and 16777215 (it must fit in 3 bytes)."); |
|
|
|
byte[] bytes = new byte[12]; |
|
|
|
bytes[0] = (byte)(timestamp >> 24); |
|
bytes[1] = (byte)(timestamp >> 16); |
|
bytes[2] = (byte)(timestamp >> 8); |
|
bytes[3] = (byte)(timestamp); |
|
bytes[4] = (byte)(machine >> 16); |
|
bytes[5] = (byte)(machine >> 8); |
|
bytes[6] = (byte)(machine); |
|
bytes[7] = (byte)(pid >> 8); |
|
bytes[8] = (byte)(pid); |
|
bytes[9] = (byte)(increment >> 16); |
|
bytes[10] = (byte)(increment >> 8); |
|
bytes[11] = (byte)(increment); |
|
|
|
return bytes; |
|
} |
|
|
|
/// <summary> |
|
/// 分析字符串,转换为 ObjectId 实例。 |
|
/// </summary> |
|
public static ObjectId Parse(string str) |
|
{ |
|
if (str == null) |
|
throw new ArgumentNullException(nameof(str)); |
|
|
|
if (TryParse(str, out var objectId)) |
|
return objectId; |
|
else |
|
{ |
|
var message = string.Format("'{0}' is not a valid 24 digit hex string.", str); |
|
throw new FormatException(message); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// 尝试分析字符串,转换为 ObjectId 实例。 |
|
/// </summary> |
|
public static bool TryParse(string str, out ObjectId objectId) |
|
{ |
|
if (str != null && str.Length == 24) |
|
{ |
|
byte[] bytes; |
|
if (TryParseHexString(str, out bytes)) |
|
{ |
|
objectId = new ObjectId(bytes); |
|
return true; |
|
} |
|
} |
|
|
|
objectId = default(ObjectId); |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// 把一个字节数组解包成 ObjectId 各组件值。 |
|
/// </summary> |
|
/// <param name="bytes">A byte array.</param> |
|
/// <param name="timestamp">The timestamp.</param> |
|
/// <param name="machine">The machine hash.</param> |
|
/// <param name="pid">The PID.</param> |
|
/// <param name="increment">The increment.</param> |
|
public static void Unpack(byte[] bytes, out int timestamp, out int machine, out short pid, out int increment) |
|
{ |
|
if (bytes == null) |
|
throw new ArgumentNullException(nameof(bytes)); |
|
|
|
if (bytes.Length != 12) |
|
throw new ArgumentOutOfRangeException(nameof(bytes), "Byte array must be 12 bytes long."); |
|
|
|
timestamp = (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3]; |
|
machine = (bytes[4] << 16) + (bytes[5] << 8) + bytes[6]; |
|
pid = (short)((bytes[7] << 8) + bytes[8]); |
|
increment = (bytes[9] << 16) + (bytes[10] << 8) + bytes[11]; |
|
} |
|
|
|
private static int GetAppDomainId() |
|
{ |
|
return AppDomain.CurrentDomain.Id; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the current process id. This method exists because of how CAS operates on the call stack, checking |
|
/// for permissions before executing the method. Hence, if we inlined this call, the calling method would not execute |
|
/// before throwing an exception requiring the try/catch at an even higher level that we don't necessarily control. |
|
/// </summary> |
|
[MethodImpl(MethodImplOptions.NoInlining)] |
|
private static int GetCurrentProcessId() |
|
{ |
|
return Process.GetCurrentProcess().Id; |
|
} |
|
|
|
private static int GetMachineHash() |
|
{ |
|
// 替代 Dns.HostName 以便可以脱机工作 |
|
var machineName = GetMachineName(); |
|
return 0x00ffffff & machineName.GetHashCode(); // 使用前 3 个字节进行哈希 |
|
} |
|
|
|
private static string GetMachineName() |
|
{ |
|
return Environment.MachineName; |
|
} |
|
|
|
private static short GetPid() |
|
{ |
|
try |
|
{ |
|
return (short)GetCurrentProcessId(); // 只使用低位 2 字节 |
|
} |
|
catch (SecurityException) |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
private static int GetTimestampFromDateTime(DateTime timestamp) |
|
{ |
|
var secondsSinceEpoch = (long)Math.Floor((ToUniversalTime(timestamp) - DateTime.UnixEpoch).TotalSeconds); |
|
|
|
if (secondsSinceEpoch < int.MinValue || secondsSinceEpoch > int.MaxValue) |
|
throw new ArgumentOutOfRangeException("timestamp"); |
|
|
|
return (int)secondsSinceEpoch; |
|
} |
|
|
|
private static void FromByteArray(byte[] bytes, int offset, out int a, out int b, out int c) |
|
{ |
|
a = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]; |
|
b = (bytes[offset + 4] << 24) | (bytes[offset + 5] << 16) | (bytes[offset + 6] << 8) | bytes[offset + 7]; |
|
c = (bytes[offset + 8] << 24) | (bytes[offset + 9] << 16) | (bytes[offset + 10] << 8) | bytes[offset + 11]; |
|
} |
|
|
|
/// <summary> |
|
/// 比较 ObjectId 大小。 |
|
/// </summary> |
|
public int CompareTo(ObjectId other) |
|
{ |
|
int result = ((uint)a).CompareTo((uint)other.a); |
|
if (result != 0) { return result; } |
|
result = ((uint)b).CompareTo((uint)other.b); |
|
if (result != 0) { return result; } |
|
return ((uint)c).CompareTo((uint)other.c); |
|
} |
|
|
|
/// <summary> |
|
/// 比较相等性。 |
|
/// </summary> |
|
public bool Equals(ObjectId rhs) |
|
{ |
|
return |
|
a == rhs.a && |
|
b == rhs.b && |
|
c == rhs.c; |
|
} |
|
|
|
/// <summary> |
|
/// 比较相等性。 |
|
/// </summary> |
|
public override bool Equals(object obj) |
|
{ |
|
if (obj is ObjectId id) |
|
return Equals(id); |
|
else |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// 获得哈希码。 |
|
/// </summary> |
|
public override int GetHashCode() |
|
{ |
|
int hash = 17; |
|
hash = 37 * hash + a.GetHashCode(); |
|
hash = 37 * hash + b.GetHashCode(); |
|
hash = 37 * hash + c.GetHashCode(); |
|
return hash; |
|
} |
|
|
|
/// <summary> |
|
/// 把 ObjectId 转换为字节数组。 |
|
/// </summary> |
|
public byte[] ToByteArray() |
|
{ |
|
var bytes = new byte[12]; |
|
ToByteArray(bytes, 0); |
|
return bytes; |
|
} |
|
|
|
/// <summary> |
|
/// 把 ObjectId 转换为字节数组。 |
|
/// </summary> |
|
public void ToByteArray(byte[] bytes, int offset) |
|
{ |
|
if (bytes == null) |
|
throw new ArgumentNullException(nameof(bytes)); |
|
|
|
if (offset + 12 > bytes.Length) |
|
throw new ArgumentException("Not enough room in destination buffer.", nameof(offset)); |
|
|
|
bytes[offset + 0] = (byte)(a >> 24); |
|
bytes[offset + 1] = (byte)(a >> 16); |
|
bytes[offset + 2] = (byte)(a >> 8); |
|
bytes[offset + 3] = (byte)(a); |
|
bytes[offset + 4] = (byte)(b >> 24); |
|
bytes[offset + 5] = (byte)(b >> 16); |
|
bytes[offset + 6] = (byte)(b >> 8); |
|
bytes[offset + 7] = (byte)(b); |
|
bytes[offset + 8] = (byte)(c >> 24); |
|
bytes[offset + 9] = (byte)(c >> 16); |
|
bytes[offset + 10] = (byte)(c >> 8); |
|
bytes[offset + 11] = (byte)(c); |
|
} |
|
|
|
/// <summary> |
|
/// 转换为字符串。 |
|
/// </summary> |
|
public override string ToString() |
|
{ |
|
var c = new char[24]; |
|
c[0] = ToHexChar((a >> 28) & 0x0f); |
|
c[1] = ToHexChar((a >> 24) & 0x0f); |
|
c[2] = ToHexChar((a >> 20) & 0x0f); |
|
c[3] = ToHexChar((a >> 16) & 0x0f); |
|
c[4] = ToHexChar((a >> 12) & 0x0f); |
|
c[5] = ToHexChar((a >> 8) & 0x0f); |
|
c[6] = ToHexChar((a >> 4) & 0x0f); |
|
c[7] = ToHexChar(a & 0x0f); |
|
c[8] = ToHexChar((b >> 28) & 0x0f); |
|
c[9] = ToHexChar((b >> 24) & 0x0f); |
|
c[10] = ToHexChar((b >> 20) & 0x0f); |
|
c[11] = ToHexChar((b >> 16) & 0x0f); |
|
c[12] = ToHexChar((b >> 12) & 0x0f); |
|
c[13] = ToHexChar((b >> 8) & 0x0f); |
|
c[14] = ToHexChar((b >> 4) & 0x0f); |
|
c[15] = ToHexChar(b & 0x0f); |
|
c[16] = ToHexChar((this.c >> 28) & 0x0f); |
|
c[17] = ToHexChar((this.c >> 24) & 0x0f); |
|
c[18] = ToHexChar((this.c >> 20) & 0x0f); |
|
c[19] = ToHexChar((this.c >> 16) & 0x0f); |
|
c[20] = ToHexChar((this.c >> 12) & 0x0f); |
|
c[21] = ToHexChar((this.c >> 8) & 0x0f); |
|
c[22] = ToHexChar((this.c >> 4) & 0x0f); |
|
c[23] = ToHexChar(this.c & 0x0f); |
|
return new string(c); |
|
} |
|
|
|
/// <summary> |
|
/// 分析 16 进制字符串,转换为等价的字节数组。 |
|
/// </summary> |
|
/// <param name="str"></param> |
|
/// <returns></returns> |
|
private static byte[] ParseHexString(string str) |
|
{ |
|
if (str == null) |
|
throw new ArgumentNullException(nameof(str)); |
|
|
|
byte[] bytes; |
|
|
|
if (!TryParseHexString(str, out bytes)) |
|
throw new FormatException("String should contain only hexadecimal digits."); |
|
|
|
return bytes; |
|
} |
|
|
|
/// <summary> |
|
/// 把整型转换为 16 进制字符。 |
|
/// </summary> |
|
/// <param name="value"></param> |
|
/// <returns>The hex character.</returns> |
|
private static char ToHexChar(int value) |
|
{ |
|
return (char)(value + (value < 10 ? '0' : 'a' - 10)); |
|
} |
|
|
|
/// <summary> |
|
/// 把字节数组转换为 16 进制字符串。 |
|
/// </summary> |
|
/// <param name="bytes"></param> |
|
/// <returns></returns> |
|
private static string ToHexString(byte[] bytes) |
|
{ |
|
if (bytes == null) |
|
throw new ArgumentNullException(nameof(bytes)); |
|
|
|
var length = bytes.Length; |
|
var c = new char[length * 2]; |
|
|
|
for (int i = 0, j = 0; i < length; i++) |
|
{ |
|
var b = bytes[i]; |
|
c[j++] = ToHexChar(b >> 4); |
|
c[j++] = ToHexChar(b & 0x0f); |
|
} |
|
|
|
return new string(c); |
|
} |
|
|
|
/// <summary> |
|
/// 把 DateTime 值转换为 UTC 时间(特殊处理 MinValue 和 MaxValue)。 |
|
/// </summary> |
|
private static DateTime ToUniversalTime(DateTime dateTime) |
|
{ |
|
if (dateTime == DateTime.MinValue) |
|
return DateTime.SpecifyKind(DateTime.MinValue, DateTimeKind.Utc); |
|
else if (dateTime == DateTime.MaxValue) |
|
return DateTime.SpecifyKind(DateTime.MaxValue, DateTimeKind.Utc); |
|
else |
|
return dateTime.ToUniversalTime(); |
|
} |
|
|
|
/// <summary> |
|
/// 尝试分析 16 进制字符串,转换为等价的字节数组。 |
|
/// </summary> |
|
private static bool TryParseHexString(string str, out byte[] bytes) |
|
{ |
|
bytes = default; |
|
|
|
if (str == null) |
|
return false; |
|
|
|
var buffer = new byte[(str.Length + 1) / 2]; |
|
|
|
var i = 0; |
|
var j = 0; |
|
|
|
if ((str.Length % 2) == 1) |
|
{ |
|
// if str has an odd length assume an implied leading "0" |
|
int y; |
|
|
|
if (!TryParseHexChar(str[i++], out y)) |
|
return false; |
|
|
|
buffer[j++] = (byte)y; |
|
} |
|
|
|
while (i < str.Length) |
|
{ |
|
int x, y; |
|
|
|
if (!TryParseHexChar(str[i++], out x)) |
|
return false; |
|
|
|
if (!TryParseHexChar(str[i++], out y)) |
|
return false; |
|
|
|
buffer[j++] = (byte)((x << 4) | y); |
|
} |
|
|
|
bytes = buffer; |
|
return true; |
|
} |
|
|
|
private static bool TryParseHexChar(char c, out int value) |
|
{ |
|
if (c >= '0' && c <= '9') |
|
{ |
|
value = c - '0'; |
|
return true; |
|
} |
|
|
|
if (c >= 'a' && c <= 'f') |
|
{ |
|
value = 10 + (c - 'a'); |
|
return true; |
|
} |
|
|
|
if (c >= 'A' && c <= 'F') |
|
{ |
|
value = 10 + (c - 'A'); |
|
return true; |
|
} |
|
|
|
value = 0; |
|
return false; |
|
} |
|
} |
|
}
|
|
|