1. 项目整体设计与需求拆解
做医疗问诊类项目,最忌讳一上来就写代码。我见过太多人拿到“在线医疗问诊咨询平台”这个需求后,直接建表、写接口、画页面,结果做到一半发现角色权限一团乱麻,问诊流程根本走不通。所以第一件事一定是把业务模型拆清楚,把用户、场景、状态流转都理顺,再动手。
1.1 在线问诊平台的核心业务闭环
先问一个问题:这个平台到底要解决什么问题?核心就一句话——让患者不用跑医院,也能在线上找到合适的医生,完成初步的病情咨询和健康指导。围绕这个目标,业务闭环至少要覆盖这几个环节:
- 患者注册登录、完善个人健康档案
- 按科室、按医生主页查找并选择医生
- 发起问诊(图文描述病情、上传历史病历或检查报告)
- 医生接诊,双方进行图文或语音沟通
- 问诊结束后生成电子病历摘要,患者可查看历史问诊记录
- 患者对本次服务进行评价,医生端看到评价反馈
这个闭环听起来简单,但它决定了技术架构的方向。比如“问诊”到底是有状态的还是无状态的?答案是一定有状态。因为患者发起的问诊,从“待接诊”到“进行中”再到“已结束”,后面的页面展示、权限控制、数据统计全部依赖这个状态字段。这也是为什么我建议在数据库设计时,把问诊记录表作为一个核心业务表,而不是把消息简单堆在聊天表里。
1.2 用户角色与权限模型设计
医疗平台最少要有三类角色:患者、医生、管理员。注意,每一类角色对应的权限边界是完全不同的,不能只靠前端“隐藏按钮”来做权限控制,后端接口必须同样校验。我的做法是后端基于Spring Security + JWT做权限校验,前端用动态路由配合路由守卫控制页面访问,两侧共同把关。
患者端能看到的功能一般是:个人中心、我的问诊、发起问诊、健康档案、消息通知。医生端则是:待接诊列表、问诊工作台、我的患者、排班设置(如果有预约挂号)、统计概览。管理后台负责:用户管理、科室管理、医生审核、问诊监控、系统公告。权限模型建议在数据库层面用角色表、菜单表、角色菜单关联表来实现,而不是硬编码在代码里。
1.3 技术栈选型:为什么是SpringBoot + Vue
这个组合到今天依然是中小型Web项目的首选,原因很直接:SpringBoot让后端开发几乎不需要关心繁琐的XML配置,内置Tomcat,打包即运行,配合MyBatis-Plus或JPA做数据访问非常顺畅。Vue(尤其是Vue3 + Vite)在前端开发体验上非常友好,组件化开发适合快速搭建业务页面,生态里也有Element Plus这类现成的UI库,能省下大量样式工作。
有人可能会问:用单体架构会不会太老?我的看法是,在线问诊平台这种业务规模,单体架构完全够用,而且部署简单、维护成本低。如果一开始就上微服务、上消息队列,反而把复杂度拉高了,不利于快速交付。当然,如果考虑到后续要做视频问诊、对接第三方支付和药品配送,那可以把这些模块拆成独立服务,但基础的问诊闭环仍然是核心,不要本末倒置。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与项目初始化
环境这块看似简单,实际上很多项目死在第一步。SpringBoot版本和JDK版本不匹配、Node版本太高导致Vue依赖安装失败、Maven拉不到依赖,这些问题我全部踩过。所以这一节把版本搭配和初始化步骤写清楚,照着做基本不会翻车。
2.1 开发环境版本搭配建议
先说结论,这是我在多个项目中验证过比较稳的组合:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | SpringBoot 2.7.x 对 JDK8/11 兼容最好 |
| SpringBoot | 2.7.x | 稳定、资料多、社区踩坑记录全 |
| MySQL | 5.7 或 8.0 | 8.0需注意驱动包和时区配置 |
| Node.js | 16.x 或 18.x | Vue3 + Vite 不建议直接用太新的20+ |
| Vue | 3.x | 搭配Vite使用 |
| Element Plus | 2.x | 饿了么团队维护的Vue3组件库 |
| MyBatis-Plus | 3.5.x | 省去大量单表CRUD代码 |
这里特别提醒一个坑:SpringBoot版本不要太新。SpringBoot 3.x 要求JDK17,如果你用的是JDK8,那就要锁定2.7.x。网上很多教程直接用最新版,结果新手一启动就报错,根本分不清是配置问题还是版本问题。我在实际教学中一直强调,项目开发初期,选择“足够成熟”的版本比选择“最新”的版本更重要。
2.2 SpringBoot后端工程快速初始化
后端项目我用Maven管理依赖,通过Spring Initializr生成基础工程。如果你用的是IDEA,新建Spring Initializr项目时手动改一下Group和Artifact就行,Java版本选8,依赖先勾选Spring Web、MySQL Driver、Lombok,后续再加MyBatis-Plus和JWT相关依赖。
工程目录建议按功能模块分包,而不是按技术类型分包。比如:
code复制com.medical.platform
├── config // 配置类,如CORS、拦截器、WebMvc配置
├── controller // 接口层
├── service // 业务逻辑层
├── mapper // 数据访问层
├── entity // 实体类
├── dto // 前端交互对象,避免直接暴露实体
├── vo // 视图对象
├── utils // 工具类
└── common // 统一返回结果、异常处理
不要把所有Controller都堆在一个包里,也不要让前端直接拿Entity当响应结果。很多安全隐患和数据结构混乱,都是因为后端图省事直接把实体序列化返回导致的。我后面会专门讲DTO和VO的作用。
2.3 Vue3前端工程搭建与依赖安装
前端用Vite创建项目:
bash复制npm create vite@latest medical-web -- --template vue
进入目录后安装依赖。如果你在安装依赖时速度很慢或者卡住,大概率是网络问题,建议先设置国内镜像源:
bash复制npm config set registry https://registry.npmmirror.com
然后安装项目基础依赖:
bash复制npm install
npm install vue-router@4 pinia axios element-plus
这里有两个细节要注意。第一,Vue3对应的路由版本必须是vue-router@4,如果你不指定版本直接安装,有些情况下会装成旧版本导致路由不生效。第二,Element Plus按需引入和全量引入各有优劣,初学者建议全量引入,在main.js里直接:
javascript复制import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
app.use(ElementPlus)
虽然打包体积会大一些,但胜在省心,后面开发时不用频繁配置按需加载。
3. 后端核心模块设计与实现
后端是整套系统的“地基”,地基没打好,前端再漂亮也没用。在线问诊平台的后端核心,我觉得可以拆成四个部分:数据库模型、用户认证、问诊状态流、消息交互。每个部分都有一些容易忽略的细节。
3.1 数据库设计:核心表与关键字段
数据库设计直接决定业务能不能跑得通。这里分享几个我在设计医疗问诊平台时的核心表,以及字段设计的理由。
第一张是用户表。我通常会把患者和医生的共性字段放一起,比如用户名、密码、真实姓名、手机号、头像、角色标识,然后通过一个用户角色表来区分身份。为什么不用两张完全独立的表?因为登录认证只认一套账号体系,管理起来更简单。医生需要额外存执业证书编号、擅长领域、所属科室,这些字段可以放到医生信息表里,与用户表一对一关联。
第二张是问诊记录表。这是整个系统的核心,我建议至少要包含以下字段:
| 字段 | 说明 |
|---|---|
| id | 主键 |
| patient_id | 患者用户ID |
| doctor_id | 接诊医生用户ID |
| status | 问诊状态:待接诊/进行中/已结束/已取消 |
| chief_complaint | 病情主诉,患者填写的症状描述 |
| start_time | 问诊发起时间 |
| accept_time | 医生接诊时间 |
| end_time | 问诊结束时间 |
| evaluation_status | 是否已评价 |
第三张是问诊消息表。消息表记录医患双方在问诊过程中的每一句话、每一张图片。字段包括:问诊ID、发送者ID、消息类型(文本/图片)、消息内容、发送时间。这里要注意给问诊ID加索引,否则随着消息量增长,查询聊天记录会越跑越慢。
3.2 JWT认证与用户会话管理
Web项目前后端分离之后,传统Session方案会遇到跨域、共享困难的问题。在线问诊平台我推荐使用JWT做认证。大概流程是:用户登录成功后,后端生成一个token返回给前端,前端存在本地存储中,每次请求在请求头带上Authorization: Bearer <token>,后端通过拦截器校验token,解析出用户信息,放入当前请求上下文。
SpringBoot里集成JWT并不复杂,主要是三步:定义一个JWT工具类负责生成和解析token,写一个拦截器实现登录校验,在WebMvcConfig里注册拦截器并配置放行路径。
java复制String token = JwtUtil.createToken(userId, role);
// 前端拿到token后,后续请求头携带 token
需要注意,JWT默认是明文base64编码的,不要把密码等敏感信息放进去。我通常只放用户ID和角色,这两个信息丢失后即使被截获,攻击者也拿不到更多有用数据。另外token一定要设置过期时间,一般24小时比较合理,过期后前端拦截到401状态码后自动跳回登录页。
3.3 问诊工单与消息推送方案
问诊不是普通聊天,它是有状态的业务流。患者发起问诊后,这条记录的状态是“待接诊”,医生接诊后变为“进行中”,任何一方点击结束问诊后变为“已结束”。整个状态机要在后端严格判断,比如“进行中”的问诊不允许患者重复发起新问诊,“已结束”的问诊不允许再发送消息。
消息推送方面,有的教程会让你直接上WebSocket,我的建议是先分清业务阶段。如果只是图文问诊,服务器只需要把新消息“通知”给客户端,实际的消息内容还是通过HTTP拉取,那么轮询就够用了。前端每隔3到5秒请求一次“未读消息数量”或“新消息列表”,实现简单,也不容易出Bug。
如果你要做视频问诊、实时音视频,那才需要考虑WebSocket或者WebRTC。我在实际项目中就踩过这个坑:一开始就上了WebSocket,结果心跳重连、消息可靠性、断线补发这些都要处理,复杂度翻倍。对于图文问诊,成熟的方案是先做轮询,后续再优化成长连接。
3.4 文件上传与电子病历管理
问诊过程中,患者经常会上传检查报告、化验单、历史病历等图片,医生端也需要保存接诊记录。这块的核心不是上传本身,而是“文件怎么存、路径怎么存、权限怎么控”。
开发阶段最简单的方式是上传到本地磁盘,数据库只保存文件访问路径,然后通过一个虚拟路径映射把上传目录暴露出去。生产环境我建议接入云存储或自建MinIO,但这里有个经验:一定要给文件上传接口增加大小限制和类型校验。
yaml复制spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
另外,医疗数据非常敏感,上传的图片最好做访问鉴权。我的做法是:图片文件不直接放在静态目录下,而是通过一个需要登录态的接口去读取。这样即使别人拿到了图片URL,没有登录态也看不到内容。市面上的云存储也支持私有读写和临时签名URL,效果是一样的。
4. 前端核心页面与交互实现
前端部分是用户直接接触的界面,体验好不好,往往决定平台能不能留住人。在线问诊前端我重点关注四块:路由权限、问诊会话界面、接口封装、状态管理。
4.1 动态路由与前端权限控制
前端路由不能把所有页面都写死在路由表里,因为不同角色能访问的页面不一样。我的做法是:登录成功后,后端返回当前用户的角色和权限菜单,前端根据权限动态生成路由,再用路由守卫拦截未登录或越权的访问。
Vue3里使用vue-router时,可以这样写路由守卫:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.path === '/login') {
next()
} else {
if (!token) {
next('/login')
} else {
next()
}
}
})
如果要做得更细,可以结合路由meta字段配置所需角色,在守卫里判断当前用户角色是否匹配。但切记,前端路由权限只是提升体验,真正的安全防线在后端接口,这个思路我在前面已经强调过。
4.2 问诊会话页面实现思路
问诊会话页是患者和医生最常用的页面,核心是一个聊天式界面。左半区是问诊信息面板,显示患者主诉、历史病历、当前状态,右半区是消息列表和输入框。消息列表我建议用一个滚动容器,加载完历史消息后把滚动条定位到底部。
消息的展示要有基本的区分:自己发的消息靠右,对方的消息靠左,消息间显示发送时间。如果有图片消息,用Element Plus的el-image组件预览大图。这里有个体验细节:发送消息后,不要等后端接口返回了才把消息push到列表,而是先把本地消息渲染出来,标记为“发送中”,收到接口成功响应后再更新为“已发送”。这样用户操作不会卡顿。
4.3 Axios封装与接口联调规范
我把Axios封装成一个独立模块,统一处理请求头、响应拦截、错误提示。核心代码如下:
javascript复制import axios from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
baseURL: '/api',
timeout: 10000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
})
service.interceptors.response.use(
response => response.data,
error => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
} else {
ElMessage.error(error.response?.data?.message || '请求失败')
}
return Promise.reject(error)
}
)
baseURL用/api开头,是为了在Nginx部署时统一做反向代理,把前端请求转发到后端服务,避免跨域问题。这个细节我在部署章节会再展开。
4.4 状态管理选择:Pinia还是Vuex
Vue3项目的状态管理我推荐Pinia,原因是它比Vuex更简洁,去掉了mutations的概念,直接在store里定义state和actions,使用起来几乎零学习成本。比如全局保存用户信息:
javascript复制import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: localStorage.getItem('token') || ''
}),
actions: {
setUserInfo(info) {
this.userInfo = info
},
logout() {
this.userInfo = null
this.token = ''
localStorage.removeItem('token')
}
}
})
注意千万不要把用户密码等敏感信息存在Pinia里,也不需要存在localStorage里。用户基本信息在刷新页面后可以重新调用后端“获取当前用户信息”接口拉取。
5. 在线问诊平台的安全与合规细节
医疗健康类项目,安全和合规怎么强调都不过分。这一节不谈空泛的大道理,只讲在代码层面真正要落地的几个举措。
5.1 密码存储与数据加密实践
用户密码一律使用BCrypt加密存储,Spring Security框架里已经集成了BCryptPasswordEncoder。BCrypt每次加密的结果都不同,而且自带盐值,比简单的MD5加盐要安全得多。即便数据库泄露,攻击者拿到密文也很难反推出明文密码。
除了密码,像手机号、身份证号这类个人敏感信息,我建议在数据库存储时进行加密处理。可以在实体字段上做加解密转换,也可以写一个MyBatis的类型处理器,查询时自动解密,插入时自动加密。这样业务代码不用关心加解密细节,对开发效率影响很小。
5.2 敏感操作审计与操作留痕
医疗问诊平台一定要有操作日志记录。谁在什么时间查看了哪个患者的病历、修改了哪条问诊记录,都需要留下痕迹。我的做法是用AOP做一个登录日志注解,标记在需要记录的操作方法上,每次调用时自动记录操作账号、操作类型、请求IP、操作时间、请求参数。审计日志单独存一张表,前端管理端提供查询页面。
这里有个经验:不要试图记录所有请求的日志,那样日志量太大了,也没人看。只对敏感操作做审计,比如登录、查看病历详情、修改问诊状态、删除问诊记录等,这样的日志才有实际排查价值。
5.3 患者隐私保护与展示脱敏
在问诊列表、医生工作台、管理后台里,患者姓名和手机号不能全部明文展示。前端展示时,姓名字段只显示姓氏加星号(张*),手机号只保留前三位和后四位(138****1234)。后端返回数据时就要做脱敏处理,不能指望前端来做。
另外,医疗内容还需要做合规层面的“软处理”。平台应该在显著位置提示用户,线上问诊内容仅供参考,不能替代线下诊断,急重症患者应及时就医。在技术实现上,可以在患者首次问诊时弹窗确认“用过须知”,问诊结束时引导用户填写满意度评价,这些功能既满足了业务需求,也承担了平台责任。
6. 部署上线与运维避坑
开发完的项目最终要跑起来。很多人在本地开发一切正常,一部署到服务器就各种问题,这一章节集中梳理我遇到过的部署问题和解法。
6.1 前后端构建与Nginx部署
后端打包:
bash复制mvn clean package -DskipTests
打包完成后,在target目录下会生成一个jar文件。放到服务器上运行:
bash复制java -jar medical-platform.jar --spring.profiles.active=prod
前端打包:
bash复制npm run build
打包产物在dist目录中,里面有静态的index.html、assets等文件。我习惯把dist目录上传到服务器的/www/medical-web目录,然后配置Nginx:
nginx复制server {
listen 80;
server_name your-domain.com;
# 前端静态资源
root /www/medical-web;
index index.html;
# 前端路由刷新404问题
location / {
try_files $uri $uri/ /index.html;
}
# 后端接口反向代理
location /api/ {
proxy_pass http://127.0.0.1:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这里的proxy_pass http://127.0.0.1:8080/;末尾斜杠很关键,它表示把/api/xxx重写为/xxx。如果你后端Controller的路径本来就带/api前缀,那么proxy_pass就不能带末尾斜杠。这个细节很多人搞混。
6.2 我踩过的典型问题与排查实录
问题一:Vue打包后首页白屏。 检查发现build配置里assetsPublicPath用的是绝对路径/,部署在服务器子目录时资源加载不到。解决办法是在Vite配置文件里设置base: './',使用相对路径。
问题二:跨域请求失败。 开发环境通过Vite的代理转发解决,生产环境用Nginx同域反向代理解决。千万不要在开发环境把后端CORS配置开放给所有人,也不要为了省事在前端直接请求后端IP加端口。
问题三:MySQL时区报错。 连接MySQL 8.0时,JDBC连接串不加serverTimezone=Asia/Shanghai会报错。同时也要注意后端Jackson序列化时间时,增加spring.jackson.date-format配置,统一时间格式。
问题四:上传大文件失败。 在前端和后端都要设置文件大小限制。如果用了Nginx反向代理,还需要在Nginx配置client_max_body_size 20m;,否则文件一超过1MB就会被Nginx直接拒绝。
问题五:Nginx部署多个Web项目。 需要配置不同的location路径,不要在server块里傻傻地只放一个root。比如医疗平台放在/medical路径下,另一个项目放在/other路径下,用location匹配即可。
6.3 性能优化与日常运维建议
在线问诊平台虽然并发量不会像电商那么大,但作为技术项目,几个性能习惯还是要养成。数据库表尽量加上必要的索引,尤其是问诊状态、用户ID、创建时间这些查询条件的字段。列表查询不要查出所有字段,按需SELECT。
还有一个小技巧:问诊消息表会越积越多,可以考虑按月份或者按问诊ID做分区。对于三个月前的历史消息,归档到历史表,避免主表数据量膨胀太快。我见过一个项目上线半年没做任何归档,消息表积累了上百万行数据,每次打开聊天页面要等一两秒,体验非常差。
运维上,建议写一个简单的服务管理脚本,包括启动、停止、重启、查看日志。日志文件一定要按天切割,否则单文件越写越大,排查问题的时候很难定位。这个习惯在项目上线后能帮你省掉很多麻烦。
写在最后:一些实际开发中的体会
如果让我总结这个在线医疗问诊咨询平台项目最值得学习的地方,我觉得不是某个具体的技术点,而是“从需求到落地”的完整链路。SpringBoot和Vue的语法在网上随便一搜都有,但怎么设计问诊状态流、怎么控制医疗数据的访问权限、怎么在前后端分离架构下做好联调和部署,这些东西才是真正的经验壁垒。
我自己的体会是,这类平台型项目,开发节奏大概分成三个阶段:第一阶段把业务闭环跑通,重点是注册登录、问诊发起、医生接诊、消息发送;第二阶段完善细节,比如电子病历、健康档案、评价体系、管理后台;第三阶段才是性能优化和部署加固。很多人一上来就想着做视频问诊、做AI辅助诊断,基础功能反而没做好,得不偿失。
最后分享一个小技巧:开发阶段一定要造好测试数据,尤其是医生排班、科室信息、历史问诊记录。有了充实的数据,前端开发时才能真实地展示页面效果,后端联调时也更容易发现逻辑漏洞。我见过不少项目因为测试数据太少,到临近上线才发现页面在数据为空时会报错。医疗问诊平台的核心是信任,而信任建立在每一个细节都经得起推敲的基础上。
