Jelajahi Sumber

The first initialization of the project

wangxq 2 bulan lalu
melakukan
ff40167dac
73 mengubah file dengan 9217 tambahan dan 0 penghapusan
  1. 59 0
      .gitignore
  2. 66 0
      app/__init__.py
  3. 88 0
      app/api/data_interface/README.md
  4. 5 0
      app/api/data_interface/__init__.py
  5. 279 0
      app/api/data_interface/routes.py
  6. 111 0
      app/api/data_metric/README.md
  7. 5 0
      app/api/data_metric/__init__.py
  8. 280 0
      app/api/data_metric/routes.py
  9. 81 0
      app/api/data_model/README.md
  10. 6 0
      app/api/data_model/__init__.py
  11. 283 0
      app/api/data_model/routes.py
  12. 42 0
      app/api/data_resource/README.md
  13. 5 0
      app/api/data_resource/__init__.py
  14. 484 0
      app/api/data_resource/routes.py
  15. 182 0
      app/api/graph/README.md
  16. 5 0
      app/api/graph/__init__.py
  17. 159 0
      app/api/graph/routes.py
  18. 5 0
      app/api/meta_data/__init__.py
  19. 560 0
      app/api/meta_data/routes.py
  20. 92 0
      app/api/production_line/README.md
  21. 11 0
      app/api/production_line/__init__.py
  22. 93 0
      app/api/production_line/routes.py
  23. 259 0
      app/api/system/README.md
  24. 5 0
      app/api/system/__init__.py
  25. 186 0
      app/api/system/routes.py
  26. 1 0
      app/config/__init__.py
  27. 91 0
      app/config/config.py
  28. 8 0
      app/core/__init__.py
  29. 16 0
      app/core/common/__init__.py
  30. 84 0
      app/core/common/functions.py
  31. 97 0
      app/core/data_interface/README.md
  32. 2 0
      app/core/data_interface/__init__.py
  33. 423 0
      app/core/data_interface/interface.py
  34. 116 0
      app/core/data_metric/README.md
  35. 2 0
      app/core/data_metric/__init__.py
  36. 591 0
      app/core/data_metric/metric_interface.py
  37. 99 0
      app/core/data_model/README.md
  38. 2 0
      app/core/data_model/__init__.py
  39. 787 0
      app/core/data_model/model.py
  40. 2 0
      app/core/data_resource/__init__.py
  41. 838 0
      app/core/data_resource/resource.py
  42. 60 0
      app/core/graph/README.md
  43. 20 0
      app/core/graph/__init__.py
  44. 265 0
      app/core/graph/graph_operations.py
  45. 55 0
      app/core/llm/README.md
  46. 12 0
      app/core/llm/__init__.py
  47. 39 0
      app/core/llm/code_generation.py
  48. 42 0
      app/core/llm/llm_service.py
  49. 78 0
      app/core/meta_data/README.md
  50. 26 0
      app/core/meta_data/__init__.py
  51. 577 0
      app/core/meta_data/meta_data.py
  52. 57 0
      app/core/production_line/README.md
  53. 10 0
      app/core/production_line/__init__.py
  54. 98 0
      app/core/production_line/production_line.py
  55. 86 0
      app/core/system/README.md
  56. 34 0
      app/core/system/__init__.py
  57. 284 0
      app/core/system/auth.py
  58. 94 0
      app/core/system/config.py
  59. 140 0
      app/core/system/health.py
  60. 102 0
      app/environment.yaml
  61. 1 0
      app/models/__init__.py
  62. 35 0
      app/models/result.py
  63. 82 0
      app/scripts/README.md
  64. 29 0
      app/scripts/create_user_table.sql
  65. 46 0
      app/scripts/init_db.py
  66. 115 0
      app/scripts/migrate_users.py
  67. 1 0
      app/services/__init__.py
  68. 17 0
      app/services/db_healthcheck.py
  69. 22 0
      app/services/neo4j_driver.py
  70. 251 0
      app/services/package_function.py
  71. 13 0
      application.py
  72. TEMPAT SAMPAH
      pythonweb开发说明.doc
  73. 16 0
      requirements.txt

+ 59 - 0
.gitignore

@@ -0,0 +1,59 @@
+# Python
+__pycache__/
+*.py[cod]
+*$py.class
+*.so
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# Virtual Environment
+.env
+.venv
+env/
+venv/
+ENV/
+
+# IDE
+.idea/
+.vscode/
+*.swp
+*.swo
+.DS_Store
+
+# Logs
+*.log
+logs/
+log/
+
+# Local development settings
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Database
+*.db
+*.sqlite3
+
+# Coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+coverage.xml
+*.cover 

+ 66 - 0
app/__init__.py

@@ -0,0 +1,66 @@
+from flask import Flask
+from flask_cors import CORS
+import logging
+from app.config.config import Config
+
+def create_app(config_class=Config):
+    """Create and configure the Flask application"""
+    app = Flask(__name__)
+    app.config.from_object(config_class)
+    
+    # Enable CORS
+    CORS(
+        app,
+        resources={
+            r"/api/*": {  # 使用更简洁的API路径
+                "origins": "*",
+                "methods": ["GET", "POST", "PUT", "DELETE"],
+                "allow_headers": ["Content-Type"]
+            }
+        },
+        supports_credentials=True
+    )
+    
+    # Configure logging
+    configure_logging(app)
+    
+    # 输出启动信息
+    app.logger.info(f"Starting server on port {config_class.PORT}")
+    
+    # 导入并注册API蓝图
+    from app.api.meta_data import bp as meta_data_bp
+    from app.api.data_resource import bp as data_resource_bp
+    from app.api.data_model import bp as data_model_bp
+    from app.api.data_interface import bp as data_interface_bp
+    from app.api.data_metric import bp as data_metric_bp
+    from app.api.production_line import bp as production_line_bp
+    from app.api.graph import bp as graph_bp
+    from app.api.system import bp as system_bp
+    
+    # 使用更简洁的API前缀
+    app.register_blueprint(meta_data_bp, url_prefix='/api/meta')
+    app.register_blueprint(data_resource_bp, url_prefix='/api/resource')
+    app.register_blueprint(data_model_bp, url_prefix='/api/model')
+    app.register_blueprint(data_interface_bp, url_prefix='/api/interface')
+    app.register_blueprint(data_metric_bp, url_prefix='/api/metric')
+    app.register_blueprint(production_line_bp, url_prefix='/api/pipeline')
+    app.register_blueprint(graph_bp, url_prefix='/api/graph')
+    app.register_blueprint(system_bp, url_prefix='/api/system')
+    
+    return app
+
+def configure_logging(app):
+    """Configure logging for the application"""
+    logger = logging.getLogger("app")
+    handler = logging.FileHandler('flask.log', encoding='UTF-8')
+    handler.setLevel(logging.INFO)
+    logging_format = logging.Formatter(
+        '%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s')
+    handler.setFormatter(logging_format)
+    console = logging.StreamHandler()
+    console.setLevel(logging.INFO)
+    console.setFormatter(logging_format)
+    logger.addHandler(handler)
+    logger.addHandler(console)
+    
+    return logger

+ 88 - 0
app/api/data_interface/README.md

@@ -0,0 +1,88 @@
+# 数据接口 API 模块
+
+本模块提供了数据标准和数据标签相关的所有API接口,包括创建、查询、更新、删除以及图谱生成功能。
+
+## 主要功能
+
+1. **数据标准操作**
+   - 创建数据标准:支持添加数据标准及相关描述
+   - 查询数据标准:支持分页查询、多条件筛选
+   - 更新数据标准:修改数据标准的基本信息
+   - 生成标准代码:根据标准描述和参数自动生成代码
+
+2. **数据标签操作**
+   - 创建数据标签:支持添加数据标签及相关描述
+   - 查询数据标签:支持分页查询、多条件筛选
+   - 动态识别标签分组:根据内容智能匹配相似的标签分组
+
+3. **图谱生成**
+   - 数据标准图谱:血缘关系、影响关系、全量关系
+   - 数据标签图谱:血缘关系、影响关系
+
+4. **关系管理**
+   - 建立标准和标签之间的关系
+   - 删除标签、标准、指标间的关系
+
+## API接口列表
+
+### 数据标准接口
+
+- `/data/standard/add`:创建数据标准
+- `/data/standard/detail`:获取数据标准详情
+- `/data/standard/code`:生成数据标准代码
+- `/data/standard/update`:更新数据标准
+- `/data/standard/list`:查询数据标准列表
+- `/data/standard/graph/all`:生成数据标准图谱
+
+### 数据标签接口
+
+- `/data/label/add`:创建数据标签
+- `/data/label/detail`:获取数据标签详情
+- `/data/label/list`:查询数据标签列表
+- `/data/label/dynamic/identify`:动态识别标签分组
+- `/data/label/graph/all`:生成数据标签图谱
+
+### 关系管理接口
+
+- `/metric/label/standard/delete`:删除节点间关系
+
+## 使用示例
+
+### 创建数据标准
+```json
+POST /data/standard/add
+{
+  "name": "用户ID格式标准",
+  "category": "数据格式",
+  "describe": "用户ID必须为16位数字,前8位为日期,后8位为流水号",
+  "tag": ["用户", "ID", "格式"]
+}
+```
+
+### 查询数据标签列表
+```json
+POST /data/label/list
+{
+  "current": 1,
+  "size": 10,
+  "name": "用户",
+  "category": "业务标签",
+  "group": "客户"
+}
+```
+
+### 生成数据标准图谱
+```json
+POST /data/standard/graph/all
+{
+  "id": 123,
+  "type": "all"
+}
+```
+
+## 依赖关系
+
+- 依赖核心业务逻辑模块 `app.core.data_interface`,提供数据标准和标签的业务处理功能
+- 依赖图数据库服务 `neo4j_driver` 进行数据存储和查询
+- 依赖元数据处理模块 `app.core.meta_data` 进行名称翻译和时间格式化
+- 依赖LLM服务 `app.core.llm` 进行代码生成 

+ 5 - 0
app/api/data_interface/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+bp = Blueprint('data_interface', __name__)
+
+from app.api.data_interface import routes 

+ 279 - 0
app/api/data_interface/routes.py

