消防培训系统服务器
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

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;
}
}
}