
简介这是一套面向计算机、通信、人工智能、自动化等相关专业学生与开发者的医疗领域知识图谱问答系统源码基于SpringBoot与Neo4j构建可作为毕业设计、课程大作业或期末课程设计的高分参考方案也适合希望入门知识图谱与图数据库应用的小白进阶学习。资源包共210个文件以61个java源码、62个class编译文件、66个txt说明文档为主另含xml配置、json数据、properties参数文件及md项目说明压缩包约71.71MB目录结构完整便于按模块阅读与二次开发。目前已有268人学习下载。项目涵盖疾病信息建模、语句分类、分词匹配与问答生成等核心模块代码均经调试测试可正常运行答辩评审分达98分读者可据此掌握图谱构建、实体关系设计与问答流程实现并在此基础上修改扩展出个性化功能。1. 医疗知识图谱问答系统从 SpringBoot 到 Neo4j 的落地路径医院信息科最头疼的场景之一就是医生在门诊系统里想查「二甲双胍和格列齐特能不能联用」这类问题得翻三四个页面、切两个系统。基于 SpringBoot Neo4j 的医疗知识图谱问答系统本质上是把散落在药品说明书、诊疗指南、科室规范里的实体和关系抽出来存进图数据库再用自然语言问句去图里找答案。它解决的不是「聊天」问题而是「结构化医学关系检索」问题——疾病、症状、药品、检查、科室之间的多跳关联用关系型数据库写 JOIN 会写到崩溃用图数据库一条 Cypher 就能走通。这套方案适合有 Java Web 基础、想做知识图谱方向毕设或企业内知识库问答的开发者前端不需要多花哨重点在后端图谱构建和查询链路。2. 医疗图谱的数据建模节点、关系与本体设计怎么定2.1 为什么医疗场景优先选 Neo4j 而不是 MySQL医疗问答的核心查询模式是「多跳关系推理」。比如用户问「高血压患者合并糖尿病能用什么药」这条链路要经过疾病→并发症→禁忌药品→替代药品至少三跳。MySQL 做这件事需要多次自连接SQL 会膨胀到几十行而且每次新增关系类型都要改表结构。Neo4j 的存储结构是「节点关系属性」关系本身可以带属性比如「用药剂量」「禁忌等级」遍历时走的是指针跳转不依赖索引回表。常见做法是把医疗数据分成四类节点疾病Disease、症状Symptom、药品Drug、检查项目Examination。关系类型至少定义五种疾病-症状HAS_SYMPTOM、疾病-药品USE_DRUG、药品-禁忌CONTRAINDICATION、疾病-并发症COMPLICATION、疾病-检查NEED_EXAM。本体建模阶段不用追求大而全先把高频问诊场景覆盖住后面再增量补。提示Neo4j 社区版单机足够支撑百万级节点医疗毕设数据量通常在几万节点性能完全不是瓶颈别一上来就纠结集群。2.2 用 Cypher 建约束和索引三个必加的操作建图之前先加约束否则后续导入数据会出现重复节点。以下命令在 Neo4j Browser 里逐条执行// 为疾病名称加唯一约束防止重复插入 CREATE CONSTRAINT disease_name_unique IF NOT EXISTS FOR (d:Disease) REQUIRE d.name IS UNIQUE; // 为药品名称加唯一约束 CREATE CONSTRAINT drug_name_unique IF NOT EXISTS FOR (dr:Drug) REQUIRE dr.name IS UNIQUE; // 为症状名称加索引加速模糊匹配 CREATE INDEX symptom_name_index IF NOT EXISTS FOR (s:Symptom) ON (s.name);约束和索引的区别要分清约束是「不允许重复」索引是「加速查找」。疾病和药品用约束因为同名实体必须合并症状用索引因为症状描述可能有多个别名允许存在相似节点但需要快速定位。执行完可以用SHOW CONSTRAINTS确认。2.3 批量导入医疗实体LOAD CSV 的字段映射手工 CREATE 节点只适合调试真实数据用 CSV 导入。准备三个文件disease.csv、drug.csv、relation.csv。disease.csv 至少两列name、description。导入语句// 导入疾病节点字段名与 CSV 表头对应 LOAD CSV WITH HEADERS FROM file:///disease.csv AS row MERGE (d:Disease {name: row.name}) SET d.description row.description, d.category row.category; // 导入药品节点 LOAD CSV WITH HEADERS FROM file:///drug.csv AS row MERGE (dr:Drug {name: row.name}) SET dr.spec row.spec, dr.manufacturer row.manufacturer; // 导入疾病-药品关系 LOAD CSV WITH HEADERS FROM file:///relation.csv AS row MATCH (d:Disease {name: row.disease}) MATCH (dr:Drug {name: row.drug}) MERGE (d)-[r:USE_DRUG]-(dr) SET r.dosage row.dosage, r.note row.note;用 MERGE 而不是 CREATE是因为 MERGE 会先查再插避免重复。MATCH 两端的节点必须已存在否则关系建不上。CSV 文件要放在 Neo4j 安装目录的 import 文件夹下路径写相对路径file:///开头。如果导入报「Unable to load CSV」先检查文件编码是不是 UTF-8 无 BOMWindows 下用记事本另存为时容易带 BOM 头。3. SpringBoot 整合 Neo4j从依赖到查询接口的完整链路3.1 pom.xml 依赖与 application.yml 连接配置SpringBoot 整合 Neo4j 走 Spring Data Neo4jSDN是最稳的路线。pom.xml 加两个依赖!-- Spring Data Neo4j版本跟随 SpringBoot 父工程 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-neo4j/artifactId /dependency !-- Web 层提供 REST 接口 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependencyapplication.yml 配置连接信息spring: neo4j: uri: bolt://localhost:7687 authentication: username: neo4j password: your_password data: neo4j: database: neo4juri 用 bolt 协议不是 http。7687 是 Bolt 默认端口7474 是 Browser 的 HTTP 端口别填错。password 是首次启动 Neo4j 时设置的忘了就删 data/dbms/auth 文件重置。3.2 实体类映射Node 和 Relationship 的写法SDN 用注解把 Java 类映射到图节点。疾病实体Node(Disease) public class Disease { Id GeneratedValue private Long id; private String name; private String description; private String category; Relationship(type HAS_SYMPTOM, direction Relationship.Direction.OUTGOING) private ListSymptom symptoms; Relationship(type USE_DRUG, direction Relationship.Direction.OUTGOING) private ListDrug drugs; // getter/setter 省略 }Node 的值对应 Neo4j 里的标签名必须一致。Relationship 的 type 对应关系类型direction 指定方向。这里有个坑如果双向都配了关系序列化时会无限递归解决办法是一端用 JsonIgnore 或者只配单向。3.3 自定义 Cypher 查询用 Query 写多跳问句SDN 的派生查询只能做简单条件医疗问答的多跳必须手写 Cypher。在 Repository 接口里public interface DiseaseRepository extends Neo4jRepositoryDisease, Long { // 根据症状查可能的疾病一跳查询 Query(MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE s.name CONTAINS $symptom RETURN d) ListDisease findBySymptom(Param(symptom) String symptom); // 查疾病可用的药品排除禁忌药两跳查询 Query(MATCH (d:Disease {name: $disease})-[:USE_DRUG]-(dr:Drug) WHERE NOT (d)-[:CONTRAINDICATION]-(dr) RETURN dr) ListDrug findSafeDrugs(Param(disease) String disease); }$symptom 是参数占位符对应 Param 注解。CONTAINS 做的是字符串包含匹配比 更宽松适合用户输入不精确的场景。第二条查询用 NOT 排除禁忌关系这是医疗问答里很关键的安全逻辑——不能推荐禁忌药。3.4 Controller 层把问句转成查询参数RestController RequestMapping(/api/qa) public class QaController { Autowired private DiseaseRepository diseaseRepository; PostMapping(/ask) public MapString, Object ask(RequestBody MapString, String body) { String question body.get(question); MapString, Object result new HashMap(); // 简化处理实际项目应接分词和意图识别 if (question.contains(症状)) { String symptom question.replace(症状, ).replace(什么病, ).trim(); result.put(diseases, diseaseRepository.findBySymptom(symptom)); } else if (question.contains(用什么药)) { String disease question.replace(用什么药, ).trim(); result.put(drugs, diseaseRepository.findSafeDrugs(disease)); } else { result.put(msg, 暂不支持该问题类型); } return result; } }这段代码是「能跑通」的最小版本真实项目里 question 的解析要接 HanLP 或 Ansj 做分词和实体识别。但毕设阶段先用 contains 做规则匹配把链路跑通再替换 NLP 模块这样调试成本最低。4. 避坑与排查医疗图谱问答最常见的五个翻车点4.1 中文实体匹配不上现象是查询返回空列表现象Cypher 里WHERE d.name 高血压查不到数据但 Browser 里明明能看到这个节点。原因通常是 CSV 导入时字段带了空格或换行符或者 Neo4j 属性值存的是全角字符。解决办法导入前用脚本清洗或者在 Cypher 里用trim(d.name) 高血压。更稳妥的做法是在 Java 层做参数时统一 trim。4.2 关系方向搞反现象是查询结果为空但关系确实存在现象MATCH (d:Disease)-[:USE_DRUG]-(dr:Drug)查不到但 Browser 里能看到关系。原因是建关系时写成了(dr)-[:USE_DRUG]-(d)。Cypher 的关系是有方向的查询时必须和存储方向一致。排查方法在 Browser 里跑MATCH (a)-[r]-(b) RETURN a, r, b LIMIT 10看实际方向。如果方向确实反了要么改查询要么重建关系。4.3 SpringBoot 启动报 Driver 连接超时现象应用启动时抛org.neo4j.driver.exceptions.ServiceUnavailableException。原因通常是 Neo4j 没启动或者 bolt 端口被防火墙拦了。排查顺序先确认 Neo4j 服务在跑neo4j status再用telnet localhost 7687测端口。如果 Neo4j 在远程服务器检查 neo4j.conf 里dbms.default_listen_address是不是只绑了 127.0.0.1。4.4 返回 JSON 无限递归导致栈溢出现象调用接口时抛StackOverflowError日志里全是 Disease 和 Drug 的互相引用。原因是实体类里双向配了 Relationship序列化时 A 引 B、B 引 A 死循环。解决办法在关系字段上加JsonIgnore或者用 DTO 手动组装返回结构不直接返回实体。4.5 导入大数据量时内存溢出现象LOAD CSV 导入几万行时 Neo4j 报OutOfMemoryError。原因是默认堆内存太小或者用了CREATE而不是MERGE导致重复节点撑爆内存。解决办法改 neo4j.conf 里dbms.memory.heap.max_size2G导入语句用 MERGE并且分批提交——每 1000 行加一个WITH row LIMIT 1000的批次控制。5. 从规则匹配到语义问答把问句解析做扎实的两个技巧规则匹配能撑起毕设演示但真实用户不会按「XX用什么药」的格式提问。想让问答系统上一个台阶核心在问句解析。第一个技巧是「实体识别 意图分类」拆开做先用 HanLP 的 NER 模型把问句里的疾病、药品、症状抽出来再用关键词模板判断意图。比如「糖尿病能吃阿卡波糖吗」抽到疾病糖尿病、药品阿卡波糖意图是「药品适用性查询」对应 Cypher 就是MATCH (d:Disease {name:$d})-[:USE_DRUG]-(dr:Drug {name:$dr}) RETURN d, dr。这样比 contains 匹配稳得多。第二个技巧是给查询加「兜底路径」。用户问了一个图谱里没有的疾病直接返回空会让体验很差。可以在 Repository 里加一个模糊查询方法用toLower(d.name) CONTAINS toLower($keyword)做降级匹配返回「你是不是想问 XXX」的候选列表。这个逻辑在医疗场景尤其重要因为药品和疾病都有大量别名。// 模糊匹配兜底返回候选疾病名 Query(MATCH (d:Disease) WHERE toLower(d.name) CONTAINS toLower($kw) RETURN d.name AS name LIMIT 5) ListString findSimilarDiseases(Param(kw) String keyword);参数 $kw 传用户原始问句里的核心词LIMIT 5 控制返回数量避免候选太多。toLower 保证大小写不敏感虽然中文场景用处不大但药品名里常混英文缩写加上更稳。验证方法上我一般会准备一组 20 条左右的测试问句覆盖单跳、两跳、禁忌查询、模糊查询四类每次改完查询逻辑跑一遍看命中率和返回准确率。这个习惯是从一次答辩被问「你怎么证明系统查得准」之后养成的后来发现它比任何文档都有说服力。希望帮到你。本文还有配套的精品资源点击获取