You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
318 lines
11 KiB
318 lines
11 KiB
import json
|
|
import traceback
|
|
from app import app
|
|
from robyn import jsonify, Response
|
|
from service.OperationService import OperationService
|
|
from urllib.parse import unquote
|
|
|
|
# 实例化业务逻辑对象
|
|
operation_service = OperationService()
|
|
|
|
|
|
# --- 核心工具函数 ---
|
|
|
|
def create_response(status_code, data_dict):
|
|
"""
|
|
统一响应格式封装,强制使用 UTF-8 防止中文乱码。
|
|
"""
|
|
return Response(
|
|
status_code=status_code,
|
|
description=jsonify(data_dict),
|
|
headers={"Content-Type": "application/json; charset=utf-8"}
|
|
)
|
|
|
|
|
|
def parse_request_body(req):
|
|
"""
|
|
解析器:适配 Robyn 框架,确保能准确拿到前端传来的 ID、nodeId、name 和 label。
|
|
"""
|
|
try:
|
|
body = getattr(req, "body", None)
|
|
if not body:
|
|
return {}
|
|
|
|
# 1. 处理 bytes 类型
|
|
if isinstance(body, (bytes, bytearray)):
|
|
body = body.decode('utf-8')
|
|
|
|
# 2. 如果已经是字典
|
|
if isinstance(body, dict):
|
|
return body
|
|
|
|
# 3. 处理字符串
|
|
if isinstance(body, str):
|
|
try:
|
|
data = json.loads(body)
|
|
# 处理双层 JSON 字符串转义的情况
|
|
if isinstance(data, str):
|
|
data = json.loads(data)
|
|
return data
|
|
except json.JSONDecodeError:
|
|
try:
|
|
from urllib.parse import parse_qs
|
|
params = parse_qs(body)
|
|
return {k: v[0] for k, v in params.items()}
|
|
except:
|
|
return {}
|
|
return {}
|
|
except Exception as e:
|
|
print(f"Request Body Parse Error: {e}")
|
|
return {}
|
|
|
|
|
|
def get_query_param(req, key, default=""):
|
|
"""
|
|
提取 URL 查询参数。
|
|
"""
|
|
try:
|
|
data_source = getattr(req, "queries", None)
|
|
if data_source is None or (isinstance(data_source, dict) and not data_source):
|
|
data_source = getattr(req, "query_params", {})
|
|
|
|
if hasattr(data_source, "to_dict"):
|
|
data_source = data_source.to_dict()
|
|
|
|
val = data_source.get(key)
|
|
if val is None:
|
|
return default
|
|
|
|
raw_val = str(val[0]) if isinstance(val, list) else str(val)
|
|
return unquote(raw_val).strip()
|
|
except Exception as e:
|
|
print(f"Get Param Error ({key}): {e}")
|
|
return default
|
|
|
|
|
|
# --- 0. 数据治理修复接口 ---
|
|
@app.post("/api/kg/admin/fix-ids")
|
|
def fix_node_ids(req):
|
|
"""
|
|
手动触发:修复数据库中 nodeId 为空或为 0 的存量数据
|
|
"""
|
|
try:
|
|
result = operation_service.fix_all_missing_node_ids()
|
|
return create_response(200, {
|
|
"code": 200 if result.get("success") else 500,
|
|
"msg": result.get("msg")
|
|
})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": f"修复接口异常: {str(e)}"})
|
|
|
|
|
|
# --- 1. 获取全量动态标签 (节点管理用) ---
|
|
@app.get("/api/kg/labels")
|
|
def get_labels(req):
|
|
try:
|
|
labels = operation_service.get_all_labels()
|
|
return create_response(200, {"code": 200, "data": labels, "msg": "success"})
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return create_response(200, {"code": 500, "msg": f"获取标签失败: {str(e)}"})
|
|
|
|
|
|
# --- 新增:获取全量动态关系类型 (关系管理用) ---
|
|
@app.get("/api/kg/relationship-types")
|
|
def get_rel_types(req):
|
|
"""
|
|
从数据库动态获取所有关系类型 type 及其 label 映射
|
|
"""
|
|
try:
|
|
rel_types = operation_service.get_all_relationship_types()
|
|
return create_response(200, {"code": 200, "data": rel_types, "msg": "success"})
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return create_response(200, {"code": 500, "msg": f"获取关系类型失败: {str(e)}"})
|
|
|
|
|
|
# --- 2. 输入联想建议 ---
|
|
@app.get("/api/kg/node/suggest")
|
|
def suggest_node(req):
|
|
try:
|
|
clean_keyword = get_query_param(req, "keyword", "")
|
|
suggestions = operation_service.suggest_nodes(clean_keyword)
|
|
return create_response(200, {"code": 200, "data": suggestions, "msg": "success"})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": str(e)})
|
|
|
|
|
|
# --- 3. 获取分页节点列表 ---
|
|
@app.get("/api/kg/nodes")
|
|
def get_nodes(req):
|
|
try:
|
|
name_raw = get_query_param(req, "name", "")
|
|
label_raw = get_query_param(req, "label", "")
|
|
page_str = get_query_param(req, "page", "1")
|
|
size_str = get_query_param(req, "pageSize", "20")
|
|
|
|
page = int(page_str) if page_str.isdigit() else 1
|
|
page_size = int(size_str) if size_str.isdigit() else 20
|
|
|
|
name = name_raw if name_raw else None
|
|
label = label_raw if (label_raw and label_raw != "全部") else None
|
|
|
|
res_data = operation_service.get_nodes_subset(page, page_size, name=name, label=label)
|
|
return create_response(200, {"code": 200, "data": res_data, "msg": "success"})
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return create_response(200, {"code": 500, "msg": f"获取节点失败: {str(e)}"})
|
|
|
|
|
|
# --- 4. 获取分页关系列表 ---
|
|
@app.get("/api/kg/relationships")
|
|
def get_relationships(req):
|
|
try:
|
|
source_raw = get_query_param(req, "source", "")
|
|
target_raw = get_query_param(req, "target", "")
|
|
type_raw = get_query_param(req, "type", "")
|
|
page_str = get_query_param(req, "page", "1")
|
|
size_str = get_query_param(req, "pageSize", "20")
|
|
|
|
page = int(page_str) if page_str.isdigit() else 1
|
|
page_size = int(size_str) if size_str.isdigit() else 20
|
|
|
|
source = source_raw if source_raw else None
|
|
target = target_raw if target_raw else None
|
|
rel_type = type_raw if (type_raw and type_raw != "全部") else None
|
|
|
|
res_data = operation_service.get_relationships_subset(page, page_size, source, target, rel_type)
|
|
return create_response(200, {"code": 200, "data": res_data, "msg": "success"})
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return create_response(200, {"code": 500, "msg": f"获取关系失败: {str(e)}"})
|
|
|
|
|
|
# --- 5. 新增节点 ---
|
|
@app.post("/api/kg/node/add")
|
|
def add_node(req):
|
|
try:
|
|
body = parse_request_body(req)
|
|
label = str(body.get("label", "Drug")).strip()
|
|
name = str(body.get("name", "")).strip()
|
|
|
|
if not name:
|
|
return create_response(200, {"code": 400, "msg": "名称不能为空"})
|
|
|
|
result = operation_service.add_node(label, name)
|
|
return create_response(200, {
|
|
"code": 200 if result.get("success") else 400,
|
|
"msg": result.get("msg")
|
|
})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": f"新增异常: {str(e)}"})
|
|
|
|
|
|
# --- 6. 修改节点 ---
|
|
@app.post("/api/kg/node/update")
|
|
def update_node(req):
|
|
try:
|
|
body = parse_request_body(req)
|
|
node_id = body.get("id")
|
|
name = str(body.get("name", "")).strip()
|
|
label = str(body.get("label", "")).strip()
|
|
|
|
if not node_id or not name:
|
|
return create_response(200, {"code": 400, "msg": "参数缺失: 修改必须包含ID和名称"})
|
|
|
|
result = operation_service.update_node(node_id, name, label)
|
|
return create_response(200, {
|
|
"code": 200 if result.get("success") else 400,
|
|
"msg": result.get("msg")
|
|
})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": f"更新异常: {str(e)}"})
|
|
|
|
|
|
# --- 7. 新增关系 ---
|
|
@app.post("/api/kg/rel/add")
|
|
def add_relationship(req):
|
|
try:
|
|
body = parse_request_body(req)
|
|
source = str(body.get("source", "")).strip()
|
|
target = str(body.get("target", "")).strip()
|
|
rel_type = str(body.get("type", "")).strip()
|
|
rel_label = str(body.get("label", "")).strip() or rel_type
|
|
|
|
if not all([source, target, rel_type]):
|
|
return create_response(200, {"code": 400, "msg": "参数缺失: 起点、终点和类型为必填项"})
|
|
|
|
result = operation_service.add_relationship(source, target, rel_type, rel_label)
|
|
return create_response(200, {
|
|
"code": 200 if result.get("success") else 400,
|
|
"msg": result.get("msg")
|
|
})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": f"新增关系异常: {str(e)}"})
|
|
|
|
|
|
# --- 8. 修改关系 ---
|
|
@app.post("/api/kg/rel/update")
|
|
def update_rel(req):
|
|
try:
|
|
body = parse_request_body(req)
|
|
rel_id = body.get("id")
|
|
source = str(body.get("source", "")).strip()
|
|
target = str(body.get("target", "")).strip()
|
|
rel_type = str(body.get("type", "")).strip()
|
|
rel_label = str(body.get("label", "")).strip() or rel_type
|
|
|
|
if not rel_id:
|
|
return create_response(200, {"code": 400, "msg": "修改失败:关系ID缺失"})
|
|
|
|
result = operation_service.update_relationship(rel_id, source, target, rel_type, rel_label)
|
|
return create_response(200, {
|
|
"code": 200 if result.get("success") else 400,
|
|
"msg": result.get("msg")
|
|
})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": f"修改关系异常: {str(e)}"})
|
|
|
|
|
|
# --- 9. 删除节点 ---
|
|
@app.post("/api/kg/node/delete")
|
|
def delete_node(req):
|
|
try:
|
|
body = parse_request_body(req)
|
|
node_id = body.get("id")
|
|
if not node_id:
|
|
return create_response(200, {"code": 400, "msg": "删除失败: 未指定节点系统ID"})
|
|
|
|
result = operation_service.delete_node(node_id)
|
|
return create_response(200, {
|
|
"code": 200 if result.get("success") else 400,
|
|
"msg": result.get("msg")
|
|
})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": f"删除节点异常: {str(e)}"})
|
|
|
|
|
|
# --- 10. 删除关系 ---
|
|
@app.post("/api/kg/rel/delete")
|
|
def delete_rel(req):
|
|
try:
|
|
body = parse_request_body(req)
|
|
rel_id = body.get("id")
|
|
if not rel_id:
|
|
return create_response(200, {"code": 400, "msg": "删除失败: 未指定关系系统ID"})
|
|
|
|
result = operation_service.delete_relationship(rel_id)
|
|
return create_response(200, {
|
|
"code": 200 if result.get("success") else 400,
|
|
"msg": result.get("msg")
|
|
})
|
|
except Exception as e:
|
|
return create_response(200, {"code": 500, "msg": f"删除关系异常: {str(e)}"})
|
|
|
|
|
|
# --- 11. 获取图谱全局统计数据 ---
|
|
@app.get("/api/kg/stats")
|
|
def get_kg_stats(req):
|
|
try:
|
|
result = operation_service.get_kg_stats()
|
|
if result and result.get("success"):
|
|
return create_response(200, {"code": 200, "data": result.get("data"), "msg": "success"})
|
|
else:
|
|
msg = result.get("msg") if result else "未能获取统计数据"
|
|
return create_response(200, {"code": 400, "msg": msg})
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return create_response(200, {"code": 500, "msg": f"统计数据异常: {str(e)}"})
|