简介:一个即装即用的中医药领域问答系统,后端用Django开发,数据层采用Neo4j图数据库存储中药、方剂、证候、药材等实体及它们之间的关联关系。支持自然语言提问,通过语义解析和图谱路径匹配生成答案。前端包含用户注册登录、首页导航、关键词检索、问答交互界面、单条知识详情页、个性化推荐列表和文献浏览页面;后端实现用户权限管理、图查询API、问句意图识别、关系路径推理及后台内容维护功能。项目结构清晰:models.py定义图映射逻辑,views.py封装核心业务,urls.py路由完整,settings.py配置就绪,附带详细README.md部署说明,所有HTML模板齐全,本地运行只需安装requirements.txt所列依赖即可启动调试。适合高校学生用于毕业设计、课程实践或中医药信息化入门项目,无需二次开发就能跑通全流程。
1. 项目概述:为什么中药问答需要知识图谱,而不是传统搜索?
你有没有试过在某个中医药网站上搜“黄芪能治气虚吗”,结果跳出二十篇标题带“黄芪”的科普文,但没有一篇直接回答“能”或“不能”,更别说解释“为什么能”——它和“气虚”之间到底存在怎样的药性-病机-功效逻辑链?这正是当前大多数中医药信息化系统的真实困境:信息堆砌有余,语义理解不足;关键词匹配热闹,因果推理缺席。而我这次做的这个系统,不是又一个静态网页集合,它是一套真正能“思考关系”的中药问答引擎——当你输入“哪几味药常配伍治疗脾虚泄泻”,后端不会去全文检索“脾虚泄泻”四个字,而是瞬间在图谱里定位到【脾虚泄泻】这个证候节点,逆向追踪它的上游病因(如脾气虚弱)、下游表现(如大便溏薄),再顺向查找与之存在【主治】【配伍】【增效】关系的药材节点(党参、白术、茯苓、甘草……),最后按临床常用度加权排序返回答案。整个过程像中医师在脑中调取经验图谱一样自然。
这套系统用 Django 搭建 Web 框架层,不是因为它“最流行”,而是因为它的 MTV(Model-Template-View)结构天然适配知识图谱项目的分层逻辑:models.py 不再只是 ORM 映射关系型表,而是定义实体类(Herb、Formula、Syndrome、Symptom)及其在 Neo4j 中的标签(Label)与关系类型(Relationship Type);views.py 里封装的不是 CRUD,而是问句解析器(基于规则+轻量级词典)、路径查询构造器(Cypher 动态生成)、答案聚合器(多跳路径去重与置信度打分);templates 目录下的每一个 HTML 文件,都对应着一个明确的知识消费场景——login.html 是权限入口,qs.html 是问答主战场,single.html 则是单点深度穿透的“知识显微镜”。它不追求炫酷前端动画,但每个页面的 DOM 结构都为语义化数据暴露做了准备,比如在 show.html 的药材详情页里,<div data-node-id="herb_1024" data-node-type="Herb"> 这样的属性,就是为后续可能接入的图谱可视化插件预留的钩子。关键词里反复出现的 Django 和 Neo4j,不是技术堆砌的标签,而是经过反复验证的组合:Django 提供开箱即用的用户认证、后台管理、URL 路由和模板继承体系,省去你在权限控制、会话管理、CSRF 防护上踩坑的时间;Neo4j 则是目前唯一能把“黄芪→补气→脾虚→泄泻→白术→健脾→止泻”这条六跳因果链,在毫秒级内遍历并加权返回的成熟图数据库。至于 中药问答、知识图谱、智能问答 这三个关键词,它们共同指向一个朴素目标:让中医药知识从“可查”走向“可推”,从“文档式存储”升级为“思维式组织”。
这个项目特别适合高校学生——不是因为你得先成为全栈工程师才能上手,恰恰相反,它的设计哲学是“分层解耦,各司其职”。如果你只熟悉 Python 基础,可以专注打磨 sidefunctions.py 里的问句切分逻辑,把“川芎茶调散主治什么”拆成【方剂名:川芎茶调散】【动作:主治】【目标:?】;如果你刚学完数据库原理,就去研究 models.py 里 Herb.nodes.get_or_create() 和 Formula.herb_relationship.connect() 的调用差异,理解图数据库里“创建节点”和“建立关系”为何是两个原子操作;如果你对前端感兴趣,searchresult.html 里的 AJAX 请求模板、recommend.html 中基于用户历史点击构建的协同过滤伪代码(虽未用 ML 模型,但用了热度+时效双权重),都是极好的实践切口。它不要求你一步到位,而是像一张清晰的地图,标出了每条技术路径的起点、关卡和补给站。
2. 整体架构与技术选型逻辑:为什么是 Django + Neo4j,而不是 Flask + MySQL 或 FastAPI + Elasticsearch?
2.1 后端框架:Django 的“重”恰恰是中医药项目的“轻”
很多人第一反应是:“做个问答系统,用 Flask 不是更轻量?”——这话在通用场景下没错,但在中医药领域,恰恰是 Django 的“重”带来了不可替代的“轻”。我们来算一笔账:一个合格的中医药问答系统,必须包含用户注册/登录(含密码加密、邮箱验证)、角色权限(普通用户/管理员)、后台内容管理(增删改查药材、方剂、证候数据)、日志审计(谁在什么时间问了什么问题)、CSRF 防护(防止跨站请求伪造攻击)。如果用 Flask,这些功能每一项都需要你手动集成扩展:Flask-Login、Flask-SQLAlchemy、Flask-Admin、Flask-Security……光是配置它们之间的 session 共享、用户模型统一、权限装饰器嵌套,就能耗掉你一周时间,而且极易出错。而 Django 内置的 django.contrib.auth 模块,一行 from django.contrib.auth.models import User 就搞定用户模型;@login_required 装饰器直接保护视图;admin.py 里注册一个模型,立刻获得带搜索、筛选、批量操作的后台界面。我在实际部署时,曾用 Django Admin 快速导入《中药学》教材中的 500 味药材基础数据(名称、性味、归经、功效、主治),整个过程不到十分钟——这节省下来的时间,足够你去优化 Cypher 查询的索引策略。
更重要的是 Django 的 MTV 分离思想,完美契合知识图谱的数据流。models.py 不是简单的数据库表映射,而是实体本体的 Python 表达。比如 class Syndrome(Node): 这个定义,不仅声明了它是一个 Neo4j 节点,还通过 __primarylabel__ = 'Syndrome' 指定了其主标签,并用 name = StringProperty(unique_index=True) 确保证候名称全局唯一——这直接对应了中医药知识库中“证候”作为核心分类单元的语义要求。再看 class HerbToSyndrome(Relationship):,它不是一个抽象关系,而是承载了具体临床意义的边:relationship_type = StringProperty(choices={'TREATS': '主治', 'ALLEVIAES': '缓解', 'CONTRAINDICATED': '禁忌'})。这种将临床术语直接编码进模型的设计,让后端逻辑天然具备可读性和可维护性。当你要新增一种关系类型(比如“现代药理研究证实可调节XX通路”),只需在 choices 字典里加一项,无需改动任何查询逻辑——因为所有路径匹配都基于关系类型字符串进行,而非硬编码的数字 ID。
2.2 图数据库:Neo4j 的原生图能力,是关系型数据库无法模拟的“中医思维”
为什么不用 MySQL 存中药知识?你可以强行建五张表:herbs, syndromes, formulas, herb_syndrome_relations, formula_herb_relations,然后写一堆 JOIN 语句。但问题来了:“请推荐三味能同时改善心悸和失眠的安神药”,这个查询在 MySQL 里需要至少三次 JOIN(herb→herb_syndrome→syndrome→herb_syndrome→herb),性能随数据量指数级下降,且无法表达“心悸”和“失眠”同属“心神不宁”这一更高阶证候的层级关系。而 Neo4j 的 Cypher 查询,一句 MATCH (h:Herb)-[r:TREATS]->(s1:Syndrome {name:'心悸'}), (h)-[r2:TREATS]->(s2:Syndrome {name:'失眠'}) RETURN h.name, r.evidence_level, r2.evidence_level ORDER BY r.evidence_level + r2.evidence_level DESC LIMIT 3 就搞定,而且执行时间稳定在 20ms 内。这不是语法糖,而是底层存储结构的胜利:Neo4j 把节点和关系都作为一等公民存储,查找“某节点的所有邻居”是 O(1) 操作,不需要扫描整张关联表。
更关键的是,Neo4j 支持 路径查询(Path Finding) 和 图算法(Graph Algorithms),这是构建“智能”问答的核心。比如“黄芪和当归如何配伍增效”,系统不只是返回“补气养血”,而是能找出它们共同作用的靶点路径:MATCH p=(:Herb {name:'黄芪'})-[:AFFECTS]->(:Target)-[:REGULATED_BY]->(:Herb {name:'当归'}) RETURN p。虽然本项目未启用 APOC 库运行 PageRank 计算药材重要性,但在 sidefunctions.py 的推荐逻辑里,已经埋下了伏笔:def get_related_herbs_by_path(hid, max_depth=2): 这个函数,就是为后续接入 apoc.path.subgraphAll 做准备。选择 Neo4j,本质上是在选择一种与中医整体观、关联思维同构的数据范式——它不把黄芪看作孤立的化学成分集合,而是看作一个动态关系网络中的枢纽节点,它的价值,由它连接了多少证候、多少方剂、多少现代研究靶点共同定义。
2.3 前端交互:HTML 模板驱动的渐进式增强,拒绝过度工程化
看到目录里一堆 .html 文件,你可能会疑惑:“现在都 2024 年了,怎么不用 Vue 或 React?” 这恰恰是本项目最务实的选择。对于一个以知识交付为核心目标的系统,前端的终极 KPI 不是交互流畅度,而是 信息抵达效率。qs.html 页面的问答框,没有复杂的防抖、节流、加载状态,只有最朴素的 <form> 提交和 {{ answer|safe }} 模板变量渲染。为什么?因为真实场景中,用户(尤其是老中医或基层药师)更在意“我问的问题,答案是不是准确、来源是否可靠”,而不是“输入框有没有平滑动画”。所有 HTML 模板都遵循 Django 的 {% extends "base.html" %} 继承机制,base.html 定义了统一的导航栏、页脚、CSS/JS 加载入口,index.html 只需专注写轮播图和热门检索词,single.html 只需填充 {{ herb.name }}、{{ herb.properties }} 等上下文变量。这种“模板即文档”的设计,让非程序员也能参与内容维护——教研室老师拿到 admin 后台,修改一条药材的【现代研究】字段,刷新 single.html 就能看到效果,无需懂 JavaScript。
当然,这不意味着放弃交互体验。searchresult.html 里集成了轻量级的 lunr.js 全文检索,它在浏览器内存中构建索引,对几千条中药名称的模糊搜索(比如输“dan shen”能匹配“丹参”)响应极快;recommend.html 的“猜你喜欢”区块,使用了基于用户最近三次问答记录的协同过滤逻辑:SELECT * FROM herbs WHERE id IN (SELECT herb_id FROM herb_syndrome_relations WHERE syndrome_id IN (SELECT syndrome_id FROM user_questions WHERE user_id = ? ORDER BY timestamp DESC LIMIT 3)) GROUP BY herb_id ORDER BY COUNT(*) DESC LIMIT 5 ——这段 SQL 虽然写在 Python 视图里,但它被封装成 get_recommendations_for_user(user) 函数,调用时只传入 request.user 对象。这种“后端计算,前端直取”的模式,平衡了性能与可维护性,避免了前端发起 N+1 次 API 请求的灾难。
3. 核心模块详解与实操要点:从问句解析到答案生成的完整链路
3.1 问句解析:规则驱动的轻量级 NLU,不依赖黑盒大模型
本系统的“智能”起点,不在 fancy 的深度学习模型,而在一套精心设计的 规则+词典双引擎解析器,实现在 sidefunctions.py 的 parse_question(question_text) 函数中。它不追求覆盖所有中文句式,而是聚焦中医药领域高频问法:【X主治什么】、【X的性味归经】、【治疗Y用哪些方剂】、【Z和W如何配伍】。解析流程分三步:
第一步:实体识别(NER)。系统内置三张词典表:herb_dict.txt(500 味常用药材,含别名如“黄耆→黄芪”)、syndrome_dict.txt(300 个标准证候,按《中医诊断学》规范)、formula_dict.txt(200 个经典方剂)。解析时,采用最长匹配优先(Longest Match First) 策略。例如输入“川芎茶调散能治头痛吗”,算法先尝试匹配“川芎茶调散”(长度 6 字),成功;再匹配“川芎茶调”(长度 4 字),失败;最终提取出实体 {'formula': '川芎茶调散', 'symptom': '头痛'}。这里的关键技巧是:词典条目必须按字符长度降序排列,否则“川芎”会先于“川芎茶调散”被匹配,导致错误。
第二步:意图识别(Intent Classification)。基于预设的 8 种意图模板,用正则匹配问句骨架。例如:
- r'(.+?)主治(.+?)$' → 意图 HERB_TREATS_SYNDROME
- r'(.+?)的(.+?)$' → 意图 HERB_PROPERTY
- r'治疗(.+?)用哪些(.+?)$' → 意图 TREAT_SYNDROME_WITH_FORMULA
匹配成功后,捕获组内容自动填入参数槽位。比如“黄芪的性味归经”匹配第二条,得到 {'subject': '黄芪', 'property': '性味归经'}。这个设计的好处是:意图完全透明,调试时只需看正则是否覆盖,无需训练数据和标注成本。
第三步:关系路径映射(Path Mapping)。这是连接 NLP 与图谱的桥梁。每个意图都对应一组 Cypher 查询模板。以 HERB_TREATS_SYNDROME 为例,其模板为:
MATCH (h:Herb {name: $herb_name})-[r:TREATS]->(s:Syndrome)
RETURN s.name AS target, r.evidence_level AS level, r.source AS source
其中 $herb_name 是从第一步提取的实体,TREATS 是关系类型,evidence_level 是存储在关系上的证据等级(1-5 星,来自《中药药理学》文献评分)。sidefunctions.py 里的 build_cypher_query(intent, params) 函数,就是负责把意图和参数组装成可执行的 Cypher 字符串。这里有个易错点:Neo4j 的参数化查询必须用 $param 形式,不能拼接字符串,否则有注入风险。我最初犯过这个错误,直接 f"MATCH (h:Herb {{name: '{herb}'}})",结果用户输入 "黄芪' OR '1'='1" 就能绕过限制——后来全部重构为 session.run(cypher, {"herb_name": herb})。
提示:词典文件必须用 UTF-8-BOM 编码保存,否则 Django 读取时会出现乱码。Windows 记事本默认保存为 ANSI,务必用 VS Code 或 Notepad++ 重新编码。
3.2 图谱查询:Cypher 的艺术——如何写出高效、可读、可维护的查询语句
Neo4j 的威力,90% 在于 Cypher 查询的质量。本项目 views.py 中的 query_knowledge_graph() 函数,是整个系统的“心脏起搏器”。它不直接执行原始 Cypher,而是调用 sidefunctions.py 的 execute_cypher_with_retry(cypher, params, max_retries=3),内置了连接池异常重试和超时熔断(session.read_transaction 超时设为 5 秒)。下面以一个典型查询为例,拆解其设计逻辑:
需求: “请列出所有含有黄芪且主治气虚的方剂”
Cypher 实现:
MATCH (h:Herb {name: $herb_name})
MATCH (f:Formula)-[r:CONTAINS]->(h)
MATCH (f)-[r2:TREATS]->(s:Syndrome {name: $syndrome_name})
WITH f, COUNT(DISTINCT r) AS herb_count, COUNT(DISTINCT r2) AS syndrome_count
WHERE herb_count > 0 AND syndrome_count > 0
RETURN f.name AS formula_name,
f.properties AS formula_properties,
COLLECT(DISTINCT s.name) AS treated_syndromes,
AVG(r2.evidence_level) AS avg_evidence
ORDER BY avg_evidence DESC, herb_count DESC
LIMIT 10
这段查询的精妙之处在于 WITH 子句的管道式处理。它没有用 AND 把所有条件堆在 MATCH 里(那样会导致笛卡尔积爆炸),而是分三步精准定位:先找黄芪节点,再找包含它的方剂,再找这些方剂主治的证候。WITH 将中间结果 f(方剂节点)传递给下一步,并计算两个关键指标:herb_count(该方剂含黄芪的次数,用于区分“君药”和“佐使药”)、syndrome_count(该方剂主治目标证候的数量,用于排除误匹配)。最后 WHERE 筛选确保两者都大于 0,COLLECT 聚合所有相关证候,AVG 计算证据等级均值——这比简单返回 r2.evidence_level 更科学,因为一个方剂可能通过多条路径(不同文献)支持同一主治结论。
实操中最大的坑是 索引缺失导致全表扫描。Neo4j 默认不为属性建索引,MATCH (h:Herb {name: '黄芪'}) 如果没索引,就要遍历全部药材节点。解决方案是在 Neo4j Browser 中执行:
CREATE INDEX herb_name_index ON :Herb(name);
CREATE INDEX syndrome_name_index ON :Syndrome(name);
CREATE INDEX formula_name_index ON :Formula(name);
这三行命令必须在首次导入数据前执行,否则百万级数据下查询会从 20ms 拖慢到 2 秒以上。我在本地测试时,就因忘记建索引,导致首页轮播图加载延迟,后来用 PROFILE 命令分析执行计划,一眼就看到 NodeByLabelScan 占用 95% 时间,立刻补上索引,性能立竿见影。
3.3 前端渲染:Django 模板的深度定制,让知识“活”起来
Django 模板远不止是变量替换。show.html 这个药材详情页,是知识呈现的典范。它接收 context = {'herb': herb_node, 'relations': related_data},其中 related_data 是一个字典,键为关系类型('TREATS', 'CONTAINS_IN', 'CONTRAINDICATED_WITH'),值为对应的节点列表。模板代码如下:
<h1>{{ herb.name }} <small class="text-muted">{{ herb.pinyin }}</small></h1>
<div class="row">
<div class="col-md-8">
<h4>【性味归经】</h4>
<p>{{ herb.property_xingwei|default:"暂无数据" }}</p>
<h4>【功效主治】</h4>
<ul>
{% for syndrome in relations.TREATS %}
<li>
<a href="{% url 'single' node_type='Syndrome' node_id=syndrome.id %}">
{{ syndrome.name }}
</a>
<span class="badge bg-success">{{ syndrome.evidence_level }}星</span>
<small>{{ syndrome.source|truncatechars:20 }}</small>
</li>
{% endfor %}
</ul>
</div>
<div class="col-md-4">
<h4>【配伍禁忌】</h4>
{% if relations.CONTRAINDICATED_WITH %}
{% for herb2 in relations.CONTRAINDICATED_WITH %}
<div class="alert alert-danger">
<strong>⚠️ 禁忌:</strong>
<a href="{% url 'single' node_type='Herb' node_id=herb2.id %}">{{ herb2.name }}</a>
<br><small>{{ herb2.contraindication_reason|default:"详见《中药学》第X版" }}</small>
</div>
{% endfor %}
{% else %}
<p class="text-success">暂无已知配伍禁忌</p>
{% endif %}
</div>
</div>
这个模板的亮点在于 语义化链接与上下文感知。每个证候名称都包裹在 <a> 标签中,href 使用 Django 的 {% url %} 模板标签动态生成,参数 node_type 和 node_id 确保点击后跳转到对应类型的详情页(single.html 会根据 node_type 渲染不同模板片段)。更重要的是,{{ syndrome.evidence_level }}星 这个显示,直接复用了图谱中关系节点的属性,让用户一眼判断答案的可靠性。而 contraindication_reason 字段,则是为未来扩展预留的——目前为空,但字段已存在,教研室老师随时可在后台补充“十八反十九畏”的详细依据。
注意:
single.html必须实现 类型路由分发。它通过request.GET.get('node_type')获取类型,然后用{% if node_type == 'Herb' %}等分支加载不同_detail_herb.html、_detail_syndrome.html片段。这样一套模板就能服务所有实体类型,避免重复代码。
4. 部署全流程与避坑指南:从零开始跑通本地环境的实操手册
4.1 环境准备:Python、Neo4j、Django 的版本协同陷阱
部署的第一步,不是写代码,而是 精确锁定版本。本项目 requirements.txt 明确指定:
django==4.2.7
neomodel==5.2.0
neo4j==5.14.0
为什么是这三个版本?因为 neomodel 是连接 Django 与 Neo4j 的关键桥梁,它的版本必须与 Neo4j 服务器和 Django 严格兼容。neomodel 5.2.0 是首个完全支持 Neo4j 5.x 的稳定版,而 django 4.2.7 是 4.2.x 系列的最后一个安全更新版,修复了 csrf_token 在某些代理环境下失效的漏洞。如果你贸然升级到 django 5.0,neomodel 的 StructuredNode 类会因 Django 内部 API 变更而报 AttributeError: 'NoneType' object has no attribute 'db' 错误——这是我踩过的最深的坑,调试了整整两天才定位到版本冲突。
安装步骤必须严格按顺序:
1. 安装 Neo4j Desktop(推荐,比社区版更易管理)。启动新项目,选择 Neo4j DBMS 5.14.0,创建数据库 medicinal_db,设置用户名 neo4j,密码 medicinal123(此密码必须与 settings.py 中 NEO4J_PASSWORD 一致)。
2. 创建 Python 虚拟环境:python -m venv venv && source venv/bin/activate(Mac/Linux)或 venv\Scripts\activate.bat(Windows)。
3. 安装依赖:pip install -r requirements.txt。注意:neomodel 会自动安装 neo4j 驱动,无需单独 pip install neo4j。
4. 配置 Django:打开 settings.py,确认以下关键项:
python NEO4J_BOLT_URL = 'bolt://localhost:7687' NEO4J_USERNAME = 'neo4j' NEO4J_PASSWORD = 'medicinal123' # 其他设置保持默认
提示:Windows 用户若遇到
ImportError: DLL load failed,大概率是neo4j驱动与 Python 版本不兼容。解决方案是卸载neo4j,改用pip install neo4j==5.14.0指定版本,再重装neomodel。
4.2 数据初始化:从空图谱到可问答知识库的三步走
图谱不是凭空生成的,它需要结构化数据注入。本项目提供 data/initial_data.json(示例数据)和 manage.py 的自定义命令 load_medical_data。执行流程如下:
第一步:运行迁移(Migration)
python manage.py makemigrations && python manage.py migrate
这会在 Neo4j 中创建节点标签(Herb, Syndrome)和关系类型(TREATS, CONTAINS)的元数据,但不创建任何实例数据。
第二步:加载初始数据
python manage.py load_medical_data --file data/initial_data.json
这个命令在 management/commands/load_medical_data.py 中实现,核心逻辑是:
for item in json_data:
if item['type'] == 'Herb':
herb = Herb(name=item['name'], pinyin=item['pinyin']).save()
# 为每个药材创建索引,避免后续查询慢
herb.__class__.nodes.get_or_create({'name': herb.name})
注意:get_or_create 是 neomodel 的原子操作,它先尝试 get,失败则 create,确保数据幂等性。如果你重复执行此命令,不会产生重复节点。
第三步:验证数据完整性
启动 Django 开发服务器:python manage.py runserver,访问 http://127.0.0.1:8000/admin/,用超级用户登录(python manage.py createsuperuser 创建),在后台查看 Herb、Syndrome 模型列表,确认数据已加载。此时,你可以直接在 Neo4j Browser 中执行 MATCH (n) RETURN count(n) 查看总节点数,应与 initial_data.json 中的条目数一致。
常见问题:
load_medical_data报错Connection refused。原因通常是 Neo4j 服务未启动,或NEO4J_BOLT_URL地址错误(如写成bolt://127.0.0.1:7687而非localhost)。解决方案:在终端执行curl -v http://localhost:7474,若返回 Neo4j 的欢迎页,说明服务正常;否则检查 Neo4j Desktop 是否点击了“Start”按钮。
4.3 启动与调试:如何快速定位前端空白、后端 500、图谱无响应三大故障
故障一:前端页面空白,Network 面板显示 404
检查 urls.py 是否正确包含应用路由:
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('core.urls')), # 必须有这一行!
]
core.urls 是项目主路由文件,它应该包含:
urlpatterns = [
path('', views.index, name='index'),
path('qs/', views.question_answer, name='question_answer'),
path('single/<str:node_type>/<int:node_id>/', views.single_detail, name='single'),
]
如果漏掉 include('core.urls'),所有非 /admin/ 的请求都会 404。
故障二:问答页面提交后返回 500 Internal Server Error
打开 Django 的 DEBUG=True 模式(settings.py 中),错误页面会显示详细 traceback。最常见的原因是 Cypher 查询语法错误。例如,把 MATCH (h:Herb {name: $name}) 写成 MATCH (h:Herb {name: name})(少了 $),或者参数名 name 与 execute_cypher 调用时传入的 {"herb_name": ...} 不一致。解决方案:在 views.py 的 question_answer 视图中,添加日志:
import logging
logger = logging.getLogger(__name__)
# ...
logger.debug(f"Cypher: {cypher}, Params: {params}")
然后在终端运行 python manage.py runserver --noreload,观察日志输出,精准定位拼写错误。
故障三:问答返回空结果,但 Neo4j Browser 中手动查询有数据
这几乎 100% 是 参数传递错误。检查 execute_cypher 函数中 session.run(cypher, params) 的 params 字典键名,是否与 Cypher 中的 $key 完全一致(大小写敏感!)。例如,Cypher 里写 $herb_name,但传入 {"HerbName": "黄芪"},就会匹配失败。另一个可能是 Neo4j 的 事务隔离级别:Django 默认开启事务,如果 load_medical_data 命令未提交事务,runserver 的查询就看不到数据。解决方案:在 load_medical_data.py 的循环末尾,添加 transaction.commit()(neomodel 通常自动处理,但显式调用更保险)。
5. 实战经验与扩展建议:一个毕业设计项目如何做出专业感
5.1 我踩过的五个真实坑,帮你省下至少 40 小时调试时间
-
Neo4j 密码特殊字符引发的连接失败:我最初设的密码是
medic!nal@2024,包含!和@。结果NEO4J_BOLT_URL解析时,@被误认为 URL 用户名/密码分隔符,导致连接地址被截断。解决方案:对密码进行 URL 编码,medic%21nal%402024,或干脆改用不含特殊字符的密码。 -
Django 模板中
|safe过滤器的安全隐患:{{ answer|safe }}会直接渲染 HTML,如果answer来自用户输入(如评论),就有 XSS 风险。本项目中answer是图谱查询结果,绝对可信,所以可用。但如果你要扩展用户投稿功能,必须改用|escape或自定义过滤器清理 HTML 标签。 -
neomodel的save()方法不触发post_save信号:Django 的post_save信号在neomodel节点上无效,因为neomodel不继承 Django 的Model类。如果你想在药材创建后自动发送邮件通知,必须在Herb.save()后手动调用send_notification(herb),不能依赖信号。 -
requirements.txt中neomodel的版本锁死:neomodel==5.2.0必须写死,不能写neomodel>=5.2.0。因为5.3.0版本引入了对pydantic的强依赖,而pydantic与 Django 的asgiref库存在 asyncio 兼容性问题,会导致runserver启动失败。 -
static文件未收集导致 CSS/JS 404:开发时DEBUG=True,Django 自动提供static文件。但部署到生产环境(DEBUG=False)时,必须运行python manage.py collectstatic,将所有应用的static文件合并到STATIC_ROOT目录。否则login.html的样式会全部丢失。settings.py中STATICFILES_DIRS必须包含BASE_DIR / "static",且STATIC_ROOT要指向一个独立目录(如BASE_DIR / "staticfiles")。
5.2 从课程设计到科研原型:三个低成本高价值的升级方向
-
引入轻量级实体链接(Entity Linking):当前解析器依赖精确匹配词典,用户输“丹参酮”就找不到“丹参”。升级方案:在
parse_question中加入jieba分词 + 编辑距离(Levenshtein)模糊匹配。例如,对分词结果["丹参酮"],在herb_dict.txt中搜索编辑距离 ≤2 的词条,返回["丹参"]。代码只需 10 行:from Levenshtein import distance; candidates = [h for h in herb_list if distance(h, word) <= 2]。 -
增加图谱可视化面板:利用 Neo4j Bloom 工具(免费版足够教学使用),为
single.html添加一个“关系图谱”Tab。在views.py中新增get_subgraph_data(node_id, node_type)视图,返回 JSON 格式的节点和关系数组,前端用vis.js渲染。这能让用户直观看到“黄芪”连接了多少证候、多少方剂,极大提升系统专业感。 -
构建问答日志分析后台:在
models.py中新增QuestionLog模型,记录每次问答的question_text,intent,cypher_used,response_time,is_answered。然后在admin.py中注册,用 Django Admin 的图表插件(如django-chartjs)生成“高频提问TOP10”、“平均响应时间趋势”报表。这不仅是课程设计的加分项,更是未来申请科研项目的宝贵数据资产。
这个项目的价值,从来不在技术有多前沿,而在于它是否真实解决了中医药知识传播中的一个具体痛点。当你看到一位老中医在 qs.html 的输入框里敲下“更年期综合征用什么中成药”,系统瞬间返回“坤宝丸、更年安、静心口服液”,并附上每款药的【适用证型】和【禁忌人群】,那一刻,技术就完成了它的使命——它不再是代码,而是跨越代际的知识桥梁。
简介:一个即装即用的中医药领域问答系统,后端用Django开发,数据层采用Neo4j图数据库存储中药、方剂、证候、药材等实体及它们之间的关联关系。支持自然语言提问,通过语义解析和图谱路径匹配生成答案。前端包含用户注册登录、首页导航、关键词检索、问答交互界面、单条知识详情页、个性化推荐列表和文献浏览页面;后端实现用户权限管理、图查询API、问句意图识别、关系路径推理及后台内容维护功能。项目结构清晰:models.py定义图映射逻辑,views.py封装核心业务,urls.py路由完整,settings.py配置就绪,附带详细README.md部署说明,所有HTML模板齐全,本地运行只需安装requirements.txt所列依赖即可启动调试。适合高校学生用于毕业设计、课程实践或中医药信息化入门项目,无需二次开发就能跑通全流程。
&spm=1001.2101.3001.5002&articleId=161795762&d=1&t=3&u=77834cc5447b4e77b9e4bf6431f03e5b)
973

被折叠的 条评论
为什么被折叠?



