操作日志前端完成
parent
38e09f556b
commit
f2f8fa241e
|
@ -118,7 +118,7 @@ USE_I18N = True
|
||||||
|
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = False
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||||
|
@ -316,4 +316,4 @@ CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge'
|
||||||
|
|
||||||
API_LOG_ENABLE = True
|
API_LOG_ENABLE = True
|
||||||
# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE']
|
# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE']
|
||||||
API_LOG_METHODS = ['POST', 'DELETE'] # ['POST', 'DELETE']
|
# API_LOG_METHODS = ['POST', 'DELETE'] # ['POST', 'DELETE']
|
||||||
|
|
|
@ -201,3 +201,12 @@ class CustomerModelViewLogger(ModelViewLogger):
|
||||||
operator = self.user.username
|
operator = self.user.username
|
||||||
model_name = getattr(self.model, '_meta').verbose_name
|
model_name = getattr(self.model, '_meta').verbose_name
|
||||||
self.logger(f'{self.log_prefix}用户[username={operator}]删除{model_name}:[{instance}]')
|
self.logger(f'{self.log_prefix}用户[username={operator}]删除{model_name}:[{instance}]')
|
||||||
|
|
||||||
|
def handle_other(self, request: Request, instance: Model = None, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
仅 其他 请求才会触发此方法
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
operator = self.user.username
|
||||||
|
model_name = getattr(self.model, '_meta').verbose_name
|
||||||
|
self.logger(f'{self.log_prefix}用户[username={operator}]其他请求{model_name}:[{instance}]')
|
||||||
|
|
|
@ -5,7 +5,7 @@ django中间件
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
from apps.vadmin.system.models import RequestLog
|
from apps.vadmin.system.models import OperationLog
|
||||||
from ..utils.request_util import get_request_ip, get_request_data, get_request_path, get_browser, get_os, \
|
from ..utils.request_util import get_request_ip, get_request_data, get_request_path, get_browser, get_os, \
|
||||||
get_login_location
|
get_login_location
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class ApiLoggingMiddleware(MiddlewareMixin):
|
||||||
'request_method': request.method,
|
'request_method': request.method,
|
||||||
'request_path': request.request_path,
|
'request_path': request.request_path,
|
||||||
'request_body': body,
|
'request_body': body,
|
||||||
'response_code': response.status_code,
|
'response_code': response.data.get('code'),
|
||||||
'request_location': get_login_location(request),
|
'request_location': get_login_location(request),
|
||||||
'request_os': get_os(request),
|
'request_os': get_os(request),
|
||||||
'request_browser': get_browser(request),
|
'request_browser': get_browser(request),
|
||||||
|
@ -48,7 +48,7 @@ class ApiLoggingMiddleware(MiddlewareMixin):
|
||||||
'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')},
|
'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')},
|
||||||
'request_modular': request.session.get('model_name'),
|
'request_modular': request.session.get('model_name'),
|
||||||
}
|
}
|
||||||
log = RequestLog(**info)
|
log = OperationLog(**info)
|
||||||
log.save()
|
log.save()
|
||||||
|
|
||||||
def process_request(self, request):
|
def process_request(self, request):
|
||||||
|
|
|
@ -7,6 +7,7 @@ from rest_framework.request import Request
|
||||||
|
|
||||||
from .response import SuccessResponse
|
from .response import SuccessResponse
|
||||||
from ..utils.export_excel import excel_to_data, export_excel_save_model
|
from ..utils.export_excel import excel_to_data, export_excel_save_model
|
||||||
|
from ..utils.request_util import get_verbose_name
|
||||||
|
|
||||||
|
|
||||||
class CreateModelMixin(mixins.CreateModelMixin):
|
class CreateModelMixin(mixins.CreateModelMixin):
|
||||||
|
@ -35,10 +36,10 @@ class ListModelMixin(mixins.ListModelMixin):
|
||||||
list_serializer_class = None
|
list_serializer_class = None
|
||||||
|
|
||||||
def list(self, request: Request, *args, **kwargs):
|
def list(self, request: Request, *args, **kwargs):
|
||||||
queryset = self.filter_queryset(self.get_queryset())
|
|
||||||
page = self.paginate_queryset(queryset)
|
|
||||||
if hasattr(self, 'handle_logging'):
|
if hasattr(self, 'handle_logging'):
|
||||||
self.handle_logging(request, *args, **kwargs)
|
self.handle_logging(request, *args, **kwargs)
|
||||||
|
queryset = self.filter_queryset(self.get_queryset())
|
||||||
|
page = self.paginate_queryset(queryset)
|
||||||
if page is not None:
|
if page is not None:
|
||||||
if getattr(self, 'values_queryset', None):
|
if getattr(self, 'values_queryset', None):
|
||||||
return self.get_paginated_response(page)
|
return self.get_paginated_response(page)
|
||||||
|
@ -298,8 +299,7 @@ class ImportSerializerMixin:
|
||||||
# 导入序列化器
|
# 导入序列化器
|
||||||
import_serializer_class = None
|
import_serializer_class = None
|
||||||
|
|
||||||
|
@transaction.atomic # Django 事物
|
||||||
@transaction.atomic # Django 事物
|
|
||||||
def importTemplate(self, request: Request, *args, **kwargs):
|
def importTemplate(self, request: Request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
用户导人模板
|
用户导人模板
|
||||||
|
@ -358,4 +358,5 @@ class ExportSerializerMixin:
|
||||||
% self.__class__.__name__
|
% self.__class__.__name__
|
||||||
)
|
)
|
||||||
data = self.export_serializer_class(self.get_queryset(), many=True).data
|
data = self.export_serializer_class(self.get_queryset(), many=True).data
|
||||||
return SuccessResponse(export_excel_save_model(request, self.export_field_data, data, '导出用户数据.xls'))
|
return SuccessResponse(export_excel_save_model(request, self.export_field_data, data,
|
||||||
|
f'导出{get_verbose_name(self.get_queryset())}.xls'))
|
||||||
|
|
|
@ -72,7 +72,7 @@ class CustomAPIView(APIView):
|
||||||
method = request.method.lower()
|
method = request.method.lower()
|
||||||
for view_logger in view_loggers:
|
for view_logger in view_loggers:
|
||||||
view_logger.handle(request, *args, **kwargs)
|
view_logger.handle(request, *args, **kwargs)
|
||||||
logger_fun = getattr(view_logger, f'handle_{method}', None)
|
logger_fun = getattr(view_logger, f'handle_{method}', f'handle_other')
|
||||||
if logger_fun and isinstance(logger_fun, (FunctionType, MethodType)):
|
if logger_fun and isinstance(logger_fun, (FunctionType, MethodType)):
|
||||||
logger_fun(request, *args, **kwargs)
|
logger_fun(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import django_filters
|
import django_filters
|
||||||
|
|
||||||
from .models import LoginInfor
|
from .models import LoginInfor, OperationLog
|
||||||
from ..system.models import DictDetails, DictData, ConfigSettings, MessagePush, SaveFile
|
from ..system.models import DictDetails, DictData, ConfigSettings, MessagePush, SaveFile
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,3 +70,15 @@ class LoginInforFilter(django_filters.rest_framework.FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LoginInfor
|
model = LoginInfor
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class OperationLogFilter(django_filters.rest_framework.FilterSet):
|
||||||
|
"""
|
||||||
|
操作日志 简单过滤器
|
||||||
|
"""
|
||||||
|
request_modular = django_filters.CharFilter(lookup_expr='icontains')
|
||||||
|
creator_username = django_filters.CharFilter(field_name='creator__username', lookup_expr='icontains')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OperationLog
|
||||||
|
fields = '__all__'
|
||||||
|
|
|
@ -6,5 +6,5 @@ from ..models.save_file import SaveFile
|
||||||
from ..models.message_push import MessagePush
|
from ..models.message_push import MessagePush
|
||||||
from ..models.message_push import MessagePushUser
|
from ..models.message_push import MessagePushUser
|
||||||
from ..models.logininfor import LoginInfor
|
from ..models.logininfor import LoginInfor
|
||||||
from ..models.request_log import RequestLog
|
from ..models.operation_log import OperationLog
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,12 @@ from django.db.models import TextField, CharField, BooleanField
|
||||||
from ...op_drf.models import CoreModel
|
from ...op_drf.models import CoreModel
|
||||||
|
|
||||||
|
|
||||||
class RequestLog(CoreModel):
|
class OperationLog(CoreModel):
|
||||||
request_modular = CharField(max_length=64, verbose_name="请求模块", null=True, blank=True)
|
request_modular = CharField(max_length=64, verbose_name="请求模块", null=True, blank=True)
|
||||||
request_path = CharField(max_length=400, verbose_name="请求地址", null=True, blank=True)
|
request_path = CharField(max_length=400, verbose_name="请求地址", null=True, blank=True)
|
||||||
request_body = TextField(verbose_name="请求参数", null=True, blank=True)
|
request_body = TextField(verbose_name="请求参数", null=True, blank=True)
|
||||||
request_method = CharField(max_length=64, verbose_name="请求方式", null=True, blank=True)
|
request_method = CharField(max_length=64, verbose_name="请求方式", null=True, blank=True)
|
||||||
request_msg = CharField(max_length=64, verbose_name="操作说明", null=True, blank=True)
|
request_msg = TextField(verbose_name="操作说明", null=True, blank=True)
|
||||||
request_ip = CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True)
|
request_ip = CharField(max_length=32, verbose_name="请求ip地址", null=True, blank=True)
|
||||||
request_browser = CharField(max_length=32, verbose_name="请求浏览器", null=True, blank=True)
|
request_browser = CharField(max_length=32, verbose_name="请求浏览器", null=True, blank=True)
|
||||||
response_code = CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True)
|
response_code = CharField(max_length=32, verbose_name="响应状态码", null=True, blank=True)
|
|
@ -1,6 +1,6 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import LoginInfor
|
from .models import LoginInfor, OperationLog
|
||||||
from ..op_drf.serializers import CustomModelSerializer
|
from ..op_drf.serializers import CustomModelSerializer
|
||||||
from ..system.models import DictData, DictDetails, ConfigSettings, SaveFile, MessagePush, MessagePushUser
|
from ..system.models import DictData, DictDetails, ConfigSettings, SaveFile, MessagePush, MessagePushUser
|
||||||
|
|
||||||
|
@ -196,6 +196,7 @@ class ExportMessagePushSerializer(CustomModelSerializer):
|
||||||
'id', 'title', 'content', 'message_type', 'is_reviewed', 'status', 'users', 'creator', 'modifier',
|
'id', 'title', 'content', 'message_type', 'is_reviewed', 'status', 'users', 'creator', 'modifier',
|
||||||
'update_datetime', 'create_datetime')
|
'update_datetime', 'create_datetime')
|
||||||
|
|
||||||
|
|
||||||
class MessagePushUserSerializer(CustomModelSerializer):
|
class MessagePushUserSerializer(CustomModelSerializer):
|
||||||
"""
|
"""
|
||||||
消息通知 用户查询简单序列化器
|
消息通知 用户查询简单序列化器
|
||||||
|
@ -208,7 +209,7 @@ class MessagePushUserSerializer(CustomModelSerializer):
|
||||||
# return UserProfileSerializer(obj.user.all(), many=True).data
|
# return UserProfileSerializer(obj.user.all(), many=True).data
|
||||||
# 返回这个消息是否已读
|
# 返回这个消息是否已读
|
||||||
def get_is_read(self, obj):
|
def get_is_read(self, obj):
|
||||||
object = MessagePushUser.objects.filter(message_push=obj,user=self.context.get('request').user).first()
|
object = MessagePushUser.objects.filter(message_push=obj, user=self.context.get('request').user).first()
|
||||||
return object.is_read if object else False
|
return object.is_read if object else False
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -218,6 +219,7 @@ class MessagePushUserSerializer(CustomModelSerializer):
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
return super().save(**kwargs)
|
return super().save(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
# ************** 登录日志 序列化器 ************** #
|
# ************** 登录日志 序列化器 ************** #
|
||||||
# ================================================= #
|
# ================================================= #
|
||||||
|
@ -230,3 +232,30 @@ class LoginInforSerializer(CustomModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = LoginInfor
|
model = LoginInfor
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================= #
|
||||||
|
# ************** 操作日志 序列化器 ************** #
|
||||||
|
# ================================================= #
|
||||||
|
|
||||||
|
class OperationLogSerializer(CustomModelSerializer):
|
||||||
|
"""
|
||||||
|
操作日志 简单序列化器
|
||||||
|
"""
|
||||||
|
creator_name = serializers.SlugRelatedField(slug_field="username", source="creator", read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = OperationLog
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class ExportOperationLogSerializer(CustomModelSerializer):
|
||||||
|
"""
|
||||||
|
导出 操作日志 简单序列化器
|
||||||
|
"""
|
||||||
|
creator_name = serializers.SlugRelatedField(slug_field="username", source="creator", read_only=True)
|
||||||
|
class Meta:
|
||||||
|
model = OperationLog
|
||||||
|
fields = ('request_modular', 'request_path', 'request_body', 'request_method', 'request_msg', 'request_ip',
|
||||||
|
'request_browser', 'response_code', 'request_location', 'request_os', 'json_result', 'status',
|
||||||
|
'creator_name')
|
||||||
|
|
|
@ -2,7 +2,8 @@ from django.urls import re_path
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from ..system.views import DictDataModelViewSet, DictDetailsModelViewSet, \
|
from ..system.views import DictDataModelViewSet, DictDetailsModelViewSet, \
|
||||||
ConfigSettingsModelViewSet, SaveFileModelViewSet, MessagePushModelViewSet, LoginInforModelViewSet
|
ConfigSettingsModelViewSet, SaveFileModelViewSet, MessagePushModelViewSet, LoginInforModelViewSet, \
|
||||||
|
OperationLogModelViewSet
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'dict/type', DictDataModelViewSet)
|
router.register(r'dict/type', DictDataModelViewSet)
|
||||||
|
@ -11,6 +12,8 @@ router.register(r'config', ConfigSettingsModelViewSet)
|
||||||
router.register(r'savefile', SaveFileModelViewSet)
|
router.register(r'savefile', SaveFileModelViewSet)
|
||||||
router.register(r'message', MessagePushModelViewSet)
|
router.register(r'message', MessagePushModelViewSet)
|
||||||
router.register(r'logininfor', LoginInforModelViewSet)
|
router.register(r'logininfor', LoginInforModelViewSet)
|
||||||
|
router.register(r'operation_log', OperationLogModelViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path('dict/get/type/(?P<pk>.*)/', DictDetailsModelViewSet.as_view({'get': 'dict_details_list'})),
|
re_path('dict/get/type/(?P<pk>.*)/', DictDetailsModelViewSet.as_view({'get': 'dict_details_list'})),
|
||||||
re_path('config/configKey/(?P<pk>.*)/', ConfigSettingsModelViewSet.as_view({'get': 'get_config_key'})),
|
re_path('config/configKey/(?P<pk>.*)/', ConfigSettingsModelViewSet.as_view({'get': 'get_config_key'})),
|
||||||
|
@ -30,5 +33,9 @@ urlpatterns = [
|
||||||
re_path('message/user_messages/', MessagePushModelViewSet.as_view({'get': 'get_user_messages', })),
|
re_path('message/user_messages/', MessagePushModelViewSet.as_view({'get': 'get_user_messages', })),
|
||||||
# 改为已读
|
# 改为已读
|
||||||
re_path('message/is_read/(?P<pk>.*)/', MessagePushModelViewSet.as_view({'put': 'update_is_read', })),
|
re_path('message/is_read/(?P<pk>.*)/', MessagePushModelViewSet.as_view({'put': 'update_is_read', })),
|
||||||
|
# 清空操作日志
|
||||||
|
re_path('operation_log/clean/', OperationLogModelViewSet.as_view({'delete': 'clean_all', })),
|
||||||
|
# 导出操作日志
|
||||||
|
re_path('operation_log/export/', OperationLogModelViewSet.as_view({'get': 'export', })),
|
||||||
]
|
]
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
|
||||||
from .models import LoginInfor
|
from .models import LoginInfor, OperationLog
|
||||||
from ..op_drf.filters import DataLevelPermissionsFilter
|
from ..op_drf.filters import DataLevelPermissionsFilter
|
||||||
from ..op_drf.viewsets import CustomModelViewSet
|
from ..op_drf.viewsets import CustomModelViewSet
|
||||||
from ..system.filters import DictDetailsFilter, DictDataFilter, ConfigSettingsFilter, MessagePushFilter, \
|
from ..system.filters import DictDetailsFilter, DictDataFilter, ConfigSettingsFilter, MessagePushFilter, \
|
||||||
SaveFileFilter, LoginInforFilter
|
SaveFileFilter, LoginInforFilter, OperationLogFilter
|
||||||
from ..system.models import DictData, DictDetails, ConfigSettings, SaveFile, MessagePush
|
from ..system.models import DictData, DictDetails, ConfigSettings, SaveFile, MessagePush
|
||||||
from ..system.models import MessagePushUser
|
from ..system.models import MessagePushUser
|
||||||
from ..system.serializers import DictDataSerializer, DictDataCreateUpdateSerializer, DictDetailsSerializer, \
|
from ..system.serializers import DictDataSerializer, DictDataCreateUpdateSerializer, DictDetailsSerializer, \
|
||||||
DictDetailsCreateUpdateSerializer, DictDetailsListSerializer, ConfigSettingsSerializer, \
|
DictDetailsCreateUpdateSerializer, DictDetailsListSerializer, ConfigSettingsSerializer, \
|
||||||
ConfigSettingsCreateUpdateSerializer, SaveFileSerializer, SaveFileCreateUpdateSerializer, \
|
ConfigSettingsCreateUpdateSerializer, SaveFileSerializer, SaveFileCreateUpdateSerializer, \
|
||||||
ExportConfigSettingsSerializer, ExportDictDataSerializer, ExportDictDetailsSerializer, \
|
ExportConfigSettingsSerializer, ExportDictDataSerializer, ExportDictDetailsSerializer, \
|
||||||
MessagePushSerializer, MessagePushCreateUpdateSerializer, ExportMessagePushSerializer, LoginInforSerializer
|
MessagePushSerializer, MessagePushCreateUpdateSerializer, ExportMessagePushSerializer, LoginInforSerializer, \
|
||||||
|
OperationLogSerializer, ExportOperationLogSerializer
|
||||||
from ..utils.export_excel import export_excel_save_model
|
from ..utils.export_excel import export_excel_save_model
|
||||||
from ..utils.response import SuccessResponse
|
from ..utils.response import SuccessResponse
|
||||||
|
|
||||||
|
@ -224,3 +225,28 @@ class LoginInforModelViewSet(CustomModelViewSet):
|
||||||
filter_class = LoginInforFilter
|
filter_class = LoginInforFilter
|
||||||
extra_filter_backends = [DataLevelPermissionsFilter]
|
extra_filter_backends = [DataLevelPermissionsFilter]
|
||||||
ordering = 'create_datetime' # 默认排序
|
ordering = 'create_datetime' # 默认排序
|
||||||
|
|
||||||
|
|
||||||
|
class OperationLogModelViewSet(CustomModelViewSet):
|
||||||
|
"""
|
||||||
|
操作日志 模型的CRUD视图
|
||||||
|
"""
|
||||||
|
queryset = OperationLog.objects.all()
|
||||||
|
serializer_class = OperationLogSerializer
|
||||||
|
filter_class = OperationLogFilter
|
||||||
|
extra_filter_backends = [DataLevelPermissionsFilter]
|
||||||
|
ordering = '-create_datetime' # 默认排序
|
||||||
|
export_field_data = ['请求模块', '请求地址', '请求参数', '请求方式', '操作说明', '请求ip地址',
|
||||||
|
'请求浏览器', '响应状态码', '操作地点', '操作系统', '返回信息', '响应状态', '操作用户名']
|
||||||
|
export_serializer_class = ExportOperationLogSerializer
|
||||||
|
|
||||||
|
def clean_all(self, request: Request, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
清空操作日志
|
||||||
|
:param request:
|
||||||
|
:param args:
|
||||||
|
:param kwargs:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self.get_queryset().delete()
|
||||||
|
return SuccessResponse(msg="清空成功")
|
||||||
|
|
|
@ -4,6 +4,7 @@ import traceback
|
||||||
from rest_framework import serializers, exceptions
|
from rest_framework import serializers, exceptions
|
||||||
from rest_framework.views import set_rollback
|
from rest_framework.views import set_rollback
|
||||||
|
|
||||||
|
from .request_util import get_verbose_name
|
||||||
from .response import ErrorResponse
|
from .response import ErrorResponse
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -17,9 +18,9 @@ class APIException(Exception):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, code=201, message='API异常', args=('API异常',)):
|
def __init__(self, code=201, message='API异常', args=('API异常',)):
|
||||||
self.args = args
|
args = args
|
||||||
self.code = code
|
code = code
|
||||||
self.message = message
|
message = message
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.message
|
return self.message
|
||||||
|
@ -36,7 +37,7 @@ class FrameworkException(Exception):
|
||||||
|
|
||||||
def __init__(self, message='框架异常', *args: object, **kwargs: object) -> None:
|
def __init__(self, message='框架异常', *args: object, **kwargs: object) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.message = message
|
message = message
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self.message}"
|
return f"{self.message}"
|
||||||
|
@ -64,6 +65,9 @@ def op_exception_handler(ex, context):
|
||||||
"""
|
"""
|
||||||
msg = ''
|
msg = ''
|
||||||
code = '201'
|
code = '201'
|
||||||
|
request = context.get('request')
|
||||||
|
request.session['model_name'] = get_verbose_name(view=context.get('view'))
|
||||||
|
|
||||||
if isinstance(ex, AuthenticationFailed):
|
if isinstance(ex, AuthenticationFailed):
|
||||||
code = 401
|
code = 401
|
||||||
msg = ex.detail
|
msg = ex.detail
|
||||||
|
|
|
@ -172,3 +172,21 @@ def get_login_location(request, *args, **kwargs):
|
||||||
content = r.content.decode('GBK')
|
content = r.content.decode('GBK')
|
||||||
return content.replace('\r', '').replace('\n', '')
|
return content.replace('\r', '').replace('\n', '')
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def get_verbose_name(queryset=None, view=None, model=None):
|
||||||
|
"""
|
||||||
|
获取 verbose_name
|
||||||
|
:param request:
|
||||||
|
:param view:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if queryset and hasattr(queryset, 'model'):
|
||||||
|
model = queryset.model
|
||||||
|
elif view and hasattr(view.get_queryset(), 'model'):
|
||||||
|
model = view.get_queryset().model
|
||||||
|
elif view and hasattr(view.get_serializer(), 'Meta') and hasattr(view.get_serializer().Meta, 'model'):
|
||||||
|
model = view.get_serializer().Meta.model
|
||||||
|
if model:
|
||||||
|
return getattr(model, '_meta').verbose_name
|
||||||
|
return ""
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 查询操作日志列表
|
||||||
|
export function list(query) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/system/operation_log/',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除操作日志
|
||||||
|
export function delOperationLog(operId) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/system/operation_log/' + operId + '/',
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空操作日志
|
||||||
|
export function cleanOperationLog() {
|
||||||
|
return request({
|
||||||
|
url: '/admin/system/operation_log/clean/',
|
||||||
|
method: 'delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出操作日志
|
||||||
|
export function exportOperationLog(query) {
|
||||||
|
return request({
|
||||||
|
url: '/admin/system/operation_log/export/',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
|
@ -98,6 +98,18 @@ export const constantRoutes = [
|
||||||
meta: { title: '字典数据', icon: '' }
|
meta: { title: '字典数据', icon: '' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},{
|
||||||
|
path: '/operlog',
|
||||||
|
component: Layout,
|
||||||
|
hidden: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'log',
|
||||||
|
component: (resolve) => require(['@/views/vadmin/system/operlog'], resolve),
|
||||||
|
name: 'Data',
|
||||||
|
meta: { title: '操作日志', icon: '' }
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -56,16 +56,13 @@ export function resetForm(refName) {
|
||||||
// 添加日期范围
|
// 添加日期范围
|
||||||
export function addDateRange(params, dateRange, propName) {
|
export function addDateRange(params, dateRange, propName) {
|
||||||
var search = params;
|
var search = params;
|
||||||
search.params = {};
|
|
||||||
if (null != dateRange && '' != dateRange) {
|
if (null != dateRange && '' != dateRange) {
|
||||||
if (typeof(propName) === "undefined") {
|
// create_datetime__range = this.dateRange
|
||||||
search.params["beginTime"] = dateRange[0];
|
var dateTime=new Date();
|
||||||
search.params["endTime"] = dateRange[1];
|
|
||||||
} else {
|
search.as = JSON.stringify({create_datetime__range : dateRange});
|
||||||
search.params["begin" + propName] = dateRange[0];
|
|
||||||
search.params["end" + propName] = dateRange[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
console.log(11,search)
|
||||||
return search;
|
return search;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,292 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
|
||||||
|
<el-form-item label="系统模块" prop="request_modular">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.request_modular"
|
||||||
|
placeholder="请输入系统模块"
|
||||||
|
clearable
|
||||||
|
style="width: 240px;"
|
||||||
|
size="small"
|
||||||
|
@keyup.enter.native="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="操作人员" prop="creator_name">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.creator_name"
|
||||||
|
placeholder="请输入操作人员"
|
||||||
|
clearable
|
||||||
|
style="width: 240px;"
|
||||||
|
size="small"
|
||||||
|
@keyup.enter.native="handleQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.status"
|
||||||
|
placeholder="操作状态"
|
||||||
|
clearable
|
||||||
|
size="small"
|
||||||
|
style="width: 240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="dict in statusOptions"
|
||||||
|
:key="dict.dictValue"
|
||||||
|
:label="dict.dictLabel"
|
||||||
|
:value="dict.dictValue"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="操作时间">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="dateRange"
|
||||||
|
size="small"
|
||||||
|
style="width: 240px"
|
||||||
|
value-format="yyyy-MM-dd HH:mm:ss"
|
||||||
|
type="daterange"
|
||||||
|
range-separator="-"
|
||||||
|
start-placeholder="开始日期"
|
||||||
|
end-placeholder="结束日期"
|
||||||
|
:default-time="['00:00:00', '23:59:59']"
|
||||||
|
></el-date-picker>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||||
|
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-row :gutter="10" class="mb8">
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
icon="el-icon-delete"
|
||||||
|
size="mini"
|
||||||
|
:disabled="multiple"
|
||||||
|
@click="handleDelete"
|
||||||
|
v-hasPermi="['monitor:operlog:remove']"
|
||||||
|
>删除</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
icon="el-icon-delete"
|
||||||
|
size="mini"
|
||||||
|
@click="handleClean"
|
||||||
|
v-hasPermi="['monitor:operlog:remove']"
|
||||||
|
>清空</el-button>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="1.5">
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
plain
|
||||||
|
icon="el-icon-download"
|
||||||
|
size="mini"
|
||||||
|
@click="handleExport"
|
||||||
|
v-hasPermi="['system:config:export']"
|
||||||
|
>导出</el-button>
|
||||||
|
</el-col>
|
||||||
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column label="日志编号" align="center" prop="id" />
|
||||||
|
<el-table-column label="系统模块" align="center" prop="request_modular" />
|
||||||
|
<el-table-column label="请求方式" align="center" prop="request_method" />
|
||||||
|
<el-table-column label="操作人员" align="center" prop="creator_name" />
|
||||||
|
<el-table-column label="主机" align="center" prop="request_ip" width="130" :show-overflow-tooltip="true" />
|
||||||
|
<el-table-column label="操作地点" align="center" prop="request_location" :show-overflow-tooltip="true" />
|
||||||
|
<el-table-column label="操作状态" align="center" prop="status" :formatter="statusFormat" />
|
||||||
|
<el-table-column label="操作日期" align="center" prop="create_datetime" width="180">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ parseTime(scope.row.create_datetime) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
icon="el-icon-view"
|
||||||
|
@click="handleView(scope.row,scope.index)"
|
||||||
|
v-hasPermi="['monitor:operlog:query']"
|
||||||
|
>详细</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="total>0"
|
||||||
|
:total="total"
|
||||||
|
:page.sync="queryParams.pageNum"
|
||||||
|
:limit.sync="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 操作日志详细 -->
|
||||||
|
<el-dialog title="操作日志详细" :visible.sync="open" width="700px" append-to-body>
|
||||||
|
<el-form ref="form" :model="form" label-width="100px" size="mini">
|
||||||
|
<el-row>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="操作模块:">{{ form.request_modular }} / {{ form.request_msg }}</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
label="登录信息:"
|
||||||
|
>{{ form.creator_name }} / {{ form.request_ip }} / {{ form.request_location }} / {{ form.request_browser }}</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="请求地址:">{{ form.request_path }}</el-form-item>
|
||||||
|
<el-form-item label="请求方式:">{{ form.request_method }}</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="请求参数:">{{ form.request_body }}</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="返回参数:">{{ form.json_result }}</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-form-item label="返回状态码:">{{ form.response_code }}</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="操作状态:">
|
||||||
|
<div v-if="form.status === true">正常</div>
|
||||||
|
<div v-else-if="form.status === false">失败</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="操作时间:">{{ parseTime(form.create_datetime) }}</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<!-- <el-col :span="24">-->
|
||||||
|
<!-- <el-form-item label="异常信息:" v-if="form.status === false">{{ form.json_result }}</el-form-item>-->
|
||||||
|
<!-- </el-col>-->
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="open = false">关 闭</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {cleanOperationLog, delOperationLog, exportOperationLog, list} from "@/api/vadmin/system/operationlog";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Operlog",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 遮罩层
|
||||||
|
loading: true,
|
||||||
|
// 选中数组
|
||||||
|
ids: [],
|
||||||
|
// 非多个禁用
|
||||||
|
multiple: true,
|
||||||
|
// 显示搜索条件
|
||||||
|
showSearch: true,
|
||||||
|
// 总条数
|
||||||
|
total: 0,
|
||||||
|
// 表格数据
|
||||||
|
list: [],
|
||||||
|
// 是否显示弹出层
|
||||||
|
open: false,
|
||||||
|
// 类型数据字典
|
||||||
|
statusOptions: [{dictLabel: '成功', dictValue: true}, {dictLabel: '失败', dictValue: false}],
|
||||||
|
// 日期范围
|
||||||
|
dateRange: [],
|
||||||
|
// 表单参数
|
||||||
|
form: {},
|
||||||
|
// 查询参数
|
||||||
|
queryParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
request_modular: undefined,
|
||||||
|
creator_name: undefined,
|
||||||
|
status: undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/** 查询登录日志 */
|
||||||
|
getList() {
|
||||||
|
this.loading = true;
|
||||||
|
list(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
|
||||||
|
this.list = response.data.results;
|
||||||
|
this.total = response.data.count;
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// 操作日志状态字典翻译
|
||||||
|
statusFormat(row, column) {
|
||||||
|
return this.selectDictLabel(this.statusOptions, row.status);
|
||||||
|
},
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
handleQuery() {
|
||||||
|
this.queryParams.pageNum = 1;
|
||||||
|
this.getList();
|
||||||
|
},
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
resetQuery() {
|
||||||
|
this.dateRange = [];
|
||||||
|
this.resetForm("queryForm");
|
||||||
|
this.handleQuery();
|
||||||
|
},
|
||||||
|
// 多选框选中数据
|
||||||
|
handleSelectionChange(selection) {
|
||||||
|
this.ids = selection.map(item => item.id)
|
||||||
|
this.multiple = !selection.length
|
||||||
|
},
|
||||||
|
/** 详细按钮操作 */
|
||||||
|
handleView(row) {
|
||||||
|
this.open = true;
|
||||||
|
this.form = row;
|
||||||
|
},
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
handleDelete(row) {
|
||||||
|
const ids = row.id || this.ids;
|
||||||
|
this.$confirm('是否确认删除日志编号为"' + ids + '"的数据项?', "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(function() {
|
||||||
|
return delOperationLog(ids);
|
||||||
|
}).then(() => {
|
||||||
|
this.getList();
|
||||||
|
this.msgSuccess("删除成功");
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 清空按钮操作 */
|
||||||
|
handleClean() {
|
||||||
|
this.$confirm('是否确认清空所有操作日志数据项?', "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(function() {
|
||||||
|
return cleanOperationLog();
|
||||||
|
}).then(() => {
|
||||||
|
this.getList();
|
||||||
|
this.msgSuccess("清空成功");
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
handleExport() {
|
||||||
|
const queryParams = this.queryParams;
|
||||||
|
this.$confirm('是否确认导出所有操作日志数据项?', "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning"
|
||||||
|
}).then(function() {
|
||||||
|
return exportOperationLog(queryParams);
|
||||||
|
}).then(response => {
|
||||||
|
this.download(response.data.file_url,response.data.name);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue