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)
95 lines
3.4 KiB
Python
95 lines
3.4 KiB
Python
"""标注任务 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"}
|