ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

SpringBoot+Uniapp医疗云平台源码:三端闭环与部署实战解析

SpringBoot+Uniapp医疗云平台源码:三端闭环与部署实战解析

简介:E疗通综合医疗云平台是一套基于SpringBoot 2.x与Uniapp的完整医疗系统源码,面向需要搭建在线问诊与私人医生定制业务的开发者、产品经理或医疗信息化项目团队。平台参考京东健康与好医生模式,完整包含后台管理、PC端及手机端,实现名医在线一对一咨询、电子处方流转与药剂师审核、模拟医保购药、病患跟踪和智能用药提醒等核心功能,同时覆盖用户端、运营端与医生端,链路完整可直接演示或二次开发。后端采用SpringBoot集成Mybatis-Plus、SpringSecurity、Redis与MySQL8.0,提供Swagger接口文档;前端包含Vue+ElementUI的Web后台和Uniapp的移动端APP,模块化结构清晰,便于学习复用。压缩包共2000个文件,以1018个Java接口代码、500个Vue页面、297个JS逻辑文件为主,另含XML、JSON、SQL、Shell脚本等配置与部署资源,包体约79.73MB。已有166人学习下载,适合具备SpringBoot与Vue基础、希望获得可落地工程方案的中高级Java全栈开发人员。

1. 一套医疗云平台源码,最值钱的不是页面,而是三端闭环的业务链路

「基于 SpringBoot + Uniapp 的综合医疗云平台」这类源码,我前后接手过三个版本,形态大同小异:患者用的 Android/iOS APP 一套、医生和运营用的后台管理一套、对外展示的 PC 门户一套,后端清一色 SpringBoot + MySQL。拿到代码后先别急着点运行,最值钱的不是某个页面画得多好看,而是「患者在线问诊 → 医生开方 → 运营后台排班统计 → 私人医生随访」这条链路在同一条数据库里闭环。它适合三类人:想快速跑通互联网医疗 demo 找融资的团队、需要一套能答辩能演示的课设或毕设进阶项目的人、接了医疗外包但缺底层代码的开发者。有了这套东西,原本从零建模要两个月的工作量可以压缩到两周。

2. 拆开这套架构:后端 SpringBoot、多端 Uniapp 与 PC 管理端各自的分工

这套源码在工程结构上一般分成三个并列目录:后端一个 SpringBoot 工程,负责所有接口、鉴权、支付回调、定时任务;APP 与 H5 共一个 Uniapp 工程,一份代码产出 Android、iOS、微信小程序和 H5 页面;PC 管理端通常是一个 Vue 工程,部署方式有两种,要么独立 Nginx 托管,要么打包成静态文件塞进 SpringBoot 的 resources/static 目录。三端共用同一套/api接口,只是页面载体不同。

2.1 为什么这套方案选 SpringBoot + Uniapp,而不是 SpringCloud 或原生 App

常见做法是 SpringBoot 2.x + MyBatis-Plus + MySQL 组合,理由很直接:生态成熟、招人容易、部署简单,一个 jar 包扔到服务器就能跑。管理后台用 Vue + Element UI 是因为组件现成,表格、表单、权限菜单这类后台页面几乎不用自己写样式。Uniapp 能进这个组合,核心原因是医疗 C 端必须同时覆盖微信小程序和 APP,一套代码出多端比原生开发省掉一整支前端团队。

对比一下就清楚:后端如果换成 SpringCloud 微服务,单机 demo 反而要多维护注册中心、网关、配置中心三套组件,对这套业务量来说是负资产;APP 如果换成 Flutter 或 React Native,微信小程序仍然要单独维护一份代码,等于同时养两套前端。标题里的组合是「做省力」的选型,不是「做炫技」的选型,看懂这一点,后续二开就不会动辄想重构。

2.2 核心业务模块:在线问诊、私人医生定制、健康档案的数据流

在线问诊的主链路是:用户选科室、提交病情描述 → 创建问诊订单(状态为待支付)→ 支付成功进入待接诊 → 医生接诊后进入接诊中 → 图文或语音对话 → 医生写处方 → 用户确认处方 → 订单完成。状态机一般收敛在一个 service 类里,每次改状态都要校验前置状态,防止并发请求把订单状态改乱。这条链路里最容易写崩的是支付回调,回调成功但订单状态没同步,用户付款了却看不到医生接诊,二开时优先检查这块的幂等处理。

