PMFlow/backend/app/services/audit_query_service/service.py

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]