完善权限缓存与监控指标
This commit is contained in:
@@ -52,6 +52,35 @@ class PermissionCache:
|
||||
self._member_role_cache[cache_key] = (role, time.time())
|
||||
return role
|
||||
|
||||
def get_project_permissions(
|
||||
self,
|
||||
study_id: uuid.UUID,
|
||||
ttl: int | None = None,
|
||||
) -> dict[str, dict[str, bool]] | None:
|
||||
"""获取项目接口权限覆盖表缓存"""
|
||||
if ttl is None:
|
||||
ttl = self.default_ttl
|
||||
|
||||
cache_key = self._make_project_cache_key(study_id)
|
||||
if cache_key not in self._project_permissions_cache:
|
||||
return None
|
||||
|
||||
permissions, timestamp = self._project_permissions_cache[cache_key]
|
||||
if self._is_expired(timestamp, ttl):
|
||||
self._project_permissions_cache.pop(cache_key, None)
|
||||
return None
|
||||
|
||||
return permissions
|
||||
|
||||
def set_project_permissions(
|
||||
self,
|
||||
study_id: uuid.UUID,
|
||||
permissions: dict[str, dict[str, bool]],
|
||||
) -> None:
|
||||
"""缓存项目接口权限覆盖表"""
|
||||
cache_key = self._make_project_cache_key(study_id)
|
||||
self._project_permissions_cache[cache_key] = (permissions, time.time())
|
||||
|
||||
def invalidate_project_permissions(self, study_id: uuid.UUID) -> None:
|
||||
cache_key = self._make_project_cache_key(study_id)
|
||||
self._project_permissions_cache.pop(cache_key, None)
|
||||
|
||||
@@ -17,6 +17,9 @@ from typing import Any
|
||||
from app.core.permission_cache import get_permission_cache
|
||||
|
||||
|
||||
CACHE_HIT_RATE_HEALTH_MIN_ACCESSES = 10
|
||||
|
||||
|
||||
@dataclass
|
||||
class PermissionCheckMetrics:
|
||||
"""权限检查指标"""
|
||||
@@ -293,3 +296,39 @@ def set_permission_monitor(monitor: PermissionMonitor) -> None:
|
||||
"""设置全局权限监控器实例(用于测试)"""
|
||||
global _permission_monitor
|
||||
_permission_monitor = monitor
|
||||
|
||||
|
||||
def evaluate_permission_system_health(metrics: dict[str, Any], cache_stats: dict[str, Any]) -> dict[str, Any]:
|
||||
"""根据权限系统指标评估健康状态"""
|
||||
check_metrics = metrics["check_metrics"]
|
||||
cache_metrics = metrics["cache_metrics"]
|
||||
|
||||
health_score = 100
|
||||
issues = []
|
||||
|
||||
if check_metrics["error_rate"] > 1:
|
||||
health_score -= 20
|
||||
issues.append("权限检查错误率过高")
|
||||
|
||||
if (
|
||||
cache_metrics["total_accesses"] >= CACHE_HIT_RATE_HEALTH_MIN_ACCESSES
|
||||
and cache_metrics["hit_rate"] < 50
|
||||
):
|
||||
health_score -= 10
|
||||
issues.append("缓存命中率过低")
|
||||
|
||||
if check_metrics["avg_time"] > 0.01:
|
||||
health_score -= 10
|
||||
issues.append("权限检查响应时间过长")
|
||||
|
||||
if check_metrics["deny_rate"] > 50:
|
||||
health_score -= 5
|
||||
issues.append("权限拒绝率过高")
|
||||
|
||||
return {
|
||||
"status": "healthy" if health_score >= 80 else "degraded" if health_score >= 50 else "unhealthy",
|
||||
"health_score": max(0, health_score),
|
||||
"issues": issues,
|
||||
"metrics": metrics,
|
||||
"cache_stats": cache_stats,
|
||||
}
|
||||
|
||||
@@ -10,6 +10,35 @@ from app.core.api_permissions import API_ENDPOINT_PERMISSIONS, OPERATION_PREREQU
|
||||
from app.core.permission_cache import get_permission_cache
|
||||
|
||||
|
||||
async def _get_project_permission_overrides(
|
||||
db: AsyncSession,
|
||||
study_id: uuid.UUID,
|
||||
) -> dict[str, dict[str, bool]]:
|
||||
cache = get_permission_cache()
|
||||
cached_permissions = cache.get_project_permissions(study_id)
|
||||
from app.core.permission_monitor import get_permission_monitor
|
||||
|
||||
monitor = get_permission_monitor()
|
||||
if cached_permissions is not None:
|
||||
monitor.record_cache_hit()
|
||||
return cached_permissions
|
||||
|
||||
monitor.record_cache_miss()
|
||||
result = await db.execute(
|
||||
select(ApiEndpointPermission).where(
|
||||
ApiEndpointPermission.study_id == study_id,
|
||||
)
|
||||
)
|
||||
rows = result.scalars().all()
|
||||
|
||||
permissions: dict[str, dict[str, bool]] = {}
|
||||
for row in rows:
|
||||
permissions.setdefault(row.role, {})[row.endpoint_key] = row.allowed
|
||||
|
||||
cache.set_project_permissions(study_id, permissions)
|
||||
return permissions
|
||||
|
||||
|
||||
async def role_has_api_permission(
|
||||
db: AsyncSession,
|
||||
study_id: uuid.UUID,
|
||||
@@ -21,18 +50,12 @@ async def role_has_api_permission(
|
||||
if role == "ADMIN":
|
||||
return True
|
||||
|
||||
result = await db.execute(
|
||||
select(ApiEndpointPermission).where(
|
||||
ApiEndpointPermission.study_id == study_id,
|
||||
ApiEndpointPermission.role == role,
|
||||
ApiEndpointPermission.endpoint_key == endpoint_key,
|
||||
)
|
||||
)
|
||||
perm = result.scalar_one_or_none()
|
||||
if perm is None:
|
||||
permissions = await _get_project_permission_overrides(db, study_id)
|
||||
allowed = permissions.get(role or "", {}).get(endpoint_key)
|
||||
if allowed is None:
|
||||
return False
|
||||
|
||||
if not perm.allowed:
|
||||
if not allowed:
|
||||
return False
|
||||
|
||||
if check_prerequisites:
|
||||
@@ -77,12 +100,7 @@ async def get_api_endpoint_permissions(
|
||||
|
||||
返回格式: {role: {endpoint_key: {allowed: bool}}}
|
||||
"""
|
||||
result = await db.execute(
|
||||
select(ApiEndpointPermission).where(
|
||||
ApiEndpointPermission.study_id == study_id,
|
||||
)
|
||||
)
|
||||
rows = result.scalars().all()
|
||||
overrides = await _get_project_permission_overrides(db, study_id)
|
||||
|
||||
roles = ["PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA"]
|
||||
matrix: dict[str, dict[str, dict[str, bool]]] = {}
|
||||
@@ -92,10 +110,11 @@ async def get_api_endpoint_permissions(
|
||||
default_allowed = role in config.get("default_roles", [])
|
||||
matrix[role][endpoint_key] = {"allowed": default_allowed}
|
||||
|
||||
for row in rows:
|
||||
if row.role not in matrix:
|
||||
matrix[row.role] = {}
|
||||
matrix[row.role][row.endpoint_key] = {"allowed": row.allowed}
|
||||
for role, endpoints in overrides.items():
|
||||
if role not in matrix:
|
||||
matrix[role] = {}
|
||||
for endpoint_key, allowed in endpoints.items():
|
||||
matrix[role][endpoint_key] = {"allowed": allowed}
|
||||
|
||||
return matrix
|
||||
|
||||
@@ -132,5 +151,8 @@ async def replace_api_endpoint_permissions(
|
||||
cache = get_permission_cache()
|
||||
cache.invalidate_project_permissions(study_id)
|
||||
cache.invalidate_all_member_roles(study_id)
|
||||
from app.core.permission_monitor import get_permission_monitor
|
||||
|
||||
get_permission_monitor().record_cache_invalidation()
|
||||
|
||||
return await get_api_endpoint_permissions(db, study_id)
|
||||
|
||||
Reference in New Issue
Block a user