人妻夜夜爽天天爽三区丁香花-人妻夜夜爽天天爽三-人妻夜夜爽天天爽欧美色院-人妻夜夜爽天天爽免费视频-人妻夜夜爽天天爽-人妻夜夜爽天天

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

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

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

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

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

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

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

例如,這是 Message 表的結構定義:

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")
);

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

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

Chats 項目的重構是一項系統性工程,數據庫的大規模重構只是其中關鍵環節之一。實際上,軟件重構是一個持續迭代的過程,貫穿于項目的整個生命周期。

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

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

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

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

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

  • 節省存儲空間:雖然單個 Guid 僅比整數 ID 多幾個字節,但當數據量累積到百萬、千萬甚至億級別時,Guid 額外占用的存儲空間將非常可觀。對于長期運行的應用,節省存儲空間也意味著降低了硬件成本。

當然,從 Guid 遷移到自增 ID 并非一帆風順,最大的挑戰在于數據遷移的復雜性。我們需要編寫嚴謹的數據遷移腳本,確保數據遷移過程中數據的一致性和完整性不受破壞。同時,還需要仔細評估遷移可能帶來的業務影響,例如外鍵關聯的更新,以及應用程序代碼的調整。幸運的是,正如前面提到的,Chats 項目已經積累了多次數據庫遷移的經驗,這為我們這次從 Guid 到自增 ID 的遷移奠定了堅實的基礎。

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

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

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

通過這樣的映射,我們可以在數據遷移過程中,將舊的 Guid 主鍵平滑地轉換為新的自增 ID,并確保數據關聯關系的正確性。

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

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

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

此外,從用戶體驗的角度來看,連續的數字 ID 也顯得不夠專業和優雅。用戶可能會覺得這些 ID 過于簡單和隨意,與他們對現代聊天應用的期望不符。

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

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

/// <summary>
/// 加密數據結構為 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);
}

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

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

其中,初始化向量 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 參數
    byte[] salt = new byte[16];
    using Rfc2898DeriveBytes rfc2898DeriveBytes = new(idHasherPassword, salt, iterations, HashAlgorithmName.SHA256);
    return rfc2898DeriveBytes.GetBytes(keyLength);
}

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

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

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

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

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

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

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

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

細心的朋友可能已經注意到,由于我的輸入長度為 4 字節或 8 字節(分別對應 int32 和 int64 類型的 ID),AES CBC 加密后的輸出長度固定為 16 字節(但前端代碼額外增加了一個字節作為版本號前綴,固定為 0)。而一個 Guid 的長度恰好也是 16 字節。因此,只需將 Base64Url 序列化方式替換為 Guid 序列化,即可輕松將加密 ID 轉換為 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 算法,而不是現在更流行的 AES GCM 算法呢?這又可以展開一篇長文討論。簡單來說:

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

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

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

// 示例密鑰和 nonce(通常 nonce 應該是隨機生成的)
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));
// 加密連續的整數 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();
}

輸出結果如下:

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 的部分,可以發現它們之間只有極小的差異(只有一個字節不同)。

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

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

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

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

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

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

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

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

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

總結與展望

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

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

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

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

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

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

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

Sdcb Chats 的主要特性包括:

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

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

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

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

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

轉自https://www.cnblogs.com/sdcb/p/18691585/sdcb-chats-id-in-url



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

主站蜘蛛池模板: 精品国产人成亚洲区 | 91日韩天堂一区二区二区 | 麻豆小偷闯空门巧遇空 | 在线看片福利无码青青 | 精品国内自产拍在线视频 | 国产午夜毛片v一区二区三区 | 国产真实伦在线观看 | 久久国产亚洲精品美女久久久久 | 94色94色永久网站 | 黑人狂躁日本妞无码A片视频 | 国产av无码专区亚洲av琪琪 | 无码日韩精品一区二区免费 | 天美传媒mv免费观看完整视频 | 成人免费一级纶理片 | 91在线观看国产 | 国产精品亚洲精品久久国语 | av天堂永久| 国产欧美日韩精品a在线观看 | 久久无码人妻一区二 | 波多野成人 | 四虎影视永久免费观看在线 | 精品泰妻少妇嫩草av无码专区高清一区二区三区四区五区六区 | 国产av无码专区亚洲精品 | av在线观看网站免费 | 97人妻夜夜爽一区二区 | 国产精品亚洲精品在线观看 | 欧美精品成人一区二区在线观看 | 久久亚洲av无码精品天天 | 成人免费毛片一区二区三区 | 麻豆精品人妻一区二区三区蜜桃 | 日本高清不卡码无码v亚洲 日本高清不卡免费 | 东北丰满熟女 | 国产精品无码AV在线观小说 | 日韩精品无码免费网站 | 色偷偷色偷偷色偷偷在线视频 | 少妇无码吹潮久久精品AV网站 | 久久青草国产免费频观 | 国产精品免费aⅴ片 | 国产成a人亚洲精v品无码樱花 | 日本免费一区二区三区最新vr | 久久久久久亚洲精品首页 |