☰
SpringBoot+Uniapp仿京东健康在线问诊系统源码解析与实战
2026/10/1 23:46:44 网站建设 项目流程

简介:E疗通综合医疗云平台是一套面向医疗信息化开发者与毕业设计学习者的完整项目源码,借鉴京东健康与好医生系统模式,集成在线问诊、私人医生定制、电子处方审核、模拟医保购药、病患跟踪与智能用药提醒等核心业务,适合希望深入理解医疗类前后端全栈开发的中高级开发者参考。资源包共约2000个文件,以1018个Java后端源码、500个Vue页面组件、297个JavaScript脚本为主,另含XML配置、JSON数据、CSS样式及SQL建表脚本等,压缩包约79.73MB。项目采用SpringBoot 2.x、Mybatis-Plus、SpringSecurity构建后端API,前端由Uniapp与Vue2配合ElementUI实现移动端与Web后台,数据层使用MySQL 8.0与Redis,划分为admin-ui后台、app移动端、elt-server服务端三大模块,目录结构清晰,便于按模块研读与二次开发。目前已有166人学习下载,可作为医疗云平台架构设计与业务落地的实战参考。

1. 仿京东健康与好医生系统:一套能跑通在线问诊全链路的 SpringBoot + Uniapp 源码

最近在拆一套医疗云平台的源码,起因是群里有人问「在线问诊这种业务,前后端到底怎么串起来」。这套基于 SpringBoot + Uniapp 的仿京东健康与好医生系统,恰好把在线问诊、私人医生定制服务、后台管理、PC 端和手机端都塞进了一个工程里,数据库脚本也一并给了。它不是那种只放几张截图的演示壳子,而是有完整的表结构、接口分层和移动端页面。适合两类人:一类是想拿它当毕业设计或课程作业底座的学生,另一类是想快速摸清医疗类 App 业务闭环的初中级开发。我花了两天把后端跑起来、前端编译过一遍,下面把能复现的路径和踩过的坑摊开讲。

2. 环境搭建与工程结构:从 JDK 到 Uniapp 编译链路

2.1 后端技术栈与依赖版本核对

拿到源码第一步不是急着mvn spring-boot:run,而是先看pom.xml里的 SpringBoot 版本和 JDK 要求。这套工程用的是 SpringBoot 2.x 系列,配套 JDK 8 或 JDK 11 都能跑,但如果你本机默认是 JDK 17,启动时大概率会在 MyBatis 或 Lombok 上翻车。常见做法是直接用 JDK 8 建一个独立的运行环境,避免和本机其他项目打架。

数据库层用的是 MySQL 5.7 或 8.0,连接池是 Druid,ORM 是 MyBatis-Plus。这里有个细节:MyBatis-Plus 的分页插件必须手动注册PaginationInnerInterceptor,否则后台管理里的用户列表、订单列表分页会全部失效,返回全量数据。源码里如果没写这段配置,你得自己补上。

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件,不注册的话 page 参数形同虚设 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

这段配置的作用是让Page<T>对象真正参与 SQL 拼接。参数DbType.MYSQL指定了方言,如果你换成其他数据库要同步改。少了它,后台管理的分页查询会一次性把整张表拉出来,数据量一大直接拖垮接口。

2.2 数据库导入与表结构速览

源码包里一般会有一个sql目录,里面是.sql文件。导入之前先建库,字符集用utf8mb4,排序规则utf8mb4_general_ci。医疗类业务涉及患者姓名、病历描述,字符集不对会出现乱码或 emoji 存储失败。

导入命令用命令行比图形化工具稳,因为有些工具会静默跳过报错的表:

mysql -u root -p --default-character-set=utf8mb4 medical_cloud < medical_cloud.sql

执行完用SHOW TABLES;核对数量。核心表通常包括:用户表、医生表、科室表、问诊订单表、私人医生服务表、处方表、后台管理员表。其中问诊订单表和私人医生服务表是业务核心,字段里会有状态机字段,比如consult_status,取值可能是待接诊、进行中、已完成、已取消。理解这个状态流转,后面调接口才不会懵。

2.3 Uniapp 端编译与 manifest 配置

Uniapp 端要跑起来,先确认 HBuilderX 版本或 CLI 版本。如果是 CLI 工程,package.json里会有@dcloudio/uni-app相关依赖。安装依赖用npm install或yarn,然后npm run dev:h5可以先在浏览器里看效果。

manifest.json里有几个关键配置容易漏:appid如果是空的,打包成 App 时会失败;微信小程序的appid也要填对,否则npm run dev:mp-weixin编译出来的包导入微信开发者工具会报错。另外,如果后端接口地址写在config.js或.env文件里,记得把baseUrl改成你本机的 IP,不要用localhost,因为手机端调试时localhost指向的是手机自己。

// config.js 示例 const config = { // 真机调试时改成电脑局域网 IP,模拟器可以用 localhost baseUrl: 'http://192.168.1.100:8080/api', timeout: 10000 } export default config

baseUrl后面拼的/api要和后端server.servlet.context-path保持一致。如果后端没配 context-path,这里就去掉/api。超时时间设 10 秒是问诊类业务的常见值,因为图片上传和病历加载可能偏慢,设太短会频繁超时。

