Files
aiagent/backend/app/api/audit_logs.py

126 lines
4.3 KiB
Python
Raw Normal View History

"""
操作审计日志 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}