@@ -0,0 +1,279 @@
+from flask import request
+from app import app
+from app.models.result import success, failed
+from app.core.graph.graph_operations import connect_graph, MyEncoder, create_or_get_node
+from app.core.data_interface import interface
+from app.core.meta_data import translate_and_parse, get_formatted_time
+from app.core.llm import code_generate_standard
+import json
+
+
+# 数据标准新增 data_standard
+@app.route('/data/standard/add', methods=['POST'])
+def data_standard_add():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        name = receiver['name']  # 名称
+        en_name = translate_and_parse(name)  # 英文名
+        receiver['en_name'] = en_name[0]
+        receiver['time'] = get_formatted_time()
+        receiver['tag'] = json.dumps(receiver['tag'], ensure_ascii=False)
+
+        create_or_get_node('data_standard', **receiver)
+
+        res = success('', "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标准详情 data_standard
+@app.route('/data/standard/detail', methods=['POST'])
+def data_standard_detail():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        nodeid = receiver['id']  # id
+
+        cql = """MATCH (n:data_standard) where id(n) = $nodeId 
+                  RETURN properties(n) as property"""
+        # Create a session from the driver returned by connect_graph
+        with connect_graph().session() as session:
+            property = session.run(cql, nodeId=nodeid).evaluate()
+            if "tag" not in property:
+                property["tag"] = None
+            else:
+                property["tag"] = json.loads(property["tag"])
+            if "describe" not in property:
+                property["describe"] = None
+            res = success(property, "success")
+            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标准代码 data_standard
+@app.route('/data/standard/code', methods=['POST'])
+def data_standard_code():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        input = receiver['input']
+        describe = receiver['describe']
+        output = receiver['output']
+        relation = {
+            "输入参数": input,
+            "输出参数": output
+        }
+        result = code_generate_standard(describe, relation)
+
+        res = success(result, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标准更新 data_standard 未加到接口文档
+@app.route('/data/standard/update', methods=['POST'])
+def data_standard_update():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        name = receiver['name']  # 名称
+        en_name = translate_and_parse(name)  # 英文名
+        receiver['en_name'] = en_name[0]
+        receiver['time'] = get_formatted_time()
+
+        create_or_get_node('data_standard', **receiver)
+
+        res = success('', "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标准列表展示
+@app.route('/data/standard/list', methods=['POST'])
+def data_standard_list():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        page = int(receiver.get('current', 1))
+        page_size = int(receiver.get('size', 10))
+        en_name_filter = receiver.get('en_name', None)
+        name_filter = receiver.get('name', None)
+        category = receiver.get('category', None)
+        time = receiver.get('time', None)
+
+        # 计算跳过的记录的数量
+        skip_count = (page - 1) * page_size
+
+        data, total = interface.standard_list(skip_count, page_size, en_name_filter,
+                                              name_filter, category, time)
+
+        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标准的图谱(血缘关系Kinship+影响关系Impact+所有关系all)
+@app.route('/data/standard/graph/all', methods=['POST'])
+def data_standard_graph_all():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        nodeid = receiver['id']
+        type = receiver['type']  # kinship/impact/all
+        if type == 'kinship':
+            result = interface.standard_kinship_graph(nodeid)
+        elif type == 'impact':
+            result = interface.standard_impact_graph(nodeid)
+        else:
+            result = interface.standard_all_graph(nodeid)
+        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标签新增 data_label
+@app.route('/data/label/add', methods=['POST'])
+def data_label_add():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        name = receiver['name']  # 名称
+        en_name = translate_and_parse(name)  # 英文名
+        receiver['en_name'] = en_name[0]
+        receiver['time'] = get_formatted_time()
+        create_or_get_node('data_label', **receiver)
+
+        res = success('', "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标签详情 data_label
+@app.route('/data/label/detail', methods=['POST'])
+def data_label_detail():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        nodeid = receiver['id']  # id
+
+        cql = """MATCH (n:data_label) where id(n) = $nodeId 
+                  RETURN properties(n) as property"""
+        with connect_graph().session() as session:
+            property = session.run(cql, nodeId=nodeid).evaluate()
+            if "describe" not in property:
+                property["describe"] = None
+            res = success(property, "success")
+            return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标签列表展示(分类,名称,时间检索)
+@app.route('/data/label/list', methods=['POST'])
+def data_label_list():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        page = int(receiver.get('current', 1))
+        page_size = int(receiver.get('size', 10))
+        en_name_filter = receiver.get('en_name', None)
+        name_filter = receiver.get('name', None)
+        category = receiver.get('category', None)
+        group = receiver.get('group', None)
+
+        # 计算跳过的记录的数量
+        skip_count = (page - 1) * page_size
+
+        data, total = interface.label_list(skip_count, page_size, en_name_filter,
+                                           name_filter, category, group)
+
+        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 24.11.19 数据标签动态识别分组
+@app.route('/data/label/dynamic/identify', methods=['POST'])
+def data_label_dynamic_identify():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        name_filter = receiver.get('content', None)
+
+        data = interface.dynamic_label_list(name_filter)
+
+        res = success(data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据标签的图谱(血缘关系Kinship+影响关系Impact+所有关系all)
+@app.route('/data/label/graph/all', methods=['POST'])
+def data_label_graph():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        nodeid = receiver['id']
+        type = receiver['type']  # kinship/impact/all
+        if type == 'kinship':
+            result = interface.label_kinship_graph(nodeid)
+        elif type == 'impact':
+            result = interface.label_impact_graph(nodeid)
+        else:
+            result = interface.label_kinship_graph(nodeid)  # 对于标签,将all和kinship都视为相同处理
+        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 删除标签、标准、指标间的关系
+@app.route('/metric/label/standard/delete', methods=['POST'])
+def metric_label_standard_delete():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        sourceid = receiver['sourceid']
+        targetid = receiver['targetid']
+
+        # 查询语句,查询两个节点之间的关系
+        cql = """
+        MATCH (source)-[r]-(target)
+        WHERE id(source) = $sourceid AND id(target) = $targetid
+        DELETE r
+        """
+        with connect_graph().session() as session:
+            result = session.run(cql, sourceid=sourceid, targetid=targetid)
+
+        res = success("", "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder) 

+ 111 - 0
app/api/data_metric/README.md

@@ -0,0 +1,111 @@
+# API数据指标模块
+
+## 简介
+
+API数据指标模块(`app.api.data_metric`)提供了与数据指标相关的所有API接口,包括数据指标的新增、修改、查询、列表展示、图谱生成等功能。该模块作为前端与核心业务逻辑之间的桥梁,负责接收HTTP请求,调用相应的核心业务逻辑函数,并将结果返回给前端。
+
+## 主要功能
+
+1. **数据指标管理**:提供新增、更新、查询数据指标的接口
+2. **数据指标列表**:支持分页、筛选的数据指标列表展示
+3. **数据指标图谱**:生成不同类型的数据指标关系图谱,包括血缘关系、影响关系等
+4. **代码生成**:基于指标规则描述和映射关系生成指标计算代码
+5. **关系管理**:处理数据指标与其他实体(如模型、元数据、标签)之间的关系
+
+## API接口列表
+
+| 接口路径 | 方法 | 描述 |
+|---------|------|------|
+| `/data/metric/add` | POST | 新增数据指标 |
+| `/data/metric/update` | POST | 更新数据指标 |
+| `/data/metric/detail` | POST | 获取数据指标详情 |
+| `/data/metric/list` | POST | 获取数据指标列表 |
+| `/data/metric/code` | POST | 生成指标计算代码 |
+| `/data/metric/relation` | POST | 处理数据指标血缘关系 |
+| `/data/metric/graph/all` | POST | 获取数据指标图谱 |
+| `/data/metric/list/graph` | POST | 获取数据指标列表图谱 |
+
+## 使用示例
+
+### 1. 新增数据指标
+
+```json
+POST /data/metric/add
+Content-Type: application/json
+
+{
+  "name": "月活用户数",
+  "category": "用户指标",
+  "describe": "每月登录系统的独立用户数量",
+  "childrenId": [],
+  "tag": 123,
+  "id_list": [
+    {
+      "id": 456,
+      "type": "model",
+      "metaData": [789, 790]
+    }
+  ]
+}
+```
+
+### 2. 查询数据指标列表
+
+```json
+POST /data/metric/list
+Content-Type: application/json
+
+{
+  "current": 1,
+  "size": 10,
+  "name": "用户",
+  "category": "用户指标"
+}
+```
+
+### 3. 生成指标图谱
+
+```json
+POST /data/metric/graph/all
+Content-Type: application/json
+
+{
+  "id": 123,
+  "type": "all",
+  "meta": true
+}
+```
+
+## 依赖关系
+
+本模块主要依赖以下组件:
+
+1. **core.data_metric**:核心业务逻辑模块,包含数据指标相关的所有核心功能
+2. **routes.graph_routes**:图数据库连接和相关工具
+3. **DataOps_function.llm_solve**:代码生成相关功能
+4. **DataOps_function.meta_data_function**:元数据处理功能
+5. **model.result**:统一的响应结果模型
+
+## 错误处理
+
+所有API接口都包含统一的错误处理机制。当发生异常时,将返回包含错误信息的JSON响应:
+
+```json
+{
+  "code": -1,
+  "message": "操作失败",
+  "data": {},
+  "error": {
+    "error": "发生的具体错误信息"
+  }
+}
+```
+
+## 代码结构
+
+- **__init__.py**:定义Blueprint和路由导入
+- **routes.py**:包含所有API路由处理函数
+
+## 维护者
+
+数据平台开发团队 

+ 5 - 0
app/api/data_metric/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+bp = Blueprint('data_metric', __name__)
+
+from app.api.data_metric import routes 

+ 280 - 0
app/api/data_metric/routes.py

@@ -0,0 +1,280 @@
+"""
+数据指标API接口模块
+
+该模块提供了数据指标的各种API接口,包括:
+- 指标新增、更新和查询
+- 指标列表展示
+- 指标图谱生成
+- 指标代码生成
+- 指标关系管理
+"""
+
+from flask import request, jsonify
+import json
+from app.api.data_metric import bp
+from app.core.data_metric.metric_interface import (
+    metric_list, handle_metric_relation, handle_data_metric, 
+    handle_meta_data_metric, handle_id_metric, metric_kinship_graph, 
+    metric_impact_graph, metric_all_graph, data_metric_edit
+)
+from app.core.llm import code_generate_metric
+from app.core.meta_data import translate_and_parse
+from app.models.result import success, failed
+from app.core.graph.graph_operations import MyEncoder, connect_graph
+
+
+@bp.route('/data/metric/relation', methods=['POST'])
+def data_metric_relation():
+    """
+    处理数据指标血缘关系
+    
+    请求参数:
+    - id: 数据模型ID
+    
+    返回:
+    - 处理结果,包含血缘关系数据
+    """
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        model_ids = receiver['id']  # 给定一个数据模型的id
+        response_data = handle_metric_relation(model_ids)
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+@bp.route('/data/metric/add', methods=['POST'])
+def data_metric_add():
+    """
+    新增数据指标
+    
+    请求参数:
+    - name: 指标名称
+    - 其他指标相关属性
+    
+    返回:
+    - 处理结果,成功或失败
+    """
+    # 传入请求参数
+    receiver = request.get_json()
+    metric_name = receiver['name']  # 数据指标的name
+    try:
+        result_list = translate_and_parse(metric_name)
+        id, id_list = handle_data_metric(metric_name, result_list, receiver)
+        handle_meta_data_metric(id, id_list)
+
+        res = success({}, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+@bp.route('/data/metric/code', methods=['POST'])
+def data_metric_code():
+    """
+    生成指标计算的代码
+    
+    请求参数:
+    - content: 指标规则描述
+    - relation: 映射关系
+    
+    返回:
+    - 生成的代码
+    """
+    # 传入请求参数
+    receiver = request.get_json()
+    content = receiver['content']  # 指标规则描述
+    relation = receiver['relation']  # 映射关系
+
+    try:
+        id_list = [item["id"] for item in relation]
+        cql = """
+        MATCH (n:meta_node) WHERE id(n) IN $Id_list
+        WITH n.en_name AS en_name, n.name AS name
+        WITH collect({en_name: en_name, name: name}) AS res
+        WITH reduce(acc = {}, item IN res | apoc.map.setKey(acc, item.en_name, item.name)) AS result
+        RETURN result
+        """
+        id_relation = connect_graph.run(cql, Id_list=id_list).evaluate()
+        result = code_generate_metric(content, id_relation)
+        res = success(result, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+@bp.route('/data/metric/detail', methods=['POST'])
+def data_metric_detail():
+    """
+    获取数据指标详情
+    
+    请求参数:
+    - id: 指标ID
+    
+    返回:
+    - 指标详情数据
+    """
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        id = int(receiver.get('id'))
+        response_data = handle_id_metric(id)
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+@bp.route('/data/metric/list', methods=['POST'])
+def data_metric_list():
+    """
+    获取数据指标列表
+    
+    请求参数:
+    - current: 当前页码,默认1
+    - size: 每页大小,默认10
+    - en_name: 英文名称过滤
+    - name: 名称过滤
+    - category: 类别过滤
+    - time: 时间过滤
+    - tag: 标签过滤
+    
+    返回:
+    - 指标列表数据和分页信息
+    """
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        page = int(receiver.get('current', 1))
+        page_size = int(receiver.get('size', 10))
+        en_name_filter = receiver.get('en_name', None)
+        name_filter = receiver.get('name', None)
+        category = receiver.get('category', None)
+        time = receiver.get('time', None)
+        tag = receiver.get('tag', None)
+
+        # 计算跳过的记录的数量
+        skip_count = (page - 1) * page_size
+
+        data, total = metric_list(skip_count, page_size, en_name_filter,
+                                  name_filter, category, time, tag)
+
+        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+@bp.route('/data/metric/graph/all', methods=['POST'])
+def data_metric_graph_all():
+    """
+    获取数据指标图谱
+    
+    请求参数:
+    - id: 指标ID
+    - type: 图谱类型,可选kinship/impact/all
+    - meta: 是否返回元数据,true/false
+    
+    返回:
+    - 图谱数据
+    """
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        nodeid = receiver['id']
+        type = receiver['type']  # kinship/impact/all
+        meta = receiver['meta']  # true/false 是否返回元数据
+
+        if type == 'kinship':
+            result = metric_kinship_graph(nodeid, meta)
+        elif type == 'impact':
+            result = metric_impact_graph(nodeid, meta)
+        else:
+            result = metric_all_graph(nodeid, meta)
+        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
+
+
+@bp.route('/data/metric/list/graph', methods=['POST'])
+def data_metric_list_graph():
+    """
+    获取数据指标列表图谱
+    
+    请求参数:
+    - tag: 标签ID
+    
+    返回:
+    - 图谱数据,包含节点和连线
+    """
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+
+        if not receiver or 'tag' not in receiver:
+            raise ValueError("Missing 'tag' parameter in request body")
+
+        nodeid = receiver['tag']
+
+        # 构建查询条件
+        params = {}
+        where_clause = ""
+
+        if nodeid is not None:
+            where_clause = "MATCH (n)-[:label]->(la) WHERE id(la) = $nodeId"
+            params['nodeId'] = nodeid
+        query = f"""
+        MATCH (n:data_metric)
+        OPTIONAL MATCH (n)-[:child]->(child)
+        {where_clause}
+        WITH 
+            collect(DISTINCT {{id: toString(id(n)), text: n.name, type: split(labels(n)[0], '_')[1]}}) AS nodes,
+            collect(DISTINCT {{id: toString(id(child)), text: child.name, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
+            collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
+        RETURN nodes  + nodes2 AS nodes, lines  AS lines
+        """
+        data = connect_graph.run(query, **params)
+        res = {}
+        for item in data:
+            res = {
+                "nodes": [record for record in item['nodes'] if record['id']],
+                "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            }
+        return json.dumps(success(res, "success"), ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
+
+
+@bp.route('/data/metric/update', methods=['POST'])
+def data_metric_update():
+    """
+    更新数据指标
+    
+    请求参数:
+    - id: 指标ID
+    - 其他需要更新的属性
+    
+    返回:
+    - 处理结果,成功或失败
+    """
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        data_metric_edit(receiver)
+
+        res = success({}, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder) 

+ 81 - 0
app/api/data_model/README.md

@@ -0,0 +1,81 @@
+# 数据模型 API 接口模块
+
+本模块提供了数据模型相关的所有API接口,包括数据模型的创建、查询、更新、删除以及各类数据模型图谱生成功能。
+
+## 主要功能
+
+1. **数据模型基础操作**
+   - 创建数据模型:支持从数据资源、已有数据模型或DDL中创建数据模型
+   - 查询数据模型:支持分页查询、多条件筛选
+   - 更新数据模型:修改数据模型的基本信息和标签关系
+   - 删除数据模型
+
+2. **数据模型血缘关系**
+   - 查询数据模型血缘关系
+   - 获取数据资源间的血缘关系
+   - 查询相关数据资源列表
+
+3. **数据模型图谱生成**
+   - 血缘关系图谱:展示数据模型与数据资源的血缘关系
+   - 影响关系图谱:展示数据模型之间的使用关系
+   - 全量关系图谱:展示数据模型的所有关系
+
+## API接口列表
+
+- `/model/data/relation`:数据模型血缘关系查询
+- `/model/relatives/relation`:查询与指定数据资源有血缘关系的资源列表
+- `/data/model/save`:保存从DDL中选取的数据模型
+- `/model/data/search`:从数据资源中创建数据模型
+- `/model/data/model/add`:从其他数据模型中创建数据模型
+- `/data/model/detail`:获取数据模型详情
+- `/data/model/delete`:删除数据模型
+- `/data/model/list`:查询数据模型列表
+- `/data/model/graph/all`:生成数据模型图谱
+- `/data/model/list/graph`:生成数据模型列表图谱
+- `/data/model/update`:更新数据模型信息
+
+## 使用示例
+
+### 创建数据模型
+```json
+POST /model/data/search
+{
+  "name": "客户信息模型",
+  "en_name": "customer_info_model",
+  "category": "业务模型",
+  "description": "包含客户基本信息的数据模型",
+  "tag": 123,
+  "childrenId": [],
+  "id_list": [
+    {
+      "resource_id": 456,
+      "metaData": [
+        {
+          "id": 789,
+          "data_standard": "GB/T 22240-2008",
+          "data_name": "客户ID"
+        }
+      ]
+    }
+  ]
+}
+```
+
+### 查询数据模型列表
+```json
+POST /data/model/list
+{
+  "current": 1,
+  "size": 10,
+  "name": "客户",
+  "category": "业务模型",
+  "tag": 123,
+  "level": 1
+}
+```
+
+## 依赖关系
+
+- 依赖核心业务逻辑模块 `app.core.data_model`,提供数据模型的业务处理功能
+- 依赖图数据库服务 `neo4j_driver` 进行数据存储和查询
+- 依赖元数据处理模块 `app.core.meta_data` 进行模型元数据解析 

+ 6 - 0
app/api/data_model/__init__.py

@@ -0,0 +1,6 @@
+from flask import Blueprint
+
+bp = Blueprint('data_model', __name__)
+
+from app.api.data_model import routes
+from app.api.data_model.routes import * 

+ 283 - 0
app/api/data_model/routes.py

@@ -0,0 +1,283 @@
+from flask import request
+from app import app
+from app.models.result import success, failed
+from app.routes.graph_routes import MyEncoder
+from app.core.data_model import model as model_functions
+import json
+
+# 2024.09.11 数据模型血缘关系(传入数据资源id)
+@app.route('/model/data/relation', methods=['POST'])
+def data_model_relation():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        resource_ids = receiver['id']  # 给定一个数据资源的id
+        response_data = model_functions.handle_model_relation(resource_ids)
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 传入一个数据资源的id,返回多个有血缘关系的数据资源
+@app.route('/model/relatives/relation', methods=['POST'])
+def data_relatives_relation():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        page = int(receiver.get('current', 1))
+        page_size = int(receiver.get('size', 10))
+        id = receiver['id']
+
+        name_filter = receiver.get('name', None)
+        category = receiver.get('category', None)
+        time = receiver.get('time', None)
+
+        # 计算跳过的记录的数量
+        skip_count = (page - 1) * page_size
+
+        data, total = model_functions.model_resource_list(skip_count, page_size, name_filter, id, category, time)
+
+        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# DDL数据模型保存
+@app.route('/data/model/save', methods=['POST'])
+def data_model_save():
+    # 传入请求参数
+    receiver = request.get_json()
+    data_model = receiver['name']
+    id_list = receiver['id_list']
+    # resource_id和meta_id构成json格式
+    result = json.dumps(id_list, ensure_ascii=False)
+    try:
+        # 从DDL中选取保存数据模型
+        result_list = [receiver['en_name']]
+        id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
+        model_functions.handle_no_meta_data_model(id_list, receiver, data_model_node)
+        model_functions.calculate_model_level(id)
+
+        res = success({}, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 新建数据模型请求接口(从数据资源中选取)
+@app.route('/model/data/search', methods=['POST'])
+def data_model_search():
+    # 传入请求参数
+    receiver = request.get_json()
+    data_model = receiver['name']
+    id_list = receiver['id_list']
+    # resource_id和meta_id构成json格式
+    result = json.dumps(id_list, ensure_ascii=False)
+    try:
+        from app.core.meta_data import translate_and_parse
+        result_list = translate_and_parse(data_model)
+        id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
+        if id_list:
+            model_functions.resource_handle_meta_data_model(id_list, id)
+        model_functions.calculate_model_level(id)
+
+        res = success({}, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 新建数据模型请求接口(从数据模型中选取)
+@app.route('/model/data/model/add', methods=['POST'])
+def data_model_model_add():
+    # 传入请求参数
+    receiver = request.get_json()
+    data_model = receiver['name']
+    id_list = receiver['id_list']
+    # model_id和meta_id构成json格式
+    result = json.dumps(id_list, ensure_ascii=False)
+    try:
+        from app.core.meta_data import translate_and_parse
+        result_list = translate_and_parse(data_model)
+        id, data_model_node = model_functions.handle_data_model(data_model, result_list, result, receiver)
+        model_functions.model_handle_meta_data_model(id_list, id)
+        model_functions.calculate_model_level(id)
+
+        res = success({}, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据模型-详情接口
+@app.route('/data/model/detail', methods=['POST'])
+def data_model_detail():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        id = int(receiver.get('id'))
+
+        response_data = model_functions.handle_id_model(id)
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 删除数据模型
+@app.route('/data/model/delete', methods=['POST'])
+def data_model_delete():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        id = receiver['id']
+
+        # 首先删除数据模型节点
+        from app.services.neo4j_driver import neo4j_driver
+        query = """
+        MATCH (n:data_model) where id(n) = $nodeId
+        detach delete n
+        """
+        with neo4j_driver.get_session() as session:
+            session.run(query, nodeId=id)
+
+        res = success({}, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 列表 数据模型查询
+@app.route('/data/model/list', methods=['POST'])
+def data_model_list():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        page = int(receiver.get('current', 1))
+        page_size = int(receiver.get('size', 10))
+        name_filter = receiver.get('name', None)
+        en_name_filter = receiver.get('en_name', None)
+        category = receiver.get('category', None)
+        tag = receiver.get('tag', None)
+        level = receiver.get('level', None)
+
+        # 计算跳过的记录的数量
+        skip_count = (page - 1) * page_size
+
+        data, total = model_functions.model_list(skip_count, page_size, en_name_filter,
+                                 name_filter, category, tag, level)
+
+        response_data = {'records': data, 'total': total, 'size': page_size, 'current': page}
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据模型的图谱(血缘关系Kinship+影响关系Impact+所有关系all)
+@app.route('/data/model/graph/all', methods=['POST'])
+def data_model_graph_all():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        nodeid = receiver['id']
+        type = receiver['type'] # kinship/impact/all
+        meta = receiver['meta'] # true/false 是否返回元数据
+
+        if type == 'kinship':
+            result = model_functions.model_kinship_graph(nodeid, meta)
+        elif type == 'impact':
+            result = model_functions.model_impact_graph(nodeid, meta)
+        else:
+            result = model_functions.model_all_graph(nodeid, meta)
+        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
+
+
+# 数据模型的列表图谱
+@app.route('/data/model/list/graph', methods=['POST'])
+def data_model_list_graph():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+
+        if not receiver or 'tag' not in receiver:
+            raise ValueError("Missing 'tag' parameter in request body")
+
+        nodeid = receiver['tag']
+
+        # 构建查询条件
+        params = {}
+        where_clause = ""
+
+        if nodeid is not None:
+            where_clause = "MATCH (n)-[:label]->(la) WHERE id(la) = $nodeId"
+            params['nodeId'] = nodeid
+
+        from app.services.neo4j_driver import neo4j_driver
+        query = f"""
+        MATCH (n:data_model)
+        OPTIONAL MATCH (n)-[:child]->(child)
+        {where_clause}
+        WITH 
+            collect(DISTINCT {{id: toString(id(n)), text: n.name, type: split(labels(n)[0], '_')[1]}}) AS nodes,
+            collect(DISTINCT {{id: toString(id(child)), text: child.name, type: split(labels(child)[0], '_')[1]}}) AS nodes2,
+            collect(DISTINCT {{from: toString(id(n)), to: toString(id(child)), text: '下级'}}) AS lines
+        RETURN nodes + nodes2 AS nodes, lines AS lines
+        """
+
+        with neo4j_driver.get_session() as session:
+            result = session.run(query, **params)
+            data = result.data()
+            
+            if len(data) > 0:
+                # 过滤掉空节点(即 id 为 null 的节点)
+                nodes = []
+                for node in data[0]['nodes']:
+                    if node['id'] != 'null':
+                        nodes.append(node)
+                
+                lines = data[0]['lines']
+                
+                response_data = {
+                    'nodes': nodes,
+                    'edges': lines
+                }
+                
+                return json.dumps(success(response_data, "success"), ensure_ascii=False, cls=MyEncoder)
+            else:
+                return json.dumps(success({'nodes': [], 'edges': []}, "No data found"), ensure_ascii=False, cls=MyEncoder)
+    
+    except Exception as e:
+        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder)
+
+
+# 更新数据模型
+@app.route('/data/model/update', methods=['POST'])
+def data_model_update():
+    try:
+        # 传入请求参数
+        receiver = request.get_json()
+        
+        result = model_functions.data_model_edit(receiver)
+        
+        return json.dumps(success(result, "success"), ensure_ascii=False, cls=MyEncoder)
+    except Exception as e:
+        return json.dumps(failed({}, str(e)), ensure_ascii=False, cls=MyEncoder) 

+ 42 - 0
app/api/data_resource/README.md

@@ -0,0 +1,42 @@
+ # 数据资源API模块
+
+本模块是数据资源管理的API接口,提供了一系列与数据资源相关的操作,包括资源列表查询、详情查询、添加、更新、删除等功能。
+
+## 主要功能
+
+1. **基础操作**
+   - 获取数据资源列表 (`/list`)
+   - 获取数据资源详情 (`/detail`)
+   - 添加数据资源 (`/save`)
+   - 更新数据资源 (`/update`)
+   - 删除数据资源 (`/delete`)
+
+2. **数据查询与解析**
+   - DDL语句解析 (`/ddl`)
+   - SQL查询测试 (`/sql/test`)
+   - DDL语句识别 (`/ddl/identify`)
+
+3. **元数据关联**
+   - 关联元数据查询 (`/search`)
+   - 保存关联元数据 (`/save/metadata`)
+
+4. **图谱操作**
+   - 获取数据资源亲缘关系图谱 (`/graph`)
+   - 获取数据资源完整图谱 (`/graph/all`)
+
+5. **模型资源**
+   - 获取模型资源列表 (`/model/list`)
+
+6. **多语言支持**
+   - 数据资源名称翻译 (`/translate`)
+
+## 错误处理
+
+所有API接口都实现了统一的错误处理机制,当操作失败时会返回错误信息和相应的HTTP状态码。
+
+## 依赖项
+
+- Flask:Web框架
+- Neo4j:图数据库
+- MinIO:对象存储
+- 业务逻辑层 (`app.core.data_resource`)

+ 5 - 0
app/api/data_resource/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+bp = Blueprint('data_resource', __name__)
+
+from app.api.data_resource import routes 

+ 484 - 0
app/api/data_resource/routes.py

@@ -0,0 +1,484 @@
+from io import BytesIO, StringIO
+import os
+import pandas as pd
+from flask import request, jsonify, send_file
+from app.api.data_resource import bp
+from app.models.result import success, failed
+import logging
+import json
+import re
+from minio import Minio
+from app.config.config import Config
+from app.services.neo4j_driver import neo4j_driver
+from app.core.data_resource.resource import (
+    resource_list, 
+    handle_node, 
+    resource_kinship_graph, 
+    resource_impact_all_graph, 
+    model_resource_list, 
+    select_create_ddl, 
+    data_resource_edit, 
+    handle_id_resource,
+    id_data_search_list,
+    table_sql,
+    select_sql,
+    id_resource_graph
+)
+from app.core.meta_data import (
+    translate_and_parse,
+    infer_column_type,
+    text_resource_solve,
+    get_file_content,
+    get_formatted_time
+)
+
+logger = logging.getLogger("app")
+
+# 配置MinIO客户端
+minio_client = Minio(
+    Config.MINIO_HOST,
+    access_key=Config.MINIO_USER,
+    secret_key=Config.MINIO_PASSWORD,
+    secure=True
+)
+
+# 配置文件上传相关
+UPLOAD_FOLDER = Config.UPLOAD_FOLDER
+bucket_name = Config.BUCKET_NAME
+prefix = Config.PREFIX
+
+def is_english(text):
+    """检查文本是否为英文"""
+    return text.isascii() and bool(re.match(r'^[a-zA-Z0-9_\s.,;:!?()\'"-]+$', text))
+
+@bp.route('/translate', methods=['POST'])
+def data_resource_translate():
+    """数据资源翻译"""
+    try:
+        # 获取表单数据
+        name = request.json.get('name', '')
+        en_name = request.json.get('en_name', '')
+        data_type = request.json.get('data_type', 'table')
+        is_file = request.json.get('is_file', False)
+        
+        # 验证输入
+        if not name:
+            return jsonify(failed("名称不能为空"))
+            
+        # 如果已经提供了英文名,则直接使用
+        if en_name and is_english(en_name):
+            translated = True
+            return jsonify(success({"name": name, "en_name": en_name, "translated": translated}))
+        
+        # 否则进行翻译
+        try:
+            if data_type == 'table':
+                prompt = f"""将以下数据表名(中文)翻译成英文,不需要额外说明:
+                中文:{name}
+                英文(snake_case格式):
+                """
+                result = text_resource_solve(None, name, "")
+                translated = True
+                return jsonify(success({"name": name, "en_name": result["en_name"], "translated": translated}))
+            else:
+                result = text_resource_solve(None, name, "")
+                translated = True
+                return jsonify(success({"name": name, "en_name": result["en_name"], "translated": translated}))
+        except Exception as e:
+            logger.error(f"翻译失败: {str(e)}")
+            return jsonify(failed(f"翻译失败: {str(e)}"))
+    except Exception as e:
+        logger.error(f"处理数据资源翻译请求失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/save', methods=['POST'])
+def data_resource_save():
+    """保存数据资源"""
+    try:
+        # 获取表单数据
+        receiver = request.json.get('receiver', {})
+        head_data = request.json.get('head_data', [])
+        data_resource = request.json.get('data_resource', {})
+        
+        if not receiver or not data_resource:
+            return jsonify(failed("参数不完整"))
+        
+        # 调用业务逻辑处理数据资源创建
+        resource_id = handle_node(receiver, head_data, data_resource)
+        
+        return jsonify(success({"id": resource_id}))
+    except Exception as e:
+        logger.error(f"保存数据资源失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/delete', methods=['POST'])
+def data_resource_delete():
+    """删除数据资源"""
+    try:
+        # 获取资源ID
+        resource_id = request.json.get('id')
+        if not resource_id:
+            return jsonify(failed("资源ID不能为空"))
+        
+        with neo4j_driver.get_session() as session:
+            # 删除数据资源节点及其关系
+            cypher = """
+            MATCH (n:data_resource)
+            WHERE id(n) = $resource_id
+            DETACH DELETE n
+            """
+            
+            session.run(cypher, resource_id=int(resource_id))
+            
+            return jsonify(success({"message": "数据资源删除成功"}))
+    except Exception as e:
+        logger.error(f"删除数据资源失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/update', methods=['POST'])
+def data_resource_update():
+    """更新数据资源"""
+    try:
+        # 获取更新数据
+        data = request.json
+        
+        if not data or "id" not in data:
+            return jsonify(failed("参数不完整"))
+        
+        # 调用业务逻辑更新数据资源
+        updated_data = data_resource_edit(data)
+        
+        return jsonify(success(updated_data))
+    except Exception as e:
+        logger.error(f"更新数据资源失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/ddl', methods=['POST'])
+def id_data_ddl():
+    """解析数据资源的DDL"""
+    try:
+        # 获取SQL内容
+        sql_content = request.json.get('sql', '')
+        if not sql_content:
+            return jsonify(failed("SQL内容不能为空"))
+        
+        # 提取创建表的DDL语句
+        create_ddl_list = select_create_ddl(sql_content)
+        
+        if not create_ddl_list:
+            return jsonify(failed("未找到有效的CREATE TABLE语句"))
+        
+        # 解析每个表定义
+        tables = []
+        for ddl in create_ddl_list:
+            table_info = table_sql(ddl)
+            if table_info:
+                tables.append(table_info)
+        
+        if not tables:
+            return jsonify(failed("解析表结构失败"))
+        
+        # 转换结果为前端需要的格式
+        result = []
+        for table in tables:
+            table_result = {
+                "name": table["table_name"],
+                "en_name": table["table_name"],  # 初始使用原名
+                "fields": []
+            }
+            
+            # 处理每个字段
+            for field in table["fields"]:
+                field_info = {
+                    "name": field["name"],
+                    "en_name": field["name"],  # 初始使用原名
+                    "type": field["type"],
+                    "is_primary": field["is_primary"],
+                    "nullable": not field.get("not_null", False)
+                }
+                
+                if "default" in field:
+                    field_info["default"] = field["default"]
+                    
+                table_result["fields"].append(field_info)
+                
+            result.append(table_result)
+        
+        return jsonify(success(result))
+    except Exception as e:
+        logger.error(f"解析DDL失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/list', methods=['POST'])
+def data_resource_list():
+    """获取数据资源列表"""
+    try:
+        # 获取分页和筛选参数
+        page = int(request.json.get('current', 1))
+        page_size = int(request.json.get('size', 10))
+        en_name_filter = request.json.get('en_name')
+        name_filter = request.json.get('name')
+        type_filter = request.json.get('type', 'all')
+        category_filter = request.json.get('category')
+        tag_filter = request.json.get('tag')
+        
+        # 调用业务逻辑查询数据资源列表
+        resources, total_count = resource_list(
+            page, 
+            page_size, 
+            en_name_filter, 
+            name_filter, 
+            type_filter, 
+            category_filter, 
+            tag_filter
+        )
+        
+        # 返回结果
+        return jsonify(success({
+            "records": resources,
+            "total": total_count,
+            "size": page_size,
+            "current": page
+        }))
+    except Exception as e:
+        logger.error(f"获取数据资源列表失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/search', methods=['POST'])
+def id_data_search():
+    """搜索数据资源关联的元数据"""
+    try:
+        # 获取参数
+        resource_id = request.json.get('id')
+        if not resource_id:
+            return jsonify(failed("资源ID不能为空"))
+            
+        page = int(request.json.get('current', 1))
+        page_size = int(request.json.get('size', 10))
+        en_name_filter = request.json.get('en_name')
+        name_filter = request.json.get('name')
+        category_filter = request.json.get('category')
+        tag_filter = request.json.get('tag')
+        
+        # 调用业务逻辑查询关联元数据
+        metadata_list, total_count = id_data_search_list(
+            resource_id, 
+            page, 
+            page_size, 
+            en_name_filter, 
+            name_filter, 
+            category_filter, 
+            tag_filter
+        )
+        
+        # 返回结果
+        return jsonify(success({
+            "records": metadata_list,
+            "total": total_count,
+            "size": page_size,
+            "current": page
+        }))
+    except Exception as e:
+        logger.error(f"搜索数据资源关联的元数据失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+def dynamic_type_conversion(value, target_type):
+    """动态类型转换"""
+    if value is None:
+        return None
+        
+    if target_type == "int" or target_type == "INT":
+        return int(value)
+    elif target_type == "float" or target_type == "FLOAT" or target_type == "double" or target_type == "DOUBLE":
+        return float(value)
+    elif target_type == "bool" or target_type == "BOOL" or target_type == "boolean" or target_type == "BOOLEAN":
+        if isinstance(value, str):
+            return value.lower() in ('true', 'yes', '1', 't', 'y')
+        return bool(value)
+    else:
+        return str(value)
+
+@bp.route('/graph/all', methods=['POST'])
+def data_resource_graph_all():
+    """获取数据资源完整图谱"""
+    try:
+        # 获取参数
+        resource_id = request.json.get('id')
+        meta = request.json.get('meta', True)
+        
+        if not resource_id:
+            return jsonify(failed("资源ID不能为空"))
+            
+        # 调用业务逻辑获取图谱
+        graph_data = resource_impact_all_graph(resource_id, meta)
+        
+        return jsonify(success(graph_data))
+    except Exception as e:
+        logger.error(f"获取数据资源完整图谱失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/graph', methods=['POST'])
+def data_resource_list_graph():
+    """获取数据资源亲缘关系图谱"""
+    try:
+        # 获取参数
+        resource_id = request.json.get('id')
+        meta = request.json.get('meta', True)
+        
+        if not resource_id:
+            return jsonify(failed("资源ID不能为空"))
+            
+        # 调用业务逻辑获取图谱
+        graph_data = resource_kinship_graph(resource_id, meta)
+        
+        return jsonify(success(graph_data))
+    except Exception as e:
+        logger.error(f"获取数据资源亲缘关系图谱失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/save/metadata', methods=['POST'])
+def id_data_save():
+    """保存数据资源关联的元数据"""
+    try:
+        # 获取参数
+        resource_id = request.json.get('id')
+        metadata_list = request.json.get('data', [])
+        
+        if not resource_id:
+            return jsonify(failed("资源ID不能为空"))
+            
+        if not metadata_list:
+            return jsonify(failed("元数据列表不能为空"))
+            
+        # 处理元数据保存
+        with neo4j_driver.get_session() as session:
+            # 先删除现有关系
+            cypher_delete = """
+            MATCH (n:data_resource)-[r:contain]->()
+            WHERE id(n) = $resource_id
+            DELETE r
+            """
+            session.run(cypher_delete, resource_id=int(resource_id))
+            
+            # 添加新关系
+            for meta in metadata_list:
+                # 创建或获取元数据节点
+                meta_cypher = """
+                MERGE (m:Metadata {name: $name})
+                ON CREATE SET m.en_name = $en_name, m.createTime = $create_time
+                RETURN m
+                """
+                
+                meta_result = session.run(
+                    meta_cypher,
+                    name=meta["name"],
+                    en_name=meta["en_name"],
+                    create_time=meta.get("createTime", get_formatted_time())
+                )
+                meta_node = meta_result.single()["m"]
+                
+                # 创建关系
+                rel_cypher = """
+                MATCH (n:data_resource), (m:Metadata)
+                WHERE id(n) = $resource_id AND id(m) = $meta_id
+                CREATE (n)-[r:contain]->(m)
+                RETURN r
+                """
+                
+                session.run(
+                    rel_cypher,
+                    resource_id=int(resource_id),
+                    meta_id=meta_node.id
+                )
+                
+        return jsonify(success({"message": "元数据保存成功"}))
+    except Exception as e:
+        logger.error(f"保存数据资源关联的元数据失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/sql/test', methods=['POST'])
+def sql_test():
+    """测试SQL查询"""
+    try:
+        # 获取参数
+        sql_query = request.json.get('sql', '')
+        
+        if not sql_query:
+            return jsonify(failed("SQL查询不能为空"))
+            
+        # 解析SQL
+        parsed_sql = select_sql(sql_query)
+        
+        if not parsed_sql:
+            return jsonify(failed("解析SQL失败"))
+            
+        # 返回解析结果
+        return jsonify(success(parsed_sql))
+    except Exception as e:
+        logger.error(f"测试SQL查询失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/ddl/identify', methods=['POST'])
+def sql_ddl_identify():
+    """识别DDL语句"""
+    try:
+        # 获取参数
+        sql_content = request.json.get('sql', '')
+        
+        if not sql_content:
+            return jsonify(failed("SQL内容不能为空"))
+            
+        # 提取创建表的DDL语句
+        create_ddl_list = select_create_ddl(sql_content)
+        
+        if not create_ddl_list:
+            return jsonify(failed("未找到有效的CREATE TABLE语句"))
+            
+        return jsonify(success({"count": len(create_ddl_list)}))
+    except Exception as e:
+        logger.error(f"识别DDL语句失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/model/list', methods=['POST'])
+def resource_model_list():
+    """获取模型资源列表"""
+    try:
+        # 获取分页和筛选参数
+        page = int(request.json.get('current', 1))
+        page_size = int(request.json.get('size', 10))
+        name_filter = request.json.get('name')
+        
+        # 调用业务逻辑查询模型资源列表
+        resources, total_count = model_resource_list(page, page_size, name_filter)
+        
+        # 返回结果
+        return jsonify(success({
+            "records": resources,
+            "total": total_count,
+            "size": page_size,
+            "current": page
+        }))
+    except Exception as e:
+        logger.error(f"获取模型资源列表失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+@bp.route('/detail', methods=['POST'])
+def data_resource_detail():
+    """获取数据资源详情"""
+    try:
+        # 获取资源ID
+        resource_id = request.json.get('id')
+        
+        if not resource_id:
+            return jsonify(failed("资源ID不能为空"))
+            
+        # 调用业务逻辑查询数据资源详情
+        resource_data = handle_id_resource(resource_id)
+        
+        if not resource_data:
+            return jsonify(failed("资源不存在"))
+            
+        return jsonify(success(resource_data))
+    except Exception as e:
+        logger.error(f"获取数据资源详情失败: {str(e)}")
+        return jsonify(failed(str(e))) 

+ 182 - 0
app/api/graph/README.md

@@ -0,0 +1,182 @@
+# 图数据库API接口模块
+
+本模块提供了与Neo4j图数据库交互的HTTP API接口,用于前端或其他服务调用。
+
+## 功能概述
+
+图数据库API模块为前端应用提供了一组REST接口,用于执行各种图数据库操作,如执行查询、创建节点和关系、获取子图数据等。这些接口统一采用JSON格式进行数据交换,并提供标准化的错误处理和响应格式。
+
+## API接口
+
+### 1. 执行Cypher查询 (/graph/query)
+
+- **URL**: `/graph/query`
+- **方法**: POST
+- **描述**: 执行自定义Cypher查询并返回结果
+- **请求参数**:
+  ```json
+  {
+    "cypher": "MATCH (n:Person) WHERE n.name = $name RETURN n",
+    "params": {
+      "name": "张三"
+    }
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": [
+      {
+        "n": {
+          "_id": 123,
+          "_labels": ["Person"],
+          "name": "张三",
+          "age": 30
+        }
+      }
+    ]
+  }
+  ```
+
+### 2. 创建节点 (/graph/node/create)
+
+- **URL**: `/graph/node/create`
+- **方法**: POST
+- **描述**: 创建一个新节点
+- **请求参数**:
+  ```json
+  {
+    "labels": ["Person", "Employee"],
+    "properties": {
+      "name": "张三",
+      "age": 30,
+      "department": "技术部"
+    }
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "n": {
+        "_id": 123,
+        "_labels": ["Person", "Employee"],
+        "name": "张三",
+        "age": 30,
+        "department": "技术部"
+      }
+    }
+  }
+  ```
+
+### 3. 创建关系 (/graph/relationship/create)
+
+- **URL**: `/graph/relationship/create`
+- **方法**: POST
+- **描述**: 在两个节点之间创建关系
+- **请求参数**:
+  ```json
+  {
+    "startNodeId": 123,
+    "endNodeId": 456,
+    "type": "KNOWS",
+    "properties": {
+      "since": 2020,
+      "relationship": "同事"
+    }
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "r": {
+        "_id": 789,
+        "_type": "KNOWS",
+        "_start_node_id": 123,
+        "_end_node_id": 456,
+        "since": 2020,
+        "relationship": "同事"
+      }
+    }
+  }
+  ```
+
+### 4. 获取子图 (/graph/subgraph)
+
+- **URL**: `/graph/subgraph`
+- **方法**: POST
+- **描述**: 获取以指定节点为起点的子图
+- **请求参数**:
+  ```json
+  {
+    "nodeIds": [123],
+    "relationshipTypes": ["KNOWS", "WORKS_WITH"],
+    "maxDepth": 2
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "nodes": [
+        {
+          "id": 123,
+          "labels": ["Person"],
+          "name": "张三",
+          "age": 30
+        },
+        {
+          "id": 456,
+          "labels": ["Person"],
+          "name": "李四",
+          "age": 28
+        }
+      ],
+      "relationships": [
+        {
+          "id": 789,
+          "type": "KNOWS",
+          "source": 123,
+          "target": 456,
+          "since": 2020
+        }
+      ]
+    }
+  }
+  ```
+
+## 技术实现
+
+本模块基于Flask框架实现API接口,并使用core/graph模块提供的核心功能。主要技术点包括:
+
+- RESTful API设计
+- 请求参数验证与处理
+- 异常处理与错误响应
+- JSON序列化
+
+## 依赖关系
+
+本模块依赖于core/graph模块中的核心功能实现:
+
+```python
+from app.core.graph import (
+    connect_graph,
+    create_or_get_node,
+    create_relationship,
+    get_subgraph,
+    execute_cypher_query
+)
+``` 

+ 5 - 0
app/api/graph/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+bp = Blueprint('graph', __name__)
+
+from app.api.graph import routes 

+ 159 - 0
app/api/graph/routes.py

@@ -0,0 +1,159 @@
+"""
+Graph API module
+提供图数据库操作的API接口
+"""
+
+from flask import request, jsonify
+from app.api.graph import bp
+from app.models.result import success, failed
+from app.core.graph import (
+    connect_graph,
+    create_or_get_node,
+    create_relationship,
+    get_subgraph,
+    execute_cypher_query
+)
+from app.core.graph.graph_operations import MyEncoder
+import logging
+import json
+
+logger = logging.getLogger("app")
+
+# 查询图数据
+@bp.route('/query', methods=['POST'])
+def query_graph():
+    """
+    执行自定义Cypher查询
+    
+    Args (通过JSON请求体):
+        cypher (str): Cypher查询语句
+        params (dict, optional): 查询参数
+        
+    Returns:
+        JSON: 包含查询结果的响应
+    """
+    try:
+        # 获取查询语句
+        cypher = request.json.get('cypher', '')
+        params = request.json.get('params', {})
+        
+        if not cypher:
+            return jsonify(failed("查询语句不能为空"))
+        
+        # 执行查询
+        data = execute_cypher_query(cypher, params)
+        return jsonify(success(data))
+    except Exception as e:
+        logger.error(f"图数据查询失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 创建节点
+@bp.route('/node/create', methods=['POST'])
+def create_node():
+    """
+    创建新节点
+    
+    Args (通过JSON请求体):
+        labels (list): 节点标签列表
+        properties (dict): 节点属性
+        
+    Returns:
+        JSON: 包含创建的节点信息的响应
+    """
+    try:
+        # 获取节点信息
+        labels = request.json.get('labels', [])
+        properties = request.json.get('properties', {})
+        
+        if not labels:
+            return jsonify(failed("节点标签不能为空"))
+        
+        # 构建标签字符串
+        label = ':'.join(labels)
+        
+        # 创建节点
+        node_id = create_or_get_node(label, **properties)
+        
+        # 查询创建的节点
+        cypher = f"MATCH (n) WHERE id(n) = {node_id} RETURN n"
+        result = execute_cypher_query(cypher)
+        
+        if result and len(result) > 0:
+            return jsonify(success(result[0]))
+        else:
+            return jsonify(failed("节点创建失败"))
+    except Exception as e:
+        logger.error(f"创建节点失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 创建关系
+@bp.route('/relationship/create', methods=['POST'])
+def create_rel():
+    """
+    创建节点间的关系
+    
+    Args (通过JSON请求体):
+        startNodeId (int): 起始节点ID
+        endNodeId (int): 结束节点ID
+        type (str): 关系类型
+        properties (dict, optional): 关系属性
+        
+    Returns:
+        JSON: 包含创建的关系信息的响应
+    """
+    try:
+        # 获取关系信息
+        start_node_id = request.json.get('startNodeId')
+        end_node_id = request.json.get('endNodeId')
+        rel_type = request.json.get('type')
+        properties = request.json.get('properties', {})
+        
+        if not all([start_node_id, end_node_id, rel_type]):
+            return jsonify(failed("关系参数不完整"))
+        
+        # 创建关系
+        rel_id = create_relationship(start_node_id, end_node_id, rel_type, **properties)
+        
+        if rel_id:
+            # 查询创建的关系
+            cypher = f"MATCH ()-[r]-() WHERE id(r) = {rel_id} RETURN r"
+            result = execute_cypher_query(cypher)
+            
+            if result and len(result) > 0:
+                return jsonify(success(result[0]))
+        
+        return jsonify(failed("关系创建失败"))
+    except Exception as e:
+        logger.error(f"创建关系失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 获取图谱数据
+@bp.route('/subgraph', methods=['POST'])
+def get_graph_data():
+    """
+    获取子图数据
+    
+    Args (通过JSON请求体):
+        nodeIds (list): 节点ID列表
+        relationshipTypes (list, optional): 关系类型列表
+        maxDepth (int, optional): 最大深度,默认为1
+        
+    Returns:
+        JSON: 包含节点和关系的子图数据
+    """
+    try:
+        # 获取请求参数
+        node_ids = request.json.get('nodeIds', [])
+        rel_types = request.json.get('relationshipTypes', [])
+        max_depth = request.json.get('maxDepth', 1)
+        
+        if not node_ids:
+            return jsonify(failed("节点ID列表不能为空"))
+        
+        # 获取子图
+        graph_data = get_subgraph(node_ids, rel_types, max_depth)
+        
+        return jsonify(success(graph_data))
+    except Exception as e:
+        logger.error(f"获取图谱数据失败: {str(e)}")
+        return jsonify(failed(str(e))) 

+ 5 - 0
app/api/meta_data/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+bp = Blueprint('meta_data', __name__)
+
+from app.api.meta_data import routes 

+ 560 - 0
app/api/meta_data/routes.py

@@ -0,0 +1,560 @@
+from flask import request, jsonify, send_from_directory, send_file
+from app.api.meta_data import bp
+from app.models.result import success, failed
+from app.config.config import Config
+import logging
+import json
+import io
+import os
+from minio import Minio
+from minio.error import S3Error
+from app.services.neo4j_driver import neo4j_driver
+from app.services.package_function import create_or_get_node, relationship_exists
+from app.core.meta_data import (
+    translate_and_parse, 
+    get_formatted_time, 
+    meta_list, 
+    meta_kinship_graph, 
+    meta_impact_graph,
+    parse_text,
+    parse_entity_relation,
+    handle_txt_graph,
+    get_file_content,
+    text_resource_solve,
+    handle_id_unstructured,
+    solve_unstructured_data
+)
+
+logger = logging.getLogger("app")
+
+# 配置MinIO客户端
+minio_client = Minio(
+    Config.MINIO_HOST,
+    access_key=Config.MINIO_USER,
+    secret_key=Config.MINIO_PASSWORD,
+    secure=True
+)
+
+# 配置文件上传相关
+UPLOAD_FOLDER = Config.UPLOAD_FOLDER
+bucket_name = Config.BUCKET_NAME
+prefix = Config.PREFIX
+
+# 允许的文件扩展名
+ALLOWED_EXTENSIONS = {'txt', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'csv'}
+
+def allowed_file(filename):
+    """检查文件扩展名是否允许"""
+    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
+
+# 元数据列表
+@bp.route('/node/list', methods=['POST'])
+def meta_node_list():
+    try:
+        # 从请求中获取分页参数
+        page = int(request.json.get('current', 1))
+        page_size = int(request.json.get('size', 10))
+        # 获取搜索参数
+        search = request.json.get('search', '')
+        en_name_filter = request.json.get('en_name', None)
+        name_filter = request.json.get('name', None)
+        category_filter = request.json.get('category', None)
+        time_filter = request.json.get('time', None)
+        tag_filter = request.json.get('tag', None)
+        
+        # 调用核心业务逻辑
+        result, total_count = meta_list(
+            page, 
+            page_size, 
+            search, 
+            en_name_filter, 
+            name_filter, 
+            category_filter, 
+            time_filter, 
+            tag_filter
+        )
+        
+        # 返回结果
+        return jsonify(success({
+            "records": result,
+            "total": total_count,
+            "size": page_size,
+            "current": page
+        }))
+    except Exception as e:
+        logger.error(f"获取元数据列表失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 元数据图谱
+@bp.route('/node/graph', methods=['POST'])
+def meta_node_graph():
+    try:
+        # 从请求中获取节点ID
+        node_id = request.json.get('nodeId')
+        
+        # 调用核心业务逻辑
+        result = meta_kinship_graph(node_id)
+        
+        # 返回结果
+        return jsonify(success(result))
+    except Exception as e:
+        logger.error(f"获取元数据图谱失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 删除元数据
+@bp.route('/node/delete', methods=['POST'])
+def meta_node_delete():
+    try:
+        # 从请求中获取节点ID
+        node_id = request.json.get('id')
+        
+        # 删除节点逻辑
+        with neo4j_driver.get_session() as session:
+            cypher = "MATCH (n) WHERE id(n) = $node_id DETACH DELETE n"
+            session.run(cypher, node_id=int(node_id))
+        
+        # 返回结果
+        return jsonify(success({}))
+    except Exception as e:
+        logger.error(f"删除元数据失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 编辑元数据
+@bp.route('/node/edit', methods=['POST'])
+def meta_node_edit():
+    try:
+        # 从请求中获取节点信息
+        node_id = request.json.get('id')
+        node_name = request.json.get('name')
+        node_type = request.json.get('type')
+        node_desc = request.json.get('desc', '')
+        node_properties = request.json.get('properties', {})
+        
+        with neo4j_driver.get_session() as session:
+            # 更新节点属性
+            cypher = """
+            MATCH (n) WHERE id(n) = $node_id 
+            SET n.name = $name, n.type = $type, n.desc = $desc, 
+                n.properties = $properties, n.updateTime = $update_time
+            RETURN n
+            """
+            update_time = get_formatted_time()
+            result = session.run(
+                cypher, 
+                node_id=int(node_id), 
+                name=node_name, 
+                type=node_type, 
+                desc=node_desc,
+                properties=node_properties,
+                update_time=update_time
+            )
+            
+            node = result.single()
+            if node:
+                return jsonify(success(dict(node["n"])))
+            else:
+                return jsonify(failed("节点不存在"))
+    except Exception as e:
+        logger.error(f"编辑元数据失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 增加元数据
+@bp.route('/node/add', methods=['POST'])
+def meta_node_add():
+    try:
+        # 从请求中获取节点信息
+        node_name = request.json.get('name')
+        node_type = request.json.get('type')
+        node_desc = request.json.get('desc', '')
+        node_properties = request.json.get('properties', {})
+        
+        # 创建节点
+        with neo4j_driver.get_session() as session:
+            cypher = """
+            CREATE (n:Metadata {name: $name, type: $type, desc: $desc, 
+                               properties: $properties, createTime: $create_time, 
+                               updateTime: $update_time})
+            RETURN n
+            """
+            create_time = update_time = get_formatted_time()
+            result = session.run(
+                cypher, 
+                name=node_name, 
+                type=node_type, 
+                desc=node_desc,
+                properties=node_properties,
+                create_time=create_time,
+                update_time=update_time
+            )
+            
+            node = result.single()
+            return jsonify(success(dict(node["n"])))
+    except Exception as e:
+        logger.error(f"添加元数据失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 搜索元数据
+@bp.route('/search', methods=['GET'])
+def search_metadata_route():
+    try:
+        keyword = request.args.get('keyword', '')
+        if not keyword:
+            return jsonify(success([]))
+            
+        cypher = """
+        MATCH (n:Metadata) 
+        WHERE n.name CONTAINS $keyword
+        RETURN n LIMIT 100
+        """
+        
+        with neo4j_driver.get_session() as session:
+            result = session.run(cypher, keyword=keyword)
+            metadata_list = [dict(record["n"]) for record in result]
+            
+        return jsonify(success(metadata_list))
+    except Exception as e:
+        logger.error(f"搜索元数据失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 全文检索查询
+@bp.route('/full/text/query', methods=['POST'])
+def full_text_query():
+    try:
+        # 获取查询条件
+        query = request.json.get('query', '')
+        if not query:
+            return jsonify(failed("查询条件不能为空"))
+            
+        # 执行Neo4j全文索引查询
+        with neo4j_driver.get_session() as session:
+            cypher = """
+            CALL db.index.fulltext.queryNodes("metadataFulltext", $query)
+            YIELD node, score
+            RETURN node, score
+            ORDER BY score DESC
+            LIMIT 20
+            """
+            
+            result = session.run(cypher, query=query)
+            
+            # 处理查询结果
+            search_results = []
+            for record in result:
+                node_data = dict(record["node"])
+                node_data["id"] = record["node"].id
+                node_data["score"] = record["score"]
+                search_results.append(node_data)
+                
+            return jsonify(success(search_results))
+    except Exception as e:
+        logger.error(f"全文检索查询失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 非结构化文本查询
+@bp.route('/unstructure/text/query', methods=['POST'])
+def unstructure_text_query():
+    try:
+        # 获取查询参数
+        node_id = request.json.get('id')
+        if not node_id:
+            return jsonify(failed("节点ID不能为空"))
+            
+        # 获取节点信息
+        node_data = handle_id_unstructured(node_id)
+        if not node_data:
+            return jsonify(failed("节点不存在"))
+            
+        # 获取对象路径
+        object_name = node_data.get('objectName')
+        if not object_name:
+            return jsonify(failed("文档路径不存在"))
+            
+        # 从MinIO获取文件内容
+        file_content = get_file_content(minio_client, bucket_name, object_name)
+        
+        # 解析文本内容
+        parsed_data = parse_text(file_content)
+        
+        # 返回结果
+        result = {
+            "node": node_data,
+            "parsed": parsed_data,
+            "content": file_content[:1000] + "..." if len(file_content) > 1000 else file_content
+        }
+        
+        return jsonify(success(result))
+    except Exception as e:
+        logger.error(f"非结构化文本查询失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 文件上传
+@bp.route('/resource/upload', methods=['POST'])
+def upload_file():
+    try:
+        # 检查请求中是否有文件
+        if 'file' not in request.files:
+            return jsonify(failed("没有找到上传的文件"))
+            
+        file = request.files['file']
+        
+        # 检查文件名
+        if file.filename == '':
+            return jsonify(failed("未选择文件"))
+            
+        # 检查文件类型
+        if not allowed_file(file.filename):
+            return jsonify(failed("不支持的文件类型"))
+            
+        # 上传到MinIO
+        file_content = file.read()
+        file_size = len(file_content)
+        file_type = file.filename.rsplit('.', 1)[1].lower()
+        
+        # 生成唯一文件名
+        object_name = f"{prefix}/{get_formatted_time()}_{file.filename}"
+        
+        # 上传文件
+        minio_client.put_object(
+            bucket_name, 
+            object_name,
+            io.BytesIO(file_content),
+            file_size,
+            content_type=f"application/{file_type}"
+        )
+        
+        # 返回结果
+        return jsonify(success({
+            "filename": file.filename,
+            "size": file_size,
+            "type": file_type,
+            "objectName": object_name
+        }))
+    except Exception as e:
+        logger.error(f"文件上传失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 文件下载显示
+@bp.route('/resource/display', methods=['POST'])
+def upload_file_display():
+    try:
+        object_name = request.json.get('objectName')
+        if not object_name:
+            return jsonify(failed("文件路径不能为空"))
+            
+        # 获取文件内容
+        try:
+            response = minio_client.get_object(bucket_name, object_name)
+            file_data = response.read()
+            
+            # 获取文件名
+            file_name = object_name.split('/')[-1]
+            
+            # 确定文件类型
+            file_extension = file_name.split('.')[-1].lower()
+            
+            # 为不同文件类型设置合适的MIME类型
+            mime_types = {
+                'pdf': 'application/pdf',
+                'doc': 'application/msword',
+                'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+                'xls': 'application/vnd.ms-excel',
+                'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+                'txt': 'text/plain',
+                'csv': 'text/csv'
+            }
+            
+            content_type = mime_types.get(file_extension, 'application/octet-stream')
+            
+            # 返回结果
+            return jsonify(success({
+                "filename": file_name,
+                "type": file_extension,
+                "contentType": content_type,
+                "size": len(file_data),
+                "url": f"/api/meta/resource/download?objectName={object_name}"
+            }))
+        finally:
+            response.close()
+            response.release_conn()
+    except Exception as e:
+        logger.error(f"文件显示信息获取失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 文件下载接口
+@bp.route('/resource/download', methods=['GET'])
+def download_file():
+    try:
+        object_name = request.args.get('objectName')
+        if not object_name:
+            return jsonify(failed("文件路径不能为空"))
+            
+        # 获取文件
+        response = minio_client.get_object(bucket_name, object_name)
+        file_data = response.read()
+        
+        # 获取文件名
+        file_name = object_name.split('/')[-1]
+        
+        # 创建临时文件
+        temp_path = os.path.join(UPLOAD_FOLDER, file_name)
+        with open(temp_path, 'wb') as f:
+            f.write(file_data)
+            
+        # 返回文件
+        return send_file(
+            temp_path,
+            as_attachment=True,
+            download_name=file_name
+        )
+    except Exception as e:
+        logger.error(f"文件下载失败: {str(e)}")
+        return jsonify(failed(str(e)))
+    finally:
+        if 'response' in locals():
+            response.close()
+            response.release_conn()
+
+# 文本资源翻译
+@bp.route('/resource/translate', methods=['POST'])
+def text_resource_translate():
+    try:
+        # 获取参数
+        name = request.json.get('name', '')
+        keyword = request.json.get('keyword', '')
+        
+        if not name:
+            return jsonify(failed("名称不能为空"))
+            
+        # 调用资源处理逻辑
+        result = text_resource_solve(None, name, keyword)
+        
+        return jsonify(success(result))
+    except Exception as e:
+        logger.error(f"文本资源翻译失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 创建文本资源节点
+@bp.route('/resource/node', methods=['POST'])
+def text_resource_node():
+    try:
+        # 获取参数
+        name = request.json.get('name', '')
+        en_name = request.json.get('en_name', '')
+        keywords = request.json.get('keywords', [])
+        keywords_en = request.json.get('keywords_en', [])
+        object_name = request.json.get('objectName', '')
+        
+        if not name or not en_name or not object_name:
+            return jsonify(failed("参数不完整"))
+            
+        # 创建节点
+        with neo4j_driver.get_session() as session:
+            # 创建资源节点
+            cypher = """
+            CREATE (n:TextResource {
+                name: $name,
+                en_name: $en_name,
+                keywords: $keywords,
+                keywords_en: $keywords_en,
+                objectName: $object_name,
+                createTime: $create_time,
+                updateTime: $update_time
+            })
+            RETURN n
+            """
+            
+            create_time = update_time = get_formatted_time()
+            result = session.run(
+                cypher,
+                name=name,
+                en_name=en_name,
+                keywords=keywords,
+                keywords_en=keywords_en,
+                object_name=object_name,
+                create_time=create_time,
+                update_time=update_time
+            )
+            
+            node = result.single()["n"]
+            
+            # 为每个关键词创建标签节点并关联
+            for i, keyword in enumerate(keywords):
+                if keyword:
+                    # 创建标签节点
+                    tag_cypher = """
+                    MERGE (t:Tag {name: $name})
+                    ON CREATE SET t.en_name = $en_name, t.createTime = $create_time
+                    RETURN t
+                    """
+                    
+                    tag_result = session.run(
+                        tag_cypher,
+                        name=keyword,
+                        en_name=keywords_en[i] if i < len(keywords_en) else "",
+                        create_time=create_time
+                    )
+                    
+                    tag_node = tag_result.single()["t"]
+                    
+                    # 创建关系
+                    rel_cypher = """
+                    MATCH (n), (t)
+                    WHERE id(n) = $node_id AND id(t) = $tag_id
+                    CREATE (n)-[r:HAS_TAG]->(t)
+                    RETURN r
+                    """
+                    
+                    session.run(
+                        rel_cypher,
+                        node_id=node.id,
+                        tag_id=tag_node.id
+                    )
+            
+            # 返回创建的节点
+            return jsonify(success(dict(node)))
+    except Exception as e:
+        logger.error(f"创建文本资源节点失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 处理非结构化数据
+@bp.route('/unstructured/process', methods=['POST'])
+def processing_unstructured_data():
+    try:
+        # 获取参数
+        node_id = request.json.get('id')
+        if not node_id:
+            return jsonify(failed("节点ID不能为空"))
+            
+        # 调用处理逻辑
+        result = solve_unstructured_data(node_id, minio_client, prefix)
+        
+        if result:
+            return jsonify(success({"message": "处理成功"}))
+        else:
+            return jsonify(failed("处理失败"))
+    except Exception as e:
+        logger.error(f"处理非结构化数据失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 创建文本图谱
+@bp.route('/text/graph', methods=['POST'])
+def create_text_graph():
+    try:
+        # 获取参数
+        node_id = request.json.get('id')
+        entity = request.json.get('entity')
+        entity_en = request.json.get('entity_en')
+        
+        if not all([node_id, entity, entity_en]):
+            return jsonify(failed("参数不完整"))
+            
+        # 创建图谱
+        result = handle_txt_graph(node_id, entity, entity_en)
+        
+        if result:
+            return jsonify(success({"message": "图谱创建成功"}))
+        else:
+            return jsonify(failed("图谱创建失败"))
+    except Exception as e:
+        logger.error(f"创建文本图谱失败: {str(e)}")
+        return jsonify(failed(str(e))) 

+ 92 - 0
app/api/production_line/README.md

@@ -0,0 +1,92 @@
+# 生产线API接口模块
+
+本模块提供生产线相关的API接口,用于前端交互与数据展示。
+
+## 功能概述
+
+生产线API模块提供了生产线列表查询和图谱绘制的接口,支持数据模型、数据资源和数据指标的展示和关系查询。
+
+## API接口
+
+### 1. 生产线列表查询 (/production/line/list)
+
+- **URL**: `/production/line/list`
+- **方法**: POST
+- **描述**: 获取生产线列表,支持分页和名称过滤
+- **请求参数**:
+  ```json
+  {
+    "current": 1,     // 当前页码,默认为1
+    "size": 10,       // 每页大小,默认为10
+    "name": "关键词"  // 可选,按名称过滤
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "records": [
+        {
+          "id": 123,
+          "name": "样例生产线",
+          "type": "data_model"
+        }
+        // 更多记录...
+      ],
+      "total": 100,
+      "size": 10,
+      "current": 1
+    }
+  }
+  ```
+
+### 2. 生产线图谱绘制 (/production/line/graph)
+
+- **URL**: `/production/line/graph`
+- **方法**: POST
+- **描述**: 根据生产线ID绘制关系图谱
+- **请求参数**:
+  ```json
+  {
+    "id": 123  // 节点ID
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "nodes": [
+        {"id": "节点ID", "text": "节点名称", "type": "节点类型"},
+        // 更多节点...
+      ],
+      "lines": [
+        {"from": "起始节点ID", "to": "目标节点ID", "text": "关系描述"},
+        // 更多连线...
+      ],
+      "rootId": "根节点ID"
+    }
+  }
+  ```
+
+## 技术实现
+
+本模块通过Flask框架实现API接口,使用Neo4j图数据库进行数据查询,主要涉及以下技术点:
+
+- Flask路由定义与请求处理
+- Neo4j图数据库Cypher查询
+- 数据分页与过滤处理
+- JSON数据序列化与返回
+
+## 依赖关系
+
+本模块依赖于core/production_line模块中的核心功能实现:
+
+```python
+from app.core.production_line import production_draw_graph
+``` 

+ 11 - 0
app/api/production_line/__init__.py

@@ -0,0 +1,11 @@
+"""
+Production Line API module
+包含生产线相关的API接口
+"""
+
+from app.api.production_line.routes import production_line_list, production_line_graph
+
+__all__ = [
+    'production_line_list',
+    'production_line_graph',
+] 

+ 93 - 0
app/api/production_line/routes.py

@@ -0,0 +1,93 @@
+from flask import request
+from app import app
+from app.model.result import success, failed
+from app.routes.graph_routes import MyEncoder, connect_graph
+from app.core.production_line import production_draw_graph
+import json
+
+
+# 生产线列表
+@app.route('/production/line/list', methods=['POST'])
+def production_line_list():
+    """
+    获取生产线列表,支持分页和名称过滤
+    
+    Args (通过JSON请求体):
+        current (int): 当前页码,默认为1
+        size (int): 每页大小,默认为10
+        name (str, optional): 名称过滤条件
+        
+    Returns:
+        JSON: 包含生产线列表和分页信息的响应
+    """
+    try:
+        receiver = request.get_json()
+        page = int(receiver.get('current', 1))
+        page_size = int(receiver.get('size', 10))
+        name_filter = receiver.get('name', None)
+
+        # 计算跳过的记录的数量
+        skip_count = (page - 1) * page_size
+
+        if name_filter:
+            where_clause = f"n.name CONTAINS'{name_filter}'"
+        else:
+            where_clause = "TRUE"
+        cql = f"""
+        MATCH (n)
+        WHERE (n:data_model OR n:data_resource OR n:data_metric) AND {where_clause}
+        WITH id(n) AS id, n.name AS name, labels(n)[0] AS type,n
+        RETURN  {{
+            id: id,
+            name: name,
+            type: type
+        }} AS result,n.time as time
+        ORDER BY time desc
+        SKIP {skip_count}
+        LIMIT {page_size}
+        """
+        data = connect_graph.run(cql).data()
+        records = []
+        for item in data:
+            records.append(item['result'])
+
+        # 获取总量
+        total_query = f"MATCH (n) WHERE (n:data_model OR n:data_resource OR n:data_metric) AND {where_clause}" \
+                      f" RETURN COUNT(n) AS total"
+        total_result = connect_graph.run(total_query).evaluate()
+        response_data = {'records': records, 'total': total_result, 'size': page_size, 'current': page}
+        res = success(response_data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+
+# 根据生产线列表,传入id,绘制图谱
+@app.route('/production/line/graph', methods=['POST'])
+def production_line_graph():
+    """
+    根据生产线ID绘制关系图谱
+    
+    Args (通过JSON请求体):
+        id (int): 节点ID
+        
+    Returns:
+        JSON: 包含图谱数据的响应
+    """
+    # 传入请求参数
+    receiver = request.get_json()
+    id = receiver['id']
+    try:
+        cql = """
+        MATCH (n) where id(n) = $nodeId return labels(n)[0] as type
+        """
+        type = connect_graph.run(cql, nodeId=id).evaluate()
+        data = production_draw_graph(id, type)
+        res = success(data, "success")
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder)
+
+    except Exception as e:
+        res = failed({}, {"error": f"{e}"})
+        return json.dumps(res, ensure_ascii=False, cls=MyEncoder) 

+ 259 - 0
app/api/system/README.md

@@ -0,0 +1,259 @@
+# 系统管理API接口模块
+
+本模块提供系统管理相关的API接口,包括系统健康检查、配置管理、系统信息获取和用户认证等功能,为前端应用和其他服务提供系统级别的支持。
+
+## 功能概述
+
+系统管理API模块主要提供以下功能的HTTP接口:
+
+1. **系统健康检查**:提供系统和依赖组件的健康状态监控
+2. **配置管理**:获取和验证系统配置信息
+3. **系统信息**:获取系统运行环境的详细信息
+4. **用户认证**:提供用户注册、登录等功能
+
+## API接口
+
+### 1. 健康检查接口 (/system/health)
+
+- **URL**: `/system/health`
+- **方法**: GET
+- **描述**: 检查系统及其依赖组件的健康状态
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "service": "DataOps-platform",
+      "status": "UP",
+      "version": "1.0.0",
+      "time": "2023-03-17 12:34:56",
+      "dependencies": {
+        "neo4j": {
+          "status": "UP",
+          "details": {
+            "url": "bolt://localhost:7687",
+            "encrypted": false
+          }
+        }
+      }
+    }
+  }
+  ```
+
+### 2. 系统配置信息 (/system/config)
+
+- **URL**: `/system/config`
+- **方法**: GET
+- **描述**: 获取系统配置信息(非敏感信息)
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "environment": "development",
+      "debug_mode": true,
+      "port": 5000,
+      "platform": "DataOps",
+      "upload_folder": "/path/to/upload",
+      "bucket_name": "dev",
+      "prefix": "data",
+      "neo4j_uri": "bolt://localhost:7687",
+      "neo4j_encrypted": false
+    }
+  }
+  ```
+
+### 3. 系统信息接口 (/system/info)
+
+- **URL**: `/system/info`
+- **方法**: GET
+- **描述**: 获取系统运行环境的详细信息
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "os": {
+        "name": "Windows",
+        "version": "10.0.19045",
+        "platform": "Windows-10-10.0.19045-SP0"
+      },
+      "python": {
+        "version": "3.9.10",
+        "implementation": "CPython"
+      },
+      "network": {
+        "hostname": "DESKTOP-12345",
+        "ip": "192.168.1.100"
+      },
+      "resources": {
+        "cpu": {
+          "cores": 8,
+          "logical_cores": 16,
+          "usage_percent": 25.5
+        },
+        "memory": {
+          "total": "16.00 GB",
+          "available": "8.50 GB",
+          "used": "7.50 GB",
+          "percent": 46.9
+        },
+        "disk": {
+          "total": "512.00 GB",
+          "used": "256.00 GB",
+          "free": "256.00 GB",
+          "percent": 50.0
+        }
+      },
+      "application": {
+        "environment": "development",
+        "debug_mode": true,
+        "port": 5000,
+        "platform": "DataOps"
+      }
+    }
+  }
+  ```
+
+### 4. 配置验证接口 (/system/config/validate)
+
+- **URL**: `/system/config/validate`
+- **方法**: GET
+- **描述**: 验证系统配置的有效性
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "success": true,
+    "message": "success",
+    "data": {
+      "valid": true,
+      "errors": []
+    }
+  }
+  ```
+
+### 5. 用户注册接口 (/system/auth/register)
+
+- **URL**: `/system/auth/register`
+- **方法**: POST
+- **描述**: 注册新用户
+- **请求参数**:
+  ```json
+  {
+    "username": "用户名",
+    "password": "密码"
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "message": "注册成功",
+    "data": null
+  }
+  ```
+- **错误响应**:
+  ```json
+  {
+    "code": 400,
+    "message": "用户名已存在",
+    "data": null
+  }
+  ```
+
+### 6. 用户登录接口 (/system/auth/login)
+
+- **URL**: `/system/auth/login`
+- **方法**: POST
+- **描述**: 用户登录验证
+- **请求参数**:
+  ```json
+  {
+    "username": "用户名",
+    "password": "密码"
+  }
+  ```
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "message": "登录成功",
+    "data": {
+      "id": "用户ID",
+      "username": "用户名",
+      "created_at": 1679047342.123456,
+      "last_login": 1679047400.654321
+    }
+  }
+  ```
+- **错误响应**:
+  ```json
+  {
+    "code": 401,
+    "message": "用户名或密码错误",
+    "data": null
+  }
+  ```
+
+### 7. 获取用户信息接口 (/system/auth/user/{username})
+
+- **URL**: `/system/auth/user/{username}`
+- **方法**: GET
+- **描述**: 获取指定用户的信息
+- **参数**:
+  - `username`: 要查询的用户名
+- **返回数据**:
+  ```json
+  {
+    "code": 200,
+    "message": "success",
+    "data": {
+      "id": "用户ID",
+      "username": "用户名",
+      "created_at": 1679047342.123456,
+      "last_login": 1679047400.654321
+    }
+  }
+  ```
+- **错误响应**:
+  ```json
+  {
+    "code": 404,
+    "message": "用户不存在",
+    "data": null
+  }
+  ```
+
+## 技术实现
+
+本模块基于Flask框架实现API接口,并使用core/system模块提供的核心功能。主要技术点包括:
+
+- RESTful API设计原则
+- 标准化的响应格式
+- 异常处理与错误日志记录
+- JSON序列化
+- Base64加密保护用户密码
+
+## 依赖关系
+
+本模块依赖于core/system模块中的核心功能实现:
+
+```python
+from app.core.system import (
+    check_neo4j_connection,
+    check_system_health,
+    get_system_info,
+    get_system_config,
+    validate_config,
+    register_user,
+    login_user,
+    get_user_by_username
+)
+``` 

+ 5 - 0
app/api/system/__init__.py

@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+bp = Blueprint('system', __name__)
+
+from app.api.system import routes 

+ 186 - 0
app/api/system/routes.py

@@ -0,0 +1,186 @@
+"""
+System API Module
+提供系统管理相关的API接口
+"""
+
+from flask import jsonify, request
+from app.api.system import bp
+from app.models.result import success, failed
+import logging
+from app.core.system import (
+    check_neo4j_connection,
+    check_system_health,
+    get_system_info,
+    get_system_config,
+    validate_config,
+    register_user,
+    login_user,
+    get_user_by_username
+)
+
+logger = logging.getLogger("app")
+
+# 健康检查接口
+@bp.route('/health', methods=['GET'])
+def health_check():
+    """
+    系统健康状态检查
+    检查关键依赖组件的连接状态
+    
+    Returns:
+        JSON: 系统健康状态信息
+    """
+    try:
+        # 获取系统健康状态
+        status = check_system_health()
+        return jsonify(success(status))
+    except Exception as e:
+        logger.error(f"健康检查失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 系统配置信息
+@bp.route('/config', methods=['GET'])
+def system_config():
+    """
+    获取系统配置信息
+    返回非敏感的系统配置项
+    
+    Returns:
+        JSON: 系统配置信息
+    """
+    try:
+        # 获取系统配置信息
+        config_info = get_system_config()
+        return jsonify(success(config_info))
+    except Exception as e:
+        logger.error(f"获取系统配置失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 系统信息接口
+@bp.route('/info', methods=['GET'])
+def system_info():
+    """
+    获取系统运行环境信息
+    包括操作系统、Python版本、资源使用情况等
+    
+    Returns:
+        JSON: 系统运行环境详细信息
+    """
+    try:
+        # 获取系统详细信息
+        info = get_system_info()
+        return jsonify(success(info))
+    except Exception as e:
+        logger.error(f"获取系统信息失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 配置验证接口
+@bp.route('/config/validate', methods=['GET'])
+def config_validate():
+    """
+    验证系统配置的有效性
+    检查必要的配置项是否存在且有效
+    
+    Returns:
+        JSON: 配置验证结果
+    """
+    try:
+        is_valid, errors = validate_config()
+        result = {
+            "valid": is_valid,
+            "errors": errors
+        }
+        return jsonify(success(result))
+    except Exception as e:
+        logger.error(f"配置验证失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 用户注册接口
+@bp.route('/auth/register', methods=['POST'])
+def user_register():
+    """
+    用户注册
+    
+    请求参数:
+        username: 用户名
+        password: 密码
+    
+    Returns:
+        JSON: 注册结果
+    """
+    try:
+        # 获取请求参数
+        data = request.json
+        username = data.get('username')
+        password = data.get('password')
+        
+        # 参数验证
+        if not username or not password:
+            return jsonify(failed("用户名和密码不能为空", code=400))
+        
+        # 注册用户
+        success_flag, message = register_user(username, password)
+        
+        if success_flag:
+            return jsonify(success(message="注册成功"))
+        else:
+            return jsonify(failed(message, code=400))
+    except Exception as e:
+        logger.error(f"用户注册失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 用户登录接口
+@bp.route('/auth/login', methods=['POST'])
+def user_login():
+    """
+    用户登录
+    
+    请求参数:
+        username: 用户名
+        password: 密码
+    
+    Returns:
+        JSON: 登录结果,包含用户信息
+    """
+    try:
+        # 获取请求参数
+        data = request.json
+        username = data.get('username')
+        password = data.get('password')
+        
+        # 参数验证
+        if not username or not password:
+            return jsonify(failed("用户名和密码不能为空", code=400))
+        
+        # 登录验证
+        success_flag, result = login_user(username, password)
+        
+        if success_flag:
+            return jsonify(success(result, "登录成功"))
+        else:
+            return jsonify(failed(result, code=401))
+    except Exception as e:
+        logger.error(f"用户登录失败: {str(e)}")
+        return jsonify(failed(str(e)))
+
+# 获取用户信息接口
+@bp.route('/auth/user/<username>', methods=['GET'])
+def get_user(username):
+    """
+    获取用户信息
+    
+    Args:
+        username: 用户名
+    
+    Returns:
+        JSON: 用户信息
+    """
+    try:
+        user = get_user_by_username(username)
+        if user:
+            return jsonify(success(user))
+        else:
+            return jsonify(failed("用户不存在", code=404))
+    except Exception as e:
+        logger.error(f"获取用户信息失败: {str(e)}")
+        return jsonify(failed(str(e))) 

+ 1 - 0
app/config/__init__.py

@@ -0,0 +1 @@
+# Config package initialization 

+ 91 - 0
app/config/config.py

@@ -0,0 +1,91 @@
+import os
+import platform
+
+class Config:
+    """Base configuration class"""
+    SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
+    JSON_AS_ASCII = False
+    
+    # Platform specific configurations
+    PLATFORM = platform.system().lower()
+    
+    # File paths
+    if PLATFORM == 'windows':
+        FILE_PATH = os.environ.get('FILE_PATH') or 'C:/temp/'
+    elif PLATFORM == 'linux':
+        FILE_PATH = os.environ.get('FILE_PATH') or '/tmp/'
+    
+    # Upload configurations
+    UPLOAD_FOLDER = f"{FILE_PATH}resource_uploads/"
+    ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'xlsx', 'xls', 'csv'}
+    
+    # MinIO configurations
+    MINIO_HOST = os.environ.get('MINIO_HOST') or 'minio.citupro.com'
+    MINIO_USER = os.environ.get('MINIO_USER') or 'default-user'
+    MINIO_PASSWORD = os.environ.get('MINIO_PASSWORD') or 'default-password'
+    MINIO_SECURE = True
+    
+    # Bucket configurations
+    BUCKET_NAME = os.environ.get('BUCKET_NAME') or 'dev'
+    if PLATFORM == 'windows':
+        PREFIX = 'dataops_test'
+    elif PLATFORM == 'linux':
+        PREFIX = 'dataops'
+    
+    # 新增端口配置基类设置
+    PORT = 5500  # 默认端口
+
+    # 修改后(PostgreSQL配置)
+     
+    SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:citupgdba@192.168.3.143:5432/dataops'
+    SQLALCHEMY_ENGINE_OPTIONS = {
+        'pool_pre_ping': True,
+        'pool_recycle': 300,
+        'pool_size': 10,
+        'max_overflow': 20
+    } 
+   
+    # 修改后(PostgreSQL配置)
+    """ 
+    SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:citumxl2357@127.0.0.1:5432/dataops'
+    SQLALCHEMY_ENGINE_OPTIONS = {
+        'pool_pre_ping': True,
+        'pool_recycle': 300,
+        'pool_size': 10,
+        'max_overflow': 20
+    } 
+    """
+
+    # Neo4j配置段
+     
+    NEO4J_URI = "bolt://192.168.3.143:7687"
+    NEO4J_HTTP_URI = "http://192.168.3.143:7474"
+    NEO4J_USER = "neo4j"
+    NEO4J_PASSWORD = "mxlneo4j"
+    NEO4J_ENCRYPTED = False  # 内网环境可关闭加密 
+
+    # Neo4j配置段
+    """ 
+    NEO4J_URI = "bolt://115.190.96.180:7687"
+    NEO4J_HTTP_URI = "http://115.190.96.180:7474"
+    NEO4J_USER = "neo4j"
+    NEO4J_PASSWORD = "mxlneo4j"
+    NEO4J_ENCRYPTED = False  # 内网环境可关闭加密
+    """
+
+class DevelopmentConfig(Config):
+    """Development configuration"""
+    DEBUG = True
+    PORT = 5500  # 开发环境保持5500
+
+class ProductionConfig(Config):
+    """Production configuration"""
+    DEBUG = False
+    PORT = 80  # 生产环境使用标准HTTP端口
+
+# Configuration dictionary
+config = {
+    'development': DevelopmentConfig,
+    'production': ProductionConfig,
+    'default': DevelopmentConfig
+} 

+ 8 - 0
app/core/__init__.py

@@ -0,0 +1,8 @@
+# app/core/__init__.py
+# 核心业务逻辑模块
+# 这里包含与数据库无关的纯业务逻辑 
+
+# 导入核心功能模块
+from app.core import meta_data
+from app.core import llm
+from app.core import common 

+ 16 - 0
app/core/common/__init__.py

@@ -0,0 +1,16 @@
+"""
+通用工具函数模块
+提供项目中需要的各种通用功能
+"""
+
+from app.core.common.functions import (
+    delete_relationships,
+    update_or_create_node,
+    get_node_by_id_no_label
+)
+
+__all__ = [
+    'delete_relationships',
+    'update_or_create_node',
+    'get_node_by_id_no_label'
+]

+ 84 - 0
app/core/common/functions.py

@@ -0,0 +1,84 @@
+"""
+通用函数工具集
+提供常用的图数据库操作和数据处理功能
+"""
+
+import logging
+from app.core.graph.graph_operations import connect_graph
+
+logger = logging.getLogger("app")
+
+def delete_relationships(node_id):
+    """
+    删除指定节点的所有关系
+    
+    Args:
+        node_id: 节点ID
+    """
+    try:
+        cql = """
+        MATCH (n)-[r]-()
+        WHERE id(n) = $node_id
+        DELETE r
+        """
+        with connect_graph().session() as session:
+            session.run(cql, node_id=node_id)
+        return True
+    except Exception as e:
+        logger.error(f"删除关系错误: {e}")
+        return False
+
+def update_or_create_node(node_id, **properties):
+    """
+    更新或创建节点
+    
+    Args:
+        node_id: 节点ID
+        **properties: 节点属性
+        
+    Returns:
+        节点对象
+    """
+    try:
+        # 检查节点是否存在
+        with connect_graph().session() as session:
+            check_query = "MATCH (n) WHERE id(n) = $node_id RETURN n"
+            result = session.run(check_query, node_id=node_id).single()
+            
+            if result:
+                # 如果有属性则更新,否则只返回节点
+                if properties:
+                    props_string = ", ".join([f"n.{key} = ${key}" for key in properties])
+                    update_query = f"""
+                    MATCH (n) WHERE id(n) = $node_id
+                    SET {props_string}
+                    RETURN n
+                    """
+                    result = session.run(update_query, node_id=node_id, **properties).single()
+                return result["n"]
+            else:
+                # 节点不存在,无法更新
+                logger.warning(f"节点 {node_id} 不存在,无法更新")
+                return None
+    except Exception as e:
+        logger.error(f"更新或创建节点错误: {e}")
+        return None
+
+def get_node_by_id_no_label(node_id):
+    """
+    通过ID获取节点,不考虑标签
+    
+    Args:
+        node_id: 节点ID
+        
+    Returns:
+        节点对象
+    """
+    try:
+        with connect_graph().session() as session:
+            query = "MATCH (n) WHERE id(n) = $node_id RETURN n"
+            result = session.run(query, node_id=node_id).single()
+            return result["n"] if result else None
+    except Exception as e:
+        logger.error(f"获取节点错误: {e}")
+        return None 

+ 97 - 0
app/core/data_interface/README.md

@@ -0,0 +1,97 @@
+# 数据接口核心业务逻辑模块
+
+本模块包含了数据接口相关的所有核心业务逻辑函数,处理数据标准和数据标签的创建、查询、更新、删除以及与其他数据对象的关系管理。
+
+## 主要功能
+
+1. **数据标准管理**
+   - 数据标准列表查询与筛选
+   - 数据标准图谱生成(血缘关系、影响关系、全量关系)
+   - 数据标准与其他数据对象(资源、模型、元数据等)的关系处理
+
+2. **数据标签管理**
+   - 数据标签列表查询与筛选
+   - 数据标签图谱生成(血缘关系、影响关系)
+   - 数据标签与其他数据对象的关系处理
+
+3. **动态标签识别**
+   - 基于内容相似度的标签分组识别
+   - 使用Levenshtein相似度算法进行匹配
+
+## 核心函数列表
+
+### 数据标准函数
+
+- `standard_list`:获取数据标准列表,支持多种过滤条件
+- `standard_kinship_graph`:生成数据标准的血缘关系图谱
+- `standard_impact_graph`:生成数据标准的影响关系图谱
+- `standard_all_graph`:生成数据标准的全量关系图谱
+
+### 数据标签函数
+
+- `label_list`:获取数据标签列表,支持多种过滤条件
+- `id_label_graph`:根据ID生成数据标签图谱
+- `label_kinship_graph`:生成数据标签的血缘关系图谱
+- `label_impact_graph`:生成数据标签的影响关系图谱
+- `dynamic_label_list`:根据内容查询相似的数据标签分组
+
+## 数据模型
+
+### 数据标准(data_standard)
+
+数据标准节点具有以下主要属性:
+- `name`:标准名称
+- `en_name`:标准英文名称
+- `category`:标准分类
+- `describe`:标准描述
+- `time`:创建/更新时间
+- `tag`:标签(JSON序列化的数组)
+- `code`:生成的标准代码(可选)
+- `input`:输入参数(可选)
+- `output`:输出参数(可选)
+
+### 数据标签(data_label)
+
+数据标签节点具有以下主要属性:
+- `name`:标签名称
+- `en_name`:标签英文名称
+- `category`:标签分类
+- `describe`:标签描述
+- `time`:创建/更新时间
+- `group`:标签分组
+- `scope`:标签作用域(可选)
+
+## 关系类型
+
+- `label`:表示标签关系,连接数据标签与其他数据对象
+- `clean_resource`:标准与资源的清洗关系
+- `clean_model`:标准与模型的清洗关系
+
+## 依赖关系
+
+- 依赖 `app.routes.graph_routes` 提供图数据库连接
+- 使用 Cypher 查询语言进行图数据库操作
+- 使用 apoc 插件提供的高级图算法和函数
+
+## 调用示例
+
+```python
+# 获取数据标准列表示例
+from app.core.data_interface.interface import standard_list
+
+# 查询名称包含"用户",分类为"数据格式"的数据标准
+skip_count = 0
+page_size = 10
+name_filter = "用户"
+category_filter = "数据格式"
+
+standards, total = standard_list(skip_count, page_size, 
+                                 name_filter=name_filter, 
+                                 category_filter=category_filter)
+
+# 生成数据标签图谱示例
+from app.core.data_interface.interface import label_kinship_graph
+
+# 生成ID为123的数据标签的血缘关系图谱
+graph_data = label_kinship_graph(123)
+``` 

+ 2 - 0
app/core/data_interface/__init__.py

@@ -0,0 +1,2 @@
+# 数据接口业务逻辑模块
+from app.core.data_interface import interface 

+ 423 - 0
app/core/data_interface/interface.py

@@ -0,0 +1,423 @@
+"""
+数据接口核心业务逻辑模块
+
+本模块包含了数据接口相关的所有核心业务逻辑函数,包括:
+- 数据标准(data_standard)相关功能
+- 数据标签(data_label)相关功能
+- 图谱生成
+- 动态标签识别等功能
+"""
+
+from app.core.graph.graph_operations import connect_graph
+
+
+# 数据标准列表展示
+def standard_list(skip_count, page_size, en_name_filter=None,
+                  name_filter=None, category_filter=None, time_filter=None):
+    """
+    获取数据标准列表
+    
+    Args:
+        skip_count: 跳过的记录数量
+        page_size: 每页记录数量
+        en_name_filter: 英文名称过滤条件
+        name_filter: 名称过滤条件
+        category_filter: 分类过滤条件
+        time_filter: 时间过滤条件
+        
+    Returns:
+        tuple: (数据列表, 总记录数)
+    """
+    data = []
+
+    # 构建查询条件
+    where_clause = []
+    params = {}
+    if name_filter:
+        where_clause.append("n.name CONTAINS $name_filter")
+        params['name_filter'] = name_filter
+    if en_name_filter:
+        where_clause.append("n.en_name CONTAINS $en_name_filter")
+        params['en_name_filter'] = en_name_filter
+    if category_filter:
+        where_clause.append("n.category CONTAINS $category_filter")
+        params['category_filter'] = category_filter
+    if time_filter:
+        where_clause.append("n.time CONTAINS $time_filter")
+        params['time_filter'] = time_filter
+    else:
+        where_clause.append("TRUE")
+
+    where_str = " AND ".join(where_clause)
+
+    # 构建完整的查询语句
+    cql = f"""
+    MATCH (n:data_standard)
+    WHERE {where_str}
+    RETURN properties(n) as properties,n.time as time,id(n) as nodeid,size((n)--()) as relationship_count
+    ORDER BY time desc
+    SKIP $skip_count
+    LIMIT $page_size
+    """
+    params['skip_count'] = skip_count
+    params['page_size'] = page_size
+    result = connect_graph.run(cql, **params)
+    for record in result:
+        properties = {
+            key: value for key, value in record['properties'].items()
+            if key not in ['input', 'code', 'output']
+        }
+        properties.setdefault("describe", None)
+
+        new_attr = {
+            'id': record['nodeid'],
+            'number': record['relationship_count']
+        }
+        properties.update(new_attr)
+        data.append(properties)
+
+    # 获取总量
+    total_query = f"MATCH (n:data_standard) WHERE {where_str} RETURN COUNT(n) AS total"
+    total_result = connect_graph.run(total_query, **params).evaluate()
+    return data, total_result
+
+
+# 数据标准图谱展示(血缘关系)父节点
+def standard_kinship_graph(nodeid):
+    """
+    生成数据标准的血缘关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        
+    Returns:
+        图谱数据
+    """
+    # 查询语句
+    cql = """
+    MATCH(da:data_standard)
+    WHERE id(da)=$nodeId
+    OPTIONAL MATCH(a:data_resource)-[:clean_resource]-(da)
+    OPTIONAL MATCH(b:data_model)-[:clean_model]-(da)
+    WITH 
+        collect({id:toString(id(a)),text:a.name,type:split(labels(a)[0],'_')[1]})+
+        collect({id:toString(id(b)),text:b.name,type:split(labels(b)[0],'_')[1]})+
+        collect({id:toString(id(da)),text:da.name,type:split(labels(da)[0],'_')[1]}) as nodes,da,
+        collect({from:toString(id(a)),to:toString(id(da)),text:'标准'})+
+        collect({from:toString(id(b)),to:toString(id(da)),text:'标准'})as lines
+    WITH  
+        toString(id(da)) as rootId,
+        apoc.coll.toSet(lines) as lines,
+        apoc.coll.toSet(nodes) as nodes
+    RETURN nodes,lines,rootId
+    """
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['rootId']
+        }
+    return res
+
+
+# 数据标准图谱展示(影响关系)下游
+def standard_impact_graph(nodeid):
+    """
+    生成数据标准的影响关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        
+    Returns:
+        图谱数据
+    """
+    # 查询语句
+    cql = """
+        MATCH(da:data_standard)
+        WHERE id(da)=$nodeId
+        OPTIONAL MATCH(da)-[:clean_model]-(m1:meta_node)
+        OPTIONAL MATCH(da)-[:clean_model]-(m2:meta_node)
+        WITH 
+            collect({id:toString(id(da)),text:da.name,type:split(labels(da)[0],'_')[1]})+
+            collect({id:toString(id(m1)),text:m1.name})+
+            collect({id:toString(id(m2)),text:m2.name})as nodes,da,
+            collect({from:toString(id(da)),to:toString(id(m1)),text:'标准清洗'})+
+            collect({from:toString(id(da)),to:toString(id(m2)),text:'标准清洗'})as lines
+        WITH  
+            toString(id(da)) as rootId,
+            apoc.coll.toSet(lines) as lines,
+            apoc.coll.toSet(nodes) as nodes
+        RETURN nodes,lines,rootId
+        """
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['rootId']
+        }
+    return res
+
+
+# 数据标准图谱展示(所有关系)
+def standard_all_graph(nodeid):
+    """
+    生成数据标准的所有关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        
+    Returns:
+        图谱数据
+    """
+    # 查询语句
+    cql = """
+    MATCH(da:data_standard)
+    WHERE id(da)=$nodeId
+    OPTIONAL MATCH(a:data_resource)-[:clean_resource]-(da)
+    OPTIONAL MATCH(b:data_model)-[:clean_model]-(da)
+    OPTIONAL MATCH(da)-[:clean_model]-(m1:meta_node)
+    OPTIONAL MATCH(da)-[:clean_model]-(m2:meta_node)
+    WITH 
+        collect({id:toString(id(a)),text:a.name,type:split(labels(a)[0],'_')[1]})+
+        collect({id:toString(id(b)),text:b.name,type:split(labels(b)[0],'_')[1]})+
+        collect({id:toString(id(da)),text:da.name,type:split(labels(da)[0],'_')[1]})+
+        collect({id:toString(id(m1)),text:m1.name})+
+        collect({id:toString(id(m2)),text:m2.name})as nodes,da,
+        collect({from:toString(id(a)),to:toString(id(da)),text:'标准'})+
+        collect({from:toString(id(b)),to:toString(id(da)),text:'标准'})+
+        collect({from:toString(id(da)),to:toString(id(m1)),text:'标准清洗'})+
+        collect({from:toString(id(da)),to:toString(id(m2)),text:'标准清洗'})as lines
+    WITH  
+        toString(id(da)) as rootId,
+        apoc.coll.toSet(lines) as lines,
+        apoc.coll.toSet(nodes) as nodes
+    RETURN nodes,lines,rootId
+    """
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['rootId']
+        }
+    return res
+
+
+# 数据标签列表展示
+def label_list(skip_count, page_size, en_name_filter=None,
+               name_filter=None, category_filter=None, group_filter=None):
+    """
+    获取数据标签列表
+    
+    Args:
+        skip_count: 跳过的记录数量
+        page_size: 每页记录数量
+        en_name_filter: 英文名称过滤条件
+        name_filter: 名称过滤条件
+        category_filter: 分类过滤条件
+        group_filter: 分组过滤条件
+        
+    Returns:
+        tuple: (数据列表, 总记录数)
+    """
+    data = []
+
+    # 构建查询条件
+    where_clause = []
+    params = {}
+    if name_filter:
+        where_clause.append("n.name CONTAINS $name_filter")
+        params['name_filter'] = name_filter
+    if en_name_filter:
+        where_clause.append("n.en_name CONTAINS $en_name_filter")
+        params['en_name_filter'] = en_name_filter
+    if category_filter:
+        where_clause.append("n.category CONTAINS $category_filter")
+        params['category_filter'] = category_filter
+    if group_filter:
+        where_clause.append(f"n.group CONTAINS $group_filter")
+        params['group_filter'] = group_filter
+    else:
+        where_clause.append("TRUE")
+
+    where_str = " AND ".join(where_clause)
+
+    # 构建完整的查询语句
+    cql = f"""
+    MATCH (n:data_label)
+    WHERE {where_str}
+    RETURN properties(n) as properties,n.time as time,id(n) as nodeid,
+           size((n)--()) as relationship_count
+    ORDER BY time desc
+    SKIP $skip_count
+    LIMIT $page_size
+    """
+    params['skip_count'] = skip_count
+    params['page_size'] = page_size
+    result = connect_graph.run(cql, **params)
+    for record in result:
+        properties = record['properties']
+        new_attr = {
+            'id': record['nodeid'],
+            'number': record['relationship_count']
+        }
+        if "describe" not in properties:
+            properties["describe"] = None
+        if "scope" not in properties:
+            properties["scope"] = None
+        properties.update(new_attr)
+        data.append(properties)
+
+    # 获取总量
+    total_query = f"MATCH (n:data_label) WHERE {where_str} RETURN COUNT(n) AS total"
+    total_result = connect_graph.run(total_query, **params).evaluate()
+    return data, total_result
+
+
+# 数据标签图谱展示
+def id_label_graph(id):
+    """
+    根据ID生成数据标签图谱
+    
+    Args:
+        id: 节点ID
+        
+    Returns:
+        图谱数据
+    """
+    query = """
+    MATCH (n:data_label)
+    WHERE id(n) = $nodeId
+    OPTIONAL MATCH (a)-[:label]-(n)
+    WITH 
+       collect({from: toString(id(a)), to: toString(id(n)), text: "标签"}) AS line1,
+       collect({id: toString(id(n)), text: n.name, type:"label"}) AS node1,
+       collect({id: toString(id(a)), text: a.name, type: split(labels(a)[0], '_')[1]}) AS node2, n
+    WITH apoc.coll.toSet(line1) AS lines,
+                 apoc.coll.toSet(node1 + node2) AS nodes,
+                 toString(id(n)) AS res
+    RETURN lines, nodes, res
+    """
+    data = connect_graph.run(query, nodeId=id)
+
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['res'],
+        }
+    return res
+
+
+# 数据标签图谱展示(血缘关系)父节点/(所有关系)
+def label_kinship_graph(nodeid):
+    """
+    生成数据标签的血缘关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        
+    Returns:
+        图谱数据
+    """
+    # 查询语句
+    cql = """
+    MATCH(la:data_label)
+    WHERE id(la)=$nodeId
+    OPTIONAL MATCH(a:data_resource)-[:label]-(la)
+    OPTIONAL MATCH(b:data_model)-[:label]-(la)
+    OPTIONAL MATCH(meta:meta_node)-[:label]-(la)
+    OPTIONAL MATCH(d:data_standard)-[:label]-(la)
+    OPTIONAL MATCH(e:data_metric)-[:label]-(la)
+    WITH 
+        collect({id:toString(id(a)),text:a.name,type:split(labels(a)[0],'_')[1]})+
+        collect({id:toString(id(b)),text:b.name,type:split(labels(b)[0],'_')[1]})+
+        collect({id:toString(id(d)),text:d.name,type:split(labels(d)[0],'_')[1]})+
+        collect({id:toString(id(e)),text:e.name,type:split(labels(e)[0],'_')[1]})+
+        collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
+        collect({id:toString(id(meta)),text:meta.name}) as nodes,la,
+        collect({from:toString(id(a)),to:toString(id(la)),text:'标签'})+
+        collect({from:toString(id(b)),to:toString(id(la)),text:'标签'})+
+        collect({from:toString(id(meta)),to:toString(id(la)),text:'标签'})+
+        collect({from:toString(id(d)),to:toString(id(la)),text:'标签'})+
+        collect({from:toString(id(e)),to:toString(id(la)),text:'标签'})as lines
+    WITH  
+        toString(id(la)) as rootId,
+        apoc.coll.toSet(lines) as lines,
+        apoc.coll.toSet(nodes) as nodes
+    RETURN nodes,lines,rootId
+    """
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['rootId']
+        }
+    return res
+
+
+# 数据标签图谱展示(影响关系)下游
+def label_impact_graph(nodeid):
+    """
+    生成数据标签的影响关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        
+    Returns:
+        图谱数据
+    """
+    # 查询语句
+    cql = """
+        MATCH(n:data_label)
+        WHERE id(n)=$nodeId
+        RETURN {id:toString(id(n)),text:(n.name),type:"label"} AS nodes,
+               toString(id(n)) as rootId
+        """
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": item['nodes'],
+            "rootId": item['rootId'],
+            "lines": []
+        }
+    return res
+
+
+# 数据标签按照提交内容查询相似分组,并且返回
+def dynamic_label_list(name_filter=None):
+    """
+    根据内容查询相似的数据标签分组
+    
+    Args:
+        name_filter: 内容过滤条件
+        
+    Returns:
+        标签分组列表
+    """
+    # 构建完整的查询语句
+    cql = f"""
+    MATCH (n:data_label)
+    WITH n, apoc.text.levenshteinSimilarity(n.group, "{name_filter}") AS similarity
+    WHERE similarity > 0.1 // 设置相似度阈值
+    RETURN DISTINCT n.group as name,id(n) as nodeid
+    """
+
+    result = connect_graph.run(cql).data()
+    data = []
+    for record in result:
+        data.append({
+            "name": record['name'],
+            "id": record['nodeid']
+        })
+
+    return data 

+ 116 - 0
app/core/data_metric/README.md

@@ -0,0 +1,116 @@
+# 核心数据指标模块
+
+## 简介
+
+核心数据指标模块(`app.core.data_metric`)包含与数据指标相关的所有核心业务逻辑,负责实现数据指标的管理、查询、图谱生成等功能的核心算法和数据处理逻辑。该模块不直接处理HTTP请求,而是由API层调用,实现业务与接口的分离。
+
+## 主要功能
+
+1. **数据指标管理**:创建、更新、查询数据指标的核心逻辑
+2. **数据指标列表**:实现数据指标列表的查询和过滤功能
+3. **图谱生成**:计算和生成不同类型的数据指标关系图谱,包括:
+   - 血缘关系图谱(Kinship Graph)
+   - 影响关系图谱(Impact Graph)
+   - 完整关系图谱(All Relationships Graph)
+4. **关系处理**:管理数据指标与其他实体(数据模型、其他指标、元数据、标签)之间的关系
+
+## 核心函数列表
+
+| 函数名 | 描述 |
+|-------|------|
+| `metric_list()` | 获取数据指标列表,支持多种过滤条件 |
+| `handle_metric_relation()` | 处理数据指标血缘关系 |
+| `id_mertic_graph()` | 生成数据指标关系图谱 |
+| `handle_data_metric()` | 创建或更新数据指标节点 |
+| `handle_meta_data_metric()` | 处理数据指标与其他节点的关系 |
+| `handle_id_metric()` | 获取数据指标详情 |
+| `metric_kinship_graph()` | 生成数据指标血缘关系图谱 |
+| `metric_impact_graph()` | 生成数据指标影响关系图谱 |
+| `metric_all_graph()` | 生成数据指标所有关系图谱 |
+| `data_metric_edit()` | 编辑数据指标 |
+
+## 数据模型
+
+### 数据指标(data_metric)
+
+数据指标是对数据的测量和计算规则,主要属性包括:
+
+- **name**: 指标名称(中文)
+- **en_name**: 指标英文名称
+- **category**: 指标分类
+- **describe**: 指标描述
+- **time**: 创建/更新时间
+- **tag**: 关联的标签ID
+- **code**: 计算代码
+- **id_list**: 关联的数据模型或其他指标ID列表
+
+### 指标关系
+
+数据指标与其他实体之间的主要关系类型:
+
+- **origin**: 指标来源(指标→模型或指标→指标)
+- **connection**: 指标与元数据的连接
+- **label**: 指标与标签的连接
+- **child**: 指标的下级关系
+
+## 使用示例
+
+### 1. 获取数据指标列表
+
+```python
+from app.core.data_metric.metric_interface import metric_list
+
+# 获取第一页,每页10条数据
+skip_count = 0
+page_size = 10
+data, total = metric_list(skip_count, page_size, name_filter="用户")
+
+print(f"共找到 {total} 条记录")
+for item in data:
+    print(f"指标名称: {item['name']}, 分类: {item['category']}")
+```
+
+### 2. 生成指标关系图谱
+
+```python
+from app.core.data_metric.metric_interface import metric_all_graph
+
+# 生成某个指标的所有关系图谱,包含元数据
+graph_data = metric_all_graph(nodeid=123, meta=True)
+
+# 输出图谱数据
+print(f"节点数量: {len(graph_data['nodes'])}")
+print(f"连线数量: {len(graph_data['lines'])}")
+```
+
+## 依赖项
+
+本模块主要依赖以下组件:
+
+1. **py2neo**: 用于Neo4j图数据库操作
+2. **routes.graph_routes**: 提供图数据库连接
+3. **DataOps_function.common_functions**: 提供通用操作函数
+4. **DataOps_function.meta_data_function**: 提供元数据相关功能
+5. **services.package_function**: 提供节点和关系操作功能
+
+## 错误处理
+
+本模块使用Python标准异常处理机制。大多数函数在遇到错误时会抛出相应的异常,由调用者(通常是API层)捕获和处理。模块还使用logging模块记录关键操作和错误,日志文件为`metric_interface.log`。
+
+## 性能考虑
+
+数据指标图谱查询可能涉及大量的节点和关系,查询性能可能受到影响。为优化性能,本模块:
+
+1. 使用MATCH查询时,尽量添加适当的WHERE条件减少结果集
+2. 使用SKIP和LIMIT进行分页查询
+3. 合理使用OPTIONAL MATCH减少查询失败
+4. 对图谱节点和关系进行过滤,只返回有效的数据
+
+## 代码结构
+
+- **__init__.py**: 导入模块接口
+- **metric_interface.py**: 包含所有核心功能实现
+
+## 维护者
+
+数据平台开发团队 

+ 2 - 0
app/core/data_metric/__init__.py

@@ -0,0 +1,2 @@
+# 数据指标业务逻辑模块
+from app.core.data_metric import metric_interface 

+ 591 - 0
app/core/data_metric/metric_interface.py

@@ -0,0 +1,591 @@
+"""
+数据指标核心业务逻辑模块
+
+该模块提供了数据指标的各种核心功能,包括:
+- 指标列表查询和过滤
+- 指标血缘关系处理
+- 指标图谱生成(血缘关系、影响关系、所有关系)
+- 指标数据的创建、更新和查询
+"""
+
+import json
+import logging
+from py2neo import Relationship
+from app.core.common import delete_relationships, update_or_create_node, get_node_by_id_no_label
+from app.core.meta_data import get_formatted_time
+from app.core.graph.graph_operations import connect_graph
+from app.core.data_resource.resource import get_node_by_id
+from app.services.package_function import get_node, create_or_get_node, relationship_exists
+
+# 配置日志
+logging.basicConfig(filename='metric_interface.log', level=logging.INFO)
+
+def metric_list(skip_count, page_size, en_name_filter=None,
+                name_filter=None, category_filter=None, time_filter=None, tag_filter=None):
+    """
+    获取数据指标列表,支持多种过滤条件
+    
+    Args:
+        skip_count: 分页跳过的记录数
+        page_size: 每页记录数
+        en_name_filter: 英文名称过滤
+        name_filter: 名称过滤
+        category_filter: 类别过滤
+        time_filter: 时间过滤
+        tag_filter: 标签过滤
+        
+    Returns:
+        tuple: (数据列表, 总记录数)
+    """
+    data = []
+
+    # 构建查询条件
+    where_clause = []
+    params = {}
+    if name_filter:
+        where_clause.append("n.name CONTAINS $name_filter")
+        params['name_filter'] = name_filter
+    if en_name_filter:
+        where_clause.append("n.en_name CONTAINS $en_name_filter")
+        params['en_name_filter'] = en_name_filter
+    if category_filter:
+        where_clause.append("n.category CONTAINS $category_filter")
+        params['category_filter'] = category_filter
+    if tag_filter:
+        where_clause.append("n.tag = $nodeId")
+        params['nodeId'] = tag_filter
+    if time_filter:
+        where_clause.append("n.time CONTAINS $time_filter")
+        params['time_filter'] = time_filter
+    else:
+        where_clause.append("TRUE")
+
+    where_str = " AND ".join(where_clause)
+
+    # 构建完整的查询语句
+    cql = f"""
+    MATCH (n:data_metric)
+    WHERE {where_str}
+    OPTIONAL MATCH (la:label) where id(la) = n.tag 
+    OPTIONAL MATCH (n:data_metric)-[:origin]-(m:data_model)
+    OPTIONAL MATCH (n:data_metric)-[:origin]-(mr:data_metric)
+    //数据标签
+    WITH 
+    CASE 
+        WHEN m IS NOT NULL THEN collect(m.name)
+        WHEN mr IS NOT NULL THEN collect(mr.name)
+        ELSE []
+    END AS data_model,properties(n) as properties,
+           n.time as time,id(n) as nodeid,{{id:id(la),name:la.name}} as tag
+    return properties,time,nodeid,data_model,tag
+    ORDER BY time desc
+    SKIP $skip_count
+    LIMIT $page_size
+    """
+    params['skip_count'] = skip_count
+    params['page_size'] = page_size
+    result = connect_graph.run(cql, **params)
+    for record in result:
+        properties = record['properties']
+        properties['data_model'] = record['data_model']
+        properties['tag'] = record['tag']
+        new_attr = {
+            'id': record['nodeid']
+        }
+        if "id_list" in properties:
+            properties['id_list'] = json.loads(properties['id_list'])
+        if "describe" not in properties:
+            properties["describe"] = None
+
+        properties.update(new_attr)
+        data.append(properties)
+
+    # 获取总量
+    total_query = f"MATCH (n:data_metric) " \
+                  f"WHERE {where_str} RETURN COUNT(n) AS total"
+    total_result = connect_graph.run(total_query, **params).evaluate()
+    return data, total_result
+
+
+def handle_metric_relation(model_ids):
+    """
+    处理数据指标血缘关系
+    
+    Args:
+        model_ids: 数据模型ID
+        
+    Returns:
+        list: 血缘关系数据
+    """
+    query = """
+            MATCH (search:data_model)-[:connection]->(common_node:meta_node)<-[:connection]-(connect:data_model)
+            WHERE id(search) = $model_Ids
+            WITH search, connect, common_node
+            MATCH (search)-[:connection]->(search_node:meta_node)
+            WITH search, connect, common_node, collect(DISTINCT id(search_node)) AS search_nodes
+            MATCH (connect)-[:connection]->(connect_node:meta_node)
+            WITH search, connect, common_node, search_nodes, collect(DISTINCT id(connect_node)) AS connect_nodes
+            WITH search, connect, search_nodes, connect_nodes, collect(DISTINCT id(common_node)) AS common_nodes
+
+            // 剔除 search_nodes 和 connect_nodes 中包含在 common_nodes 中的内容
+            WITH search, connect, common_nodes,
+                 [node IN search_nodes WHERE NOT node IN common_nodes] AS filtered_search_nodes,
+                 [node IN connect_nodes WHERE NOT node IN common_nodes] AS filtered_connect_nodes
+
+            RETURN  id(connect) as blood_model, common_nodes, 
+            filtered_search_nodes as origin_nodes, filtered_connect_nodes as blood_nodes
+            """
+
+    result = connect_graph.run(query, model_Ids=model_ids)
+    return result.data()
+
+
+def id_mertic_graph(id):
+    """
+    生成数据指标图谱
+    
+    Args:
+        id: 指标ID
+        
+    Returns:
+        dict: 图谱数据,包含节点、连线和根节点ID
+    """
+    query = """
+    MATCH (n:data_metric)
+    WHERE id(n) = $nodeId
+    WITH n, apoc.convert.fromJsonList(n.model_id) AS id_lists
+    UNWIND id_lists AS id_list
+    WITH n, id_list.id AS model_id, id_list.metaData AS meta_ids
+    OPTIONAL MATCH (t:data_label) WHERE id(t) = n.tag
+    UNWIND meta_ids AS meta_id
+    MATCH (d:data_model) WHERE id(d) = model_id
+    MATCH (a:meta_node) WHERE id(a) = meta_id
+    with 
+       collect({from:toString(id(n)),to:toString(id(d)),text:"来源"})+
+       collect({from:toString(id(n)),to:toString(id(a)),text:"包含"})+
+       collect({from:toString(id(d)),to:toString(id(a)),text:"包含"})+
+       collect({from:toString(id(n)),to:toString(id(t)),text:"标签"})+
+       collect({from:toString(id(d)),to:toString(id(t)),text:"标签"})AS line,
+       collect({id: toString(id(n)), text: n.name, type: "metric"}) +
+       collect({id: toString(id(d)), text: d.name, type: "model"}) +
+       collect({id: toString(id(t)), text: t.name, type: "label"}) +
+       collect({id: toString(id(a)), text: a.name}) AS node,n
+    WITH apoc.coll.toSet(line) AS lines,
+                 apoc.coll.toSet(node) AS nodes,
+                 toString(id(n)) as res
+    RETURN lines,nodes,res
+    """
+    data = connect_graph.run(query, nodeId=id)
+
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['res'],
+        }
+
+    logging.info(res)  # 记录 'res' 变量
+    return res
+
+
+def handle_data_metric(metric_name, result_list, receiver):
+    """
+    创建一个数据指标节点
+    
+    Args:
+        metric_name: 指标名称
+        result_list: 结果列表
+        receiver: 请求参数
+        
+    Returns:
+        tuple: (指标节点ID, ID列表)
+    """
+    data_metric_en = result_list[0]
+    id_list = receiver['id_list']
+    receiver['id_list'] = json.dumps(receiver['id_list'], ensure_ascii=False)
+
+    receiver.update({
+        'time': get_formatted_time(),
+        'en_name': data_metric_en
+    })
+
+    data_metric_node = get_node('data_metric', name=metric_name) or create_or_get_node('data_metric', **receiver)
+
+    child_list = receiver['childrenId']
+    for child_id in child_list:
+        child = get_node_by_id_no_label(child_id)
+        # 建立关系:当前节点的childrenId指向,以及关系child
+        if child and not relationship_exists(data_metric_node, 'child', child):
+            connect_graph.create(Relationship(data_metric_node, 'child', child))
+
+    if receiver.get('tag'):
+        tag = get_node_by_id('data_label', receiver['tag'])
+        if tag and not relationship_exists(data_metric_node, 'label', tag):
+            connect_graph.create(Relationship(data_metric_node, 'label', tag))
+
+    return data_metric_node.identity, id_list
+
+
+def handle_meta_data_metric(data_metric_node_id, id_list):
+    """
+    处理数据指标与其他节点之间的关系
+    
+    Args:
+        data_metric_node_id: 数据指标节点ID
+        id_list: ID列表
+    """
+    # 提取 model_id 和 metric_id
+    model_ids = [item['id'] for item in id_list if item['type'] == 'model']
+    metric_ids = [item['id'] for item in id_list if item['type'] == 'metric']
+    # 创建与data_model的关系
+    if model_ids:
+        cql_resource = """
+            MATCH (n:data_metric)
+            WHERE id(n) = $data_metric_node_id
+            UNWIND $model_ids AS model_id
+            MATCH (d:data_model)
+            WHERE id(d) = model_id
+            MERGE (n)-[:origin]->(d)
+        """
+        connect_graph.run(cql_resource, data_metric_node_id=data_metric_node_id, model_ids=model_ids)
+    # 创建与data_metric的关系
+    if metric_ids:
+        cql_resource = """
+            MATCH (n:data_metric)
+            WHERE id(n) = $data_metric_node_id
+            UNWIND $metric_ids AS metric_id
+            MATCH (d:data_metric)
+            WHERE id(d) = metric_id
+            MERGE (n)-[:origin]->(d)
+        """
+        connect_graph.run(cql_resource, data_metric_node_id=data_metric_node_id, metric_ids=metric_ids)
+
+    # 创建与元数据的关系
+    for record in id_list:
+        if "metaData" in record and record['metaData'] != []:
+            cql_meta = """
+                        MATCH (n:data_metric)
+                        WHERE id(n) = $data_metric_node_id
+                        UNWIND $meta_ids AS meta_id
+                        MATCH (d:meta_node)
+                        WHERE id(d) = meta_id
+                        MERGE (n)-[:connection]->(d)
+                    """
+            connect_graph.run(cql_meta, data_metric_node_id=data_metric_node_id, meta_ids=record['metaData'])
+
+
+def handle_id_metric(id):
+    """
+    获取数据指标详情
+    
+    Args:
+        id: 指标ID
+        
+    Returns:
+        dict: 指标详情数据
+    """
+    query = """
+    MATCH (n:data_metric)
+    WHERE id(n) = $nodeId
+    WITH apoc.convert.fromJsonList(n.id_list) AS info, n
+    UNWIND info AS item
+    WITH n, item.id AS model_or_metric_id, item.metaData AS meta_ids, item.type AS type
+    
+    // 数据模型或者数据指标
+    OPTIONAL MATCH (n)-[:origin]->(m1:data_model)
+    WHERE type = 'model' AND id(m1) = model_or_metric_id
+    WITH n, model_or_metric_id, meta_ids, type, m1
+    OPTIONAL MATCH (n)-[:origin]->(m2:data_metric)
+    WHERE type = 'metric' AND id(m2) = model_or_metric_id
+    WITH n, model_or_metric_id, meta_ids, type, m1, m2
+    // 元数据
+    OPTIONAL MATCH (n)-[:connection]-(meta:meta_node)
+    // 数据标签
+    OPTIONAL MATCH (n)-[:label]-(la:data_label)
+    OPTIONAL MATCH (parent)-[:child]-(n)
+    WITH properties(n) AS properties,collect(DISTINCT id(meta)) AS meta_list,parent,
+        {id: id(la), name: la.name} AS tag,
+        CASE 
+            WHEN type = 'model' THEN m1
+            WHEN type = 'metric' THEN m2
+            ELSE NULL
+        END AS m
+    WITH {model_name: m.name, model_id: id(m), meta: meta_list} AS result, properties,
+         tag,{id:id(parent),name:parent.name} as parentId
+    RETURN collect(result) AS id_list, properties, tag,collect(parentId)as parentId
+    """
+    data_ = connect_graph.run(query, nodeId=id).data()
+
+    if not data_:
+        return {"data_metric": {}}
+
+    record = data_[0]
+    properties = record['properties']
+    properties['id_list'] = record['id_list']
+    properties['tag'] = record['tag']
+    properties['parentId'] = record['parentId']
+
+    # 移除不需要的属性
+    properties.pop('model_id', None)
+
+    # 添加缺失的属性
+    for key in ["describe", "tag", "code"]:
+        if key not in properties:
+            properties[key] = None
+
+    response_data = {"data_metric": properties}
+
+    return response_data
+
+
+def metric_kinship_graph(nodeid, meta):
+    """
+    生成数据指标血缘关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        meta: 是否包含元数据
+        
+    Returns:
+        dict: 图谱数据
+    """
+    # 查询语句 - 使用简单的路径匹配实现逐层查找
+    cql = """
+    // 使用可变长度路径查找所有相关节点
+    MATCH path = (start)-[:origin*0..]->(target)
+    WHERE id(start) = $nodeId
+    
+    // 提取路径中的所有节点
+    WITH collect(DISTINCT path) as paths
+    UNWIND paths as p
+    UNWIND nodes(p) as n
+    
+    // 收集所有节点信息
+    WITH collect(DISTINCT {
+        id: toString(id(n)),
+        text: n.name+'-测试',
+        type: CASE 
+            WHEN 'data_metric' IN labels(n) THEN 'metric'
+            WHEN 'data_model' IN labels(n) THEN 'model'
+            ELSE split(labels(n)[0],'_')[1]
+        END
+    }) as nodes, paths
+    
+    // 从路径中提取关系
+    UNWIND paths as p
+    UNWIND relationships(p) as r
+    WITH nodes, collect(DISTINCT {
+        from: toString(id(startNode(r))),
+        to: toString(id(endNode(r))),
+        text: '来源'
+    }) as relations
+    
+    // 返回结果
+    RETURN nodes,
+           [rel in relations WHERE rel.from IS NOT NULL AND rel.to IS NOT NULL] as lines,
+           toString($nodeId) as rootId
+    """
+    
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": str(nodeid)
+        }
+    logging.info(res)  # 记录 'res' 变量
+    return res
+
+
+def metric_impact_graph(nodeid, meta):
+    """
+    生成数据指标影响关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        meta: 是否包含元数据
+        
+    Returns:
+        dict: 图谱数据
+    """
+    if meta:
+        # 查询语句
+        cql = """
+        MATCH(mc2:data_metric)
+        WHERE id(mc2)=$nodeId
+        OPTIONAL MATCH(mc4:data_metric)-[:origin]-(mc2)
+        OPTIONAL MATCH(mc2)-[:connection]-(meta:meta_node)
+        OPTIONAL MATCH(mc2)-[:label]-(la:data_label)
+        OPTIONAL MATCH(mc2)-[:child]-(child)
+        WITH 
+            collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
+            collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
+            collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
+            collect({id:toString(id(meta)),text:meta.name})+
+            collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
+            collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
+            collect({from:toString(id(mc2)),to:toString(id(meta)),text:'包含'})+
+            collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
+            collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
+        WITH  
+           toString(id(mc2)) as rootId,
+           apoc.coll.toSet(lines) as lines,
+           apoc.coll.toSet(nodes) as nodes
+        RETURN nodes,lines,rootId
+        """
+    else:
+        # 查询语句
+        cql = """
+            MATCH(mc2:data_metric)
+            WHERE id(mc2)=$nodeId
+            OPTIONAL MATCH(mc4:data_metric)-[:origin]-(mc2)
+            OPTIONAL MATCH(mc2)-[:label]-(la:data_label)
+            OPTIONAL MATCH(mc2)-[:child]-(child)
+            WITH 
+                collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
+                collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
+                collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
+                collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
+                collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
+                collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
+                collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
+            WITH  
+               toString(id(mc2)) as rootId,
+               apoc.coll.toSet(lines) as lines,
+               apoc.coll.toSet(nodes) as nodes
+            RETURN nodes,lines,rootId
+            """
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['rootId']
+        }
+    logging.info(res)  # 记录 'res' 变量
+    return res
+
+
+def metric_all_graph(nodeid, meta):
+    """
+    生成数据指标所有关系图谱
+    
+    Args:
+        nodeid: 节点ID
+        meta: 是否包含元数据
+        
+    Returns:
+        dict: 图谱数据
+    """
+    if meta:
+        # 查询语句
+        cql = """
+        MATCH(mc2:data_metric)
+        WHERE id(mc2)=$nodeId
+        OPTIONAL MATCH(mc2)-[:origin]-(mo:data_model)
+        OPTIONAL MATCH(mc2)-[:origin]-(mc1:data_metric)
+        OPTIONAL MATCH(mc4:data_metric)-[:origin]-(mc2)
+        OPTIONAL MATCH(mc2)-[:connection]-(meta:meta_node)
+        OPTIONAL MATCH(mc2)-[:label]-(la:data_label)
+        OPTIONAL MATCH(mc2)-[:child]-(child)
+        WITH 
+            collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
+            collect({id:toString(id(mo)),text:mo.name,type:split(labels(mo)[0],'_')[1]})+
+            collect({id:toString(id(mc1)),text:mc1.name,type:split(labels(mc1)[0],'_')[1]})+
+            collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
+            collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
+            collect({id:toString(id(meta)),text:meta.name})+
+            collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
+            collect({from:toString(id(mc2)),to:toString(id(mo)),text:'来源'})+
+            collect({from:toString(id(mc2)),to:toString(id(mc1)),text:'来源'})+
+            collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
+            collect({from:toString(id(mc2)),to:toString(id(meta)),text:'包含'})+
+            collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
+            collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
+        WITH  
+            toString(id(mc2)) as rootId,
+            apoc.coll.toSet(lines) as lines,
+            apoc.coll.toSet(nodes) as nodes
+        RETURN nodes,lines,rootId
+        """
+    else:
+        # 查询语句
+        cql = """
+            MATCH(mc2:data_metric)
+            WHERE id(mc2)=$nodeId
+            OPTIONAL MATCH(mc2)-[:origin]-(mo:data_model)
+            OPTIONAL MATCH(mc2)-[:origin]-(mc1:data_metric)
+            OPTIONAL MATCH(mc4:data_metric)-[:origin]-(mc2)
+            OPTIONAL MATCH(mc2)-[:label]-(la:data_label)
+            OPTIONAL MATCH(mc2)-[:child]-(child)
+            WITH 
+                collect({id:toString(id(mc2)),text:mc2.name,type:split(labels(mc2)[0],'_')[1]})+
+                collect({id:toString(id(mo)),text:mo.name,type:split(labels(mo)[0],'_')[1]})+
+                collect({id:toString(id(mc1)),text:mc1.name,type:split(labels(mc1)[0],'_')[1]})+
+                collect({id:toString(id(mc4)),text:mc4.name,type:split(labels(mc4)[0],'_')[1]})+
+                collect({id:toString(id(la)),text:la.name,type:split(labels(la)[0],'_')[1]})+
+                collect({id:toString(id(child)),text:child.name,type:split(labels(child)[0],'_')[1]})as nodes,mc2,
+                collect({from:toString(id(mc2)),to:toString(id(mo)),text:'来源'})+
+                collect({from:toString(id(mc2)),to:toString(id(mc1)),text:'来源'})+
+                collect({from:toString(id(mc2)),to:toString(id(mc4)),text:'包含'})+
+                collect({from:toString(id(la)),to:toString(id(mc2)),text:'标记'})+
+                collect({from:toString(id(mc2)),to:toString(id(child)),text:'下级'})as lines
+            WITH  
+                toString(id(mc2)) as rootId,
+                apoc.coll.toSet(lines) as lines,
+                apoc.coll.toSet(nodes) as nodes
+            RETURN nodes,lines,rootId
+            """
+    data = connect_graph.run(cql, nodeId=nodeid)
+    res = {}
+    for item in data:
+        res = {
+            "nodes": [record for record in item['nodes'] if record['id']],
+            "lines": [record for record in item['lines'] if record['from'] and record['to']],
+            "rootId": item['rootId']
+        }
+    logging.info(res)  # 记录 'res' 变量
+    return res
+
+
+def data_metric_edit(data):
+    """
+    编辑数据指标
+    
+    Args:
+        data: 数据指标数据
+    """
+    node_a = get_node_by_id('data_metric', data["id"])
+    if node_a:
+        delete_relationships(data["id"])
+
+    # 更新或创建数据指标节点的属性
+    for key, value in data.items():
+        if value is not None and key != "model_selected":
+            node_a[key] = value
+    connect_graph.push(node_a)
+
+    child_list = data['childrenId']
+    for child_id in child_list:
+        child = get_node_by_id_no_label(child_id)
+        # 建立关系:当前节点的childrenId指向,以及关系child
+        if child and not relationship_exists(node_a, 'child', child):
+            connect_graph.create(Relationship(node_a, 'child', child))
+
+    # 处理数据标签及其关系
+    if data["tag"]:
+        tag_node = get_node_by_id('data_label', data["tag"])
+        relationship_label = Relationship(node_a, "label", tag_node)
+        connect_graph.merge(relationship_label)
+
+    # 处理元数据节点及其关系(此处只调整关系,不修改对应属性)
+    for record in data['model_selected']:
+        for parsed_item in record["meta"]:
+            metadata_node = update_or_create_node(
+                parsed_item["id"]
+            )
+            relationship_connection = Relationship(node_a, "connection", metadata_node)
+            connect_graph.merge(relationship_connection) 

+ 99 - 0
app/core/data_model/README.md

@@ -0,0 +1,99 @@
+# 数据模型核心业务逻辑模块
+
+本模块包含了数据模型相关的所有核心业务逻辑函数,处理数据模型的创建、查询、更新、删除以及与其他数据对象的关系管理。
+
+## 主要功能
+
+1. **数据模型基础操作**
+   - 创建数据模型节点
+   - 更新数据模型属性
+   - 删除数据模型节点
+   - 查询数据模型详情
+
+2. **数据模型关系管理**
+   - 数据模型与元数据(meta_node)的关系处理
+   - 数据模型与数据资源(data_resource)的关系处理
+   - 数据模型与其他数据模型的关系处理
+   - 数据模型与标签(data_label)的关系处理
+
+3. **数据模型层级管理**
+   - 计算并设置数据模型层级(level)属性
+   - 处理父子关系(child)
+
+4. **数据模型血缘分析**
+   - 数据模型血缘关系分析
+   - 数据资源血缘关系分析
+   - 生成血缘图谱数据
+
+5. **图谱生成**
+   - 生成血缘关系图谱(Kinship)
+   - 生成影响关系图谱(Impact)
+   - 生成全量关系图谱(All)
+
+## 核心函数列表
+
+- `calculate_model_level`:计算数据模型层级
+- `handle_model_relation`:处理数据模型血缘关系
+- `handle_data_model`:创建数据模型节点
+- `resource_handle_meta_data_model`:处理数据模型与数据资源的关系
+- `model_handle_meta_data_model`:处理数据模型与其他数据模型的关系
+- `handle_no_meta_data_model`:处理从DDL中选取的没有元数据的数据模型
+- `handle_id_model`:获取数据模型详情
+- `model_list`:获取数据模型列表
+- `model_resource_list`:获取有血缘关系的数据资源列表
+- `model_kinship_graph`:生成数据模型血缘图谱
+- `model_impact_graph`:生成数据模型影响图谱
+- `model_all_graph`:生成数据模型全量图谱
+- `data_model_edit`:更新数据模型
+
+## 数据模型
+
+数据模型(data_model)节点具有以下主要属性:
+- `name`:数据模型名称(中文)
+- `en_name`:数据模型英文名称
+- `category`:数据模型分类
+- `description`:数据模型描述
+- `time`:创建/更新时间
+- `level`:数据模型层级
+- `id_list`:相关ID列表(JSON序列化字符串)
+
+## 关系类型
+
+数据模型相关的主要关系类型:
+- `component`:数据模型与元数据的组成关系
+- `resource`:数据模型与数据资源的关联关系
+- `use`:数据模型之间的使用关系
+- `child`:数据模型的父子关系
+- `label`:数据模型与标签的分类关系
+
+## 依赖关系
+
+- 依赖 `app.services.package_function` 提供图数据库节点操作功能
+- 依赖 `app.routes.graph_routes` 提供图数据库连接
+- 依赖 `app.services.neo4j_driver` 提供Neo4j会话管理
+- 依赖 `app.core.meta_data` 提供元数据处理功能
+- 依赖 `app.utils.common_functions` 提供通用功能函数
+- 依赖 `app.core.data_resource` 提供数据资源处理功能
+
+## 调用示例
+
+```python
+# 创建数据模型示例
+from app.core.data_model.model import handle_data_model, calculate_model_level
+
+# 创建数据模型
+model_name = "客户信息模型"
+result_list = ["customer_info_model"]
+result = json.dumps([{"resource_id": 123, "metaData": [{"id": 456}]}])
+receiver = {
+    "category": "业务模型",
+    "description": "包含客户基本信息的数据模型",
+    "tag": 789,
+    "childrenId": []
+}
+
+model_id, model_node = handle_data_model(model_name, result_list, result, receiver)
+
+# 计算模型层级
+calculate_model_level(model_id)
+``` 

+ 2 - 0
app/core/data_model/__init__.py

@@ -0,0 +1,2 @@
+# 数据模型业务逻辑模块 
+from app.core.data_model import model 

+ 787 - 0
app/core/data_model/model.py

@@ -0,0 +1,787 @@
+"""
+数据模型核心业务逻辑模块
+
+本模块包含了数据模型相关的所有核心业务逻辑函数,包括:
+- 数据模型的创建、更新、删除
+- 数据模型与数据资源、元数据之间的关系处理
+- 数据模型血缘关系管理
+- 数据模型图谱生成
+- 数据模型层级计算等功能
+"""
+
+import math
+import threading
+from concurrent.futures import ThreadPoolExecutor
+import pandas as pd
+from py2neo import Relationship
+import logging
+import json
+
+from app.services.package_function import create_or_get_node, relationship_exists, get_node
+from app.core.graph.graph_operations import connect_graph
+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
+
+
+# 根据child关系计算数据模型当前的level自动保存
+def calculate_model_level(id):
+    """
+    根据child关系计算数据模型当前的level并自动保存
+    
+    Args:
+        id: 数据模型的节点ID
+        
+    Returns:
+        None
+    """
+    cql = """
+    MATCH (start_node:data_model {id: $nodeId})
+    CALL {
+        WITH start_node
+        OPTIONAL MATCH path = (start_node)-[:child*]->(end_node)
+        RETURN length(path) AS level
+    }
+    WITH coalesce(max(level), 0) AS max_level
+    RETURN max_level
+    """
+    data = connect_graph.run(cql, nodeId=id).evaluate()
+    # 更新level属性
+    update_query = """
+        MATCH (n:data_model {id: $nodeId})
+        SET n.level = $level
+        RETURN n
+        """
+    connect_graph.run(update_query, nodeId=id, level=data)
+
+
+# 处理数据模型血缘关系
+def handle_model_relation(resource_ids):
+    """
+    处理数据模型血缘关系
+    
+    Args:
+        resource_ids: 数据资源ID
+        
+    Returns:
+        血缘关系数据
+    """
+    query = """
+            MATCH (search:data_resource)-[:connection]->(common_node:meta_node)<-[:connection]-(connect:data_resource)
+            WHERE id(search) = $resource_Ids
+            WITH search, connect, common_node
+            MATCH (search)-[:connection]->(search_node:meta_node)
+            WITH search, connect, common_node, collect(DISTINCT id(search_node)) AS search_nodes
+            MATCH (connect)-[:connection]->(connect_node:meta_node)
+            WITH search, connect, common_node, search_nodes, collect(DISTINCT id(connect_node)) AS connect_nodes
+            WITH search, connect, search_nodes, connect_nodes, collect(DISTINCT id(common_node)) AS common_nodes
+
+            // 剔除 search_nodes 和 connect_nodes 中包含在 common_nodes 中的内容
+            WITH search, connect, common_nodes,
+                 [node IN search_nodes WHERE NOT node IN common_nodes] AS filtered_search_nodes,
+                 [node IN connect_nodes WHERE NOT node IN common_nodes] AS filtered_connect_nodes
+
+            RETURN  id(connect) as blood_resources, common_nodes, 
+            filtered_search_nodes as origin_nodes, filtered_connect_nodes as blood_nodes
+            """
+
+    result = connect_graph.run(query, resource_Ids=resource_ids)
+    return result.data()
+
+
+# 创建一个数据模型节点
+def handle_data_model(data_model, result_list, result, receiver):
+    """
+    创建一个数据模型节点
+    
+    Args:
+        data_model: 数据模型名称
+        result_list: 数据模型英文名列表
+        result: 序列化的ID列表
+        receiver: 接收到的请求参数
+        
+    Returns:
+        tuple: (id, data_model_node)
+    """
+    # 添加数据资源 血缘关系的字段 blood_resource
+    data_model_en = result_list[0]
+    receiver['id_list'] = result
+    add_attribute = {
+        'time': get_formatted_time(),
+        'en_name': data_model_en
+    }
+    receiver.update(add_attribute)
+    data_model_node = get_node('data_model', name=data_model) or create_or_get_node('data_model', **receiver)
+
+    child_list = receiver['childrenId']
+    for child_id in child_list:
+        child = get_node_by_id_no_label(child_id)
+        # 建立关系:当前节点的childrenId指向,以及关系child
+        res = relationship_exists(data_model_node, 'child', child)
+        if child and not res:
+            connect_graph.create(Relationship(data_model_node, 'child', child))
+
+    # 根据传入参数id,和数据标签建立关系
+    if receiver['tag']:
+        # 使用 Cypher 查询通过 id 查找节点
+        tag = get_node_by_id('data_label', receiver['tag'])
+        if tag and not relationship_exists(data_model_node, 'label', tag):
+            connection = Relationship(data_model_node, 'label', tag)
+            connect_graph.create(connection)
+
+    id = data_model_node.identity
+    return id, data_model_node
+
+
+# (从数据资源中选取)
+def resource_handle_meta_data_model(id_lists, data_model_node_id):
+    """
+    处理从数据资源中选取的数据模型与元数据的关系
+    
+    Args:
+        id_lists: ID列表
+        data_model_node_id: 数据模型节点ID
+        
+    Returns:
+        None
+    """
+    # 构建meta_id和resouce_id的列表
+    resouce_ids = [record['resource_id'] for record in id_lists]
+    meta_ids = [record['id'] for id_list in id_lists for record in id_list['metaData']]
+    metaData = [record['data_standard'] for id_list in id_lists for record in id_list['metaData']]
+    
+    # 创建与meta_node的关系 组成关系
+    if meta_ids:
+        query = """
+        MATCH (source:data_model), (target:meta_node)
+        WHERE id(source)=$source_id AND id(target) IN $target_ids
+        MERGE (source)-[:component]->(target)
+        """
+        with neo4j_driver.get_session() as session:
+            session.run(query, source_id=data_model_node_id, target_ids=meta_ids)
+
+    # 创建与data_resource的关系 资源关系
+    if resouce_ids:
+        query = """
+        MATCH (source:data_model), (target:data_resource)
+        WHERE id(source)=$source_id AND id(target) IN $target_ids
+        MERGE (source)-[:resource]->(target)
+        """
+        with neo4j_driver.get_session() as session:
+            session.run(query, source_id=data_model_node_id, target_ids=resouce_ids)
+
+
+# (从数据模型中选取)
+def model_handle_meta_data_model(id_lists, data_model_node_id):
+    """
+    处理从数据模型中选取的数据模型与元数据的关系
+    
+    Args:
+        id_lists: ID列表
+        data_model_node_id: 数据模型节点ID
+        
+    Returns:
+        None
+    """
+    # 构建meta_id和model_id的列表
+    model_ids = [record['model_id'] for record in id_lists]
+    meta_ids = [record['id'] for id_list in id_lists for record in id_list['metaData']]
+    
+    # 创建与meta_node的关系 组成关系
+    if meta_ids:
+        query = """
+        MATCH (source:data_model), (target:meta_node)
+        WHERE id(source)=$source_id AND id(target) IN $target_ids
+        MERGE (source)-[:component]->(target)
+        """
+        with neo4j_driver.get_session() as session:
+            session.run(query, source_id=data_model_node_id, target_ids=meta_ids)
+
+    # 创建与data_model的关系 模型关系
+    if model_ids:
+        query = """
+        MATCH (source:data_model), (target:data_model)
+        WHERE id(source)=$source_id AND id(target) IN $target_ids
+        MERGE (source)-[:use]->(target)
+        """
+        with neo4j_driver.get_session() as session:
+            session.run(query, source_id=data_model_node_id, target_ids=model_ids)
+
+
+# (从DDL中选取)
+def handle_no_meta_data_model(id_lists, receiver, data_model_node):
+    """
+    处理从DDL中选取的没有元数据的数据模型
+    
+    Args:
+        id_lists: ID列表
+        receiver: 接收到的请求参数
+        data_model_node: 数据模型节点
+        
+    Returns:
+        None
+    """
+    # 构建meta_id和resouce_id的列表
+    resouce_ids = [record['resource_id'] for record in id_lists]
+    meta_ids = [record['id'] for id_list in id_lists for record in id_list['metaData']]
+    
+    # 创建与data_resource的关系 资源关系
+    if resouce_ids:
+        query = """
+        MATCH (source:data_model), (target:data_resource)
+        WHERE id(source)=$source_id AND id(target) IN $target_ids
+        MERGE (source)-[:resource]->(target)
+        """
+        with neo4j_driver.get_session() as session:
+            session.run(query, source_id=data_model_node.identity, target_ids=resouce_ids)
+
+    if meta_ids:
+        meta_node_list = []
+        for id in meta_ids:
+            query = """
+            MATCH (n)
+            WHERE id(n) = $node_id
+            RETURN n
+            """
+            result = connect_graph.run(query, node_id=id)
+            if result:
+                record = result.data()
+                if record:
+                    meta_node_list.append(record[0]['n'])
+        
+        # 提取接收到的数据并创建meta_node节点
+        meta_node = None
+        resource_ids = []
+        
+        for item in id_lists:
+            resource_id = item['resource_id']
+            resource_ids.append(resource_id)
+            
+            for meta_item in item['metaData']:
+                meta_id = meta_item['id']
+                data_standard = meta_item.get('data_standard', '')
+                en_name_zh = meta_item.get('en_name_zh', '')
+                data_name = meta_item.get('data_name', '')
+                
+                # 使用传递的参数创建meta_node节点
+                meta_params = {
+                    'name': data_name,
+                    'cn_name': en_name_zh,
+                    'standard': data_standard,
+                    'time': get_formatted_time()
+                }
+                
+                # 创建meta_node节点
+                meta_node = create_or_get_node('meta_node', **meta_params)
+                
+                # 创建与data_model的关系
+                if meta_node and not relationship_exists(data_model_node, 'component', meta_node):
+                    connection = Relationship(data_model_node, 'component', meta_node)
+                    connect_graph.create(connection)
+
+
+# 数据模型详情
+def handle_id_model(model_id):
+    """
+    获取数据模型详情
+    
+    Args:
+        model_id: 数据模型ID
+        
+    Returns:
+        数据模型详情
+    """
+    model_detail_query = """
+    MATCH (n:data_model) WHERE id(n) = $model_id
+    RETURN n
+    """
+    model_detail_result = connect_graph.run(model_detail_query, model_id=model_id).data()
+    
+    if not model_detail_result:
+        return None
+    
+    model_detail = model_detail_result[0]['n']
+    model_info = dict(model_detail)
+    model_info['id'] = model_id
+    
+    # 获取data_model节点连接的resource节点
+    resource_query = """
+    MATCH (n:data_model)-[:resource]->(r:data_resource) WHERE id(n) = $model_id
+    RETURN r
+    """
+    resource_result = connect_graph.run(resource_query, model_id=model_id).data()
+    resources = []
+    
+    for item in resource_result:
+        resource = dict(item['r'])
+        resource['id'] = item['r'].identity
+        resources.append(resource)
+    
+    model_info['resources'] = resources
+    
+    # 获取data_model节点连接的component节点
+    component_query = """
+    MATCH (n:data_model)-[:component]->(m:meta_node) WHERE id(n) = $model_id
+    RETURN m
+    """
+    component_result = connect_graph.run(component_query, model_id=model_id).data()
+    components = []
+    
+    for item in component_result:
+        component = dict(item['m'])
+        component['id'] = item['m'].identity
+        components.append(component)
+    
+    model_info['components'] = components
+    
+    # 获取data_model节点连接的use节点
+    use_query = """
+    MATCH (n:data_model)-[:use]->(u:data_model) WHERE id(n) = $model_id
+    RETURN u
+    """
+    use_result = connect_graph.run(use_query, model_id=model_id).data()
+    uses = []
+    
+    for item in use_result:
+        use = dict(item['u'])
+        use['id'] = item['u'].identity
+        uses.append(use)
+    
+    model_info['uses'] = uses
+    
+    # 获取data_model节点连接的标签
+    tag_query = """
+    MATCH (n:data_model)-[:label]->(t:data_label) WHERE id(n) = $model_id
+    RETURN t
+    """
+    tag_result = connect_graph.run(tag_query, model_id=model_id).data()
+    
+    if tag_result:
+        tag = dict(tag_result[0]['t'])
+        tag['id'] = tag_result[0]['t'].identity
+        model_info['tag'] = tag
+    
+    return model_info
+
+
+# 数据模型列表
+def model_list(skip_count, page_size, en_name_filter=None, name_filter=None, 
+               category=None, tag=None, level=None):
+    """
+    获取数据模型列表
+    
+    Args:
+        skip_count: 跳过的记录数量
+        page_size: 每页记录数量
+        en_name_filter: 英文名称过滤条件
+        name_filter: 名称过滤条件
+        category: 分类过滤条件
+        tag: 标签过滤条件
+        level: 级别过滤条件
+        
+    Returns:
+        tuple: (数据列表, 总记录数)
+    """
+    # 构建查询条件
+    params = {}
+    where_clause = []
+    
+    if name_filter:
+        where_clause.append("n.name =~ $name_filter")
+        params['name_filter'] = f"(?i).*{name_filter}.*"
+    
+    if en_name_filter:
+        where_clause.append("n.en_name =~ $en_name_filter")
+        params['en_name_filter'] = f"(?i).*{en_name_filter}.*"
+    
+    if level:
+        where_clause.append("n.level = $level")
+        params['level'] = level
+    
+    if category:
+        where_clause.append("n.category = $category")
+        params['category'] = category
+    
+    # 添加tag标签查询逻辑
+    if tag:
+        match_clause = "MATCH (n:data_model)"
+        if tag:
+            match_clause += "\nMATCH (n)-[:label]->(t:data_label) WHERE id(t) = $tag_id"
+            params['tag_id'] = tag
+    else:
+        match_clause = "MATCH (n:data_model)"
+    
+    # 转换为字符串形式
+    where_str = " AND ".join(where_clause)
+    if where_str:
+        where_str = "WHERE " + where_str
+    
+    # 获取数据总数
+    count_query = f"""
+    {match_clause}
+    {where_str}
+    RETURN count(n) as count
+    """
+    
+    count = connect_graph.run(count_query, **params).evaluate()
+    
+    # 获取分页数据
+    params['skip'] = skip_count
+    params['limit'] = page_size
+    
+    data_query = f"""
+    {match_clause}
+    {where_str}
+    OPTIONAL MATCH (n)-[:label]->(t:data_label)
+    WITH n, t
+    OPTIONAL MATCH (n)-[:component]->(m:meta_node)
+    RETURN 
+        id(n) as id, 
+        n.name as name, 
+        n.en_name as en_name, 
+        n.category as category, 
+        n.description as description, 
+        n.time as time,
+        n.level as level,
+        t.name as tag_name,
+        id(t) as tag_id,
+        count(m) as component_count
+    ORDER BY n.time DESC
+    SKIP $skip
+    LIMIT $limit
+    """
+    
+    result = connect_graph.run(data_query, **params).data()
+    
+    return result, count
+
+
+# 有血缘关系的数据资源列表
+def model_resource_list(skip_count, page_size, name_filter=None, id=None, 
+                        category=None, time=None):
+    """
+    获取有血缘关系的数据资源列表
+    
+    Args:
+        skip_count: 跳过的记录数量
+        page_size: 每页记录数量
+        name_filter: 名称过滤条件
+        id: 数据资源ID
+        category: 分类过滤条件
+        time: 时间过滤条件
+        
+    Returns:
+        tuple: (数据列表, 总记录数)
+    """
+    # 构建查询条件
+    params = {'id': id}
+    where_clause = []
+    
+    if name_filter:
+        where_clause.append("n.name =~ $name_filter")
+        params['name_filter'] = f"(?i).*{name_filter}.*"
+    
+    if category:
+        where_clause.append("n.category = $category")
+        params['category'] = category
+    
+    if time:
+        where_clause.append("n.time >= $time")
+        params['time'] = time
+    
+    # 转换为字符串形式
+    where_str = " AND ".join(where_clause)
+    if where_str:
+        where_str = "WHERE " + where_str
+    
+    # 获取数据总数
+    count_query = f"""
+    MATCH (search:data_resource) WHERE id(search) = $id
+    MATCH (search)-[:connection]->(mn:meta_node)<-[:connection]-(n:data_resource)
+    {where_str}
+    RETURN count(DISTINCT n) as count
+    """
+    
+    count = connect_graph.run(count_query, **params).evaluate()
+    
+    # 获取分页数据
+    params['skip'] = skip_count
+    params['limit'] = page_size
+    
+    data_query = f"""
+    MATCH (search:data_resource) WHERE id(search) = $id
+    MATCH (search)-[:connection]->(mn:meta_node)<-[:connection]-(n:data_resource)
+    {where_str}
+    WITH DISTINCT n, mn
+    RETURN 
+        id(n) as id, 
+        n.name as name, 
+        n.en_name as en_name, 
+        n.category as category, 
+        n.description as description, 
+        n.time as time,
+        collect({{id: id(mn), name: mn.name}}) as common_meta
+    ORDER BY n.time DESC
+    SKIP $skip
+    LIMIT $limit
+    """
+    
+    result = connect_graph.run(data_query, **params).data()
+    
+    return result, count
+
+
+# 数据模型血缘图谱
+def model_kinship_graph(nodeid, meta=False):
+    """
+    获取数据模型血缘图谱
+    
+    Args:
+        nodeid: 节点ID
+        meta: 是否返回元数据
+        
+    Returns:
+        图谱数据
+    """
+    if meta:
+        query = """
+        MATCH p = (n:data_model)-[r:component|resource*..3]-(m) 
+        WHERE id(n) = $nodeId
+        WITH p, relationships(p) as rels
+        RETURN p
+        limit 300
+        """
+    else:
+        query = """
+        MATCH p = (n:data_model)-[r:resource*..3]-(m) 
+        WHERE id(n) = $nodeId and labels(m) <> ['meta_node']
+        WITH p, relationships(p) as rels
+        RETURN p
+        limit 300
+        """
+    
+    result = connect_graph.run(query, nodeId=nodeid)
+    
+    nodes = set()
+    relationships = set()
+    nodes_by_id = {}
+    
+    for record in result:
+        path = record["p"]
+        
+        for node in path.nodes:
+            if node.identity not in nodes:
+                node_id = str(node.identity)
+                node_type = list(node.labels)[0].split('_')[1]
+                node_data = {
+                    "id": node_id,
+                    "text": node.get("name", ""),
+                    "type": node_type
+                }
+                
+                nodes.add(node.identity)
+                nodes_by_id[node.identity] = node_data
+        
+        for rel in path.relationships:
+            relationship_id = f"{rel.start_node.identity}-{rel.end_node.identity}"
+            if relationship_id not in relationships:
+                relationship_data = {
+                    "from": str(rel.start_node.identity),
+                    "to": str(rel.end_node.identity),
+                    "text": type(rel).__name__
+                }
+                relationships.add(relationship_id)
+    
+    # 转换为所需格式
+    return {
+        "nodes": list(nodes_by_id.values()),
+        "edges": [{"from": rel.split("-")[0], "to": rel.split("-")[1], "text": ""} 
+                for rel in relationships]
+    }
+
+
+# 数据模型影响图谱
+def model_impact_graph(nodeid, meta=False):
+    """
+    获取数据模型影响图谱
+    
+    Args:
+        nodeid: 节点ID
+        meta: 是否返回元数据
+        
+    Returns:
+        图谱数据
+    """
+    if meta:
+        query = """
+        MATCH p = (n:data_model)-[r:use*..3]-(m) 
+        WHERE id(n) = $nodeId
+        WITH p, relationships(p) as rels
+        RETURN p
+        limit 300
+        """
+    else:
+        query = """
+        MATCH p = (n:data_model)-[r:use*..3]-(m) 
+        WHERE id(n) = $nodeId
+        WITH p, relationships(p) as rels
+        RETURN p
+        limit 300
+        """
+    
+    result = connect_graph.run(query, nodeId=nodeid)
+    
+    nodes = set()
+    relationships = set()
+    nodes_by_id = {}
+    
+    for record in result:
+        path = record["p"]
+        
+        for node in path.nodes:
+            if node.identity not in nodes:
+                node_id = str(node.identity)
+                node_type = list(node.labels)[0].split('_')[1]
+                node_data = {
+                    "id": node_id,
+                    "text": node.get("name", ""),
+                    "type": node_type
+                }
+                
+                nodes.add(node.identity)
+                nodes_by_id[node.identity] = node_data
+        
+        for rel in path.relationships:
+            relationship_id = f"{rel.start_node.identity}-{rel.end_node.identity}"
+            if relationship_id not in relationships:
+                relationship_data = {
+                    "from": str(rel.start_node.identity),
+                    "to": str(rel.end_node.identity),
+                    "text": type(rel).__name__
+                }
+                relationships.add(relationship_id)
+    
+    # 转换为所需格式
+    return {
+        "nodes": list(nodes_by_id.values()),
+        "edges": [{"from": rel.split("-")[0], "to": rel.split("-")[1], "text": ""} 
+                for rel in relationships]
+    }
+
+
+# 数据模型全部图谱
+def model_all_graph(nodeid, meta=False):
+    """
+    获取数据模型全部图谱
+    
+    Args:
+        nodeid: 节点ID
+        meta: 是否返回元数据
+        
+    Returns:
+        图谱数据
+    """
+    if meta:
+        query = """
+        MATCH p = (n:data_model)-[r*..3]-(m) 
+        WHERE id(n) = $nodeId
+        WITH p, relationships(p) as rels
+        RETURN p
+        limit 300
+        """
+    else:
+        query = """
+        MATCH p = (n:data_model)-[r*..3]-(m) 
+        WHERE id(n) = $nodeId and labels(m) <> ['meta_node']
+        WITH p, relationships(p) as rels
+        RETURN p
+        limit 300
+        """
+    
+    result = connect_graph.run(query, nodeId=nodeid)
+    
+    nodes = set()
+    relationships = set()
+    nodes_by_id = {}
+    
+    for record in result:
+        path = record["p"]
+        
+        for node in path.nodes:
+            if node.identity not in nodes:
+                node_id = str(node.identity)
+                node_type = list(node.labels)[0].split('_')[1]
+                node_data = {
+                    "id": node_id,
+                    "text": node.get("name", ""),
+                    "type": node_type
+                }
+                
+                nodes.add(node.identity)
+                nodes_by_id[node.identity] = node_data
+        
+        for rel in path.relationships:
+            relationship_id = f"{rel.start_node.identity}-{rel.end_node.identity}"
+            if relationship_id not in relationships:
+                relationship_data = {
+                    "from": str(rel.start_node.identity),
+                    "to": str(rel.end_node.identity),
+                    "text": type(rel).__name__
+                }
+                relationships.add(relationship_id)
+    
+    # 转换为所需格式
+    return {
+        "nodes": list(nodes_by_id.values()),
+        "edges": [{"from": rel.split("-")[0], "to": rel.split("-")[1], "text": ""} 
+                for rel in relationships]
+    }
+
+
+# 更新数据模型
+def data_model_edit(receiver):
+    """
+    更新数据模型
+    
+    Args:
+        receiver: 接收到的请求参数
+        
+    Returns:
+        更新结果
+    """
+    id = receiver.get('id')
+    name = receiver.get('name')
+    en_name = receiver.get('en_name')
+    category = receiver.get('category')
+    description = receiver.get('description')
+    tag = receiver.get('tag')
+    
+    # 更新数据模型节点
+    query = """
+    MATCH (n:data_model) WHERE id(n) = $id
+    SET n.name = $name, n.en_name = $en_name, n.category = $category, n.description = $description
+    RETURN n
+    """
+    
+    result = connect_graph.run(query, id=id, name=name, en_name=en_name, 
+                             category=category, description=description).data()
+    
+    # 处理标签关系
+    if tag:
+        # 先删除所有标签关系
+        delete_query = """
+        MATCH (n:data_model)-[r:label]->() WHERE id(n) = $id
+        DELETE r
+        """
+        connect_graph.run(delete_query, id=id)
+        
+        # 再创建新的标签关系
+        tag_node = get_node_by_id('data_label', tag)
+        if tag_node:
+            model_node = get_node_by_id_no_label(id)
+            if model_node and not relationship_exists(model_node, 'label', tag_node):
+                connection = Relationship(model_node, 'label', tag_node)
+                connect_graph.create(connection)
+    
+    return {"message": "数据模型更新成功"} 

+ 2 - 0
app/core/data_resource/__init__.py

@@ -0,0 +1,2 @@
+# app/core/data_resource/__init__.py
+# 数据资源业务逻辑模块 

+ 838 - 0
app/core/data_resource/resource.py

@@ -0,0 +1,838 @@
+import json
+import re
+import logging
+from py2neo import Relationship
+import pandas as pd
+from app.services.neo4j_driver import neo4j_driver
+from app.services.package_function import create_or_get_node, relationship_exists, get_node
+
+logger = logging.getLogger("app")
+
+def get_formatted_time():
+    """获取格式化的当前时间"""
+    import time
+    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
+
+def get_node_by_id(label, id):
+    """根据ID获取指定标签的节点"""
+    try:
+        with neo4j_driver.get_session() as session:
+            cypher = f"MATCH (n:{label}) WHERE id(n) = $id RETURN n"
+            result = session.run(cypher, id=int(id))
+            record = result.single()
+            return record["n"] if record else None
+    except Exception as e:
+        logger.error(f"根据ID获取节点失败: {str(e)}")
+        return None
+
+def get_node_by_id_no_label(id):
+    """根据ID获取节点,不限制标签"""
+    try:
+        with neo4j_driver.get_session() as session:
+            cypher = "MATCH (n) WHERE id(n) = $id RETURN n"
+            result = session.run(cypher, id=int(id))
+            record = result.single()
+            return record["n"] if record else None
+    except Exception as e:
+        logger.error(f"根据ID获取节点失败: {str(e)}")
+        return None
+
+def delete_relationships(start_node, rel_type=None, end_node=None):
+    """删除关系"""
+    try:
+        with neo4j_driver.get_session() as session:
+            if rel_type and end_node:
+                cypher = "MATCH (a)-[r:`{rel_type}`]->(b) WHERE id(a) = $start_id AND id(b) = $end_id DELETE r"
+                cypher = cypher.replace("{rel_type}", rel_type)
+                session.run(cypher, start_id=start_node.id, end_id=end_node.id)
+            elif rel_type:
+                cypher = "MATCH (a)-[r:`{rel_type}`]->() WHERE id(a) = $start_id DELETE r"
+                cypher = cypher.replace("{rel_type}", rel_type)
+                session.run(cypher, start_id=start_node.id)
+            else:
+                cypher = "MATCH (a)-[r]->() WHERE id(a) = $start_id DELETE r"
+                session.run(cypher, start_id=start_node.id)
+        return True
+    except Exception as e:
+        logger.error(f"删除关系失败: {str(e)}")
+        return False
+
+def update_or_create_node(label, **properties):
+    """更新或创建节点"""
+    try:
+        with neo4j_driver.get_session() as session:
+            node_id = properties.pop('id', None)
+            if node_id:
+                # 更新现有节点
+                set_clause = ", ".join([f"n.{k} = ${k}" for k in properties.keys()])
+                cypher = f"MATCH (n:{label}) WHERE id(n) = $id SET {set_clause} RETURN n"
+                result = session.run(cypher, id=int(node_id), **properties)
+            else:
+                # 创建新节点
+                props_str = ", ".join([f"{k}: ${k}" for k in properties.keys()])
+                cypher = f"CREATE (n:{label} {{{props_str}}}) RETURN n"
+                result = session.run(cypher, **properties)
+            
+            record = result.single()
+            return record["n"] if record else None
+    except Exception as e:
+        logger.error(f"更新或创建节点失败: {str(e)}")
+        return None
+
+# 数据资源-元数据 关系节点创建、查询
+def handle_node(receiver, head_data, data_resource):
+    """处理数据资源节点创建和关系建立"""
+    try:
+        # 更新属性
+        update_attributes = {
+            'en_name': data_resource['en_name'],
+            'time': get_formatted_time(),
+            'type': 'structure'  # 结构化文件没有type
+        }
+        if 'additional_info' in receiver:
+            del receiver['additional_info']
+        tag_list = receiver.get('tag')
+        receiver.update(update_attributes)
+
+        # 创建或获取 data_resource 节点
+        with neo4j_driver.get_session() as session:
+            props_str = ", ".join([f"{k}: ${k}" for k in receiver.keys()])
+            cypher = f"""
+            MERGE (n:data_resource {{name: $name}})
+            ON CREATE SET n = {{{props_str}}}
+            ON MATCH SET {", ".join([f"n.{k} = ${k}" for k in receiver.keys()])}
+            RETURN n
+            """
+            result = session.run(cypher, **receiver)
+            data_resource_node = result.single()["n"]
+            resource_id = data_resource_node.id
+
+            # 处理标签关系
+            if tag_list:
+                tag_node = get_node_by_id('data_label', tag_list)
+                if tag_node:
+                    # 检查关系是否存在
+                    rel_check = """
+                    MATCH (a:data_resource)-[r:label]->(b:data_label) 
+                    WHERE id(a) = $resource_id AND id(b) = $tag_id
+                    RETURN r
+                    """
+                    rel_result = session.run(rel_check, resource_id=resource_id, tag_id=tag_node.id)
+                    
+                    # 如果关系不存在则创建
+                    if not rel_result.single():
+                        rel_create = """
+                        MATCH (a:data_resource), (b:data_label)
+                        WHERE id(a) = $resource_id AND id(b) = $tag_id
+                        CREATE (a)-[r:label]->(b)
+                        RETURN r
+                        """
+                        session.run(rel_create, resource_id=resource_id, tag_id=tag_node.id)
+
+            # 处理头部数据(元数据)
+            if head_data:
+                for item in head_data:
+                    # 创建元数据节点
+                    meta_cypher = """
+                    MERGE (m:Metadata {name: $name})
+                    ON CREATE SET m.en_name = $en_name, m.createTime = $create_time
+                    RETURN m
+                    """
+                    
+                    create_time = get_formatted_time()
+                    meta_result = session.run(
+                        meta_cypher,
+                        name=item['name'],
+                        en_name=item['en_name'],
+                        create_time=create_time
+                    )
+                    meta_node = meta_result.single()["m"]
+                    
+                    # 创建关系
+                    rel_cypher = """
+                    MATCH (a:data_resource), (m:Metadata)
+                    WHERE id(a) = $resource_id AND id(m) = $meta_id
+                    MERGE (a)-[r:contain]->(m)
+                    RETURN r
+                    """
+                    
+                    session.run(
+                        rel_cypher,
+                        resource_id=resource_id,
+                        meta_id=meta_node.id
+                    )
+            
+            return resource_id
+    except Exception as e:
+        logger.error(f"处理数据资源节点创建和关系建立失败: {str(e)}")
+        raise
+
+def handle_id_resource(resource_id):
+    """处理单个数据资源查询"""
+    try:
+        with neo4j_driver.get_session() as session:
+            # 查询数据资源节点
+            cypher = """
+            MATCH (n:data_resource)
+            WHERE id(n) = $resource_id
+            RETURN n
+            """
+            result = session.run(cypher, resource_id=int(resource_id))
+            record = result.single()
+            
+            if not record:
+                return None
+                
+            data_resource = dict(record["n"])
+            data_resource["id"] = record["n"].id
+            
+            # 查询关联的标签
+            tag_cypher = """
+            MATCH (n:data_resource)-[:label]->(t:data_label)
+            WHERE id(n) = $resource_id
+            RETURN t
+            """
+            tag_result = session.run(tag_cypher, resource_id=int(resource_id))
+            tag_record = tag_result.single()
+            
+            if tag_record:
+                tag = dict(tag_record["t"])
+                tag["id"] = tag_record["t"].id
+                data_resource["tag_info"] = tag
+            
+            # 查询关联的元数据
+            meta_cypher = """
+            MATCH (n:data_resource)-[:contain]->(m:Metadata)
+            WHERE id(n) = $resource_id
+            RETURN m
+            """
+            meta_result = session.run(meta_cypher, resource_id=int(resource_id))
+            
+            meta_list = []
+            for meta_record in meta_result:
+                meta = dict(meta_record["m"])
+                meta["id"] = meta_record["m"].id
+                meta_list.append(meta)
+            
+            data_resource["meta_list"] = meta_list
+            
+            return data_resource
+    except Exception as e:
+        logger.error(f"处理单个数据资源查询失败: {str(e)}")
+        return None
+
+def id_resource_graph(resource_id):
+    """获取数据资源图谱"""
+    try:
+        with neo4j_driver.get_session() as session:
+            # 查询数据资源节点及其关系
+            cypher = """
+            MATCH (n:data_resource)-[r]-(m)
+            WHERE id(n) = $resource_id
+            RETURN n, r, m
+            """
+            result = session.run(cypher, resource_id=int(resource_id))
+            
+            # 收集节点和关系
+            nodes = {}
+            relationships = []
+            
+            for record in result:
+                # 处理源节点
+                source_node = dict(record["n"])
+                source_node["id"] = record["n"].id
+                nodes[source_node["id"]] = source_node
+                
+                # 处理目标节点
+                target_node = dict(record["m"])
+                target_node["id"] = record["m"].id
+                nodes[target_node["id"]] = target_node
+                
+                # 处理关系
+                rel = record["r"]
+                relationship = {
+                    "id": rel.id,
+                    "source": record["n"].id,
+                    "target": record["m"].id,
+                    "type": rel.type
+                }
+                relationships.append(relationship)
+            
+            return {
+                "nodes": list(nodes.values()),
+                "relationships": relationships
+            }
+    except Exception as e:
+        logger.error(f"获取数据资源图谱失败: {str(e)}")
+        return {"nodes": [], "relationships": []}
+
+def resource_list(page, page_size, en_name_filter=None, name_filter=None, 
+                 type_filter='all', category_filter=None, tag_filter=None):
+    """获取数据资源列表"""
+    try:
+        with neo4j_driver.get_session() as session:
+            # 构建查询条件
+            match_clause = "MATCH (n:data_resource)"
+            where_conditions = []
+            
+            if en_name_filter:
+                where_conditions.append(f"n.en_name CONTAINS '{en_name_filter}'")
+                
+            if name_filter:
+                where_conditions.append(f"n.name CONTAINS '{name_filter}'")
+                
+            if type_filter and type_filter != 'all':
+                where_conditions.append(f"n.type = '{type_filter}'")
+                
+            if category_filter:
+                where_conditions.append(f"n.category = '{category_filter}'")
+                
+            # 标签过滤需要额外的匹配
+            if tag_filter:
+                match_clause += "-[:label]->(t:data_label)"
+                where_conditions.append(f"t.name = '{tag_filter}'")
+            
+            where_clause = " WHERE " + " AND ".join(where_conditions) if where_conditions else ""
+            
+            # 计算总数
+            count_cypher = f"{match_clause}{where_clause} RETURN count(n) as count"
+            count_result = session.run(count_cypher)
+            total_count = count_result.single()["count"]
+            
+            # 分页查询
+            skip = (page - 1) * page_size
+            cypher = f"""
+            {match_clause}{where_clause}
+            RETURN n
+            ORDER BY n.time DESC
+            SKIP {skip} LIMIT {page_size}
+            """
+            result = session.run(cypher)
+            
+            # 格式化结果
+            resources = []
+            for record in result:
+                node = dict(record["n"])
+                node["id"] = record["n"].id
+                
+                # 查询关联的标签
+                tag_cypher = """
+                MATCH (n:data_resource)-[:label]->(t:data_label)
+                WHERE id(n) = $resource_id
+                RETURN t
+                """
+                tag_result = session.run(tag_cypher, resource_id=node["id"])
+                tag_record = tag_result.single()
+                
+                if tag_record:
+                    tag = dict(tag_record["t"])
+                    tag["id"] = tag_record["t"].id
+                    node["tag_info"] = tag
+                
+                resources.append(node)
+                
+            return resources, total_count
+    except Exception as e:
+        logger.error(f"获取数据资源列表失败: {str(e)}")
+        return [], 0
+
+def id_data_search_list(resource_id, page, page_size, en_name_filter=None,
+                       name_filter=None, category_filter=None, tag_filter=None):
+    """获取特定数据资源关联的元数据列表"""
+    try:
+        with neo4j_driver.get_session() as session:
+            # 基本匹配语句
+            match_clause = """
+            MATCH (n:data_resource)-[:contain]->(m:Metadata)
+            WHERE id(n) = $resource_id
+            """
+            where_conditions = []
+            
+            if en_name_filter:
+                where_conditions.append(f"m.en_name CONTAINS '{en_name_filter}'")
+                
+            if name_filter:
+                where_conditions.append(f"m.name CONTAINS '{name_filter}'")
+                
+            if category_filter:
+                where_conditions.append(f"m.category = '{category_filter}'")
+                
+            # 标签过滤需要额外的匹配
+            tag_match = ""
+            if tag_filter:
+                tag_match = "MATCH (m)-[:HAS_TAG]->(t:Tag) WHERE t.name = $tag_filter"
+            
+            where_clause = " AND " + " AND ".join(where_conditions) if where_conditions else ""
+            
+            # 计算总数
+            count_cypher = f"""
+            {match_clause}{where_clause}
+            {tag_match}
+            RETURN count(m) as count
+            """
+            count_params = {"resource_id": int(resource_id)}
+            if tag_filter:
+                count_params["tag_filter"] = tag_filter
+                
+            count_result = session.run(count_cypher, **count_params)
+            total_count = count_result.single()["count"]
+            
+            # 分页查询
+            skip = (page - 1) * page_size
+            cypher = f"""
+            {match_clause}{where_clause}
+            {tag_match}
+            RETURN m
+            ORDER BY m.name
+            SKIP {skip} LIMIT {page_size}
+            """
+            
+            result = session.run(cypher, **count_params)
+            
+            # 格式化结果
+            metadata_list = []
+            for record in result:
+                node = dict(record["m"])
+                node["id"] = record["m"].id
+                metadata_list.append(node)
+                
+            return metadata_list, total_count
+    except Exception as e:
+        logger.error(f"获取数据资源关联的元数据列表失败: {str(e)}")
+        return [], 0
+
+def resource_kinship_graph(resource_id, include_meta=True):
+    """获取数据资源亲缘关系图谱"""
+    try:
+        with neo4j_driver.get_session() as session:
+            # 基本查询
+            cypher_parts = [
+                "MATCH (n:data_resource) WHERE id(n) = $resource_id",
+                "OPTIONAL MATCH (n)-[:label]->(l:data_label)",
+            ]
+            
+            # 是否包含元数据
+            if include_meta:
+                cypher_parts.append("OPTIONAL MATCH (n)-[:contain]->(m:Metadata)")
+                
+            cypher_parts.append("RETURN n, l, collect(m) as metadata")
+            
+            cypher = "\n".join(cypher_parts)
+            
+            result = session.run(cypher, resource_id=int(resource_id))
+            record = result.single()
+            
+            if not record:
+                return {"nodes": [], "relationships": []}
+                
+            # 收集节点和关系
+            nodes = {}
+            relationships = []
+            
+            # 处理数据资源节点
+            resource_node = dict(record["n"])
+            resource_node["id"] = record["n"].id
+            resource_node["labels"] = list(record["n"].labels)
+            nodes[resource_node["id"]] = resource_node
+            
+            # 处理标签节点
+            if record["l"]:
+                label_node = dict(record["l"])
+                label_node["id"] = record["l"].id
+                label_node["labels"] = 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"
+                })
+            
+            # 处理元数据节点
+            if include_meta and record["metadata"]:
+                for meta in record["metadata"]:
+                    if meta:  # 检查元数据节点是否存在
+                        meta_node = dict(meta)
+                        meta_node["id"] = meta.id
+                        meta_node["labels"] = list(meta.labels)
+                        nodes[meta_node["id"]] = meta_node
+                        
+                        # 添加资源-元数据关系
+                        relationships.append({
+                            "id": f"rel-{resource_node['id']}-contain-{meta_node['id']}",
+                            "source": resource_node["id"],
+                            "target": meta_node["id"],
+                            "type": "contain"
+                        })
+            
+            return {
+                "nodes": list(nodes.values()),
+                "relationships": relationships
+            }
+    except Exception as e:
+        logger.error(f"获取数据资源亲缘关系图谱失败: {str(e)}")
+        return {"nodes": [], "relationships": []}
+
+def resource_impact_all_graph(resource_id, include_meta=True):
+    """获取数据资源影响关系图谱"""
+    try:
+        with neo4j_driver.get_session() as session:
+            # 根据meta参数决定查询深度
+            if include_meta:
+                cypher = """
+                MATCH path = (n:data_resource)-[*1..3]-(m)
+                WHERE id(n) = $resource_id
+                RETURN path
+                """
+            else:
+                cypher = """
+                MATCH path = (n:data_resource)-[*1..2]-(m)
+                WHERE id(n) = $resource_id
+                AND NOT (m:Metadata)
+                RETURN path
+                """
+                
+            result = session.run(cypher, resource_id=int(resource_id))
+            
+            # 收集节点和关系
+            nodes = {}
+            relationships = {}
+            
+            for record in result:
+                path = record["path"]
+                
+                # 处理路径中的所有节点
+                for node in path.nodes:
+                    if node.id not in nodes:
+                        node_dict = dict(node)
+                        node_dict["id"] = node.id
+                        node_dict["labels"] = list(node.labels)
+                        nodes[node.id] = node_dict
+                
+                # 处理路径中的所有关系
+                for rel in path.relationships:
+                    if rel.id not in relationships:
+                        rel_dict = {
+                            "id": rel.id,
+                            "source": rel.start_node.id,
+                            "target": rel.end_node.id,
+                            "type": rel.type
+                        }
+                        relationships[rel.id] = rel_dict
+            
+            return {
+                "nodes": list(nodes.values()),
+                "relationships": list(relationships.values())
+            }
+    except Exception as e:
+        logger.error(f"获取数据资源影响关系图谱失败: {str(e)}")
+        return {"nodes": [], "relationships": []}
+
+def clean_type(type_str):
+    """清洗SQL类型字符串"""
+    return re.sub(r'\(.*?\)', '', type_str).strip().upper()
+
+def clean_field_name(field_name):
+    """清洗字段名"""
+    return field_name.strip('`').strip('"').strip("'")
+
+def select_create_ddl(sql_content):
+    """从SQL内容中提取创建表的DDL语句"""
+    create_pattern = r'CREATE\s+TABLE.*?;'
+    matches = re.findall(create_pattern, sql_content, re.DOTALL | re.IGNORECASE)
+    return matches
+
+def table_sql(sql):
+    """解析表定义SQL"""
+    try:
+        # 提取表名
+        table_name_pattern = r'CREATE\s+TABLE\s+(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))'
+        table_name_match = re.search(table_name_pattern, sql, re.IGNORECASE)
+        
+        if not table_name_match:
+            return None
+            
+        # 获取匹配的表名(从四个捕获组中选择非None的一个)
+        table_name = next((g for g in table_name_match.groups() if g is not None), "")
+        
+        # 提取字段定义
+        fields_pattern = r'CREATE\s+TABLE[^(]*\(\s*(.*?)\s*\)'
+        fields_match = re.search(fields_pattern, sql, re.DOTALL | re.IGNORECASE)
+        
+        if not fields_match:
+            return None
+            
+        fields_text = fields_match.group(1)
+        
+        # 分割字段定义
+        field_definitions = []
+        
+        # 处理字段定义,避免在逗号内的括号中分割
+        in_parenthesis = 0
+        current_field = ""
+        
+        for char in fields_text:
+            if char == '(':
+                in_parenthesis += 1
+                current_field += char
+            elif char == ')':
+                in_parenthesis -= 1
+                current_field += char
+            elif char == ',' and in_parenthesis == 0:
+                field_definitions.append(current_field.strip())
+                current_field = ""
+            else:
+                current_field += char
+                
+        if current_field.strip():
+            field_definitions.append(current_field.strip())
+        
+        # 解析每个字段
+        fields = []
+        primary_keys = []
+        
+        for field_def in field_definitions:
+            # 忽略PRIMARY KEY等约束定义
+            if re.match(r'^\s*(?:PRIMARY|UNIQUE|FOREIGN|CHECK|CONSTRAINT)\s+', field_def, re.IGNORECASE):
+                # 提取主键字段
+                pk_pattern = r'PRIMARY\s+KEY\s*\(\s*(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))\s*\)'
+                pk_match = re.search(pk_pattern, field_def, re.IGNORECASE)
+                
+                if pk_match:
+                    pk = next((g for g in pk_match.groups() if g is not None), "")
+                    primary_keys.append(pk)
+                continue
+                
+            # 解析常规字段定义
+            field_pattern = r'^\s*(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))\s+([A-Za-z0-9_]+(?:\s*\([^)]*\))?)'
+            field_match = re.search(field_pattern, field_def)
+            
+            if field_match:
+                # 提取字段名和类型
+                field_name = next((g for g in field_match.groups()[:4] if g is not None), "")
+                field_type = field_match.group(5)
+                
+                # 检查是否为主键
+                is_primary = "PRIMARY KEY" in field_def.upper()
+                if is_primary:
+                    primary_keys.append(field_name)
+                
+                # 检查是否为非空
+                not_null = "NOT NULL" in field_def.upper()
+                
+                # 检查默认值
+                default_match = re.search(r'DEFAULT\s+([^,\s]+)', field_def, re.IGNORECASE)
+                default_value = default_match.group(1) if default_match else None
+                
+                # 添加字段信息
+                field_info = {
+                    "name": field_name,
+                    "type": clean_type(field_type),
+                    "is_primary": is_primary,
+                    "not_null": not_null
+                }
+                
+                if default_value:
+                    field_info["default"] = default_value
+                    
+                fields.append(field_info)
+        
+        # 更新主键标记
+        for field in fields:
+            if field["name"] in primary_keys and not field["is_primary"]:
+                field["is_primary"] = True
+        
+        # 返回结果
+        return {
+            "table_name": table_name,
+            "fields": fields
+        }
+    except Exception as e:
+        logger.error(f"解析表定义SQL失败: {str(e)}")
+        return None
+
+def select_sql(sql_query):
+    """解析SELECT查询语句"""
+    try:
+        # 提取SELECT子句
+        select_pattern = r'SELECT\s+(.*?)\s+FROM'
+        select_match = re.search(select_pattern, sql_query, re.IGNORECASE | re.DOTALL)
+        
+        if not select_match:
+            return None
+            
+        select_clause = select_match.group(1)
+        
+        # 分割字段
+        fields = []
+        
+        # 处理字段列表,避免在函数调用中的逗号导致错误分割
+        in_parenthesis = 0
+        current_field = ""
+        
+        for char in select_clause:
+            if char == '(':
+                in_parenthesis += 1
+                current_field += char
+            elif char == ')':
+                in_parenthesis -= 1
+                current_field += char
+            elif char == ',' and in_parenthesis == 0:
+                fields.append(current_field.strip())
+                current_field = ""
+            else:
+                current_field += char
+                
+        if current_field.strip():
+            fields.append(current_field.strip())
+        
+        # 解析每个字段
+        parsed_fields = []
+        
+        for field in fields:
+            # 检查是否有字段别名
+            alias_pattern = r'(.*?)\s+[aA][sS]\s+(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))$'
+            alias_match = re.search(alias_pattern, field)
+            
+            if alias_match:
+                field_expr = alias_match.group(1).strip()
+                field_alias = next((g for g in alias_match.groups()[1:] if g is not None), "")
+                
+                parsed_fields.append({
+                    "expression": field_expr,
+                    "alias": field_alias
+                })
+            else:
+                # 没有别名的情况
+                parsed_fields.append({
+                    "expression": field.strip(),
+                    "alias": None
+                })
+        
+        # 提取FROM子句和表名
+        from_pattern = r'FROM\s+(.*?)(?:\s+WHERE|\s+GROUP|\s+HAVING|\s+ORDER|\s+LIMIT|$)'
+        from_match = re.search(from_pattern, sql_query, re.IGNORECASE | re.DOTALL)
+        
+        tables = []
+        if from_match:
+            from_clause = from_match.group(1).strip()
+            
+            # 分析FROM子句中的表
+            table_pattern = r'(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+))(?:\s+(?:AS\s+)?(?:`([^`]+)`|"([^"]+)"|\'([^\']+)\'|([a-zA-Z0-9_]+)))?'
+            table_matches = re.finditer(table_pattern, from_clause)
+            
+            for match in table_matches:
+                table_name = next((g for g in match.groups()[:4] if g is not None), "")
+                table_alias = next((g for g in match.groups()[4:] if g is not None), table_name)
+                
+                tables.append({
+                    "name": table_name,
+                    "alias": table_alias
+                })
+        
+        return {
+            "fields": parsed_fields,
+            "tables": tables
+        }
+    except Exception as e:
+        logger.error(f"解析SELECT查询语句失败: {str(e)}")
+        return None
+
+def model_resource_list(page, page_size, name_filter=None):
+    """获取模型资源列表"""
+    try:
+        with neo4j_driver.get_session() as session:
+            # 构建查询条件
+            match_clause = "MATCH (n:model_resource)"
+            where_clause = ""
+            
+            if name_filter:
+                where_clause = f" WHERE n.name CONTAINS '{name_filter}'"
+            
+            # 计算总数
+            count_cypher = f"{match_clause}{where_clause} RETURN count(n) as count"
+            count_result = session.run(count_cypher)
+            total_count = count_result.single()["count"]
+            
+            # 分页查询
+            skip = (page - 1) * page_size
+            cypher = f"""
+            {match_clause}{where_clause}
+            RETURN n
+            ORDER BY n.createTime DESC
+            SKIP {skip} LIMIT {page_size}
+            """
+            result = session.run(cypher)
+            
+            # 格式化结果
+            resources = []
+            for record in result:
+                node = dict(record["n"])
+                node["id"] = record["n"].id
+                resources.append(node)
+                
+            return resources, total_count
+    except Exception as e:
+        logger.error(f"获取模型资源列表失败: {str(e)}")
+        return [], 0
+
+def data_resource_edit(data):
+    """编辑数据资源"""
+    try:
+        resource_id = data.get("id")
+        if not resource_id:
+            raise ValueError("缺少资源ID")
+            
+        with neo4j_driver.get_session() as session:
+            # 更新节点属性
+            update_fields = {}
+            for key, value in data.items():
+                if key != "id" and key != "tag":
+                    update_fields[key] = value
+            
+            # 添加更新时间
+            update_fields["updateTime"] = get_formatted_time()
+            
+            # 构建更新语句
+            set_clause = ", ".join([f"n.{k} = ${k}" for k in update_fields.keys()])
+            cypher = f"""
+            MATCH (n:data_resource)
+            WHERE id(n) = $resource_id
+            SET {set_clause}
+            RETURN n
+            """
+            
+            result = session.run(cypher, resource_id=int(resource_id), **update_fields)
+            updated_node = result.single()
+            
+            if not updated_node:
+                raise ValueError("资源不存在")
+                
+            # 处理标签关系
+            tag_id = data.get("tag")
+            if tag_id:
+                # 删除旧的标签关系
+                delete_rel_cypher = """
+                MATCH (n:data_resource)-[r:label]->()
+                WHERE id(n) = $resource_id
+                DELETE r
+                """
+                session.run(delete_rel_cypher, resource_id=int(resource_id))
+                
+                # 创建新的标签关系
+                create_rel_cypher = """
+                MATCH (n:data_resource), (t:data_label)
+                WHERE id(n) = $resource_id AND id(t) = $tag_id
+                CREATE (n)-[r:label]->(t)
+                RETURN r
+                """
+                
+                session.run(create_rel_cypher, resource_id=int(resource_id), tag_id=int(tag_id))
+            
+            # 返回更新后的节点
+            return dict(updated_node["n"])
+    except Exception as e:
+        logger.error(f"编辑数据资源失败: {str(e)}")
+        raise 

+ 60 - 0
app/core/graph/README.md

@@ -0,0 +1,60 @@
+# 图数据库核心功能模块
+
+本模块提供了与Neo4j图数据库交互的核心功能,包括连接、查询和数据操作。
+
+## 功能概述
+
+图数据库核心模块是整个系统的基础设施之一,负责处理所有与图数据库相关的底层操作。它提供了一组工具函数,用于执行常见的图数据库操作,如节点和关系的创建、查询和子图提取等。
+
+## 主要功能
+
+### 1. 数据库连接 (connect_graph)
+
+提供了与Neo4j图数据库建立连接的功能,支持配置化连接参数。
+
+### 2. 节点操作 (create_or_get_node)
+
+创建具有给定标签和属性的新节点或获取现有节点,支持节点属性的更新。如果节点已存在(通过ID匹配),则更新节点属性。
+
+### 3. 关系操作 (create_relationship)
+
+在两个节点之间创建关系,支持定义关系类型和属性。
+
+### 4. 子图提取 (get_subgraph)
+
+根据指定的起始节点、关系类型和深度,提取子图数据。返回的数据包括节点和关系的集合,可直接用于前端图形可视化。
+
+### 5. Cypher查询执行 (execute_cypher_query)
+
+执行自定义的Cypher查询,支持参数化查询,并处理查询结果为标准格式。可处理各种类型的查询结果,包括节点、关系和路径。
+
+## 技术实现
+
+本模块主要基于py2neo和neo4j-python-driver,实现了对Neo4j图数据库的访问。主要技术点包括:
+
+- 数据库连接与会话管理
+- Cypher查询构建与执行
+- 查询结果序列化与转换
+- 异常处理与日志记录
+
+## 使用方法
+
+```python
+from app.core.graph import connect_graph, create_or_get_node, execute_cypher_query
+
+# 创建节点
+node_id = create_or_get_node('Person', name='张三', age=30)
+
+# 执行查询
+cypher = "MATCH (n:Person) WHERE n.name = $name RETURN n"
+results = execute_cypher_query(cypher, {'name': '张三'})
+
+# 获取子图
+from app.core.graph import get_subgraph
+graph_data = get_subgraph([node_id], ['KNOWS', 'WORKS_WITH'], max_depth=2)
+```
+
+## 依赖项
+
+- neo4j: 图数据库核心驱动
+- Config: 系统配置模块,提供数据库连接参数 

+ 20 - 0
app/core/graph/__init__.py

@@ -0,0 +1,20 @@
+"""
+Graph Database Core module
+包含图数据库相关的核心业务逻辑
+"""
+
+from app.core.graph.graph_operations import (
+    connect_graph,
+    create_or_get_node,
+    create_relationship,
+    get_subgraph,
+    execute_cypher_query
+)
+
+__all__ = [
+    'connect_graph',
+    'create_or_get_node',
+    'create_relationship',
+    'get_subgraph',
+    'execute_cypher_query'
+] 

+ 265 - 0
app/core/graph/graph_operations.py

@@ -0,0 +1,265 @@
+"""
+Graph Database Core Operations
+提供图数据库的基本操作功能
+"""
+
+from neo4j import GraphDatabase
+from app.config.config import Config
+import json
+import logging
+
+logger = logging.getLogger(__name__)
+
+class MyEncoder(json.JSONEncoder):
+    """Neo4j数据序列化的自定义JSON编码器"""
+    def default(self, obj):
+        if isinstance(obj, (int, float, str, bool, list, dict, tuple, type(None))):
+            return super(MyEncoder, self).default(obj)
+        return str(obj)
+
+def connect_graph():
+    """
+    连接到Neo4j图数据库
+    
+    Returns:
+        Neo4j driver实例,如果连接失败则返回None
+    """
+    try:
+        # 从Config获取Neo4j连接参数
+        uri = Config.NEO4J_URI
+        user = Config.NEO4J_USER
+        password = Config.NEO4J_PASSWORD
+        encrypted = Config.NEO4J_ENCRYPTED
+        
+        # 创建Neo4j驱动
+        driver = GraphDatabase.driver(
+            uri=uri,
+            auth=(user, password),
+            encrypted=encrypted
+        )
+        
+        # 验证连接
+        driver.verify_connectivity()
+        
+        return driver
+    except Exception as e:
+        # 处理连接错误
+        logger.error(f"Error connecting to Neo4j database: {str(e)}")
+        return None
+
+def create_or_get_node(label, **properties):
+    """
+    创建具有给定标签和属性的新节点或获取现有节点
+    如果具有相同id的节点存在,则更新属性
+    
+    Args:
+        label (str): Neo4j节点标签
+        **properties: 作为关键字参数的节点属性
+        
+    Returns:
+        节点id
+    """
+    try:
+        with connect_graph().session() as session:
+            # 检查是否提供了id
+            if 'id' in properties:
+                node_id = properties['id']
+                # 检查节点是否存在
+                query = f"""
+                MATCH (n:{label}) WHERE id(n) = $node_id
+                RETURN n
+                """
+                result = session.run(query, node_id=node_id).single()
+                
+                if result:
+                    # 节点存在,更新属性
+                    props_string = ", ".join([f"n.{key} = ${key}" for key in properties if key != 'id'])
+                    if props_string:
+                        update_query = f"""
+                        MATCH (n:{label}) WHERE id(n) = $node_id
+                        SET {props_string}
+                        RETURN id(n) as node_id
+                        """
+                        result = session.run(update_query, node_id=node_id, **properties).single()
+                        return result["node_id"]
+                    return node_id
+            
+            # 如果到这里,则创建新节点
+            props_keys = ", ".join([f"{key}: ${key}" for key in properties])
+            create_query = f"""
+            CREATE (n:{label} {{{props_keys}}})
+            RETURN id(n) as node_id
+            """
+            result = session.run(create_query, **properties).single()
+            return result["node_id"]
+            
+    except Exception as e:
+        logger.error(f"Error in create_or_get_node: {str(e)}")
+        raise e
+
+def create_relationship(start_node_id, end_node_id, rel_type, **properties):
+    """
+    在两个节点之间创建关系
+    
+    Args:
+        start_node_id: 起始节点ID
+        end_node_id: 结束节点ID
+        rel_type: 关系类型
+        **properties: 关系的属性
+        
+    Returns:
+        关系的ID
+    """
+    try:
+        # 构建属性部分
+        properties_str = ', '.join([f"{k}: ${k}" for k in properties.keys()])
+        properties_part = f" {{{properties_str}}}" if properties else ""
+        
+        # 构建Cypher语句
+        cypher = f"""
+        MATCH (a), (b)
+        WHERE id(a) = $start_node_id AND id(b) = $end_node_id
+        CREATE (a)-[r:{rel_type}{properties_part}]->(b)
+        RETURN id(r) as rel_id
+        """
+        
+        # 执行创建
+        with connect_graph().session() as session:
+            params = {
+                'start_node_id': int(start_node_id),
+                'end_node_id': int(end_node_id),
+                **properties
+            }
+            result = session.run(cypher, **params).single()
+            
+            if result:
+                return result["rel_id"]
+            else:
+                logger.error("Failed to create relationship")
+                return None
+    except Exception as e:
+        logger.error(f"Error creating relationship: {str(e)}")
+        raise e
+
+def get_subgraph(node_ids, rel_types=None, max_depth=1):
+    """
+    获取以指定节点为起点的子图
+    
+    Args:
+        node_ids: 节点ID列表
+        rel_types: 关系类型列表(可选)
+        max_depth: 最大深度,默认为1
+        
+    Returns:
+        包含节点和关系的字典
+    """
+    try:
+        # 处理节点ID列表
+        node_ids_str = ', '.join([str(nid) for nid in node_ids])
+        
+        # 处理关系类型过滤
+        rel_filter = ''
+        if rel_types:
+            rel_types_str = '|'.join(rel_types)
+            rel_filter = f":{rel_types_str}"
+        
+        # 构建Cypher语句
+        cypher = f"""
+        MATCH path = (n)-[r{rel_filter}*0..{max_depth}]-(m)
+        WHERE id(n) IN [{node_ids_str}]
+        RETURN path
+        """
+        
+        # 执行查询
+        with connect_graph().session() as session:
+            result = session.run(cypher)
+            
+            # 处理结果为图谱数据
+            nodes = {}
+            relationships = {}
+            
+            for record in result:
+                path = record["path"]
+                
+                # 处理节点
+                for node in path.nodes:
+                    if node.id not in nodes:
+                        node_dict = dict(node)
+                        node_dict['id'] = node.id
+                        node_dict['labels'] = list(node.labels)
+                        nodes[node.id] = node_dict
+                
+                # 处理关系
+                for rel in path.relationships:
+                    if rel.id not in relationships:
+                        rel_dict = dict(rel)
+                        rel_dict['id'] = rel.id
+                        rel_dict['type'] = rel.type
+                        rel_dict['source'] = rel.start_node.id
+                        rel_dict['target'] = rel.end_node.id
+                        relationships[rel.id] = rel_dict
+            
+            # 转换为列表形式
+            graph_data = {
+                'nodes': list(nodes.values()),
+                'relationships': list(relationships.values())
+            }
+            
+            return graph_data
+    except Exception as e:
+        logger.error(f"Error getting subgraph: {str(e)}")
+        raise e
+
+def execute_cypher_query(cypher, params=None):
+    """
+    执行Cypher查询并返回结果
+    
+    Args:
+        cypher: Cypher查询语句
+        params: 查询参数(可选)
+        
+    Returns:
+        查询结果的列表
+    """
+    if params is None:
+        params = {}
+        
+    try:
+        with connect_graph().session() as session:
+            result = session.run(cypher, **params)
+            
+            # 处理查询结果
+            data = []
+            for record in result:
+                record_dict = {}
+                for key, value in record.items():
+                    # 节点处理
+                    if hasattr(value, 'id') and hasattr(value, 'labels') and hasattr(value, 'items'):
+                        node_dict = dict(value)
+                        node_dict['_id'] = value.id
+                        node_dict['_labels'] = list(value.labels)
+                        record_dict[key] = node_dict
+                    # 关系处理
+                    elif hasattr(value, 'id') and hasattr(value, 'type') and hasattr(value, 'start_node'):
+                        rel_dict = dict(value)
+                        rel_dict['_id'] = value.id
+                        rel_dict['_type'] = value.type
+                        rel_dict['_start_node_id'] = value.start_node.id
+                        rel_dict['_end_node_id'] = value.end_node.id
+                        record_dict[key] = rel_dict
+                    # 路径处理
+                    elif hasattr(value, 'start_node') and hasattr(value, 'end_node') and hasattr(value, 'nodes'):
+                        path_dict = {
+                            'nodes': [dict(node) for node in value.nodes],
+                            'relationships': [dict(rel) for rel in value.relationships]
+                        }
+                        record_dict[key] = path_dict
+                    # 其他类型直接转换
+                    else:
+                        record_dict[key] = value
+                data.append(record_dict)
+            
+            return data
+    except Exception as e:
+        logger.error(f"Error executing Cypher query: {str(e)}")
+        raise e 

+ 55 - 0
app/core/llm/README.md

@@ -0,0 +1,55 @@
+# LLM服务模块
+
+本模块提供了大语言模型相关的功能接口,用于支持系统中的智能代码生成、文本分析等功能。
+
+## 功能概述
+
+- **LLM基础调用服务**:提供与大语言模型通信的基础功能
+- **代码生成服务**:提供基于LLM的代码自动生成功能
+
+## 主要功能
+
+### LLM基础服务 (llm_service.py)
+
+- **llm_client(content)**:调用LLM服务进行内容生成,传入提示内容,返回模型响应
+
+### 代码生成服务 (code_generation.py)
+
+- **code_generate_standard(describe, relation)**:根据描述和参数关系生成标准化代码
+
+## 使用示例
+
+```python
+# 调用LLM生成内容
+from app.core.llm import llm_client
+
+content = "将以下中文专业术语翻译成英文: 数据治理"
+result = llm_client(content)
+print(result)  # 输出: Data Governance
+
+# 生成数据标准相关代码
+from app.core.llm import code_generate_standard
+
+describe = "计算两个数的和"
+relation = {
+    "输入参数": "a: int, b: int",
+    "输出参数": "sum: int"
+}
+code = code_generate_standard(describe, relation)
+print(code)
+```
+
+## 配置说明
+
+LLM服务使用以下配置参数:
+
+- **api_key**: LLM服务的API密钥
+- **base_url**: API服务地址
+- **model_name**: 使用的模型名称
+
+在生产环境中,建议将这些参数移至环境变量或配置文件中。
+
+## 依赖说明
+
+- 需要安装`openai`包
+- 依赖标准Python日志模块 

+ 12 - 0
app/core/llm/__init__.py

@@ -0,0 +1,12 @@
+"""
+LLM服务模块
+提供大语言模型相关的功能接口
+"""
+
+from app.core.llm.llm_service import llm_client
+from app.core.llm.code_generation import code_generate_standard
+
+__all__ = [
+    'llm_client',
+    'code_generate_standard'
+] 

+ 39 - 0
app/core/llm/code_generation.py

@@ -0,0 +1,39 @@
+"""
+代码生成服务
+提供基于LLM的代码生成功能
+"""
+
+import logging
+from app.core.llm.llm_service import llm_client
+
+logger = logging.getLogger("app")
+
+def code_generate_standard(describe, relation):
+    """
+    生成数据标准相关的代码
+    
+    Args:
+        describe: 描述文本
+        relation: 关系字典,包含输入和输出参数
+        
+    Returns:
+        str: 生成的代码
+    """
+    try:
+        prompt = f"""
+        请根据以下描述和参数生成一个标准的Python函数:
+        
+        描述: {describe}
+        
+        输入参数: {relation['输入参数']}
+        
+        输出参数: {relation['输出参数']}
+        
+        请提供标准实现的Python代码。
+        """
+        
+        result = llm_client(prompt)
+        return result if result else "代码生成失败,请重试"
+    except Exception as e:
+        logger.error(f"代码生成失败: {str(e)}")
+        return f"代码生成错误: {str(e)}" 

+ 42 - 0
app/core/llm/llm_service.py

@@ -0,0 +1,42 @@
+"""
+LLM基础服务
+提供与大语言模型通信的基础功能
+"""
+
+import logging
+from openai import OpenAI
+
+logger = logging.getLogger("app")
+
+# LLM客户端配置
+api_key = "sk-86d4622141d74e9a8d7c38ee873c4d91"
+base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
+model_name = "qwen-turbo"
+
+def llm_client(content):
+    """
+    调用LLM服务进行内容生成
+    
+    Args:
+        content: 输入提示内容
+        
+    Returns:
+        str: LLM响应内容
+    """
+    client = OpenAI(
+        api_key=api_key,
+        base_url=base_url
+    )
+
+    try:
+        completion = client.chat.completions.create(
+            model=model_name,
+            messages=[
+                {"role": "system", "content": "You are a helpful assistant."},
+                {"role": "user", "content": content}
+            ]
+        )
+        return completion.choices[0].message.content.strip()
+    except Exception as e:
+        logger.error(f"LLM调用失败: {str(e)}")
+        return None 

+ 78 - 0
app/core/meta_data/README.md

@@ -0,0 +1,78 @@
+# 元数据核心功能模块
+
+本模块提供元数据管理相关的核心业务逻辑,包括元数据查询、图谱分析、非结构化数据处理等功能。
+
+## 功能概述
+
+元数据核心模块是整个平台的基础设施之一,负责处理和管理各类元数据,包括结构化和非结构化数据。主要提供以下功能:
+
+1. **元数据查询**:支持多条件查询和过滤元数据
+2. **图谱分析**:提供元数据亲缘关系图谱和影响关系图谱
+3. **非结构化数据处理**:从文本中提取实体关系,构建知识图谱
+4. **LLM服务**:利用大语言模型进行文本翻译和内容提取
+
+## 主要功能
+
+### 元数据查询功能
+
+- **meta_list**:支持分页查询元数据列表,并可根据多种条件进行过滤
+- **handle_id_unstructured**:根据ID获取非结构化数据节点
+- **get_file_content**:从MinIO对象存储中获取文件内容
+
+### 图谱分析功能
+
+- **meta_kinship_graph**:生成展示元数据直接关联关系的图谱数据
+- **meta_impact_graph**:生成展示元数据间接影响关系的扩展图谱数据
+- **parse_entity_relation**:从文本内容中自动提取实体及其关系
+- **handle_txt_graph**:处理文本图谱的创建
+
+### 非结构化数据处理
+
+- **text_resource_solve**:处理文本资源,提取关键信息并进行中英文转换
+- **solve_unstructured_data**:解析非结构化数据文件,提取实体关系并构建知识图谱
+
+### 基础功能
+
+- **get_formatted_time**:获取格式化的当前时间
+- **llm_client**:封装了与大语言模型的交互
+- **infer_column_type**:自动推断DataFrame中各列的数据类型
+
+## 技术实现
+
+模块使用以下技术和库实现其功能:
+
+- Neo4j图数据库作为元数据和关系存储
+- MinIO对象存储作为非结构化数据存储
+- OpenAI兼容的LLM服务进行内容生成和提取
+- Pandas进行数据处理
+- Python标准库提供基础功能支持
+
+## 使用方法
+
+```python
+from app.core.meta_data import meta_list, meta_kinship_graph, solve_unstructured_data
+
+# 获取元数据列表
+meta_data_list, total_count = meta_list(page=1, page_size=10, category_filter="文档")
+print(f"共找到 {total_count} 条元数据记录")
+
+# 获取元数据亲缘关系图谱
+graph_data = meta_kinship_graph(node_id=123)
+print(f"图谱包含 {len(graph_data['nodes'])} 个节点和 {len(graph_data['relationships'])} 个关系")
+
+# 处理非结构化数据
+from app.services.minio_client import minio_client
+result = solve_unstructured_data(node_id=123, minio_client=minio_client, prefix="data")
+print(f"非结构化数据处理{'成功' if result else '失败'}")
+```
+
+## 依赖关系
+
+本模块依赖于以下组件:
+
+- app/services/neo4j_driver.py:提供Neo4j数据库连接
+- app/services/minio_client.py:提供MinIO对象存储连接
+- 外部依赖:
+  - openai:与LLM服务交互
+  - pandas:数据处理
+  - minio:对象存储访问 

+ 26 - 0
app/core/meta_data/__init__.py

@@ -0,0 +1,26 @@
+"""
+元数据核心模块
+提供元数据处理、查询、图谱分析等功能
+"""
+
+# 从meta_data.py导入所有功能
+from app.core.meta_data.meta_data import *
+
+# 定义模块导出的所有函数
+__all__ = [
+    'get_formatted_time',
+    'translate_and_parse',
+    'llm_client',
+    'infer_column_type',
+    'meta_list',
+    'handle_id_unstructured',
+    'get_file_content',
+    'parse_text',
+    'parse_keyword',
+    'text_resource_solve',
+    'meta_kinship_graph',
+    'meta_impact_graph',
+    'parse_entity_relation',
+    'handle_txt_graph',
+    'solve_unstructured_data'
+] 

+ 577 - 0
app/core/meta_data/meta_data.py

@@ -0,0 +1,577 @@
+"""
+元数据处理模块
+提供元数据管理、查询、图谱分析和非结构化数据处理的核心功能
+"""
+
+import time
+import logging
+from app.services.neo4j_driver import neo4j_driver
+import ast
+import re
+import pandas as pd
+from minio import S3Error
+from py2neo import Relationship
+import json
+import io
+import random
+import string
+import numpy as np
+from openai import OpenAI
+
+logger = logging.getLogger("app")
+
+# LLM客户端配置
+api_key = "sk-86d4622141d74e9a8d7c38ee873c4d91"
+base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
+model_name = "qwen-turbo"
+
+def get_formatted_time():
+    """获取格式化的当前时间"""
+    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
+
+def translate_and_parse(data):
+    """转换并解析数据"""
+    if isinstance(data, dict):
+        return data
+    else:
+        return {}
+
+# LLM服务
+def llm_client(content):
+    """调用LLM服务进行内容生成"""
+    client = OpenAI(
+        api_key=api_key,
+        base_url=base_url
+    )
+
+    try:
+        completion = client.chat.completions.create(
+            model=model_name,
+            messages=[
+                {"role": "system", "content": "You are a helpful assistant."},
+                {"role": "user", "content": content}
+            ]
+        )
+        return completion.choices[0].message.content.strip()
+    except Exception as e:
+        logger.error(f"LLM调用失败: {str(e)}")
+        return None
+
+def infer_column_type(df):
+    """推断DataFrame的列类型"""
+    column_types = {}
+    for column in df.columns:
+        if df[column].dtype == 'object':
+            # 如果列是对象类型,尝试判断是否为日期或字符串
+            if pd.to_datetime(df[column], errors='coerce').notna().all():
+                column_types[column] = 'datetime'
+            else:
+                column_types[column] = 'varchar(255)'
+        elif pd.api.types.is_integer_dtype(df[column]):
+            column_types[column] = 'int'
+        elif pd.api.types.is_float_dtype(df[column]):
+            column_types[column] = 'float'
+        elif pd.api.types.is_bool_dtype(df[column]):
+            column_types[column] = 'boolean'
+        else:
+            column_types[column] = 'varchar(255)'
+    return column_types
+
+def meta_list(page, page_size, search="", en_name_filter=None, 
+             name_filter=None, category_filter=None, time_filter=None, tag_filter=None):
+    """
+    获取元数据列表
+    
+    Args:
+        page: 当前页码
+        page_size: 每页数量
+        search: 搜索关键词
+        en_name_filter: 英文名称过滤
+        name_filter: 名称过滤
+        category_filter: 分类过滤
+        time_filter: 时间过滤
+        tag_filter: 标签过滤
+    
+    Returns:
+        tuple: (result_list, total_count)
+    """
+    try:
+        with neo4j_driver.get_session() as session:
+            # 构建查询条件
+            match_clause = "MATCH (n:Metadata)"
+            where_conditions = []
+            
+            if search:
+                where_conditions.append(f"n.name CONTAINS '{search}'")
+            
+            if en_name_filter:
+                where_conditions.append(f"n.en_name CONTAINS '{en_name_filter}'")
+                
+            if name_filter:
+                where_conditions.append(f"n.name CONTAINS '{name_filter}'")
+                
+            if category_filter:
+                where_conditions.append(f"n.category = '{category_filter}'")
+                
+            if time_filter:
+                where_conditions.append(f"n.createTime CONTAINS '{time_filter}'")
+                
+            if tag_filter:
+                where_conditions.append(f"n.tag = '{tag_filter}'")
+            
+            where_clause = " WHERE " + " AND ".join(where_conditions) if where_conditions else ""
+            
+            # 计算总数
+            count_cypher = f"{match_clause}{where_clause} RETURN count(n) as count"
+            count_result = session.run(count_cypher)
+            total_count = count_result.single()["count"]
+            
+            # 分页查询
+            skip = (page - 1) * page_size
+            cypher = f"""
+            {match_clause}{where_clause}
+            RETURN n
+            ORDER BY n.name
+            SKIP {skip} LIMIT {page_size}
+            """
+            result = session.run(cypher)
+            
+            # 格式化结果
+            result_list = []
+            for record in result:
+                node = dict(record["n"])
+                node["id"] = record["n"].id
+                result_list.append(node)
+                
+            return result_list, total_count
+    except Exception as e:
+        logger.error(f"获取元数据列表失败: {str(e)}")
+        raise
+
+def handle_id_unstructured(node_id):
+    """处理非结构化数据节点"""
+    try:
+        with neo4j_driver.get_session() as session:
+            query = "MATCH (n) WHERE id(n) = $node_id RETURN n"
+            result = session.run(query, node_id=int(node_id))
+            node = result.single()
+            if node:
+                return dict(node["n"])
+            else:
+                return None
+    except Exception as e:
+        logger.error(f"处理非结构化数据节点失败: {str(e)}")
+        raise
+
+def get_file_content(minio_client, bucket_name, object_name):
+    """从MinIO获取文件内容"""
+    try:
+        # 获取对象
+        response = minio_client.get_object(bucket_name, object_name)
+        
+        # 读取内容
+        file_content = response.read().decode('utf-8')
+        return file_content
+    except S3Error as e:
+        logger.error(f"MinIO访问失败: {str(e)}")
+        raise
+    finally:
+        response.close()
+        response.release_conn()
+
+def parse_text(text):
+    """解析文本内容,提取关键信息"""
+    # 提取作者信息
+    author_match = re.search(r'作者[::]\s*(.+?)[\n\r]', text)
+    author = author_match.group(1) if author_match else ""
+    
+    # 提取关键词
+    keyword_match = re.search(r'关键词[::]\s*(.+?)[\n\r]', text)
+    keywords = keyword_match.group(1) if keyword_match else ""
+    
+    return {
+        "author": author.strip(),
+        "keywords": keywords.strip()
+    }
+
+def parse_keyword(content):
+    """解析关键词"""
+    if "," in content:
+        return content.split(",")
+    elif "," in content:
+        return content.split(",")
+    elif "、" in content:
+        return content.split("、")
+    else:
+        return [content]
+
+def text_resource_solve(receiver, name, keyword):
+    """处理文本资源解析"""
+    try:
+        # 构建提示词
+        prompt = f"""将以下中文内容翻译成英文,要求:
+        1. 保持原意,语法正确,符合英文表达习惯
+        2. 专业术语保持精准
+        中文内容: {name}
+        """
+        
+        # 调用LLM获取英文翻译
+        english_name = llm_client(prompt)
+        
+        # 提取关键词
+        keywords = parse_keyword(keyword)
+        
+        # 为每个关键词获取英文翻译
+        keywords_en = []
+        for kw in keywords:
+            prompt = f"将以下中文专业术语翻译成英文: {kw}"
+            kw_en = llm_client(prompt)
+            keywords_en.append(kw_en)
+            
+        # 构建返回数据
+        return {
+            "name": name,
+            "en_name": english_name,
+            "keywords": keywords,
+            "keywords_en": keywords_en
+        }
+    except Exception as e:
+        logger.error(f"文本资源处理失败: {str(e)}")
+        raise
+
+def meta_kinship_graph(node_id):
+    """
+    获取元数据亲缘关系图谱
+    
+    Args:
+        node_id: 元数据节点ID
+    
+    Returns:
+        dict: 图谱数据
+    """
+    try:
+        with neo4j_driver.get_session() as session:
+            # 获取节点和直接关系
+            cypher = """
+            MATCH (n)-[r]-(m)
+            WHERE id(n) = $node_id
+            RETURN n, r, m
+            """
+            result = session.run(cypher, node_id=int(node_id))
+            
+            # 格式化结果为图谱数据
+            nodes = {}
+            relationships = []
+            
+            for record in result:
+                # 处理源节点
+                source_node = dict(record["n"])
+                source_node["id"] = record["n"].id
+                nodes[source_node["id"]] = source_node
+                
+                # 处理目标节点
+                target_node = dict(record["m"])
+                target_node["id"] = record["m"].id
+                nodes[target_node["id"]] = target_node
+                
+                # 处理关系
+                rel = record["r"]
+                relationship = {
+                    "id": rel.id,
+                    "source": record["n"].id,
+                    "target": record["m"].id,
+                    "type": rel.type
+                }
+                relationships.append(relationship)
+            
+            # 转换为列表
+            nodes_list = list(nodes.values())
+            
+            return {
+                "nodes": nodes_list,
+                "relationships": relationships
+            }
+    except Exception as e:
+        logger.error(f"获取元数据亲缘关系图谱失败: {str(e)}")
+        raise
+
+def meta_impact_graph(node_id):
+    """
+    获取元数据影响关系图谱
+    
+    Args:
+        node_id: 元数据节点ID
+    
+    Returns:
+        dict: 图谱数据
+    """
+    try:
+        with neo4j_driver.get_session() as session:
+            # 获取所有可达节点和关系
+            cypher = """
+            MATCH path = (n)-[*1..3]-(m)
+            WHERE id(n) = $node_id
+            RETURN path
+            """
+            result = session.run(cypher, node_id=int(node_id))
+            
+            # 格式化结果
+            nodes = {}
+            relationships = set()
+            
+            for record in result:
+                path = record["path"]
+                
+                # 处理路径中的所有节点
+                for node in path.nodes:
+                    node_dict = dict(node)
+                    node_dict["id"] = node.id
+                    nodes[node.id] = node_dict
+                
+                # 处理路径中的所有关系
+                for rel in path.relationships:
+                    relationship = (rel.id, rel.start_node.id, rel.end_node.id, rel.type)
+                    relationships.add(relationship)
+            
+            # 转换为列表
+            nodes_list = list(nodes.values())
+            relationships_list = [
+                {
+                    "id": rel[0],
+                    "source": rel[1],
+                    "target": rel[2],
+                    "type": rel[3]
+                }
+                for rel in relationships
+            ]
+            
+            return {
+                "nodes": nodes_list,
+                "relationships": relationships_list
+            }
+    except Exception as e:
+        logger.error(f"获取元数据影响关系图谱失败: {str(e)}")
+        raise
+
+def parse_entity_relation(text):
+    """从文本中解析实体关系"""
+    try:
+        # 构建提示词
+        prompt = f"""
+        请从以下文本中提取实体及其关系,以JSON格式返回,格式为:
+        [
+          {{"entity1": "实体1", "relation": "关系", "entity2": "实体2"}}
+        ]
+        
+        文本内容:
+        {text}
+        """
+        
+        # 调用LLM获取关系提取结果
+        result = llm_client(prompt)
+        
+        # 解析JSON结果
+        try:
+            relations = json.loads(result)
+            return relations
+        except json.JSONDecodeError:
+            logger.error(f"关系提取结果JSON解析失败: {result}")
+            return []
+            
+    except Exception as e:
+        logger.error(f"实体关系提取失败: {str(e)}")
+        return []
+        
+def handle_txt_graph(node_id, entity, entity_en):
+    """处理文本图谱创建"""
+    try:
+        # 创建实体节点
+        with neo4j_driver.get_session() as session:
+            # 查找源节点
+            query = "MATCH (n) WHERE id(n) = $node_id RETURN n"
+            result = session.run(query, node_id=int(node_id))
+            source_node = result.single()["n"]
+            
+            if not source_node:
+                return False
+                
+            # 创建实体节点
+            cypher = """
+            MERGE (e:Entity {name: $name, en_name: $en_name})
+            ON CREATE SET e.createTime = $create_time
+            RETURN e
+            """
+            
+            create_time = get_formatted_time()
+            result = session.run(
+                cypher,
+                name=entity,
+                en_name=entity_en,
+                create_time=create_time
+            )
+            
+            entity_node = result.single()["e"]
+            
+            # 创建关系
+            if source_node and entity_node:
+                # 检查关系是否已存在
+                rel_check = """
+                MATCH (s)-[r:CONTAINS]->(e) 
+                WHERE id(s) = $source_id AND id(e) = $entity_id
+                RETURN r
+                """
+                
+                rel_result = session.run(
+                    rel_check,
+                    source_id=source_node.id,
+                    entity_id=entity_node.id
+                )
+                
+                # 如果关系不存在,则创建
+                if not rel_result.single():
+                    rel_create = """
+                    MATCH (s), (e) 
+                    WHERE id(s) = $source_id AND id(e) = $entity_id
+                    CREATE (s)-[r:CONTAINS]->(e)
+                    RETURN r
+                    """
+                    
+                    session.run(
+                        rel_create,
+                        source_id=source_node.id,
+                        entity_id=entity_node.id
+                    )
+                    
+            return True
+    except Exception as e:
+        logger.error(f"文本图谱处理失败: {str(e)}")
+        return False
+
+def solve_unstructured_data(node_id, minio_client, prefix):
+    """处理非结构化数据并提取实体关系"""
+    try:
+        # 获取节点数据
+        node_data = handle_id_unstructured(node_id)
+        if not node_data:
+            logger.error(f"节点不存在: {node_id}")
+            return False
+            
+        # 获取对象路径
+        object_name = node_data.get('objectName')
+        if not object_name:
+            logger.error(f"文档路径不存在: {node_id}")
+            return False
+            
+        # 获取文件内容
+        file_content = get_file_content(minio_client, bucket_name=node_data.get('bucket_name', 'dataops'), object_name=object_name)
+        
+        # 解析文本内容中的实体关系
+        relations = parse_entity_relation(file_content[:5000])  # 只处理前5000字符,避免过大内容
+        
+        # 如果成功提取了关系
+        if relations:
+            # 更新节点信息
+            with neo4j_driver.get_session() as session:
+                update_cypher = """
+                MATCH (n) WHERE id(n) = $node_id
+                SET n.processed = true, n.processTime = $process_time
+                RETURN n
+                """
+                
+                process_time = get_formatted_time()
+                session.run(
+                    update_cypher,
+                    node_id=int(node_id),
+                    process_time=process_time
+                )
+                
+                # 为每个提取的关系创建实体和关系
+                for relation in relations:
+                    entity1 = relation.get("entity1", "")
+                    relation_type = relation.get("relation", "")
+                    entity2 = relation.get("entity2", "")
+                    
+                    if entity1 and entity2 and relation_type:
+                        # 翻译实体名称为英文
+                        entity1_en = llm_client(f"将以下中文专业术语翻译成英文: {entity1}")
+                        entity2_en = llm_client(f"将以下中文专业术语翻译成英文: {entity2}")
+                        
+                        # 创建第一个实体
+                        entity1_cypher = """
+                        MERGE (e:Entity {name: $name})
+                        ON CREATE SET e.en_name = $en_name, e.createTime = $create_time
+                        RETURN e
+                        """
+                        
+                        entity1_result = session.run(
+                            entity1_cypher,
+                            name=entity1,
+                            en_name=entity1_en,
+                            create_time=process_time
+                        )
+                        entity1_node = entity1_result.single()["e"]
+                        
+                        # 创建第二个实体
+                        entity2_cypher = """
+                        MERGE (e:Entity {name: $name})
+                        ON CREATE SET e.en_name = $en_name, e.createTime = $create_time
+                        RETURN e
+                        """
+                        
+                        entity2_result = session.run(
+                            entity2_cypher,
+                            name=entity2,
+                            en_name=entity2_en,
+                            create_time=process_time
+                        )
+                        entity2_node = entity2_result.single()["e"]
+                        
+                        # 创建它们之间的关系
+                        rel_cypher = """
+                        MATCH (e1:Entity), (e2:Entity)
+                        WHERE id(e1) = $entity1_id AND id(e2) = $entity2_id
+                        MERGE (e1)-[r:`{relation_type}`]->(e2)
+                        RETURN r
+                        """.replace("{relation_type}", relation_type)
+                        
+                        session.run(
+                            rel_cypher,
+                            entity1_id=entity1_node.id,
+                            entity2_id=entity2_node.id
+                        )
+                        
+                        # 创建源节点与实体的关系
+                        source_rel1_cypher = """
+                        MATCH (s), (e:Entity)
+                        WHERE id(s) = $source_id AND id(e) = $entity_id
+                        MERGE (s)-[r:CONTAINS]->(e)
+                        RETURN r
+                        """
+                        
+                        session.run(
+                            source_rel1_cypher,
+                            source_id=int(node_id),
+                            entity_id=entity1_node.id
+                        )
+                        
+                        source_rel2_cypher = """
+                        MATCH (s), (e:Entity)
+                        WHERE id(s) = $source_id AND id(e) = $entity_id
+                        MERGE (s)-[r:CONTAINS]->(e)
+                        RETURN r
+                        """
+                        
+                        session.run(
+                            source_rel2_cypher,
+                            source_id=int(node_id),
+                            entity_id=entity2_node.id
+                        )
+            
+            return True
+        else:
+            logger.warning(f"未能从文本中提取到实体关系: {node_id}")
+            return False
+            
+    except Exception as e:
+        logger.error(f"处理非结构化数据失败: {str(e)}")
+        return False 

+ 57 - 0
app/core/production_line/README.md

@@ -0,0 +1,57 @@
+# 生产线核心功能模块
+
+本模块包含生产线相关的核心业务逻辑功能实现。
+
+## 功能概述
+
+生产线核心模块主要负责数据模型、数据资源和数据指标之间的关系构建与可视化,提供图形化展示和处理功能。
+
+## 主要功能
+
+### 生产线图谱绘制 (production_draw_graph)
+
+根据节点ID和类型绘制生产线图谱,支持以下三种类型的节点:
+- 数据模型(data_model):处理数据模型与元数据节点及数据标准之间的关系
+- 数据资源(data_resource):处理数据资源与元数据节点及数据标准之间的关系
+- 数据指标(data_metric):处理数据指标与元数据节点之间的关系
+
+## 技术实现
+
+本模块主要使用Neo4j图数据库进行数据关系的查询与处理,通过Cypher查询语言构建复杂的图谱关系。主要关系类型包括:
+
+- connection:连接关系
+- clean_model:模型清洗关系
+- clean_resource:资源清洗关系
+
+## 使用方法
+
+```python
+from app.core.production_line import production_draw_graph
+
+# 获取数据模型的图谱
+graph_data = production_draw_graph(model_id, "data_model")
+
+# 获取数据资源的图谱
+graph_data = production_draw_graph(resource_id, "data_resource")
+
+# 获取数据指标的图谱
+graph_data = production_draw_graph(metric_id, "data_metric")
+```
+
+## 返回数据结构
+
+图谱数据的格式为:
+
+```python
+{
+    "nodes": [
+        {"id": "节点ID", "text": "节点名称", "type": "节点类型"},
+        # 更多节点...
+    ],
+    "lines": [
+        {"from": "起始节点ID", "to": "目标节点ID", "text": "关系描述"},
+        # 更多连线...
+    ],
+    "rootId": "根节点ID"
+}
+``` 

+ 10 - 0
app/core/production_line/__init__.py

@@ -0,0 +1,10 @@
+"""
+Production Line core functionality module
+包含生产线相关的核心业务逻辑
+"""
+
+from app.core.production_line.production_line import production_draw_graph
+
+__all__ = [
+    'production_draw_graph',
+] 

+ 98 - 0
app/core/production_line/production_line.py

@@ -0,0 +1,98 @@
+from app.core.graph.graph_operations import connect_graph
+
+def production_draw_graph(id, type):
+    """
+    根据节点ID和类型绘制生产线图谱
+    
+    Args:
+        id: 节点ID
+        type: 节点类型(data_model, data_resource, data_metric)
+        
+    Returns:
+        dict: 包含节点、连线和根节点ID的图谱数据
+    """
+    # 数据模型
+    if type == "data_model":
+        cql = """
+        MATCH (n)
+        WHERE id(n) = $nodeId AND labels(n)[0] = "data_model"
+        MATCH (n)-[r:connection]-(m:meta_node)
+        OPTIONAL MATCH (n)-[r2:clean_model]-(d:data_standard)
+        OPTIONAL MATCH (d)-[r3:clean_model]-(m)
+        WITH 
+             collect({from: toString(id(n)), to: toString(id(m)), text: "包含"}) AS line1,
+             collect({from: toString(id(n)), to: toString(id(d)), text: "清洗"}) AS line2,
+             collect({from: toString(id(d)), to: toString(id(m)), text: "清洗"}) AS line3,
+             collect({id: toString(id(n)), text: n.name, type: "model"}) AS node1,
+             collect({id: toString(id(m)), text: m.name}) AS node2,
+             collect({id: toString(id(d)), text: d.name, type: "standard"}) AS node3,n
+        WITH apoc.coll.toSet(line1 + line2 + line3) AS lines,
+             apoc.coll.toSet(node1 + node2 + node3) AS nodes,
+             toString(id(n)) as res
+        RETURN lines,nodes,res
+        """
+        data = connect_graph.run(cql, nodeId=id).data()
+        res = {}
+        for item in data:
+            res = {
+                "nodes": [record for record in item['nodes'] if record['id']],
+                "lines": [record for record in item['lines'] if record['from'] and record['to']],
+                "rootId": item['res'],
+            }
+
+        return res
+    # 数据资源
+    elif type == "data_resource":
+        cql = """
+        MATCH (n)
+        WHERE id(n) = $nodeId AND labels(n)[0] = "data_resource"
+        MATCH (n)-[r:connection]-(m:meta_node)
+        OPTIONAL MATCH (n)-[r2:clean_resource]-(d:data_standard)
+        OPTIONAL MATCH (d)-[r3:clean_resource]-(m)
+        WITH 
+            collect({from: toString(id(n)), to: toString(id(m)), text: "包含"}) AS lines1,
+            collect({from: toString(id(n)), to: toString(id(d)), text: "清洗"}) AS lines2,
+            collect({from: toString(id(d)), to: toString(id(m)), text: "清洗"}) AS lines3,
+            collect({id: toString(id(n)), text: n.name, type: "resource"}) AS nodes1,
+            collect({id: toString(id(m)), text: m.name}) AS nodes2,
+            collect({id: toString(id(d)), text: d.name, type: "standard"}) AS nodes3,n     
+        WITH 
+            apoc.coll.toSet(lines1 + lines2 + lines3) AS lines,
+            apoc.coll.toSet(nodes1 + nodes2 + nodes3) AS nodes,
+            toString(id(n)) AS res       
+        RETURN lines, nodes, res
+        """
+        data = connect_graph.run(cql, nodeId=id).data()
+        res = {}
+        for item in data:
+            res = {
+                "nodes": [record for record in item['nodes'] if record['id']],
+                "lines": [record for record in item['lines'] if record['from'] and record['to'] ],
+                "rootId": item['res'],
+            }
+
+        return res
+    # 数据指标
+    elif type == "data_metric":
+        cql = """
+                MATCH (n)
+                WHERE id(n) = $nodeId AND labels(n)[0] = "data_metric"
+                MATCH (n)-[r:connection]-(m:meta_node)
+                WITH collect({from: toString(id(n)), to: toString(id(m)), text: "处理"}) AS line1,
+                    collect({id: toString(id(n)), text: n.name, type: "etric"}) AS node1,
+                    collect({id: toString(id(m)), text: m.name}) AS node2,n
+                WITH apoc.coll.toSet(line1) AS lines,
+                    apoc.coll.toSet(node1 + node2) AS nodes,
+                    toString(id(n)) as res
+                RETURN lines,nodes,res
+                """
+        data = connect_graph.run(cql, nodeId=id).data()
+        res = {}
+        for item in data:
+            res = {
+                "nodes": [record for record in item['nodes'] if record['id']],
+                "lines": [record for record in item['lines'] if record['from'] and record['to']],
+                "rootId": item['res'],
+            }
+
+        return res 

+ 86 - 0
app/core/system/README.md

@@ -0,0 +1,86 @@
+# 系统管理核心功能模块
+
+本模块提供系统管理相关的核心业务逻辑,包括系统健康检查、配置管理和系统信息获取等功能。
+
+## 功能概述
+
+系统管理核心模块是整个平台的基础设施之一,负责监控和管理系统的运行状态、配置和环境信息。主要提供以下功能:
+
+1. **系统健康检查**:监控和报告系统各组件的健康状态
+2. **配置管理**:获取、验证和过滤系统配置信息
+3. **系统信息收集**:获取系统运行环境的详细信息
+
+## 主要功能
+
+### 1. 系统健康检查
+
+#### 1.1 Neo4j连接检查 (check_neo4j_connection)
+
+检查与Neo4j图数据库的连接状态,确保数据库服务正常运行。
+
+#### 1.2 系统健康状态检查 (check_system_health)
+
+检查系统各核心组件的健康状态,返回包含依赖服务状态的完整报告。
+
+#### 1.3 系统信息获取 (get_system_info)
+
+收集系统运行环境的详细信息,包括操作系统、Python版本、CPU和内存使用情况等。
+
+### 2. 配置管理
+
+#### 2.1 获取系统配置 (get_system_config)
+
+获取过滤后的系统配置信息,确保不会泄露敏感信息。
+
+#### 2.2 验证配置有效性 (validate_config)
+
+验证系统配置的完整性和有效性,确保必要的配置项都已正确设置。
+
+#### 2.3 获取配置文件路径 (get_config_file_paths)
+
+获取系统中所有配置文件的路径,用于配置管理和审计。
+
+## 技术实现
+
+模块使用以下技术和库实现其功能:
+
+- 基于Python标准库实现系统信息收集
+- 使用Neo4j驱动检查数据库连接
+- 使用psutil库获取系统资源使用情况
+- 基于日志记录提供错误和警告信息
+
+## 使用方法
+
+```python
+from app.core.system import check_neo4j_connection, check_system_health, get_system_info
+
+# 检查Neo4j连接
+is_connected = check_neo4j_connection()
+print(f"Neo4j连接状态: {'正常' if is_connected else '异常'}")
+
+# 获取系统健康状态
+health_status = check_system_health()
+print(f"系统状态: {health_status['status']}")
+
+# 获取系统信息
+system_info = get_system_info()
+print(f"操作系统: {system_info['os']['name']} {system_info['os']['version']}")
+print(f"Python版本: {system_info['python']['version']}")
+print(f"CPU使用率: {system_info['resources']['cpu']['usage_percent']}%")
+```
+
+## 配置依赖
+
+模块依赖于app/config/config.py中的Config类,需要以下配置项:
+
+- NEO4J_URI: Neo4j数据库连接URI
+- NEO4J_USER: Neo4j用户名
+- NEO4J_PASSWORD: Neo4j密码
+- NEO4J_ENCRYPTED: 是否使用加密连接
+- ENVIRONMENT: 运行环境(开发、测试、生产)
+- DEBUG: 是否启用调试模式
+- PORT: 应用程序端口
+- PLATFORM: 平台标识
+- UPLOAD_FOLDER: 文件上传目录
+- BUCKET_NAME: MinIO存储桶名称
+- PREFIX: 文件前缀 

+ 34 - 0
app/core/system/__init__.py

@@ -0,0 +1,34 @@
+"""
+System Core module
+包含系统管理相关的核心业务逻辑
+"""
+
+from app.core.system.health import (
+    check_neo4j_connection,
+    check_system_health,
+    get_system_info
+)
+
+from app.core.system.config import (
+    get_system_config,
+    validate_config,
+    get_config_file_paths
+)
+
+from app.core.system.auth import (
+    register_user,
+    login_user,
+    get_user_by_username
+)
+
+__all__ = [
+    'check_neo4j_connection',
+    'check_system_health',
+    'get_system_info',
+    'get_system_config',
+    'validate_config',
+    'get_config_file_paths',
+    'register_user',
+    'login_user',
+    'get_user_by_username'
+] 

+ 284 - 0
app/core/system/auth.py

@@ -0,0 +1,284 @@
+"""
+系统用户认证模块
+提供用户注册、登录验证等功能
+"""
+
+import logging
+import base64
+import time
+import uuid
+import psycopg2
+from psycopg2 import pool
+
+logger = logging.getLogger(__name__)
+
+# PostgreSQL连接池
+pg_pool = None
+
+def get_pg_connection():
+    """
+    获取PostgreSQL数据库连接
+    
+    Returns:
+        connection: PostgreSQL连接对象
+    """
+    global pg_pool
+    
+    if pg_pool is None:
+        try:
+            # 创建连接池
+            pg_pool = psycopg2.pool.SimpleConnectionPool(
+                1, 20,
+                host="localhost",
+                database="dataops",
+                user="postgres",
+                password="postgres",
+                port="5432"
+            )
+            logger.info("PostgreSQL连接池初始化成功")
+        except Exception as e:
+            logger.error(f"PostgreSQL连接池初始化失败: {str(e)}")
+            raise
+    
+    return pg_pool.getconn()
+
+def release_pg_connection(conn):
+    """
+    释放PostgreSQL连接到连接池
+    
+    Args:
+        conn: 数据库连接对象
+    """
+    global pg_pool
+    if pg_pool and conn:
+        pg_pool.putconn(conn)
+
+def encode_password(password):
+    """
+    对密码进行base64编码
+    
+    Args:
+        password: 原始密码
+        
+    Returns:
+        str: 编码后的密码
+    """
+    return base64.b64encode(password.encode('utf-8')).decode('utf-8')
+
+def create_user_table():
+    """
+    创建用户表,如果不存在
+    
+    Returns:
+        bool: 是否成功创建
+    """
+    conn = None
+    try:
+        conn = get_pg_connection()
+        cursor = conn.cursor()
+        
+        # 创建用户表
+        create_table_query = """
+        CREATE TABLE IF NOT EXISTS users (
+            id VARCHAR(100) PRIMARY KEY,
+            username VARCHAR(50) UNIQUE NOT NULL,
+            password VARCHAR(100) NOT NULL,
+            created_at FLOAT NOT NULL,
+            last_login FLOAT,
+            is_admin BOOLEAN DEFAULT FALSE
+        );
+        """
+        cursor.execute(create_table_query)
+        
+        # 创建索引加速查询
+        create_index_query = """
+        CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
+        """
+        cursor.execute(create_index_query)
+        
+        conn.commit()
+        cursor.close()
+        
+        logger.info("用户表创建成功")
+        return True
+    except Exception as e:
+        logger.error(f"创建用户表失败: {str(e)}")
+        if conn:
+            conn.rollback()
+        return False
+    finally:
+        if conn:
+            release_pg_connection(conn)
+
+def register_user(username, password):
+    """
+    注册新用户
+    
+    Args:
+        username: 用户名
+        password: 密码
+        
+    Returns:
+        tuple: (是否成功, 消息)
+    """
+    conn = None
+    try:
+        # 确保表已创建
+        create_user_table()
+        
+        # 对密码进行编码
+        encoded_password = encode_password(password)
+        
+        # 生成用户ID
+        user_id = str(uuid.uuid4())
+        
+        conn = get_pg_connection()
+        cursor = conn.cursor()
+        
+        # 检查用户名是否存在
+        check_query = "SELECT username FROM users WHERE username = %s"
+        cursor.execute(check_query, (username,))
+        
+        if cursor.fetchone():
+            return False, "用户名已存在"
+        
+        # 创建用户
+        insert_query = """
+        INSERT INTO users (id, username, password, created_at, last_login)
+        VALUES (%s, %s, %s, %s, %s)
+        """
+        cursor.execute(
+            insert_query, 
+            (user_id, username, encoded_password, time.time(), None)
+        )
+        
+        conn.commit()
+        cursor.close()
+        
+        return True, "注册成功"
+    except Exception as e:
+        logger.error(f"用户注册失败: {str(e)}")
+        if conn:
+            conn.rollback()
+        return False, f"注册失败: {str(e)}"
+    finally:
+        if conn:
+            release_pg_connection(conn)
+
+def login_user(username, password):
+    """
+    用户登录验证
+    
+    Args:
+        username: 用户名
+        password: 密码
+        
+    Returns:
+        tuple: (是否成功, 用户信息/错误消息)
+    """
+    conn = None
+    try:
+        # 对输入的密码进行编码
+        encoded_password = encode_password(password)
+        
+        conn = get_pg_connection()
+        cursor = conn.cursor()
+        
+        # 查询用户
+        query = """
+        SELECT id, username, password, created_at, last_login, is_admin
+        FROM users WHERE username = %s
+        """
+        cursor.execute(query, (username,))
+        
+        user = cursor.fetchone()
+        
+        # 检查用户是否存在
+        if not user:
+            return False, "用户名或密码错误"
+        
+        # 验证密码
+        if user[2] != encoded_password:
+            return False, "用户名或密码错误"
+        
+        # 更新最后登录时间
+        current_time = time.time()
+        update_query = """
+        UPDATE users SET last_login = %s WHERE username = %s
+        """
+        cursor.execute(update_query, (current_time, username))
+        
+        conn.commit()
+        
+        # 构建用户信息
+        user_info = {
+            "id": user[0],
+            "username": user[1],
+            "created_at": user[3],
+            "last_login": current_time,
+            "is_admin": user[5] if len(user) > 5 else False
+        }
+        
+        cursor.close()
+        
+        return True, user_info
+    except Exception as e:
+        logger.error(f"用户登录失败: {str(e)}")
+        if conn:
+            conn.rollback()
+        return False, f"登录失败: {str(e)}"
+    finally:
+        if conn:
+            release_pg_connection(conn)
+
+def get_user_by_username(username):
+    """
+    根据用户名获取用户信息
+    
+    Args:
+        username: 用户名
+        
+    Returns:
+        dict: 用户信息(不包含密码)
+    """
+    conn = None
+    try:
+        conn = get_pg_connection()
+        cursor = conn.cursor()
+        
+        query = """
+        SELECT id, username, created_at, last_login, is_admin
+        FROM users WHERE username = %s
+        """
+        cursor.execute(query, (username,))
+        
+        user = cursor.fetchone()
+        cursor.close()
+        
+        if not user:
+            return None
+        
+        user_info = {
+            "id": user[0],
+            "username": user[1],
+            "created_at": user[2],
+            "last_login": user[3],
+            "is_admin": user[4] if user[4] is not None else False
+        }
+        
+        return user_info
+    except Exception as e:
+        logger.error(f"获取用户信息失败: {str(e)}")
+        return None
+    finally:
+        if conn:
+            release_pg_connection(conn)
+
+def init_db():
+    """
+    初始化数据库,创建用户表
+    
+    Returns:
+        bool: 是否成功初始化
+    """
+    return create_user_table() 

+ 94 - 0
app/core/system/config.py

@@ -0,0 +1,94 @@
+"""
+系统配置管理模块
+提供系统配置的获取、验证和安全过滤功能
+"""
+
+import logging
+import json
+import os
+from app.config.config import Config
+
+logger = logging.getLogger(__name__)
+
+def get_system_config():
+    """
+    获取系统配置信息
+    过滤掉敏感的配置项
+    
+    Returns:
+        dict: 过滤后的系统配置信息
+    """
+    try:
+        # 收集系统配置信息(去除敏感信息)
+        config_info = {
+            "environment": Config.ENVIRONMENT,
+            "debug_mode": Config.DEBUG,
+            "port": Config.PORT,
+            "platform": Config.PLATFORM,
+            "upload_folder": Config.UPLOAD_FOLDER,
+            "bucket_name": Config.BUCKET_NAME,
+            "prefix": Config.PREFIX,
+            "neo4j_uri": Config.NEO4J_URI,
+            "neo4j_encrypted": Config.NEO4J_ENCRYPTED,
+            # 不返回敏感信息如密码、密钥等
+        }
+        
+        return config_info
+    except Exception as e:
+        logger.error(f"获取系统配置失败: {str(e)}")
+        return {"error": str(e)}
+
+def validate_config():
+    """
+    验证系统配置的有效性
+    检查必要的配置项是否存在且有效
+    
+    Returns:
+        tuple: (是否有效, 错误信息)
+    """
+    errors = []
+    
+    # 检查Neo4j配置
+    if not hasattr(Config, 'NEO4J_URI') or not Config.NEO4J_URI:
+        errors.append("NEO4J_URI未配置")
+    if not hasattr(Config, 'NEO4J_USER') or not Config.NEO4J_USER:
+        errors.append("NEO4J_USER未配置")
+    if not hasattr(Config, 'NEO4J_PASSWORD') or not Config.NEO4J_PASSWORD:
+        errors.append("NEO4J_PASSWORD未配置")
+    
+    # 检查MinIO配置
+    if not hasattr(Config, 'MINIO_HOST') or not Config.MINIO_HOST:
+        errors.append("MINIO_HOST未配置")
+    if not hasattr(Config, 'MINIO_USER') or not Config.MINIO_USER:
+        errors.append("MINIO_USER未配置")
+    if not hasattr(Config, 'MINIO_PASSWORD') or not Config.MINIO_PASSWORD:
+        errors.append("MINIO_PASSWORD未配置")
+    
+    # 检查其他必要配置
+    if not hasattr(Config, 'UPLOAD_FOLDER') or not Config.UPLOAD_FOLDER:
+        errors.append("UPLOAD_FOLDER未配置")
+    if not os.path.isdir(Config.UPLOAD_FOLDER):
+        errors.append(f"UPLOAD_FOLDER目录不存在: {Config.UPLOAD_FOLDER}")
+    
+    return (len(errors) == 0, errors)
+
+def get_config_file_paths():
+    """
+    获取系统所有配置文件的路径
+    
+    Returns:
+        list: 配置文件路径列表
+    """
+    base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+    config_dir = os.path.join(base_dir, 'config')
+    
+    if not os.path.exists(config_dir):
+        logger.warning(f"配置目录不存在: {config_dir}")
+        return []
+    
+    config_files = []
+    for file in os.listdir(config_dir):
+        if file.endswith('.py') or file.endswith('.yaml') or file.endswith('.json'):
+            config_files.append(os.path.join(config_dir, file))
+    
+    return config_files 

+ 140 - 0
app/core/system/health.py

@@ -0,0 +1,140 @@
+"""
+系统健康检查模块
+提供系统各组件健康状态检查和系统信息获取功能
+"""
+
+import logging
+import platform
+import psutil
+import os
+import socket
+from datetime import datetime
+from app.config.config import Config
+from app.services.neo4j_driver import neo4j_driver
+
+logger = logging.getLogger(__name__)
+
+def check_neo4j_connection():
+    """
+    检查Neo4j数据库连接状态
+    
+    Returns:
+        bool: 连接成功返回True,失败返回False
+    """
+    try:
+        with neo4j_driver.get_session() as session:
+            # 执行简单查询确认连接
+            session.run("RETURN 1")
+            return True
+    except Exception as e:
+        logger.error(f"Neo4j数据库连接失败: {str(e)}")
+        return False
+
+def check_system_health():
+    """
+    检查系统整体健康状态
+    包括关键依赖组件的连接状态
+    
+    Returns:
+        dict: 包含各组件健康状态的字典
+    """
+    # 检查Neo4j连接
+    neo4j_status = check_neo4j_connection()
+    
+    # 可以添加其他组件的健康检查
+    # 例如MySQL、Redis、MinIO等
+    
+    # 构造健康状态信息
+    health_status = {
+        "service": "DataOps-platform",
+        "status": "UP" if neo4j_status else "DEGRADED",
+        "version": "1.0.0",  # 可以从配置或版本文件中读取
+        "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+        "dependencies": {
+            "neo4j": {
+                "status": "UP" if neo4j_status else "DOWN",
+                "details": {
+                    "url": Config.NEO4J_URI,
+                    "encrypted": Config.NEO4J_ENCRYPTED
+                }
+            }
+            # 可以添加其他依赖的状态
+        }
+    }
+    
+    return health_status
+
+def get_system_info():
+    """
+    获取系统运行环境信息
+    包括操作系统、Python版本、CPU使用率、内存使用情况等
+    
+    Returns:
+        dict: 包含系统信息的字典
+    """
+    try:
+        # 获取基本系统信息
+        sys_info = {
+            "os": {
+                "name": platform.system(),
+                "version": platform.version(),
+                "platform": platform.platform(),
+            },
+            "python": {
+                "version": platform.python_version(),
+                "implementation": platform.python_implementation(),
+            },
+            "network": {
+                "hostname": socket.gethostname(),
+                "ip": socket.gethostbyname(socket.gethostname()),
+            },
+            "resources": {
+                "cpu": {
+                    "cores": psutil.cpu_count(logical=False),
+                    "logical_cores": psutil.cpu_count(logical=True),
+                    "usage_percent": psutil.cpu_percent(interval=0.1),
+                },
+                "memory": {
+                    "total": _format_bytes(psutil.virtual_memory().total),
+                    "available": _format_bytes(psutil.virtual_memory().available),
+                    "used": _format_bytes(psutil.virtual_memory().used),
+                    "percent": psutil.virtual_memory().percent,
+                },
+                "disk": {
+                    "total": _format_bytes(psutil.disk_usage("/").total),
+                    "used": _format_bytes(psutil.disk_usage("/").used),
+                    "free": _format_bytes(psutil.disk_usage("/").free),
+                    "percent": psutil.disk_usage("/").percent,
+                },
+            },
+            "application": {
+                "environment": Config.ENVIRONMENT,
+                "debug_mode": Config.DEBUG,
+                "port": Config.PORT,
+                "platform": Config.PLATFORM,
+                "upload_folder": Config.UPLOAD_FOLDER,
+                "bucket_name": Config.BUCKET_NAME,
+                "prefix": Config.PREFIX,
+                # 不返回敏感信息如密码、密钥等
+            }
+        }
+        
+        return sys_info
+    except Exception as e:
+        logger.error(f"获取系统信息失败: {str(e)}")
+        return {"error": str(e)}
+
+def _format_bytes(bytes_value):
+    """
+    将字节数格式化为易读形式
+    
+    Args:
+        bytes_value: 字节数
+        
+    Returns:
+        str: 格式化后的字符串,如"1.23 GB"
+    """
+    for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
+        if bytes_value < 1024 or unit == 'TB':
+            return f"{bytes_value:.2f} {unit}"
+        bytes_value /= 1024 

+ 102 - 0
app/environment.yaml

@@ -0,0 +1,102 @@
+name: python-web
+channels:
+  - https://mirrors.sjtug.sjtu.edu.cn/anaconda/pkgs/main
+  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch/
+  - https://mirrors.sjtug.sjtu.edu.cn/anaconda/pkgs/main/
+  - https://mirrors.sjtug.sjtu.edu.cn/anaconda/pkgs/free/
+  - defaults
+  - conda-forge
+dependencies:
+  - brotlipy=0.7.0=py37h2bbff1b_1003
+  - ca-certificates=2022.4.26=haa95532_0
+  - certifi=2022.6.15=py37haa95532_0
+  - cffi=1.15.0=py37h2bbff1b_1
+  - charset-normalizer=2.0.4=pyhd3eb1b0_0
+  - click=8.0.4=py37haa95532_0
+  - colorama=0.4.4=pyhd3eb1b0_0
+  - cryptography=37.0.1=py37h21b164f_0
+  - dataclasses=0.8=pyh6d0b6a4_7
+  - flask=2.0.3=pyhd3eb1b0_0
+  - flask-cors=3.0.10=pyhd3eb1b0_0
+  - flask-json=0.3.4=pyhd3eb1b0_0
+  - flask-login=0.5.0=pyhd3eb1b0_0
+  - flask_cors=3.0.10=pyhd3eb1b0_0
+  - idna=3.3=pyhd3eb1b0_0
+  - importlib-metadata=4.11.3=py37haa95532_0
+  - itsdangerous=2.0.1=pyhd3eb1b0_0
+  - jinja2=3.0.3=pyhd3eb1b0_0
+  - markupsafe=2.0.1=py37h2bbff1b_0
+  - openssl=1.1.1q=h2bbff1b_0
+  - pip=21.2.4=py37haa95532_0
+  - pycparser=2.21=pyhd3eb1b0_0
+  - pyopenssl=22.0.0=pyhd3eb1b0_0
+  - pysocks=1.7.1=py37_1
+  - python=3.7.13=h6244533_0
+  - requests=2.28.1=py37haa95532_0
+  - schedule=1.1.0=pyhd8ed1ab_0
+  - setuptools=61.2.0=py37haa95532_0
+  - six=1.16.0=pyhd3eb1b0_1
+  - sqlite=3.38.2=h2bbff1b_0
+  - typing_extensions=4.1.1=pyh06a4308_0
+  - urllib3=1.26.9=py37haa95532_0
+  - vc=14.2=h21ff451_1
+  - vs2015_runtime=14.27.29016=h5e58377_2
+  - werkzeug=2.0.3=pyhd3eb1b0_0
+  - wheel=0.37.1=pyhd3eb1b0_0
+  - win_inet_pton=1.1.0=py37haa95532_0
+  - wincertstore=0.2=py37haa95532_2
+  - zipp=3.7.0=pyhd3eb1b0_0
+  - pip:
+    - aiohttp==3.8.1
+    - aiosignal==1.2.0
+    - astor==0.8.1
+    - async-timeout==4.0.2
+    - asynctest==0.13.0
+    - attrs==21.4.0
+    - colorlog==6.6.0
+    - datasets==2.3.2
+    - decorator==5.1.1
+    - dill==0.3.4
+    - filelock==3.7.1
+    - frozenlist==1.3.0
+    - fsspec==2022.5.0
+    - huggingface-hub==0.8.1
+    - jieba==0.42.1
+    - joblib==1.1.0
+    - lxml==4.9.1
+    - minio==7.1.11
+    - multidict==6.0.2
+    - multiprocess==0.70.12.2
+    - myapplication==0.1.0
+    - nacos-sdk-python==0.1.8
+    - numpy==1.21.6
+    - opencv-contrib-python==4.5.5.64
+    - opt-einsum==3.3.0
+    - packaging==21.3
+    - paddle-bfloat==0.1.7
+    - paddle2onnx==0.9.8
+    - paddlefsl==1.1.0
+    - paddlenlp==2.3.4
+    - paddlepaddle==2.3.1
+    - pandas==1.3.5
+    - pillow==9.1.0
+    - protobuf==3.20.0
+    - pyarrow==8.0.0
+    - pymysql==1.0.2
+    - pyparsing==3.0.9
+    - pypiwin32==223
+    - python-dateutil==2.8.2
+    - python-docx==0.8.11
+    - pytz==2022.1
+    - pywin32==304
+    - pyyaml==6.0
+    - responses==0.18.0
+    - scikit-learn==1.0.2
+    - scipy==1.7.3
+    - sentencepiece==0.1.96
+    - seqeval==1.2.2
+    - threadpoolctl==3.1.0
+    - tqdm==4.64.0
+    - xxhash==3.0.0
+    - yarl==1.7.2
+prefix: C:\ProgramData\Anaconda3\envs\python-web

+ 1 - 0
app/models/__init__.py

@@ -0,0 +1 @@
+# Models package initialization 

+ 35 - 0
app/models/result.py

@@ -0,0 +1,35 @@
+def success(data=None, message="操作成功", code=200):
+    """
+    Return a standardized success response
+    
+    Args:
+        data: The data to return
+        message: A success message
+        code: HTTP status code
+        
+    Returns:
+        dict: A standardized success response
+    """
+    return {
+        "code": code,
+        "message": message,
+        "data": data
+    }
+
+def failed(message="操作失败", code=500, data=None):
+    """
+    Return a standardized error response
+    
+    Args:
+        message: An error message
+        code: HTTP status code
+        data: Optional data to return
+        
+    Returns:
+        dict: A standardized error response
+    """
+    return {
+        "code": code,
+        "message": message,
+        "data": data
+    } 

+ 82 - 0
app/scripts/README.md

@@ -0,0 +1,82 @@
+# 数据库初始化脚本
+
+本目录包含用于初始化数据库的脚本,特别是用户认证相关的表和索引。
+
+## 用户表初始化
+
+用户数据现在存储在PostgreSQL数据库中,表名为`users`。有两种方式可以初始化用户表:
+
+### 1. 使用Python脚本
+
+运行以下命令可以自动创建用户表和相关索引:
+
+```bash
+# 在项目根目录执行
+python app/scripts/init_db.py
+```
+
+这将自动创建以下内容:
+- 用户表结构
+- 用户名索引
+
+### 2. 使用SQL脚本
+
+如果你想直接在PostgreSQL客户端中执行,可以使用提供的SQL脚本:
+
+```bash
+# 使用psql命令行工具执行
+psql -U postgres -d dataops -f app/scripts/create_user_table.sql
+
+# 或者直接在pgAdmin或其他PostgreSQL客户端中复制粘贴脚本内容执行
+```
+
+## 数据库连接配置
+
+数据库连接配置在`app/core/system/auth.py`文件中的`get_pg_connection`函数中定义:
+
+```python
+pg_pool = psycopg2.pool.SimpleConnectionPool(
+    1, 20,
+    host="localhost",
+    database="dataops",
+    user="postgres",
+    password="postgres",
+    port="5432"
+)
+```
+
+在生产环境中,建议将这些连接参数移至配置文件或环境变量中。
+
+## 用户表结构
+
+用户表(`users`)具有以下字段:
+
+- `id` (VARCHAR(100)): 用户唯一标识符,主键
+- `username` (VARCHAR(50)): 用户名,唯一非空
+- `password` (VARCHAR(100)): 密码(使用base64编码),非空
+- `created_at` (FLOAT): 创建时间戳,非空
+- `last_login` (FLOAT): 最后登录时间戳
+- `is_admin` (BOOLEAN): 是否为管理员,默认为false
+
+## 用户数据迁移
+
+如果您之前使用的是文件存储方式,可以使用以下命令将用户数据迁移到数据库:
+
+```bash
+python app/scripts/migrate_users.py
+```
+
+这个脚本会:
+1. 读取`app/data/users.json`文件中的用户数据
+2. 将数据导入到PostgreSQL数据库的`users`表中
+3. 备份原始JSON文件
+
+## 安全建议
+
+在生产环境中,建议进行以下安全增强:
+
+1. 使用更强的密码哈希算法(如bcrypt)替代base64编码
+2. 将数据库连接参数存储在环境变量或安全的配置文件中
+3. 为数据库用户设置最小权限原则
+4. 启用PostgreSQL的SSL连接
+5. 定期备份用户数据 

+ 29 - 0
app/scripts/create_user_table.sql

@@ -0,0 +1,29 @@
+-- 创建用户表SQL脚本
+-- 用于在PostgreSQL数据库中创建用户表和相关索引
+
+-- 创建用户表
+CREATE TABLE IF NOT EXISTS users (
+    id VARCHAR(100) PRIMARY KEY,
+    username VARCHAR(50) UNIQUE NOT NULL,
+    password VARCHAR(100) NOT NULL,
+    created_at FLOAT NOT NULL,
+    last_login FLOAT,
+    is_admin BOOLEAN DEFAULT FALSE
+);
+
+-- 创建索引以加快查询速度
+CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
+
+-- 创建默认管理员用户(密码为base64编码的'admin123')
+INSERT INTO users (id, username, password, created_at, is_admin)
+VALUES (
+    'admin-default-id',
+    'admin',
+    'YWRtaW4xMjM=',
+    extract(epoch from now()),
+    TRUE
+)
+ON CONFLICT (username) DO NOTHING;
+
+-- 验证是否创建成功
+SELECT username, id, created_at, is_admin FROM users; 

+ 46 - 0
app/scripts/init_db.py

@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+"""
+数据库初始化脚本
+创建用户表和相关索引
+"""
+
+import os
+import sys
+import logging
+
+# 添加项目根目录到Python路径
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+from app.core.system.auth import init_db
+
+# 配置日志
+logging.basicConfig(
+    level=logging.INFO,
+    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+)
+logger = logging.getLogger(__name__)
+
+def init_database():
+    """
+    初始化数据库,创建必要的表和索引
+    """
+    logger.info("开始初始化数据库...")
+    
+    # 初始化用户表
+    if init_db():
+        logger.info("用户表初始化成功")
+    else:
+        logger.error("用户表初始化失败")
+    
+    # 可以在这里添加其他表的初始化
+    
+    logger.info("数据库初始化完成")
+
+if __name__ == "__main__":
+    try:
+        init_database()
+    except Exception as e:
+        logger.error(f"数据库初始化失败: {str(e)}")
+        sys.exit(1)
+    
+    sys.exit(0) 

+ 115 - 0
app/scripts/migrate_users.py

@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+"""
+用户数据迁移脚本
+将用户数据从JSON文件迁移到PostgreSQL数据库
+"""
+
+import os
+import sys
+import json
+import logging
+import time
+import psycopg2
+
+# 添加项目根目录到Python路径
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+from app.core.system.auth import init_db, get_pg_connection, release_pg_connection
+
+# 配置日志
+logging.basicConfig(
+    level=logging.INFO,
+    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+)
+logger = logging.getLogger(__name__)
+
+# 旧的用户数据文件路径
+OLD_USER_DATA_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data', 'users.json')
+
+def migrate_users():
+    """
+    将用户数据从JSON文件迁移到PostgreSQL数据库
+    """
+    logger.info("开始迁移用户数据...")
+    
+    # 确保用户表已创建
+    init_db()
+    
+    # 检查旧的用户数据文件是否存在
+    if not os.path.exists(OLD_USER_DATA_PATH):
+        logger.warning(f"用户数据文件不存在: {OLD_USER_DATA_PATH},无需迁移")
+        return
+    
+    conn = None
+    try:
+        # 读取旧的用户数据
+        with open(OLD_USER_DATA_PATH, 'r', encoding='utf-8') as f:
+            users = json.load(f)
+        
+        logger.info(f"从文件中读取了 {len(users)} 个用户")
+        
+        # 连接数据库
+        conn = get_pg_connection()
+        cursor = conn.cursor()
+        
+        migrated_count = 0
+        skipped_count = 0
+            
+        for username, user_data in users.items():
+            # 检查用户是否已存在
+            check_query = "SELECT username FROM users WHERE username = %s"
+            cursor.execute(check_query, (username,))
+            
+            if cursor.fetchone():
+                logger.info(f"用户 {username} 已存在,跳过")
+                skipped_count += 1
+                continue
+            
+            # 创建用户
+            insert_query = """
+            INSERT INTO users (id, username, password, created_at, last_login, is_admin)
+            VALUES (%s, %s, %s, %s, %s, %s)
+            """
+            
+            cursor.execute(
+                insert_query,
+                (
+                    user_data.get('id', f"migrated-{time.time()}"),
+                    username,
+                    user_data.get('password', ''),
+                    user_data.get('created_at', time.time()),
+                    user_data.get('last_login'),
+                    user_data.get('is_admin', False)
+                )
+            )
+            
+            migrated_count += 1
+            logger.info(f"已迁移用户: {username}")
+        
+        conn.commit()
+        cursor.close()
+        
+        logger.info(f"迁移完成: 成功迁移 {migrated_count} 个用户,跳过 {skipped_count} 个用户")
+        
+        # 备份旧文件
+        backup_path = f"{OLD_USER_DATA_PATH}.bak.{int(time.time())}"
+        os.rename(OLD_USER_DATA_PATH, backup_path)
+        logger.info(f"已备份旧用户数据文件到: {backup_path}")
+        
+    except Exception as e:
+        logger.error(f"迁移用户数据失败: {str(e)}")
+        if conn:
+            conn.rollback()
+        raise
+    finally:
+        if conn:
+            release_pg_connection(conn)
+
+if __name__ == "__main__":
+    try:
+        migrate_users()
+    except Exception as e:
+        logger.error(f"迁移失败: {str(e)}")
+        sys.exit(1)
+    
+    sys.exit(0) 

+ 1 - 0
app/services/__init__.py

@@ -0,0 +1 @@
+# Services package initialization 

+ 17 - 0
app/services/db_healthcheck.py

@@ -0,0 +1,17 @@
+from sqlalchemy import text
+from sqlalchemy.exc import OperationalError
+from app.config.config import Config
+from app import db
+import logging
+
+# Set up logger
+logger = logging.getLogger(__name__)
+
+def check_db_connection():
+    try:
+        with db.engine.connect() as conn:
+            conn.execute(text("SELECT 1"))
+            return True
+    except OperationalError as e:
+        logger.error(f"数据库连接失败: {str(e)}")
+        return False 

+ 22 - 0
app/services/neo4j_driver.py

@@ -0,0 +1,22 @@
+from neo4j import GraphDatabase
+from app.config.config import Config
+
+class Neo4jDriver:
+    def __init__(self):
+        self._driver = GraphDatabase.driver(
+            Config.NEO4J_URI,
+            auth=(Config.NEO4J_USER, Config.NEO4J_PASSWORD),
+            encrypted=Config.NEO4J_ENCRYPTED,
+            max_connection_pool_size=20,  # 根据负载调整
+            connection_timeout=30,  # 秒
+            connection_acquisition_timeout=60  # 秒
+        )
+    
+    def get_session(self):
+        return self._driver.session()
+    
+    def close(self):
+        self._driver.close()
+
+# 单例实例
+neo4j_driver = Neo4jDriver() 

+ 251 - 0
app/services/package_function.py

@@ -0,0 +1,251 @@
+# 封装mysql执行函数、创建节点函数
+from app.core.graph.graph_operations import mysql_configs
+from app.core.graph.graph_operations import connect_graph
+from py2neo import Node, RelationshipMatch
+
+
+def execute_sql(cur, sql, params):
+    cur.execute(sql, params)
+    return cur.fetchall()
+
+
+def sql_commit(sql):
+    link = mysql_configs.POOL.connection()
+    cur = link.cursor()
+    cur.execute(sql)
+    link.commit()
+    cur.close()  # 关闭游标
+    link.close()  # 关闭连接
+
+
+def sql_execute_result(sql):
+    link = mysql_configs.POOL.connection()
+    cur = link.cursor()
+    cur.execute(sql)
+    result = cur.fetchall()
+    cur.close()  # 关闭游标
+    link.close()  # 关闭连接
+    return result
+
+
+# 创建或获取节点
+def create_or_get_node(label, **properties):
+    node = connect_graph.nodes.match(label, **properties).first()
+    if node is None:
+        node = Node(label, **properties)
+        connect_graph.create(node)
+    return node
+
+
+# 查询是否存在节点
+def get_node(label, **properties):
+    node = connect_graph.nodes.match(label, **properties).first()
+    # 如果没有找到匹配的节点,node 将会是 None
+    return node
+
+
+# 查询是否存在关系
+def relationship_exists(start_node, rel_type, end_node, **properties):
+    matcher = connect_graph.match(nodes=[start_node, end_node], r_type=rel_type)
+    # 如果需要匹配关系的属性
+    if properties:
+        matcher = matcher.where(**properties)
+    result = matcher.first()
+    return result is not None
+
+
+# 关系权重生成
+def relation_weights(relation):
+    relation_list = ['父亲', '母亲', '儿子', '女儿']
+    if relation in relation_list:
+        return 3
+    else:
+        return 1
+
+
+def workplace_weights(workplace_list, workplace):
+    if workplace in workplace_list:
+        return 3
+    else:
+        return 1
+
+
+def soure_organization_name(workplace):
+    query = f"match (n:workplace)<-[r:workin]-(subordinate_person:worker)" \
+            f"WHERE n.organization_no = '{workplace}' " \
+            f"return subordinate_person.code as code"
+    result = connect_graph.run(query).data()
+    return result
+
+
+# 输入人员编码列表,得到员工与工作单位的关系,并且在此函数内完成员工,亲属,以及人-工作单位关系的创建
+def create_person_workplace(code_list, flag, relatives_type):
+    nodes = []
+    links = []
+
+    condition = tuple(map(int, relatives_type.split(",")))
+    relation_dict = {
+        (0, 0, 0, 0): lambda: [],
+        (0, 0, 0, 1): lambda: [],
+        (0, 0, 1, 0): lambda: [],
+        (0, 1, 0, 0): lambda: person_relative(links, code_list, 0),
+        (0, 1, 0, 1): lambda: person_relative(links, code_list, 0),
+        (0, 1, 1, 0): lambda: person_relative(links, code_list, 0),
+        (0, 1, 1, 1): lambda: person_relative(links, code_list, 0),
+        (1, 0, 0, 0): lambda: person_relative(links, code_list, 1),
+        (1, 0, 0, 1): lambda: person_relative(links, code_list, 1),
+        (1, 0, 1, 0): lambda: person_relative(links, code_list, 1),
+        (1, 0, 1, 1): lambda: person_relative(links, code_list, 1),
+        (1, 1, 0, 0): lambda: person_relative(links, code_list, (0, 1)),
+        (1, 1, 0, 1): lambda: person_relative(links, code_list, (0, 1)),
+        (1, 1, 1, 0): lambda: person_relative(links, code_list, (0, 1)),
+        (1, 1, 1, 1): lambda: person_relative(links, code_list, (0, 1))
+    }
+
+    query = """
+        MATCH (n:worker)-[r:relatives]-(m:worker), (n)-[:workin]-(wrk_n:workplace), (m)-[:workin]-(wrk_m:workplace)
+        WHERE n.code IN $codes
+        RETURN 
+            n.name as employee,
+            id(n) as id_n,
+            wrk_n.name as employee_workplace,
+            id(wrk_n) as id_wrk_n,
+            m.name as relatives,
+            id(m) as id_m,
+            wrk_m.name as relatives_workplace,
+            id(wrk_m) as id_wrk_m,
+            CASE WHEN exists(wrk_m.organization_no) THEN 1 ELSE 0 END as relatives_status
+    """
+    result = connect_graph.run(query, codes=code_list).data()
+    handle_function = relation_dict.get(condition, [])
+
+    for row in result:
+        employee = row['employee']
+        id_employee = row['id_n']
+        employee_workplace = row['employee_workplace']
+        id_employee_workplace = row['id_wrk_n']
+        relatives = row['relatives']
+        id_relatives = row['id_m']
+        relatives_workplace = row['relatives_workplace']
+        id_relatives_workplace = row['id_wrk_m']
+        relatives_status = row['relatives_status']
+
+        nodes.extend(create_node(employee, id_employee, 'selected'))
+        nodes.extend(create_node(employee_workplace, id_employee_workplace,
+                                 'work_place_selected' if flag else 'internel_work_place'))
+        links.extend(create_relation(id_employee, id_employee_workplace, 'work_in'))
+        temp_node, temp_link = handle_condition(condition, relatives, id_relatives, relatives_workplace,
+                                                id_relatives_workplace, relatives_status)
+        nodes.extend(temp_node)
+        links.extend(temp_link)
+
+    if condition[0] != 0 or condition[1] != 0:
+        links.extend(handle_function())
+    return nodes, links
+
+
+# 处理不同筛选条件的节点/关系
+def handle_condition(condition, relatives, id_relatives, relatives_workplace, id_relatives_workplace, relatives_status):
+    nodes = []
+    links = []
+    if condition == (0, 0, 0, 1):
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace, '' if relatives_status else 'externel_work_place'))
+    elif condition == (0, 0, 1, 0):
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace, 'internel_work_place' if relatives_status else ''))
+    elif condition == (0, 0, 1, 1):
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace,
+                        'internel_work_place' if relatives_status else 'externel_work_place'))
+    elif condition == (0, 1, 0, 0):
+        nodes.extend(create_node(relatives, id_relatives, 'external_relatives' if relatives_status == 0 else ''))
+    elif condition == (0, 1, 0, 1):
+        nodes.extend(create_node(relatives, id_relatives, '' if relatives_status else 'external_relatives'))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace, '' if relatives_status else 'externel_work_place'))
+        links.extend(create_relation(id_relatives, id_relatives_workplace if relatives_status == 0 else '', 'work_in'))
+    elif condition == (0, 1, 1, 0):
+        nodes.extend(create_node(relatives, id_relatives, '' if relatives_status else 'external_relatives'))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace, 'internel_work_place' if relatives_status else ''))
+    elif condition == (0, 1, 1, 1):
+        nodes.extend(create_node(relatives, id_relatives, '' if relatives_status else 'external_relatives'))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace,
+                        'internel_work_place' if relatives_status else 'externel_work_place'))
+        links.extend(create_relation(id_relatives, id_relatives_workplace if relatives_status == 0 else '', 'work_in'))
+    elif condition == (1, 0, 0, 0):
+        nodes.extend(create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else ''))
+    elif condition == (1, 0, 0, 1):
+        nodes.extend(create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else ''))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace, '' if relatives_status else 'externel_work_place'))
+    elif condition == (1, 0, 1, 0):
+        nodes.extend(create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else ''))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace, 'internel_work_place' if relatives_status else ''))
+        links.extend(create_relation(id_relatives, id_relatives_workplace if relatives_status else '', 'work_in'))
+    elif condition == (1, 0, 1, 1):
+        nodes.extend(create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else ''))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace,
+                        'internel_work_place' if relatives_status else 'externel_work_place'))
+        links.extend(create_relation(id_relatives, id_relatives_workplace if relatives_status else '', 'work_in'))
+    elif condition == (1, 1, 0, 0):
+        nodes.extend(
+            create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else 'external_relatives'))
+    elif condition == (1, 1, 0, 1):
+        nodes.extend(
+            create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else 'external_relatives'))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace,
+                        'externel_work_place' if relatives_status == 0 else ''))
+        links.extend(create_relation(id_relatives, id_relatives_workplace if relatives_status == 0 else '', 'work_in'))
+    elif condition == (1, 1, 1, 0):
+        nodes.extend(
+            create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else 'external_relatives'))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace, 'internel_work_place' if relatives_status else ''))
+        links.extend(create_relation(id_relatives, id_relatives_workplace if relatives_status else '', 'work_in'))
+    elif condition == (1, 1, 1, 1):
+        nodes.extend(
+            create_node(relatives, id_relatives, 'internal_relatives' if relatives_status else 'external_relatives'))
+        nodes.extend(
+            create_node(relatives_workplace, id_relatives_workplace,
+                        'internel_work_place' if relatives_status else 'externel_work_place'))
+        links.extend(create_relation(id_relatives, id_relatives_workplace, 'work_in'))
+    return nodes, links
+
+
+# 创建节点
+def create_node(name, nodeid, node_type):
+    if name in (None, '无') or node_type == '':
+        return []
+    return [{'name': name, 'id': nodeid, 'type': node_type}]
+
+
+# 创建关系
+def create_relation(start, end, relation_type):
+    if end in (None, '无', ''):
+        return []
+    return [{"source": start, "target": end, "type": relation_type}]
+
+
+# 创建员工和亲属的关系
+def person_relative(links, code_list, status):
+    query = """
+    MATCH (n:worker)-[r:relatives]-(m:worker)
+    WHERE n.code IN $codes
+    {}
+    RETURN id(STARTNODE(r)) AS startnode, r.content AS content, id(ENDNODE(r)) AS endnode
+    """.format("WITH CASE WHEN exists(m.code) THEN 1 ELSE 0 END AS status,r "
+               "WHERE status = $relatives_status" if isinstance(status, int) else "")
+
+    result = connect_graph.run(query, codes=code_list, relatives_status=status).data()
+    for row in result:
+        startnode = row['startnode']
+        endnode = row['endnode']
+        content = row['content']
+        links.extend(create_relation(startnode, endnode, content))
+    return links

+ 13 - 0
application.py

@@ -0,0 +1,13 @@
+from app import create_app
+from app.config.config import Config  # 新增配置导入
+
+app = create_app()
+
+if __name__ == '__main__':
+    # 修改后带端口配置的启动命令
+    app.run(
+        host='0.0.0.0',
+        port=Config.PORT,  # 从配置类获取端口
+        debug=Config.DEBUG,
+        use_reloader=False if Config.PLATFORM == 'linux' else True
+    ) 

TEMPAT SAMPAH
pythonweb开发说明.doc


+ 16 - 0
requirements.txt

@@ -0,0 +1,16 @@
+Flask==3.0.2
+Flask-Cors>=4.0.0
+# mysqlclient==2.1.1
+psycopg2-binary==2.9.10
+neo4j>=5.0.0
+py2neo>=2021.2.0
+pandas>=1.3.0
+numpy>=1.20.0
+minio>=7.0.0
+openai>=1.0.0
+PyPDF2>=3.0.0
+python-docx>=0.8.11
+SQLAlchemy>=2.0.0
+PyYAML>=6.0
+python-dateutil>=2.8.0
+psutil>=6.0.0