Pārlūkot izejas kodu

修改数据模型影响关系图谱
修改数据模型血缘关系图谱
修改数据模型全链路图谱
修改API接口返回前端的响应标头,content-type为application/json
修改API接口返回前端的响应数据,增加节点类型node_type
修改API接口返回前端的响应数据,增加关系类型text

maxiaolong 2 dienas atpakaļ
vecāks
revīzija
874b1f4214

+ 36 - 1
app/__init__.py

@@ -1,4 +1,4 @@
-from flask import Flask
+from flask import Flask, jsonify
 from flask_sqlalchemy import SQLAlchemy
 from flask_cors import CORS
 import logging
@@ -44,6 +44,9 @@ def create_app():
     app.register_blueprint(data_parse_bp, url_prefix='/api/parse')
     app.register_blueprint(data_flow_bp, url_prefix='/api/dataflow')
     
+    # Configure global response headers
+    configure_response_headers(app)
+    
     # Configure logging
     configure_logging(app)
     
@@ -52,6 +55,38 @@ def create_app():
     
     return app
 
+def configure_response_headers(app):
+    """Configure global response headers for JSON content"""
+    
+    @app.after_request
+    def after_request(response):
+        from flask import request
+        
+        # 检查是否是API路径
+        if request.path.startswith('/api/'):
+            # 排除文件下载和特殊响应类型
+            if (response.content_type and 
+                any(ct in response.content_type for ct in [
+                    'application/octet-stream', 
+                    'application/pdf', 
+                    'image/', 
+                    'text/csv',
+                    'application/vnd.ms-excel',
+                    'application/vnd.openxmlformats-officedocument'
+                ])):
+                # 保持原有的文件类型不变
+                pass
+            elif response.content_type and 'application/json' in response.content_type:
+                # 确保JSON响应设置正确的Content-Type和charset
+                response.headers['Content-Type'] = 'application/json; charset=utf-8'
+            elif (not response.content_type or 
+                  response.content_type == 'text/html; charset=utf-8' or
+                  response.content_type == 'text/plain'):
+                # 对于API路由,如果没有明确设置Content-Type或设置为HTML,默认设置为JSON
+                response.headers['Content-Type'] = 'application/json; charset=utf-8'
+        
+        return response
+
 def configure_logging(app):
     """Configure logging for the application"""
     if not app.config.get('LOG_ENABLED', True):

+ 2 - 0
app/config/config.py

@@ -26,6 +26,8 @@ class BaseConfig:
     """基础配置类,包含所有环境共享的配置"""
     SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
     JSON_AS_ASCII = False
+    JSONIFY_PRETTYPRINT_REGULAR = True
+    JSON_SORT_KEYS = False
     
     # 平台特定配置
     PLATFORM = platform.system().lower()

+ 251 - 372
app/core/data_model/model.py

@@ -25,7 +25,7 @@ from app.core.graph.graph_operations import connect_graph,create_or_get_node,get
 from app.services.neo4j_driver import neo4j_driver
 from app.core.meta_data import get_formatted_time, handle_id_unstructured
 from app.core.common import delete_relationships, update_or_create_node, get_node_by_id_no_label
-from app.core.data_resource.resource import get_node_by_id
+from app.core.data_resource.resource import get_node_by_id, serialize_node_properties
 
 
 # 根据child关系计算数据模型当前的level自动保存
@@ -253,7 +253,7 @@ def resource_handle_meta_data_model(id_lists, data_model_node_id):
         #     query = """
         #     MATCH (source:DataModel), (target:DataResource)
         #     WHERE id(source)=$source_id AND id(target) IN $target_ids
-        #     MERGE (source)-[:DERIVES_FROM]->(target)
+        #     MERGE (source)-[:DERIVED_FROM]->(target)
         #     RETURN count(*) as count
         #     """
         #     with connect_graph().session() as session:
