電商、金流、物流系統常需產生「唯一、不可預測、可排序」的訂單編號。單純用 DateTime.Now.Ticks 或 Guid.NewGuid() 皆有缺失:前者並發下易衝突,後者無序且過長。本文示範「時間戳(精確到秒/毫秒)+ 密碼級隨機字串」的組合策略,兼顧唯一性、可讀性與安全性。
yyyyMMddHHmmss(或 yyyyMMddHHmmssfff 含毫秒),天然可排序、人類可讀。RNGCryptoServiceProvider(或 .NET 6+ RandomNumberGenerator)產生 5~8 碼英數字元,確保同秒內大量並發不重複。using System;
using System.Security.Cryptography;
using System.Text;
public static class OrderNoGenerator
{
private const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static readonly RandomNumberGenerator Rng = RandomNumberGenerator.Create(); // .NET Core 3.0+
///
/// 產生指定長度的密碼級隨機字串
///
public static string GetRandomSuffix(int length = 8)
{
if (length < 1) throw new ArgumentOutOfRangeException(nameof(length));
var bytes = new byte[length];
Rng.GetBytes(bytes);
var sb = new StringBuilder(length);
foreach (byte b in bytes)
sb.Append(Chars[b % Chars.Length]);
return sb.ToString();
}
///
/// 產生唯一訂單編號:yyyyMMddHHmmss + 隨機尾碼
///
public static string Generate(bool includeMilliseconds = false, int suffixLength = 8)
{
string timePart = DateTime.Now.ToString(includeMilliseconds ? "yyyyMMddHHmmssfff" : "yyyyMMddHHmmss");
return timePart + GetRandomSuffix(suffixLength);
}
}
for (int i = 0; i < 10; i++)
{
// 含毫秒 + 8 碼隨機
Console.WriteLine(OrderNoGenerator.Generate(true, 8));
// 無毫秒 + 6 碼隨機(較短)
Console.WriteLine(OrderNoGenerator.Generate(false, 6));
}
20240518143022123Xk9pQ2z
20240518143022aB3f
20240518143022456Yz8mN1q
20240518143022dE7h
...
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 時間戳 + RNGCryptoServiceProvider | 可排序、不可預測、長度固定 | 極高併發(萬級/秒)理論仍有極微機率衝突 | 一般電商訂單、物流單號 |
| 時間戳(毫秒) + Interlocked.Increment 計數器 | 同進程內絕對不重複、效能極高 | 跨進程/分散式部署需額外協調 | 單體高併發服務 |
| Snowflake 演算法 (64-bit Long) | 分散式唯一、趨勢遞增、數值型索引極快 | 需部署機器 ID、時鐘回撥處理 | 微服務、分散式系統主鍵 |
| Guid.NewGuid().ToString("N") | 無需協調、標準庫支援 | 32 碼、無序、索引碎片化嚴重 | 非索引欄位、臨時 Token |
BIGINT 儲存,避免字串比較開銷。// RandomNumberGenerator 為靜態單例,執行緒安全
private static readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
public static string GetRandomSuffix(int length)
{
Span<byte> bytes = stackalloc byte[length];
_rng.GetBytes(bytes);
return string.Create(length, bytes, (chars, b) => {
for (int i = 0; i < length; i++)
chars[i] = Chars[b[i] % Chars.Length];
});
}
採用「時間戳 + 密碼級隨機」策略,即能以極低複雜度產出兼具業務可讀性與技術唯一性的訂單編號,滿足絕大多數中小型系統需求;若進入分散式高併發領域,再引入 Snowflake 或 ULID 等演算法即可平滑升級。