私人医生定制的本质是「会员订阅 + 服务计划」。用户购买套餐后,系统在 member_subscription 表插入订阅记录并绑定一个医生;医生按周期生成随访计划,系统每天按计划生成待办提醒;随访结果写回 health_record 表,最终在 APP 端展示成趋势图表。PC 管理后台承担的则是医生排班、问诊订单列表、支付对账、健康资讯发布、会员到期续费提醒。数据归属上,问诊订单是绝对主干,chat_message 和 prescription 都挂 inquiry_order_id 外键,健康档案不直接挂订单,而是用户级汇总表——这样统计医生月接诊量、科室热门病种时,SQL 只查订单表和关联表,不用去翻聊天记录。

2.3 数据库核心表:从用户体系到随访计划

这套源码的数据库一般包含十几张核心表,我按业务权重排了个清单:

表名职责关键字段
sys_user患者、医生、管理员统一账号体系user_type, status, mobile
doctor_info医生扩展信息doctor_no, title, specialty, service_package_id
department科室树parent_id, name, sort
inquiry_order问诊订单主干order_no, user_id, doctor_id, status, fee, create_time
chat_message图文、语音消息inquiry_order_id, sender_type, content, create_time
prescription处方inquiry_order_id, doctor_id, disease_name, medicine_json, status
health_record健康档案user_id, record_type, content_json
member_subscription私人医生订阅user_id, doctor_id, package_id, expire_date, status
follow_plan随访计划subscription_id, plan_json, cron_expr, next_run_time

先说一个容易踩的坑:订单表里通常会冗余 doctor_name、user_name 这类快照字段,原因是「历史快照」——医生后来改名或离职,订单里仍然要显示当时服务的人。二开时不要图省事把它改成纯关联表,否则历史订单查询会多出大量 join,一期数据量上来后性能明显变差。另一个要点是处方明细常用 JSON 存,原因很简单,开方页面的动态表单比固定字段更灵活;但要统计药品使用频次就得用 MySQL 的 JSON_TABLE 函数,不是普通 where 能搞定的。数据库这块不需要会复杂的增删改查技巧,把表间关系理清楚,比会写一百条 SQL 更重要。

3. 本地跑通后端:建库、改 yml、启动 SpringBoot 的最小命令集

拿到源码的第一天,我建议只做三件事:建库导数据、改配置、启动 jar 包验证接口。不要一上来就翻业务代码,先把地基打实。以下命令和配置以常见的 MySQL 8.0 + SpringBoot 2.x 为例,具体文件名以你拿到手的工程为准。

3.1 初始化 MySQL:导入 SQL 前的字符集与时区准备

mysql -uroot -p -e "CREATE DATABASE IF NOT EXISTS health_cloud DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" mysql -uroot -p health_cloud < /path/to/health_cloud.sql

第一行建库,第二行把源码包里的 SQL 文件导入。如果 SQL 脚本里自己带了 CREATE DATABASE 和 USE 语句,第一行可以跳过,直接执行第二行;重复建库不会报错,但会把原有数据覆盖掉,操作前确认这个库是全新环境。字符集必须用 utf8mb4,因为问诊描述、处方备注里大概率出现 emoji 和生僻字,老版本的 utf8 字符集会报乱码或 1366 错误;MySQL 8.0 默认就是 utf8mb4,但如果脚本内部写了旧字符集,建议先全局替换再导入。

导入完成后别急着关终端,先验证数据是否完整:select count(*) from sys_user;如果主表都不存在,说明导错了库或脚本本身不完整。顺手把 MySQL 时区确认一下,JDBC 连接串里会用到 Asia/Shanghai,数据库服务器的时区不一致时,时间字段会差 8 小时,排查起来非常消耗耐心。

3.2 application.yml 里必改的四个配置项

spring: datasource: url: jdbc:mysql://localhost:3306/health_cloud?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: health password: Health@2024 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 50MB max-request-size: 100MB file: upload-path: D:/health-cloud-upload/ access-path: /files/** jwt: secret: change-me-to-a-random-string-at-least-32-chars-long expire: 7200 server: port: 8080 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true

