附件
執行可續傳的分段上傳,並透過資料列、欄位與租戶權限檢查安全下載附件。
上傳工作階段的每個呼叫——init、公開分塊路由、status、complete 與 abort——在工作階段快取或物件儲存無法使用時,也可能回 5xx。其中 503 {"detail":"Cache service temporarily unavailable"}(任一呼叫)與 init 的 500 {"detail":"Failed to initialize multipart upload"} 不會變更任何狀態,也不會建立工作階段,請稍後以相同請求重試;下方各卡片不再為它們另列錯誤列。
/private/module/custom_tables/{scopeName}/tables/{table_id}/blobs/multipart/init替準備寫入自訂資料表附件儲存格的檔案建立可續傳工作階段。
傳送第 0 個分塊前先呼叫,以建立綁定此資料表的上傳工作階段,並取得分塊大小、分塊總數與到期時間。
- 聊天室
/private/module/custom_tables/chatroom/tables/{table_id}/blobs/multipart/init - 部門
/private/module/custom_tables/department/tables/{table_id}/blobs/multipart/init - 公司
/private/module/custom_tables/company/tables/{table_id}/blobs/multipart/init
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableWriteRequired |
| 部門 | 可使用 | CustomTableWriteRequired |
| 公司 | 可使用 | CustomTableWriteRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 上傳工作階段所綁定的自訂資料表 UUID;它的 attachment 欄位決定前期的大小與 MIME 檢查。完成的檔案物件屬於該資料表的租戶,且不記錄資料表或上傳者,所以同租戶的任何 attachment 欄位都可以引用它:寫入資料列時,只要 blob id 屬於同一租戶就會被接受,不論是誰、透過哪一種上傳管道(此流程、聊天、DCC、ETL)上傳的。 |
回應結構
InitMultipartUploadResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
chunk_size | integer | 是 | Size of each chunk in bytes |
expires_at | string | 是 | ISO format timestamp when the session expires |
session_id | string | 是 | Unique identifier for this upload session |
total_chunks | integer | 是 | Total number of chunks expected |
範例
開始上傳只有一個分塊的文字檔
{
"pathParams": {
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"filename": "order-note.txt",
"file_size": 18,
"content_type": "text/plain"
}
}{
"session_id": "55555555-5555-4555-8555-555555555555",
"chunk_size": 5242880,
"total_chunks": 1,
"expires_at": "2026-07-20T02:30:00"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | file_size ≤ 0 或超過 50 GiB(53687091200 位元組)、chunk_size 超過 200 MiB(209715200)、檔案超過資料表 attachment 欄位中最大的 max_file_bytes,或 content_type 不在任何 attachment 欄宣告的 allowlist 內。chunk_size 小於 5 MiB(5242880)會在表單驗證時得到 422,而不是 400。 | |
| 400 | file_size ≤ 0 或超過 50 GiB(53687091200 位元組)、chunk_size 超過 200 MiB(209715200)、檔案超過資料表 attachment 欄位中最大的 max_file_bytes,或 content_type 不在任何 attachment 欄宣告的 allowlist 內。chunk_size 小於 5 MiB(5242880)會在表單驗證時得到 422,而不是 400。 | |
| 400 | file_size ≤ 0 或超過 50 GiB(53687091200 位元組)、chunk_size 超過 200 MiB(209715200)、檔案超過資料表 attachment 欄位中最大的 max_file_bytes,或 content_type 不在任何 attachment 欄宣告的 allowlist 內。chunk_size 小於 5 MiB(5242880)會在表單驗證時得到 422,而不是 400。 | |
| 400 | file_size ≤ 0 或超過 50 GiB(53687091200 位元組)、chunk_size 超過 200 MiB(209715200)、檔案超過資料表 attachment 欄位中最大的 max_file_bytes,或 content_type 不在任何 attachment 欄宣告的 allowlist 內。chunk_size 小於 5 MiB(5242880)會在表單驗證時得到 422,而不是 400。 | |
| 400 | file_size ≤ 0 或超過 50 GiB(53687091200 位元組)、chunk_size 超過 200 MiB(209715200)、檔案超過資料表 attachment 欄位中最大的 max_file_bytes,或 content_type 不在任何 attachment 欄宣告的 allowlist 內。chunk_size 小於 5 MiB(5242880)會在表單驗證時得到 422,而不是 400。 | |
| 403 | 資料表的 scope 門檻拒絕你(`Not a member of this chatroom.` 或 `Insufficient permissions for department-scoped table.`),或你沒有新增或編輯能力(如範例)。自後端 5.21.0 起,其他公司的資料表——以及任何資料表對不屬於任何公司的呼叫者——回 `404 CustomTable Not found`。 | |
| 404 | table_id 不存在,或資料表在垃圾桶中(寫入門檻會先於處理器回應)。 | |
/public/module/etl/multipart/chunk把一個從 0 起算的 binary part,上傳到 multipart init 回傳的可續傳工作階段。
Init 之後、complete 之前,每個 chunk 各呼叫一次;缺少或需要重試的 chunk 可依任意順序送出。
- 完整路由
/public/module/etl/multipart/chunk
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 完整路由 | 可使用 | UploadSessionCapability |
請求結構
Body_upload_chunk_public_module_etl_multipart_chunk_post| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
chunk | string | 是 | The chunk data |
chunk_hash | string | null | 否 | SHA-256 hash of the chunk for integrity verification |
chunk_index | integer | 是 | Zero-based index of this chunk |
session_id | string | 是 | Upload session ID |
回應結構
UploadChunkResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
chunk_index | integer | 是 | Index of the uploaded chunk |
is_complete | boolean | 是 | Whether all chunks have been uploaded |
progress_percent | number | 是 | Upload progress as a percentage |
session_id | string | 是 | Upload session ID |
total_chunks | integer | 是 | Total number of chunks expected |
uploaded_chunks | integer[] | 是 | List of all successfully uploaded chunk indices |
範例
上傳小檔案唯一的一個分塊
{
"pathParams": {},
"body": {
"session_id": "55555555-5555-4555-8555-555555555555",
"chunk_index": 0,
"chunk": "<binary chunk: 18 bytes>",
"chunk_hash": "96c4b7d5fb8e6ef963fab7b8708fd574a3836fabb46539d22af6ca68ac3b27ff"
}
}{
"session_id": "55555555-5555-4555-8555-555555555555",
"chunk_index": 0,
"uploaded_chunks": [
0
],
"total_chunks": 1,
"progress_percent": 100,
"is_complete": true
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | Index 超出工作階段範圍、非最後一塊大小不等於 chunk_size、最後一塊大小不等於剩餘 bytes,或 chunk_hash 與 SHA-256 digest 不符。在 hash 不符的 detail 中,「Expected」是你送出的 chunk_hash,「got」是伺服器算出的小寫 digest——比對區分大小寫,請送小寫十六進位。 | |
| 400 | Index 超出工作階段範圍、非最後一塊大小不等於 chunk_size、最後一塊大小不等於剩餘 bytes,或 chunk_hash 與 SHA-256 digest 不符。在 hash 不符的 detail 中,「Expected」是你送出的 chunk_hash,「got」是伺服器算出的小寫 digest——比對區分大小寫,請送小寫十六進位。 | |
| 400 | Index 超出工作階段範圍、非最後一塊大小不等於 chunk_size、最後一塊大小不等於剩餘 bytes,或 chunk_hash 與 SHA-256 digest 不符。在 hash 不符的 detail 中,「Expected」是你送出的 chunk_hash,「got」是伺服器算出的小寫 digest——比對區分大小寫,請送小寫十六進位。 | |
| 400 | Index 超出工作階段範圍、非最後一塊大小不等於 chunk_size、最後一塊大小不等於剩餘 bytes,或 chunk_hash 與 SHA-256 digest 不符。在 hash 不符的 detail 中,「Expected」是你送出的 chunk_hash,「got」是伺服器算出的小寫 digest——比對區分大小寫,請送小寫十六進位。 | |
| 403 | Storage service 拒絕存取。這是基礎設施授權,不是 custom-table 使用者權限回應。 | |
| 404 | 此 session_id 沒有對應的工作階段:從未存在、已完成或已中止,或其鍵值已過期(每接受一個分塊,24 小時 TTL 就重新計算)。 | |
| 404 | 工作階段仍存在,但已超過 expires_at(init 後 24 小時)。伺服器會中止儲存端的上傳並刪除工作階段,請從 init 重新開始。 | |
| 409 | 該 zero-based chunk index 已被接受,或另一個並行上傳先取得同一 part。 | |
| 409 | 該 zero-based chunk index 已被接受,或另一個並行上傳先取得同一 part。 | |
| 413 | Storage provider 判定此 part 過大。 | |
| 503 | 上傳或記錄此 part 時發生非預期失敗(如範例),或儲存端回了未另行處理的錯誤(`Storage service error: <provider 錯誤代碼>`),或此 worker 無法建立 Redis 連線(`Cache service temporarily unavailable`;連線建立之後才發生的 Redis 故障則會以 `500 Internal Server Error` 呈現)。請重傳此分塊——但 index 在 10000 以上時的儲存端 part 編號錯誤除外,那需要以更大的 chunk_size 重新 init。 | |
| 503 | 儲存端判定此工作階段的分段上傳不存在或無法讀取(例如 NoSuchUpload)。伺服器已重新建立儲存端上傳並清除所有已上傳的分塊:請在原本的 expires_at 之前,以同一個 session_id 從 index 0 重傳全部分塊。 | |
| 503 | 儲存端拒絕了此工作階段的分段上傳,且重新建立失敗。請依 detail 指示,以 init 開始新的上傳。 | |
/private/module/custom_tables/{scopeName}/tables/{table_id}/blobs/multipart/complete組合所有已上傳分塊、建立歸屬正確租戶的檔案物件,並回傳已遮蔽網址的附件資訊。
狀態顯示所有預期分塊都已上傳後再呼叫。
- 聊天室
/private/module/custom_tables/chatroom/tables/{table_id}/blobs/multipart/complete - 部門
/private/module/custom_tables/department/tables/{table_id}/blobs/multipart/complete - 公司
/private/module/custom_tables/company/tables/{table_id}/blobs/multipart/complete
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableWriteRequired |
| 部門 | 可使用 | CustomTableWriteRequired |
| 公司 | 可使用 | CustomTableWriteRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 初始化此上傳工作階段時使用的資料表 UUID。 |
回應結構
AttachmentUploadResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
blob | AttachmentInfo | 是 | Uploaded attachment metadata (url-redacted) |
範例
組合已完成上傳的文字檔
{
"pathParams": {
"table_id": "22222222-2222-4222-8222-222222222222"
},
"body": {
"session_id": "55555555-5555-4555-8555-555555555555"
}
}{
"blob": {
"id": "44444444-4444-4444-8444-444444444444",
"created_at": "2026-07-19 02:35:00",
"url": null,
"content_type": "text/plain",
"filename": "order-note.txt",
"size_bytes": 18
}
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 仍有分塊缺漏、table_id 與工作階段不符,或重新量測後的組裝物件超過整張表最大的附件上限。超限拒絕是終局的——物件會被刪除、工作階段會被清除,必須從 init 重新開始(與可重試的 409 不同)。 | |
| 400 | 仍有分塊缺漏、table_id 與工作階段不符,或重新量測後的組裝物件超過整張表最大的附件上限。超限拒絕是終局的——物件會被刪除、工作階段會被清除,必須從 init 重新開始(與可重試的 409 不同)。 | |
| 400 | 仍有分塊缺漏、table_id 與工作階段不符,或重新量測後的組裝物件超過整張表最大的附件上限。超限拒絕是終局的——物件會被刪除、工作階段會被清除,必須從 init 重新開始(與可重試的 409 不同)。 | |
| 403 | 工作階段屬於其他使用者,或呼叫者已失去寫入權限。 | |
| 404 | 此 session_id 沒有對應的工作階段:從未存在、已完成或已中止、因終局錯誤被捨棄,或其鍵值已過期。 | |
| 404 | table_id 不存在,或資料表在垃圾桶中(寫入門檻會先於處理器回應)。 | |
| 409 | 寫入 blob 資料列時遇到暫時性鎖衝突。分塊已經組裝完成,物件與 session(24 小時 TTL)都還在——拿同一個 session_id 重呼 complete 收尾;這個重試具冪等性。 | |
| 409 | 同一工作階段的另一個 complete 呼叫仍在執行(每個工作階段一把鎖,該呼叫結束時釋放,最長 120 秒後失效)。請稍後以相同請求重試;若另一個呼叫已成功,工作階段已被刪除,重試會回 404,請改用那次呼叫回傳的 blob。 | |
| 500 | 工作階段仍列出某個分塊,但它儲存的 part 紀錄已不存在。每筆 part 紀錄從該分塊上傳起只保留 24 小時,而工作階段鍵值的 TTL 會隨每個分塊重新計算,所以實務上是在 expires_at 之後才呼叫 complete 時發生。detail `Part <N> data not found` 指出從 1 起算的 part 編號(chunk_index + 1)。此工作階段已無法完成:請中止它,再從 init 重新開始。 | |
| 500 | 組裝物件或建立 blob 時,發生鎖衝突以外的失敗。這是終局錯誤:伺服器會中止儲存端的上傳並刪除工作階段,重試 complete 只會得到 404——請從 init 重新開始。 | |
/private/module/custom_tables/{scopeName}/tables/{table_id}/blobs/multipart/abort取消未完成的上傳,並移除暫存物件分塊與工作階段鍵值。
使用者取消上傳,或工作階段已無法安全續傳時使用。
- 聊天室
/private/module/custom_tables/chatroom/tables/{table_id}/blobs/multipart/abort - 部門
/private/module/custom_tables/department/tables/{table_id}/blobs/multipart/abort - 公司
/private/module/custom_tables/company/tables/{table_id}/blobs/multipart/abort
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableWriteRequired |
| 部門 | 可使用 | CustomTableWriteRequired |
| 公司 | 可使用 | CustomTableWriteRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 初始化此上傳工作階段時使用的資料表 UUID。 |
session_id | query | uuid | 是 | 要取消的上傳工作階段 UUID。 |
回應結構
SuccessResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
message | string | 否 | 未指定 |
範例
中止未完成的上傳
{
"pathParams": {
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"session_id": "55555555-5555-4555-8555-555555555555"
}
}{
"message": "Upload session 55555555-5555-4555-8555-555555555555 has been aborted"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 此工作階段是在另一個 table_id 上建立的;請透過 init 時使用的資料表中止它。 | |
| 403 | 工作階段屬於其他使用者,或呼叫者沒有資料表寫入權限。 | |
| 404 | 工作階段已過期、已完成,或先前已中止。 | |
| 404 | table_id 不存在,或資料表在垃圾桶中(寫入門檻會先於處理器回應)。 | |
/private/module/custom_tables/{scopeName}/tables/{table_id}/blobs/multipart/status回傳工作階段資訊、已上傳與缺少的分塊、進度、完成狀態及到期時間。
適合用來續傳中斷的上傳,或判斷何時可以安全呼叫 complete。
- 聊天室
/private/module/custom_tables/chatroom/tables/{table_id}/blobs/multipart/status - 部門
/private/module/custom_tables/department/tables/{table_id}/blobs/multipart/status - 公司
/private/module/custom_tables/company/tables/{table_id}/blobs/multipart/status
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableWriteRequired |
| 部門 | 可使用 | CustomTableWriteRequired |
| 公司 | 可使用 | CustomTableWriteRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 初始化此上傳工作階段時使用的資料表 UUID。 |
session_id | query | uuid | 是 | 要查詢進度的上傳工作階段 UUID。 |
回應結構
SessionStatusResponse| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
chunk_size | integer | 是 | Size of each chunk in bytes |
content_type | string | 是 | MIME type of the file |
created_at | string | 是 | ISO format timestamp when session was created |
expires_at | string | 是 | ISO format timestamp when session expires |
file_size | integer | 是 | Total file size in bytes |
filename | string | 是 | Name of the file being uploaded |
is_complete | boolean | 是 | Whether all chunks have been uploaded |
missing_chunks | integer[] | 是 | List of chunk indices that haven't been uploaded yet |
progress_percent | number | 是 | Upload progress as a percentage |
session_id | string | 是 | Upload session identifier |
total_chunks | integer | 是 | Total number of chunks |
uploaded_chunks | integer[] | 是 | List of successfully uploaded chunk indices |
範例
查看新建立的單分塊工作階段
{
"pathParams": {
"table_id": "22222222-2222-4222-8222-222222222222"
},
"query": {
"session_id": "55555555-5555-4555-8555-555555555555"
}
}{
"session_id": "55555555-5555-4555-8555-555555555555",
"filename": "order-note.txt",
"file_size": 18,
"content_type": "text/plain",
"chunk_size": 5242880,
"total_chunks": 1,
"uploaded_chunks": [],
"missing_chunks": [
0
],
"progress_percent": 0,
"is_complete": false,
"created_at": "2026-07-19T02:30:00",
"expires_at": "2026-07-20T02:30:00"
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 400 | 此工作階段是在另一個 table_id 上建立的;請透過 init 時使用的資料表查詢它。 | |
| 403 | 工作階段屬於其他使用者,或呼叫者沒有資料表寫入權限。 | |
| 404 | 工作階段不存在或已過期。 | |
| 404 | table_id 不存在,或資料表在垃圾桶中(寫入門檻會先於處理器回應)。 | |
/private/module/custom_tables/{scopeName}/tables/{table_id}/records/{record_id}/attachments/{blob_id}/download重新驗證資料列 ACL、附件欄位可見性與租戶歸屬後,回傳受控的檔案資訊與存取網址。
使用者要開啟或下載附件儲存格中的 blob id 時使用。
- 聊天室
/private/module/custom_tables/chatroom/tables/{table_id}/records/{record_id}/attachments/{blob_id}/download - 部門
/private/module/custom_tables/department/tables/{table_id}/records/{record_id}/attachments/{blob_id}/download - 公司
/private/module/custom_tables/company/tables/{table_id}/records/{record_id}/attachments/{blob_id}/download
範圍
| 範圍 | 是否提供 | 驗證與權限 |
|---|---|---|
| 聊天室 | 可使用 | CustomTableReadRequired |
| 部門 | 可使用 | CustomTableReadRequired |
| 公司 | 可使用 | CustomTableReadRequired |
參數
| 欄位 | 位置 | 型別 | 必填 | 說明 |
|---|---|---|---|---|
table_id | path | uuid | 是 | 包含附件欄位的資料表 UUID。 |
record_id | path | uuid | 是 | 實際參照此檔案物件,且呼叫者可讀取的資料列 UUID。 |
blob_id | path | uuid | 是 | 保存在可見附件儲存格中的檔案物件 UUID。 |
回應結構
BlobInfoWithSignedUrl| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
content_type | string | 是 | MIME type of the blob content |
created_at | string | 是 | Timestamp when the blob was uploaded |
expiration_seconds | integer | 是 | Always 0 for custom-table attachment downloads. Zero means this endpoint did not add a URL TTL; it is not an immediate expiry or a refresh interval. |
expires_at | string | 是 | Response-construction timestamp for this endpoint when expiration_seconds is 0. It is not a URL expiry or a refresh deadline. |
filename | string | null | 是 | Original filename of the uploaded content, if available |
id | string | 是 | Unique identifier for the blob |
signed_url | string | 是 | Legacy field containing the blob's stored URL after this endpoint passes its request-time authorization gates. The endpoint returns the stored URL unchanged; it does not cryptographically sign or time-limit it. |
tags | string[] | 否 | Tags associated with the blob (parsed from comma-separated string) |
thumbnail | _BlobInfo | null | 是 | Information about an associated thumbnail blob, if available |
url | string | null | 是 | Always null in the custom-table attachment download response; use the legacy signed_url field. |
範例
將可見附件解析成存取網址
{
"pathParams": {
"table_id": "22222222-2222-4222-8222-222222222222",
"record_id": "33333333-3333-4333-8333-333333333333",
"blob_id": "44444444-4444-4444-8444-444444444444"
}
}{
"id": "44444444-4444-4444-8444-444444444444",
"created_at": "2026-07-19 02:35:00",
"url": null,
"content_type": "text/plain",
"filename": "order-note.txt",
"tags": [],
"thumbnail": null,
"signed_url": "https://files.example.invalid/CustomTable_Blob/1784428200_22222222-2222-4222-8222-222222222222/55555555-5555-4555-8555-555555555555.txt",
"expires_at": "2026-07-19 02:40:00",
"expiration_seconds": 0
}錯誤
驗證錯誤範例列出識別欄位;部署中的驗證器也可能回傳 input 與 ctx。錯誤訊息 URL 的版本由建置決定,因此使用 pydantic-version 佔位符。
| 狀態 | 發生條件 | 錯誤本文 |
|---|---|---|
| 403 | 資料表的 scope 門檻拒絕你(`Not a member of this chatroom.` 或 `Insufficient permissions for department-scoped table.`),或你沒有此資料表的讀取權限(如範例)。自後端 5.21.0 起,其他公司的資料表回 `404 CustomTable Not found`。 | |
| 404 | 資料表不存在或在垃圾桶中(CustomTable Not found)、資料列不存在或在垃圾桶中(CustomTableRecord Not found)或被列 ACL/頻道 floor 隱藏(Record not found)、可見的 attachment 欄位未引用此 blob(Attachment not found on this record),或 blob 屬於其他租戶(Attachment not found)。呼叫者完全無法讀取的資料表是上面的 403,不是 404。 | |