尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于SpringBoot和Uniapp的医疗云平台:在线问诊、私人医生与电子处方全栈实践

基于SpringBoot和Uniapp的医疗云平台:在线问诊、私人医生与电子处方全栈实践 简介医疗信息化是数字化转型的重要领域在线问诊、电子处方、健康档案管理等系统需求快速增长。从技术视角看SpringBoot作为主流的后端开发框架凭借快速构建RESTful接口和成熟的生态体系成为支撑业务逻辑的首选Uniapp则通过一套代码多端编译显著降低APP与H5的研发成本二者结合能够高效实现远程医疗场景下的完整业务闭环。本文详细解析了基于SpringBoot与Uniapp打造的综合医疗云平台涵盖在线问诊状态机、私人医生会员体系、排班管理、电子处方流转、支付回调幂等处理等核心设计并给出了数据库表结构、后端鉴权、WebSocket消息推送及跨端适配的工程实践方案为医疗项目研发和全栈开发者提供了一套可复用的参考路径。 这两年医疗健康赛道的系统需求特别多从在线问诊到私人医生服务产品形态越来越重光靠一个后台管理加一个简单的H5页面根本撑不起完整业务闭环。我之前完整落地过一个基于SpringBoot Uniapp的综合医疗云平台仿的就是京东健康、好医生这类头部产品的核心流程包含后台管理、PC端、手机APP三端集成了在线问诊、私人医生定制服务、电子处方、健康档案管理这些模块代码和数据库都有完整沉淀。如果你正准备做医疗类项目或者想参考一套可复用的全栈方案这篇文章应该能帮你省掉大量调研和踩坑的时间。先说下这套系统能做什么用户端APP负责挂号问诊、图文咨询、私人医生服务购买医生端可以接单、回复问诊、开处方后台管理负责科室医生管理、排班、订单统计、内容管理PC端则面向运营人员和医生工作站。技术栈选了SpringBoot做后端服务Uniapp做跨端APPMySQL存业务数据Redis扛热点缓存前后端分离接口统一走RESTful风格。规模上这是一套适合小团队自研或者作为毕业设计、课程设计升级版的完整方案。1. 项目整体设计与架构思路1.1 医疗云平台的核心业务闭环做医疗平台和做电商平台最大的区别在于业务链路长、角色多、数据敏感。一个完整的在线问诊流程从用户发起问诊到最终药品配送中间要经过选科室、选医生、支付、医生接诊、图文对话、开处方、审方、配送多个环节任何一个节点断了整个体验就废了。我先梳理一下这套系统要跑通的几个核心闭环在线问诊闭环用户提交问诊单 - 系统分配医生 - 医生接诊 - 图文/视频沟通 - 医生开处方 - 药师审方 - 用户查看电子处方 - 选择购药或线下自取私人医生服务闭环用户浏览服务套餐 - 下单购买 - 系统分配专属医生团队 - 建立健康档案 - 按周期随访 - 服务到期提醒续费运营管理闭环管理员维护科室医生 - 排班设定 - 用户预约 - 统计问诊量 - 佣金结算这三个闭环对应了三种不同的设计思路。问诊链路是核心中的核心必须把状态机设计清楚私人医生更偏会员订阅制要有服务周期管理的概念运营管理则是标准的后台CRUD加报表统计。1.2 技术选型为什么是SpringBoot Uniapp的组合这套组合在医疗类项目里非常成熟。SpringBoot生态完善做微服务也好、单体也好上手成本低接第三方支付、短信、阿里云OSS都很方便社区资料多遇到问题几乎都能搜到解决方案。Uniapp则解决了多端复用的问题一套代码同时编译到iOS、Android、H5对中小团队来说节省的成本非常可观。具体选型上后端SpringBoot 2.7.x MyBatis-Plus Spring Security JWT Redis RabbitMQ可选前端AUniapp Vue2也可以用Vue3版本 uView UI组件库管理后台Vue2 Element UI独立PC端项目数据库MySQL 5.7表结构用InnoDB引擎字符集utf8mb4文件存储阿里云OSS头像、处方图片、问诊图片接口文档Swagger/Knife4j自动生成为什么不用Spring Cloud微服务医疗平台如果业务量没到千万级单体应用加缓存、加异步队列完全够用拆得太细反而增加部署和运维成本。这套系统我的建议是做成模块化的单体应用按业务分包user、doctor、order、consult、prescription、admin等后续真要拆微服务也方便。1.3 三端协同后台管理、PC端、APP怎么分工三端不能各做各的职责必须清晰。用户APP面向患者只处理与用户相关的功能——注册登录、找医生、问诊、订单、档案。医生端APP与用户端同源通过角色切换进入工作台处理接诊事宜。PC管理后台面向平台运营人员管理医生入驻审核、排班、敏感词过滤配置、订单处理、财务统计。接口设计上要注意权限隔离。用户端接口统一走 /api/user/前缀医生端走 /api/doctor/前缀管理后台走 /api/admin/前缀各自有独立的Spring Security配置链JWT里带上角色信息拦截器做二次校验。这样三端共用一个后端服务但权限边界清晰不会出现用户拿着自己的token调管理接口的情况。2. 核心功能模块与关键设计2.1 在线问诊的完整状态机设计在线问诊是整套系统的命脉我花了最多精力设计它的状态流转。问诊单在不同角色操作下要经历多个状态如果只靠一个status字段用魔法数字到处判断代码很快就乱成一团。我最后用了一套前后端统一的枚举体系public enum ConsultStatus { PENDING_PAY(0, 待支付), WAIT_ASSIGN(1, 待分配), WAIT_ACCEPT(2, 待接诊), CONSULTING(3, 问诊中), WAIT_PRESCRIPTION(4, 待开方), WAIT_AUDIT(5, 待审方), COMPLETED(6, 已完成), CANCELED(7, 已取消), REFUNDING(8, 退款中), REFUNDED(9, 已退款); }设计状态机时有几个关键决策待支付状态独立存在。用户提交问诊单后先锁定医生和时段15分钟内不支付自动取消释放医生资源待分配和待接诊拆成两个状态。因为有的用户指定医生有的走平台智能分配分配逻辑和医生接单逻辑要分开处理开方和审方必须分开。医生开完处方不能直接生效要经过药师端审核这是医疗合规的基本要求哪怕系统里做的是简化版流程状态机上也要留出这一步问诊过程中的通信我用了WebSocket做实时消息推送后端通过Spring的SimpMessagingTemplate向指定用户推送新消息、订单状态变更通知等。同时保存一份消息记录到数据库保证用户刷新页面后历史消息不丢。2.2 私人医生定制服务的会员体系设计私人医生服务本质上是一个会员订阅产品。用户购买的不是一次问诊而是一段时间内的专属服务。这块设计和在线问诊完全不同我拆成了几个层次服务套餐表定义套餐名称、时长月/季/年、价格、包含次数每月几次图文问诊、几次电话问诊订单表记录用户购买记录包含订单号、套餐ID、服务生效时间、到期时间服务关系表用户购买后为用户分配一个私人医生团队形成用户和医生的绑定关系随访任务表根据套餐规则自动生成随访任务比如购买后第3天做首次回访、之后每2周回访一次这块最容易踩的坑是服务次数的核销逻辑。比如用户买了月度套餐包含10次图文问诊每次用完要不要扣次数如果用户在下单当天就把10次用完了后面20多天怎么办我最后设计的方案是优先扣套餐次数套餐次数用完后自动降级为普通问诊按次付费但享受专属医生的优先接诊通道。这样既保证了用户的体验又不会让平台亏本。2.3 排班系统与医生资源管理排班是整个平台能否运转顺畅的隐形瓶颈。没有排班系统用户选了医生却没人接诊体验极其糟糕。我设计了在线排班模式医生在APP端或PC工作台设置自己未来1-2周的可接诊时间段。管理员在后台可以统一排班也可以让医生自助排班。核心表设计CREATE TABLE doctor_schedule ( id bigint(20) NOT NULL AUTO_INCREMENT, doctor_id bigint(20) NOT NULL COMMENT 医生ID, work_date date NOT NULL COMMENT 出诊日期, period_type tinyint(4) NOT NULL COMMENT 时段类型1上午 2下午 3晚上, start_time varchar(10) NOT NULL COMMENT 开始时间, end_time varchar(10) NOT NULL COMMENT 结束时间, max_count int(11) NOT NULL DEFAULT 10 COMMENT 最大接诊人数, current_count int(11) NOT NULL DEFAULT 0 COMMENT 当前已约人数, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态0停用 1启用, PRIMARY KEY (id), KEY idx_doctor_date (doctor_id,work_date) ) ENGINEInnoDB;用户端展示医生排班时直接查这张表把current_count max_count的时段置为灰色不可选并显示已约满。用户选定时段后创建问诊单联动事务锁表更新current_count用乐观锁版本号避免并发超约。3. 数据库设计的核心表结构拆解3.1 用户体系与医生体系的建模用户和医生虽然角色不同但有大量公共字段手机号、头像、昵称。我没有把两个角色拆成完全独立的两张表而是用统一用户表加扩展表的方式t_user核心用户表存手机号、密码、昵称、头像、状态、注册时间。t_user_profile用户扩展信息存真实姓名、身份证号、病史简介、过敏史等。t_doctor医生表认证手机号、执业证书编号、所属科室ID、职称、擅长领域、个人简介。t_doctor_audit医生入驻审核记录表记录提交资料、审核状态、审核意见。用户端注册时只会往t_user写一条数据医生入驻时走t_doctor表后台审核通过后会把doctor_id和user_id关联起来。这种设计的好处是未来如果要做药师角色、运营角色可以复用同一套用户体系只需要新增扩展表就好。3.2 问诊与处方核心表问诊相关的表是整个系统的核心中的核心我列一下最终沉淀下来的核心表t_consult_order问诊订单主表关联用户、医生、科室、排班时段、问诊类型图文/电话/视频、费用、状态、创建时间。t_consult_message问诊沟通消息记录存消息内容、类型文本/图片、发送方角色、发送时间。t_prescription处方表关联问诊订单记录诊断结果、处方备注。t_prescription_item处方明细表一条处方对应多种药品记录药品名称、规格、用量用法、天数、数量。t_drug药品目录表药品名、通用名、生产厂家、规格、价格、库存。处方状态和问诊状态是联动的。问诊单进入待开方状态后医生可以创建处方开方后进入待审方药师审核通过后处方变为有效状态同时问诊单自动流转为已完成。这些状态流转我写了统一的状态机服务类避免在业务代码里到处散落状态判断。3.3 订单支付与健康档案的存储策略支付订单我单独建了t_payment_order表不跟业务订单表混在一块。原因是支付回调是异步的一个业务订单可能会对应多次支付尝试比如第一次支付超时用户重新发起支付把支付记录单独存更清晰。支付表核心字段支付单号、业务订单号、订单类型问诊/药品/私人医生服务、支付渠道微信/支付宝、支付金额、支付状态、回调时间。健康档案这块我的建议是用JSON字段存非结构化数据。用户的血压、血糖、过敏史这些信息字段不固定每次问诊医生还可能更新建一堆字段列反而难维护。我在t_health_profile表里设计了profile_data JSON字段把不同维度的健康数据分类存查询时按需解析。MySQL 5.7以上对JSON字段支持已经足够好这种场景用JSON比硬拆列要灵活得多。4. 后端SpringBoot核心实现细节4.1 工程结构与关键依赖后端工程按业务模块分包结构清晰。标准的maven工程长这样medical-cloud ├── medical-common // 公共模块统一返回、异常处理、常量 ├── medical-framework // 框架模块安全认证、Redis、配置 ├── medical-system // 系统模块用户、角色、菜单、字典 ├── medical-doctor // 医生模块医生、排班、科室 ├── medical-consult // 问诊模块问诊订单、消息、评估 ├── medical-prescription // 处方模块处方、药品 ├── medical-order // 订单模块业务订单、支付订单 ├── medical-admin // 管理端接口 └── medical-api // 对外接口聚合模块关键依赖就几个不追求多但一定要稳定dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.auth0/groupId artifactIdjava-jwt/artifactId version4.4.0/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency4.2 基于JWT的用户鉴权与权限控制医疗平台涉及大量用户隐私鉴权设计不能马虎。我用的方案是JWT Redis黑名单机制用户登录成功后后端签发JWT有效期设置为7天APP场景低频操作可以适当延长同时把token的jti存在Redis里过期时间与JWT一致用户每次请求带上Authorization头拦截器解析JWT并校验签名、有效期、角色用户退出登录时把jti加入Redis黑名单实现服务端主动失效token修改密码、账号异常时直接清掉Redis中该用户的所有tokenComponent public class TokenFilter extends OncePerRequestFilter { Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { String token request.getHeader(Authorization); // 解析JWT获取userId和roles // 校验Redis黑名单 // 放入SecurityContext chain.doFilter(request, response); } }这里有个细节用户端和后台管理员走两套登录接口但可以用同一个JWT工具类。JWT里的claims用role字段区分角色接口授权用PreAuthorize(hasRole(DOCTOR))注解控制不用专门写一堆if判断代码干净不少。4.3 在线问诊接口与消息推送实现在线问诊的核心接口包括创建问诊单、支付回调、取消订单、分配医生、医生接诊、发送消息、查询历史消息、创建处方、审核处方。这里拿创建问诊单举例可以看到事务和锁怎么配合Transactional(rollbackFor Exception.class) public ConsultOrder createConsultOrder(ConsultOrderDTO dto) { // 1. 校验用户状态 User user userMapper.selectById(dto.getUserId()); // 2. 校验医生排班时段是否可约乐观锁版本号 DoctorSchedule schedule scheduleMapper.selectById(dto.getScheduleId()); int rows scheduleMapper.decreaseAvailableCount(schedule.getId(), schedule.getVersion()); if (rows 0) throw new BusinessException(该时段已被约满); // 3. 创建问诊单 ConsultOrder order new ConsultOrder(); order.setOrderNo(generateOrderNo()); order.setStatus(ConsultStatus.PENDING_PAY.getCode()); // 4. 锁定医生 // 5. 返回订单号等待用户支付 return order; }消息推送用的是Spring WebSocket。用户和医生建立WebSocket连接后后端把连接会话和userId绑定发送消息时根据目标角色和userId定位到具体会话。为了兼容负载均衡场景我用Redis的发布订阅模式做了消息中转WebSocket服务收到消息后先发到Redis频道所有WebSocket节点订阅频道后再推送给目标会话。这样即使以后扩展多节点部署消息也不会丢。4.4 支付回调和退款流程的坑支付这块我踩过最多的坑。微信和支付宝支付回调是异步的回调可能延迟、可能重复、可能顺序颠倒所以回调处理一定要做幂等。我的处理方式是回调接口先根据支付单号查询本地支付状态如果已经是已支付就立即返回成功不再重复处理业务逻辑。同时开启事务把更新支付状态和更新业务订单状态放在同一个事务里任何一个失败都回滚避免出现支付单显示已支付但业务订单还是待支付的状态不一致。退款流程相对简单一些但也需要异步化处理。用户提交退款申请 - 后台审核 - 调用支付渠道退款接口 - 回调更新状态。需要注意的是微信退款和支付宝退款都要传原支付金额和退款金额如果金额不对会直接拒绝所以退款前最好做一次金额校验。5. Uniapp前端实现与跨端适配5.1 项目结构与页面规划Uniapp前端工程按功能模块和角色拆分布局用户端和医生端在同一个工程里通过tabBar和页面路由区分。用户端核心页面首页医生推荐、快捷问诊入口、科室列表、医生列表、医生详情、问诊详情聊天界面、订单列表、我的档案、私人医生购买页。医生端页面工作台待接诊/问诊中订单数、接单大厅、问诊聊天、开方页面、排班设置、收益统计。pages/ ├── user/ │ ├── index.vue // 患者首页 │ ├── doctor-list.vue // 医生列表 │ ├── doctor-detail.vue // 医生详情 │ ├── consult-chat.vue // 问诊聊天 │ ├── order-list.vue // 订单列表 │ └── private-doctor.vue // 私人医生 ├── doctor/ │ ├── workbench.vue // 医生工作台 │ ├── consult-chat.vue // 接诊聊天 │ ├── prescription.vue // 开方 │ └── schedule.vue // 排班 └── common/ ├── login.vue // 登录 ├── register.vue // 注册 └── webview.vue // 通用WebView5.2 跨端适配的四个关键细节Uniapp写起来爽适配起来还是有不少细节的。我总结了几条血泪经验rpx单位适配Uniapp用的是rpx750rpx等于屏幕宽度大部分场景直接用rpx做布局没问题。但是涉及到canvas绘制、地图标注点、富文本编辑器内部样式时还是要用px我踩过图表在部分安卓机上显示模糊的坑后来统一封装了px和rpx转换工具函数底部安全区适配苹果全面屏手机底部有home indicator页面底部按钮如果不做安全区适配会被挡住。用环境变量判断机型给底部按钮加上safe-area-inset-bottom的padding图片上传格式统一不同手机拍照传到后端的图片有的带exif信息导致方向不对有的二进制格式不标准。我在uni.uploadFile封装层做了统一处理前端压缩后转base64后端再做一次格式校验安卓返回键和物理返回APP端安卓用户可以按物理返回键但是页面栈和微信小程序不一样直接back会退出整个应用。我在onBackPress生命周期里做了统一路由控制确认是否是首页再允许退出5.3 聊天界面的实践方案问诊聊天是这个项目前端的核心交互上其实类似微信左侧是对方的消息气泡右侧是自己的消息气泡底部是输入框和表情按钮。用Uniapp实现聊天界面有几个注意点消息列表用scroll-view高度要撑满剩余空间使用flex: 1布局避免出现滚动条计算出错进入聊天页面时先调用后端接口拉取最近的历史消息再通过WebSocket长连接接收新消息消息渲染用v-for加key保证消息顺序稳定图片消息用uni.previewImage做预览大图要用云存储的缩略图参数直接加载原图在弱网环境下会卡开处方页面比较特殊我专门设计了一个表单选择诊断结果支持搜索常见病名称、添加药品明细从药品目录选择自动带出默认用量、填写医嘱说明。开完方后提交到后端进入药师审方流程。这个页面数据层级比较深用Vuex管理处方数据状态以免用户在多个步骤之间跳转时数据丢失。6. 系统部署与运维实录6.1 从零到能跑的部署流程整套系统后端是标准的jar包部署前端是编译后的静态资源加反向代理。部署流程如下准备一台Linux服务器2核4G以上够用安装JDK 1.8、MySQL 5.7、Redis 5、Nginx导入数据库脚本项目sql目录下修改application.yml中的数据库配置和Redis配置mvn clean package -Dmaven.test.skiptrue打包把jar包上传到服务器用systemd或nohup后台启动前端admin项目npm run build:prod编译dist目录丢到Nginx的html目录Uniapp项目用HBuilderX云打包生成安卓/ iOS安装包Nginx关键配置server { listen 80; server_name your-domain.com; # 前端页面 location / { root /usr/share/nginx/html/admin; 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; } # WebSocket代理 location /ws/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection Upgrade; } }6.2 常见问题与排查技巧把我在集成测试和现场部署阶段遇到的高频问题整理成了一张速查表基本覆盖了这套系统从开发到上线的常见坑问题现象可能原因解决方案APP请求接口报跨域后端未配置CORSSpringBoot加CorsFilter或Nginx统一加Access-Control-Allow-Origin header问诊单创建后医生看不到消息推送WebSocket断了检查Nginx的ws代理配置浏览器F12看WebSocket连接状态支付成功但订单状态没变支付回调被服务器防火墙拦截检查回调地址是否公网可访问关闭防火墙对8和443之外端口的限制图片上传失败阿里云OSS的bucket权限不对检查Bucket权限为公共读或私有读写签名URL数据库连接超时连接池配置太小调整HikariCP的maximum-pool-size推荐10~20安卓手机无法安装APP没有签名或目标SDK版本太高用正式证书签名打包targetSdkVersion保持在33以下聊天消息重复发送WebSocket重连机制导致消息重发前端保存消息发送状态重连后根据消息ID做去重6.3 上线后我建议优先优化的几个点系统刚跑通时只满足基本功能距离真正生产环境还有距离。我落地之后又做了几轮优化你可以按优先级参考问诊高峰期接口性能热门科室的医生列表和排班接口加Redis缓存缓存时间为30秒降低数据库压力敏感信息加密用户手机号、身份证号、健康档案数据入库前做AES加密查询时解密。虽然有性能损耗但医疗场景值得操作日志与审计后台管理端和医生端的关键操作处方审核、订单退款、用户信息修改都要记录操作日志出问题时才能追溯消息通知渠道扩展除了站内WebSocket推送重要的订单状态变化如医生接诊提醒接入短信或微信模板消息我个人在实际项目里体会最深的一点是医疗平台的开发难点不在技术本身而在业务流程的完整性和角色权限的边界控制。SpringBoot和Uniapp这套技术组合开发效率很高但一定要把状态机、角色权限、支付幂等这些基础设计做扎实否则后面每加一个功能前面欠的债都会加倍还回来。最后再分享一个小技巧如果你需要演示或验收建议在数据库中预置一批演示数据。我预置了10个科室、30个医生含排班、5个私人医生套餐、20条处方模板演示在线问诊时可以先创建一条问诊单再把问诊状态直接改到待接诊方便现场展示医生接诊和开方的完整流程。这套系统后续还可以扩展的方向不少比如接入视频问诊腾讯云TRTC、电子签名、药品库存管理等核心业务表结构都保留了扩展余地往这些方向加功能时不用推倒重来。本文还有配套的精品资源点击获取
返回列表