二十六、【健壮性与审计篇】操作日志:前端日志管理界面的设计与实现

发布于:2025-06-19 ⋅ 阅读:(15) ⋅ 点赞:(0)

前言

在上一篇关于健壮性的文章中,我们重点配置了后端的错误日志和前端的错误捕获。现在,我们将更进一步,实现一个专门的操作日志系统,用于记录用户在平台上的关键操作,并提供一个前端界面供管理员查看这些日志,以达到审计和问题追踪的目的。

这篇文章将分为后端和前端两大部分:

  • 后端:
    1. 创建 OperationLog 模型来结构化地存储操作日志。
    2. 实现一个工具函数或装饰器,方便在关键的视图操作中记录日志。
    3. 创建 OperationLogSerializerOperationLogViewSet 提供 API。
  • 前端:
    1. 创建 API 服务调用后端日志接口。
    2. 实现操作日志列表页面,支持筛选和分页。

第一部分:后端实现 - 操作日志模型与API

1. 创建 OperationLog 模型

打开 test-platform/backend/api/models.py,添加 OperationLog 模型:
在这里插入图片描述

# test-platform/api/models.py
import uuid
from django.db import models
from django.conf import settings # 导入 settings
from django.contrib.auth.models import User

# ... (其他模型保持不变) ...

class OperationLog(models.Model):
    """
    操作日志模型
    """
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL, 
        on_delete=models.SET_NULL, # 用户删除后,日志中的用户字段设为NULL
        null=True, 
        blank=True, 
        verbose_name="操作用户"
    )
    action_time = models.DateTimeField(auto_now_add=True, verbose_name="操作时间")
    action_type_choices = [
        ('CREATE', '创建'), ('UPDATE', '更新'), ('DELETE', '删除'),
        ('LOGIN', '登录'), ('LOGOUT', '登出'), ('EXECUTE', '执行'),
        ('VIEW', '查看'), ('OTHER', '其他')
    ]
    action_type = models.CharField(max_length=10, choices=action_type_choices, verbose_name="操作类型")
    
    # 例如:Project, Module, TestCase, User, Role 等
    target_resource = models.CharField(max_length=100, verbose_name="操作对象类型")
    target_id = models.CharField(max_length=100, null=True, blank=True, verbose_name="操作对象ID") # 有些操作可能没有具体ID
    description = models.TextField(verbose_name="操作描述") # 例如 "创建了项目 '项目A'"
    
    # 存储更详细的上下文信息,例如请求参数、修改前后的数据差异等 (可选)
    details = models.JSONField(null=True, blank=True, verbose_name="详细信息 (JSON)")
    
    # 记录操作时的 IP 地址
    ip_address = models.GenericIPAddressField(null=True, blank=True, verbose_name="IP地址")

    class Meta:
        verbose_name = "操作日志"
        verbose_name_plural = "操作日志列表"
        ordering = ['-action_time'] # 按操作时间降序

    def __str__(self):
        user_str = self.user.username if self.user else "系统操作"
        return f"{
     self.action_time.strftime('%Y-%m-%d %H:%M:%S')} - {
     user_str} {
     self.get_action_type_display()} {
     self.target_resource}: {
     self.description[:50]}"

模型字段解释:

  • user: 关联到执行操作的用户。
  • action_time: 操作发生的时间。
  • action_type: 操作的类型(创建、更新、删除、登录、执行等)。
  • target_resource: 操作影响的资源类型(如"项目"、“用户”)。
  • target_id: 操作影响的具体资源ID。
  • description: 对操作的简短描述。
  • details: (可选) JSON格式,存储更详细的上下文或数据快照。
  • ip_address: 操作者IP地址。

2. 生成并应用数据库迁移

python manage.py makemigrations api
python manage.py migrate api

在这里插入图片描述

3. 创建记录操作日志的工具/装饰器

为了方便在各个视图中记录操作日志,我们可以创建一个工具函数。

a. 创建 api/utils/log_utils.py
在这里插入图片描述
在这里插入图片描述

# test-platform/api/utils/log_utils.py
from ..models import OperationLog # 从父级 models 导入
from django.contrib.auth.models import User
from typing import Optional, Dict, Any

def get_client_ip(request) -> Optional[str]:
    """获取客户端IP地址"""
    x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
    if x_forwarded_for:
        ip = x_forwarded_for.split(',')[0]
    else:
        ip = request.META.get('REMOTE_ADDR')
    return ip

def record_operation_log(
    user: Optional[User],
    action_type: str,
    target_resource: str,
    description: str,
    target_id: Optional[Any] = None,
    details: Optional[Dict[str, Any]] = None,
    request = None # 可选,用于获取 IP
):
    """
    记录一条操作日志。
    """
    ip_addr = None
    if request:
        ip_addr = get_client_ip(request)

    try:
        OperationLog.objects.create(
            user=user if user and user.is_authenticated else None,
            action_type=action_type,
            target_resource=target_resource,
            target_id=str(target_id) if target_id is not None else None,
            description=description,
            details=details,
            ip_address=ip_addr
        )
    except Exception as e:
        # 记录日志失败不应影响主业务流程,但应记录错误
        import logging
        logger = logging.getLogger(__name__) # 或者使用 app 级别的 logger
        logger.error(f"记录操作日志失败: {
     e}", exc_info=True)

4. 在关键视图操作中调用日志记录函数

现在,我们需要在一些关键的 ModelViewSet 操作 (如 create, update, destroy) 或自定义 action 中调用 record_operation_log

修改 ProjectViewSet (api/views.py):
在这里插入图片描述

# test-platform/api/views.py
from .utils.log_utils import record_operation_log # 导入日志记录函数
# ... 其他导入 ...

class ProjectViewSet(viewsets.ModelViewSet):
    queryset = Project.objects.all().order_by('-create_time')
    serializer_class = ProjectSerializer
    permission_classes = [permissions.IsAuthenticated]

    def perform_create(self, serializer):
        instance = serializer.save()
        record_operation_log(
            user=self.request.user,
            action_type='CREATE',
            target_resource='项目',
            target_id=instance.id,
            description=f"创建了项目: '{
     instance.name}' (ID: {
     instance.id})",
            request=self.request # 传递 request 对象以获取 IP
        )

    def perform_update(self, serializer):
        instance = serializer.save()
        # 可以在 details 中记录修改前后的差异 (更复杂)
        record_operation_log(
            user=self.request.user,
            action_type='UPDATE',
            target_resource='项目',
            target_id=instance.id,
            description=f"更新了项目: '{
     instance.name}' (ID: {
     instance.id})",
            request=self.request
        )

    def perform_destroy(self, instance):
        project_name = instance.name
        project_id = instance.id
        instance.delete()
        record_operation_log(
            user=self.request.user,
            action_type='DELETE',
            target_resource='项目',
            target_id=project_id, # instance 在 delete() 后可能没有 id
            description=f"删除了项目: '{
     project_name}' (ID: {
     project_id})",
            request=self.request
        )

修改 ModuleViewSet (api/views.py):


网站公告

今日签到

点亮在社区的每一天
去签到