超碰人人人人人,亚洲AV午夜福利精品一区二区,亚洲欧美综合区丁香五月1区,日韩欧美亚洲系列

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

Sdcb Chats 技術(shù)博客:數(shù)據(jù)庫 ID 選型的曲折之路 - 從 Guid 到自增 ID,再到 Guid

freeflydom
2025年2月5日 9:49 本文熱度 150

在軟件開發(fā)中,數(shù)據(jù)庫主鍵的選擇,Guid 還是自增整數(shù) ID,一直是一個備受開發(fā)者關(guān)注和討論的經(jīng)典話題。作為開源 ChatGPT 前端項目 Sdcb Chats 的開發(fā)者,我們在這個問題上也經(jīng)歷了一系列探索和演進,頗具代表性。Sdcb Chats 項目致力于打造一個強大、易用、可高度定制的 ChatGPT 及大語言模型前端,幫助用戶輕松連接、管理和使用各種主流的大語言模型。 總的來說,Sdcb Chats 的 ID 策略經(jīng)歷了從最初使用 Guid,到遷移至自增 ID,再到界面顯示加密 ID,最終又回歸到界面顯示 Guid 的過程,其中蘊含著許多有趣的思考和實踐經(jīng)驗,也反映了我們在項目迭代過程中對性能、安全和用戶體驗的不斷權(quán)衡與優(yōu)化。

第一階段 - 擁抱 Guid:“一步到位”的方案

項目初期,我的好友 G 負(fù)責(zé)總體系統(tǒng)設(shè)計,包括前端和數(shù)據(jù)庫。他果斷選擇了 Guid 作為主鍵方案。這在當(dāng)時是很自然的選擇,因為在普遍的技術(shù)認(rèn)知中,自增 ID 在分布式系統(tǒng)中似乎存在諸多不便,而 Guid(全局唯一標(biāo)識符)則被視為一種更現(xiàn)代、更通用的解決方案。Guid 的核心優(yōu)勢在于其全局唯一性,能夠在不同的數(shù)據(jù)庫和服務(wù)器之間獨立生成,無需擔(dān)心 ID 沖突問題。

以下是項目初期基于 PostgreSQL 設(shè)計的數(shù)據(jù)庫創(chuàng)建腳本鏈接(如果您感興趣可以查看):
https://github.com/sdcb/chats/blob/raw/prisma/postgresql/migrations/20240627111401_init/migration.sql

例如,這是 Message 表的結(jié)構(gòu)定義:

CREATE TABLE "ChatMessages" (
    "id" UUID NOT NULL,
    "userId" UUID NOT NULL,
    "chatId" UUID NOT NULL,
    "parentId" UUID,
    "chatModelId" UUID,
    "role" TEXT NOT NULL,
    "messages" TEXT NOT NULL,
    "inputTokens" INTEGER NOT NULL DEFAULT 0,
    "outputTokens" INTEGER NOT NULL DEFAULT 0,
    "inputPrice" DECIMAL(65,30) NOT NULL DEFAULT 0,
    "outputPrice" DECIMAL(65,30) NOT NULL DEFAULT 0,
    "duration" INTEGER NOT NULL DEFAULT 0,
    "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT "ChatMessages_pkey" PRIMARY KEY ("id")
);

然而,隨著項目的深入發(fā)展,Guid 方案的一些局限性逐漸顯現(xiàn)。首先,Guid 較長的長度和復(fù)雜的結(jié)構(gòu)在某些場景下給數(shù)據(jù)庫性能帶來了一定負(fù)擔(dān)。尤其是在數(shù)據(jù)量快速增長的情況下,索引體積增大,查詢速度變慢等問題開始凸顯。作為負(fù)責(zé)維護公司內(nèi)部 Chats 數(shù)據(jù)庫服務(wù)器的人,我注意到核心表 Chats 的索引碎片率持續(xù)偏高。為了避免性能下降,我不得不設(shè)置每日任務(wù),定期重建索引。這讓我開始認(rèn)真考慮對 Chats 項目的數(shù)據(jù)庫進行大規(guī)模重構(gòu)。

