Browse Source

添加翻译接口
修改DataMeta创建接口
修改DataResource列表接口。序列化操作。
添加翻译接口文档

maxiaolong 2 tuần trước cách đây
mục cha
commit
2949005d0d

+ 12 - 12
app/api/meta_data/routes.py

@@ -134,7 +134,7 @@ def meta_node_edit():
         with neo4j_driver.get_session() as session:
             # 查询节点信息
             cypher = """
-            MATCH (n:meta_data)
+            MATCH (n:DataMeta)
             WHERE id(n) = $node_id
             RETURN n
             """
@@ -150,7 +150,7 @@ def meta_node_edit():
             
             # 获取标签信息
             tag_cypher = """
-            MATCH (n:meta_data)-[:label]->(t:DataLabel)
+            MATCH (n:DataMeta)-[:label]->(t:DataLabel)
             WHERE id(n) = $node_id
             RETURN t
             """
@@ -159,7 +159,7 @@ def meta_node_edit():
             
             # 获取主数据信息
             master_data_cypher = """
-            MATCH (n:meta_data)-[:master_data]->(m:master_data)
+            MATCH (n:DataMeta)-[:master_data]->(m:master_data)
             WHERE id(n) = $node_id
             RETURN m
             """
@@ -216,7 +216,7 @@ def meta_node_add():
         # 创建节点
         with neo4j_driver.get_session() as session:
             cypher = """
-            MERGE (n:meta_data {name: $name})
+            MERGE (n:DataMeta {name: $name})
             ON CREATE SET n.data_type = $type,
                          n.category = $category,
                          n.alias = $alias,
@@ -259,7 +259,7 @@ def meta_node_add():
                 # 如果提供了标签ID,创建标签关系
                 if node_tag:
                     tag_cypher = """
-                    MATCH (n:meta_data), (t:DataLabel)
+                    MATCH (n:DataMeta), (t:DataLabel)
                     WHERE id(n) = $node_id AND id(t) = $tag_id
                     MERGE (n)-[r:label]->(t)
                     RETURN r
@@ -284,7 +284,7 @@ def search_metadata_route():
             return jsonify(success([]))
             
         cypher = """
-        MATCH (n:meta_data) 
+        MATCH (n:DataMeta) 
         WHERE n.name CONTAINS $keyword
         RETURN n LIMIT 100
         """
@@ -310,7 +310,7 @@ def full_text_query():
         # 执行Neo4j全文索引查询
         with neo4j_driver.get_session() as session:
             cypher = """
-            CALL db.index.fulltext.queryNodes("meta_dataFulltext", $query)
+            CALL db.index.fulltext.queryNodes("DataMetaFulltext", $query)
             YIELD node, score
             RETURN node, score
             ORDER BY score DESC
@@ -572,7 +572,7 @@ def text_resource_node():
         with neo4j_driver.get_session() as session:
             # 创建资源节点
             cypher = """