@@ -734,6 +734,7 @@ def model_resource_list(skip_count, page_size, name_filter=None, id=None,
 def model_kinship_graph(nodeid, meta=False):
     """
     生成数据模型的血缘关系图谱
+    按照DERIVED_FROM关系进行递归查找,从当前节点作为起点查找所有DERIVED_FROM关系指向的节点
     
     Args:
         nodeid: 节点ID
@@ -742,127 +743,123 @@ def model_kinship_graph(nodeid, meta=False):
     Returns:
         dict: 包含节点和连线信息的图谱数据
     """
-    result = {}
-    
-    with connect_graph().session() as session:
-        # 查询起始模型节点
-        start_node_query = """
-        MATCH (n:DataModel)
-        WHERE id(n) = $nodeId
-        RETURN n.name as name, n.en_name as en_name
-        """
-        
-        start_result = session.run(start_node_query, nodeId=nodeid)
-        start_record = start_result.single()
-        
-        if not start_record:
-            return {"nodes": [], "lines": []}
-        
-        # 查询与模型关联的血缘关系(包括数据资源和其他数据模型),表示哪些节点是当前节点的上级血缘关系
-        lineage_query = """
-        MATCH (n:DataModel)
-        WHERE id(n) = $nodeId
-        OPTIONAL MATCH (n)-[:DERIVED_FROM]->(resource:DataResource)
-        OPTIONAL MATCH (n)-[:DERIVED_FROM]->(model:DataModel)
-        RETURN resource, model
-        """
-        
-        lineage_result = session.run(lineage_query, nodeId=nodeid)
-        
-        nodes = [{"id": str(nodeid), "text": start_record['name'], "type": "model"}]
-        lines = []
-        
-        # 处理血缘节点(数据资源和数据模型)
-        for record in lineage_result:
-            # 处理数据资源节点
-            if record['resource']:
-                resource = record['resource']
-                resource_id = str(resource.id)
-                resource_name = resource.get('name', '')
-                
-                # 创建资源节点
-                resource_node = {
-                    "id": resource_id,
-                    "text": resource_name,
-                    "type": "DataResource"
-                }
-                nodes.append(resource_node)
-                
-                # 创建模型到资源的关系
-                line = {
-                    "from": str(nodeid),
-                    "to": resource_id,
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
+    try:
+        with connect_graph().session() as session:
+            # 确保nodeid为整数
+            try:
+                nodeid_int = int(nodeid)
+            except (ValueError, TypeError):
+                logger.error(f"节点ID不是有效的整数: {nodeid}")
+                return {"nodes": [], "lines": []}
             
-            # 处理数据模型节点
-            if record['model']:
-                model = record['model']
-                model_id = str(model.id)
-                model_name = model.get('name', '')
-                
-                # 创建模型节点
-                model_node = {
-                    "id": model_id,
-                    "text": model_name,
-                    "type": "DataModel"
-                }
-                nodes.append(model_node)
-                
-                # 创建模型到模型的关系
-                line = {
-                    "from": str(nodeid),
-                    "to": model_id,
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
-
-        # 处理元数据节点
-        if meta:
-            meta_query = """
+            # 查询起始模型节点是否存在
+            start_node_query = """
             MATCH (n:DataModel)
             WHERE id(n) = $nodeId
-            MATCH p = (n)-[:INCLUDES]->(meta:DataMeta)
-            RETURN meta
+            RETURN n
+            """
+            
+            start_result = session.run(start_node_query, nodeId=nodeid_int)
+            start_record = start_result.single()
+            
+            if not start_record:
+                logger.error(f"未找到ID为{nodeid_int}的DataModel节点")
+                return {"nodes": [], "lines": []}
+            
+            # 递归查找DERIVED_FROM关系
+            cypher = """
+            MATCH (start:DataModel)
+            WHERE id(start) = $nodeId
+            MATCH path = (start)-[:DERIVED_FROM*0..]->(target)
+            WHERE target:DataResource OR target:DataModel
+            RETURN path
             """
-            meta_result = session.run(meta_query, nodeId=nodeid)
+                
+            result = session.run(cypher, nodeId=nodeid_int)
             
-            for record in meta_result:
-                if 'meta' in record:
-                    meta_node = record['meta']
-                    meta_id = str(meta.id)
-                    meta_name = meta.get('name', '')
-                    meta_en_name = meta.get('en_name', '')
+            # 收集节点和关系
+            nodes = {}
+            lines = {}
+            
+            for record in result:
+                # 处理路径
+                path = record['path']
+                logger.debug(f"处理路径,长度: {len(path)}, 节点数: {len(path.nodes)}, 关系数: {len(path.relationships)}")
+                
+                # 处理路径中的所有节点
+                for node in path.nodes:
+                    node_id = int(node.id)  # 直接转换为整数
+                    if node_id not in nodes:
+                        node_dict = serialize_node_properties(node)
+                        node_dict["id"] = str(node_id)
+                        node_dict["node_type"] = list(node.labels)[0] if node.labels else ""
+                        nodes[node_id] = node_dict
+                        logger.debug(f"添加节点: ID={node_id}, 标签={list(node.labels)}")
+                
+                # 处理路径中的所有关系
+                for rel in path.relationships:
+                    rel_id = int(rel.id)  # 直接转换为整数
+                    if rel_id not in lines:
+                        rel_dict = {
+                            "id": str(rel_id),
+                            "from": str(int(rel.start_node.id)),
+                            "to": str(int(rel.end_node.id)),
+                            "text": rel.type
+                        }
+                        lines[rel_id] = rel_dict
+                        logger.debug(f"添加关系: ID={rel_id}, 类型={rel.type}, 从{int(rel.start_node.id)}到{int(rel.end_node.id)}")
+            
+            # 如果需要元数据,查询INCLUDES关系
+            if meta:
+                meta_cypher = """
+                MATCH (start:DataModel)-[r:INCLUDES]->(meta:DataMeta)
+                WHERE id(start) = $nodeId
+                RETURN start, r, meta
+                """
+                meta_result = session.run(meta_cypher, nodeId=nodeid_int)
+                
+                for meta_record in meta_result:
+                    start_node = meta_record['start']
+                    rel = meta_record['r']
+                    meta_node = meta_record['meta']
                     
-                    # 创建元数据节点
-                    meta_node = {
-                        "id": meta_id,
-                        "text": meta_name,
-                        "type": "DataMeta"
-                    }
-                    nodes.append(meta_node)
+                    # 添加元数据节点
+                    meta_node_id = int(meta_node.id)
+                    if meta_node_id not in nodes:
+                        node_dict = serialize_node_properties(meta_node)
+                        node_dict["id"] = str(meta_node_id)
+                        node_dict["node_type"] = list(meta_node.labels)[0] if meta_node.labels else ""
+                        nodes[meta_node_id] = node_dict
                     
-                    # 创建模型到元数据的标签关系
-                    tag_line = {
-                        "from": str(nodeid),
-                        "to": meta_id,
-                        "text": "INCLUDES"
-                    }
-                    lines.append(tag_line)
-
-        # 构建结果
-        result = {
-            "nodes": nodes,
-            "lines": lines
-        }
-        return result
+                    # 添加INCLUDES关系
+                    rel_id = int(rel.id)
+                    if rel_id not in lines:
+                        rel_dict = {
+                            "id": str(rel_id),
+                            "from": str(nodeid_int),
+                            "to": str(meta_node_id),
+                            "text": rel.type
+                        }
+                        lines[rel_id] = rel_dict
+            
+            logger.info(f"成功获取血缘关系图谱,ID: {nodeid_int}, 节点数: {len(nodes)}, 关系数: {len(lines)}")
+            return {
+                "nodes": list(nodes.values()),
+                "lines": list(lines.values())
+            }
+            
+    except Exception as e:
+        logger.error(f"获取数据模型血缘关系图谱失败: {str(e)}")
+        import traceback
+        logger.error(f"错误详情: {traceback.format_exc()}")
+        return {"nodes": [], "lines": []}
 
 
 # 数据模型影响图谱
 def model_impact_graph(nodeid, meta=False):
     """
     生成数据模型的影响关系图谱
+    按照DERIVED_FROM关系进行递归查找,从当前节点作为终点查找所有指向这个终点的节点
     
     Args:
         nodeid: 节点ID
@@ -871,126 +868,124 @@ def model_impact_graph(nodeid, meta=False):
     Returns:
         dict: 包含节点和连线信息的图谱数据
     """
-    result = {}
-    
-    with connect_graph().session() as session:
-        # 查询起始模型节点
-        start_node_query = """
-        MATCH (n:DataModel)
-        WHERE id(n) = $nodeId
-        RETURN n.name as name, n.en_name as en_name
-        """
-        
-        start_result = session.run(start_node_query, nodeId=nodeid)
-        start_record = start_result.single()
-        
-        if not start_record:
-            return {"nodes": [], "lines": []}
-        
-        # 查询指向当前模型的影响关系(包括数据资源和其他数据模型),表示哪些节点受到当前节点影响
-        impact_query = """
-        MATCH (n:DataModel)
-        WHERE id(n) = $nodeId
-        OPTIONAL MATCH (resource:DataResource)-[:DERIVED_FROM]->(n)
-        OPTIONAL MATCH (model:DataModel)-[:DERIVED_FROM]->(n)
-        RETURN resource, model
-        """
-        
-        impact_result = session.run(impact_query, nodeId=nodeid)
-        
-        nodes = [{"id": str(nodeid), "text": start_record['name'], "type": "model"}]
-        lines = []
-        
-        # 处理影响节点(数据资源和数据模型)
-        for record in impact_result:
-            # 处理数据资源节点
-            if record['resource']:
-                resource = record['resource']
-                resource_id = str(resource.id)
-                resource_name = resource.get('name', '')
-                
-                # 创建资源节点
-                resource_node = {
-                    "id": resource_id,
-                    "text": resource_name,
-                    "type": "DataResource"
-                }
-                nodes.append(resource_node)
-                
-                # 创建资源到模型的关系
-                line = {
-                    "from": resource_id,
-                    "to": str(nodeid),
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
+    try:
+        with connect_graph().session() as session:
+            # 确保nodeid为整数
+            try:
+                nodeid_int = int(nodeid)
+            except (ValueError, TypeError):
+                logger.error(f"节点ID不是有效的整数: {nodeid}")
+                return {"nodes": [], "lines": []}
             
-            # 处理数据模型节点
-            if record['model']:
-                model = record['model']
-                model_id = str(model.id)
-                model_name = model.get('name', '')
-                
-                # 创建模型节点
-                model_node = {
-                    "id": model_id,
-                    "text": model_name,
-                    "type": "DataModel"
-                }
-                nodes.append(model_node)
-                
-                # 创建模型到模型的关系
-                line = {
-                    "from": model_id,
-                    "to": str(nodeid),
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
-
-        # 处理元数据节点
-        if meta:
-            meta_query = """
+            # 查询起始模型节点是否存在
+            start_node_query = """
             MATCH (n:DataModel)
             WHERE id(n) = $nodeId
-            MATCH p = (n)-[:INCLUDES]->(meta:DataMeta)
-            RETURN meta
+            RETURN n
             """
-            meta_result = session.run(meta_query, nodeId=nodeid)
             
-            for record in meta_result:
-                if 'meta' in record:
-                    meta_node = record['meta']
-                    meta_id = str(meta.id)
-                    meta_name = meta.get('name', '')
+            start_result = session.run(start_node_query, nodeId=nodeid_int)
+            start_record = start_result.single()
+            
+            if not start_record:
+                logger.error(f"未找到ID为{nodeid_int}的DataModel节点")
+                return {"nodes": [], "lines": []}
+            
+            # 递归查找指向当前节点的DERIVED_FROM关系
+            cypher = """
+            MATCH (target:DataModel)
+            WHERE id(target) = $nodeId
+            MATCH path = (source)-[:DERIVED_FROM*0..]->(target)
+            WHERE source:DataResource OR source:DataModel
+            RETURN path
+            """
+                
+            result = session.run(cypher, nodeId=nodeid_int)
+            
+            # 收集节点和关系
+            nodes = {}
+            lines = {}
+            
+            for record in result:
+                # 处理路径
+                path = record['path']
+                logger.debug(f"处理影响路径,长度: {len(path)}, 节点数: {len(path.nodes)}, 关系数: {len(path.relationships)}")
+                
+                # 处理路径中的所有节点
+                for node in path.nodes:
+                    node_id = int(node.id)  # 直接转换为整数
+                    if node_id not in nodes:
+                        node_dict = serialize_node_properties(node)
+                        node_dict["id"] = str(node_id)
+                        node_dict["node_type"] = list(node.labels)[0] if node.labels else ""
+                        nodes[node_id] = node_dict
+                        logger.debug(f"添加影响节点: ID={node_id}, 标签={list(node.labels)}")
+                
+                # 处理路径中的所有关系
+                for rel in path.relationships:
+                    rel_id = int(rel.id)  # 直接转换为整数
+                    if rel_id not in lines:
+                        rel_dict = {
+                            "id": str(rel_id),
+                            "from": str(int(rel.start_node.id)),
+                            "to": str(int(rel.end_node.id)),
+                            "text": rel.type
+                        }
+                        lines[rel_id] = rel_dict
+                        logger.debug(f"添加影响关系: ID={rel_id}, 类型={rel.type}, 从{int(rel.start_node.id)}到{int(rel.end_node.id)}")
+            
+            # 如果需要元数据,查询INCLUDES关系
+            if meta:
+                meta_cypher = """
+                MATCH (target:DataModel)-[r:INCLUDES]->(meta:DataMeta)
+                WHERE id(target) = $nodeId
+                RETURN target, r, meta
+                """
+                meta_result = session.run(meta_cypher, nodeId=nodeid_int)
+                
+                for meta_record in meta_result:
+                    target_node = meta_record['target']
+                    rel = meta_record['r']
+                    meta_node = meta_record['meta']
                     
-                    # 创建元数据节点
-                    meta_node = {
-                        "id": meta_id,
-                        "text": meta_name,
-                        "type": "DataMeta"
-                    }
-                    nodes.append(meta_node)
+                    # 添加元数据节点
+                    meta_node_id = int(meta_node.id)
+                    if meta_node_id not in nodes:
+                        node_dict = serialize_node_properties(meta_node)
+                        node_dict["id"] = str(meta_node_id)
+                        node_dict["node_type"] = list(meta_node.labels)[0] if meta_node.labels else ""
+                        nodes[meta_node_id] = node_dict
                     
-                    # 创建模型到元数据的标签关系
-                    tag_line = {
-                        "from": str(nodeid),
-                        "to": meta_id,
-                        "text": "INCLUDES"
-                    }
-                    lines.append(tag_line)
-
-        # 构建结果
-        result = {
-            "nodes": nodes,
-            "lines": lines
-        }
-        return result
+                    # 添加INCLUDES关系
+                    rel_id = int(rel.id)
+                    if rel_id not in lines:
+                        rel_dict = {
+                            "id": str(rel_id),
+                            "from": str(nodeid_int),
+                            "to": str(meta_node_id),
+                            "text": rel.type
+                        }
+                        lines[rel_id] = rel_dict
+            
+            logger.info(f"成功获取影响关系图谱,ID: {nodeid_int}, 节点数: {len(nodes)}, 关系数: {len(lines)}")
+            return {
+                "nodes": list(nodes.values()),
+                "lines": list(lines.values())
+            }
+            
+    except Exception as e:
+        logger.error(f"获取数据模型影响关系图谱失败: {str(e)}")
+        import traceback
+        logger.error(f"错误详情: {traceback.format_exc()}")
+        return {"nodes": [], "lines": []}
 
 
 # 数据模型全部图谱
 def model_all_graph(nodeid, meta=False):
     """
     生成数据模型的所有关系图谱
+    分别调用model_impact_graph查找影响关系,调用model_kinship_graph查找血缘关系,
+    然后合并两部分数据返回
     
     Args:
         nodeid: 节点ID
@@ -999,171 +994,55 @@ def model_all_graph(nodeid, meta=False):
     Returns:
         dict: 包含节点和连线信息的图谱数据
     """
-    result = {}
-    
-    with connect_graph().session() as session:
-        # 查询起始模型节点
-        start_node_query = """
-        MATCH (n:DataModel)
-        WHERE id(n) = $nodeId
-        RETURN n.name as name, n.en_name as en_name
-        """
+    try:
+        # 获取血缘关系图谱
+        kinship_data = model_kinship_graph(nodeid, meta)
         
-        start_result = session.run(start_node_query, nodeId=nodeid)
-        start_record = start_result.single()
+        # 获取影响关系图谱
+        impact_data = model_impact_graph(nodeid, meta)
         
-        if not start_record:
-            return {"nodes": [], "lines": []}
+        # 合并节点数据,使用字典去重
+        merged_nodes = {}
+        merged_lines = {}
         
-        # 查询与模型关联的所有DERIVED_FROM关系(双向,包括数据资源和其他数据模型)
-        all_relations_query = """
-        MATCH (n:DataModel)
-        WHERE id(n) = $nodeId
+        # 添加血缘关系的节点和连线
+        if kinship_data and 'nodes' in kinship_data:
+            for node in kinship_data['nodes']:
+                node_id = node.get('id')
+                if node_id:
+                    merged_nodes[node_id] = node
         
-        // 查询当前节点指向的节点(血缘关系)
-        OPTIONAL MATCH (n)-[:DERIVED_FROM]->(outbound_resource:DataResource)
-        OPTIONAL MATCH (n)-[:DERIVED_FROM]->(outbound_model:DataModel)
+        if kinship_data and 'lines' in kinship_data:
+            for line in kinship_data['lines']:
+                line_id = line.get('id')
+                if line_id:
+                    merged_lines[line_id] = line
         
-        // 查询指向当前节点的节点(影响关系)
-        OPTIONAL MATCH (inbound_resource:DataResource)-[:DERIVED_FROM]->(n)
-        OPTIONAL MATCH (inbound_model:DataModel)-[:DERIVED_FROM]->(n)
+        # 添加影响关系的节点和连线
+        if impact_data and 'nodes' in impact_data:
+            for node in impact_data['nodes']:
+                node_id = node.get('id')
+                if node_id:
+                    merged_nodes[node_id] = node
         
-        RETURN outbound_resource, outbound_model, inbound_resource, inbound_model
-        """
-        
-        relations_result = session.run(all_relations_query, nodeId=nodeid)
-        
-        nodes = [{"id": str(nodeid), "text": start_record['name'], "type": "DataModel"}]
-        lines = []
+        if impact_data and 'lines' in impact_data:
+            for line in impact_data['lines']:
+                line_id = line.get('id')
+                if line_id:
+                    merged_lines[line_id] = line
         
-        # 处理所有DERIVED_FROM关系节点
-        for record in relations_result:
-            # 处理当前节点指向的数据资源(血缘关系)
-            if record['outbound_resource']:
-                resource = record['outbound_resource']
-                resource_id = str(resource.id)
-                resource_name = resource.get('name', '')
-                
-                # 创建资源节点
-                resource_node = {
-                    "id": resource_id,
-                    "text": resource_name,
-                    "type": "DataResource"
-                }
-                nodes.append(resource_node)
-                
-                # 创建当前模型到资源的关系
-                line = {
-                    "from": str(nodeid),
-                    "to": resource_id,
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
-            
-            # 处理当前节点指向的数据模型(血缘关系)
-            if record['outbound_model']:
-                model = record['outbound_model']
-                model_id = str(model.id)
-                model_name = model.get('name', '')
-                
-                # 创建模型节点
-                model_node = {
-                    "id": model_id,
-                    "text": model_name,
-                    "type": "DataModel"
-                }
-                nodes.append(model_node)
-                
-                # 创建当前模型到模型的关系
-                line = {
-                    "from": str(nodeid),
-                    "to": model_id,
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
-            
-            # 处理指向当前节点的数据资源(影响关系)
-            if record['inbound_resource']:
-                resource = record['inbound_resource']
-                resource_id = str(resource.id)
-                resource_name = resource.get('name', '')
-                
-                # 创建资源节点
-                resource_node = {
-                    "id": resource_id,
-                    "text": resource_name,
-                    "type": "DataResource"
-                }
-                nodes.append(resource_node)
-                
-                # 创建资源到当前模型的关系
-                line = {
-                    "from": resource_id,
-                    "to": str(nodeid),
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
-            
-            # 处理指向当前节点的数据模型(影响关系)
-            if record['inbound_model']:
-                model = record['inbound_model']
-                model_id = str(model.id)
-                model_name = model.get('name', '')
-                
-                # 创建模型节点
-                model_node = {
-                    "id": model_id,
-                    "text": model_name,
-                    "type": "DataModel"
-                }
-                nodes.append(model_node)
-                
-                # 创建模型到当前模型的关系
-                line = {
-                    "from": model_id,
-                    "to": str(nodeid),
-                    "text": "DERIVED_FROM"
-                }
-                lines.append(line)
-
-        # 处理元数据节点
-        if meta:
-            meta_query = """
-            MATCH (n:DataModel)
-            WHERE id(n) = $nodeId
-            MATCH p = (n)-[:INCLUDES]->(meta:DataMeta)
-            RETURN meta
-            """
-            meta_result = session.run(meta_query, nodeId=nodeid)
-            
-            for record in meta_result:
-                if 'meta' in record:
-                    meta_node = record['meta']
-                    meta_id = str(meta.id)
-                    meta_name = meta.get('name', '')
-                    
-                    # 创建元数据节点
-                    meta_node = {
-                        "id": meta_id,
-                        "text": meta_name,
-                        "type": "DataMeta"
-                    }
-                    nodes.append(meta_node)
-                    
-                    # 创建模型到元数据的关系
-                    tag_line = {
-                        "from": str(nodeid),
-                        "to": meta_id,
-                        "text": "INCLUDES"
-                    }
-                    lines.append(tag_line)
-
-        # 构建结果
+        # 构建最终结果
         result = {
-            "nodes": nodes,
-            "lines": lines
+            "nodes": list(merged_nodes.values()),
+            "lines": list(merged_lines.values())
         }
+        
+        logger.info(f"成功获取完整关系图谱,ID: {nodeid}, 节点数: {len(merged_nodes)}, 关系数: {len(merged_lines)}")
         return result
+        
+    except Exception as e:
+        logger.error(f"获取数据模型完整关系图谱失败: {str(e)}")
+        return {"nodes": [], "lines": []}
 
 
 # 更新数据模型

+ 13 - 13
app/core/data_resource/resource.py

@@ -667,22 +667,22 @@ def resource_kinship_graph(resource_id, include_meta=True):
             # 处理数据资源节点
             resource_node = serialize_node_properties(record["n"])
             resource_node["id"] = record["n"].id
-            resource_node["labels"] = list(record["n"].labels)
+            resource_node["node_type"] = list(record["n"].labels)
             nodes[resource_node["id"]] = resource_node
             
             # 处理标签节点
             if record["l"]:
                 label_node = serialize_node_properties(record["l"])
                 label_node["id"] = record["l"].id
-                label_node["labels"] = list(record["l"].labels)
+                label_node["node_type"] = list(record["l"].labels)
                 nodes[label_node["id"]] = label_node
                 
                 # 添加资源-标签关系
                 relationships.append({
                     "id": f"rel-{resource_node['id']}-label-{label_node['id']}",
-                    "source": resource_node["id"],
-                    "target": label_node["id"],
-                    "type": "label"
+                    "from": resource_node["id"],
+                    "to": label_node["id"],
+                    "text": "label"
                 })
             
             # 处理元数据节点
@@ -691,15 +691,15 @@ def resource_kinship_graph(resource_id, include_meta=True):
                     if meta:  # 检查元数据节点是否存在
                         meta_node = serialize_node_properties(meta)
                         meta_node["id"] = meta.id
-                        meta_node["labels"] = list(meta.labels)
+                        meta_node["node_type"] = list(meta.labels)
                         nodes[meta_node["id"]] = meta_node
                         
                         # 添加资源-元数据关系
                         relationships.append({
                             "id": f"rel-{resource_node['id']}-INCLUDES-{meta_node['id']}",
-                            "source": resource_node["id"],
-                            "target": meta_node["id"],
-                            "type": "INCLUDES"
+                            "from": resource_node["id"],
+                            "to": meta_node["id"],
+                            "text": "INCLUDES"
                         })
             
             logger.info(f"成功获取资源图谱,ID: {resource_id_int}, 节点数: {len(nodes)}")
@@ -722,16 +722,16 @@ def resource_impact_all_graph(resource_id, include_meta=True):
                 logger.error(f"资源ID不是有效的整数: {resource_id}")
                 return {"nodes": [], "lines": []}
             
-            # 根据meta参数决定查询深度
+            # 根据meta参数决定查询深度,限制为一层
             if include_meta:
                 cypher = """
-                MATCH path = (n:DataResource)-[*1..3]-(m)
+                MATCH path = (n:DataResource)-[*1..1]-(m)
                 WHERE id(n) = $resource_id
                 RETURN path
                 """
             else:
                 cypher = """
-                MATCH path = (n:DataResource)-[*1..2]-(m)
+                MATCH path = (n:DataResource)-[*1..1]-(m)
                 WHERE id(n) = $resource_id
                 AND NOT (m:DataMeta) AND NOT (m:Metadata)
                 RETURN path
@@ -761,7 +761,7 @@ def resource_impact_all_graph(resource_id, include_meta=True):
                             "id": str(rel.id),
                             "from": str(rel.start_node.id),
                             "to": str(rel.end_node.id),
-                            "type": rel.type
+                            "text": rel.type
                         }
                         lines[rel.id] = rel_dict
             

+ 46 - 1
app/models/result.py

@@ -1,3 +1,5 @@
+from flask import jsonify, make_response
+
 def success(data=None, message="操作成功", code=200):
     """
     Return a standardized success response
@@ -32,4 +34,47 @@ def failed(message="操作失败", code=500, data=None):
         "code": code,
         "message": message,
         "data": data
-    } 
+    }
+
+def json_response(data, status_code=200):
+    """
+    Create a JSON response with proper headers
+    
+    Args:
+        data: The data to return (will be passed to jsonify)
+        status_code: HTTP status code
+        
+    Returns:
+        Flask Response object with proper JSON headers
+    """
+    response = make_response(jsonify(data), status_code)
+    response.headers['Content-Type'] = 'application/json; charset=utf-8'
+    return response
+
+def success_response(data=None, message="操作成功", status_code=200):
+    """
+    Return a standardized success response with proper headers
+    
+    Args:
+        data: The data to return
+        message: A success message  
+        status_code: HTTP status code
+        
+    Returns:
+        Flask Response object with proper JSON headers
+    """
+    return json_response(success(data, message, status_code), status_code)
+
+def failed_response(message="操作失败", status_code=500, data=None):
+    """
+    Return a standardized error response with proper headers
+    
+    Args:
+        message: An error message
+        status_code: HTTP status code
+        data: Optional data to return
+        
+    Returns:
+        Flask Response object with proper JSON headers
+    """
+    return json_response(failed(message, status_code, data), status_code) 

+ 423 - 0
modelgraphresult.txt

@@ -0,0 +1,423 @@
+[
+  {
+    "keys": [
+      "path"
+    ],
+    "length": 1,
+    "_fields": [
+      {
+        "start": {
+          "identity": {
+            "low": 287,
+            "high": 0
+          },
+          "labels": [
+            "DataModel"
+          ],
+          "properties": {
+            "leader": "mxl",
+            "level": {
+              "low": 0,
+              "high": 0
+            },
+            "origin": "resource",
+            "blood_resource": {
+              "low": 234,
+              "high": 0
+            },
+            "frequency": "月",
+            "childrenId": [],
+            "organization": "citu",
+            "name": "客户维业绩流水账",
+            "data_sensitivity": "中",
+            "en_name": "customer_service_ledger",
+            "describe": "将多个原始数据资源的数据统一加工到这个客户维流水账里",
+            "tag": {
+              "low": 114,
+              "high": 0
+            },
+            "time": "2025-06-26 17:17:58",
+            "category": "应用类",
+            "status": true
+          },
+          "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287"
+        },
+        "end": {
+          "identity": {
+            "low": 287,
+            "high": 0
+          },
+          "labels": [
+            "DataModel"
+          ],
+          "properties": {
+            "leader": "mxl",
+            "level": {
+              "low": 0,
+              "high": 0
+            },
+            "origin": "resource",
+            "blood_resource": {
+              "low": 234,
+              "high": 0
+            },
+            "frequency": "月",
+            "childrenId": [],
+            "organization": "citu",
+            "name": "客户维业绩流水账",
+            "data_sensitivity": "中",
+            "en_name": "customer_service_ledger",
+            "describe": "将多个原始数据资源的数据统一加工到这个客户维流水账里",
+            "tag": {
+              "low": 114,
+              "high": 0
+            },
+            "time": "2025-06-26 17:17:58",
+            "category": "应用类",
+            "status": true
+          },
+          "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287"
+        },
+        "segments": [],
+        "length": 0
+      }
+    ],
+    "_fieldLookup": {
+      "path": 0
+    }
+  },
+  {
+    "keys": [
+      "path"
+    ],
+    "length": 1,
+    "_fields": [
+      {
+        "start": {
+          "identity": {
+            "low": 287,
+            "high": 0
+          },
+          "labels": [
+            "DataModel"
+          ],
+          "properties": {
+            "leader": "mxl",
+            "level": {
+              "low": 0,
+              "high": 0
+            },
+            "origin": "resource",
+            "blood_resource": {
+              "low": 234,
+              "high": 0
+            },
+            "frequency": "月",
+            "childrenId": [],
+            "organization": "citu",
+            "name": "客户维业绩流水账",
+            "data_sensitivity": "中",
+            "en_name": "customer_service_ledger",
+            "describe": "将多个原始数据资源的数据统一加工到这个客户维流水账里",
+            "tag": {
+              "low": 114,
+              "high": 0
+            },
+            "time": "2025-06-26 17:17:58",
+            "category": "应用类",
+            "status": true
+          },
+          "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287"
+        },
+        "end": {
+          "identity": {
+            "low": 273,
+            "high": 0
+          },
+          "labels": [
+            "DataResource"
+          ],
+          "properties": {
+            "leader": "mxl",
+            "storage_location": "/",
+            "type": "structure",
+            "url": "/对公客户中收_资源_20250624154149.xlsx",
+            "frequency": "月",
+            "organization": "citu",
+            "name": "对公客户中收_资源",
+            "data_sensitivity": "中",
+            "en_name": "corporate_customer_fee_income_resource",
+            "tag": {
+              "low": 114,
+              "high": 0
+            },
+            "describe": "对公客户中收的原始数据",
+            "time": "2025-06-24 15:41:49",
+            "category": "应用类",
+            "status": true
+          },
+          "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:273"
+        },
+        "segments": [
+          {
+            "start": {
+              "identity": {
+                "low": 287,
+                "high": 0
+              },
+              "labels": [
+                "DataModel"
+              ],
+              "properties": {
+                "leader": "mxl",
+                "level": {
+                  "low": 0,
+                  "high": 0
+                },
+                "origin": "resource",
+                "blood_resource": {
+                  "low": 234,
+                  "high": 0
+                },
+                "frequency": "月",
+                "childrenId": [],
+                "organization": "citu",
+                "name": "客户维业绩流水账",
+                "data_sensitivity": "中",
+                "en_name": "customer_service_ledger",
+                "describe": "将多个原始数据资源的数据统一加工到这个客户维流水账里",
+                "tag": {
+                  "low": 114,
+                  "high": 0
+                },
+                "time": "2025-06-26 17:17:58",
+                "category": "应用类",
+                "status": true
+              },
+              "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287"
+            },
+            "relationship": {
+              "identity": {
+                "low": 43,
+                "high": 0
+              },
+              "start": {
+                "low": 287,
+                "high": 0
+              },
+              "end": {
+                "low": 273,
+                "high": 0
+              },
+              "type": "DERIVED_FROM",
+              "properties": {},
+              "elementId": "5:f0477778-7189-4347-80d0-f9c694a877a8:43",
+              "startNodeElementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287",
+              "endNodeElementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:273"
+            },
+            "end": {
+              "identity": {
+                "low": 273,
+                "high": 0
+              },
+              "labels": [
+                "DataResource"
+              ],
+              "properties": {
+                "leader": "mxl",
+                "storage_location": "/",
+                "type": "structure",
+                "url": "/对公客户中收_资源_20250624154149.xlsx",
+                "frequency": "月",
+                "organization": "citu",
+                "name": "对公客户中收_资源",
+                "data_sensitivity": "中",
+                "en_name": "corporate_customer_fee_income_resource",
+                "tag": {
+                  "low": 114,
+                  "high": 0
+                },
+                "describe": "对公客户中收的原始数据",
+                "time": "2025-06-24 15:41:49",
+                "category": "应用类",
+                "status": true
+              },
+              "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:273"
+            }
+          }
+        ],
+        "length": 1
+      }
+    ],
+    "_fieldLookup": {
+      "path": 0
+    }
+  },
+  {
+    "keys": [
+      "path"
+    ],
+    "length": 1,
+    "_fields": [
+      {
+        "start": {
+          "identity": {
+            "low": 287,
+            "high": 0
+          },
+          "labels": [
+            "DataModel"
+          ],
+          "properties": {
+            "leader": "mxl",
+            "level": {
+              "low": 0,
+              "high": 0
+            },
+            "origin": "resource",
+            "blood_resource": {
+              "low": 234,
+              "high": 0
+            },
+            "frequency": "月",
+            "childrenId": [],
+            "organization": "citu",
+            "name": "客户维业绩流水账",
+            "data_sensitivity": "中",
+            "en_name": "customer_service_ledger",
+            "describe": "将多个原始数据资源的数据统一加工到这个客户维流水账里",
+            "tag": {
+              "low": 114,
+              "high": 0
+            },
+            "time": "2025-06-26 17:17:58",
+            "category": "应用类",
+            "status": true
+          },
+          "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287"
+        },
+        "end": {
+          "identity": {
+            "low": 234,
+            "high": 0
+          },
+          "labels": [
+            "DataResource"
+          ],
+          "properties": {
+            "leader": "mxl",
+            "storage_location": "/",
+            "type": "structure",
+            "url": "/个人贷款_资源_20250624114046.xlsx",
+            "frequency": "月",
+            "organization": "citu",
+            "name": "个人贷款_资源",
+            "data_sensitivity": "中",
+            "en_name": "personal_loan_resource",
+            "describe": "个人贷款原始记录",
+            "tag": {
+              "low": 114,
+              "high": 0
+            },
+            "time": "2025-06-24 11:40:46",
+            "category": "应用类",
+            "status": true
+          },
+          "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:234"
+        },
+        "segments": [
+          {
+            "start": {
+              "identity": {
+                "low": 287,
+                "high": 0
+              },
+              "labels": [
+                "DataModel"
+              ],
+              "properties": {
+                "leader": "mxl",
+                "level": {
+                  "low": 0,
+                  "high": 0
+                },
+                "origin": "resource",
+                "blood_resource": {
+                  "low": 234,
+                  "high": 0
+                },
+                "frequency": "月",
+                "childrenId": [],
+                "organization": "citu",
+                "name": "客户维业绩流水账",
+                "data_sensitivity": "中",
+                "en_name": "customer_service_ledger",
+                "describe": "将多个原始数据资源的数据统一加工到这个客户维流水账里",
+                "tag": {
+                  "low": 114,
+                  "high": 0
+                },
+                "time": "2025-06-26 17:17:58",
+                "category": "应用类",
+                "status": true
+              },
+              "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287"
+            },
+            "relationship": {
+              "identity": {
+                "low": 41,
+                "high": 0
+              },
+              "start": {
+                "low": 287,
+                "high": 0
+              },
+              "end": {
+                "low": 234,
+                "high": 0
+              },
+              "type": "DERIVED_FROM",
+              "properties": {},
+              "elementId": "5:f0477778-7189-4347-80d0-f9c694a877a8:41",
+              "startNodeElementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:287",
+              "endNodeElementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:234"
+            },
+            "end": {
+              "identity": {
+                "low": 234,
+                "high": 0
+              },
+              "labels": [
+                "DataResource"
+              ],
+              "properties": {
+                "leader": "mxl",
+                "storage_location": "/",
+                "type": "structure",
+                "url": "/个人贷款_资源_20250624114046.xlsx",
+                "frequency": "月",
+                "organization": "citu",
+                "name": "个人贷款_资源",
+                "data_sensitivity": "中",
+                "en_name": "personal_loan_resource",
+                "describe": "个人贷款原始记录",
+                "tag": {
+                  "low": 114,
+                  "high": 0
+                },
+                "time": "2025-06-24 11:40:46",
+                "category": "应用类",
+                "status": true
+              },
+              "elementId": "4:f0477778-7189-4347-80d0-f9c694a877a8:234"
+            }
+          }
+        ],
+        "length": 1
+      }
+    ],
+    "_fieldLookup": {
+      "path": 0
+    }
+  }
+]