第二階段 - 性能至上:遷移至自增 ID

Chats 項目的重構(gòu)是一項系統(tǒng)性工程,數(shù)據(jù)庫的大規(guī)模重構(gòu)只是其中關(guān)鍵環(huán)節(jié)之一。實際上,軟件重構(gòu)是一個持續(xù)迭代的過程,貫穿于項目的整個生命周期。

在我看來,項目重構(gòu)如同軟件的自我革新,需要開發(fā)者具備“刀刃向內(nèi)”的勇氣和決心。當(dāng)我們審視代碼,發(fā)現(xiàn)不足之處,就如同在前進的道路上遇到了障礙。我們當(dāng)然可以選擇繞行,暫時規(guī)避問題,但這些技術(shù)債務(wù)會像隱患一樣潛伏下來,并在未來某個時刻影響系統(tǒng)的穩(wěn)定性和可維護性。特別是對于數(shù)據(jù)庫這種核心模塊,開發(fā)者往往出于謹(jǐn)慎,傾向于避免改動既有結(jié)構(gòu)和數(shù)據(jù)。但長此以往,問題會逐漸累積,最終侵蝕系統(tǒng)的健康。因此,正視并解決這些問題才是負(fù)責(zé)任的做法。

當(dāng)然,在 Chats 項目的重構(gòu)中,我也有著得天獨厚的優(yōu)勢。作為后端設(shè)計的主導(dǎo)者和核心開發(fā)者,我對系統(tǒng)的每一個細(xì)節(jié)都了如指掌。正所謂“船小好調(diào)頭”,即使是數(shù)據(jù)庫大規(guī)模遷移這樣的“大手術(shù)”,我也能快速決策、高效執(zhí)行。事實上,Chats 數(shù)據(jù)庫已經(jīng)經(jīng)歷過多次重要的數(shù)據(jù)遷移,各位可以通過項目倉庫中的數(shù)據(jù)庫遷移腳本了解詳情:https://github.com/sdcb/chats/tree/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/scripts/db-migration。

將主鍵從 Guid 切換為自增 int ID,最直接的好處就是性能的提升,具體體現(xiàn)在以下幾個方面:

  • 更小的索引尺寸和更快的查詢速度:相比 Guid,自增整數(shù) ID 在存儲空間上占用更少字節(jié),這意味著數(shù)據(jù)庫索引的體積也隨之減小。更小的索引意味著數(shù)據(jù)庫在執(zhí)行查詢時,特別是面對海量數(shù)據(jù)時,能夠更快地遍歷索引,從而顯著提升查詢速度。這對于像 Chats 這種消息量可能快速增長的應(yīng)用至關(guān)重要。

  • 減少索引碎片:Guid 的無序性導(dǎo)致在插入新數(shù)據(jù)時,索引頁分裂的概率大大增加,從而產(chǎn)生索引碎片。而自增整數(shù) ID 的順序遞增特性,可以保證新插入的數(shù)據(jù)大概率追加在索引的末尾,最大限度地減少索引頁分裂,降低索引碎片的產(chǎn)生。正如我在維護 Chats 數(shù)據(jù)庫時觀察到的,Guid 主鍵的表索引碎片問題尤為突出。切換到自增 ID 后,索引維護工作將大大簡化。(當(dāng)然,如果您的系統(tǒng)情況特殊,難以完全拋棄 Guid,也可以考慮使用 Uuid v7,它是一種基于時間戳的連續(xù)性 Guid,也能有效減少索引碎片)

  • 節(jié)省存儲空間:雖然單個 Guid 僅比整數(shù) ID 多幾個字節(jié),但當(dāng)數(shù)據(jù)量累積到百萬、千萬甚至億級別時,Guid 額外占用的存儲空間將非??捎^。對于長期運行的應(yīng)用,節(jié)省存儲空間也意味著降低了硬件成本。

