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.

336 lines
12 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 框架
针对前端 Vue3 + ElementPlus 的请求进行深度解析确保获取 IDnodeIdname label
"""
try:
body = getattr(req, "body", None)
if not body:
return {}
# 1. 处理 bytes 类型 (Robyn 常见的 body 类型)
if isinstance(body, (bytes, bytearray)):
body = body.decode('utf-8')
# 2. 如果已经是字典,直接返回
if isinstance(body, dict):
return body
# 3. 处理字符串 (JSON 序列化后的字符串)
if isinstance(body, str):
try:
data = json.loads(body)
# 处理双层 JSON 序列化的情况 (有些前端框架会序列化两次)
if isinstance(data, str):
data = json.loads(data)
return data
except json.JSONDecodeError:
# 尝试解析 URL 编码格式 (application/x-www-form-urlencoded)
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 查询参数适配不同版本的 Robyn 参数存放位置
"""
try:
# 尝试从新版/旧版 Robyn 的不同属性中提取
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", {})
# 适配 Robyn 特有的 Query 对象
if hasattr(data_source, "to_dict"):
data_source = data_source.to_dict()
val = data_source.get(key)
if val is None:
return default
# 提取值并进行 URL 解码
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):
"""
联想词接口
支持 keyword 模糊搜索同时支持 label 强过滤
"""
try:
# 1. 提取前端传来的参数
clean_keyword = get_query_param(req, "keyword", "")
clean_label = get_query_param(req, "label", "")
# 2. 调用 Service 层
# 如果 label 为 "全部" 或空,Service 层会自动处理成全库建议
suggestions = operation_service.suggest_nodes(clean_keyword, clean_label)
return create_response(200, {"code": 200, "data": suggestions, "msg": "success"})
except Exception as e:
print(f"Suggest Interface Error: {e}")
return create_response(200, {"code": 500, "msg": f"联想接口异常: {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 not in ["全部", "", "null"]) 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 not in ["全部", ""]) 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)
# 兼容两种写法:id (elementId) 或 nodeId (业务ID)
node_id = body.get("id") or body.get("nodeId")
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)}"})