Skip to Content

分段上傳並掛載附件

情境:一筆訂單需要附上 7 MiB 的 PDF,而且上傳中斷後要能從缺少的 chunk 繼續。

前置條件

建立 attachment 欄位需要 table moderator;初始化與完成上傳需要該表的 insert 或 edit 權限;掛到既有資料列需要 edit 權限;下載需要該資料列與附件欄位的 read 權限。本例使用聊天室資料表 22222222-2222-4222-8222-222222222222 與資料列 33333333-3333-4333-8333-333333333333

注意 attachment 專用 endpoint 的 scope path 是裸 chatroomdepartmentcompany,不含 scope UUID;一般欄位與資料列 endpoint 仍包含 scope UUID。以下 private 請求使用使用者 access token。

步驟

1. 建立有明確限制的 attachment 欄位

透過 columns.create 定義附件欄位時,max_count 可設 1–100,max_file_bytes 每檔最多 5 GiB,allowed_mime_types 則是 MIME allow-list。範例最多三個檔案、每檔 10 MiB,只接受 PDF 或 PNG。

POST /private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/22222222-2222-4222-8222-222222222222/columns Content-Type: application/json { "name": "訂單附件", "type": "attachment", "max_count": 3, "max_file_bytes": 10485760, "allowed_mime_types": [ "application/pdf", "image/png" ] }

保留回應中的內部欄位 ID;以下假設是 col_44444444_4444_4444_8444_444444444444

2. 初始化 multipart session

先呼叫 attachments.multipartInit,並以 form fields 傳入資料;file_size 是完整檔案的 bytes,content_type 應與之後要掛入的欄位 allow-list 相符。

curl -X POST \ -H "Authorization: Bearer <user-access-token>" \ -F "filename=invoice.pdf" \ -F "file_size=7340032" \ -F "content_type=application/pdf" \ "https://api.example.invalid/private/module/custom_tables/chatroom/tables/22222222-2222-4222-8222-222222222222/blobs/multipart/init"

使用回應的 session_idchunk_sizetotal_chunksexpires_at 切檔;不要自行假設 5 MiB。init 的 table-level 大小與 MIME 檢查是彼此獨立的早期提示:只要某欄沒有 max_file_bytes,就只停用早期大小 guard;只要某欄沒有 allowed_mime_types,就只停用早期 MIME guard。真正的強制執行在後面兩個點:multipart complete 會重新量測組裝後的物件,超過 table size guard 就不建立 blob;資料列寫入則套用目標欄位自己的 max_count、MIME 與大小限制。

3. 上傳每個 zero-based chunk

Chunk 送到共用 public endpoint。它沒有 user-auth dependency:持有高熵、短期的 session_id,就是可以替該 session 新增 parts 的 capability。不要把它寫入 log、前端分析事件或 URL;也不要把 user bearer token 當成這一段的 gate。

Request 是 multipart/form-data,欄位為:必填 session_id、必填 zero-based 整數 chunk_index、必填 binary file field chunk,以及選填 chunk_hash。每個非最後 part 的 bytes 必須剛好等於 init response 的 chunk_size;最後 part 必須剛好等於 file_size - (chunk_index * chunk_size) 的剩餘 bytes。若提供,chunk_hash 是原始上傳 bytes 的小寫 SHA-256 hex digest。下例假設回應要求兩個 chunks。這個 chunk leg 是 attachments.multipartInit 所描述 lifecycle 的一部分。

curl -X POST \ -F "session_id=55555555-5555-4555-8555-555555555555" \ -F "chunk_index=0" \ -F "chunk=@invoice.part-00000;type=application/octet-stream" \ -F "chunk_hash=<sha256-of-part-00000>" \ "https://api.example.invalid/public/module/etl/multipart/chunk"
curl -X POST \ -F "session_id=55555555-5555-4555-8555-555555555555" \ -F "chunk_index=1" \ -F "chunk=@invoice.part-00001;type=application/octet-stream" \ -F "chunk_hash=<sha256-of-part-00001>" \ "https://api.example.invalid/public/module/etl/multipart/chunk"

成功時會回傳整個 session 的進度:

