操作日志后端逻辑完成

pull/2/head
李强 2021-03-20 23:59:42 +08:00
parent 4cfdde7622
commit 38e09f556b
8 changed files with 79 additions and 275 deletions

View File

@ -59,7 +59,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 'vadmin.op_drf.middleware.ApiLoggingMiddleware', # 用于记录API访问日志 'vadmin.op_drf.middleware.ApiLoggingMiddleware', # 用于记录API访问日志
] ]
# 允许跨域源 # 允许跨域源
CORS_ORIGIN_ALLOW_ALL = CORS_ORIGIN_ALLOW_ALL 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.random_char_challenge'
CAPTCHA_CHALLENGE_FUNCT = 'captcha.helpers.math_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']

View File

@ -16,11 +16,27 @@ class ViewLogger(object):
super().__init__() super().__init__()
self.view = view self.view = view
self.request = request self.request = request
self.model = None
self.log_prefix: str = '' 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): def handle(self, request: Request, *args, **kwargs):
pass pass
def logger(self, msg):
"""
:param msg:
:return: logger
"""
self.request.session['request_msg'] = msg
return logger
class APIViewLogger(ViewLogger): class APIViewLogger(ViewLogger):
""" """
@ -36,7 +52,7 @@ class APIViewLogger(ViewLogger):
""" """
def __init__(self, view=None, request=None, *args, **kwargs) -> None: def __init__(self, view=None, request=None, *args, **kwargs) -> None:
super().__init__() super().__init__(view, request, *args, **kwargs)
self.view: APIView = view self.view: APIView = view
self.request: Request = request self.request: Request = request
self.user = request.user self.user = request.user
@ -52,12 +68,7 @@ class ModelViewLogger(APIViewLogger):
""" """
def __init__(self, view=None, request=None, *args, **kwargs) -> None: def __init__(self, view=None, request=None, *args, **kwargs) -> None:
super().__init__(view, request) super().__init__(view, request, *args, **kwargs)
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
class RelationshipViewLogger(APIViewLogger): class RelationshipViewLogger(APIViewLogger):
@ -103,7 +114,7 @@ class CustomerRelationshipViewLogger(RelationshipViewLogger):
model_name = getattr(self.view.model, '_meta').verbose_name model_name = getattr(self.view.model, '_meta').verbose_name
to_field_name = self.view.to_field_name to_field_name = self.view.to_field_name
to_model_name = getattr(self.view.relationship_model, '_meta').verbose_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}的关联关系') f'{self.log_prefix}用户[username={operator}]新增, {model_name}实例[{to_field_name}={self.instanceId}]与{to_model_name}的关联关系')
def handle_put(self, request: Request, *args, **kwargs): def handle_put(self, request: Request, *args, **kwargs):
@ -114,7 +125,7 @@ class CustomerRelationshipViewLogger(RelationshipViewLogger):
model_name = getattr(self.view.model, '_meta').verbose_name model_name = getattr(self.view.model, '_meta').verbose_name
to_field_name = self.view.to_field_name to_field_name = self.view.to_field_name
to_model_name = getattr(self.view.relationship_model, '_meta').verbose_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}的关联关系') f'{self.log_prefix}用户[username={operator}]重置, {model_name}实例[{to_field_name}={self.instanceId}]与{to_model_name}的关联关系')
def handle_delete(self, request: Request, *args, **kwargs): def handle_delete(self, request: Request, *args, **kwargs):
@ -125,7 +136,7 @@ class CustomerRelationshipViewLogger(RelationshipViewLogger):
model_name = getattr(self.view.model, '_meta').verbose_name model_name = getattr(self.view.model, '_meta').verbose_name
to_field_name = self.view.to_field_name to_field_name = self.view.to_field_name
to_model_name = getattr(self.view.relationship_model, '_meta').verbose_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}的关联关系') f'{self.log_prefix}用户[username={operator}]移除, {model_name}实例[{to_field_name}={self.instanceId}]与{to_model_name}的关联关系')
@ -144,7 +155,7 @@ class CustomerModelViewLogger(ModelViewLogger):
pass pass
operator = self.user.username operator = self.user.username
model_name = getattr(self.model, '_meta').verbose_name 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): def handle_list(self, request: Request, *args, **kwargs):
""" """
@ -153,7 +164,7 @@ class CustomerModelViewLogger(ModelViewLogger):
pass pass
operator = self.user.username operator = self.user.username
model_name = getattr(self.model, '_meta').verbose_name 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): def handle_create(self, request: Request, instance: Model = None, *args, **kwargs):
""" """
@ -162,7 +173,7 @@ class CustomerModelViewLogger(ModelViewLogger):
pass pass
operator = self.user.username operator = self.user.username
model_name = getattr(self.model, '_meta').verbose_name 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): def handle_update(self, request: Request, instance: Model = None, *args, **kwargs):
""" """
@ -171,7 +182,7 @@ class CustomerModelViewLogger(ModelViewLogger):
pass pass
operator = self.user.username operator = self.user.username
model_name = getattr(self.model, '_meta').verbose_name 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): def handle_partial_update(self, request: Request, instance: Model = None, *args, **kwargs):
""" """
@ -180,7 +191,7 @@ class CustomerModelViewLogger(ModelViewLogger):
pass pass
operator = self.user.username operator = self.user.username
model_name = getattr(self.model, '_meta').verbose_name 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): def handle_destroy(self, request: Request, instance: Model = None, *args, **kwargs):
""" """
@ -189,4 +200,4 @@ class CustomerModelViewLogger(ModelViewLogger):
pass pass
operator = self.user.username operator = self.user.username
model_name = getattr(self.model, '_meta').verbose_name 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}]')

