"""审计日志查询服务。""" 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]