feat: 平台治理与权限体系完善,存储进度/GPU预留/审批中心与日志整合
- 平台治理: 租户用户权限层次、资源ACL、审批中心与审批模板、访问申请 - 存储: MinIO 存储进度迁移、对象存储安全加固与测试 - 计算: GPU 资源预留、compute 轮询与同步增强 - 权限: permission v2 迁移、权限安全验收测试 - 日志: 后端运行日志中文说明、操作日志整合 - 数据处理/评测: 数据转换与模型评测优化 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,7 @@ from typing import Any, Callable, Optional, TypeVar
|
||||
|
||||
from fastapi import Request
|
||||
|
||||
from app.core.logging import get_logger, request_id_var
|
||||
from app.core.logging import get_client_ip, get_logger, mask_sensitive_string, request_id_var
|
||||
|
||||
logger = get_logger("app.audit")
|
||||
|
||||
@@ -67,12 +67,25 @@ def audit_log(
|
||||
detail=detail,
|
||||
trace_id=trace_id,
|
||||
duration_ms=elapsed_ms,
|
||||
kwargs=kwargs,
|
||||
args=args,
|
||||
)
|
||||
return result
|
||||
except Exception:
|
||||
logger.error(
|
||||
"审计日志记录失败 action=%s", action, exc_info=True
|
||||
except Exception as exc:
|
||||
_record_audit(
|
||||
action=action,
|
||||
actor_id=_extract_actor_id(kwargs),
|
||||
target_type=target_type,
|
||||
target_id=_extract_target_id(None, kwargs, extract_target_id),
|
||||
detail=_build_detail(detail_template, kwargs),
|
||||
trace_id=trace_id,
|
||||
duration_ms=(time.perf_counter() - started_at) * 1000,
|
||||
result="failure",
|
||||
reason=_safe_exception_reason(exc),
|
||||
kwargs=kwargs,
|
||||
args=args,
|
||||
)
|
||||
logger.warning("业务操作失败 action=%s reason=%s", action, _safe_exception_reason(exc))
|
||||
raise
|
||||
|
||||
return async_wrapper # type: ignore
|
||||
@@ -94,12 +107,25 @@ def audit_log(
|
||||
detail=detail,
|
||||
trace_id=trace_id,
|
||||
duration_ms=elapsed_ms,
|
||||
kwargs=kwargs,
|
||||
args=args,
|
||||
)
|
||||
return result
|
||||
except Exception:
|
||||
logger.error(
|
||||
"审计日志记录失败 action=%s", action, exc_info=True
|
||||
except Exception as exc:
|
||||
_record_audit(
|
||||
action=action,
|
||||
actor_id=_extract_actor_id(kwargs),
|
||||
target_type=target_type,
|
||||
target_id=_extract_target_id(None, kwargs, extract_target_id),
|
||||
detail=_build_detail(detail_template, kwargs),
|
||||
trace_id=trace_id,
|
||||
duration_ms=(time.perf_counter() - started_at) * 1000,
|
||||
result="failure",
|
||||
reason=_safe_exception_reason(exc),
|
||||
kwargs=kwargs,
|
||||
args=args,
|
||||
)
|
||||
logger.warning("业务操作失败 action=%s reason=%s", action, _safe_exception_reason(exc))
|
||||
raise
|
||||
|
||||
return sync_wrapper # type: ignore
|
||||
@@ -144,18 +170,36 @@ def _record_audit(
|
||||
detail: str,
|
||||
trace_id: str,
|
||||
duration_ms: float,
|
||||
*,
|
||||
kwargs: dict[str, Any] | None = None,
|
||||
args: tuple[Any, ...] = (),
|
||||
result: str = "success",
|
||||
reason: str | None = None,
|
||||
) -> None:
|
||||
"""通过已有的 record_audit 方法写入审计日志"""
|
||||
try:
|
||||
from app.db.platform_store import get_platform_store
|
||||
|
||||
store = get_platform_store()
|
||||
kwargs = kwargs or {}
|
||||
request = _extract_request(args, kwargs)
|
||||
current_user = kwargs.get("current_user") or kwargs.get("user") or {}
|
||||
request_id = request.headers.get("X-Request-ID") if request else None
|
||||
request_id = request_id or trace_id
|
||||
client_ip = get_client_ip(request) or None
|
||||
detail_text = f"{detail} trace_id={trace_id} duration_ms={duration_ms:.1f}" if detail else f"trace_id={trace_id} duration_ms={duration_ms:.1f}"
|
||||
store.record_audit(
|
||||
action=action,
|
||||
actor_id=actor_id,
|
||||
target_type=target_type or None,
|
||||
target_id=target_id,
|
||||
detail=f"{detail} trace_id={trace_id} duration_ms={duration_ms:.1f}" if detail else f"trace_id={trace_id} duration_ms={duration_ms:.1f}",
|
||||
tenant_id=str(current_user.get("tenant_id") or "") or None,
|
||||
detail=mask_sensitive_string(detail_text),
|
||||
result=result,
|
||||
reason=mask_sensitive_string(reason or "") or None,
|
||||
request_id=request_id,
|
||||
session_id=str(current_user.get("session_id") or "") or None,
|
||||
ip=client_ip,
|
||||
)
|
||||
except Exception:
|
||||
logger.error("写入审计日志失败 action=%s", action, exc_info=True)
|
||||
@@ -170,6 +214,19 @@ def _extract_actor_id(kwargs: dict) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def _extract_request(args: tuple[Any, ...], kwargs: dict[str, Any]) -> Request | None:
|
||||
for value in tuple(kwargs.values()) + tuple(args):
|
||||
if isinstance(value, Request):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _safe_exception_reason(exc: Exception) -> str:
|
||||
"""Keep audit failures useful without recording credentials or tokens."""
|
||||
value = getattr(exc, "detail", None) or str(exc) or exc.__class__.__name__
|
||||
return mask_sensitive_string(str(value))[:500]
|
||||
|
||||
|
||||
# ==================== 预定义的审计操作常量 ====================
|
||||
|
||||
class AuditActions:
|
||||
|
||||
Reference in New Issue
Block a user