diff --git a/dvadmin-backend/application/settings.py b/dvadmin-backend/application/settings.py index e1b13ae..9d459d5 100644 --- a/dvadmin-backend/application/settings.py +++ b/dvadmin-backend/application/settings.py @@ -59,7 +59,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - # 'vadmin.op_drf.middleware.ApiLoggingMiddleware', # 用于记录API访问日志 + 'vadmin.op_drf.middleware.ApiLoggingMiddleware', # 用于记录API访问日志 ] # 允许跨域源 CORS_ORIGIN_ALLOW_ALL = CORS_ORIGIN_ALLOW_ALL @@ -313,3 +313,7 @@ CAPTCHA_NOISE_FUNCTIONS = ( ) # CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.random_char_challenge' CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_challenge' + +API_LOG_ENABLE = True +# API_LOG_METHODS = 'ALL' # ['POST', 'DELETE'] +API_LOG_METHODS = ['POST', 'DELETE'] # ['POST', 'DELETE'] diff --git a/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py b/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py index 2435711..9f513a7 100644 --- a/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py +++ b/dvadmin-backend/apps/vadmin/op_drf/logging/view_logger.py @@ -16,11 +16,27 @@ class ViewLogger(object): super().__init__() self.view = view self.request = request + self.model = None self.log_prefix: str = '' + if self.view and hasattr(self.view.get_queryset(), 'model'): + self.model: Model = self.view.get_queryset().model + elif self.view and hasattr(self.view.get_serializer(), 'Meta') and hasattr(self.view.get_serializer().Meta, + 'model'): + self.model: Model = self.view.get_serializer().Meta.model + if self.model: + request.session['model_name'] = getattr(self.model, '_meta').verbose_name def handle(self, request: Request, *args, **kwargs): pass + def logger(self, msg): + """ + + :param msg: + :return: logger + """ + self.request.session['request_msg'] = msg + return logger class APIViewLogger(ViewLogger): """ @@ -36,7 +52,7 @@ class APIViewLogger(ViewLogger): """ def __init__(self, view=None, request=None, *args, **kwargs) -> None: - super().__init__() + super().__init__(view, request, *args, **kwargs) self.view: APIView = view self.request: Request = request self.user = request.user @@ -52,12 +68,7 @@ class ModelViewLogger(APIViewLogger): """ def __init__(self, view=None, request=None, *args, **kwargs) -> None: - super().__init__(view, request) - - if hasattr(self.view.get_queryset(), 'model'): - self.model: Model = self.view.get_queryset().model - elif hasattr(self.view.get_serializer(), 'Meta') and hasattr(self.view.get_serializer().Meta, 'model'): - self.model: Model = self.view.get_serializer().Meta.model + super().__init__(view, request, *args, **kwargs) class RelationshipViewLogger(APIViewLogger): @@ -103,7 +114,7 @@ class CustomerRelationshipViewLogger(RelationshipViewLogger): model_name = getattr(self.view.model, '_meta').verbose_name to_field_name = self.view.to_field_name to_model_name = getattr(self.view.relationship_model, '_meta').verbose_name - logger.info( + self.logger( f'{self.log_prefix}用户[username={operator}]新增, {model_name}实例[{to_field_name}={self.instanceId}]与{to_model_name}的关联关系') def handle_put(self, request: Request, *args, **kwargs): @@ -114,7 +125,7 @@ class CustomerRelationshipViewLogger(RelationshipViewLogger): model_name = getattr(self.view.model, '_meta').verbose_name to_field_name = self.view.to_field_name to_model_name = getattr(self.view.relationship_model, '_meta').verbose_name - logger.info( + self.logger( f'{self.log_prefix}用户[username={operator}]重置, {model_name}实例[{to_field_name}={self.instanceId}]与{to_model_name}的关联关系') def handle_delete(self, request: Request, *args, **kwargs): @@ -125,7 +136,7 @@ class CustomerRelationshipViewLogger(RelationshipViewLogger): model_name = getattr(self.view.model, '_meta').verbose_name to_field_name = self.view.to_field_name to_model_name = getattr(self.view.relationship_model, '_meta').verbose_name - logger.info( + self.logger( f'{self.log_prefix}用户[username={operator}]移除, {model_name}实例[{to_field_name}={self.instanceId}]与{to_model_name}的关联关系') @@ -144,7 +155,7 @@ class CustomerModelViewLogger(ModelViewLogger): pass operator = self.user.username model_name = getattr(self.model, '_meta').verbose_name - logger.info(f'{self.log_prefix}用户[username={operator}]检索{model_name}:[{instance}]') + self.logger(f'{self.log_prefix}用户[username={operator}]检索{model_name}:[{instance}]') def handle_list(self, request: Request, *args, **kwargs): """ @@ -153,7 +164,7 @@ class CustomerModelViewLogger(ModelViewLogger): pass operator = self.user.username model_name = getattr(self.model, '_meta').verbose_name - logger.info(f'{self.log_prefix}用户[username={operator}]查询{model_name}') + self.logger(f'{self.log_prefix}用户[username={operator}]查询{model_name}') def handle_create(self, request: Request, instance: Model = None, *args, **kwargs): """ @@ -162,7 +173,7 @@ class CustomerModelViewLogger(ModelViewLogger): pass operator = self.user.username model_name = getattr(self.model, '_meta').verbose_name - logger.info(f'{self.log_prefix}用户[username={operator}]创建{model_name}:[{instance}]') + self.logger(f'{self.log_prefix}用户[username={operator}]创建{model_name}:[{instance}]') def handle_update(self, request: Request, instance: Model = None, *args, **kwargs): """ @@ -171,7 +182,7 @@ class CustomerModelViewLogger(ModelViewLogger): pass operator = self.user.username model_name = getattr(self.model, '_meta').verbose_name - logger.info(f'{self.log_prefix}用户[username={operator}]更新{model_name}:[{instance}]') + self.logger(f'{self.log_prefix}用户[username={operator}]更新{model_name}:[{instance}]') def handle_partial_update(self, request: Request, instance: Model = None, *args, **kwargs): """ @@ -180,7 +191,7 @@ class CustomerModelViewLogger(ModelViewLogger): pass operator = self.user.username model_name = getattr(self.model, '_meta').verbose_name - logger.info(f'{self.log_prefix}用户[username={operator}]部分更新{model_name}:[{instance}]') + self.logger(f'{self.log_prefix}用户[username={operator}]部分更新{model_name}:[{instance}]') def handle_destroy(self, request: Request, instance: Model = None, *args, **kwargs): """ @@ -189,4 +200,4 @@ class CustomerModelViewLogger(ModelViewLogger): pass operator = self.user.username model_name = getattr(self.model, '_meta').verbose_name - logger.info(f'{self.log_prefix}用户[username={operator}]删除{model_name}:[{instance}]') + self.logger(f'{self.log_prefix}用户[username={operator}]删除{model_name}:[{instance}]') diff --git a/dvadmin-backend/apps/vadmin/op_drf/middleware.py b/dvadmin-backend/apps/vadmin/op_drf/middleware.py index 218ef04..0ab7caa 100644 --- a/dvadmin-backend/apps/vadmin/op_drf/middleware.py +++ b/dvadmin-backend/apps/vadmin/op_drf/middleware.py @@ -2,70 +2,31 @@ django中间件 """ -import json -import datetime -from django.utils.deprecation import MiddlewareMixin -from mongoengine import DynamicDocument, StringField, IntField, DictField, DateTimeField -from rest_framework_mongoengine.serializers import DocumentSerializer -import logging -from ..utils.decorators import exceptionHandler -from ..utils.request_util import get_request_ip, get_request_data, get_request_path -from .viewsets import CustomMongoModelViewSet from django.conf import settings -logger = logging.getLogger(__name__) +from django.utils.deprecation import MiddlewareMixin - -class ApiLog(DynamicDocument): - """ - API访问日志的Mongo模型 - """ - request_ip = StringField(verbose_name="request_ip", help_text="请求IP") - request_username = StringField(verbose_name="request_username", help_text="请求username") - request_method = StringField(verbose_name="request_method", help_text="请求方法") - request_path = StringField(verbose_name="request_path", help_text="请求路径") - request_body = DictField(verbose_name="request_body", help_text="请求参数") - response_code = IntField(verbose_name="response_code", help_text="响应状态码") - response_reason = StringField(verbose_name="response_reason", help_text="响应简述") - access_time = DateTimeField(verbose_name="access_time", help_text="访问时间") - - -class ApiLogSerializer(DocumentSerializer): - """ - API访问日志的Mongo序列化器 - """ - class Meta: - model = ApiLog - fields = '__all__' - - -class ApiLogModelViewSet(CustomMongoModelViewSet): - """ - API访问日志的CRUD视图 - """ - queryset = ApiLog.objects.all() - serializer_class = ApiLogSerializer - search_fields = ('request_ip', 'request_username', 'request_method', 'response_reason', 'source_system') - ordering = '-access_time' # 默认排序 +from apps.vadmin.system.models import RequestLog +from ..utils.request_util import get_request_ip, get_request_data, get_request_path, get_browser, get_os, \ + get_login_location class ApiLoggingMiddleware(MiddlewareMixin): """ 用于记录API访问日志中间件 """ + def __init__(self, get_response=None): super().__init__(get_response) - self.enable = op_settings.get_api_log_setting().get('enable', False) - self.methods = op_settings.get_api_log_setting().get('methods', set()) + self.enable = settings.API_LOG_ENABLE or False + self.methods = settings.API_LOG_METHODS or set() @classmethod - @exceptionHandler() def __handle_request(cls, request): request.request_ip = get_request_ip(request) request.request_data = get_request_data(request) - request.access_time = datetime.datetime.now() + request.request_path = get_request_path(request) @classmethod - @exceptionHandler(logger=logger) def __handle_response(cls, request, response): # request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性 body = getattr(request, 'request_data', {}) @@ -74,16 +35,20 @@ class ApiLoggingMiddleware(MiddlewareMixin): body['password'] = '*' * len(body['password']) info = { 'request_ip': getattr(request, 'request_ip', 'unknown'), - 'request_username': request.user.username, + 'creator': request.user, 'request_method': request.method, - 'request_path': request.path, + 'request_path': request.request_path, 'request_body': body, 'response_code': response.status_code, - 'response_reason': response.reason_phrase, - 'source_system': getattr(settings,'SOURCE_SYSTEM_NAME',None), - 'access_time': request.access_time.strftime('%Y-%m-%d %H:%M:%S'), + 'request_location': get_login_location(request), + 'request_os': get_os(request), + 'request_browser': get_browser(request), + 'request_msg': request.session.get('request_msg'), + 'status': True if response.data.get('code') in [200, 204] else False, + 'json_result': {"code": response.data.get('code'), "msg": response.data.get('msg')}, + 'request_modular': request.session.get('model_name'), } - log = ApiLog(**info) + log = RequestLog(**info) log.save() def process_request(self, request): diff --git a/dvadmin-backend/apps/vadmin/op_drf/views.py b/dvadmin-backend/apps/vadmin/op_drf/views.py index 97638ab..1844dd4 100644 --- a/dvadmin-backend/apps/vadmin/op_drf/views.py +++ b/dvadmin-backend/apps/vadmin/op_drf/views.py @@ -99,189 +99,3 @@ class CustomAPIView(APIView): self.permission_denied( request, message=getattr(permission, 'message', None) ) - - -class BatchModelApIView(CustomAPIView): - """ - 模型批量CRUD通用视图 - """ - model = None - serializer_class = None - POST_serializer_class = None - PUT_serializer_class = None - field_name = 'instanceId' - instanceId_list_param_name = 'instanceIdList' - instance_info_param_name = 'info' - - def get_serializer(self, *args, **kwargs): - if not self.request: - return None - serializer_class = getattr(self, f"{self.request.method}_serializer_class", None) or getattr(self, - 'serializer_class') - serializer = serializer_class(*args, **kwargs) - if isinstance(serializer, CustomModelSerializer): - serializer.request = self.request - return serializer - - def get(self, request: Request = None, *args, **kwargs): - data = self.get_serializer(self.model.objects.filter(**{f'{self.field_name}__in': request.data}), - many=True).data - return SuccessResponse(data=data) - - def post(self, request: Request = None, *args, **kwargs): - data = [] - for info in request.data: - serializer = self.get_serializer(data=info) - serializer.is_valid(raise_exception=True) - serializer.save() - data.append(serializer.data) - return SuccessResponse(data=data) - - def put(self, request: Request = None, *args, **kwargs): - data = [] - instanceId_list = request.data.get(self.instanceId_list_param_name, []) - info = request.data.get(self.instance_info_param_name, {}) - for instanceId in instanceId_list: - serializer = self.get_serializer( - instance=self.model.objects.get(**{f'{self.field_name}': instanceId}), - data=info, - partial=True - ) - serializer.is_valid(raise_exception=True) - serializer.save() - return SuccessResponse(data=instanceId_list) - - def delete(self, request: Request = None, *args, **kwargs): - self.model.objects.filter(**{f'{self.field_name}__in': request.data}).delete() - return SuccessResponse(data=request.data) - - -class ModelRelationshipAPIView(CustomAPIView): - """ - 模型关联关系通用CRUD视图 - """ - model = None - through_model = None - relationship_model = None - - relationship_serializer = None - field_name: str = None - from_field_name: str = 'instanceId' - to_field_name: str = None - relationship_field_values = () - - view_logger_classes = [CustomerRelationshipViewLogger, ] - - def get_relationship_data(self, instanceId: str): - relationship_model_field_name = self.relationship_field_values[0] - params = {} - params[self.field_name] = instanceId - business_key_dict = self.through_model.objects.filter(**params).values( - *self.relationship_field_values).distinct() - business_key_list = [ele[relationship_model_field_name] for ele in business_key_dict] - - params = {} - params[f"{self.to_field_name}__in"] = business_key_list - queryset = self.relationship_model.objects.filter(**params) - - data = ModelRelateUtils.model_to_dict(queryset, self.relationship_serializer, default=[]) - if 'creator' in self.relationship_field_values and 'ctime' in self.relationship_field_values: - for _index in range(len(data)): - ele = data[_index] - ele['relationship_creator'] = business_key_dict[_index]['creator'] - ele['relationship_ctime'] = business_key_dict[_index]['ctime'] - return data - - def execute_method(self, execute: str, request: Request, instanceId: str, *args, **kwargs): - method = request.method.lower() - fun = None - if execute == 'before': - fun = getattr(self, f'before_{method}', None) - elif execute == 'handle': - fun = getattr(self, f'handle_{method}', None) - elif execute == 'after': - fun = getattr(self, f'after_{method}', None) - if fun and isinstance(fun, (FunctionType, MethodType)): - fun(request, instanceId, *args, **kwargs) - - def do_request(self, request: Request, instanceId: str, *args, **kwargs): - self.execute_method('before', request, instanceId, *args, **kwargs) - self.execute_method('handle', request, instanceId, *args, **kwargs) - self.execute_method('after', request, instanceId, *args, **kwargs) - self.handle_logging(request, instanceId=instanceId, *args, **kwargs) - data = self.get_relationship_data(instanceId) - return SuccessResponse(data) - - def get(self, request: Request, instanceId: str, *args, **kwargs): - return self.do_request(request, instanceId, *args, **kwargs) - - def post(self, request: Request, instanceId: str, *args, **kwargs): - return self.do_request(request, instanceId, *args, **kwargs) - - def put(self, request: Request, instanceId: str, *args, **kwargs): - return self.do_request(request, instanceId, *args, **kwargs) - - def delete(self, request: Request, instanceId: str, *args, **kwargs): - return self.do_request(request, instanceId, *args, **kwargs) - - -class ModelRelationshipView(ModelRelationshipAPIView): - """ - 模型关联关系通用CRUD视图 - """ - - def handle_get(self, request: Request, instanceId: str, *args, **kwargs): - data = self.get_relationship_data(instanceId) - return SuccessResponse(data) - - def handle_post(self, request: Request, instanceId: str, *args, **kwargs): - relationship_model_field_name = self.relationship_field_values[0] - params = {} - params[f"{self.to_field_name}__in"] = request.data - queryset = self.relationship_model.objects.filter(**params) - - exist_list = [getattr(ele, self.to_field_name) for ele in queryset] - bulk_info = [] - for _id in exist_list: - info = {} - info[relationship_model_field_name] = _id - info[self.field_name] = instanceId - info['creator'] = request.user.username - bulk_info.append(self.through_model(**info)) - self.through_model.objects.bulk_create(bulk_info) - data = self.get_relationship_data(instanceId) - return SuccessResponse(data) - - def handle_put(self, request: Request, instanceId: str, *args, **kwargs): - relationship_model_field_name = self.relationship_field_values[0] - - params1 = {} - params1[f"{self.field_name}"] = instanceId - params2 = {} - params2[f"{relationship_model_field_name}__in"] = request.data - - relationships = self.through_model.objects.filter(**params1).exclude(**params2) - relationships.delete() - - params = {} - params[f"{self.field_name}"] = instanceId - - instanceId_dict = self.through_model.objects.filter(**params).values(*self.relationship_field_values).distinct() - instanceId_list = [ele.get(relationship_model_field_name) for ele in instanceId_dict] - create_list = list(set(request.data).difference(set(instanceId_list))) - for _id in create_list: - info = {} - info[relationship_model_field_name] = _id - info[self.field_name] = instanceId - info['creator'] = request.user.username - data = self.get_relationship_data(instanceId) - return SuccessResponse(data) - - def handle_delete(self, request: Request, instanceId: str, *args, **kwargs): - relationship_model_field_name = self.relationship_field_values[0] - params = {} - params[f"{self.field_name}"] = instanceId - params[f"{relationship_model_field_name}__in"] = request.data - self.through_model.objects.filter(**params).delete() - data = self.get_relationship_data(instanceId) - return SuccessResponse(data) diff --git a/dvadmin-backend/apps/vadmin/system/models/__init__.py b/dvadmin-backend/apps/vadmin/system/models/__init__.py index c51517a..a5f45bd 100644 --- a/dvadmin-backend/apps/vadmin/system/models/__init__.py +++ b/dvadmin-backend/apps/vadmin/system/models/__init__.py @@ -6,4 +6,5 @@ from ..models.save_file import SaveFile from ..models.message_push import MessagePush from ..models.message_push import MessagePushUser from ..models.logininfor import LoginInfor +from ..models.request_log import RequestLog diff --git a/dvadmin-backend/apps/vadmin/system/models/operlog.py b/dvadmin-backend/apps/vadmin/system/models/operlog.py deleted file mode 100644 index 34a55f5..0000000 --- a/dvadmin-backend/apps/vadmin/system/models/operlog.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db.models import TextField, CharField - -from ...op_drf.models import CoreModel - - -class WebSet(CoreModel): - name = CharField(max_length=64, verbose_name="站点名称") - web_site = CharField(max_length=256, verbose_name="站点网址", null=True, blank=True) - logo = CharField(max_length=256, verbose_name="网站Logo", null=True, blank=True) - record_info = TextField(verbose_name="备案信息", null=True, blank=True) - statistics_code = TextField(verbose_name="统计代码", null=True, blank=True) - copyright_info = TextField(verbose_name="版权信息", null=True, blank=True) - - class Meta: - verbose_name = '操作日志' - verbose_name_plural = verbose_name - - def __str__(self): - return f"{self.name}" diff --git a/dvadmin-backend/apps/vadmin/system/models/request_log.py b/dvadmin-backend/apps/vadmin/system/models/request_log.py new file mode 100644 index 0000000..5f44ac2 --- /dev/null +++ b/dvadmin-backend/apps/vadmin/system/models/request_log.py @@ -0,0 +1,25 @@ +from django.db.models import TextField, CharField, BooleanField + +from ...op_drf.models import CoreModel + + +class RequestLog(CoreModel): + 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_body = TextField(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_ip = CharField(max_length=32, verbose_name="请求ip地址", 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) + request_location = CharField(max_length=32, verbose_name="操作地点", null=True, blank=True) + request_os = CharField(max_length=64, verbose_name="操作系统", null=True, blank=True) + json_result = TextField(verbose_name="返回信息", null=True, blank=True) + status = BooleanField(default=False, verbose_name="响应状态") + + class Meta: + verbose_name = '操作日志' + verbose_name_plural = verbose_name + + def __str__(self): + return f"{self.request_msg}[{self.request_modular}]" diff --git a/dvadmin-backend/conf/env.example.py b/dvadmin-backend/conf/env.example.py index 7346180..9f5a203 100644 --- a/dvadmin-backend/conf/env.example.py +++ b/dvadmin-backend/conf/env.example.py @@ -41,3 +41,6 @@ CORS_ORIGIN_ALLOW_ALL = True CORS_ALLOW_CREDENTIALS = False # 验证码状态 CAPTCHA_STATE = True +# 操作日志配置 +API_LOG_ENABLE = True +API_LOG_METHODS = ['POST', 'DELETE', 'PUT'] # 'ALL' or ['POST', 'DELETE']