perf: optimize user operation logs (#13221)

pull/13361/head
jiangweidong 2024-05-31 11:05:35 +08:00 committed by GitHub
parent cdfb11549e
commit dfd133cf5a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 38 additions and 10 deletions

View File

@ -52,7 +52,7 @@ class OperateLogStore(object):
resource_map = {
'Asset permission': lambda k, v: ActionChoices.display(int(v)) if k == 'Actions' else v
}
return resource_map.get(resource_type, lambda k, v: v)
return resource_map.get(resource_type, lambda k, v: _(v))
@classmethod
def convert_diff_friendly(cls, op_log):

View File

@ -37,6 +37,9 @@ class ActionChoices(TextChoices):
approve = 'approve', _('Approve')
close = 'close', _('Close')
# Custom action
finished = 'finished', _('Finished')
class LoginTypeChoices(TextChoices):
web = "W", _("Web")

View File

@ -58,7 +58,7 @@ class OperatorLogHandler(metaclass=Singleton):
return
key = '%s_%s' % (self.CACHE_KEY, instance_id)
cache.set(key, instance_dict, 3 * 60)
cache.set(key, instance_dict, 3)
def get_instance_dict_from_cache(self, instance_id):
if instance_id is None:

View File

@ -257,6 +257,8 @@ class UserLoginLog(models.Model):
class UserSession(models.Model):
_OPERATE_LOG_ACTION = {'delete': ActionChoices.finished}
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
ip = models.GenericIPAddressField(verbose_name=_("Login IP"))
key = models.CharField(max_length=128, verbose_name=_("Session key"))

View File

@ -3,7 +3,9 @@
import uuid
from django.apps import apps
from django.db.models.signals import post_save, pre_save, m2m_changed, pre_delete
from django.db.models.signals import (
pre_delete, pre_save, m2m_changed, post_delete, post_save
)
from django.dispatch import receiver
from django.utils import translation
@ -94,7 +96,7 @@ def signal_of_operate_log_whether_continue(
return condition
@receiver(pre_save)
@receiver([pre_save, pre_delete])
def on_object_pre_create_or_update(
sender, instance=None, raw=False, using=None, update_fields=None, **kwargs
):
@ -103,6 +105,7 @@ def on_object_pre_create_or_update(
)
if not ok:
return
with translation.override('en'):
# users.PrivateToken Model 没有 id 有 pk字段
instance_id = getattr(instance, 'id', getattr(instance, 'pk', None))
@ -145,7 +148,7 @@ def on_object_created_or_update(
)
@receiver(pre_delete)
@receiver(post_delete)
def on_object_delete(sender, instance=None, **kwargs):
ok = signal_of_operate_log_whether_continue(sender, instance, False)
if not ok:
@ -153,9 +156,15 @@ def on_object_delete(sender, instance=None, **kwargs):
with translation.override('en'):
resource_type = sender._meta.verbose_name
action = getattr(sender, '_OPERATE_LOG_ACTION', {})
action = action.get('delete', ActionChoices.delete)
instance_id = getattr(instance, 'id', getattr(instance, 'pk', None))
log_id, before = get_instance_dict_from_cache(instance_id)
if not log_id:
log_id, before = None, model_to_dict(instance)
create_or_update_operate_log(
ActionChoices.delete, resource_type,
resource=instance, before=model_to_dict(instance)
action, resource_type, log_id=log_id,
resource=instance, before=before,
)
@ -166,7 +175,7 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
'django_celery_beat', 'contenttypes', 'sessions', 'auth',
}
exclude_models = {
'UserPasswordHistory', 'ContentType',
'UserPasswordHistory', 'ContentType', 'Asset',
'MessageContent', 'SiteMessage',
'PlatformAutomation', 'PlatformProtocol', 'Protocol',
'HistoricalAccount', 'GatheredUser', 'ApprovalRule',
@ -180,11 +189,13 @@ def on_django_start_set_operate_log_monitor_models(sender, **kwargs):
'ApplyCommandTicket', 'ApplyLoginAssetTicket',
'FavoriteAsset',
}
include_models = {'UserSession'}
for i, app in enumerate(apps.get_models(), 1):
app_name = app._meta.app_label
model_name = app._meta.object_name
if app_name in exclude_apps or \
model_name in exclude_models or \
model_name.endswith('Execution'):
continue
if model_name not in include_models:
continue
MODELS_NEED_RECORD.add(model_name)

View File

@ -49,9 +49,15 @@ def _get_instance_field_value(
continue
value = getattr(instance, f.name, None) or getattr(instance, f.attname, None)
if not isinstance(value, bool) and not value:
if not isinstance(value, (bool, int)) and not value:
continue
choices = getattr(f, 'choices', []) or []
for c_value, c_label in choices:
if c_value == value:
value = c_label
break
if getattr(f, 'primary_key', False):
f.verbose_name = 'id'
elif isinstance(value, list):

View File

@ -75,6 +75,7 @@ class AuthMixin:
if self.can_update_ssh_key():
self.public_key = public_key
self.save()
post_user_change_password.send(self.__class__, user=self)
def can_update_password(self):
return self.is_local

View File

@ -17,6 +17,7 @@ from orgs.utils import current_org
from rbac.builtin import BuiltinRole
from rbac.models import OrgRoleBinding, SystemRoleBinding, Role
from rbac.permissions import RBACPermission
from users.signals import post_user_change_password
from ..const import PasswordStrategy
from ..models import User
@ -268,6 +269,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
instance = self.save_and_set_custom_m2m_fields(
validated_data, save_handler, created=False
)
if validated_data.get('public_key'):
post_user_change_password.send(instance.__class__, user=instance)
return instance
def create(self, validated_data):
@ -275,6 +278,8 @@ class UserSerializer(RolesSerializerMixin, CommonBulkSerializerMixin, ResourceLa
instance = self.save_and_set_custom_m2m_fields(
validated_data, save_handler, created=True
)
if validated_data.get('public_key'):
post_user_change_password.send(instance.__class__, user=instance)
return instance
@classmethod