-            CREATE (n:meta_data {
+            CREATE (n:DataMeta {
                 name: $name,
                 en_name: $en_name,
                 keywords: $keywords,
@@ -710,7 +710,7 @@ def meta_node_update():
         with neo4j_driver.get_session() as session:
             # 检查节点是否存在并获取当前值
             check_cypher = """
-            MATCH (n:meta_data)
+            MATCH (n:DataMeta)
             WHERE id(n) = $node_id
             RETURN n
             """
@@ -726,7 +726,7 @@ def meta_node_update():
             
             # 构建更新语句,只更新提供的属性
             update_cypher = """
-            MATCH (n:meta_data)
+            MATCH (n:DataMeta)
             WHERE id(n) = $node_id
             SET n.updateTime = $update_time
             """
@@ -780,7 +780,7 @@ def meta_node_update():
                 if tag is not None:
                     # 先删除现有标签关系
                     delete_tag_cypher = """
-                    MATCH (n:meta_data)-[r:label]->(t:DataLabel)
+                    MATCH (n:DataMeta)-[r:label]->(t:DataLabel)
                     WHERE id(n) = $node_id
                     DELETE r
                     """
@@ -789,7 +789,7 @@ def meta_node_update():
                     # 创建新的标签关系
                     if tag and isinstance(tag, dict) and 'id' in tag:
                         create_tag_cypher = """
-                        MATCH (n:meta_data), (t:DataLabel)
+                        MATCH (n:DataMeta), (t:DataLabel)
                         WHERE id(n) = $node_id AND id(t) = $tag_id
                         MERGE (n)-[r:label]->(t)
                         RETURN r

+ 38 - 1
app/api/system/routes.py

@@ -17,6 +17,7 @@ from app.core.system import (
     login_user,
     get_user_by_username
 )
+from app.core.common.functions import translate_and_parse
 
 logger = logging.getLogger("app")
 
@@ -183,4 +184,40 @@ def get_user(username):
             return jsonify(failed("用户不存在", code=404))
     except Exception as e:
         logger.error(f"获取用户信息失败: {str(e)}")
-        return jsonify(failed(str(e))) 
+        return jsonify(failed(str(e)))
+
+# 翻译接口
+@bp.route('/translate', methods=['POST'])
+def translate():
+    """
+    翻译节点名称
+    
+    请求参数:
+        node_name: 需要翻译的节点名称
+    
+    Returns:
+        JSON: 翻译结果
+    """
+    try:
+        # 获取请求参数
+        data = request.json
+        node_name = data.get('node_name')
+        
+        # 参数验证
+        if not node_name:
+            return jsonify(failed("node_name参数不能为空", code=400))
+        
+        # 调用翻译函数
+        translated_result = translate_and_parse(node_name)
+        
+        # 返回翻译结果
+        result = {
+            "original": node_name,
+            "translated": translated_result[0] if translated_result else node_name,
+            "translated_list": translated_result
+        }
+        
+        return jsonify(success(result, "翻译成功"))
+    except Exception as e:
+        logger.error(f"翻译失败: {str(e)}")
+        return jsonify(failed(f"翻译失败: {str(e)}")) 

+ 22 - 1
app/core/common/functions.py

@@ -5,6 +5,7 @@
 
 import logging
 from app.core.graph.graph_operations import connect_graph
+from app.core.llm.llm_service import llm_client as llm_call
 
 logger = logging.getLogger("app")
 
@@ -81,4 +82,24 @@ def get_node_by_id_no_label(node_id):
             return result["n"] if result else None
     except Exception as e:
         logger.error(f"获取节点错误: {e}")
-        return None 
+        return None
+
+def translate_and_parse(content):
+    """
+    翻译内容并返回结果
+    
+    Args:
+        content: 需要翻译的内容
+        
+    Returns:
+        list: 包含翻译结果的列表
+    """
+    # 调用LLM服务进行翻译
+    translated_text = llm_call(content)
+    
+    # 如果翻译失败,返回原文
+    if translated_text is None:
+        return [content]
+    
+    # 确保返回格式为列表
+    return [translated_text] 

+ 51 - 14
app/core/data_resource/resource.py

@@ -6,9 +6,46 @@ import pandas as pd
 from app.services.neo4j_driver import neo4j_driver
 from app.core.graph.graph_operations import create_or_get_node, relationship_exists, get_node, connect_graph
 import time
+from datetime import datetime
+from app.core.meta_data import get_formatted_time, translate_and_parse
+from app import db
+from sqlalchemy import text
 
 logger = logging.getLogger("app")
 
+def serialize_neo4j_object(obj):
+    """
+    将Neo4j对象转换为可JSON序列化的格式
+    
+    Args:
+        obj: Neo4j节点或属性值
+        
+    Returns:
+        序列化后的对象
+    """
+    if hasattr(obj, 'year'):  # DateTime对象
+        # 将Neo4j DateTime转换为字符串
+        return obj.strftime("%Y-%m-%d %H:%M:%S") if hasattr(obj, 'strftime') else str(obj)
+    elif hasattr(obj, '__dict__'):  # 复杂对象
+        return str(obj)
+    else:
+        return obj
+
+def serialize_node_properties(node):
+    """
+    将Neo4j节点属性序列化为可JSON化的字典
+    
+    Args:
+        node: Neo4j节点对象
+        
+    Returns:
+        dict: 序列化后的属性字典
+    """
+    properties = {}
+    for key, value in dict(node).items():
+        properties[key] = serialize_neo4j_object(value)
+    return properties
+
 def get_formatted_time():
     """获取格式化的当前时间"""
     return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
@@ -313,7 +350,7 @@ def handle_id_resource(resource_id):
             # 构建返回数据
             logger.info(f"record: {record}")
 
-            data_resource = dict(record["n"])
+            data_resource = serialize_node_properties(record["n"])
             
             logger.info(f"data_resource: {data_resource}")
             logger.info(f"describe field in node: {record['n'].get('describe')}")
@@ -358,7 +395,7 @@ def handle_id_resource(resource_id):
             
             parsed_data = []
             for meta_record in meta_result:
-                meta = dict(meta_record["m"])
+                meta = serialize_node_properties(meta_record["m"])
                 meta_data = {
                     "id": meta_record["m"].id,
                     "name": meta.get("name"),
@@ -419,12 +456,12 @@ def id_resource_graph(resource_id):
             
             for record in result:
                 # 处理源节点
-                source_node = dict(record["n"])
+                source_node = serialize_node_properties(record["n"])
                 source_node["id"] = record["n"].id
                 nodes[source_node["id"]] = source_node
                 
                 # 处理目标节点
-                target_node = dict(record["m"])
+                target_node = serialize_node_properties(record["m"])
                 target_node["id"] = record["m"].id
                 nodes[target_node["id"]] = target_node
                 
@@ -492,7 +529,7 @@ def resource_list(page, page_size, en_name_filter=None, name_filter=None,
             # 格式化结果
             resources = []
             for record in result:
-                node = dict(record["n"])
+                node = serialize_node_properties(record["n"])
                 node["id"] = record["n"].id
                 
                 # 查询关联的标签
@@ -505,7 +542,7 @@ def resource_list(page, page_size, en_name_filter=None, name_filter=None,
                 tag_record = tag_result.single()
                 
                 if tag_record:
-                    tag = dict(tag_record["t"])
+                    tag = serialize_node_properties(tag_record["t"])
                     tag["id"] = tag_record["t"].id
                     node["tag_info"] = tag
                 
@@ -581,7 +618,7 @@ def id_data_search_list(resource_id, page, page_size, en_name_filter=None,
             # 格式化结果
             metadata_list = []
             for record in result:
-                meta = dict(record["m"])
+                meta = serialize_node_properties(record["m"])
                 meta["id"] = record["m"].id
                 metadata_list.append(meta)
             
@@ -628,14 +665,14 @@ def resource_kinship_graph(resource_id, include_meta=True):
             relationships = []
             
             # 处理数据资源节点
-            resource_node = dict(record["n"])
+            resource_node = serialize_node_properties(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 = serialize_node_properties(record["l"])
                 label_node["id"] = record["l"].id
                 label_node["labels"] = list(record["l"].labels)
                 nodes[label_node["id"]] = label_node
@@ -652,7 +689,7 @@ def resource_kinship_graph(resource_id, include_meta=True):
             if include_meta and record["metadata"]:
                 for meta in record["metadata"]:
                     if meta:  # 检查元数据节点是否存在
-                        meta_node = dict(meta)
+                        meta_node = serialize_node_properties(meta)
                         meta_node["id"] = meta.id
                         meta_node["labels"] = list(meta.labels)
                         nodes[meta_node["id"]] = meta_node
@@ -712,7 +749,7 @@ def resource_impact_all_graph(resource_id, include_meta=True):
                 # 处理路径中的所有节点
                 for node in path.nodes:
                     if node.id not in nodes:
-                        node_dict = dict(node)
+                        node_dict = serialize_node_properties(node)
                         node_dict["id"] = node.id
                         node_dict["labels"] = list(node.labels)
                         nodes[node.id] = node_dict
@@ -1185,7 +1222,7 @@ def model_resource_list(page, page_size, name_filter=None):
             # 格式化结果
             resources = []
             for record in result:
-                node = dict(record["n"])
+                node = serialize_node_properties(record["n"])
                 node["id"] = record["n"].id
                 resources.append(node)
                 
@@ -1322,7 +1359,7 @@ def data_resource_edit(data):
                             session.run(create_resource_standard_cypher, resource_id=int(resource_id), standard_id=int(standard_id))
             
             # 返回更新后的节点
-            node_data = dict(updated_node["n"])
+                            node_data = serialize_node_properties(updated_node["n"])
             node_data["id"] = updated_node["n"].id
             
             # 记录最终返回的describe字段
@@ -1361,7 +1398,7 @@ def handle_data_source(data_source):
             existing_record = existing_result.single()
             
             if existing_record:
-                existing_data_source = dict(existing_record["ds"])
+                existing_data_source = serialize_node_properties(existing_record["ds"])
                 logger.info(f"根据名称找到现有数据源: {existing_data_source.get('en_name')}")
                 return existing_data_source.get("en_name")
             else:

+ 1 - 1
app/core/meta_data/meta_data.py

@@ -117,7 +117,7 @@ def meta_list(page, page_size, search="", en_name_filter=None,
     try:
         with neo4j_driver.get_session() as session:
             # 构建查询条件
-            match_clause = "MATCH (n:meta_data)"
+            match_clause = "MATCH (n:DataMeta)"
             where_conditions = []
             
             if search:

+ 293 - 0
translate_api_documentation.md

@@ -0,0 +1,293 @@
+# System Translate API 使用说明文档
+
+## 接口概述
+
+translate接口用于将中文节点名称翻译成符合数据库规范的英文名称,主要用于数据建模和表名/字段名的英文化处理。
+
+## 接口信息
+
+### 访问路径
+```
+POST /api/system/translate
+```
+
+### 请求头
+```
+Content-Type: application/json
+```
+
+## 输入参数
+
+### 请求体参数
+
+| 参数名 | 类型 | 必填 | 描述 | 示例 |
+|--------|------|------|------|------|
+| node_name | string | 是 | 需要翻译的中文节点名称 | "用户数据表" |
+
+### 请求体示例
+```json
+{
+    "node_name": "用户数据表"
+}
+```
+
+## 输出参数
+
+### 成功响应
+
+| 字段名 | 类型 | 描述 |
+|--------|------|------|
+| code | integer | 响应状态码,成功时为200 |
+| message | string | 响应消息,成功时为"翻译成功" |
+| data | object | 翻译结果数据对象 |
+| data.original | string | 原始输入的中文名称 |
+| data.translated | string | 翻译后的英文名称(主要结果) |
+| data.translated_list | array | 翻译结果数组(兼容性字段) |
+
+### 错误响应
+
+| 字段名 | 类型 | 描述 |
+|--------|------|------|
+| code | integer | 错误状态码 |
+| message | string | 错误描述信息 |
+| data | null | 错误时数据为空 |
+
+## 样例代码
+
+### cURL 示例
+
+```bash
+curl -X POST \
+  http://localhost:5000/api/system/translate \
+  -H 'Content-Type: application/json' \
+  -d '{
+    "node_name": "用户数据表"
+  }'
+```
+
+### JavaScript (Fetch API) 示例
+
+```javascript
+// 发送翻译请求
+async function translateNodeName(nodeName) {
+    try {
+        const response = await fetch('/api/system/translate', {
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/json',
+            },
+            body: JSON.stringify({
+                node_name: nodeName
+            })
+        });
+        
+        const result = await response.json();
+        
+        if (result.code === 200) {
+            console.log('翻译成功:', result.data.translated);
+            return result.data.translated;
+        } else {
+            console.error('翻译失败:', result.message);
+            return null;
+        }
+    } catch (error) {
+        console.error('请求失败:', error);
+        return null;
+    }
+}
+
+// 使用示例
+translateNodeName('用户数据表').then(translated => {
+    if (translated) {
+        console.log('英文名称:', translated); // 输出: user_data_table
+    }
+});
+```
+
+### Python 示例
+
+```python
+import requests
+import json
+
+def translate_node_name(node_name):
+    """
+    调用翻译API
+    
+    Args:
+        node_name (str): 需要翻译的中文名称
+        
+    Returns:
+        str: 翻译后的英文名称,失败时返回None
+    """
+    url = 'http://localhost:5000/api/system/translate'
+    headers = {
+        'Content-Type': 'application/json'
+    }
+    data = {
+        'node_name': node_name
+    }
+    
+    try:
+        response = requests.post(url, headers=headers, json=data)
+        result = response.json()
+        
+        if result['code'] == 200:
+            print(f"翻译成功: {result['data']['translated']}")
+            return result['data']['translated']
+        else:
+            print(f"翻译失败: {result['message']}")
+            return None
+    except Exception as e:
+        print(f"请求失败: {str(e)}")
+        return None
+
+# 使用示例
+translated_name = translate_node_name('用户数据表')
+if translated_name:
+    print(f"英文名称: {translated_name}")  # 输出: user_data_table
+```
+
+### Java 示例
+
+```java
+import java.io.IOException;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+public class TranslateAPI {
+    
+    public static String translateNodeName(String nodeName) {
+        try {
+            // 构建请求体
+            JsonObject requestBody = new JsonObject();
+            requestBody.addProperty("node_name", nodeName);
+            
+            // 创建HTTP请求
+            HttpRequest request = HttpRequest.newBuilder()
+                .uri(URI.create("http://localhost:5000/api/system/translate"))
+                .header("Content-Type", "application/json")
+                .POST(HttpRequest.BodyPublishers.ofString(requestBody.toString()))
+                .build();
+            
+            // 发送请求
+            HttpClient client = HttpClient.newHttpClient();
+            HttpResponse<String> response = client.send(request, 
+                HttpResponse.BodyHandlers.ofString());
+            
+            // 解析响应
+            Gson gson = new Gson();
+            JsonObject result = gson.fromJson(response.body(), JsonObject.class);
+            
+            if (result.get("code").getAsInt() == 200) {
+                String translated = result.getAsJsonObject("data")
+                    .get("translated").getAsString();
+                System.out.println("翻译成功: " + translated);
+                return translated;
+            } else {
+                String message = result.get("message").getAsString();
+                System.err.println("翻译失败: " + message);
+                return null;
+            }
+        } catch (Exception e) {
+            System.err.println("请求失败: " + e.getMessage());
+            return null;
+        }
+    }
+    
+    // 使用示例
+    public static void main(String[] args) {
+        String translated = translateNodeName("用户数据表");
+        if (translated != null) {
+            System.out.println("英文名称: " + translated); // 输出: user_data_table
+        }
+    }
+}
+```
+
+## 返回状态和编码
+
+### HTTP状态码
+
+| 状态码 | 描述 |
+|--------|------|
+| 200 | 请求成功 |
+| 400 | 请求参数错误 |
+| 500 | 服务器内部错误 |
+
+### 业务状态码
+
+| 业务码 | 描述 | 场景 |
+|--------|------|------|
+| 200 | 成功 | 翻译操作成功完成 |
+| 400 | 参数错误 | node_name参数为空或格式错误 |
+| 500 | 服务器错误 | LLM服务调用失败或其他内部错误 |
+
+## 响应示例
+
+### 成功响应
+
+```json
+{
+    "code": 200,
+    "message": "翻译成功",
+    "data": {
+        "original": "用户数据表",
+        "translated": "user_data_table",
+        "translated_list": ["user_data_table"]
+    }
+}
+```
+
+### 参数错误响应
+
+```json
+{
+    "code": 400,
+    "message": "node_name参数不能为空",
+    "data": null
+}
+```
+
+### 服务器错误响应
+
+```json
+{
+    "code": 500,
+    "message": "翻译失败: LLM服务调用超时",
+    "data": null
+}
+```
+
+## 使用场景
+
+1. **数据库表名翻译**:将中文表名翻译为符合数据库规范的英文表名
+2. **字段名翻译**:将中文字段名翻译为英文字段名
+3. **数据建模**:在数据建模过程中自动生成英文标识符
+4. **系统集成**:为第三方系统提供标准化的英文名称
+
+## 翻译规则
+
+翻译结果遵循以下规则:
+- 使用小写字母
+- 多个单词用下划线连接
+- 符合PostgreSQL数据库命名规范
+- 优先使用简短、准确的英文单词
+- "表"字统一翻译为"table"
+
+## 注意事项
+
+1. 请确保输入的`node_name`为有效的字符串
+2. 翻译结果适用于数据库表名和字段名,符合SQL命名规范
+3. 如果翻译服务不可用,接口会返回相应的错误信息
+4. 建议在生产环境中实现重试机制以提高可靠性
+
+## 版本信息
+
+- API版本:v1.0
+- 文档版本:1.0.0
+- 最后更新:2025年