3. 在线问诊核心链路:从挂号到医生接诊的接口串联

3.1 问诊订单状态机与接口设计

在线问诊不是简单的「提交表单 → 返回成功」。它有一条状态链:患者选科室和医生 → 创建问诊订单(待支付)→ 支付成功(待接诊)→ 医生接诊(进行中)→ 医生结束问诊(已完成)→ 患者评价。每个状态变更都对应一个接口,而且后端要做状态校验,不能允许从「待支付」直接跳到「已完成」。

源码里通常会有ConsultOrderController,核心方法包括createOrder、payCallback、acceptOrder、finishOrder。这里最容易出问题的是支付回调:如果回调接口没有做幂等,同一笔订单可能被重复处理,导致状态错乱。常见做法是在回调里先查订单当前状态,只有「待支付」才更新为「待接诊」。

@Transactional public void handlePayCallback(String orderNo) { ConsultOrder order = consultOrderMapper.selectByOrderNo(orderNo); // 幂等校验:只有待支付状态才处理 if (order == null || !"PENDING_PAY".equals(order.getStatus())) { return; } order.setStatus("PENDING_ACCEPT"); order.setPayTime(new Date()); consultOrderMapper.updateById(order); }

@Transactional保证状态更新和后续操作在一个事务里。orderNo是外部传入的订单号,必须做非空和格式校验。如果源码里没写幂等,自己补上,否则测试时连点两次支付回调就能复现状态错乱。

3.2 医生接诊与消息推送的衔接

医生接诊后,患者端要能收到通知。这套源码里消息推送可能用的是 WebSocket 或轮询。如果是 WebSocket,后端会有WebSocketServer类,前端 Uniapp 里用uni.connectSocket连接。这里有个坑:Uniapp 在 App 端和 H5 端的 WebSocket 行为不一致,H5 端断线重连需要自己写心跳,App 端相对稳定。

接诊接口的逻辑一般是:校验医生身份 → 校验订单状态为「待接诊」→ 更新为「进行中」→ 记录接诊时间 → 推送消息给患者。如果推送失败,不应该回滚接诊状态,因为接诊本身已经成功,推送可以重试。常见做法是把推送做成异步,用@Async或消息队列解耦。

@Async public void pushAcceptMessage(Long patientId, String doctorName) { // 推送失败只记录日志,不影响主流程 try { webSocketServer.sendToUser(patientId, "医生 " + doctorName + " 已接诊"); } catch (Exception e) { log.error("接诊消息推送失败, patientId={}", patientId, e); } }

@Async需要启动类上加@EnableAsync才生效。patientId和doctorName是推送内容的关键参数。如果推送失败,日志里要能查到,方便排查是连接断了还是用户不在线。

3.3 私人医生定制服务的实现差异

私人医生定制服务和普通在线问诊的区别在于:它是周期性的、有套餐概念的。源码里会有PrivateDoctorService表,记录用户购买的套餐、服务周期、剩余次数。接口设计上,购买套餐 → 生成服务记录 → 每次问诊扣减次数 → 到期提醒。

这里的状态管理比单次问诊复杂。常见坑是并发扣减:同一个用户同时发起两次问诊,如果扣减次数没用乐观锁或数据库行锁,可能把剩余次数扣成负数。做法是在UPDATE语句里加条件remaining_count > 0,然后看影响行数。

UPDATE private_doctor_service SET remaining_count = remaining_count - 1 WHERE id = #{id} AND remaining_count > 0;

执行后判断affectedRows是否大于 0,如果是 0 说明次数已用完或并发冲突,接口返回「服务次数不足」。这个写法比先查再更新安全,避免了竞态条件。

4. 后台管理与多端适配:PC 端和手机端的接口复用策略

4.1 后台管理模块的权限控制

后台管理用的是典型的 RBAC 模型:管理员 → 角色 → 权限。源码里会有sys_admin、sys_role、sys_permission等表。登录后返回 token,后续请求在 Header 里带Authorization。这里容易忽略的是接口级别的权限校验:光有菜单权限不够,后端每个接口都要校验当前管理员是否有对应操作权限。

常见做法是用 Spring Security 或 Shiro,配合自定义注解。如果源码里只做了登录拦截没做接口权限,后台的删除、修改类接口就存在越权风险。测试时可以拿一个低权限账号去调删除接口,看是否返回 403。

@PreAuthorize("hasPermission('consult:order:delete')") @DeleteMapping("/order/{id}") public Result deleteOrder(@PathVariable Long id) { // 只有拥有删除权限的管理员才能调用 return Result.success(consultOrderService.removeById(id)); }

@PreAuthorize里的权限字符串要和权限表里的标识一致。id是路径参数,删除前建议再查一次订单状态,已完成的订单是否允许删除要看业务规则。

4.2 PC 端与手机端的接口复用与差异处理

这套系统同时有 PC 端和手机端,后端接口大部分是复用的,但返回字段可能有差异。比如问诊列表,PC 端需要分页和更多筛选条件,手机端可能只需要简化的字段。常见做法是用同一个接口,通过platform参数区分,或者用 VO 转换在不同 Controller 里返回不同结构。

Uniapp 端要注意的是条件编译。比如某些功能只在 App 端有,H5 端没有,就用#ifdef APP-PLUS包裹。如果没做条件编译,H5 端调用 App 原生 API 会直接报错。

// #ifdef APP-PLUS uni.getSystemInfo({ success: (res) => { // 只有 App 端才执行的逻辑 console.log('App 端设备信息', res.platform) } }) // #endif

#ifdef APP-PLUS和#endif必须成对出现,中间写只在 App 端编译的代码。H5 端编译时这段会被整体剔除,不会报错。

4.3 文件上传与静态资源访问

医疗类业务少不了上传病历图片、检查报告。源码里可能用的是本地存储或 MinIO。如果是本地存储,要注意上传路径和访问路径的映射。SpringBoot 默认的静态资源目录是classpath:/static/,但上传的文件如果放在项目外的目录,需要配置WebMvcConfigurer做资源映射。

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 把 /upload/** 映射到本地上传目录 registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + System.getProperty("user.dir") + "/upload/"); } }

/upload/**是访问 URL 的前缀,file:后面是实际存储路径。System.getProperty("user.dir")是项目运行目录,这样配置在不同机器上都能找到上传文件夹。如果上传后访问 404,先检查这个映射有没有配,再看文件是否真的写入了目标目录。

5. 避坑与排查:源码跑不起来时先看这几处

5.1 启动报错「Table doesn't exist」

现象:SpringBoot 启动时控制台报Table 'medical_cloud.xxx' doesn't exist。 原因:数据库脚本没导入完整,或者导入时用了错误的库。 解决:重新执行SHOW TABLES;核对表数量,确认application.yml里的数据库名和实际库名一致。如果脚本里有CREATE DATABASE语句但你没执行,手动建库再导入。

5.2 前端请求跨域被拦截

现象:Uniapp H5 端调接口返回 CORS 错误,浏览器控制台提示Access-Control-Allow-Origin。 原因:后端没配跨域,或者配了但没生效。 解决:在后端加全局跨域配置,允许前端域名。开发阶段可以用@CrossOrigin注解临时解决,但生产环境建议用 Nginx 统一处理。

@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedMethod("*"); config.addAllowedHeader("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

addAllowedOriginPattern("*")比addAllowedOrigin("*")更安全,配合setAllowCredentials(true)不会冲突。/**表示对所有路径生效。

5.3 问诊图片上传后无法预览

现象:上传成功但前端展示时图片裂开。 原因:上传路径和访问路径不一致,或者 Nginx 没配静态资源代理。 解决:先直接在浏览器访问图片 URL,看返回的是 404 还是 403。404 检查资源映射,403 检查文件权限。如果是 Nginx 代理,确认location /upload/的root或alias指向正确。

5.4 医生接诊后患者端没收到通知

现象:医生点击接诊,数据库状态已更新,但患者端没弹消息。 原因:WebSocket 连接断了没重连,或者推送时用户不在线。 解决:前端加心跳重连机制,后端推送前检查用户是否在线。如果用户不在线,把消息存到离线消息表,用户上线后拉取。

5.5 打包 App 时提示签名错误

现象:Uniapp 云打包或本地打包时提示证书或签名相关错误。 原因:manifest.json里的 App 模块配置不完整,或者用了公共测试证书。 解决:检查manifest.json的app-plus节点,确认distribute里的android和ios配置。正式打包必须用自己的证书,测试可以用 DCloud 的公共证书但有限制。

6. 二次开发切入点:把私人医生套餐做成可配置化

这套源码跑通之后,最有价值的二次开发方向是把私人医生套餐从硬编码改成可配置。原始实现里套餐类型、价格、服务次数可能是写死在代码或枚举里的,改一次要重新编译。我一般会抽一张private_doctor_package表,把套餐名称、价格、服务次数、有效期都做成字段,后台管理里加一个套餐管理页面。

CREATE TABLE private_doctor_package ( id BIGINT PRIMARY KEY AUTO_INCREMENT, package_name VARCHAR(64) NOT NULL COMMENT '套餐名称', price DECIMAL(10,2) NOT NULL COMMENT '价格', service_count INT NOT NULL COMMENT '服务次数', valid_days INT NOT NULL COMMENT '有效天数', status TINYINT DEFAULT 1 COMMENT '1上架 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

service_count和valid_days是核心参数,前者控制问诊次数,后者控制套餐有效期。status用来上下架,避免删除历史数据。建完表后,购买接口从查枚举改成查表,后台加对应的 CRUD 接口。

验证配置化是否生效,可以走一遍完整流程:后台新增套餐 → 手机端购买 → 问诊扣减次数 → 次数用完提示续费。每一步都看数据库字段变化,比只看接口返回更可靠。

从那以后我每次拿到这类医疗源码,都强制先跑一遍「购买 → 问诊 → 扣减 → 完成」的闭环,再去看后台管理和其他模块。因为主链路不通,其他功能都是空中楼阁。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询