From b254fa0985aad87ce067412fcb02b4e88967f62b Mon Sep 17 00:00:00 2001 From: wangjiming Date: Wed, 19 Aug 2026 10:32:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=99=AE=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=AF=B9=E6=A8=A1=E5=9E=8B=E8=AE=AD=E7=BB=83=E7=9A=84?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/endpoints/platform.py | 44 +++++++++++++++++-- backend/app/db/platform_store.py | 15 ++++++- .../views/fine-tune/FineTuneCreateView.vue | 9 +++- 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/backend/app/api/v1/endpoints/platform.py b/backend/app/api/v1/endpoints/platform.py index f8142c0..eb3ab1d 100644 --- a/backend/app/api/v1/endpoints/platform.py +++ b/backend/app/api/v1/endpoints/platform.py @@ -225,12 +225,16 @@ def _require_approval_or_admin( """ 高风险操作审批旁路: - admin 用户直接放行(返回 None) - - 普通用户创建审批实例,返回审批待定响应(code=202,非 None) + - 资源创建者(Owner)直接放行(返回 None) + - 其他普通用户创建审批实例,返回审批待定响应(code=202,非 None) code=202 使前端响应拦截器走业务错误分支,弹提示并 reject, 避免前端误认为删除成功。 """ if is_admin(current_user): return None + # 资源创建者直接放行,无需审批 + if _check_owner(resource_type, resource_id, current_user.get("id")): + return None store = get_platform_store() instance = store.create_approval_instance({ "resource_type": resource_type, @@ -245,6 +249,28 @@ def _require_approval_or_admin( } +def _check_owner(resource_type: str, resource_id: str, user_id: str | None) -> bool: + """直接查数据库判断 user_id 是否为资源的 created_by。""" + from app.core.auth import OWNER_TABLES + table_info = OWNER_TABLES.get(resource_type) + if not table_info: + return False + table, column = table_info + store = get_platform_store() + with store.connect() as conn: + row = conn.execute(f"SELECT {column} FROM {table} WHERE id=?", (resource_id,)).fetchone() + if not row: + return False + owner = row[column] + if column == "payload": + try: + import json + owner = json.loads(owner or "{}").get("created_by") + except (TypeError, ValueError): + owner = None + return owner == user_id + + def _node_for_task(task: dict[str, Any]) -> dict[str, Any] | None: return next((node for node in get_platform_store().compute_nodes() if node["id"] == task.get("compute_node_id")), None) @@ -1457,8 +1483,11 @@ async def fine_tune_list(current_user: dict = Depends(get_current_user)) -> dict tasks = get_platform_store().tasks() if current_user.get("role") == "admin" or current_user.get("protected"): return ok(tasks) + # 普通用户可见:自己创建的 + ACL 授权的 + user_id = current_user.get("id") accessible = set(filter_accessible_resource_ids("fine-tune", [t["id"] for t in tasks], current_user)) - return ok([t for t in tasks if t["id"] in accessible]) + result = [t for t in tasks if t.get("created_by") == user_id or t["id"] in accessible] + return ok(result) @router.post("/fine-tune") @@ -1740,8 +1769,11 @@ async def model_eval_list(current_user: dict = Depends(get_current_user)) -> dic tasks = get_platform_store().eval_tasks() if current_user.get("role") == "admin" or current_user.get("protected"): return ok(tasks) + # 普通用户可见:自己创建的 + ACL 授权的 + user_id = current_user.get("id") accessible = set(filter_accessible_resource_ids("eval", [t["id"] for t in tasks], current_user)) - return ok([t for t in tasks if t["id"] in accessible]) + result = [t for t in tasks if t.get("created_by") == user_id or t["id"] in accessible] + return ok(result) @router.get("/model-eval/{task_id}") @@ -1777,6 +1809,7 @@ async def model_eval_start(payload: dict[str, Any] = Body(...), current_user: di """Start an evaluation task: submit eval job to compute node.""" store = get_platform_store() # 1. Create eval task record + payload.setdefault("created_by", current_user.get("id")) task = store.create_eval_task({**payload, "status": "pending"}) # 2. Resolve model path (supports both regular models and trained models) @@ -1978,8 +2011,11 @@ async def model_compare_list(current_user: dict = Depends(get_current_user)) -> tasks = get_platform_store().compare_tasks() if is_admin(current_user): return ok(tasks) + # 普通用户可见:自己创建的 + ACL 授权的 + user_id = current_user.get("id") accessible = filter_accessible_resource_ids_batch("compare", [item["id"] for item in tasks], current_user) - return ok([item for item in tasks if item["id"] in accessible]) + result = [item for item in tasks if item.get("created_by") == user_id or item["id"] in accessible] + return ok(result) @router.post("/model-compare") diff --git a/backend/app/db/platform_store.py b/backend/app/db/platform_store.py index 2746081..2a2f5dd 100644 --- a/backend/app/db/platform_store.py +++ b/backend/app/db/platform_store.py @@ -570,6 +570,16 @@ class PlatformStore: """) except Exception: pass # 列不存在时忽略 + # 修复历史数据:为 eval_tasks 表回填 created_by(从 payload JSON 中提取) + try: + conn.execute(""" + UPDATE eval_tasks SET created_by = payload::json->>'created_by' + WHERE (created_by IS NULL OR created_by = '') + AND payload IS NOT NULL + AND payload::json->>'created_by' IS NOT NULL + """) + except Exception: + pass # 列或语法不支持时忽略 # 清理超过 7 天的操作日志 try: cutoff = (datetime.now(timezone.utc) - timedelta(days=7)).isoformat() @@ -2031,6 +2041,7 @@ class PlatformStore: "process_id": None, "train_duration": "", "create_time": now, + "created_by": payload.get("created_by"), } with self.connect() as conn: conn.execute( @@ -2529,8 +2540,8 @@ class PlatformStore: if data.get("metric") == "custom": data["metric"] = data["metric_label"] conn.execute( - "INSERT INTO eval_tasks (id, name, payload, status, create_time) VALUES (?, ?, ?, ?, ?)", - (task_id, name, json_dumps(data), status, now), + "INSERT INTO eval_tasks (id, name, payload, status, create_time, created_by) VALUES (?, ?, ?, ?, ?, ?)", + (task_id, name, json_dumps(data), status, now, data.get("created_by")), ) return self.eval_task(task_id) diff --git a/frontend/src/views/fine-tune/FineTuneCreateView.vue b/frontend/src/views/fine-tune/FineTuneCreateView.vue index cd20d34..6215554 100644 --- a/frontend/src/views/fine-tune/FineTuneCreateView.vue +++ b/frontend/src/views/fine-tune/FineTuneCreateView.vue @@ -1,4 +1,4 @@ -