forked from xiaohei/taiji-AI-PAD
411 lines
11 KiB
Python
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
|
|
|