feat: HSAP platform v2 — modular navigation, quality review, audit log, world model simulation
Major changes: - New frontend (platform/web/): Vite + React 18 + TypeScript + Tailwind - 4-module navigation: 数据送标 / 模型管理 / 车队管理 / 系统管理 - Data catalog with charts (DMS/ADAS/Lane 3-tab view) - Quality review workflow (标注质检): Good/Fine/Bad scoring with auto-advance - Audit enhancements: batch operations, rejection categories, Feishu notifications - Operation audit log (操作日志) - World model simulation studio (仿真工坊) - Dataset version management with snapshots and diff - ADAS 7-class dataset integration (138K images organized + compressed) - User management with Feishu integration and pagination - CRUD/search/filter on all pages, card layout redesign - PIL-optimized image overlay rendering - Auto-snapshot on build, in_review workflow stage - Removed embedded algorithm code (now in workspace)
This commit is contained in:
94
platform/as_platform/labeling/lock.py
Normal file
94
platform/as_platform/labeling/lock.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""标注任务 Redis 互斥锁(campaign + task 粒度)。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from as_platform.redis.bus import get_redis
|
||||
|
||||
LOCK_TTL_SEC = 300
|
||||
_LOCK_PREFIX = "labeling:lock:"
|
||||
# API 进程内回退(无 Redis 时)
|
||||
_memory: dict[str, dict[str, Any]] = {}
|
||||
|
||||
|
||||
def _key(campaign_id: str, task_id: str) -> str:
|
||||
return f"{_LOCK_PREFIX}{campaign_id}:{task_id}"
|
||||
|
||||
|
||||
def _parse_holder(raw: str | None) -> dict[str, Any] | None:
|
||||
if not raw:
|
||||
return None
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
return {"name": raw}
|
||||
|
||||
|
||||
def acquire_lock(campaign_id: str, task_id: str, *, user_id: int, user_name: str) -> dict[str, Any]:
|
||||
payload = json.dumps({"user_id": user_id, "name": user_name}, ensure_ascii=False)
|
||||
r = get_redis()
|
||||
if not r:
|
||||
now = time.time()
|
||||
mem = _memory.get(_key(campaign_id, task_id))
|
||||
if mem and mem.get("user_id") != user_id and now < mem.get("expires_at", 0):
|
||||
return {"ok": False, "holder": mem.get("name"), "user_id": mem.get("user_id")}
|
||||
_memory[_key(campaign_id, task_id)] = {
|
||||
"user_id": user_id,
|
||||
"name": user_name,
|
||||
"expires_at": now + LOCK_TTL_SEC,
|
||||
}
|
||||
return {"ok": True, "holder": user_name, "ttl_sec": LOCK_TTL_SEC, "backend": "memory"}
|
||||
|
||||
key = _key(campaign_id, task_id)
|
||||
if r.set(key, payload, nx=True, ex=LOCK_TTL_SEC):
|
||||
return {"ok": True, "holder": user_name, "ttl_sec": LOCK_TTL_SEC, "backend": "redis"}
|
||||
existing = _parse_holder(r.get(key))
|
||||
if existing and existing.get("user_id") == user_id:
|
||||
r.expire(key, LOCK_TTL_SEC)
|
||||
return {"ok": True, "holder": user_name, "ttl_sec": LOCK_TTL_SEC, "renewed": True, "backend": "redis"}
|
||||
return {
|
||||
"ok": False,
|
||||
"holder": (existing or {}).get("name"),
|
||||
"user_id": (existing or {}).get("user_id"),
|
||||
"backend": "redis",
|
||||
}
|
||||
|
||||
|
||||
def release_lock(campaign_id: str, task_id: str, *, user_id: int) -> dict[str, Any]:
|
||||
r = get_redis()
|
||||
if not r:
|
||||
key = _key(campaign_id, task_id)
|
||||
mem = _memory.get(key)
|
||||
if mem and mem.get("user_id") == user_id:
|
||||
_memory.pop(key, None)
|
||||
return {"ok": True, "released": True, "backend": "memory"}
|
||||
return {"ok": True, "released": False, "backend": "memory"}
|
||||
|
||||
key = _key(campaign_id, task_id)
|
||||
existing = _parse_holder(r.get(key))
|
||||
if not existing:
|
||||
return {"ok": True, "released": False, "backend": "redis"}
|
||||
if existing.get("user_id") != user_id:
|
||||
return {"ok": False, "holder": existing.get("name"), "backend": "redis"}
|
||||
r.delete(key)
|
||||
return {"ok": True, "released": True, "backend": "redis"}
|
||||
|
||||
|
||||
def renew_lock(campaign_id: str, task_id: str, *, user_id: int) -> dict[str, Any]:
|
||||
r = get_redis()
|
||||
if not r:
|
||||
key = _key(campaign_id, task_id)
|
||||
mem = _memory.get(key)
|
||||
if mem and mem.get("user_id") == user_id:
|
||||
mem["expires_at"] = time.time() + LOCK_TTL_SEC
|
||||
return {"ok": True, "ttl_sec": LOCK_TTL_SEC, "backend": "memory"}
|
||||
return {"ok": False, "backend": "memory"}
|
||||
|
||||
key = _key(campaign_id, task_id)
|
||||
existing = _parse_holder(r.get(key))
|
||||
if not existing or existing.get("user_id") != user_id:
|
||||
return {"ok": False, "holder": (existing or {}).get("name"), "backend": "redis"}
|
||||
r.expire(key, LOCK_TTL_SEC)
|
||||
return {"ok": True, "ttl_sec": LOCK_TTL_SEC, "backend": "redis"}
|
||||
Reference in New Issue
Block a user