from datetime import datetime from functools import wraps from typing import Any, Callable, Optional from fastapi import Request from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.models.audit_log import AuditAction, AuditLog from app.schemas.audit_log import AuditLogCreate, AuditLogQuery class AuditService: """审计日志服务""" @staticmethod async def log_action( db: AsyncSession, company_id: int, action: str, resource_type: str, user_id: Optional[int] = None, resource_id: Optional[str] = None, details: Optional[dict[str, Any]] = None, ip_address: Optional[str] = None, user_agent: Optional[str] = None, ) -> AuditLog: """ 记录审计日志 Args: db: 数据库会话 company_id: 企业 ID action: 操作类型 resource_type: 资源类型 user_id: 用户 ID resource_id: 资源 ID details: 操作详情 ip_address: IP 地址 user_agent: User-Agent Returns: 创建的审计日志对象 """ log_data = AuditLogCreate( company_id=company_id, user_id=user_id, action=action, resource_type=resource_type, resource_id=resource_id, details=details, ip_address=ip_address, user_agent=user_agent, ) audit_log = AuditLog(**log_data.model_dump()) db.add(audit_log) await db.commit() await db.refresh(audit_log) return audit_log @staticmethod async def get_logs( db: AsyncSession, query_params: AuditLogQuery, ) -> list[AuditLog]: """ 查询审计日志 Args: db: 数据库会话 query_params: 查询参数 Returns: 审计日志列表 """ stmt = select(AuditLog) # 添加过滤条件 if query_params.company_id: stmt = stmt.where(AuditLog.company_id == query_params.company_id) if query_params.user_id: stmt = stmt.where(AuditLog.user_id == query_params.user_id) if query_params.action: stmt = stmt.where(AuditLog.action == query_params.action) if query_params.resource_type: stmt = stmt.where(AuditLog.resource_type == query_params.resource_type) if query_params.resource_id: stmt = stmt.where(AuditLog.resource_id == query_params.resource_id) if query_params.start_date: stmt = stmt.where(AuditLog.created_at >= query_params.start_date) if query_params.end_date: stmt = stmt.where(AuditLog.created_at <= query_params.end_date) # 排序和分页 stmt = stmt.order_by(AuditLog.created_at.desc()) stmt = stmt.offset(query_params.skip).limit(query_params.limit) result = await db.execute(stmt) return list(result.scalars().all()) @staticmethod async def log_from_request( db: AsyncSession, request: Request, company_id: int, action: str, resource_type: str, user_id: Optional[int] = None, resource_id: Optional[str] = None, details: Optional[dict[str, Any]] = None, ) -> AuditLog: """ 从 Request 对象中提取信息并记录审计日志 Args: db: 数据库会话 request: FastAPI 请求对象 company_id: 企业 ID action: 操作类型 resource_type: 资源类型 user_id: 用户 ID resource_id: 资源 ID details: 操作详情 Returns: 创建的审计日志对象 """ # 提取 IP 地址 ip_address = request.client.host if request.client else None # 提取 User-Agent user_agent = request.headers.get("user-agent") return await AuditService.log_action( db=db, company_id=company_id, action=action, resource_type=resource_type, user_id=user_id, resource_id=resource_id, details=details, ip_address=ip_address, user_agent=user_agent, ) # 单例实例 audit_service = AuditService() def audit_log( action: str, resource_type: str, get_resource_id: Optional[Callable] = None, ): """ 审计日志装饰器 Args: action: 操作类型 resource_type: 资源类型 get_resource_id: 从函数返回值中获取 resource_id 的函数 Example: @audit_log(action=AuditAction.CREATE, resource_type="company") async def create_company(...): ... """ def decorator(func: Callable): @wraps(func) async def wrapper(*args, **kwargs): result = await func(*args, **kwargs) # TODO: 在实现认证后,从上下文获取 company_id 和 user_id # 目前暂时跳过实际记录 return result return wrapper return decorator