From ba34b2692845ad7d0da70a44c896532cc54fb159 Mon Sep 17 00:00:00 2001 From: caoqianming Date: Thu, 7 Dec 2023 09:38:24 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E6=97=A5=E5=BF=97auditlog=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/monitor/migrations/0004_auditlog.py | 31 ++++++++++++ apps/monitor/models.py | 16 +++++- apps/monitor/serializers.py | 12 ++++- apps/monitor/services.py | 63 +++++++++++++++++++++++- apps/monitor/urls.py | 7 ++- apps/monitor/views.py | 17 ++++++- 6 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 apps/monitor/migrations/0004_auditlog.py diff --git a/apps/monitor/migrations/0004_auditlog.py b/apps/monitor/migrations/0004_auditlog.py new file mode 100644 index 00000000..41402798 --- /dev/null +++ b/apps/monitor/migrations/0004_auditlog.py @@ -0,0 +1,31 @@ +# Generated by Django 3.2.12 on 2023-12-07 01:35 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('monitor', '0003_alter_drfrequestlog_view_method'), + ] + + operations = [ + migrations.CreateModel( + name='AuditLog', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('action', models.CharField(max_length=20, verbose_name='动作')), + ('model_name', models.CharField(max_length=20, verbose_name='模型名')), + ('instance_id', models.CharField(editable=False, max_length=20, verbose_name='记录ID')), + ('change_reason', models.CharField(default='', max_length=50, verbose_name='变更原因')), + ('change_time', models.DateTimeField(verbose_name='变更时间')), + ('val_new', models.JSONField(default=dict, verbose_name='变更后完整数据')), + ('difference', models.JSONField(default=list, verbose_name='变更情况')), + ('change_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='操作人')), + ], + ), + ] diff --git a/apps/monitor/models.py b/apps/monitor/models.py index d20c77dc..4bf3b475 100755 --- a/apps/monitor/models.py +++ b/apps/monitor/models.py @@ -4,6 +4,19 @@ from django.db import models from apps.utils.models import BaseModel +class AuditLog(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + action = models.CharField('动作', max_length=20) + model_name = models.CharField('模型名', max_length=20) + instance_id = models.CharField('记录ID', max_length=20, editable=False) + change_reason = models.CharField('变更原因', default='', max_length=50) + change_user = models.ForeignKey( + 'system.user', on_delete=models.SET_NULL, verbose_name='操作人', null=True, blank=True) + change_time = models.DateTimeField('变更时间') + val_new = models.JSONField('变更后完整数据', default=dict) + difference = models.JSONField('变更情况', default=list) + + class DrfRequestLog(BaseModel): """Logs Django rest framework API requests""" @@ -42,7 +55,8 @@ class DrfRequestLog(BaseModel): response = models.TextField(null=True, blank=True) errors = models.TextField(null=True, blank=True) agent = models.TextField(null=True, blank=True) - status_code = models.PositiveIntegerField(null=True, blank=True, db_index=True) + status_code = models.PositiveIntegerField( + null=True, blank=True, db_index=True) class Meta: verbose_name = "DRF请求日志" diff --git a/apps/monitor/serializers.py b/apps/monitor/serializers.py index 4a79c5aa..c2303c0d 100644 --- a/apps/monitor/serializers.py +++ b/apps/monitor/serializers.py @@ -1,4 +1,14 @@ from rest_framework import serializers +from apps.utils.serializers import CustomModelSerializer +from apps.monitor.models import AuditLog + class DbbackupDeleteSerializer(serializers.Serializer): - filepaths = serializers.ListField(child=serializers.CharField(), label="文件地址列表") \ No newline at end of file + filepaths = serializers.ListField( + child=serializers.CharField(), label="文件地址列表") + + +class AuditLogSerializer(CustomModelSerializer): + class Meta: + model = AuditLog + fields = '__all__' diff --git a/apps/monitor/services.py b/apps/monitor/services.py index 3c0a1dad..d1c83ff1 100644 --- a/apps/monitor/services.py +++ b/apps/monitor/services.py @@ -1,4 +1,63 @@ import psutil +from apps.monitor.models import AuditLog +from apps.system.models import User +from datetime import datetime +from apps.utils.tools import compare_values +from apps.utils.models import get_model_info + + +def delete_auditlog(model, instance_id): + """ + 删除其对应的审计记录 + """ + model_name = get_model_info(model) + AuditLog.objects.filter(model_name=model_name, + instance_id=instance_id).delete() + + +def create_auditlog(action: str, instance, val_new: dict, val_old: dict = None, change_reason: str = '', delete_time: datetime = None, delete_user: User = None): + """ + 生成审计日志 + action: create/update/delete/其他action + """ + app_label_model_name = get_model_info(instance) + if val_old is None: + val_old = {} + difference = [] + has_changed = False + if action == 'create': + has_changed = True + change_user = instance.create_by + change_time = instance.create_time + elif action == 'delete': + has_changed = True + change_user = delete_user if delete_user else instance.update_by + change_time = delete_time if delete_time else instance.update_time + else: + change_user = instance.update_by + change_time = instance.update_time + for k, v in val_new.items(): + if k not in ['create_by', 'update_by', 'create_time', 'update_time', 'id']: + if k not in val_old: + difference.append( + {'field': k, 'action': 'create', 'val_old': None, 'val_new': v}) + elif not compare_values(val_new.get(k), val_old.get(k), ignore_order=True): + difference.append( + {'field': k, 'action': 'update', 'val_old': val_old[k], 'val_new': v}) + if difference: + has_changed = True + if has_changed: + AuditLog.objects.create( + action=action, + model_name=app_label_model_name, + instance_id=instance.id, + val_new=val_new, + difference=difference, + change_reason=change_reason, + change_user=change_user, + change_time=change_time + ) + class ServerService: @classmethod @@ -17,7 +76,7 @@ class ServerService: ret['count'] = psutil.cpu_count(logical=False) ret['percent'] = psutil.cpu_percent(interval=1) return ret - + @classmethod def get_disk_dict(cls): ret = {} @@ -29,4 +88,4 @@ class ServerService: @classmethod def get_full(cls): - return {'cpu': cls.get_cpu_dict(), 'memory': cls.get_memory_dict(), 'disk': cls.get_disk_dict()} \ No newline at end of file + return {'cpu': cls.get_cpu_dict(), 'memory': cls.get_memory_dict(), 'disk': cls.get_disk_dict()} diff --git a/apps/monitor/urls.py b/apps/monitor/urls.py index f960a32c..66c88138 100755 --- a/apps/monitor/urls.py +++ b/apps/monitor/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video, DbBackupView +from .views import DrfRequestLogViewSet, ServerInfoView, LogView, LogDetailView, index, room, video, DbBackupView, AuditlogViewSet API_BASE_URL = 'api/monitor/' HTML_BASE_URL = 'monitor/' @@ -13,5 +13,8 @@ urlpatterns = [ path(API_BASE_URL + 'log//', LogDetailView.as_view()), path(API_BASE_URL + 'dbbackup/', DbBackupView.as_view()), path(API_BASE_URL + 'server/', ServerInfoView.as_view()), - path(API_BASE_URL + 'request_log/', DrfRequestLogViewSet.as_view({'get': 'list'}), name='requestlog_view') + path(API_BASE_URL + 'request_log/', + DrfRequestLogViewSet.as_view({'get': 'list'}), name='requestlog_view'), + path(API_BASE_URL + 'auditlog/', + AuditlogViewSet.as_view({'get': 'list'}), name='auditlog_view') ] diff --git a/apps/monitor/views.py b/apps/monitor/views.py index da476795..a840fe64 100755 --- a/apps/monitor/views.py +++ b/apps/monitor/views.py @@ -7,13 +7,13 @@ from rest_framework.permissions import IsAuthenticated from django.conf import settings import os from rest_framework import serializers -from apps.monitor.serializers import DbbackupDeleteSerializer +from apps.monitor.serializers import DbbackupDeleteSerializer, AuditLogSerializer from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework.exceptions import NotFound from rest_framework.mixins import ListModelMixin from apps.monitor.filters import DrfLogFilterSet -from apps.monitor.models import DrfRequestLog +from apps.monitor.models import DrfRequestLog, AuditLog from apps.monitor.errors import LOG_NOT_FONED from apps.monitor.services import ServerService @@ -188,3 +188,16 @@ class DrfRequestLogViewSet(ListModelMixin, CustomGenericViewSet): ordering = ['-requested_at'] filterset_class = DrfLogFilterSet search_fields = ['path', 'view'] + + +class AuditlogViewSet(ListModelMixin, CustomGenericViewSet): + """审计日志 + + 审计日志 + """ + perms_map = {'get': '*'} + queryset = AuditLog.objects.all() + list_serializer_class = AuditLogSerializer + ordering = ['-change_time'] + filterset_fields = ['change_user'] + search_fields = ['model_name', 'action']