• django 实现:闭包表—树状结构


    闭包表—树状结构数据的数据库表设计

    闭包表模型

    闭包表(Closure Table)是一种通过空间换时间的模型,它是用一个专门的关系表(其实这也是我们推荐的归一化方式)来记录树上节点之间的层级关系以及距离。

    场景

    我们 基于 django orm实现一个文件树,文件夹直接可以实现无限嵌套
    在这里插入图片描述

    models

    #  文件详情表(主要用于记录文件名)
    class DmFileDetail(models.Model):
        file_name = models.CharField("文件名(文件夹名)", max_length=50)
        is_file = models.BooleanField("是否是文件", default=False)
        user_id = models.IntegerField("用户id", default=0)
        create_time = models.IntegerField("创建时间", default=0)
        update_time = models.IntegerField("创建时间", default=0)
        is_del = models.BooleanField("是否删除", default=False)
    
        class Meta:
            db_table = 'inchat_dm_file_detail'
            verbose_name = verbose_name_plural = u'数字人文件详情表'
    
        def __str__(self):
            return self.file_name
    
    #  文件关系表(主要用户记录文件之间的关联,即路径)
    class DmFileRelation(models.Model):
        ancestor_id = models.IntegerField("祖先节点ID")
        descendant_id = models.IntegerField("子孙节点ID")
        depth = models.IntegerField("深度(层级)", db_index=True)
        user_id = models.IntegerField("用户id", default=0, db_index=True)
        is_del = models.BooleanField("是否删除", default=False)
    
        class Meta:
            db_table = 'inchat_dm_file_relation'
            index_together = ('ancestor_id', 'descendant_id')
            verbose_name = verbose_name_plural = u'数字人文件关系表'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    idfile_name
    1AAA
    2aaa.pdf
    idancestor_iddescendant_iddepth
    1110
    2220
    3121

    增删改查

    class DmRelationNode:
        """
        关系节点
        """
        NAME = "DmRelationNode"
        RELATION_CLIENT = DmFileRelation
    
        @classmethod
        def insert_relation_node(cls, node_id, user_id, parent_id):
            """
            插入新的关系节点
            """
            # 自身
            insert_self = cls.RELATION_CLIENT(
                ancestor_id=parent_id,
                descendant_id=node_id,
                user_id=user_id,
                depth=1
            )
            insert_list = []
            # 获取父节点所有祖先
            parent_relation = cls.RELATION_CLIENT.objects.filter(descendant_id=parent_id) \
                .values_list('ancestor_id', 'depth')
            for ancestor_id, depth in parent_relation:
                insert_data = cls.RELATION_CLIENT(
                    ancestor_id=ancestor_id,
                    descendant_id=node_id,
                    depth=depth + 1,
                    user_id=user_id
                )
                insert_list.append(insert_data)
            # 插入自身
            insert_list.append(insert_self)
            logger.info('%s insert_relation_node.node_id:%s,parent_id:%s,insert_list:%s', cls.NAME, node_id, parent_id,
                        insert_list)
            ret = cls.RELATION_CLIENT.objects.bulk_create(insert_list)
            logger.info('%s insert_relation_node.node_id:%s,parent_id:%s,ret_list:%s', cls.NAME, node_id, parent_id, ret)
            return ret
    
        @classmethod
        def get_ancestor_relation(cls, node_id):
            """
            获取某个节点的所有祖先节点
            """
            arges = ['ancestor_id', 'descendant_id', 'depth']
            ancestor_relation_list = cls.RELATION_CLIENT.objects.filter(descendant_id=node_id, is_del=False).values(*arges)
            relation_map = dict()
            relation_dict = relation_map
            for ancestor in ancestor_relation_list:
                relation_dict['id'] = ancestor['ancestor_id']
                if ancestor['ancestor_id'] != node_id:
                    relation_dict['children'] = {}
                    relation_dict = relation_dict['children']
            return ancestor_relation_list
    
        @classmethod
        def get_descendant_relation(cls, node_id):
            """
            获取所有的子节点
            """
            arges = ['ancestor_id', 'descendant_id', 'depth']
            descendant_relation_list = cls.RELATION_CLIENT.objects.filter(ancestor_id=node_id, is_del=False).values(*arges)
    
            return descendant_relation_list
    
        @classmethod
        def get_direct_relation(cls, user_id):
            """
            获取所有直系
            """
            arges = ['ancestor_id', 'descendant_id', 'depth']
            direct_relation = cls.RELATION_CLIENT.objects.filter(depth=1, user_id=user_id, is_del=False).values(*arges)
            return direct_relation
    
        @classmethod
        def get_children_node(cls, node_id):
            """
            获取某节点的子节点
            """
            children_node = cls.RELATION_CLIENT.objects.filter(depth=1, ancestor_id=node_id, is_del=False) \
                .values_list('descendant_id', flat=True)
            return children_node
    
        @classmethod
        def remove_node(cls, node_id):
    				"""
            删除节点
            """
            logger.info('%s remove_node. node_id:%s', cls.NAME, node_id)
            query = Q(ancestor_id=node_id, is_del=False) | Q(descendant_id=node_id, is_del=False)
            res = cls.RELATION_CLIENT.objects.filter(query).update(is_del=True)
            logger.info('%s remove_node. node_id:%s,count:%s', cls.NAME, node_id, res)
            return res
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93

    以下 是一些常规的操作

    class DmFileTree:
        """
        DM文件树
        """
        NAME = "DmFileTree"
        DETAIL_CLIENT = DmFileDetail
        RELATION_NODE_CLIENT = DmRelationNode
        FILE_SAVE_DIR = 'media/dm/'
    
        @classmethod
        def get_file_map(cls, user_id):
            """
            获取用户所有文件文件名
            """
            file_detail = cls.DETAIL_CLIENT.objects.filter(user_id=user_id).values('id', 'file_name', 'path', 'is_file')
            file_map = dict()
            for file in file_detail:
                file_dict = dict(
                    id=file['id'],
                    name=file['file_name'],
                    is_file=file['is_file'],
                    filePath=cls.FILE_SAVE_DIR + file['path'] + file['file_name']
                )
                file_map[file['id']] = file_dict
            return file_map
    
        @classmethod
        def add_file(cls, user_id, file_name, parent_id, path='', is_file=False):
            """
             新建文件(夹)
            """
            kwargs = dict(
                file_name=file_name,
                path=path,
                is_file=is_file,
                user_id=user_id,
                create_time=get_cur_timestamp()
            )
            file_obj = cls.DETAIL_CLIENT.objects.create(**kwargs)
            if not file_obj:
                logger.error('%s add_file failed. kwargs:%s', cls.NAME, kwargs)
                return False
    
            res = cls.RELATION_NODE_CLIENT.insert_relation_node(node_id=file_obj.id, user_id=user_id, parent_id=parent_id)
            if not res:
                return False
    
            return dict(id=file_obj.id, name=file_name)
    
        @classmethod
        def get_file_path(cls, file_id):
            """
            获取文件路径
            """
            ancestor_query = cls.RELATION_NODE_CLIENT.get_ancestor_relation(file_id)
            ancestor = map(lambda x: x['ancestor_id'], ancestor_query)
            # 过滤0
            ancestor = list(filter(lambda x: x > 0, ancestor))
            # 排序
            ancestor.sort()
            path = '/'.join(map(str, ancestor))
            return '/' + path + '/' if path else '/'
    
        @classmethod
        def get_all_files(cls, user_id):
            # 获取所有文件名字典
            file_map = cls.get_file_map(user_id)
            # 查询所有子目录及文件
            files_relation_list = cls.RELATION_NODE_CLIENT.get_direct_relation(user_id)
            file_info = {a['descendant_id']: file_map.get(a['descendant_id']) or {} for a in files_relation_list}
            tree = cls.list_to_tree(files_relation_list, file_info)
            return tree
    
        @classmethod
        def get_child_files(cls, user_id, parent_id):
            """
            获取下级文件
            """
            # 获取所有文件名字典
            file_map = cls.get_file_map(user_id)
            file_list = cls.RELATION_NODE_CLIENT.get_children_node(node_id=parent_id)
    
            files = map(lambda x: dict(id=x, name=file_map.get(x) or ''), file_list)
            return files
    
        @staticmethod
        def list_to_tree(data, node_dict):
            """
            将节点列表转换成树形结构字典
            :param data: 带有 id 和 parent_id 属性的节点列表
            :param node_dict: 单节点的数据结构字典
            :return: 树形结构字典
            """
    
            tree = []
    
            # 遍历每一个节点,将其添加到父节点的字典或根节点列表中
            for item in data:
                id = item['descendant_id']
                parent_id = item['ancestor_id']
    
                # 如果父节点为 None,则将当前节点添加到根节点列表中
                if not parent_id:
                    tree.append(node_dict[id])
                # 如果父节点存在,则将当前节点添加到父节点的 children 属性中
                else:
                    parent = node_dict[parent_id]
                    if 'children' not in parent:
                        parent['children'] = []
                    parent['children'].append(node_dict[id])
    
            return tree
    
        @classmethod
        def delete_file(cls, file_id):
            """
             文件删除
            """
            res1 = cls.DETAIL_CLIENT.objects.filter(id=file_id).update(is_del=True)
            logger.info('%s delete_file. file_id:%s, count:%s', cls.NAME, file_id, res1)
            res2 = cls.RELATION_NODE_CLIENT.remove_node(file_id)
    
            return res1, res2
    
        @classmethod
        def search_file(cls, file_name):
            """
            搜索文件
            """
            query_set = cls.DETAIL_CLIENT.objects.filter(file_name__icontains=file_name) \
                .values('id', 'file_name', 'path', 'is_file')
            file_list = []
            for file in query_set:
                file_dict = dict(
                    id=file['id'],
                    name=file['file_name'],
                    is_file=file['is_file'],
                    filePath='media/dm_upload' + file['path']
                )
                file_list.append(file_dict)
            return file_list
    
        @classmethod
        def get_file_url(cls, file_id, file_obj=None):
            """
            获取文件下载链接
            """
            file_url = ''
            if not file_obj:
                file_obj = cls.DETAIL_CLIENT.objects.filter(id=file_id).first()
            if file_obj:
                file_url = 'http://127.0.0.1:8000/' + cls.FILE_SAVE_DIR + file_obj.path + file_obj.file_name
            return file_url
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153

    除此之外,还有移动、复制文件(夹)。移动就是先删除再新增

  • 相关阅读:
    「Python入门」python环境搭建及VScode使用python运行方式
    java计算机毕业设计框架的电脑测评系统源码+数据库+lw文档+系统
    Java:Java中的ThreadLocal简介
    中国人造板行业现状调查分析与“十四五”战略规划研究报告2022-2028年版
    30 数据分析(上)(业务略写可跳)|jupyter|matplotlib
    大模型Java编码能力评估
    iwemeta元宇宙:一个娃娃卖9999元,泡泡玛特认为一点也不贵
    78 # koa 中间件的实现
    vue3+ts+elementplus 进度条制作浏览器进度条
    基于FPGA的64位8级流水线加法器
  • 原文地址:https://blog.csdn.net/weixin_44777680/article/details/133277293