mirror of https://github.com/jumpserver/jumpserver
[Update] 修复获取节点数量比较慢的问题 (#2184)
parent
985bd6fc82
commit
b95f8a7d6b
|
@ -20,11 +20,10 @@ from rest_framework.response import Response
|
||||||
from rest_framework_bulk import BulkModelViewSet
|
from rest_framework_bulk import BulkModelViewSet
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.db.models import Count
|
|
||||||
|
|
||||||
from common.utils import get_logger, get_object_or_none
|
from common.utils import get_logger, get_object_or_none
|
||||||
from ..hands import IsOrgAdmin
|
from ..hands import IsOrgAdmin
|
||||||
from ..models import Node
|
from ..models import Node, Asset
|
||||||
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
|
from ..tasks import update_assets_hardware_info_util, test_asset_connectability_util
|
||||||
from .. import serializers
|
from .. import serializers
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ class Node(OrgModelMixin):
|
||||||
date_create = models.DateTimeField(auto_now_add=True)
|
date_create = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
is_node = True
|
is_node = True
|
||||||
_full_value_cache_key_prefix = '_NODE_VALUE_{}'
|
_assets_amount = None
|
||||||
|
_full_value_cache_key = '_NODE_VALUE_{}'
|
||||||
|
_assets_amount_cache_key = '_NODE_ASSETS_AMOUNT_{}'
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Node")
|
verbose_name = _("Node")
|
||||||
|
@ -49,30 +51,56 @@ class Node(OrgModelMixin):
|
||||||
def name(self):
|
def name(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def assets_amount(self):
|
||||||
|
"""
|
||||||
|
获取节点下所有资产数量速度太慢,所以需要重写,使用cache等方案
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if self._assets_amount is not None:
|
||||||
|
return self._assets_amount
|
||||||
|
cache_key = self._assets_amount_cache_key.format(self.key)
|
||||||
|
cached = cache.get(cache_key)
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
assets_amount = self.get_all_assets().count()
|
||||||
|
cache.set(cache_key, assets_amount, 3600)
|
||||||
|
return assets_amount
|
||||||
|
|
||||||
|
@assets_amount.setter
|
||||||
|
def assets_amount(self, value):
|
||||||
|
self._assets_amount = value
|
||||||
|
|
||||||
|
def expire_assets_amount(self):
|
||||||
|
ancestor_keys = self.get_ancestor_keys(with_self=True)
|
||||||
|
cache_keys = [self._assets_amount_cache_key.format(k) for k in ancestor_keys]
|
||||||
|
cache.delete_many(cache_keys)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def expire_nodes_assets_amount(cls, nodes=None):
|
||||||
|
if nodes:
|
||||||
|
for node in nodes:
|
||||||
|
node.expire_assets_amount()
|
||||||
|
return
|
||||||
|
key = cls._assets_amount_cache_key.format('*')
|
||||||
|
cache.delete_pattern(key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_value(self):
|
def full_value(self):
|
||||||
key = self._full_value_cache_key_prefix.format(self.key)
|
key = self._full_value_cache_key.format(self.key)
|
||||||
cached = cache.get(key)
|
cached = cache.get(key)
|
||||||
if cached:
|
if cached:
|
||||||
return cached
|
return cached
|
||||||
value = self.get_full_value()
|
|
||||||
self.cache_full_value(value)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def get_full_value(self):
|
|
||||||
# ancestor = [a.value for a in self.get_ancestor(with_self=True)]
|
|
||||||
if self.is_root():
|
if self.is_root():
|
||||||
return self.value
|
return self.value
|
||||||
parent_full_value = self.parent.full_value
|
parent_full_value = self.parent.full_value
|
||||||
value = parent_full_value + ' / ' + self.value
|
value = parent_full_value + ' / ' + self.value
|
||||||
|
key = self._full_value_cache_key.format(self.key)
|
||||||
|
cache.set(key, value, 3600)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def cache_full_value(self, value):
|
|
||||||
key = self._full_value_cache_key_prefix.format(self.key)
|
|
||||||
cache.set(key, value, 3600)
|
|
||||||
|
|
||||||
def expire_full_value(self):
|
def expire_full_value(self):
|
||||||
key = self._full_value_cache_key_prefix.format(self.key)
|
key = self._full_value_cache_key.format(self.key)
|
||||||
cache.delete_pattern(key+'*')
|
cache.delete_pattern(key+'*')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -182,17 +210,18 @@ class Node(OrgModelMixin):
|
||||||
child.save()
|
child.save()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get_ancestor(self, with_self=False):
|
def get_ancestor_keys(self, with_self=False):
|
||||||
if self.is_root():
|
parent_keys = []
|
||||||
root = self.__class__.root()
|
key_list = self.key.split(":")
|
||||||
return [root]
|
|
||||||
_key = self.key.split(':')
|
|
||||||
if not with_self:
|
if not with_self:
|
||||||
_key.pop()
|
key_list.pop()
|
||||||
ancestor_keys = []
|
for i in range(len(key_list)):
|
||||||
for i in range(len(_key)):
|
parent_keys.append(":".join(key_list))
|
||||||
ancestor_keys.append(':'.join(_key))
|
key_list.pop()
|
||||||
_key.pop()
|
return parent_keys
|
||||||
|
|
||||||
|
def get_ancestor(self, with_self=False):
|
||||||
|
ancestor_keys = self.get_ancestor_keys(with_self=with_self)
|
||||||
ancestor = self.__class__.objects.filter(
|
ancestor = self.__class__.objects.filter(
|
||||||
key__in=ancestor_keys
|
key__in=ancestor_keys
|
||||||
).order_by('key')
|
).order_by('key')
|
||||||
|
@ -227,10 +256,6 @@ class Node(OrgModelMixin):
|
||||||
defaults = {'value': 'Default'}
|
defaults = {'value': 'Default'}
|
||||||
return cls.objects.get_or_create(defaults=defaults, key='1')
|
return cls.objects.get_or_create(defaults=defaults, key='1')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_tree_name_ref(cls):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_fake(cls, count=100):
|
def generate_fake(cls, count=100):
|
||||||
import random
|
import random
|
||||||
|
|
|
@ -43,7 +43,7 @@ class NodeGrantedSerializer(BulkSerializerMixin, serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class NodeSerializer(serializers.ModelSerializer):
|
class NodeSerializer(serializers.ModelSerializer):
|
||||||
assets_amount = serializers.SerializerMethodField()
|
assets_amount = serializers.IntegerField()
|
||||||
tree_id = serializers.SerializerMethodField()
|
tree_id = serializers.SerializerMethodField()
|
||||||
tree_parent = serializers.SerializerMethodField()
|
tree_parent = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
@ -53,6 +53,10 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||||
'id', 'key', 'value', 'assets_amount',
|
'id', 'key', 'value', 'assets_amount',
|
||||||
'is_node', 'org_id', 'tree_id', 'tree_parent',
|
'is_node', 'org_id', 'tree_id', 'tree_parent',
|
||||||
]
|
]
|
||||||
|
read_only_fields = [
|
||||||
|
'id', 'key', 'assets_amount', 'is_node',
|
||||||
|
'org_id',
|
||||||
|
]
|
||||||
list_serializer_class = BulkListSerializer
|
list_serializer_class = BulkListSerializer
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
|
@ -66,12 +70,6 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||||
)
|
)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_assets_amount(obj):
|
|
||||||
if hasattr(obj, 'assets_amount'):
|
|
||||||
return obj.assets_amount
|
|
||||||
return obj.get_all_assets().count()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_tree_id(obj):
|
def get_tree_id(obj):
|
||||||
return obj.key
|
return obj.key
|
||||||
|
@ -80,12 +78,6 @@ class NodeSerializer(serializers.ModelSerializer):
|
||||||
def get_tree_parent(obj):
|
def get_tree_parent(obj):
|
||||||
return obj.parent_key
|
return obj.parent_key
|
||||||
|
|
||||||
def get_fields(self):
|
|
||||||
fields = super().get_fields()
|
|
||||||
field = fields["key"]
|
|
||||||
field.required = False
|
|
||||||
return fields
|
|
||||||
|
|
||||||
|
|
||||||
class NodeAssetsSerializer(serializers.ModelSerializer):
|
class NodeAssetsSerializer(serializers.ModelSerializer):
|
||||||
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
assets = serializers.PrimaryKeyRelatedField(many=True, queryset=Asset.objects.all())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
#
|
#
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from django.db.models.signals import post_save, m2m_changed
|
from django.db.models.signals import post_save, m2m_changed, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from common.utils import get_logger
|
from common.utils import get_logger
|
||||||
|
@ -35,6 +35,17 @@ def on_asset_created_or_update(sender, instance=None, created=False, **kwargs):
|
||||||
update_asset_hardware_info_on_created(instance)
|
update_asset_hardware_info_on_created(instance)
|
||||||
test_asset_conn_on_created(instance)
|
test_asset_conn_on_created(instance)
|
||||||
|
|
||||||
|
# 过期节点资产数量
|
||||||
|
nodes = instance.nodes.all()
|
||||||
|
Node.expire_nodes_assets_amount(nodes)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=Asset, dispatch_uid="my_unique_identifier")
|
||||||
|
def on_asset_delete(sender, instance=None, **kwargs):
|
||||||
|
# 过期节点资产数量
|
||||||
|
nodes = instance.nodes.all()
|
||||||
|
Node.expire_nodes_assets_amount(nodes)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
@receiver(post_save, sender=SystemUser, dispatch_uid="my_unique_identifier")
|
||||||
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
def on_system_user_update(sender, instance=None, created=True, **kwargs):
|
||||||
|
@ -63,10 +74,11 @@ def on_system_user_assets_change(sender, instance=None, **kwargs):
|
||||||
|
|
||||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
def on_asset_node_changed(sender, instance=None, **kwargs):
|
def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||||
|
logger.debug("Asset node change signal received")
|
||||||
if isinstance(instance, Asset):
|
if isinstance(instance, Asset):
|
||||||
if kwargs['action'] == 'post_add':
|
if kwargs['action'] == 'post_add':
|
||||||
logger.debug("Asset node change signal received")
|
|
||||||
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
nodes = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
|
Node.expire_nodes_assets_amount(nodes)
|
||||||
system_users_assets = defaultdict(set)
|
system_users_assets = defaultdict(set)
|
||||||
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
system_users = SystemUser.objects.filter(nodes__in=nodes)
|
||||||
# 清理节点缓存
|
# 清理节点缓存
|
||||||
|
@ -79,9 +91,11 @@ def on_asset_node_changed(sender, instance=None, **kwargs):
|
||||||
@receiver(m2m_changed, sender=Asset.nodes.through)
|
@receiver(m2m_changed, sender=Asset.nodes.through)
|
||||||
def on_node_assets_changed(sender, instance=None, **kwargs):
|
def on_node_assets_changed(sender, instance=None, **kwargs):
|
||||||
if isinstance(instance, Node):
|
if isinstance(instance, Node):
|
||||||
|
logger.debug("Node assets change signal received")
|
||||||
|
# 当节点和资产关系发生改变时,过期资产数量缓存
|
||||||
|
instance.expire_assets_amount()
|
||||||
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
assets = kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||||
if kwargs['action'] == 'post_add':
|
if kwargs['action'] == 'post_add':
|
||||||
logger.debug("Node assets change signal received")
|
|
||||||
# 重新关联系统用户和资产的关系
|
# 重新关联系统用户和资产的关系
|
||||||
system_users = SystemUser.objects.filter(nodes=instance)
|
system_users = SystemUser.objects.filter(nodes=instance)
|
||||||
for system_user in system_users:
|
for system_user in system_users:
|
||||||
|
|
|
@ -116,6 +116,7 @@ function initTree2() {
|
||||||
|
|
||||||
|
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
|
}).on('show.bs.modal', function () {
|
||||||
initTable2();
|
initTable2();
|
||||||
initTree2();
|
initTree2();
|
||||||
})
|
})
|
||||||
|
|
|
@ -305,6 +305,9 @@ function onSelected(event, treeNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectQueryNode() {
|
function selectQueryNode() {
|
||||||
|
// TODO: 是否应该添加
|
||||||
|
// 暂时忽略之前选中的内容
|
||||||
|
return
|
||||||
var query_node_id = $.getUrlParam("node");
|
var query_node_id = $.getUrlParam("node");
|
||||||
var cookie_node_id = getCookie('node_selected');
|
var cookie_node_id = getCookie('node_selected');
|
||||||
var node;
|
var node;
|
||||||
|
@ -355,6 +358,9 @@ function onDrop(event, treeId, treeNodes, targetNode, moveType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTree() {
|
function initTree() {
|
||||||
|
if (zTree) {
|
||||||
|
return
|
||||||
|
}
|
||||||
var setting = {
|
var setting = {
|
||||||
view: {
|
view: {
|
||||||
dblClickExpand: false,
|
dblClickExpand: false,
|
||||||
|
@ -387,6 +393,7 @@ function initTree() {
|
||||||
};
|
};
|
||||||
|
|
||||||
var zNodes = [];
|
var zNodes = [];
|
||||||
|
console.log("Get assets")
|
||||||
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
$.get("{% url 'api-assets:node-list' %}", function(data, status){
|
||||||
$.each(data, function (index, value) {
|
$.each(data, function (index, value) {
|
||||||
value["node_id"] = value["id"];
|
value["node_id"] = value["id"];
|
||||||
|
|
Loading…
Reference in New Issue