vivo 企業(yè)云盤服務端實現簡介
一、背景
vivo 企業(yè)云盤是一個企業(yè)級文件數據管理服務,解決辦公數據的存儲、共享、審計等文件管理需求;同時便于團隊成員快速共享、管理文件,幫助集中管理企業(yè)數字資產,提升辦公效率,實現內部數據資源的共享以及與外部客戶之間的文件安全交換。
二、功能介紹
目前 vivo 企業(yè)云盤有 3 個空間:個人空間,團隊空間和備份空間。
2.1 個人空間
個人空間用于存儲用戶個人的文件數據,其他用戶不可見;容量默認為 100GB。個人空間支持文件的分享、下載、移動、重命名、星標、機房下載和刪除操作,如下圖所示:
2.2 團隊空間
團隊空間用于多人協作,團隊中可容納多名成員,每個成員都可以向團隊空間中上傳文件并與其他人共享這些文件,也可以下載其他人上傳到該團隊空間的文件;團隊空間沒有容量限制。
用戶可以在如下位置創(chuàng)建團隊空間:
團隊空間的創(chuàng)建者默認為該空間的管理員,管理員可以在左邊菜單欄中的團隊空間下看到“團隊設置”和“成員管理”,在“團隊設置”頁可以修改該團隊空間的名稱和團隊描述信息:
在“成員管理”頁可以添加成員并修改已有成員的權限:
2.3 合作伙伴
團隊空間中除了內部員工還可以加入外部合作伙伴,管理員可在如下頁面申請合作伙伴賬號:
點擊“新增”后在彈出的“申請外部用戶賬號”頁填寫合作伙伴相關信息即可提交直接上級領導審批,審批通過后會在該團隊空間中生成一個合作伙伴賬號,賬號及初始密碼會以郵件形式發(fā)送到合作伙伴郵箱,合作伙伴登錄后即可上傳文件或下載分享給他的文件。
出于數據安全的考慮,合作伙伴無法看到團隊空間中內部員工上傳的文件,只能看到自己上傳的文件以及分享給他的文件。
管理員可以在”成員管理“頁禁用合作伙伴賬號:
2.4 備份空間
備份空間用于備份用戶本地電腦上的文件。目前企業(yè)云盤網頁端只能查看已有的備份策略,新建備份策略需要在企業(yè)云盤客戶端進行;用戶可以在企業(yè)云盤網頁端右上角的“客戶端下載”下載企業(yè)云盤客戶端:
在客戶端的“備份同步”頁點擊“新增備份”,然后在彈出的對話框中選擇想要備份的本地文件夾即可創(chuàng)建備份記錄:
企業(yè)云盤客戶端將按用戶設置的頻率將指定文件夾下的文件上傳到對象存儲以實現文件備份;對于實時備份,企業(yè)云盤客戶端會每 3 分鐘掃描一次本地文件夾,并與遠程的文件進行對比,將新增的文件上傳到對象存儲。
三、功能實現
企業(yè)云盤的存儲分為元數據和對象存儲兩部分,元數據存儲使用的是 MySQL,保存的是用戶,群組以及文件等實體的元數據,文件的實際數據是以對象的形式保存在對象存儲中。企業(yè)云盤架構如下:
下面介紹一下各個功能是如何實現的:
3.1 用戶認證鑒權
企業(yè)云盤在用戶的身份驗證中使用了非對稱加密,前端持有一個公鑰,后端持有一個私鑰,用戶登錄時,前端首先獲取瀏覽器指紋 webFinger,同時生成一個隨機數種子 seed,然后用公鑰計算出一個特征字符串 RSA(webFinger+seed),然后將此字符串放入請求 header 中的 finger 字段,傳遞給服務端;另外企業(yè)云盤接入了 uuc 單點登錄系統,uuc 登錄成功后會在請求的 cookie 字段中放置 uuc-token 和 uuc-uuid,這兩個值也會傳給后端。
服務端收到登錄請求后,先使用 cookie 中的 uuc-token 以及 uuc-uuid 調用 uuc 接口查詢得到用戶 uid, 然后嘗試從 user 表中查詢用戶信息,如果查詢不到那么說明用戶是第一次登錄企業(yè)云盤,那么服務端會從 uuc 獲取用戶信息并存儲在 user 表中;然后服務端利用私鑰解密登錄請求中的特征字符串,得到 webFinger,再根據 webFinger + 當前時間 + uid 進行 AES 加密得到一個字符串 clouddisk-token,并將 clouddisk-token 放置在 cookie 中,返回給客戶端。在發(fā)送后續(xù)請求時,客戶端需要將 clouddisk-token 保持在 cookie 中。
在后續(xù)請求中,客戶端以同樣的方式生成 finger,并且在請求中攜帶 clouddisk-token;服務端接收到請求后,將 clouddisk-token 進行AES解密,獲取 uid + 時間 + webFinger,同時服務端根據自身持有的私鑰,對 header 中的 finger 解密,獲取此 finger 對應的 webFinger,與解密 token 得到的 webFinger 對比,如果相等,則驗證通過。以上過程如下圖所示:
團隊空間的數據保存在 groups 表中,該表會記錄團隊名稱、創(chuàng)建人等信息;用戶與團隊空間的歸屬關系保存在 group_usrs 表中,該表會記錄每個團隊空間有哪些用戶,以及這些用戶在團隊空間中的權限。
在個人空間中用戶對文件有最高權限,可以任意操作;當用戶操作的文件屬于某個團隊空間時前端會在請求中攜帶 group_id,服務端會根據 group_id 查詢 group_usrs 表,從而獲取該用戶在該團隊空間中的權限,進而判斷用戶是否有權限執(zhí)行相應操作。
3.2 文件上傳
用戶可以通過點擊頁面的上傳按鈕然后選擇本地文件或拖拽文件/文件夾到企業(yè)云盤頁面的方式上傳文件,除此之外開啟備份策略時也會調用上傳接口;用戶發(fā)起上傳后,前端會判斷文件大小,如果在 10MB 以內則直接上傳,否則,對于備份的文件將文件按 10MB 大小分片進行分片上傳,其他文件按 5MB 進行分片上傳。
所有文件的元數據都保存在 files 表中,該表會記錄文件名、文件路徑、文件所在空間、文件數據在對象存儲中的 key、文件所屬用戶等信息;所有文件夾的元數據都保存在 folder 表中,該表會記錄文件夾的名稱、路徑、文件夾所在空間、文件夾所屬用戶等信息。
3.2.1 小文件上傳
小文件上傳的邏輯如下:
- 查數據庫獲取用戶及其所在空間的空間信息;
- 空間用量校驗;
- 查找文件夾,如果文件夾不存在則新建文件夾;
- 查找文件,構造新 files 記錄:如果文件不存在,則使用原始文件名;如果文件已存在,則在文件名后面拼接序號以區(qū)別于原文件;
- 上傳文件數據到對象存儲;
- 生成隨機字符串作為 file_mark,將第 4 步中的 files 記錄插入 files 表。
3.2.2 大文件上傳
大文件指采用分片方式上傳的文件,文件分片的信息保存在 multi 表中,multi 表會記錄分片對應的文件、上傳者、分片總數、當前分片編號、upload id 等信息。
大文件分片上傳分 3 個步驟:
start 階段
- 查數據庫獲取用戶及所在空間信息,認證鑒權;
- 判斷文件是否已經存在;
- 查找文件夾,如果文件夾不存在則新建文件夾;
- 查找文件,構造新 files 記錄:如果文件不存在,則使用原始文件名;如果文件已存在,則在文件名后面拼接序號以區(qū)別于原文件;
- 從對象存儲獲取用于分片上傳的 upload id;
- 生成隨機字符串作為 file_mark,將第 4 步中的 files 記錄插入 files 表;
- 將分片記錄插入 multi 表;
- 將 upload id 返回給客戶端,用于后續(xù)關聯分片;將 file_mark 返回給客戶端,用于后續(xù)關聯文件。
upload 階段
- 查數據庫獲取用戶及所在空間信息,認證鑒權;
- 通過 file_mark 獲取文件信息;
- 通過 upload id 獲取文件的分片信息;
- 為當前分片生成 multi 表記錄;
- 將當前分片數據上傳到對象存儲;
- 將第 4 步中的 multi 記錄插入 files 表。
complete 階段
- 查數據庫獲取用戶及所在空間信息,認證鑒權;
- 通過 file_mark 獲取文件信息;
- 通過 upload id 獲取文件的分片信息;
- 通知對象存儲進行分片合并操作;
- 刪除該文件所有分片記錄;
- 更新目錄用量及文件狀態(tài)。
3.2.3 元數據與對象的對應
以下是使用對象存儲 SDK 從對象存儲獲取對象的示例代碼:
params := &s3.GetObjectInput{
Bucket: aws.String("BucketName"), // bucket名稱
Key: aws.String("ObjectKey"), // object key
}
resp, err := client.GetObject(params)
if err != nil{
panic(err)
}
//讀取返回結果中body的前20個字節(jié)
b := make([]byte, 20)
n, err := resp.Body.Read(b)
fmt.Printf("%-20s %-2v %v\n", b[:n], n, err)
左右滑動查看完整代碼
可以看到為了從對象存儲獲取對象只需要提供一個桶名(bucket name)和鍵名(object key)即可。桶名信息在配置文件中,服務端啟動后即會加載到內存中;object key 是通過 “用戶工號 + 路徑 + 時間戳 + _ + 文件名” 格式拼接成的字符串。
例如:
用戶 11*****9 在 2023-12-19 14:15:40 將文件 test.txt 上傳到個人空間中 /a/b/c/ 目錄下,那么這個文件對應的 object key 就是
11*****9/a/b/c/2023-12-19T14:15:40+08:00_test.txt;
如果這個字符串長度小于 128 字節(jié)那么就用這個字符串作為文件的 object key。如果拼接后的字符串長度大于 128 字節(jié),那么服務端會先計算文件路徑的 md5 值,記為 md5(path),然后拼接字符串:用戶工號 + / + md5(path) + 時間戳 + _ + 文件名,該 object key 生成之后會存入 files 表的 path 字段。
3.2.4 外鏈上傳
企業(yè)云盤還支持通過外鏈將文件從 Linux 機器上傳到企業(yè)云盤。使用外鏈上傳需要先申請權限,申請通過后企業(yè)云盤頁面可以看到”機房上傳“按鈕:
點擊該按鈕會將命令行復制到剪切板,命令行格式如下:
file="在此輸入文件名稱!";curl -s -X PUT "http://******/clouddisk-prd/******?Expires=******&AWSAccessKeyId=******&Signature=******" -H "x-amz-acl: public-read" -H "x-amz-content-maxlength: 200000000000000000" -H "Content-Type: application/octet-stream" --data-binary "@$file";curl -s -X POST "pan-idc.vivo.xyz/api/file/sync" -H "clouddisk-token: ******" -H "finger: ******" -H "Content-Type: application/json" -H "path: ******" -H "hashname: ******" -H "filename: $file"
左右滑動查看完整代碼
將 “在此輸入文件名稱!” 部分修改為要上傳的文件名然后執(zhí)行命令行即可上傳文件。
該功能實現原理如下:
- 查數據庫獲取用戶及所在空間信息,認證鑒權;
- 判斷文件夾是否存在,不存在則返回錯誤;
- 生成外鏈。用戶點擊機房上傳時服務端會為文件構造 object key,首先拼接字符串:clouddisk_ + 用戶工號 + _ + 當前時間時間戳,然后計算該字符串的 SHA1 哈希值,記為 SHA(ut),然后拼接字符串 ”用戶工號 + 文件路徑 + / + SHA(ut)“ 作為將上傳的文件的 object key;然后用這個 object key 調用對象存儲 sdk 生成預簽名 URL 用于上傳,這個預簽名 URL 就是外鏈中第一個 curl 命令行請求的 URL。第二個 curl 用于調用企業(yè)云盤服務端接口將文件元數據寫入 MySQL,包括將 object key 寫入 files 表的 path 字段。
可以看到在用戶使用外鏈上傳文件時,時間戳起到了關聯文件數據與文件元數據的作用,因此用戶每次上傳都必須重新拷貝鏈接,而不能復用之前的鏈接,否則會導致已上傳的文件被覆蓋。
3.3 文件下載
用戶在企業(yè)云盤界面選中文件即可下載文件,流程如下:
- 查數據庫獲取用戶及所在空間信息,認證鑒權
- 判斷文件是否存在
- 用文件的元數據中的 path 作為 object key 調用對象 SDK 獲取文件的預簽名 URL
- 將預簽名 URL 返回給前端,前端根據鏈接下載文件
另外用戶也可以通過機房鏈接將文件下載到 Linux 的機器上:
或者獲取辦公網鏈接,該鏈接可以在辦公網下載文件;這兩個鏈接的獲取也是調用的下載文件的接口,只是為了方便在 Linux 系統上下載文件而在前面拼接了 wget。
四、總結
本文簡單介紹了 vivo 企業(yè)云盤的基本功能,并介紹了這些功能在服務端具體的實現原理,其中重點介紹了認證鑒權和文件的上傳下載。希望讀者閱讀后對 vivo 企業(yè)云盤能有更深入的了解,也希望本文能在應用的認證鑒權及文件的上傳下載邏輯方面對讀者有所啟發(fā)。