Files
taiji-AI-PAD/services/mcp-server/app/audit.py
T
2026-03-25 21:34:44 +08:00

411 lines
11 KiB
Python

"""
合规审计日志模块
操作日志记录与审计查询
"""
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any
from sqlalchemy import select, func, and_, desc
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi import Request
from models import AuditLog, User
# 审计操作类型定义
AUDIT_ACTIONS = {
# 认证相关
"auth.login": "用户登录",
"auth.logout": "用户登出",
"auth.password_change": "密码修改",
"auth.token_revoke": "Token撤销",
# 用户管理
"user.create": "创建用户",
"user.update": "更新用户",
"user.delete": "删除用户",
"user.recharge": "用户充值",
# 渠道管理
"channel.create": "创建渠道",
"channel.update": "更新渠道",
"channel.delete": "删除渠道",
"channel.allocate": "分配渠道资源",
# 管理员管理
"admin.create": "创建管理员",
"admin.delete": "删除管理员",
"admin.permission_change": "权限变更",
# Agent管理
"agent.create": "创建Agent",
"agent.update": "更新Agent",
"agent.delete": "删除Agent",
"agent.deploy": "部署Agent",
# 资源管理
"resource.allocate": "资源分配",
"resource.revoke": "资源撤销",
# 审批操作
"application.approve": "审批通过",
"application.reject": "审批拒绝",
# 计费操作
"billing.charge": "计费扣款",
"billing.refund": "退款",
"billing.adjust": "余额调整",
# PayPal 支付操作
"payment.paypal.order_created": "PayPal订单创建",
"payment.paypal.order_captured": "PayPal支付完成",
"payment.paypal.order_failed": "PayPal支付失败",
"payment.paypal.amount_mismatch": "PayPal金额不匹配",
"payment.paypal.webhook_received": "PayPal Webhook接收",
"payment.paypal.webhook_captured": "PayPal Webhook充值完成",
"payment.paypal.webhook_amount_mismatch": "PayPal Webhook金额不匹配",
"payment.paypal.webhook_signature_invalid": "PayPal Webhook签名无效",
# 供应商管理
"provider.create": "创建供应商",
"provider.update": "更新供应商",
"provider.delete": "删除供应商",
# 系统操作
"system.config_change": "系统配置变更",
"system.maintenance": "系统维护",
}
async def log_audit_event(
action: str,
resource_type: str,
resource_id: Optional[str],
user_id: Optional[str],
success: bool,
details: Optional[Dict] = None,
error_message: Optional[str] = None,
request: Optional[Request] = None,
db: Optional[AsyncSession] = None
) -> Optional[AuditLog]:
"""
记录审计事件
Args:
action: 操作类型
resource_type: 资源类型
resource_id: 资源ID
user_id: 操作用户ID
success: 是否成功
details: 操作详情
error_message: 错误信息
request: HTTP请求对象
db: 数据库会话
Returns:
AuditLog记录
"""
if db is None:
return None
# 从请求中提取信息
ip_address = None
user_agent = None
if request:
ip_address = request.client.host if request.client else None
user_agent = request.headers.get("user-agent")
# 创建审计日志
audit_log = AuditLog(
action=action,
resource_type=resource_type,
resource_id=resource_id,
user_id=user_id,
success=success,
details=details or {},
error_message=error_message,
ip_address=ip_address,
user_agent=user_agent,
)
db.add(audit_log)
await db.commit()
await db.refresh(audit_log)
return audit_log
async def query_audit_logs(
user_id: Optional[str] = None,
action: Optional[str] = None,
resource_type: Optional[str] = None,
resource_id: Optional[str] = None,
success: Optional[bool] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
page: int = 1,
page_size: int = 20,
db: AsyncSession = None
) -> Dict:
"""
查询审计日志
Args:
user_id: 用户ID过滤
action: 操作类型过滤
resource_type: 资源类型过滤
resource_id: 资源ID过滤
success: 成功/失败过滤
start_date: 开始日期
end_date: 结束日期
page: 页码
page_size: 每页大小
db: 数据库会话
Returns:
审计日志查询结果
"""
query = select(AuditLog)
# 应用过滤条件
conditions = []
if user_id:
conditions.append(AuditLog.user_id == user_id)
if action:
conditions.append(AuditLog.action == action)
if resource_type:
conditions.append(AuditLog.resource_type == resource_type)
if resource_id:
conditions.append(AuditLog.resource_id == resource_id)
if success is not None:
conditions.append(AuditLog.success == success)
if start_date:
conditions.append(AuditLog.created_at >= start_date)
if end_date:
conditions.append(AuditLog.created_at <= end_date)
if conditions:
query = query.where(and_(*conditions))
# 计算总数
count_result = await db.execute(
select(func.count()).select_from(query.subquery())
)
total = count_result.scalar() or 0
# 分页查询
query = query.order_by(desc(AuditLog.created_at))
query = query.offset((page - 1) * page_size).limit(page_size)
result = await db.execute(query)
logs = result.scalars().all()
# 获取用户信息
user_ids = [str(log.user_id) for log in logs if log.user_id]
users_result = await db.execute(
select(User).where(User.id.in_(user_ids))
) if user_ids else None
users_map = {}
if users_result:
for user in users_result.scalars().all():
users_map[str(user.id)] = user.name or user.email
return {
"total": total,
"page": page,
"pageSize": page_size,
"totalPages": (total + page_size - 1) // page_size,
"logs": [
{
"id": str(log.id),
"action": log.action,
"actionName": AUDIT_ACTIONS.get(log.action, log.action),
"resourceType": log.resource_type,
"resourceId": log.resource_id,
"userId": str(log.user_id) if log.user_id else None,
"userName": users_map.get(str(log.user_id)) if log.user_id else None,
"success": log.success,
"details": log.details,
"errorMessage": log.error_message,
"ipAddress": log.ip_address,
"createdAt": log.created_at.isoformat(),
}
for log in logs
],
}
async def get_audit_summary(
start_date: datetime,
end_date: datetime,
db: AsyncSession
) -> Dict:
"""
获取审计日志汇总统计
Args:
start_date: 开始日期
end_date: 结束日期
db: 数据库会话
Returns:
汇总统计
"""
# 按操作类型统计
action_stats = await db.execute(
select(
AuditLog.action,
func.count(AuditLog.id).label("count"),
func.sum(func.cast(AuditLog.success, sa.Integer)).label("success_count"),
)
.where(
and_(
AuditLog.created_at >= start_date,
AuditLog.created_at <= end_date,
)
)
.group_by(AuditLog.action)
)
action_breakdown = [
{
"action": row.action,
"actionName": AUDIT_ACTIONS.get(row.action, row.action),
"count": row.count,
"successCount": int(row.success_count or 0),
"failCount": row.count - int(row.success_count or 0),
}
for row in action_stats.all()
]
# 按资源类型统计
resource_stats = await db.execute(
select(
AuditLog.resource_type,
func.count(AuditLog.id).label("count"),
)
.where(
and_(
AuditLog.created_at >= start_date,
AuditLog.created_at <= end_date,
)
)
.group_by(AuditLog.resource_type)
)
resource_breakdown = [
{
"resourceType": row.resource_type,
"count": row.count,
}
for row in resource_stats.all()
]
# 总计统计
total_stats = await db.execute(
select(
func.count(AuditLog.id).label("total"),
func.sum(func.cast(AuditLog.success, sa.Integer)).label("success_total"),
)
.where(
and_(
AuditLog.created_at >= start_date,
AuditLog.created_at <= end_date,
)
)
)
total_row = total_stats.first()
return {
"startDate": start_date.isoformat(),
"endDate": end_date.isoformat(),
"total": total_row.total or 0,
"successTotal": int(total_row.success_total or 0),
"failTotal": (total_row.total or 0) - int(total_row.success_total or 0),
"byAction": action_breakdown,
"byResourceType": resource_breakdown,
}
async def get_user_activity(
user_id: str,
days: int,
db: AsyncSession
) -> List[Dict]:
"""
获取用户活动历史
Args:
user_id: 用户ID
days: 天数
db: 数据库会话
Returns:
活动历史列表
"""
start_date = datetime.utcnow() - timedelta(days=days)
result = await db.execute(
select(AuditLog)
.where(
and_(
AuditLog.user_id == user_id,
AuditLog.created_at >= start_date,
)
)
.order_by(desc(AuditLog.created_at))
.limit(100)
)
logs = result.scalars().all()
return [
{
"action": log.action,
"actionName": AUDIT_ACTIONS.get(log.action, log.action),
"resourceType": log.resource_type,
"resourceId": log.resource_id,
"success": log.success,
"ipAddress": log.ip_address,
"createdAt": log.created_at.isoformat(),
}
for log in logs
]
async def export_audit_logs(
filters: Dict,
format: str,
db: AsyncSession
) -> Dict:
"""
导出审计日志
Args:
filters: 过滤条件
format: 导出格式 (csv, excel, json)
db: 数据库会话
Returns:
导出信息
"""
# 简化实现:生成导出URL
# 实际应该生成文件并上传到对象存储
import uuid
export_id = str(uuid.uuid4())
file_url = f"https://exports.taiji-ai.com/audit/{export_id}/audit_logs.{format}"
return {
"exportId": export_id,
"format": format,
"fileUrl": file_url,
"expiresAt": (datetime.utcnow() + timedelta(hours=24)).isoformat(),
"status": "processing",
}
# 需要导入sa模块
import sqlalchemy as sa