Studio Bug Report: get_trace_profile 讀寫不一致¶
Date: 2026-07-27 Author: Studio Team Status: 🔴 待修復 Priority: High
問題描述¶
Studio 前端 rename face group 後,離開再回來選同一個檔案,名稱變回舊的。
影響¶
使用者 rename 的變更無法持久顯示。雖然 trace_profiles.name 已更新,但 get_trace_profile API 回傳的是舊的 tkg_nodes.label,導致前端 loadClusterNames() 用舊名覆蓋新名。
測試驗證¶
1. Rename¶
curl -X PUT "http://localhost:3002/api/v1/trace-profile/group" \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{"file_uuid": "9cbeb112fcc5452e869755a539478061", "trace_ids": [1], "name": "SusanTest"}'
{"success": true, "updated_count": 1} ✅
2. 讀取單筆 profile(❌ 仍是舊名)¶
回傳:{"name": "Person_0"} ← 錯誤,應為 "SusanTest"
3. 讀取 face groups(✅ 是新名)¶
回傳:{"name": "SusanTest", "trace_ids": [1]} ← 正確
根因分析¶
寫入路徑(正確)¶
PUT /api/v1/trace-profile/group → update_trace_profile_group_handler(profile.rs:228):
trace_profiles.name ✅
讀取路徑 — 單筆(❌ 錯誤)¶
GET /api/v1/trace-profile → get_trace_profile_handler(profile.rs:78-81):
SELECT label, external_id, properties FROM tkg_nodes
WHERE file_uuid = $1 AND node_type = 'face_track'
AND (external_id = $2 OR external_id = $3)
tkg_nodes.label,沒有 JOIN trace_profiles ❌
讀取路徑 — 群組(✅ 正確)¶
GET /api/v1/file/{uuid}/face-groups → get_face_groups_handler(profile.rs:560-566):
SELECT COALESCE(tp.name, tn.label) as name, tn.properties
FROM tkg_nodes tn
LEFT JOIN trace_profiles tp ON tp.file_uuid = tn.file_uuid
AND tp.trace_id = (tn.properties->>'trace_id')::int
WHERE tn.file_uuid = $1 AND tn.node_type = 'face_track'
COALESCE(tp.name, tn.label),優先取 trace_profiles.name ✅
前端影響流程¶
User rename group → updateTraceProfileGroup() → PUT /trace-profile/group
→ trace_profiles.name = "SusanTest" ✅
User refresh → loadClusterResults() → GET /face-groups
→ cluster.name = "SusanTest" ✅
→ loadClusterNames() → GET /trace-profile
→ profile.name = "Person_0" ❌ (tkg_nodes.label)
→ c.name = profile.name → "Person_0" ❌ 覆蓋了新名
建議修復¶
方案:改 get_trace_profile_handler 使用 JOIN¶
pub async fn get_trace_profile_handler(...) {
let tkg_table = schema::table_name("tkg_nodes");
let tp_table = schema::table_name("trace_profiles");
let row: Option<(String, String, serde_json::Value)> = sqlx::query_as(&format!(
"SELECT COALESCE(tp.name, tn.label) as name, tn.external_id, tn.properties \
FROM {} tn \
LEFT JOIN {} tp ON tp.file_uuid = tn.file_uuid \
AND tp.trace_id = (tn.properties->>'trace_id')::int \
WHERE tn.file_uuid = $1 AND tn.node_type = 'face_track' \
AND (tn.external_id = $2 OR tn.external_id = $3)",
tkg_table, tp_table
))
.bind(¶ms.file_uuid)
.bind(&external_id_face)
.bind(&external_id_trace)
.fetch_optional(state.db.pool())
.await?;
let (name, _ext_id, properties) = row.ok_or(StatusCode::NOT_FOUND)?;
Ok(Json(TraceProfile {
file_uuid: params.file_uuid,
trace_id: params.trace_id,
name, // 改為使用 JOIN 後的 name
// ... 其他欄位不變
}))
}
一致性保證¶
| API | 讀取欄位 | 修復後 |
|---|---|---|
PUT /trace-profile/group |
寫入 trace_profiles.name |
不變 |
GET /trace-profile |
~~tkg_nodes.label~~ → COALESCE(tp.name, tn.label) |
✅ 一致 |
GET /file/{uuid}/face-groups |
COALESCE(tp.name, tn.label) |
不變 |
相關文件¶
18_profile.md— Profile API 文件19_people_api.md— People API 文件23_studio_face_group_name_fix.md— Studio face-groups JOIN 修復