新增外部系统限界上下文的完整路由体系,包含聚合路由层与23个子领域路由,覆盖外部系统全生命周期管理、健康检查、指标监控、审计日志、Token管理、通知渠道、回收站等功能,并将外部系统路由挂载到全局API前缀下。
232 lines
8.0 KiB
Python
232 lines
8.0 KiB
Python
"""AuditLog 子域 Router。
|
||
|
||
外部系统限界上下文的审计日志查询 API,覆盖审计日志列表 / 详情 / 导出。
|
||
所有端点通过 ``create_use_cases_from_db`` 装配 use_cases,经 ``audit_log_service``
|
||
端口调用用例。
|
||
|
||
路径顺序约束:静态路径 ``/export`` 必须在 ``/{log_id}`` 前声明,避免被路径参数捕获。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from datetime import UTC, datetime
|
||
from typing import Any
|
||
|
||
from fastapi import APIRouter, Depends, Query
|
||
from fastapi.responses import StreamingResponse
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||
from yuxi.external_systems.use_cases.dto.audit_log import (
|
||
ExportAuditLogsInput,
|
||
GetAuditLogDetailInput,
|
||
GetAuditLogStatsInput,
|
||
ListActionTypesInput,
|
||
ListAuditLogsInput,
|
||
ListConfigChangesInput,
|
||
ListEventTypesInput,
|
||
ListOperatorsInput,
|
||
)
|
||
from yuxi.storage.postgres.models_business import User
|
||
|
||
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
|
||
|
||
audit_log_router = APIRouter(
|
||
prefix="/audit-logs",
|
||
tags=["external-systems-audit-log"],
|
||
)
|
||
|
||
|
||
# =============================================================================
|
||
# === 静态路径端点(必须在 /{log_id} 之前声明) ===
|
||
# =============================================================================
|
||
|
||
|
||
@audit_log_router.get("/export", response_model=dict)
|
||
async def export_audit_logs(
|
||
system_id: int | None = Query(None),
|
||
env_key: str | None = Query(None),
|
||
event_type: str | None = Query(None),
|
||
action_type: str | None = Query(None),
|
||
resource: str | None = Query(None),
|
||
user: str | None = Query(None),
|
||
start_time: datetime | None = Query(None),
|
||
end_time: datetime | None = Query(None),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
):
|
||
"""导出审计日志为 JSON 字节流。
|
||
|
||
审计日志含跨系统敏感操作记录,导出需管理员权限。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ExportAuditLogsInput(
|
||
system_id=system_id,
|
||
env_key=env_key,
|
||
event_type=event_type,
|
||
action_type=action_type,
|
||
resource=resource,
|
||
user=user,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
)
|
||
output = await use_cases.audit_log_service.export_audit_logs(input_dto)
|
||
|
||
payload = json.dumps(
|
||
[item.model_dump() for item in output.items],
|
||
ensure_ascii=False,
|
||
default=str,
|
||
).encode("utf-8")
|
||
timestamp = datetime.now(UTC).strftime("%Y%m%d%H%M%S")
|
||
|
||
async def _stream():
|
||
yield payload
|
||
|
||
return StreamingResponse(
|
||
_stream(),
|
||
media_type="application/octet-stream",
|
||
headers={
|
||
"Content-Disposition": f"attachment; filename=audit_logs_{timestamp}.json",
|
||
},
|
||
)
|
||
|
||
|
||
@audit_log_router.get("", response_model=dict)
|
||
async def list_audit_logs(
|
||
limit: int = Query(100, ge=1, le=500),
|
||
offset: int = Query(0, ge=0),
|
||
system_id: int | None = Query(None),
|
||
env_key: str | None = Query(None),
|
||
event_type: str | None = Query(None),
|
||
action_type: str | None = Query(None),
|
||
resource: str | None = Query(None),
|
||
user: str | None = Query(None),
|
||
start_time: datetime | None = Query(None),
|
||
end_time: datetime | None = Query(None),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""分页列出审计日志。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListAuditLogsInput(
|
||
limit=limit,
|
||
offset=offset,
|
||
system_id=system_id,
|
||
env_key=env_key,
|
||
event_type=event_type,
|
||
action_type=action_type,
|
||
resource=resource,
|
||
user=user,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
)
|
||
output = await use_cases.audit_log_service.list_audit_logs(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@audit_log_router.get("/stats", response_model=dict)
|
||
async def get_audit_log_stats(
|
||
system_id: int | None = Query(None),
|
||
start_time: datetime | None = Query(None),
|
||
end_time: datetime | None = Query(None),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询审计日志统计聚合(按 action_type 分组)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetAuditLogStatsInput(
|
||
system_id=system_id,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
)
|
||
output = await use_cases.audit_log_service.get_audit_log_stats(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@audit_log_router.get("/config-changes", response_model=dict)
|
||
async def list_config_changes(
|
||
limit: int = Query(100, ge=1, le=500),
|
||
offset: int = Query(0, ge=0),
|
||
system_id: int | None = Query(None),
|
||
env_key: str | None = Query(None),
|
||
action_type: str | None = Query(None),
|
||
resource: str | None = Query(None),
|
||
user: str | None = Query(None),
|
||
start_time: datetime | None = Query(None),
|
||
end_time: datetime | None = Query(None),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询配置变更历史(含 snapshot_before / snapshot_after 的审计日志)。
|
||
|
||
``action_type`` 默认不限制,返回所有含快照的记录(CREATE/UPDATE/DELETE 等)。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListConfigChangesInput(
|
||
limit=limit,
|
||
offset=offset,
|
||
system_id=system_id,
|
||
env_key=env_key,
|
||
action_type=action_type,
|
||
resource=resource,
|
||
user=user,
|
||
start_time=start_time,
|
||
end_time=end_time,
|
||
)
|
||
output = await use_cases.audit_log_service.list_config_changes(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@audit_log_router.get("/action-types", response_model=dict)
|
||
async def list_action_types(
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询审计日志中已出现的操作类型列表。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListActionTypesInput()
|
||
output = await use_cases.audit_log_service.list_action_types(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@audit_log_router.get("/event-types", response_model=dict)
|
||
async def list_event_types(
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询审计日志中已出现的事件类型列表。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListEventTypesInput()
|
||
output = await use_cases.audit_log_service.list_event_types(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@audit_log_router.get("/operators", response_model=dict)
|
||
async def list_operators(
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询审计日志中已出现的操作人列表。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListOperatorsInput()
|
||
output = await use_cases.audit_log_service.list_operators(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
# =============================================================================
|
||
# === 动态路径端点 /{log_id} ===
|
||
# =============================================================================
|
||
|
||
|
||
@audit_log_router.get("/{log_id}", response_model=dict)
|
||
async def get_audit_log_detail(
|
||
log_id: int,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取审计日志详情。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetAuditLogDetailInput(id=log_id)
|
||
output = await use_cases.audit_log_service.get_audit_log_detail(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|