當(dāng)然,從 Guid 遷移到自增 ID 并非一帆風(fēng)順,最大的挑戰(zhàn)在于數(shù)據(jù)遷移的復(fù)雜性。我們需要編寫嚴(yán)謹(jǐn)?shù)臄?shù)據(jù)遷移腳本,確保數(shù)據(jù)遷移過程中數(shù)據(jù)的一致性和完整性不受破壞。同時,還需要仔細(xì)評估遷移可能帶來的業(yè)務(wù)影響,例如外鍵關(guān)聯(lián)的更新,以及應(yīng)用程序代碼的調(diào)整。幸運的是,正如前面提到的,Chats 項目已經(jīng)積累了多次數(shù)據(jù)庫遷移的經(jīng)驗,這為我們這次從 Guid 到自增 ID 的遷移奠定了堅實的基礎(chǔ)。

例如,這段 339 行的 C# 數(shù)據(jù)庫遷移腳本(在 LINQPad 中編寫):
https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/scripts/db-migration/2024/20240902-db-migration.linq

在遷移腳本中,我們使用了類似 GuidInt32Mapping 這樣的類來維護 Guid 和自增 ID 之間的映射關(guān)系:

public class GuidInt32Mapping
{
	int _nextId = 1;
	Dictionary<Guid, int> _mapping = new();
	public void Add(Guid guid)
	{
		_mapping.Add(guid, _nextId++);
	}
	public int this[Guid guid]
	{
		get
		{
			return _mapping[guid];
		}
	}
}

通過這樣的映射,我們可以在數(shù)據(jù)遷移過程中,將舊的 Guid 主鍵平滑地轉(zhuǎn)換為新的自增 ID,并確保數(shù)據(jù)關(guān)聯(lián)關(guān)系的正確性。

第三階段 - 安全升級:界面 ID 加密

完成數(shù)據(jù)庫主鍵從 Guid 到自增 ID 的遷移后,我們又面臨了新的問題:如何在用戶界面上安全地展示 ID。最初,我們直接沿用了數(shù)據(jù)庫的自增 ID,將其暴露在前端界面和 API 接口中。然而,這種做法很快引發(fā)了一些安全性和用戶體驗方面的問題。

例如,當(dāng)創(chuàng)建一個新的聊天會話時,界面 URL 可能會顯示為 https://chats-dev.starworks.cc:88/#/1,其中的 1 代表系統(tǒng)中第一個聊天。當(dāng)您新建聊天時,URL 就會變?yōu)?nbsp;https://chats-dev.starworks.cc:88/#/2。以此類推,ID 會順序遞增。這種連續(xù)的數(shù)字 ID 存在潛在的安全風(fēng)險:任何人都可以通過簡單地修改 URL 中的數(shù)字,嘗試猜測和訪問其他聊天會話(當(dāng)然,后端服務(wù)會進行嚴(yán)格的權(quán)限驗證)。更重要的是,這種連續(xù)的數(shù)字 ID 容易暴露系統(tǒng)的一些敏感信息,例如通過 ID 的大致范圍,外界可以推測出系統(tǒng)用戶和聊天會話的大概規(guī)模,這在某些場景下是我們不希望泄露的。

此外,從用戶體驗的角度來看,連續(xù)的數(shù)字 ID 也顯得不夠?qū)I(yè)和優(yōu)雅。用戶可能會覺得這些 ID 過于簡單和隨意,與他們對現(xiàn)代聊天應(yīng)用的期望不符。

為了解決這些問題,我們決定對界面上顯示的 ID 進行加密處理。

最初,我使用了這段 C# 代碼來實現(xiàn)整數(shù) ID 的加密:
https://github.com/sdcb/chats/blob/r-287/src/BE/Services/UrlEncryption/Utils.cs#L22-L36

/// <summary>
/// 加密數(shù)據(jù)結(jié)構(gòu)為 base64url([1:version + encryptedData])
/// </summary>
public static string Encrypt(ReadOnlySpan<byte> input, byte[] key, byte[] iv)
{
    using Aes aes = Aes.Create();
    aes.Key = key;
    byte[] encryptedIdBytes = aes.EncryptCbc(input, iv);
    byte[] encryptedIdBytesWithIV = new byte[1 + encryptedIdBytes.Length];
    encryptedIdBytesWithIV[0] = 0; // 版本號
    Array.Copy(encryptedIdBytes, 0, encryptedIdBytesWithIV, 1, encryptedIdBytes.Length);
    return WebEncoders.Base64UrlEncode(encryptedIdBytesWithIV);
}