{ "session_id": "55555555-5555-4555-8555-555555555555", "chunk_index": 1, "uploaded_chunks": [0, 1], "total_chunks": 2, "progress_percent": 100.0, "is_complete": true }

Chunk route 的錯誤與後續 private status/complete authorization 分開:index 超界、exact size 不符或 hash mismatch 是 400;session 不存在或過期是 404;該 index 已上傳或衝突是 409;storage 拒絕 part 大小是 413;cache/storage 暫時不可用或損毀 upload 被 reset 是 503。Storage policy 也可能回 403。遇到 409 先查 private status 再決定是否補傳;reset 503 應依 detail 從 chunk zero 重新開始。

4. 查狀態並補傳缺少的 chunks

attachments.multipartStatus 回傳的 uploaded_chunksmissing_chunks 都是 zero-based。只補傳 missing 清單;不要重送已完成 index。

GET /private/module/custom_tables/chatroom/tables/22222222-2222-4222-8222-222222222222/blobs/multipart/status?session_id=55555555-5555-4555-8555-555555555555

如果使用者取消流程,呼叫 attachments.multipartAbort 清理未完成 session:

DELETE /private/module/custom_tables/chatroom/tables/22222222-2222-4222-8222-222222222222/blobs/multipart/abort?session_id=55555555-5555-4555-8555-555555555555

5. 完成上傳並保留 blob ID

只有 missing_chunks 為空時才呼叫 attachments.multipartComplete。回應的 blob.id 才是要放入資料列的持久識別碼;blob.url 會刻意保持 null

curl -X POST \ -H "Authorization: Bearer <user-access-token>" \ -F "session_id=55555555-5555-4555-8555-555555555555" \ "https://api.example.invalid/private/module/custom_tables/chatroom/tables/22222222-2222-4222-8222-222222222222/blobs/multipart/complete"

以下假設 blob ID 是 66666666-6666-4666-8666-666666666666

6. 把 blob 掛到資料列

呼叫 records.update 時,多檔 attachment 欄位可使用 addremove delta,也可以傳完整 ID 陣列做 replace;max_count: 1 的欄位只接受 replace。

PUT /private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/22222222-2222-4222-8222-222222222222/records/33333333-3333-4333-8333-333333333333 Content-Type: application/json { "col_44444444_4444_4444_8444_444444444444": { "add": [ "66666666-6666-4666-8666-666666666666" ] } }

這一步會對目標欄位強制執行 tenant ownership、max_count、檔案大小與 MIME 限制;complete 成功不代表 blob 可以放進任意 attachment 欄位。

7. 需要時請求經權限檢查的下載網址

先從 record read 取得 attachment 的 blob_id,再交給 attachments.download。Route 會重新檢查 row ACL、可見附件參照與 tenant ownership,通過後才在舊欄位 signed_url 回傳儲存的存取網址;它不會在此進行密碼學簽章或加上時效限制。

GET /private/module/custom_tables/chatroom/tables/22222222-2222-4222-8222-222222222222/records/33333333-3333-4333-8333-333333333333/attachments/66666666-6666-4666-8666-666666666666/download

signed_url 當成敏感資訊,只用於這次由使用者發起的下載。expiration_seconds0expires_at 只是建構回應的時間,不是真正的 URL 到期時間;不要用它們排定更新。不要保存、分享或寫回資料表,也不要假設後續權限變更會撤銷已回傳的網址;之後要下載時再重新呼叫 endpoint。

你會看到什麼

status 會從缺少兩個 chunks 進展到 is_complete: true;complete 會建立 blob;record read 會把附件 ID enrich 成檔名與 MIME 等中繼資料,但不洩漏存取網址。download 只在呼叫者通過當次權限檢查後,於 signed_url 回傳儲存的網址;欄位名稱不代表它經過密碼學簽章或具備到期時間。

常見錯誤

請直接查看初始化錯誤表狀態錯誤表完成錯誤表資料列更新錯誤表下載錯誤表

試試看

API Playground 可執行 catalog 中的每個步驟,包括 public chunk route;其 file picker 會建立 binary multipart/form-data 請求,不會自行捏造 boundary。上方 curl 流程仍可用來以 script 執行相同 lifecycle。

Last updated on