246 lines
8.3 KiB
Python
246 lines
8.3 KiB
Python
from typing import Optional, List
|
|
from sqlalchemy.orm import Session
|
|
from datetime import datetime, timedelta
|
|
import json
|
|
|
|
from app.models.audit import AuditLog, AuditAction, AuditResource
|
|
from app.models.auth import User
|
|
|
|
|
|
class AuditService:
|
|
"""审计日志服务"""
|
|
|
|
@staticmethod
|
|
def log(db: Session,
|
|
user: User,
|
|
action: AuditAction,
|
|
resource: AuditResource,
|
|
resource_id: Optional[str] = None,
|
|
description: Optional[str] = None,
|
|
old_value: Optional[dict] = None,
|
|
new_value: Optional[dict] = None,
|
|
changed_fields: Optional[List[str]] = None,
|
|
user_ip: Optional[str] = None,
|
|
user_agent: Optional[str] = None,
|
|
request_method: Optional[str] = None,
|
|
request_path: Optional[str] = None,
|
|
success: bool = True,
|
|
error_message: Optional[str] = None) -> AuditLog:
|
|
"""
|
|
记录审计日志
|
|
|
|
Args:
|
|
db: 数据库会话
|
|
user: 操作用户
|
|
action: 操作类型
|
|
resource: 资源类型
|
|
resource_id: 资源ID
|
|
description: 操作描述
|
|
old_value: 旧值
|
|
new_value: 新值
|
|
changed_fields: 变更的字段列表
|
|
user_ip: 用户IP
|
|
user_agent: 用户代理
|
|
request_method: 请求方法
|
|
request_path: 请求路径
|
|
success: 操作是否成功
|
|
error_message: 错误信息
|
|
|
|
Returns:
|
|
AuditLog 实例
|
|
"""
|
|
audit_log = AuditLog(
|
|
user_id=user.id if user else None,
|
|
username=user.username if user else None,
|
|
real_name=user.real_name if user else None,
|
|
action=action,
|
|
resource=resource,
|
|
resource_id=str(resource_id) if resource_id else None,
|
|
description=description,
|
|
old_value=json.dumps(old_value, ensure_ascii=False, default=str) if old_value else None,
|
|
new_value=json.dumps(new_value, ensure_ascii=False, default=str) if new_value else None,
|
|
changed_fields=','.join(changed_fields) if changed_fields else None,
|
|
user_ip=user_ip,
|
|
user_agent=user_agent,
|
|
request_method=request_method,
|
|
request_path=request_path,
|
|
success=1 if success else 0,
|
|
error_message=error_message
|
|
)
|
|
|
|
db.add(audit_log)
|
|
db.commit()
|
|
db.refresh(audit_log)
|
|
return audit_log
|
|
|
|
@staticmethod
|
|
def get_logs(db: Session,
|
|
user_id: Optional[int] = None,
|
|
action: Optional[AuditAction] = None,
|
|
resource: Optional[AuditResource] = None,
|
|
success: Optional[bool] = None,
|
|
start_time: Optional[datetime] = None,
|
|
end_time: Optional[datetime] = None,
|
|
skip: int = 0,
|
|
limit: int = 100) -> tuple[int, List[AuditLog]]:
|
|
"""
|
|
查询审计日志
|
|
|
|
Args:
|
|
db: 数据库会话
|
|
user_id: 按用户ID过滤
|
|
action: 按操作类型过滤
|
|
resource: 按资源类型过滤
|
|
success: 按操作结果过滤
|
|
start_time: 开始时间
|
|
end_time: 结束时间
|
|
skip: 跳过记录数
|
|
limit: 返回记录数
|
|
|
|
Returns:
|
|
(总数, 记录列表)
|
|
"""
|
|
query = db.query(AuditLog)
|
|
|
|
if user_id:
|
|
query = query.filter(AuditLog.user_id == user_id)
|
|
if action:
|
|
query = query.filter(AuditLog.action == action)
|
|
if resource:
|
|
query = query.filter(AuditLog.resource == resource)
|
|
if success is not None:
|
|
query = query.filter(AuditLog.success == (1 if success else 0))
|
|
if start_time:
|
|
query = query.filter(AuditLog.created_at >= start_time)
|
|
if end_time:
|
|
query = query.filter(AuditLog.created_at <= end_time)
|
|
|
|
total = query.count()
|
|
items = query.order_by(AuditLog.created_at.desc()).offset(skip).limit(limit).all()
|
|
|
|
return total, items
|
|
|
|
@staticmethod
|
|
def get_user_actions(db: Session, user_id: int, limit: int = 50) -> List[AuditLog]:
|
|
"""获取指定用户的操作日志"""
|
|
return db.query(AuditLog)\
|
|
.filter(AuditLog.user_id == user_id)\
|
|
.order_by(AuditLog.created_at.desc())\
|
|
.limit(limit)\
|
|
.all()
|
|
|
|
@staticmethod
|
|
def get_resource_logs(db: Session, resource: AuditResource, resource_id: str, limit: int = 50) -> List[AuditLog]:
|
|
"""获取指定资源的操作历史"""
|
|
return db.query(AuditLog)\
|
|
.filter(AuditLog.resource == resource, AuditLog.resource_id == str(resource_id))\
|
|
.order_by(AuditLog.created_at.desc())\
|
|
.limit(limit)\
|
|
.all()
|
|
|
|
@staticmethod
|
|
def get_statistics(db: Session, days: int = 30) -> dict:
|
|
"""
|
|
获取审计统计信息
|
|
|
|
Args:
|
|
db: 数据库会话
|
|
days: 统计天数
|
|
|
|
Returns:
|
|
统计信息字典
|
|
"""
|
|
start_date = datetime.utcnow() - timedelta(days=days)
|
|
|
|
# 总操作数
|
|
total_ops = db.query(AuditLog).filter(AuditLog.created_at >= start_date).count()
|
|
|
|
# 成功/失败统计
|
|
success_ops = db.query(AuditLog).filter(
|
|
AuditLog.created_at >= start_date,
|
|
AuditLog.success == 1
|
|
).count()
|
|
failed_ops = total_ops - success_ops
|
|
|
|
# 按操作类型统计
|
|
action_stats = {}
|
|
for action in AuditAction:
|
|
count = db.query(AuditLog).filter(
|
|
AuditLog.created_at >= start_date,
|
|
AuditLog.action == action
|
|
).count()
|
|
if count > 0:
|
|
action_stats[action.value] = count
|
|
|
|
# 按资源类型统计
|
|
resource_stats = {}
|
|
for resource in AuditResource:
|
|
count = db.query(AuditLog).filter(
|
|
AuditLog.created_at >= start_date,
|
|
AuditLog.resource == resource
|
|
).count()
|
|
if count > 0:
|
|
resource_stats[resource.value] = count
|
|
|
|
# 活跃用户数
|
|
active_users = db.query(AuditLog.user_id).filter(
|
|
AuditLog.created_at >= start_date
|
|
).distinct().count()
|
|
|
|
return {
|
|
"period_days": days,
|
|
"total_operations": total_ops,
|
|
"successful_operations": success_ops,
|
|
"failed_operations": failed_ops,
|
|
"success_rate": round(success_ops / total_ops * 100, 2) if total_ops > 0 else 0,
|
|
"by_action": action_stats,
|
|
"by_resource": resource_stats,
|
|
"active_users_count": active_users
|
|
}
|
|
|
|
@staticmethod
|
|
def clean_old_logs(db: Session, days: int = 90) -> int:
|
|
"""
|
|
清理指定天数之前的日志
|
|
|
|
Args:
|
|
db: 数据库会话
|
|
days: 保留天数
|
|
|
|
Returns:
|
|
删除的记录数
|
|
"""
|
|
cutoff_date = datetime.utcnow() - timedelta(days=days)
|
|
deleted = db.query(AuditLog).filter(AuditLog.created_at < cutoff_date).delete()
|
|
db.commit()
|
|
return deleted
|
|
|
|
|
|
class AuditLogger:
|
|
"""审计日志装饰器/上下文管理器"""
|
|
|
|
def __init__(self, db: Session, user: User, resource: AuditResource, resource_id: Optional[str] = None):
|
|
self.db = db
|
|
self.user = user
|
|
self.resource = resource
|
|
self.resource_id = resource_id
|
|
self.old_value = None
|
|
|
|
def set_old_value(self, value: dict):
|
|
"""设置旧值(用于更新操作)"""
|
|
self.old_value = value
|
|
|
|
def log(self, action: AuditAction, description: str, new_value: Optional[dict] = None, changed_fields: Optional[List[str]] = None):
|
|
"""记录日志"""
|
|
AuditService.log(
|
|
db=self.db,
|
|
user=self.user,
|
|
action=action,
|
|
resource=self.resource,
|
|
resource_id=self.resource_id,
|
|
description=description,
|
|
old_value=self.old_value,
|
|
new_value=new_value,
|
|
changed_fields=changed_fields
|
|
)
|