在這個實現(xiàn)中,整數(shù) ID 首先被轉(zhuǎn)換為小端序的字節(jié)數(shù)組,然后使用 AES 算法的 CBC 模式進行加密。加密后的數(shù)據(jù)被編碼為 Base64 URL 格式,以便在 URL 中安全傳輸。最終,URL 可能呈現(xiàn)為如下形式:
https://chats-dev.starworks.cc:88/#/AFo-sKz8LTPvDBMvau1dKfA

通過這種加密方式,我們不僅提升了系統(tǒng)的安全性,也改善了用戶體驗。加密后的 ID 看起來更加復(fù)雜和專業(yè),有效避免了簡單數(shù)字序列帶來的潛在問題。

其中,初始化向量 IV 的生成方式如下。我定義了一個 EncryptionPurpose 枚舉:

// https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/BE/Services/UrlEncryption/EncryptionPurpose.cs#L4
public enum EncryptionPurpose
{
    ChatId,
    FileId,
    MessageId,
    ChatGroupId,
    ChatShareId,
}

https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/BE/Services/UrlEncryption/UrlEncryptionService.cs#L15

foreach (EncryptionPurpose purpose in Enum.GetValues<EncryptionPurpose>())
{
    _ivs[purpose] = Utils.GenerateIdHasherKey(idHasherPassword + purpose, keyLength: 16, iterations: 200);
}

https://github.com/sdcb/chats/blob/ebefd93cb187961f8c69dcf04163433ce753a5f3/src/BE/Services/UrlEncryption/Utils.cs#L8

public static byte[] GenerateIdHasherKey(string idHasherPassword, int keyLength, int iterations)
{
    // PBKDF2 參數(shù)
    byte[] salt = new byte[16];
    using Rfc2898DeriveBytes rfc2898DeriveBytes = new(idHasherPassword, salt, iterations, HashAlgorithmName.SHA256);
    return rfc2898DeriveBytes.GetBytes(keyLength);
}

每個枚舉值代表一種加密目的。代碼會根據(jù)不同的目的生成不同的 IV。這樣做的目的是確保即使同一個整數(shù) ID 在不同的上下文(例如 ChatId 和 FileId)中被加密,也會產(chǎn)生不同的加密結(jié)果。這種做法提升了安全性和靈活性,我們可以在不同的場景下復(fù)用相同的加密機制,而無需擔(dān)心 ID 重復(fù)或沖突。

可能有朋友會問,為什么不使用隨機 IV,并將 IV 添加到加密后的 ID 中,這樣安全性不是更高嗎?這主要是基于以下兩點考慮:

首先,前端的某些計算邏輯依賴于穩(wěn)定的 ID。在我們的前端代碼中,特別是在聊天會話管理和消息渲染方面,我們大量使用了基于 ID 的緩存和狀態(tài)管理機制。例如,當(dāng)用戶在一個聊天窗口中滾動瀏覽消息時,前端會根據(jù)消息的 ID 來渲染消息之間的父子關(guān)系。如果使用隨機 IV,即使是同一個聊天會話,在不同的時間或不同的上下文中被加密,生成的 ID 都會不同。這會導(dǎo)致前端緩存失效,狀態(tài)管理混亂,最終引發(fā)難以追蹤的 bug。想象一下,用戶明明還在同一個聊天中,但由于 ID 變化,前端卻認(rèn)為這是一個新的聊天,之前的消息緩存全部失效,這無疑會造成糟糕的用戶體驗。為了保證前端邏輯的穩(wěn)定性和可預(yù)測性,我們需要確保在同一上下文中,同一個整數(shù) ID 加密后的結(jié)果始終一致。

