從檔案擷取並匯入資料
情境:你收到一個訂單檔案,想先檢查伺服器推測的欄位,再決定建立新表或寫入既有表。
前置條件
你需要目標 scope 的存取權。Chatroom extraction 使用 room access gate;department extraction 與 preview 只要求部門屬於呼叫者的公司,不要求部門管理權限;company extraction 需要 company-management access,各 scope 的拒絕條件不同;跨公司部門回 404。若選擇既有表,還需要該表的 insert 權限。本例使用聊天室 11111111-1111-4111-8111-111111111111。匯入大小由部署環境的 CUSTOM_TABLE_IMPORT_MAX_BYTES 控制,預設為 50 MiB;所有 private 請求使用使用者 access token。
Chatroom/company 的 403 detail 文字仍依 scope 而定。
部門上傳與寫入權限(v5.10.0)
部門所屬公司的已登入使用者可擷取與預覽 department 匯入 session,不再要求部門管理權限。上傳或預覽不會授予資料表存取權。具有既有表 insert 權限時選擇「匯入既有表」;要「建立新表」仍須部門管理權限。權限撤銷後,後續 map 請求會被拒絕;insert 權限也不代表匯入列的 can_edit 為 true。Session 必須符合請求的部門;不存在、過期或錯誤 scope 都回 404。
後端 #1261 已以真實 staging E2E 驗證兩列 XLSX、僅新增權限的讀回、拒絕建新表與撤權行為;修正已隨 v5.10.0 發佈。
步驟
1. 上傳檔案並啟動 extract
將 multipart 欄位 file 送給 import.extract;回應會立刻給 ticket_id,不代表擷取已完成。
curl -X POST \
-H "Authorization: Bearer <user-access-token>" \
-F "file=@orders.csv;type=text/csv" \
"https://api.example.invalid/private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/import/extract"以下假設 ticket 是 44444444-4444-4444-8444-444444444444。
2. 輪詢 extract ticket
import.extract 使用共用非同步 watchdog。輪詢下列精確路徑,直到 body.status 為 completed 或 failed;processing 時依 body.current_step 顯示進度。
GET /public/task/44444444-4444-4444-8444-444444444444成功時從 body.import_session_id 取出 session ID。若 watchdog 的 ttl 是 -1,ticket 已不存在或過期;不要猜一個 session ID。
3. 檢查 session preview
import.sessionPreview 的 preview 可能包含多個候選表。對你要匯入的候選,保留原樣的 signature,並檢查 source_columns、proposed_schema、sample_rows 與 row_count。
GET /private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/import/33333333-3333-4333-8333-333333333333後續請求的 signature 必須來自這個 session;它用來鎖定候選資料,不是讓客戶端重新計算的欄位。
4. 寫入前檢查型別相容性
每個擷取出的儲存格送進 row mapper 時都是字串。Preview 的型別推測只是一項建議:它只會提出 string、text、integer、float、date 或 datetime,不會提出 boolean、任何 list 型別、principal、JSON 或 computed 型別。全空欄會推測為 string;只要任一非空值超過 256 個字元,字串欄就會推測為 text。
調整推測 schema 或對應既有表時,請依下表判斷:
| 目標欄位型別 | 檔案匯入行為 |
|---|---|
string、text | 以字串接受,仍須通過目標欄位的一般長度與 required 檢查。 |
integer、float | Record validator 會轉換數字字串;格式錯誤的數字會使該列無效。 |
boolean | 不相容於一般擷取出的儲存格。"true"、"1"、"yes" 等值仍是字串,record contract 要求 JSON boolean,因此會拒絕。 |
date | 只接受 record contract 的標準 YYYY-MM-DD 格式。 |
datetime | 只接受 record contract 的標準 YYYY-MM-DD HH:MM 格式。Preview sniffer 也會辨識 T 分隔符與選填的秒數,但寫入 validator 不接受;因此 preview 仍可能提出一個會讓資料列無效的型別。 |
select | 字串必須與某個已設定 option 完全相同才可寫入。 |
multi_select | 不相容於一般擷取出的儲存格:record contract 要求陣列,但 importer 只提供一個原始字串。 |
attachment | 不相容於一般擷取出的儲存格:record contract 要求 attachment ID 陣列,不接受檔名或分隔字串。 |
user、social_client | 字串只有在它是該資料表 tenant 所擁有 principal 的原始 ID 時才可寫入;不會解析名稱或電子郵件地址。 |
principal | 字串只有在它是帶標籤的 cell(user:<id>、smc:<id> 或 room:<id>)、該 principal 屬於資料表 tenant,且 smc:/room: 的聊天室仍存活時才可寫入。裸 ID 會以形狀錯誤被拒,名稱也不會被解析。 |
json | 在派送資料列前即被拒絕為匯入目標。 |
link、rollup、lookup、formula | 會被拒絕為 computed 匯入目標。Create-from-import 完成後再新增這些欄位。 |
Sniffer 只檢查 date 與 datetime 的字串形狀,不檢查日曆日期是否有效。請把 preview 視為建議,派送前先驗證代表性資料。
5. 在建立新表與寫入既有表之間二選一
若檔案代表新的資料集,走 import.createFromImport。schema_definition.columns 必須與候選欄位保持相同數量與順序;可以改名、調整支援的型別與 required 設定,但不能在此新增或刪除位置,也不能建立 link、rollup、lookup 或 formula。這些衍生欄位請在匯入後另行建立。
呼叫 create 前,請先在 client 端核對欄位數量。後端目前會先建立並 commit 資料表,才比較候選欄數與 schema_definition.columns;數量不符時雖然回 400,新表仍會以空表留下。不要假設這個 400 沒有副作用。
POST /private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/import/33333333-3333-4333-8333-333333333333/create
Content-Type: application/json
{
"signature": "a3f5f7d9b1c3e5f709182736455463728190aabbccddeeff0011223344556677",
"table_name": "匯入訂單",
"description": "來自七月 CSV 的訂單",
"schema_definition": {
"columns": [
{
"name": "訂單編號",
"type": "string",
"required": true
}
]
},
"skip_invalid": true
}若資料應追加到現有表,使用 import.mapIntoTable。column_map 的 key 是 preview 的來源欄名,value 是既有表的顯示名稱或內部欄位 ID;只能對應可寫入、可見的 stored 欄位,且必須符合上方的相容性表。JSON 與 computed 目標會被拒絕。沒有列入 map 的來源欄不會寫入。
POST /private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/22222222-2222-4222-8222-222222222222/import/33333333-3333-4333-8333-333333333333
Content-Type: application/json
{
"signature": "a3f5f7d9b1c3e5f709182736455463728190aabbccddeeff0011223344556677",
"column_map": {
"order_no": "訂單編號"
},
"skip_invalid": true
}skip_invalid 只控制 row mapper 對「缺少 required 值」的檢查。設為 true 時,缺少 required mapped value 的資料列會在 bulk 派送前被省略;目前 import 回應與 ticket 都不會揭露這個 mapper skip 計數。設為 false 時,請求會在第一筆這類資料列停止,不會派送 insert。Create 分支此時可能已建立空表。
型別與 schema 驗證之後才在非同步 bulk worker 執行,不受 skip_invalid 影響。原始字串無法滿足目標型別時,會成為 ticket 的逐列錯誤;其他資料列可能已 commit,而 terminal status 是 completed_with_errors。寫入前請比較來源 row count 與派送/ticket totals、檢查 ticket errors,並審查 preview、型別相容性與 required 欄位。
6. 輪詢寫入 ticket
兩個分支都會回傳新的 ticket_id;create 分支還會立即回傳 table_id。對新的 ticket 使用同一個 watchdog:
GET /public/task/55555555-5555-4555-8555-555555555555只有在 terminal status 為 completed 時才把流程標為成功。completed_with_errors 要檢查列級錯誤;held_for_approval 表示整批尚未寫入,應接到核准流程;failed 則保留 ticket 回應供診斷。
你會看到什麼
extract ticket 完成後會提供 session ID;preview 會顯示候選 schema 與樣本。選擇 create 時會新增一張表,選擇 map 時會保留原表 schema;最終 ticket 會報告實際寫入結果,而不是只代表已排入佇列。
常見錯誤
請直接查看檔案擷取錯誤表、preview 錯誤表、建立新表錯誤表與對應既有表錯誤表。
試試看
檔案匯入流程精靈會保留 extract ticket、session ID 與 signature,並讓你在 create 和 map 分支之間選擇。