75 lines
2.6 KiB
Python
75 lines
2.6 KiB
Python
"""审计日志查询服务。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Optional
|
|
|
|
from sqlalchemy import and_, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from ..models import OperationLog
|
|
from ..utils.audited_write import format_beijing_datetime
|
|
from ..utils.timezone import CN_TZ
|
|
|
|
|
|
def _to_beijing_naive(value: Optional[datetime]) -> Optional[datetime]:
|
|
if value is None:
|
|
return None
|
|
if value.tzinfo is None:
|
|
return value
|
|
return value.astimezone(CN_TZ).replace(tzinfo=None)
|
|
|
|
|
|
def _serialize_log(row: OperationLog) -> dict[str, Any]:
|
|
return {
|
|
"id": int(row.id),
|
|
"tenant_id": int(row.tenant_id),
|
|
"user_id": int(row.user_id),
|
|
"project_id": int(row.project_id) if row.project_id is not None else None,
|
|
"entity_type": str(row.entity_type or ""),
|
|
"entity_id": str(row.entity_id or ""),
|
|
"action": str(row.action or ""),
|
|
"trace_id": str(row.trace_id or ""),
|
|
"interface_path": str(row.interface_path or ""),
|
|
"caller": str(row.caller or "system"),
|
|
"remark": str(row.remark or ""),
|
|
"before_data": row.before_data if isinstance(row.before_data, dict) else None,
|
|
"after_data": row.after_data if isinstance(row.after_data, dict) else None,
|
|
"created_at": row.created_at.isoformat() if row.created_at else None,
|
|
"created_at_beijing": row.created_at_beijing or format_beijing_datetime(row.created_at),
|
|
}
|
|
|
|
|
|
class AuditQueryService:
|
|
def __init__(self, db: AsyncSession) -> None:
|
|
self.db = db
|
|
|
|
async def list_operations(
|
|
self,
|
|
*,
|
|
tenant_id: int,
|
|
entity_type: Optional[str],
|
|
start_time: Optional[datetime],
|
|
end_time: Optional[datetime],
|
|
limit: int = 100,
|
|
) -> list[dict[str, Any]]:
|
|
filters = [OperationLog.tenant_id == int(tenant_id)]
|
|
if entity_type:
|
|
filters.append(OperationLog.entity_type == str(entity_type).strip()[:64])
|
|
normalized_start = _to_beijing_naive(start_time)
|
|
normalized_end = _to_beijing_naive(end_time)
|
|
if normalized_start is not None:
|
|
filters.append(OperationLog.created_at >= normalized_start)
|
|
if normalized_end is not None:
|
|
filters.append(OperationLog.created_at <= normalized_end)
|
|
|
|
query = (
|
|
select(OperationLog)
|
|
.where(and_(*filters))
|
|
.order_by(OperationLog.created_at.desc(), OperationLog.id.desc())
|
|
.limit(max(1, min(int(limit), 100)))
|
|
)
|
|
rows = (await self.db.execute(query)).scalars().all()
|
|
return [_serialize_log(row) for row in rows]
|