其次,不固定的 IV 會顯著增加 ID 的長度。如果將隨機生成的 IV 也附加到加密后的 ID 中,最終的 ID 長度會大大增加。AES 算法的 IV 通常為 16 字節(jié),轉(zhuǎn)換為 Base64 URL 編碼后,會增加約 21 個字符的長度(16 * 4 / 3 ≈ 21.3)。原本加密后的 ID 已經(jīng)比純數(shù)字 ID 長了不少,如果再加上 20 多個字符的 IV,整個 ID 會顯得非常臃腫,尤其是在 URL 中展示時,既不美觀,也增加了 URL 的長度負(fù)擔(dān)。我們希望在保證安全性的前提下,盡可能保持 ID 的簡潔易用。

因此,綜合考慮前端的穩(wěn)定性和 ID 長度,我們最終選擇了使用基于 EncryptionPurpose 枚舉的固定 IV 方案。這樣既保證了在不同上下文中加密結(jié)果的差異性,又避免了隨機 IV 帶來的不穩(wěn)定性和長度增加問題。

第四階段 - 兼顧用戶感知:界面顯示為 Guid(當(dāng)前方案)

經(jīng)過一段時間的實際運行,我們意識到,雖然加密 ID 解決了安全性問題,但在某些場景下,用戶仍然希望看到一種更具辨識度的 ID 格式。因此,我們最終決定在界面上將 ID 顯示為 Guid 格式。

這種做法的優(yōu)點在于,Guid 格式的 ID 看起來更加隨機和復(fù)雜,更符合用戶對現(xiàn)代應(yīng)用的普遍認(rèn)知,同時也有效避免了直接暴露自增 ID 的問題。在具體實現(xiàn)上,我們將加密后的 ID 轉(zhuǎn)換為 Guid 格式進行展示。這樣一來,用戶在界面上看到的 ID 既安全又專業(yè)。

細(xì)心的朋友可能已經(jīng)注意到,由于我的輸入長度為 4 字節(jié)或 8 字節(jié)(分別對應(yīng) int32 和 int64 類型的 ID),AES CBC 加密后的輸出長度固定為 16 字節(jié)(但前端代碼額外增加了一個字節(jié)作為版本號前綴,固定為 0)。而一個 Guid 的長度恰好也是 16 字節(jié)。因此,只需將 Base64Url 序列化方式替換為 Guid 序列化,即可輕松將加密 ID 轉(zhuǎn)換為 Guid 形式:

https://github.com/sdcb/chats/blob/r-407/src/BE/Services/UrlEncryption/Utils.cs#L50-L60

    public static string Encrypt(ReadOnlySpan<byte> input, byte[] key, byte[] iv)
    {
        using Aes aes = Aes.Create();
        aes.Key = key;
        byte[] encryptedIdBytes = aes.EncryptCbc(input, iv);
        return Serialize(encryptedIdBytes);
    }
    private static string Serialize(byte[] encryptedIdBytes)
    {
        if (encryptedIdBytes.Length == 16)
        {
            return new Guid(encryptedIdBytes).ToString();
        }
        else
        {
            return WebEncoders.Base64UrlEncode(encryptedIdBytes);
        }
    }

可能有朋友會進一步追問,為什么堅持使用 AES CBC 算法,而不是現(xiàn)在更流行的 AES GCM 算法呢?這又可以展開一篇長文討論。簡單來說:

首先,AES GCM 的隨機性高度依賴于 nonce 的唯一性。Nonce(Number used once)是一個一次性使用的隨機數(shù),在 AES GCM 中扮演著至關(guān)重要的角色,類似于 AES CBC 中的 IV(Initialization Vector,初始化向量)。如果 nonce 在多次加密中重復(fù)使用,尤其是在加密序列化的、遞增的 ID 時,AES GCM 的安全性會大打折扣,甚至可能暴露出加密模式的規(guī)律性。

