本文檔描述了上傳文件到 PDS 的最佳實踐,您可以參考該文檔實現上傳文件功能。
PDS 文件類型
文件夾:目錄類型的文件,自身不承載任何物理數據,所以直接調用 PDS 創建文件接口即可。
文件:非目錄類型的文件,自身包含真實的文件數據,創建此類型文件涉及到文件上傳步驟,下面文檔主要針對此類型的文件進行上傳流程介紹。
文件上傳流程
流程簡述
PDS 的文件上傳分為以下三步:
調用 PDS CreateFile - 創建文件或文件夾接口初始化文件,PDS 會返回文件的元信息以及 HTTP 上傳地址。
上傳文件到第一步返回的 HTTP 上傳地址中。
調用 PDS CompleteFile - 完成文件上傳接口,完成上傳流程。
詳細流程
下面以上傳本地文件做一個簡單的示例。
1.創建文件
簡述:調用創建文件接口,初始化文件上傳。
核心參數設置:
設置上傳到哪個空間下:drive_id = "xxxx";
設置上傳到哪個目錄下:parent_file_id = "root",root 表示上傳到根目錄下;
設置文件名:name = "test.jpg";
設置文件類型:type = "file",file 表示文件,folder 表示目錄;
設置文件大小(本地文件真實大小,單位為字節):size = 13381200;
設置分片列表:part_info_list = [{"part_number":1},{"part_number":2},{"part_number":3}],這里要根據文件大小和客戶端定義的分片大小來計算。設置多個分片可以提高上傳成功率,還可以進行斷點續傳。示例里的文件大小約 13MB,客戶端可以將分片大小設置為 5MB,因此計算得出需要上傳三個分片。
請求 body 示例:
{
"drive_id":"xxxx",
"name":"test.jpg",
"parent_file_id":"root",
"part_info_list":[
{
"part_number":1
},
{
"part_number":2
},
{
"part_number":3
}
],
"size":13381200,
"type":"file"
}
返回 body 示例:
{
"parent_file_id":"root",
"part_info_list":[
{
"part_number":1,
"upload_url":"https://xxxx1"
},
{
"part_number":2,
"upload_url":"https://xxxx2"
},
{
"part_number":3,
"upload_url":"https://xxxx3"
}
],
"upload_id":"xxxxxx",
"rapid_upload":false,
"type":"file",
"file_id":"xxxxx",
"revision_id":"xxxxx",
"domain_id":"xxxx",
"drive_id":"xxxx",
"file_name":"test.jpg"
}
核心返回參數解析:
file_id: 云端給文件分配的唯一 id,在同一 drive 空間內,每個文件都有唯一的 file_id。
upload_id: 云端給本次上傳過程分配的 id,后續 complete 等流程都需要和該 upload_id 關聯。
part_info_list:云端給本次上傳過程分配的分片上傳地址列表,對應上行參數中的 part_info_list,云端會給每個分片分配一個上傳地址。
2.上傳文件
簡述:根據第一步返回的分片上傳地址,遍歷上傳每個分片,HTTP Method 為 PUT。
下面以 Java 代碼示例:
// 遍歷所有分片
for (PartInfo uploadPartInfo : partInfoList) {
// 計算分片在本地文件中的位置
int number = uploadPartInfo.getPartNumber();
long pos = (number - 1) * partSize;
long size = Math.min(length - pos, partSize);
byte[] partContent = new byte[(int) size];
// 從本地文件中讀取分片內容到內存中
RandomAccessFile randomAccessFile = new RandomAccessFile(localFile, "r");
randomAccessFile.seek(pos);
randomAccessFile.readFully(partContent, 0, (int) size);
randomAccessFile.close();
// 上傳分片
RequestBody body = RequestBody.create(null, partContent);
Request request = new Request.Builder()
.url(uploadPartInfo.getUploadUrl())
.header("Content-Length", String.valueOf(size))
.put(body)
.build();
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Response response = okHttpClient.newCall(request).execute();
// 判斷分片是否上傳成功
if (!response.isSuccessful()) {
System.out.println(response.body().string() + "\n");
Assert.fail("upload part failed, partNumber:" + number);
return "";
}
System.out.println("upload part success, partNumber:" + number);
}
3.完成文件上傳
簡述:當第二步中所有的分片都上傳完成后,調用完成文件上傳接口,完成上傳流程。
核心參數設置:
設置文件所在的空間 id:drive_id = "xxxx";
設置文件 id:file_id = "xxxx",file_id 填寫為第一步創建文件返回的 file_id;
設置上傳流程 id:upload_id = "xxxx",upload_id 填寫為第一步創建文件接口返回的 upload_id
請求 body 示例:
{
"drive_id":"xxxx",
"file_id":"xxx",
"upload_id":"xxxx"
}
接口返回 HTTP 狀態碼 200 后,表示文件已經上傳完成。
返回 body 示例:
{
"domain_id":"xxxx",
"drive_id":"xxxx",
"file_id":"xxxx",
"parent_file_id":"root",
"type":"file",
"file_extension":"jpg",
"name":"test.jpg",
"size":13381200,
"status":"available",
"content_hash":"xxxxx",
"created_at":"2023-01-16T11:55:12.166Z",
"updated_at":"2023-01-16T11:55:13.368Z"
}
斷點續傳
應用場景
用戶在上傳文件過程中,可以臨時暫停上傳文件,比如用戶只希望在 WIFI 場景下上傳文件,則可以在切換到非 WIFI 場景時暫停上傳,等切換回 WIFI 后可以從中斷點繼續上傳文件。
實現方式
前述提到客戶端可以在上傳文件時,對文件進行分片上傳。
以下面上傳為例:上傳 50MB 的文件,假設客戶端按照 5 MB 進行分片,則一共可以分成 10 個分片。
當客戶端已經上傳成功前 6 個分片,第 7 片已經上傳了一段數據時,用戶點擊了暫停上傳。此時客戶端需要將上傳的中間信息(文件信息、上傳進度)持久化下來,比如可以存儲到數據庫中。
當用戶再次點擊上傳時,第 7 片雖然已經上傳過一段數據,但是該分片并沒有上傳完整,會被作廢,所以從第 7 片開始需要重新上傳完整的分片。此時后面分片的上傳 URL 可能已經過期,上傳接口會返回 403,此時可以調用ListUploadedParts - 列舉已上傳分片,重新獲取已過期分片的上傳地址。
因為文件存在上傳時限,如果文件暫停上傳超過 10 天后,則無法再繼續斷點續傳該文件,此時客戶端只能重新調用創建文件接口,創建新的云端文件并進行上傳。
文件秒傳能力
秒傳介紹
PDS 提供了 Domain 級文件去重的能力,用戶上傳文件時,如果文件在云端的該 Domain 下已經存在,無需再走完整的上傳流程,只需要本地計算出文件的 SHA1,即可進行秒傳。
應用場景
用戶 A 上傳了一部影片,用戶 B 后續再次上傳相同影片時,只需走秒傳,即可在用戶 B 的空間內生成文件,B 不需要再完整上傳該影片,既提高了上傳效率,又節省了上傳流量。
用戶 B 秒傳上去的文件和用戶 A 空間下的文件在 File 級別沒有任何關聯,無需擔心數據安全問題。
使用秒傳
秒傳需要提前計算出文件完整的 SHA1,在調用創建文件接口時,設置到參數 content_hash 中。
核心參數設置:
設置文件秒傳計算算法,目前只支持 SHA1,content_hash_name = "sha1";
設置文件 sha1:content_hash = "xxxx";
設置文件 size:size = 13381200;
其他參數和前述文件上傳流程章節的創建文件參數一致。
請求 body 示例:
{
"drive_id":"xxxx",
"name":"test.jpg",
"parent_file_id":"root",
"content_hash":"xxxxx",
"content_hash_name":"sha1",
"part_info_list":[
{
"part_number":1
},
{
"part_number":2
},
{
"part_number":3
}
],
"size":13381200,
"type":"file"
}
sha1 計算示例代碼(Java):
public static String getFileHash(File file) throws IOException {
return Hex.encodeHexString((getFileHashBytes(file)));
}
public static byte[] getFileHashBytes(File file) throws IOException {
byte[] sha1;
try {
MessageDigest digest = MessageDigest.getInstance("SHA1");
byte[] buffer = new byte[10 * 1024];
FileInputStream is = new FileInputStream(file);
int len;
while ((len = is.read(buffer)) != -1) {
digest.update(buffer, 0, len);
}
is.close();
sha1 = digest.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA1 algorithm not found.");
}
return sha1;
}
返回 body 示例:
{
"domain_id":"xxxx",
"drive_id":"xxxx",
"parent_file_id":"root",
"upload_id":"rapid-xxxx",
"rapid_upload":true,
"type":"file",
"file_id":"xxxxx",
"revision_id":"xxxxx",
"file_name":"test.jpg"
}
核心返回參數解析:
rapid_upload:表示是否命中秒傳:
a. rapid_upload = true,表示命中秒傳,即云端該 domain 下存在 data 數據一樣的文件(根據 SHA1 匹配),直接秒傳成功,返回 body 中也不再返回分片上傳地址,客戶端無需再走后續的【上傳文件】和【完成文件上傳】這兩步。
b. rapid_upload = false,表示沒有命中秒傳, 此時返回 body 里會包含分片的上傳地址,需要再繼續后續的【上傳文件】和【完成文件上傳】這兩步。
使用預秒傳提升準確率
秒傳需要計算文件完整的 SHA1,一般客戶端算力有限,上傳大文件時計算完整 SHA1 比較耗時,而且云端如果沒有 SHA1 匹配的 data 數據,也不能秒傳成功,白白浪費了客戶端的算力,增加了上傳耗時。
為了解決這個問題,提升秒傳準確率,PDS 提供了預秒傳能力。客戶端只需要先計算出文件前 1k 數據的 SHA1,調用 PDS 創建文件接口,設置到參數 pre_hash 中,服務端會校驗該 data 是否可能在云端已經存在。
核心參數設置:
設置文件前 1k 數據的 SHA1:pre_hash = "xxxxx";
其他參數和前述文件上傳流程章節的創建文件參數一致。
請求 body 示例:
{
"drive_id":"10530",
"name":"test.jpg",
"parent_file_id":"root",
"part_info_list":[
{
"part_number":1
},
{
"part_number":2
},
{
"part_number":3
}
],
"pre_hash":"xxxx",
"size":13381200,
"type":"file"
}
接口返回解析:
如果云端返回 HTTP 狀態碼為 409, 則表明預秒傳匹配成功,也就是云端可能存在相同數據(因為前 1k 碰撞幾率大,所以不能保證一定存在),此時客戶端可以繼續計算文件完整 SHA1,再次調用創建文件接口,嘗試秒傳。
如果云端返回 HTTP 狀態碼為 201, 則表明預秒傳沒有匹配成功,也就是云端此刻肯定不存在相同數據,此時接口會同步返回分片上傳地址,客戶端繼續走完整上傳流程即可。
注意點:預秒傳是后臺異步計算的,可能會出現分鐘級延遲,所以剛上傳到云端的文件,再次上傳相同文件時,不一定能命中預秒傳。
整體流程圖
覆蓋上傳
PDS 支持覆蓋上傳某個文件,只需要在創建文件接口中,設置 file_id 為要覆蓋的文件 id 即可,其他流程和普通上傳一致。
請求 body 示例:
{
"drive_id":"xxxx",
"file_id":"xxxxx",
"name":"test.jpg",
"parent_file_id":"root",
"part_info_list":[
{
"part_number":1
},
{
"part_number":2
},
{
"part_number":3
}
],
"size":13373603,
"type":"file"
}
關鍵問題:
文件覆蓋寫后,文件的 file_id 不會發生變化,還可以使用之前的 file_id 進行文件操作。
多個端并發覆蓋寫同一個文件時,會使用最后一個成功調用完成文件上傳接口的版本作為文件最新版本。
如果想保留文件覆蓋寫之前的版本,需要先開啟多版本功能,多版本接口可以參考ListRevision - 列舉版本。
常見問題
文件分片的上傳地址過期了
文件分片的上傳地址是有時效的,目前有效期為 1 個小時,如果 1 個小時后再進行上傳(比如用戶暫停后,斷點續傳場景),則上傳接口會返回 403。此時可以調用ListUploadedParts - 列舉已上傳分片,重新獲取已過期分片的上傳地址。
文件上傳限制
單個文件分片最大限制 5GB
因服務端使用流式計算SHA1值,單個文件的分片需要串行上傳,不支持多個分片并行上傳
分片不允許覆蓋
文件上傳時限
文件從開始上傳到最后完成上傳,需要在 10 天內完成,超過10 天后本次上傳流程會被作廢,此時客戶端只能重新調用創建文件接口,創建新的云端文件并進行上傳。