9 changed files with 733 additions and 303 deletions
@ -0,0 +1,207 @@ |
|||
import json |
|||
import sys |
|||
from datetime import datetime |
|||
from app import app |
|||
|
|||
from robyn import Robyn, jsonify, Response |
|||
from typing import Optional, List, Any, Dict |
|||
|
|||
from service.GraphService import build_g6_subgraph_by_props, get_drug_names_from_neo4j, get_group_key, \ |
|||
get_check_names_from_neo4j |
|||
from util.neo4j_utils import Neo4jUtil |
|||
from util.neo4j_utils import neo4j_client |
|||
from util.redis_utils import set as redis_set, get as redis_get # 使用你已有的模块级 Redis 工 |
|||
|
|||
# 缓存键 |
|||
DRUG_TREE_KEY = "cache:drug_tree" |
|||
CHECK_TREE_KEY = "cache:check_tree" |
|||
|
|||
# ======================== |
|||
# 🔥 启动时预加载数据(在 app 启动前执行) |
|||
# ======================== |
|||
def preload_data(): |
|||
print("🚀 正在预加载 Drug 和 Check 树...") |
|||
|
|||
try: |
|||
# --- Drug Tree --- |
|||
names = get_drug_names_from_neo4j() |
|||
groups = {} |
|||
for name in names: |
|||
key = get_group_key(name) |
|||
groups.setdefault(key, []).append(name) |
|||
|
|||
alphabet = [chr(i) for i in range(ord('A'), ord('Z') + 1)] |
|||
all_keys = alphabet + ["0-9", "其他"] |
|||
|
|||
tree_data = [] |
|||
for key in all_keys: |
|||
if key in groups: |
|||
children = [{"label": name, "type": "Drug"} for name in sorted(groups[key])] |
|||
tree_data.append({"label": key, "type": "Drug", "children": children}) |
|||
|
|||
redis_set(DRUG_TREE_KEY, json.dumps(tree_data, ensure_ascii=False), ex=3600) |
|||
|
|||
# --- Check Tree --- |
|||
names = get_check_names_from_neo4j() |
|||
groups = {} |
|||
for name in names: |
|||
key = get_group_key(name) |
|||
groups.setdefault(key, []).append(name) |
|||
|
|||
tree_data = [] |
|||
for key in all_keys: |
|||
if key in groups: |
|||
children = [{"label": name, "type": "Check"} for name in sorted(groups[key])] |
|||
tree_data.append({"label": key, "type": "Check", "children": children}) |
|||
|
|||
redis_set(CHECK_TREE_KEY, json.dumps(tree_data, ensure_ascii=False), ex=3600) |
|||
|
|||
print("✅ 预加载完成!数据已写入 Redis 缓存。") |
|||
except Exception as e: |
|||
print(f"❌ 预加载失败: {e}", file=sys.stderr) |
|||
# 可选:是否允许启动失败?这里选择继续启动(接口会返回错误) |
|||
# 或者 sys.exit(1) 强制退出 |
|||
|
|||
# 执行预加载(在 app 创建前) |
|||
preload_data() |
|||
@app.get("/api/getData") |
|||
def get_data(): |
|||
try: |
|||
graph_data = build_g6_subgraph_by_props( |
|||
neo4j_client, |
|||
node_label="Disease", |
|||
node_properties={"name": "霍乱"}, |
|||
direction="both", |
|||
rel_type=None |
|||
) |
|||
|
|||
return Response( |
|||
status_code=200, |
|||
description=jsonify(graph_data), |
|||
headers={"Content-Type": "text/plain; charset=utf-8"} |
|||
) |
|||
except Exception as e: |
|||
return jsonify({"error": str(e)}), 500 |
|||
@app.post("/api/getGraph") |
|||
def get_graph(req): |
|||
try: |
|||
|
|||
# 1. 获取 JSON body(自动解析为 dict) |
|||
body = req.json() |
|||
# 2. 提取 label 字段(即疾病名称) |
|||
disease_name = body.get("label") |
|||
if not disease_name: |
|||
return jsonify({"error": "Missing 'label' in request body"}), 400 |
|||
graph_data = build_g6_subgraph_by_props( |
|||
neo4j_client, |
|||
node_label=body.get("type"), |
|||
node_properties={"name": disease_name}, |
|||
direction="both", |
|||
rel_type=None |
|||
) |
|||
return Response( |
|||
status_code=200, |
|||
description=jsonify(graph_data), |
|||
headers={"Content-Type": "text/plain; charset=utf-8"} |
|||
) |
|||
except Exception as e: |
|||
return jsonify({"error": str(e)}), 500 |
|||
@app.get("/api/drug-tree") |
|||
def get_drug_tree(): |
|||
return Response( |
|||
status_code=200, |
|||
description=redis_get(DRUG_TREE_KEY), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
# try: |
|||
# names = get_drug_names_from_neo4j() |
|||
# print(f"[Step 1] Loaded {len(names)} names") |
|||
# |
|||
# groups = {} |
|||
# for name in names: |
|||
# key = get_group_key(name) |
|||
# groups.setdefault(key, []).append(name) |
|||
# print(f"[Step 2] Grouped into {len(groups)} groups") |
|||
# |
|||
# # ✅ 顺序:A-Z, 0-9, 其他 |
|||
# alphabet = [chr(i) for i in range(ord('A'), ord('Z') + 1)] |
|||
# all_keys = alphabet + ["0-9", "其他"] |
|||
# |
|||
# tree_data = [] |
|||
# total_children = 0 |
|||
# for key in all_keys: |
|||
# if key in groups: |
|||
# children = [{"label": name,"type":"Drug"} for name in sorted(groups[key])] |
|||
# total_children += len(children) |
|||
# tree_data.append({"label": key,"type":"Drug", "children": children}) |
|||
# |
|||
# print(f"[Step 3] Final tree: {len(tree_data)} groups, {total_children} drugs") |
|||
# |
|||
# json_str = json.dumps(tree_data, ensure_ascii=False) |
|||
# print(f"[Step 4] JSON size: {len(json_str)} chars") |
|||
# |
|||
# return Response( |
|||
# status_code=200, |
|||
# description=json_str, |
|||
# headers={"Content-Type": "application/json; charset=utf-8"} |
|||
# ) |
|||
# except Exception as e: |
|||
# print(f"[ERROR] {str(e)}") |
|||
# return Response( |
|||
# status_code=500, |
|||
# description=json.dumps({"error": str(e)}, ensure_ascii=False), |
|||
# headers={"Content-Type": "application/json; charset=utf-8"} |
|||
# ) |
|||
@app.get("/api/check-tree") |
|||
def get_check_tree(): |
|||
return Response( |
|||
status_code=200, |
|||
description=redis_get(CHECK_TREE_KEY), |
|||
headers={"Content-Type": "application/json; charset=utf-8"} |
|||
) |
|||
# try: |
|||
# names = get_check_names_from_neo4j() |
|||
# print(f"[Step 1] Loaded {len(names)} names") |
|||
# |
|||
# groups = {} |
|||
# for name in names: |
|||
# key = get_group_key(name) |
|||
# groups.setdefault(key, []).append(name) |
|||
# print(f"[Step 2] Grouped into {len(groups)} groups") |
|||
# |
|||
# # ✅ 顺序:A-Z, 0-9, 其他 |
|||
# alphabet = [chr(i) for i in range(ord('A'), ord('Z') + 1)] |
|||
# all_keys = alphabet + ["0-9", "其他"] |
|||
# |
|||
# tree_data = [] |
|||
# total_children = 0 |
|||
# for key in all_keys: |
|||
# if key in groups: |
|||
# children = [{"label": name,"type":"Check"} for name in sorted(groups[key])] |
|||
# total_children += len(children) |
|||
# tree_data.append({"label": key,"type":"Check", "children": children}) |
|||
# |
|||
# print(f"[Step 3] Final tree: {len(tree_data)} groups, {total_children} checks") |
|||
# |
|||
# json_str = json.dumps(tree_data, ensure_ascii=False) |
|||
# print(f"[Step 4] JSON size: {len(json_str)} chars") |
|||
# |
|||
# return Response( |
|||
# status_code=200, |
|||
# description=json_str, |
|||
# headers={"Content-Type": "application/json; charset=utf-8"} |
|||
# ) |
|||
# except Exception as e: |
|||
# print(f"[ERROR] {str(e)}") |
|||
# return Response( |
|||
# status_code=500, |
|||
# description=json.dumps({"error": str(e)}, ensure_ascii=False), |
|||
# headers={"Content-Type": "application/json; charset=utf-8"} |
|||
# ) |
|||
|
|||
|
|||
@app.get("/health") |
|||
def health(): |
|||
print(redis_get(DRUG_TREE_KEY)) |
|||
print(redis_get(CHECK_TREE_KEY)) |
|||
return {"status": "ok", "drug_cached": redis_get(DRUG_TREE_KEY) is not None} |
|||
@ -1,181 +0,0 @@ |
|||
from datetime import datetime |
|||
from app import app |
|||
|
|||
from robyn import Robyn, jsonify, Response |
|||
from typing import Optional, List, Any, Dict |
|||
from util.neo4j_utils import Neo4jUtil |
|||
from util.neo4j_utils import neo4j_client |
|||
def convert_node_to_g6_v5(neo4j_node: dict) -> dict: |
|||
node_id = neo4j_node.get("id") |
|||
if node_id is None: |
|||
raise ValueError("节点必须包含 'id' 字段") |
|||
|
|||
data = {k: v for k, v in neo4j_node.items() if k != "id"} |
|||
if "name" not in data and "label" not in data: |
|||
data["name"] = str(node_id) |
|||
|
|||
return { |
|||
"id": node_id, |
|||
"data": data, |
|||
"states": [], |
|||
"combo": None |
|||
} |
|||
|
|||
|
|||
def build_g6_graph_data_from_results( |
|||
nodes: List[Dict[str, Any]], |
|||
relationships: List[Dict[str, Any]] |
|||
) -> dict: |
|||
""" |
|||
通用方法:根据节点列表和关系列表构建 G6 v5 图数据 |
|||
|
|||
Args: |
|||
nodes: 节点列表,每个节点需含 "id" |
|||
relationships: 关系列表,每个关系需含: |
|||
- source: {"id": ..., ...} |
|||
- target: {"id": ..., ...} |
|||
- relationship: {"type": str, "properties": dict} 或直接扁平化字段 |
|||
|
|||
Returns: |
|||
{"nodes": [...], "edges": [...]} |
|||
""" |
|||
g6_node_map = {} |
|||
|
|||
# 处理显式传入的节点 |
|||
for node in nodes: |
|||
node_id = node.get("id") |
|||
if node_id: |
|||
g6_node_map[node_id] = convert_node_to_g6_v5(node) |
|||
|
|||
g6_edges = [] |
|||
for rel in relationships: |
|||
source_node = rel.get("source") |
|||
target_node = rel.get("target") |
|||
if not source_node or not target_node: |
|||
continue |
|||
|
|||
source_id = source_node.get("id") |
|||
target_id = target_node.get("id") |
|||
if not source_id or not target_id: |
|||
continue |
|||
|
|||
# 确保 source/target 节点也加入图中(即使未在 nodes 中显式提供) |
|||
if source_id not in g6_node_map: |
|||
g6_node_map[source_id] = convert_node_to_g6_v5(source_node) |
|||
if target_id not in g6_node_map: |
|||
g6_node_map[target_id] = convert_node_to_g6_v5(target_node) |
|||
|
|||
# 构建 edge data |
|||
edge_data = {} |
|||
rel_type_str = rel.get("type") or rel.get("relationship") # 兼容不同结构 |
|||
if rel_type_str: |
|||
edge_data["relationship"] = rel_type_str |
|||
|
|||
# 尝试从 relProps 或 properties 或顶层提取关系属性 |
|||
rel_props = ( |
|||
rel.get("relProps") or |
|||
rel.get("properties") or |
|||
{k: v for k, v in rel.items() if k not in ("source", "target", "type", "relationship")} |
|||
) |
|||
if isinstance(rel_props, dict): |
|||
edge_data.update(rel_props) |
|||
|
|||
g6_edge = { |
|||
"source": source_id, |
|||
"target": target_id, |
|||
"type": "line", |
|||
"data": edge_data, |
|||
"states": [] |
|||
} |
|||
g6_edges.append(g6_edge) |
|||
|
|||
return { |
|||
"nodes": list(g6_node_map.values()), |
|||
"edges": g6_edges |
|||
} |
|||
def build_g6_subgraph_by_props( |
|||
neo4j_util: Neo4jUtil, |
|||
node_properties: Dict[str, Any], |
|||
node_label: Optional[str] = None, |
|||
direction: str = "both", |
|||
rel_type: Optional[str] = None |
|||
) -> dict: |
|||
neighbor_list = neo4j_util.find_neighbors_with_relationships( |
|||
node_label=node_label, |
|||
node_properties=node_properties, |
|||
direction=direction, |
|||
rel_type=rel_type |
|||
) |
|||
|
|||
# 提取所有唯一节点 |
|||
node_dict = {} |
|||
for item in neighbor_list: |
|||
for key in ["source", "target"]: |
|||
n = item[key] |
|||
nid = n.get("id") |
|||
if nid and nid not in node_dict: |
|||
node_dict[nid] = n |
|||
|
|||
# 如果没找到关系,但中心节点存在,也要包含它 |
|||
if not neighbor_list: |
|||
center_nodes = neo4j_util.find_nodes_with_element_id(node_label, node_properties) |
|||
if center_nodes: |
|||
n = center_nodes[0] |
|||
node_dict[n["id"]] = n |
|||
|
|||
nodes = list(node_dict.values()) |
|||
relationships = neighbor_list # 结构已兼容 |
|||
|
|||
return build_g6_graph_data_from_results(nodes, relationships) |
|||
print("✅ test1 已导入,路由应已注册") |
|||
@app.get("/api/getData") |
|||
def get_data(): |
|||
try: |
|||
graph_data = build_g6_subgraph_by_props( |
|||
neo4j_client, |
|||
node_label="Disease", |
|||
node_properties={"name": "埃尔托生物型霍乱"}, |
|||
direction="both", |
|||
rel_type=None |
|||
) |
|||
|
|||
return Response( |
|||
status_code=200, |
|||
description=jsonify(graph_data), |
|||
headers={"Content-Type": "text/plain; charset=utf-8"} |
|||
) |
|||
except Exception as e: |
|||
return jsonify({"error": str(e)}), 500 |
|||
|
|||
|
|||
@app.post("/api/getGraph") |
|||
def get_graph(req): |
|||
try: |
|||
|
|||
# 1. 获取 JSON body(自动解析为 dict) |
|||
body = req.json() |
|||
# 2. 提取 label 字段(即疾病名称) |
|||
disease_name = body.get("label") |
|||
if not disease_name: |
|||
return jsonify({"error": "Missing 'label' in request body"}), 400 |
|||
code = body.get("code") |
|||
level = body.get("level") |
|||
print("sssssssssss") |
|||
print(body.get("type")) |
|||
|
|||
print(code) |
|||
print(level) |
|||
graph_data = build_g6_subgraph_by_props( |
|||
neo4j_client, |
|||
node_label=body.get("type"), |
|||
node_properties={"name": disease_name}, |
|||
direction="both", |
|||
rel_type=None |
|||
) |
|||
return Response( |
|||
status_code=200, |
|||
description=jsonify(graph_data), |
|||
headers={"Content-Type": "text/plain; charset=utf-8"} |
|||
) |
|||
except Exception as e: |
|||
return jsonify({"error": str(e)}), 500 |
|||
@ -1,20 +1,10 @@ |
|||
from neo4j import GraphDatabase |
|||
from pypinyin import lazy_pinyin |
|||
from util.neo4j_utils import neo4j_client |
|||
|
|||
URI = "bolt://localhost:7687" |
|||
AUTH = ("neo4j", "12345678") |
|||
|
|||
with GraphDatabase.driver(URI, auth=AUTH) as driver: |
|||
with driver.session() as session: |
|||
result = session.run("MATCH (d:Drug) WHERE d.name IS NOT NULL RETURN d.name AS name") |
|||
names = [record["name"] for record in result] |
|||
def get_drug_count(): |
|||
cypher = "MATCH (d:Drug) WHERE d.name IS NOT NULL RETURN count(d) AS total" |
|||
res = neo4j_client.execute_read(cypher) |
|||
return res[0]["total"] |
|||
|
|||
# 按拼音首字母 A-Z 排序(支持中英文混合) |
|||
def sort_key(name): |
|||
# 将每个字转为拼音,取首字母(如 "阿司匹林" → ['a', 'si', 'pi', 'lin'] → 拼成 "asipilin") |
|||
return ''.join(lazy_pinyin(name)) |
|||
|
|||
sorted_names = sorted(names, key=sort_key) |
|||
|
|||
for name in sorted_names: |
|||
print(name) |
|||
# 在路由中临时加一行打印 |
|||
print("Total drug names:", get_drug_count()) # 看是否接近 17000+ |
|||
@ -1,111 +1,174 @@ |
|||
from typing import Dict, List, Any, Optional |
|||
from pypinyin import lazy_pinyin, Style |
|||
from util.neo4j_utils import Neo4jUtil, neo4j_client |
|||
|
|||
|
|||
def convert_node_to_g6_v5(neo4j_node: Dict[str, Any]) -> Dict[str, Any]: |
|||
""" |
|||
将 Neo4j 节点(含 id 和 props)转换为 G6 v5 节点格式。 |
|||
|
|||
:param neo4j_node: 来自 Neo4jUtil 的节点字典,必须包含 'id' 字段 |
|||
:return: G6 v5 节点对象 |
|||
""" |
|||
def convert_node_to_g6_v5(neo4j_node: dict) -> dict: |
|||
node_id = neo4j_node.get("id") |
|||
if node_id is None: |
|||
raise ValueError("节点必须包含 'id' 字段") |
|||
|
|||
# 构建 data:排除 'id',保留其他属性 |
|||
data = {k: v for k, v in neo4j_node.items() if k != "id"} |
|||
|
|||
# 确保有显示用的 name 或 label(G6 默认使用 data.name) |
|||
if "name" not in data and "label" not in data: |
|||
data["name"] = str(node_id) |
|||
|
|||
g6_node = { |
|||
return { |
|||
"id": node_id, |
|||
"data": data, |
|||
# 可选字段(前端可覆盖): |
|||
# "type": "circle", |
|||
# "style": {"size": 32, "fill": "violet"}, |
|||
"states": [], |
|||
"combo": None |
|||
} |
|||
return g6_node |
|||
|
|||
|
|||
def build_g6_graph_data( |
|||
neo4j_util: 'Neo4jUtil', |
|||
node_label: str, |
|||
rel_type: Optional[str] = None |
|||
) -> Dict[str, List[Dict[str, Any]]]: |
|||
""" |
|||
构建 G6 v5 兼容的图数据结构(nodes + edges)。 |
|||
|
|||
:param neo4j_util: 已连接的 Neo4jUtil 实例 |
|||
:param node_label: 中心节点标签(用于获取初始节点集) |
|||
:param rel_type: 关系类型(可选,若为 None 则获取所有关系) |
|||
:return: {'nodes': [...], 'edges': [...]} |
|||
def build_g6_graph_data_from_results( |
|||
nodes: List[Dict[str, Any]], |
|||
relationships: List[Dict[str, Any]] |
|||
) -> dict: |
|||
""" |
|||
# 1. 获取中心节点(指定标签的所有节点) |
|||
center_nodes = neo4j_util.find_all_nodes(node_label) |
|||
通用方法:根据节点列表和关系列表构建 G6 v5 图数据 |
|||
|
|||
# 2. 获取所有相关关系(可按类型过滤) |
|||
relationships = neo4j_util.find_all_relationships(rel_type=rel_type) |
|||
Args: |
|||
nodes: 节点列表,每个节点需含 "id" |
|||
relationships: 关系列表,每个关系需含: |
|||
- source: {"id": ..., ...} |
|||
- target: {"id": ..., ...} |
|||
- relationship: {"type": str, "properties": dict} 或直接扁平化字段 |
|||
|
|||
# 3. 使用字典去重节点(key: elementId) |
|||
g6_node_map: Dict[str, Dict[str, Any]] = {} |
|||
Returns: |
|||
{"nodes": [...], "edges": [...]} |
|||
""" |
|||
g6_node_map = {} |
|||
|
|||
# 添加中心节点 |
|||
for node in center_nodes: |
|||
# 处理显式传入的节点 |
|||
for node in nodes: |
|||
node_id = node.get("id") |
|||
if node_id: |
|||
g6_node_map[node_id] = convert_node_to_g6_v5(node) |
|||
|
|||
# 4. 处理关系,提取 source/target 节点并构建边 |
|||
g6_edges: List[Dict[str, Any]] = [] |
|||
|
|||
g6_edges = [] |
|||
for rel in relationships: |
|||
source_node = rel.get("source") |
|||
target_node = rel.get("target") |
|||
|
|||
if not source_node or not target_node: |
|||
continue |
|||
|
|||
source_id = source_node.get("id") |
|||
target_id = target_node.get("id") |
|||
|
|||
if not source_id or not target_id: |
|||
continue |
|||
|
|||
# 自动加入 source 和 target 节点(去重) |
|||
g6_node_map[source_id] = convert_node_to_g6_v5(source_node) |
|||
g6_node_map[target_id] = convert_node_to_g6_v5(target_node) |
|||
# 确保 source/target 节点也加入图中(即使未在 nodes 中显式提供) |
|||
if source_id not in g6_node_map: |
|||
g6_node_map[source_id] = convert_node_to_g6_v5(source_node) |
|||
if target_id not in g6_node_map: |
|||
g6_node_map[target_id] = convert_node_to_g6_v5(target_node) |
|||
|
|||
# 构建 G6 边对象 |
|||
# 构建 edge data |
|||
edge_data = {} |
|||
|
|||
# 关系类型 |
|||
rel_type_str = rel.get("type") |
|||
rel_type_str = rel.get("type") or rel.get("relationship") # 兼容不同结构 |
|||
if rel_type_str: |
|||
edge_data["relationship"] = rel_type_str |
|||
|
|||
# 合并关系属性 |
|||
rel_props = rel.get("relProps") or {} |
|||
edge_data.update(rel_props) |
|||
# 尝试从 relProps 或 properties 或顶层提取关系属性 |
|||
rel_props = ( |
|||
rel.get("relProps") or |
|||
rel.get("properties") or |
|||
{k: v for k, v in rel.items() if k not in ("source", "target", "type", "relationship")} |
|||
) |
|||
if isinstance(rel_props, dict): |
|||
edge_data.update(rel_props) |
|||
|
|||
g6_edge = { |
|||
"source": source_id, |
|||
"target": target_id, |
|||
"type": "line", # G6 默认边类型 |
|||
"type": "line", |
|||
"data": edge_data, |
|||
# "style": {}, # 可由前端统一配置 |
|||
"states": [] |
|||
} |
|||
g6_edges.append(g6_edge) |
|||
|
|||
# 5. 组装结果 |
|||
result = { |
|||
return { |
|||
"nodes": list(g6_node_map.values()), |
|||
"edges": g6_edges |
|||
} |
|||
return result |
|||
|
|||
|
|||
def build_g6_subgraph_by_props( |
|||
neo4j_util: Neo4jUtil, |
|||
node_properties: Dict[str, Any], |
|||
node_label: Optional[str] = None, |
|||
direction: str = "both", |
|||
rel_type: Optional[str] = None |
|||
) -> dict: |
|||
neighbor_list = neo4j_util.find_neighbors_with_relationships( |
|||
node_label=node_label, |
|||
node_properties=node_properties, |
|||
direction=direction, |
|||
rel_type=rel_type |
|||
) |
|||
|
|||
# 提取所有唯一节点 |
|||
node_dict = {} |
|||
for item in neighbor_list: |
|||
for key in ["source", "target"]: |
|||
n = item[key] |
|||
nid = n.get("id") |
|||
if nid and nid not in node_dict: |
|||
node_dict[nid] = n |
|||
|
|||
# 如果没找到关系,但中心节点存在,也要包含它 |
|||
if not neighbor_list: |
|||
center_nodes = neo4j_util.find_nodes_with_element_id(node_label, node_properties) |
|||
if center_nodes: |
|||
n = center_nodes[0] |
|||
node_dict[n["id"]] = n |
|||
|
|||
nodes = list(node_dict.values()) |
|||
relationships = neighbor_list # 结构已兼容 |
|||
|
|||
return build_g6_graph_data_from_results(nodes, relationships) |
|||
|
|||
|
|||
def get_drug_names_from_neo4j(): |
|||
"""安全获取全部 Drug.name,支持大数据量""" |
|||
cypher = "MATCH (d:Drug) WHERE d.name IS NOT NULL RETURN d.name AS name" |
|||
results = neo4j_client.execute_read(cypher) |
|||
names = [] |
|||
for record in results: |
|||
name = record.get("name") |
|||
if name is not None: # 再次过滤 None |
|||
names.append(name) |
|||
print(f"[DEBUG] Loaded {len(names)} drug names from Neo4j") # 打印实际数量 |
|||
return names |
|||
|
|||
def get_check_names_from_neo4j(): |
|||
"""安全获取全部 Drug.name,支持大数据量""" |
|||
cypher = "MATCH (d:Check) WHERE d.name IS NOT NULL RETURN d.name AS name" |
|||
results = neo4j_client.execute_read(cypher) |
|||
names = [] |
|||
for record in results: |
|||
name = record.get("name") |
|||
if name is not None: # 再次过滤 None |
|||
names.append(name) |
|||
print(f"[DEBUG] Loaded {len(names)} check names from Neo4j") # 打印实际数量 |
|||
return names |
|||
def get_group_key(name: str) -> str: |
|||
if not name or not isinstance(name, str): |
|||
return "其他" |
|||
|
|||
name = name.strip() |
|||
if not name: |
|||
return "其他" |
|||
|
|||
for char in name: |
|||
if char.isdigit(): |
|||
return "0-9" |
|||
if char.isalpha() and char.isascii(): |
|||
return char.upper() |
|||
if '\u4e00' <= char <= '\u9fff': # 中文 |
|||
try: |
|||
first_letter = lazy_pinyin(char, style=Style.FIRST_LETTER)[0].upper() |
|||
if 'A' <= first_letter <= 'Z': |
|||
return first_letter |
|||
except Exception: |
|||
continue |
|||
# 其他字符:跳过 |
|||
|
|||
return "其他" # 所有无法归类的 |
|||
@ -0,0 +1,105 @@ |
|||
# redis_utils.py |
|||
import redis |
|||
from typing import Any, Optional, Union, List, Dict |
|||
import os |
|||
|
|||
# 从环境变量读取配置(可选,更安全) |
|||
REDIS_HOST = os.getenv("REDIS_HOST", "localhost") |
|||
REDIS_PORT = int(os.getenv("REDIS_PORT", 6379)) |
|||
REDIS_DB = int(os.getenv("REDIS_DB", 0)) |
|||
REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", None) |
|||
|
|||
# 创建全局唯一的 Redis 客户端(模块加载时初始化一次) |
|||
_client = redis.Redis( |
|||
host=REDIS_HOST, |
|||
port=REDIS_PORT, |
|||
db=REDIS_DB, |
|||
password=REDIS_PASSWORD, |
|||
decode_responses=True, |
|||
socket_connect_timeout=5, |
|||
retry_on_timeout=True, |
|||
health_check_interval=30 |
|||
) |
|||
|
|||
# ========== 封装常用方法(直接操作 _client) ========== |
|||
def set(key: str, value: Any, ex: Optional[int] = None) -> bool: |
|||
"""设置字符串值""" |
|||
return _client.set(key, value, ex=ex) |
|||
|
|||
|
|||
def get(key: str) -> Optional[str]: |
|||
"""获取字符串值""" |
|||
return _client.get(key) |
|||
|
|||
|
|||
def delete(*keys: str) -> int: |
|||
"""删除 key""" |
|||
return _client.delete(*keys) |
|||
|
|||
|
|||
def exists(key: str) -> bool: |
|||
"""检查 key 是否存在""" |
|||
return _client.exists(key) == 1 |
|||
|
|||
|
|||
def hset(name: str, key: str, value: Any) -> int: |
|||
"""Hash 设置字段""" |
|||
return _client.hset(name, key, value) |
|||
|
|||
|
|||
def hget(name: str, key: str) -> Optional[str]: |
|||
"""Hash 获取字段""" |
|||
return _client.hget(name, key) |
|||
|
|||
|
|||
def hgetall(name: str) -> Dict[str, str]: |
|||
"""获取整个 Hash""" |
|||
return _client.hgetall(name) |
|||
|
|||
|
|||
def lpush(name: str, *values: Any) -> int: |
|||
"""List 左插入""" |
|||
return _client.lpush(name, *values) |
|||
|
|||
|
|||
def rpop(name: str) -> Optional[str]: |
|||
"""List 右弹出""" |
|||
return _client.rpop(name) |
|||
|
|||
|
|||
def sadd(name: str, *values: Any) -> int: |
|||
"""Set 添加成员""" |
|||
return _client.sadd(name, *values) |
|||
|
|||
|
|||
def smembers(name: str) -> set: |
|||
"""获取 Set 所有成员""" |
|||
return _client.smembers(name) |
|||
|
|||
|
|||
def keys(pattern: str = "*") -> List[str]: |
|||
"""查找匹配的 key(慎用)""" |
|||
return _client.keys(pattern) |
|||
|
|||
|
|||
def ttl(key: str) -> int: |
|||
"""获取 key 剩余生存时间(秒)""" |
|||
return _client.ttl(key) |
|||
|
|||
|
|||
def expire(key: str, seconds: int) -> bool: |
|||
"""设置 key 过期时间""" |
|||
return _client.expire(key, seconds) |
|||
|
|||
|
|||
def ping() -> bool: |
|||
"""测试 Redis 连接""" |
|||
try: |
|||
return _client.ping() |
|||
except Exception: |
|||
return False |
|||
|
|||
|
|||
# 可选:提供原始 client(谨慎使用) |
|||
def get_raw_client(): |
|||
return _client |
|||
Loading…
Reference in new issue