在我們的場景中,雖然我們?yōu)槊糠N EncryptionPurpose 生成了不同的 IV(在 AES CBC 中)或者說 nonce(如果我們使用 AES GCM),但如果我們在同一個 EncryptionPurpose 下連續(xù)加密遞增的整數(shù) ID,例如聊天 ID 1, 2, 3...,并且每次都使用相同的 nonce,那么 AES GCM 的輸出結(jié)果就會呈現(xiàn)出可預(yù)測的模式。更具體地說,后一個加密后的 ID 很可能與前一個加密后的 ID 存在某種簡單的數(shù)學(xué)關(guān)系,比如僅僅是最后幾個字節(jié)的差異。這種可預(yù)測性對于安全性來說是致命的,攻擊者可能會利用這種規(guī)律來猜測或破解 ID。

為了更直觀地說明問題,請看以下 C# 代碼示例:

// 示例密鑰和 nonce(通常 nonce 應(yīng)該是隨機生成的)
byte[] key = new byte[16]; // 128-bit key
byte[] nonce = new byte[12]; // 96-bit nonce
RandomNumberGenerator.Fill(key);
RandomNumberGenerator.Fill(nonce);
Console.WriteLine("Nonce: " + BitConverter.ToString(nonce));
// 加密連續(xù)的整數(shù) ID
for (int id = 1; id <= 5; id++)
{
	byte[] plaintext = BitConverter.GetBytes(id);
	using AesGcm aesGcm = new AesGcm(key, tagSizeInBytes: 16);
	byte[] ciphertext = new byte[plaintext.Length];
	byte[] tag = new byte[16]; // 128-bit tag
	aesGcm.Encrypt(nonce, plaintext, ciphertext, tag);
	Console.WriteLine($"ID: {id}");
	Console.WriteLine("Ciphertext: " + BitConverter.ToString(ciphertext));
	Console.WriteLine("Tag: " + BitConverter.ToString(tag));
	Console.WriteLine();
}

輸出結(jié)果如下:

Nonce: 58-8F-39-89-8C-AD-45-44-F8-C6-F0-FC
ID: 1
Ciphertext: AB-DE-99-E8
Tag: 54-C5-52-BE-3A-0E-E9-9F-EF-7F-CB-F5-09-31-7A-61
ID: 2
Ciphertext: A8-DE-99-E8
Tag: 0D-18-B3-1B-0B-07-C7-0C-90-C9-F7-40-D1-DC-26-B3
ID: 3
Ciphertext: A9-DE-99-E8
Tag: 3A-53-EC-78-1B-FF-22-82-45-A4-1C-D3-99-87-12-FD
ID: 4
Ciphertext: AE-DE-99-E8
Tag: BE-A3-70-51-69-15-9A-2A-6F-A5-8E-2B-60-06-9F-17
ID: 5
Ciphertext: AF-DE-99-E8
Tag: 89-E8-2F-32-79-ED-7F-A4-BA-C8-65-B8-28-5D-AB-59

請注意觀察 Ciphertext 的部分,可以發(fā)現(xiàn)它們之間只有極小的差異(只有一個字節(jié)不同)。

相比之下,AES CBC 雖然也依賴 IV,但即使 IV 固定,只要密鑰安全,其加密結(jié)果的隨機性依然能得到較好的保證。尤其是在我們使用了填充模式(Padding)的情況下,即使輸入數(shù)據(jù)存在一定的規(guī)律性,也能有效地隱藏這種規(guī)律。

其次,AES GCM 的輸出長度會顯著增加,難以適配 Guid 格式。AES GCM 在提供加密功能的同時,還提供了數(shù)據(jù)完整性校驗功能,這是通過附加一個認(rèn)證標(biāo)簽(Authentication Tag,簡稱 Tag)來實現(xiàn)的。這個 Tag 通常是 12 到 16 字節(jié),用于驗證數(shù)據(jù)的完整性和真實性,防止數(shù)據(jù)被篡改。除了 Tag 之外,AES GCM 還需要一個顯式的 nonce 作為輸入。對于我們來說,nonce 至少需要 12 字節(jié)才能保證足夠的安全性。