View File

@ -2,70 +2,31 @@
django中间件 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 from django.conf import settings
logger = logging.getLogger(__name__) from django.utils.deprecation import MiddlewareMixin
from apps.vadmin.system.models import RequestLog
class ApiLog(DynamicDocument): from ..utils.request_util import get_request_ip, get_request_data, get_request_path, get_browser, get_os, \
""" get_login_location
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' # 默认排序
class ApiLoggingMiddleware(MiddlewareMixin): class ApiLoggingMiddleware(MiddlewareMixin):
""" """
用于记录API访问日志中间件 用于记录API访问日志中间件
""" """
def __init__(self, get_response=None): def __init__(self, get_response=None):
super().__init__(get_response) super().__init__(get_response)
self.enable = op_settings.get_api_log_setting().get('enable', False) self.enable = settings.API_LOG_ENABLE or False
self.methods = op_settings.get_api_log_setting().get('methods', set()) self.methods = settings.API_LOG_METHODS or set()
@classmethod @classmethod
@exceptionHandler()
def __handle_request(cls, request): def __handle_request(cls, request):
request.request_ip = get_request_ip(request) request.request_ip = get_request_ip(request)
request.request_data = get_request_data(request) request.request_data = get_request_data(request)
request.access_time = datetime.datetime.now() request.request_path = get_request_path(request)
@classmethod @classmethod
@exceptionHandler(logger=logger)
def __handle_response(cls, request, response): def __handle_response(cls, request, response):
# request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性 # request_data,request_ip由PermissionInterfaceMiddleware中间件中添加的属性
body = getattr(request, 'request_data', {}) body = getattr(request, 'request_data', {})
@ -74,16 +35,20 @@ class ApiLoggingMiddleware(MiddlewareMixin):
body['password'] = '*' * len(body['password']) body['password'] = '*' * len(body['password'])
info = { info = {
'request_ip': getattr(request, 'request_ip', 'unknown'), 'request_ip': getattr(request, 'request_ip', 'unknown'),
'request_username': request.user.username, 'creator': request.user,
'request_method': request.method, 'request_method': request.method,
'request_path': request.path, 'request_path': request.request_path,
'request_body': body, 'request_body': body,
'response_code': response.status_code, 'response_code': response.status_code,
'response_reason': response.reason_phrase, 'request_location': get_login_location(request),
'source_system': getattr(settings,'SOURCE_SYSTEM_NAME',None), 'request_os': get_os(request),
'access_time': request.access_time.strftime('%Y-%m-%d %H:%M:%S'), '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() log.save()
def process_request(self, request): def process_request(self, request):

View File

@ -99,189 +99,3 @@ class CustomAPIView(APIView):
self.permission_denied( self.permission_denied(
request, message=getattr(permission, 'message', None) 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)

View File

@ -6,4 +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

View File

@ -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}"

View File

@ -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}]"

View File

@ -41,3 +41,6 @@ CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = False CORS_ALLOW_CREDENTIALS = False
# 验证码状态 # 验证码状态
CAPTCHA_STATE = True CAPTCHA_STATE = True
# 操作日志配置
API_LOG_ENABLE = True
API_LOG_METHODS = ['POST', 'DELETE', 'PUT'] # 'ALL' or ['POST', 'DELETE']