FastAPI + Vue3 实训项目复盘:医疗咨询与预约系统的架构设计
文章目录
FastAPI + Vue3 实训项目复盘:医疗咨询与预约系统的架构设计
说明:本文是课程实训项目的技术复盘,只讨论系统设计和代码实现思路,不涉及真实医疗服务,也不提供任何医疗建议。
一、项目背景
这次实训项目我做的是 医云问(MedCloud Ask)。它的主题是医疗咨询与预约管理,目标是把 AI 问答、知识库、预约、号源管理和后台数据查看串成一个完整的练习项目。
一开始我没有把它设计成单纯的聊天页面,因为只做一个“输入问题、返回回答”的页面,业务链路会比较短,也很难体现后端的数据建模和工程能力。所以我把项目拆成了两个入口:
- 机构后台:用于维护知识库、配置科室和号源、查看预约、管理文章、查看咨询记录。
- 患者端页面:用于 AI 咨询、查看科普内容、选择科室和时间段并提交预约。
这样设计之后,项目就从一个简单的问答 Demo,变成了一个包含多角色、多模块和多条业务链路的全栈练习项目。
二、技术选型
项目采用前后端分离架构。
| 层次 | 技术选型 | 作用 |
|---|---|---|
| 前端 | Vue 3、TypeScript、Element Plus、Pinia、Vue Router、Vite | 管理后台和患者端页面 |
| 后端 | FastAPI、SQLAlchemy Async、Pydantic | API 服务和业务逻辑 |
| 数据库 | MySQL 8.0 | 存储机构、用户、预约、文章、会话等数据 |
| 缓存与并发控制 | Redis | 缓存、去重、分布式锁、布隆过滤器 |
| 消息队列 | RabbitMQ | 对话消息异步处理 |
| 搜索组件 | Elasticsearch | 咨询记录检索的扩展基础 |
| AI 问答 | Dify RAG | 知识库检索增强问答 |
| 部署 | Docker Compose | 管理 MySQL、Redis、RabbitMQ、Elasticsearch 等服务 |
整体思路是:MySQL 负责业务主数据,Redis 负责高频访问和并发控制,RabbitMQ 用于解耦后处理任务,Dify 负责知识库问答,Vue3 负责前端交互。
三、系统模块设计
1. 多租户机构模型
项目中最核心的模型是机构。每个机构都有自己的后台账号、知识库、患者端入口、科室、号源、预约记录、文章和咨询记录。
后台接口会从 JWT 中解析当前登录用户,并拿到用户所属的 organization_id。后续查询预约、文章、咨询记录、号源等数据时,都必须带上这个机构 ID 作为过滤条件。
这样可以避免不同机构之间的数据互相访问。例如预约列表接口不会相信前端传来的机构 ID,而是以后端解析出的当前用户机构 ID 为准。
这个点看起来很基础,但它是多租户系统里非常重要的安全边界。
2. AI 咨询与知识库问答
AI 咨询模块主要包括三部分:
- 机构上传知识库文档;
- 患者端发起问题;
- 后端调用 Dify 的流式问答接口。
知识库上传时,后端会先校验文件类型,目前支持 PDF、TXT、DOCX。文件会上传到 OSS,同时转发给 Dify,用于后续知识库检索。
患者提问时,后端主要流程如下:
患者提交问题
↓
根据 org_slug 查询机构
↓
创建或复用 Conversation 会话
↓
保存用户问题到 MySQL
↓
调用 Dify 流式接口
↓
通过 SSE 返回给前端
↓
流结束后保存 AI 回复
↓
投递消息到 RabbitMQ
这里使用 SSE 的原因是 AI 回复可能需要一定时间,如果等完整回答生成后再一次性返回,用户体验会比较差。流式输出可以让前端边接收边展示,交互会更自然。
3. 预约与号源管理
预约模块的核心是号源。后台可以维护科室和可预约时间段,每个时间段都有容量和剩余数量。
患者端预约时,需要选择科室、时间段,并填写基本信息。后端创建预约前会检查:
- 时间段是否存在;
- 时间段是否启用;
- 时间段是否已经过期;
- 是否还有剩余号源;
- 同一手机号是否已经预约过同一时间段。
其中比较关键的是并发控制。如果同一个时间段只剩一个号源,多个请求同时提交,就可能出现超卖问题。因此我在预约接口里使用了 Redis 的 SET NX。
预约流程大致是:
Redis 写入去重 key,防止重复预约
↓
Redis 写入 lock key,锁定 time_slot_id
↓
查询并校验号源
↓
扣减 remaining
↓
创建预约记录
↓
释放锁
相关 key 的设计如下:
appt:dedup:{patient_phone}:{time_slot_id}
appt:lock:{time_slot_id}
第一个 key 用于防止同一手机号重复预约同一时间段,第二个 key 用于保证同一时间段的扣减过程不会被多个请求同时执行。
这个模块让我更直观地理解了:并发问题不是“前端按钮禁用一下”就能解决,真正的保护必须放在后端。
4. 后台工作台
后台工作台主要用于展示系统当前的基础数据,包括:
- 今日咨询数;
- 今日预约数;
- 待确认预约数;
- 知识库文档数;
- 开放号源数;
- 待处理线索数;
- 最近咨询记录;
- 最近预约记录。
这个模块本身的业务逻辑不复杂,但能让后台从“菜单集合”变得更像一个完整系统。管理员进入后台后,可以先看到系统运行情况,再进入具体模块处理数据。
5. 咨询记录与高频问题
AI 咨询结束后,如果不保存记录,后续就无法分析用户都问了什么。因此项目中设计了 Conversation 和 Message 两张表。
后台咨询记录页面支持:
- 查询会话列表;
- 查看最新问题和最新回答;
- 按关键词搜索;
- 按日期筛选;
- 查看完整上下文;
- 统计高频问题。
当前实现主要基于 MySQL 查询,同时项目里也接入了 RabbitMQ 和 Elasticsearch 的基础链路。后续如果继续优化,可以把咨询记录同步到 Elasticsearch,再做更完整的全文检索。
6. 人工跟进线索
AI 问答并不能处理所有问题,所以项目中加入了人工跟进线索模块。
当患者输入内容中出现“转人工”“联系医生”“电话联系”等关键词,或者文本中识别到手机号时,系统会生成一条待处理线索。患者也可以主动在页面中留下手机号和问题摘要。
后台可以查看线索,并进行状态流转:
待处理 -> 已联系 -> 已关闭
这个设计的重点不是复杂算法,而是给 AI 问答加一个边界:能自动回答的走 AI,不能自动处理的转人工记录。
7. 文章与预约来源统计
项目中还包含健康科普文章模块。后台可以发布、下架、编辑文章,患者端可以查看文章。
为了让文章模块不只是静态内容展示,我在预约表中加入了来源字段。当患者从文章详情进入预约页面并成功提交预约时,系统会记录对应的文章 ID。
这样后台可以看到文章的:
- 阅读次数;
- 预约点击次数;
- 成功预约次数;
- 简单转化率。
这个模块让我练习了一个常见思路:业务数据不只要能新增、查询、删除,还可以通过来源字段串联出一条用户行为链路。
四、关键技术实现
1. Redis 布隆过滤器防缓存穿透
患者端访问机构信息时,会通过机构 slug 查询。如果大量请求使用不存在的 slug,可能会绕过缓存直接访问数据库。
因此项目在启动时会从 MySQL 读取所有机构 slug,并重建 Redis 中的布隆过滤器。
查询机构信息时,流程如下:
收到 slug
↓
布隆过滤器判断是否可能存在
↓
不存在:直接返回 404
↓
可能存在:查询 Redis 缓存
↓
缓存未命中:查询 MySQL
↓
回填 Redis
这样可以减少无效请求对数据库的压力。
2. Redis 分布式锁控制预约并发
预约并发问题的本质,是多个请求可能同时读取到相同的剩余号源。
比如某个时间段只剩 1 个号,两个请求同时读到 remaining = 1,如果都继续执行扣减,就可能出现超卖。
项目中通过 Redis SET NX 给 time_slot_id 加锁,保证同一时间段在同一时刻只有一个请求能执行扣减逻辑。
这部分代码虽然不长,但比普通 CRUD 更接近真实业务问题。
3. RabbitMQ 异步解耦
AI 回复保存后,后端会将消息投递到 RabbitMQ。这样后续同步到 Elasticsearch 或做统计分析时,不需要阻塞当前问答接口。
如果直接在接口中同步写入搜索组件,那么一旦搜索组件响应慢或者异常,用户当前问答也会受影响。
使用消息队列后,主流程只需要完成“保存消息 + 投递事件”,后续处理交给消费者完成。这就是异步解耦的价值。
4. JWT 与后端权限边界
后台接口中,很多数据都不能直接相信前端传参。例如预约列表、号源列表、咨询记录列表,都应该根据当前登录用户所属机构查询。
因此项目中通过 JWT 获取当前用户,再使用用户的 organization_id 作为数据过滤条件。
这个设计可以防止水平越权问题,也是多租户系统必须注意的地方。
五、项目中遇到的思考
1. AI 应用不只是调用接口
刚开始我以为 AI 模块主要就是调用一个接口。但实际做下来发现,一个完整的 AI 应用还要考虑:
- 知识库从哪里来;
- 文档上传失败怎么记录;
- 回答内容怎么保存;
- 多轮会话怎么续上;
- 用户问题怎么沉淀;
- 不能自动处理的问题怎么转人工。
所以 AI 问答只是入口,后面的数据建模和业务闭环同样重要。
2. 并发控制要放在后端
预约模块让我体会比较深。前端可以做按钮禁用、加载状态、防重复点击,但这些都不能替代后端校验。
真正的幂等、防重和防超卖逻辑必须在后端完成,因为接口可能被重复请求,也可能被绕过页面直接调用。
3. 业务字段会影响后续分析能力
文章来源统计这个功能让我意识到,很多分析能力不是后期凭空出现的,而是在建表时就要预留字段。
比如 source_article_id 和 source_page 这类字段,单看创建预约时不是必须的,但它们能帮助后续分析预约来源。
六、后续优化方向
目前项目已经完成基础闭环,但还有不少可以继续优化的地方:
- 咨询记录检索可以进一步切换到 Elasticsearch,支持分词、高亮和更复杂的搜索条件。
- 号源管理可以支持批量生成一周或一个月的排班。
- 人工跟进线索可以加入负责人、备注和跟进时间线。
- 知识库文档可以对接更完整的处理进度,例如向量化状态轮询。
- 文章推荐可以根据当前咨询内容做简单匹配,而不是只靠后台手动设置。
七、总结
医云问这个项目让我练习了从需求拆解到前后端实现的一整套流程。它不是单点功能,而是把咨询、知识库、预约、号源、文章、线索和后台统计串起来。
通过这个项目,我对下面几个问题有了更清晰的理解:
- 多租户系统要把机构边界放到所有数据查询里;
- 预约场景需要考虑幂等、防重和并发安全;
- AI 问答系统除了模型回复,还需要知识库、会话、记录和人工兜底;
- Redis、RabbitMQ、Elasticsearch 这些组件要结合具体业务场景使用,才真正有意义。
如果简单总结,这个项目对我来说不是“写了一个 AI 聊天页面”,而是完成了一次相对完整的全栈系统设计练习。
更多推荐
所有评论(0)