mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/ - 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层 - 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构 - 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations) - 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image - 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
This commit is contained in:
462
_deprecated_py_src/services/system/audit.py
Normal file
462
_deprecated_py_src/services/system/audit.py
Normal file
@@ -0,0 +1,462 @@
|
||||
"""
|
||||
审计日志服务
|
||||
记录所有重要操作和安全事件
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from src.core.logger import logger
|
||||
from src.database import get_db
|
||||
from src.models.database import AuditEventType, AuditLog
|
||||
|
||||
# 审计模型已移至 src/models/database.py
|
||||
|
||||
|
||||
class AuditService:
|
||||
"""审计服务
|
||||
|
||||
事务策略:本服务不负责事务提交,由中间件统一管理。
|
||||
所有方法只做 db.add/flush,提交由请求结束时的中间件处理。
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def log_event(
|
||||
db: Session,
|
||||
event_type: AuditEventType,
|
||||
description: str,
|
||||
user_id: str | None = None, # UUID
|
||||
api_key_id: str | None = None, # UUID
|
||||
ip_address: str | None = None,
|
||||
user_agent: str | None = None,
|
||||
request_id: str | None = None,
|
||||
status_code: int | None = None,
|
||||
error_message: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> AuditLog:
|
||||
"""
|
||||
记录审计事件
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
event_type: 事件类型
|
||||
description: 事件描述
|
||||
user_id: 用户ID
|
||||
api_key_id: API密钥ID
|
||||
ip_address: IP地址
|
||||
user_agent: 用户代理
|
||||
request_id: 请求ID
|
||||
status_code: 状态码
|
||||
error_message: 错误消息
|
||||
metadata: 额外元数据
|
||||
|
||||
Returns:
|
||||
审计日志记录
|
||||
|
||||
Note:
|
||||
不在此方法内提交事务,由调用方或中间件统一管理。
|
||||
"""
|
||||
audit_log = AuditLog(
|
||||
event_type=event_type.value,
|
||||
description=description,
|
||||
user_id=user_id,
|
||||
api_key_id=api_key_id,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
status_code=status_code,
|
||||
error_message=error_message,
|
||||
event_metadata=metadata,
|
||||
)
|
||||
|
||||
db.add(audit_log)
|
||||
# 使用 flush 使记录可见但不提交事务,事务由中间件统一管理
|
||||
db.flush()
|
||||
|
||||
# 同时记录到系统日志
|
||||
# 检查 metadata 中是否有 quiet_logging 标志(由高频轮询端点设置)
|
||||
quiet_logging = metadata.get("quiet_logging", False) if metadata else False
|
||||
|
||||
if not quiet_logging:
|
||||
log_message = (
|
||||
f"AUDIT [{event_type.value}] - {description} | "
|
||||
f"user_id={user_id}, ip={ip_address}"
|
||||
)
|
||||
|
||||
if event_type in [
|
||||
AuditEventType.UNAUTHORIZED_ACCESS,
|
||||
AuditEventType.SUSPICIOUS_ACTIVITY,
|
||||
]:
|
||||
logger.warning(log_message)
|
||||
elif event_type in [AuditEventType.LOGIN_FAILED, AuditEventType.REQUEST_FAILED]:
|
||||
logger.info(log_message)
|
||||
# request_success 已由 Pipeline 日志覆盖,不再重复输出到控制台
|
||||
|
||||
return audit_log
|
||||
|
||||
@staticmethod
|
||||
def log_login_attempt(
|
||||
db: Session,
|
||||
email: str,
|
||||
success: bool,
|
||||
ip_address: str,
|
||||
user_agent: str,
|
||||
user_id: str | None = None, # UUID
|
||||
error_reason: str | None = None,
|
||||
) -> Any:
|
||||
"""
|
||||
记录登录尝试
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
email: 登录邮箱
|
||||
success: 是否成功
|
||||
ip_address: IP地址
|
||||
user_agent: 用户代理
|
||||
user_id: 用户ID(成功时)
|
||||
error_reason: 失败原因
|
||||
"""
|
||||
event_type = AuditEventType.LOGIN_SUCCESS if success else AuditEventType.LOGIN_FAILED
|
||||
description = f"Login attempt for {email}"
|
||||
if not success and error_reason:
|
||||
description += f": {error_reason}"
|
||||
|
||||
AuditService.log_event(
|
||||
db=db,
|
||||
event_type=event_type,
|
||||
description=description,
|
||||
user_id=user_id,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
metadata={"email": email},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def log_api_request(
|
||||
db: Session,
|
||||
user_id: str, # UUID
|
||||
api_key_id: str, # UUID
|
||||
request_id: str,
|
||||
model: str,
|
||||
provider: str,
|
||||
success: bool,
|
||||
ip_address: str,
|
||||
status_code: int,
|
||||
error_message: str | None = None,
|
||||
input_tokens: int | None = None,
|
||||
output_tokens: int | None = None,
|
||||
cost_usd: float | None = None,
|
||||
) -> Any:
|
||||
"""
|
||||
记录API请求
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user_id: 用户ID
|
||||
api_key_id: API密钥ID
|
||||
request_id: 请求ID
|
||||
model: 模型名称
|
||||
provider: 提供商名称
|
||||
success: 是否成功
|
||||
ip_address: IP地址
|
||||
status_code: 状态码
|
||||
error_message: 错误消息
|
||||
input_tokens: 输入tokens
|
||||
output_tokens: 输出tokens
|
||||
cost_usd: 成本(美元)
|
||||
"""
|
||||
event_type = AuditEventType.REQUEST_SUCCESS if success else AuditEventType.REQUEST_FAILED
|
||||
description = f"API request to {provider}/{model}"
|
||||
|
||||
metadata = {"model": model, "provider": provider}
|
||||
|
||||
if input_tokens:
|
||||
metadata["input_tokens"] = input_tokens
|
||||
if output_tokens:
|
||||
metadata["output_tokens"] = output_tokens
|
||||
if cost_usd:
|
||||
metadata["cost_usd"] = cost_usd
|
||||
|
||||
AuditService.log_event(
|
||||
db=db,
|
||||
event_type=event_type,
|
||||
description=description,
|
||||
user_id=user_id,
|
||||
api_key_id=api_key_id,
|
||||
request_id=request_id,
|
||||
ip_address=ip_address,
|
||||
status_code=status_code,
|
||||
error_message=error_message,
|
||||
metadata=metadata,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def log_security_event(
|
||||
db: Session,
|
||||
event_type: AuditEventType,
|
||||
description: str,
|
||||
ip_address: str,
|
||||
user_id: str | None = None, # UUID
|
||||
severity: str = "medium",
|
||||
details: dict[str, Any] | None = None,
|
||||
) -> Any:
|
||||
"""
|
||||
记录安全事件
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
event_type: 事件类型
|
||||
description: 事件描述
|
||||
ip_address: IP地址
|
||||
user_id: 用户ID
|
||||
severity: 严重程度 (low, medium, high, critical)
|
||||
details: 详细信息
|
||||
"""
|
||||
event_metadata = {"severity": severity}
|
||||
if details:
|
||||
event_metadata.update(details)
|
||||
|
||||
AuditService.log_event(
|
||||
db=db,
|
||||
event_type=event_type,
|
||||
description=description,
|
||||
user_id=user_id,
|
||||
ip_address=ip_address,
|
||||
metadata=event_metadata,
|
||||
)
|
||||
|
||||
# 对于高严重性事件,简化日志输出
|
||||
if severity in ["high", "critical"]:
|
||||
logger.error(f"安全告警 [{severity.upper()}]: {description}")
|
||||
|
||||
@staticmethod
|
||||
def get_user_audit_logs(
|
||||
db: Session,
|
||||
user_id: str, # UUID
|
||||
event_types: list[AuditEventType] | None = None,
|
||||
limit: int = 100,
|
||||
) -> list[AuditLog]:
|
||||
"""
|
||||
获取用户的审计日志
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user_id: 用户ID
|
||||
event_types: 事件类型过滤
|
||||
limit: 返回数量限制
|
||||
|
||||
Returns:
|
||||
审计日志列表
|
||||
"""
|
||||
query = db.query(AuditLog).filter(AuditLog.user_id == user_id)
|
||||
|
||||
if event_types:
|
||||
event_type_values = [et.value for et in event_types]
|
||||
query = query.filter(AuditLog.event_type.in_(event_type_values))
|
||||
|
||||
return query.order_by(AuditLog.created_at.desc()).limit(limit).all()
|
||||
|
||||
@staticmethod
|
||||
def get_suspicious_activities(db: Session, hours: int = 24, limit: int = 100) -> list[AuditLog]:
|
||||
"""
|
||||
获取可疑活动
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
hours: 时间范围(小时)
|
||||
limit: 返回数量限制
|
||||
|
||||
Returns:
|
||||
可疑活动列表
|
||||
"""
|
||||
cutoff_time = datetime.now(timezone.utc) - timedelta(hours=hours)
|
||||
|
||||
suspicious_types = [
|
||||
AuditEventType.SUSPICIOUS_ACTIVITY.value,
|
||||
AuditEventType.UNAUTHORIZED_ACCESS.value,
|
||||
AuditEventType.LOGIN_FAILED.value,
|
||||
AuditEventType.REQUEST_RATE_LIMITED.value,
|
||||
]
|
||||
|
||||
return (
|
||||
db.query(AuditLog)
|
||||
.filter(AuditLog.event_type.in_(suspicious_types), AuditLog.created_at >= cutoff_time)
|
||||
.order_by(AuditLog.created_at.desc())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def analyze_user_behavior(db: Session, user_id: str, days: int = 30) -> dict[str, Any]: # UUID
|
||||
"""
|
||||
分析用户行为
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
user_id: 用户ID
|
||||
days: 分析天数
|
||||
|
||||
Returns:
|
||||
行为分析结果
|
||||
"""
|
||||
from sqlalchemy import func
|
||||
|
||||
cutoff_time = datetime.now(timezone.utc) - timedelta(days=days)
|
||||
|
||||
# 统计各种事件类型
|
||||
event_counts = (
|
||||
db.query(AuditLog.event_type, func.count(AuditLog.id).label("count"))
|
||||
.filter(AuditLog.user_id == user_id, AuditLog.created_at >= cutoff_time)
|
||||
.group_by(AuditLog.event_type)
|
||||
.all()
|
||||
)
|
||||
|
||||
# 统计失败请求
|
||||
failed_requests = (
|
||||
db.query(func.count(AuditLog.id))
|
||||
.filter(
|
||||
AuditLog.user_id == user_id,
|
||||
AuditLog.event_type == AuditEventType.REQUEST_FAILED.value,
|
||||
AuditLog.created_at >= cutoff_time,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
|
||||
# 统计成功请求
|
||||
success_requests = (
|
||||
db.query(func.count(AuditLog.id))
|
||||
.filter(
|
||||
AuditLog.user_id == user_id,
|
||||
AuditLog.event_type == AuditEventType.REQUEST_SUCCESS.value,
|
||||
AuditLog.created_at >= cutoff_time,
|
||||
)
|
||||
.scalar()
|
||||
)
|
||||
|
||||
# 获取最近的可疑活动
|
||||
recent_suspicious = int(
|
||||
db.query(func.count(AuditLog.id))
|
||||
.filter(
|
||||
AuditLog.user_id == user_id,
|
||||
AuditLog.event_type.in_(
|
||||
[
|
||||
AuditEventType.SUSPICIOUS_ACTIVITY.value,
|
||||
AuditEventType.UNAUTHORIZED_ACCESS.value,
|
||||
]
|
||||
),
|
||||
AuditLog.created_at >= cutoff_time,
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"period_days": days,
|
||||
"event_counts": {event: count for event, count in event_counts},
|
||||
"failed_requests": failed_requests or 0,
|
||||
"success_requests": success_requests or 0,
|
||||
"success_rate": (
|
||||
success_requests / (success_requests + failed_requests)
|
||||
if (success_requests + failed_requests) > 0
|
||||
else 0
|
||||
),
|
||||
"suspicious_activities": recent_suspicious,
|
||||
"analysis_time": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def log_event_auto(
|
||||
event_type: AuditEventType,
|
||||
description: str,
|
||||
user_id: str | None = None,
|
||||
api_key_id: str | None = None,
|
||||
ip_address: str | None = None,
|
||||
user_agent: str | None = None,
|
||||
request_id: str | None = None,
|
||||
status_code: int | None = None,
|
||||
error_message: str | None = None,
|
||||
event_metadata: dict[str, Any] | None = None,
|
||||
db: Session | None = None,
|
||||
) -> AuditLog | None:
|
||||
"""
|
||||
自动管理数据库会话的审计日志记录方法
|
||||
适用于中间件等无法直接获取数据库会话的场景
|
||||
|
||||
Args:
|
||||
event_type: 事件类型
|
||||
description: 事件描述
|
||||
user_id: 用户ID
|
||||
api_key_id: API密钥ID
|
||||
ip_address: IP地址
|
||||
user_agent: 用户代理
|
||||
request_id: 请求ID
|
||||
status_code: 状态码
|
||||
error_message: 错误消息
|
||||
event_metadata: 额外元数据
|
||||
db: 数据库会话(可选,如不提供则自动创建)
|
||||
|
||||
Returns:
|
||||
审计日志记录
|
||||
"""
|
||||
# 如果提供了数据库会话,使用它(不自动提交)
|
||||
if db is not None:
|
||||
try:
|
||||
audit_log = AuditService.log_event(
|
||||
db=db,
|
||||
event_type=event_type,
|
||||
description=description,
|
||||
user_id=user_id,
|
||||
api_key_id=api_key_id,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
status_code=status_code,
|
||||
error_message=error_message,
|
||||
metadata=event_metadata,
|
||||
)
|
||||
# 注意:不在这里提交,让调用方决定何时提交
|
||||
return audit_log
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to log audit event: {e}")
|
||||
return None
|
||||
|
||||
# 如果没有提供会话,自动创建并管理
|
||||
db_session = None
|
||||
try:
|
||||
db_session = next(get_db())
|
||||
|
||||
audit_log = AuditService.log_event(
|
||||
db=db_session,
|
||||
event_type=event_type,
|
||||
description=description,
|
||||
user_id=user_id,
|
||||
api_key_id=api_key_id,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
status_code=status_code,
|
||||
error_message=error_message,
|
||||
metadata=event_metadata,
|
||||
)
|
||||
|
||||
db_session.commit()
|
||||
return audit_log
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to log audit event with auto session: {e}")
|
||||
if db_session is not None:
|
||||
db_session.rollback()
|
||||
return None
|
||||
finally:
|
||||
if db_session is not None:
|
||||
db_session.close()
|
||||
|
||||
|
||||
# 全局审计服务实例
|
||||
audit_service = AuditService()
|
||||
Reference in New Issue
Block a user