这是改动最密集的文件,四个重点项逐个说。数据源 URL 里的allowPublicKeyRetrieval=true是 MySQL 8.0 的专属需求,caching_sha2_password 插件首次连接时要向服务器要公钥,不加这个参数会报 Public Key Retrieval is not allowed,属于最常见的 SpringBoot 配置问题。file 段是后端的静态资源映射,upload-path是文件落盘目录,access-path是浏览器访问路径前缀,它们必须成对出现,后面第 5 章的图片 404 就是这里没配对。jwt.secret 必须手动换成一个 32 位以上的随机串,源码包自带的默认值等于把鉴权拱手送人。MyBatis-Plus 的 mapper-locations 一定要写classpath*:带星号,否则打成 jar 包后 mapper.xml 扫不到,启动不报错,一调接口就说 invalid bound statement。

3.3 启动并用 curl 验证:先看日志,再打接口

cd health-cloud-server mvn clean package -DskipTests java -jar target/health-cloud-server-1.0.0.jar --spring.profiles.active=dev # 另开一个终端验证 curl -i "http://localhost:8080/api/doctor/list?current=1&size=10"

打包启动比 IDE 里直接跑 main 类更接近生产环境,能提前暴露资源目录缺失、mapper 扫描不到这类问题。启动日志里看到 Started 才算成功;如果报端口占用,用lsof -i:8080找出占用进程处理掉,或者直接改 server.port。curl 返回 200 只代表 HTTP 层通,还要看响应体里的业务 code 是否为 0,很多源码的返回结构是{code: 0, data: {...}, msg: "success"},code 非 0 说明后端业务本身报错了。带鉴权的接口先用 /api/login 拿 token,再带Authorization: Bearer <token>请求,把这条链路验证通,后端就基本算跑起来了。

4. Uniapp 多端与 PC 管理端联调:APP、小程序、后台管理连同一个后端

后端跑通后,接下来把前端三端连到同一个接口地址上。这里最容易出现的情况是 APP 端调通了、小程序真机又 fail,或者 PC 管理端跨域报错。联调的核心是「接口地址统一 + Token 统一 + 错误处理统一」,下面按端逐个说。

4.1 封装 request.js:BaseURL 指向局域网 IP,Token 统一注入