這意味著,如果我們使用 AES GCM 加密一個 4 字節(jié)的 int32 ID,最終的輸出長度將至少是:4 字節(jié)(密文) + 12 字節(jié)(nonce) + 12 字節(jié)(最小 Tag 大?。?= 28 字節(jié)。即使我們加密一個 8 字節(jié)的 int64 ID,輸出長度也會超過 32 字節(jié)。這樣的長度,無論如何都無法直接塞到一個 16 字節(jié)的 Guid 中。而且,為了將 nonce 和 tag 都塞進去,我們勢必需要設(shè)計更復(fù)雜的序列化方案,這會增加前端和后端的處理復(fù)雜度,也可能導(dǎo)致 ID 格式的不統(tǒng)一,例如一部分 ID 是 Guid,一部分是更長的 Base64 編碼字符串,這會給前端開發(fā)帶來額外的困擾。

我們之所以最終選擇將加密后的 ID 展示為 Guid 格式,一個重要的考量就是希望保持 ID 的統(tǒng)一性和簡潔性。Guid 作為一個 16 字節(jié)的固定長度標(biāo)識符,在很多場景下都非常方便使用和處理。如果我們?yōu)榱俗非?AES GCM 的“更高安全性”而犧牲了 ID 的簡潔性和統(tǒng)一性,反而可能會得不償失。

最后,AES GCM 的額外安全優(yōu)勢在我們的應(yīng)用場景下并非不可或缺。AES GCM 最主要的優(yōu)勢在于它提供的認(rèn)證加密(Authenticated Encryption)功能,即在加密的同時,也保證了數(shù)據(jù)的完整性和真實性。這意味著,如果數(shù)據(jù)在傳輸過程中被篡改,解密時會立即發(fā)現(xiàn)并報錯。這種認(rèn)證功能對于一些對數(shù)據(jù)完整性要求極高的場景非常重要,例如金融交易、電子簽名等。

然而,在我們的 Chats 應(yīng)用中,我們對 ID 的安全性需求主要集中在防止惡意猜測和未經(jīng)授權(quán)的訪問,而不是防止數(shù)據(jù)篡改。即使加密后的 ID 在傳輸過程中被篡改,最終解密出來的 ID 也大概率無法在數(shù)據(jù)庫中找到對應(yīng)的記錄,或者即使找到了,后續(xù)的業(yè)務(wù)邏輯也會進行權(quán)限驗證,確保用戶只能訪問自己擁有的聊天或消息。

更重要的是,即使我們使用 AES CBC,也并非完全沒有數(shù)據(jù)完整性驗證機制。首先,AES CBC 配合填充模式(例如 PKCS7 Padding)本身就提供了一定程度的完整性校驗。對于 int32 類型的 ID,AES CBC 加密后會生成 16 字節(jié)的密文,其中有 12 字節(jié)實際上是填充數(shù)據(jù)。如果密文被篡改,解密時填充校驗會失敗,從而可以檢測到數(shù)據(jù)損壞。雖然這種校驗強度不如 AES GCM 的 Tag 那么高,但也足以應(yīng)對一般的篡改嘗試。

其次,在我們的系統(tǒng)中,解密后的 ID 最終會用于數(shù)據(jù)庫查詢。即使攻擊者能夠繞過 AES CBC 的填充校驗,篡改了加密后的 ID,解密出來的錯誤 ID 在數(shù)據(jù)庫中大概率也找不到對應(yīng)的記錄。即使碰巧找到了記錄,我們也會在數(shù)據(jù)庫層面和業(yè)務(wù)邏輯層面進行多重權(quán)限驗證,確保數(shù)據(jù)的安全性。

因此,綜合考慮以上三點,我們最終權(quán)衡之后,仍然選擇了 AES CBC 算法。它在保證足夠安全性的前提下,能夠生成 16 字節(jié)的密文,完美適配 Guid 格式,并且實現(xiàn)相對簡單,性能也更優(yōu)。當(dāng)然,技術(shù)選型永遠(yuǎn)是一個不斷演進的過程,未來如果我們的安全需求發(fā)生變化,或者 AES GCM 在性能和易用性方面有了新的提升,我們也不排除會重新評估并切換到 AES GCM 的可能性。

總結(jié)與展望

