126 lines
4.3 KiB
Python
126 lines
4.3 KiB
Python
|
|
"""
|
||
|
|
操作审计日志 API
|
||
|
|
"""
|
||
|
|
import logging
|
||
|
|
from datetime import datetime
|
||
|
|
from typing import Optional, List
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, Query, HTTPException, status
|
||
|
|
from sqlalchemy.orm import Session
|
||
|
|
from sqlalchemy import func, desc
|
||
|
|
from pydantic import BaseModel
|
||
|
|
|
||
|
|
from app.core.database import get_db
|
||
|
|
from app.api.auth import get_current_user
|
||
|
|
from app.models.user import User
|
||
|
|
from app.models.audit_log import AuditLog
|
||
|
|
|
||
|
|
logger = logging.getLogger(__name__)
|
||
|
|
|
||
|
|
router = APIRouter(
|
||
|
|
prefix="/api/v1/audit-logs",
|
||
|
|
tags=["audit-logs"],
|
||
|
|
responses={
|
||
|
|
401: {"description": "未授权"},
|
||
|
|
403: {"description": "无权访问"},
|
||
|
|
500: {"description": "服务器内部错误"}
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
# ── Pydantic Schemas ─────────────────────────────────────────────
|
||
|
|
|
||
|
|
class AuditLogItem(BaseModel):
|
||
|
|
id: str
|
||
|
|
username: str
|
||
|
|
action: str
|
||
|
|
resource_type: str
|
||
|
|
resource_id: Optional[str] = None
|
||
|
|
resource_name: Optional[str] = None
|
||
|
|
detail: Optional[dict] = None
|
||
|
|
ip_address: Optional[str] = None
|
||
|
|
status: str
|
||
|
|
created_at: datetime
|
||
|
|
|
||
|
|
class Config:
|
||
|
|
from_attributes = True
|
||
|
|
|
||
|
|
|
||
|
|
class AuditLogStats(BaseModel):
|
||
|
|
total: int
|
||
|
|
by_action: dict # {"CREATE": 10, "UPDATE": 5, ...}
|
||
|
|
by_resource: dict # {"agent": 8, "workflow": 7, ...}
|
||
|
|
|
||
|
|
|
||
|
|
# ── Helpers ──────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def _check_admin(current_user: User):
|
||
|
|
if getattr(current_user, "role", None) != "admin":
|
||
|
|
from app.core.exceptions import ForbiddenError
|
||
|
|
raise ForbiddenError("仅管理员可访问审计日志")
|
||
|
|
|
||
|
|
|
||
|
|
# ── Endpoints ────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
@router.get("", response_model=List[AuditLogItem])
|
||
|
|
async def get_audit_logs(
|
||
|
|
db: Session = Depends(get_db),
|
||
|
|
current_user: User = Depends(get_current_user),
|
||
|
|
user_id: Optional[str] = Query(None, description="按用户ID过滤"),
|
||
|
|
action: Optional[str] = Query(None, description="操作类型: CREATE/UPDATE/DELETE/EXECUTE/LOGIN"),
|
||
|
|
resource_type: Optional[str] = Query(None, description="资源类型: agent/workflow/user"),
|
||
|
|
status: Optional[str] = Query(None, description="操作状态: success/failure"),
|
||
|
|
start_date: Optional[str] = Query(None, description="开始时间 ISO格式"),
|
||
|
|
end_date: Optional[str] = Query(None, description="结束时间 ISO格式"),
|
||
|
|
keyword: Optional[str] = Query(None, description="资源名称关键词搜索"),
|
||
|
|
skip: int = Query(0, ge=0),
|
||
|
|
limit: int = Query(50, ge=1, le=1000),
|
||
|
|
):
|
||
|
|
"""查询操作审计日志(仅管理员)"""
|
||
|
|
_check_admin(current_user)
|
||
|
|
|
||
|
|
query = db.query(AuditLog)
|
||
|
|
|
||
|
|
if user_id:
|
||
|
|
query = query.filter(AuditLog.user_id == user_id)
|
||
|
|
if action:
|
||
|
|
query = query.filter(AuditLog.action == action.upper())
|
||
|
|
if resource_type:
|
||
|
|
query = query.filter(AuditLog.resource_type == resource_type)
|
||
|
|
if status:
|
||
|
|
query = query.filter(AuditLog.status == status)
|
||
|
|
if keyword:
|
||
|
|
query = query.filter(AuditLog.resource_name.contains(keyword))
|
||
|
|
if start_date:
|
||
|
|
query = query.filter(AuditLog.created_at >= datetime.fromisoformat(start_date))
|
||
|
|
if end_date:
|
||
|
|
query = query.filter(AuditLog.created_at <= datetime.fromisoformat(end_date))
|
||
|
|
|
||
|
|
total = query.count()
|
||
|
|
logs = query.order_by(desc(AuditLog.created_at)).offset(skip).limit(limit).all()
|
||
|
|
|
||
|
|
return logs
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/stats", response_model=AuditLogStats)
|
||
|
|
async def get_audit_logs_stats(
|
||
|
|
db: Session = Depends(get_db),
|
||
|
|
current_user: User = Depends(get_current_user),
|
||
|
|
):
|
||
|
|
"""获取审计日志统计(仅管理员)"""
|
||
|
|
_check_admin(current_user)
|
||
|
|
|
||
|
|
total = db.query(func.count(AuditLog.id)).scalar() or 0
|
||
|
|
|
||
|
|
action_rows = db.query(
|
||
|
|
AuditLog.action, func.count(AuditLog.id)
|
||
|
|
).group_by(AuditLog.action).all()
|
||
|
|
by_action = {row[0]: row[1] for row in action_rows}
|
||
|
|
|
||
|
|
resource_rows = db.query(
|
||
|
|
AuditLog.resource_type, func.count(AuditLog.id)
|
||
|
|
).group_by(AuditLog.resource_type).all()
|
||
|
|
by_resource = {row[0]: row[1] for row in resource_rows}
|
||
|
|
|
||
|
|
return {"total": total, "by_action": by_action, "by_resource": by_resource}
|