const BASE_URL = 'http://192.168.1.108:8080/api'; export function request(options) { return new Promise((resolve, reject) => { const token = uni.getStorageSync('token'); uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' }, success: (res) => { if (res.data.code === 0) { resolve(res.data.data); } else if (res.data.code === 401) { uni.removeStorageSync('token'); uni.reLaunch({ url: '/pages/login/login' }); } else { uni.showToast({ title: res.data.msg || '请求失败', icon: 'none' }); } }, fail: (err) => reject(err) }); }); }

这段封装解决三件事:BaseURL 统一收敛、Token 自动注入、401 统一跳登录。真机联调时 BASE_URL 不能用 localhost,要填电脑的局域网 IP,且手机和电脑必须在同一网段;如果后端没开 CORS,H5 端会跨域,APP 端没有跨域概念但要确认 Android 的联网权限没被关。401 分支单独拎出来是重点——很多源码里登录态判断散落在每个页面,接手后第一件事就是把它们统一收进 request 层,否则后面加一个页面就漏一个鉴权,上线后用户 token 过期会看到各种诡异的白屏。

4.2 manifest.json 配置与安卓 iOS 打包注意

{ "name": "健康云医疗", "app-plus": { "distribute": { "android": { "minSdkVersion": 21, "targetSdkVersion": 30, "permissions": [ "android.permission.CAMERA", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.RECORD_AUDIO" ] }, "ios": { "privacyDescription": { "NSCameraUsageDescription": "用于拍摄病历照片", "NSMicrophoneUsageDescription": "用于医患语音聊天", "NSPhotoLibraryUsageDescription": "用于选择相册图片" } } } } }

manifest.json 是 Uniapp 多端打包的关口配置。权限的填写原则是「用多少申请多少」,问诊场景需要相机、相册、麦克风就够了,不要把定位、后台运行、通讯录这类敏感权限全开;安卓上架应用市场时权限声明和隐私政策必须对应,多一个无关权限就会被审核打回。iOS 端的 privacyDescription 是硬性要求,缺任意一项描述,App Store 审核直接拒。打包方式上,云打包省事但要传云端,本地离线打包要装 Android Studio 或 Xcode,常见的翻车现场是本地 SDK 版本和 Uniapp 运行环境不匹配,编译到一半报 aapt2 error,处理办法是严格按 HBuilderX 官方配套版本装 SDK,不要装最新版。

4.3 微信小程序打包:本地联调与上线前的域名校验

export default { onShareAppMessage() { return { title: '在线问诊,三甲医生在线接诊', path: '/pages/index/index' }; } };

Uniapp 跑微信小程序端的联调步骤是固定的:HBuilderX 运行到小程序模拟器,微信开发者工具里点「详情 → 本地设置 → 勾选不校验合法域名」,真机预览时保持手机和电脑同一网络。小程序和 APP 有个本质区别——真机环境强制校验 request 合法域名和 HTTPS,开发者工具里关掉校验只对开发期有效。上线前必须在微信公众平台把接口域名配成 HTTPS 备案域名,如果后端只有 http,小程序真机请求会全部 fail,这不是改代码能解决的,得在 Nginx 反代一层 HTTPS。onShareAppMessage里的 path 要带参数就拼 query,但要避免在分享路径里塞 token,分享出去被第三方截获就是账号泄露。

4.4 PC 管理端联调:开发期代理和上线部署两种方式

module.exports = { devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };

PC 管理端开发期的联调一般用 devServer 代理,把前端页面里的/api请求转发到后端,绕开跨域。上线后有两种常见部署方式:独立 Nginx 托管,或把 Vue 打包的 dist 静态文件放进 SpringBoot 的 resources/static 目录。如果选后者,有一个历史路由的坑必须处理:Vue Router 用 history 模式时,用户刷新/doctor/list页面会 404,因为后端没有这个路由;最省事的方案是改用 hash 路由,URL 带个#但功能不受影响;一定要用 history 模式,后端就得写一个 fallback Controller 把未匹配的路径转发到 index.html。这也是很多人问「vue 打包放进 springboot 中」时的标准答案。

5. 跑这套源码最容易翻车的 5 个现场:现象、原因与处理办法

源码能不能快速跑起来,拼的不是代码能力,而是排查配置问题的经验。下面五个现场我基本每次都会遇到,按「现象 → 原因 → 解决」写清楚。

5.1 数据库连不上:Access denied 与 Public Key Retrieval

现象:后端启动直接报Access denied for user 'root'@'localhost',或者Public Key Retrieval is not allowed,有时候还混着Communications link failure。原因基本是三个:MySQL 8.0 默认的 caching_sha2_password 密码插件和旧驱动不兼容;JDBC URL 缺少参数;账号的 host 权限没放开。解决方法是把驱动换成 8.x 的com.mysql.cj.jdbc.Driver,URL 里补上allowPublicKeyRetrieval=true&useSSL=false,同时创建一个带远程访问权限的专用账号:

CREATE USER 'health'@'%' IDENTIFIED BY 'Health@2024'; GRANT ALL PRIVILEGES ON health_cloud.* TO 'health'@'%'; FLUSH PRIVILEGES;

这套操作做完,99% 的连接问题都能解决。剩下 1% 是密码里带了特殊字符没转义,比如@在某些连接池配置里会被截断,建议密码只用字母数字加下划线。

5.2 图片上传成功但访问 404:静态资源映射是一对

现象:医生头像、病历图片上传接口返回了文件路径,但浏览器打开路径直接 404。原因:文件确实写到了磁盘,但 SpringBoot 根本没有把/files/**这个 URL 前缀映射到磁盘目录,所以请求打进来找不到文件。解决方法是写一个 WebMvcConfigurer 配置类:

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceLocations("file:" + uploadPath + "/"); }

注意uploadPath末尾一定要带斜杠,且file:前缀不能丢。这里最隐蔽的坑是uploadPath写成相对路径,比如./upload/,用 IDE 启动没问题,打成 jar 包部署后工作目录变了,文件就写到别处去了。改成绝对路径,或者在启动脚本里显式指定工作目录。

5.3 小程序真机请求失败:域名白名单、HTTPS 与证书链

现象:微信开发者工具里一切正常,一用手机预览,所有请求全部 fail;或者控制台明确提示「不在以下 request 合法域名列表中」。原因就是小程序真机强制校验域名白名单和 HTTPS,开发工具里勾选了「不校验合法域名」只对工具生效。解决方法是上线前把接口域名在微信公众平台配到 request 合法域名,并且必须 HTTPS。这里有个容易被忽略的细节:证书链不完整也会导致请求失败,有些证书商只给了域名证书没给中间证书,App 和浏览器都能打开,小程序却报「未能完成请求」。验证证书链用openssl s_client -connect yourdomain:443 -showcerts看输出,缺中间证书就补上,别在图省事上栽跟头。

5.4 Token 时灵时不灵:拦截器排除路径没配全

现象:登录接口自己返回 401,或者某些接口带 token 也 401,换个接口又正常。原因十有八九是鉴权拦截器的excludePathPatterns配漏了。登录、注册、验证码、静态资源这些路径全部要排除,漏了登录接口就会让整个系统变成「无法登录」。另一个常见问题是客户端 header 字段名和后端校验不一致,后端读Authorization,前端传了token,自然 401。解决思路是先在后端拦截器里打印实际请求路径,逐个对照排除列表;同时统一约定 header 格式为Authorization: Bearer <token>,客户端和后端都按这个标准改。还有一个隐蔽点:拦截器把/files/**这种静态资源也拦了,导致图片带上 token 才能看,这在医疗场景里很麻烦,记得排除。

5.5 SpringBoot 版本太高或太低:依赖冲突的降级处理

现象:mvn package报 spring-boot-maven-plugin 相关错误,或者启动时 NoSuchMethodError、javax 包找不到。原因通常是本地环境 JDK 太新,或者 IDE 自动把依赖版本解析到了最新版。源码基于 SpringBoot 2.x 时,JDK 17 直接跑会踩 module 隔离问题;SpringBoot 3.x 又把包名从 javax 整个换成了 jakarta,这段迁移不是改一两个 import 能搞定的。处理办法是让工程版本对齐源码的 pom.xml parent 声明,JDK 切到 8 或 11。如果你非要用高版本,先把工程跑通再谈升级,否则你会同时面对数据库驱动、Redis 客户端、MyBatis 插件三套兼容问题。老版本能稳定扛住 100 个并发,比改不完的高版本值钱得多。

6. 二次开发的方向:把私人医生服务做成能续费的产品

6.1 订阅到期提醒:从一张状态表到定时任务

私人医生定制服务的核心是「能让用户持续付费」。我一般会在 member_subscription 表加一个到期日,然后写一个定时任务每天扫订阅状态,提前三天给用户推送续费提醒:

@Scheduled(cron = "0 0 8 * * ?") public void remindExpiringSubscriptions() { // 查 expire_date 在 3 天内且 status=active 的订阅 // 生成待办消息并调用 APP 推送 }

cron 表达式表示每天早上 8 点执行。生产环境如果有多实例部署,同一个任务会被每个实例各跑一遍,造成重复推送,需要引入分布式锁,或者直接把任务迁到 XXL-Job 这类调度平台上管理。

6.2 在线问诊的会话:WebSocket 与 HTTP 轮询的取舍

这套源码里问诊会话的实现一般不会太复杂。并发量不大时,HTTP 轮询 3 秒一次足够用,成本最低,后端只需要一个消息列表接口。如果要做成能对外的产品,建议升级到 WebSocket,Spring 自带 WebSocketHandler,改造范围能控制在会话模块内部。无论哪种方案,消息都必须落库到 chat_message 表,未读数用消息表的聚合 count 计算,不要在业务表里单独维护一个「未读数」字段,那东西同步起来非常痛苦。

6.3 上线前验证:并发、回滚与越权三件事

这套系统在见客户之前,我至少要跑三关验证。第一关是并发,用 JMeter 模拟 100 个用户同时创建问诊订单,观察订单号是否重复、状态是否错乱;第二关是事务回滚,故意让支付回调失败,确认订单状态不会卡死在中间态;第三关是越权访问,换个用户 token 尝试拉取他人病历接口,后端必须做归属校验,否则聊天和处方数据就是裸奔。三关都过,系统才谈得上稳定交付。

我第一次跑通这套工程时,卡得最久的不是业务代码,而是第 5 章里那几种配置问题。后来养成的习惯是:拿到源码第一步,先把数据库脚本和 yml 配干净,再去看业务代码;所有中间件版本一律以源码 pom 为准,不随手升级。这套流程帮我省掉了大半的玄学问题,也希望能帮到你。

本文还有配套的精品资源,点击获取

返回列表