回顧 Sdcb Chats 項目 ID 演進的四個階段,從最初擁抱 Guid 的“一步到位”,到為了性能考量轉(zhuǎn)向自增 ID,再到為了安全和體驗在界面上加密 ID,最終又回歸到使用 Guid 形式展示,這的確是一段曲折而又充滿思考的旅程。

在這個過程中,我們不斷地在性能、安全性、用戶體驗和開發(fā)效率之間權(quán)衡取舍。沒有一勞永逸的完美方案,只有在持續(xù)迭代和演進中,才能找到最適合當(dāng)前階段的最佳實踐。每一次看似“倒退”的改動,實際上都基于更深入的理解和更全面的考量。例如,從 Guid 到自增 ID 的轉(zhuǎn)變,是為了解決實際存在的數(shù)據(jù)庫性能瓶頸;而界面上從加密 ID 到 Guid 的回歸,則是在安全性得到保障的前提下,更好地滿足用戶對“現(xiàn)代感”和“專業(yè)性”的用戶感知。

這段經(jīng)歷也印證了軟件開發(fā)中一個重要的理念:沒有銀彈。技術(shù)選型需要結(jié)合具體的應(yīng)用場景和需求,持續(xù)地監(jiān)控和評估,并根據(jù)實際情況靈活調(diào)整。我們不能因為“大家都說 Guid 好”就盲目跟風(fēng),也不能因為“性能至上”就忽略安全性和用戶體驗。只有深入理解各種方案的優(yōu)缺點,才能做出最明智的選擇。

而 Sdcb Chats 項目的 ID 演進之路,也正是開源項目不斷迭代、持續(xù)進化的一個縮影。我們始終秉持著開放、務(wù)實的態(tài)度,積極擁抱變化,勇于嘗試新的技術(shù)方案,并不斷地從實踐中總結(jié)經(jīng)驗教訓(xùn)。

如果您對我們這曲折的 ID 選型故事,以及 Sdcb Chats 項目本身感興趣,歡迎繼續(xù)了解!

Sdcb Chats:一個強大的開源 ChatGPT 前端

我是開源項目 Sdcb Chats 的作者。Sdcb Chats 定位為一個強大且易于部署的 ChatGPT 前端,旨在幫助用戶輕松接入和管理各種主流的大語言模型。

Sdcb Chats 的主要特性包括:

  • 廣泛的大語言模型支持: 目前已支持 15 種不同的大語言模型提供商。只需配置簡單的 API Key 等連接信息,即可無縫切換和體驗來自不同廠商的強大 AI 能力。
  • 靈活的數(shù)據(jù)庫選擇: 支持 SQLite、SQL Server 和 PostgreSQL 三種數(shù)據(jù)庫,您可以根據(jù)自身需求和環(huán)境選擇最合適的數(shù)據(jù)庫方案。
  • 多樣化的部署方式: 提供 Docker 鏡像 部署方式,方便快捷地在各種容器環(huán)境中部署;同時提供多種操作系統(tǒng)的 二進制文件 下載,無需復(fù)雜的編譯過程,即可快速啟動和使用。
  • 完善的管理功能: 內(nèi)置 多用戶管理 功能,方便團隊協(xié)作使用;提供 Token 消耗統(tǒng)計 和 付費管理 功能,幫助您更好地控制和管理大語言模型的使用成本。

無論您是個人開發(fā)者、技術(shù)愛好者,還是企業(yè)用戶,Sdcb Chats 都能為您提供一個強大、靈活、易用的 ChatGPT 前端解決方案。

如果您覺得 Sdcb Chats 對您有所幫助,或者您認(rèn)同我們的技術(shù)理念和開源精神,請在 GitHub 上給我們一個 Star ?。您的支持是我們持續(xù)前進的最大動力!

GitHub 倉庫地址: https://github.com/sdcb/chats

希望這篇博客和項目介紹能幫助您對 Sdcb Chats 項目有更深入的了解。期待您的關(guān)注和參與,讓我們一起打造更優(yōu)秀的開源項目!

轉(zhuǎn)自https://www.cnblogs.com/sdcb/p/18691585/sdcb-chats-id-in-url



該文章在 2025/2/5 9:49:38 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved