ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue前后端分离医院挂号就诊系统设计与部署

SpringBoot+Vue前后端分离医院挂号就诊系统设计与部署 前后端分离spring boot医院挂号就诊系统系统SpringBootVueMyBatisMySQL完整源码部署教程这几年医疗信息化项目的需求一直很旺盛医院挂号就诊系统算是其中比较典型的一类。我接触过不少类似的业务系统说实话很多小诊所、社区卫生服务中心甚至部分二甲医院的线上挂号需求技术复杂度并不高核心难点往往在业务模型的梳理和前后端分离架构下的工程落地。这次要拆解的项目是一个基于 SpringBoot Vue MyBatis MySQL 的前后端分离医院挂号就诊系统包含了患者端的基本挂号流程、医生端接诊、后台管理等多个模块。适合正在学习 SpringBoot 全家桶的开发者、准备做毕业设计的计算机专业学生以及想快速搭建一套医疗业务原型的中小型团队参考。这类系统最能锻炼人的地方在于它不是简单的 CRUD 堆砌而是有真实的业务状态流转、角色权限区分、以及多表关联查询做完之后对整体架构的把控能力会有明显提升。我先把这套系统的整体设计思路和落地细节完整拆一遍。文章里会用大量实际经验讲清楚为什么这样做、哪些地方容易踩坑、以及如何把它真正跑起来。1. 项目到底在做什么从需求出发理解系统边界1.1 拆解标题中的核心关键词拿到标题先别急着写代码第一步是搞清楚这套系统解决什么问题。医院挂号就诊系统的核心用户有三类患者、医生、管理员。围绕这三类角色业务闭环大致是患者注册登录 → 查看科室和医生排班 → 选择号源完成预约挂号 → 到院后医生接诊 → 医生填写诊断和处方 → 患者查看就诊记录。这个流程听起来简单但落到系统设计上就引出了一连串问题号源怎么管理每个医生每天放多少个号上午下午各多少挂号费的支付状态怎么处理是线上支付还是到院支付医生排班如何与挂号解耦排班变动后已预约的号怎么处理就诊状态什么时候从待就诊变成已完成不同的角色登录后看到的内容怎么隔离这些问题的答案决定了数据库表怎么设计、接口怎么划分、前端路由怎么控制。我见过很多半路夭折的类似项目问题大多不是出在技术上而是业务逻辑没梳理清楚就开始写代码结果越写越乱。1.2 为什么选择前后端分离架构这个项目选择了前后端分离而不是传统的服务端渲染原因很实际。第一医院类系统的管理后台交互逻辑复杂Vue 这类前端框架在组件化复用、状态管理方面比 JSP/Thymeleaf 模板高效得多尤其在排班日历、挂号表单这类交互密集的页面上。第二前后端分离后后端只需提供标准 RESTful API将来如果需要对接微信公众号、支付宝小程序前端可以完全独立开发后端改动很小。第三对于学习目的前后端分离是当前企业开发的标配模式完整走一遍可以提前适应团队协作的工程规范。技术选型上SpringBoot 负责提供接口层和业务层MyBatis 负责数据持久化MySQL 存储结构化数据前端使用 Vue Element UI或类似组件库构建管理界面和用户界面。这套组合在中小型项目中非常成熟社区资料丰富遇到问题能快速找到解决方案。1.3 系统整体功能模块划分根据业务闭环我把系统拆成四个功能域患者端登录注册、科室浏览、医生排班查询、在线挂号、挂号记录查看。医生端查看当日接诊列表、开始接诊、填写诊断建议、填写处方、结束接诊。管理后台用户管理、科室管理、医生管理、排班管理、号源统计、挂号记录查询。系统支撑角色权限控制、统一异常处理、统一返回结果封装、日志记录。这四个功能域对应的后端接口数量大约在三十到四十个之间前端页面在十个左右。对一个学习项目来说体量适中不会因为功能太多而失去焦点又能覆盖一个完整业务系统的大部分技术难点。2. 核心架构与数据库设计把地基打牢2.1 后端分层设计与模块划分项目的后端包结构我建议按业务垂直划分而不是按技术横向划分。很多人习惯建 controller/service/mapper 三个包把所有类堆进去项目小的时候还行一但功能多起来查找和定位问题就变得很痛苦。合理的方式是类似com.hospital ├── common // 通用工具类、统一返回结果、异常处理 ├── system // 系统管理模块用户、角色、权限 ├── registration // 挂号模块科室、排班、号源、预约 ├── diagnosis // 就诊模块接诊、诊断、病历每个模块内部再拆 controller、service、mapper 子包。这样做的好处是模块与模块之间天然形成代码边界比如挂号模块需要调用系统模块的用户服务时只能通过 service 接口互相调用依赖关系清晰可控。分层方面Controller 负责参数校验和请求响应不写任何业务逻辑Service 层承担事务管理和业务规则校验Mapper 层只负责 SQL 操作。这里有个新手容易犯的毛病事务控制不到位。比如创建挂号记录时需要同时扣减号源数量这两步必须放在同一个事务里如果直接在 Controller 里依次调用两个 service 方法前者成功后者失败时会造成数据不一致。正确做法是在 Service 层方法上加Transactional注解保证原子性。2.2 MySQL 数据库表设计与关联关系数据库设计是这套系统的重头戏核心表我总结了八张左右它们之间的关联关系直接支撑着整个业务闭环。表名用途关键字段sys_user用户表三类角色通用user_id, username, password, role_type, real_name, phonesys_role角色表role_id, role_name, role_keydept科室表dept_id, dept_name, descriptiondoctor_info医生信息扩展表doctor_id, user_id, dept_id, title, introductionschedule排班表schedule_id, doctor_id, dept_id, schedule_date, period(上午/下午), total_number, remain_number, statusregistration挂号记录表reg_id, patient_id, doctor_id, schedule_id, reg_date, time_slot, status, feediagnosis就诊记录表diag_id, reg_id, patient_id, doctor_id, symptom_desc, diagnosis_result, prescriptionpayment支付记录表可选pay_id, reg_id, pay_status, pay_time, pay_amount这里需要重点解释的是排班表的total_number和remain_number字段。它们不是冗余设计而是解决并发挂同一号源的关键。每次成功创建一条挂号记录就同时执行UPDATE schedule SET remain_number remain_number - 1 WHERE schedule_id ? AND remain_number 0通过数据库的行级锁保证同一时刻不会把最后的号源卖给两个人。这个问题是隐藏的高频考点面试时经常被追问实际开发中也确实容易踩坑。用户表通过 role_type 字段区分患者、医生、管理员三种角色而不是为每种角色建独立表这是为了保持账号体系统一。医生信息单独拆到 doctor_info 表因为医生除了基础账号还需要职称、擅长领域、简介等个性化字段挂靠在用户表上会造成过度耦合。2.3 MyBatis 多表关联查询的建模技巧MyBatis 在这类系统中最常用的能力有两块动态 SQL 和自定义结果映射。先说动态 SQL比如挂号记录列表查询前端可能按状态筛选、按日期筛选、按患者姓名模糊搜索这些条件的组合靠拼接字符串显然不行。MyBatis 的whereif标签可以优雅解决select idselectRegistrationList resultTypecom.hospital.registration.entity.RegistrationVO SELECT r.*, u.real_name AS patient_name, d.dept_name FROM registration r LEFT JOIN sys_user u ON r.patient_id u.user_id LEFT JOIN schedule s ON r.schedule_id s.schedule_id LEFT JOIN dept d ON s.dept_id d.dept_id where if testpatientName ! null and patientName ! AND u.real_name LIKE CONCAT(%, #{patientName}, %) /if if testregStatus ! null AND r.status #{regStatus} /if if testregDate ! null AND DATE(r.reg_date) #{regDate} /if /where ORDER BY r.create_time DESC /select再说自定义结果映射。当查询结果包含关联对象的嵌套属性时比如查看某个医生的排班记录需要同时带上科室名称和医生姓名可以使用resultMap的 association 或 collection 标签。但我的建议是这种查询场景直接用 VO视图对象接收不要在实体上做复杂嵌套映射。VO 是专门给前端展示用的扁平结构避免把实体类变成大杂烩也减少了 MyBatis 的反射开销。3. 核心业务逻辑实现挂号与就诊的完整闭环3.1 排班管理模块的设计思路排班是挂号的前置条件它的核心是生成未来一周的排班计划。管理端选择医生、日期、上午/下午时段填写放号数量系统生成排班记录。这里有一个细节值得注意如果一个医生在同一天既排上午又排下午这算两条排班记录而不是一条记录里设置两个时段。这样设计的好处是号源维度的统计更加清晰后续挂号接口只需要通过 schedule_id 就能定位是哪一天、哪个时段、哪个医生。排班状态字段我建议设置三种0-未开始还没有放号可以修改数量、1-预约中已经开始被挂号不能再修改号源数量、2-已结束过期或已挂满。为什么排班一旦有挂号记录就不能再修改号源数量因为改大容易造成超卖改小会与已售出的号冲突最简单粗暴的处理方案就是锁定排班的号源总量。如果确实需要调整只能作废当前排班重新创建。3.2 在线挂号接口的完整实现挂号接口是系统中事务性最强的核心接口它的逻辑流程可以分解为以下步骤校验患者是否已登录。根据 schedule_id 查询排班记录校验排班是否存在、状态是否允许挂号、日期是否已过期。查询数据库确认该患者在同一个时间段没有重复挂号同一患者同一天只能挂同一个医生的一个号防止黄牛占号。扣减排班的 remain_number使用条件更新确保不超卖。创建挂号记录状态置为 1-待就诊。记录支付信息如果需要线上支付则走支付回调接口。第 4 步是关键中的关键必须使用带条件的 UPDATE 语句。伪代码如下Transactional(rollbackFor Exception.class) public Result createRegistration(RegistrationDTO dto) { // ...校验代码省略... int affected scheduleMapper.deductRemainNumber(dto.getScheduleId()); // UPDATE schedule SET remain_number remain_number - 1 // WHERE schedule_id #{scheduleId} AND remain_number 0 if (affected 0) { throw new BusinessException(号源已满挂号失败); } Registration reg new Registration(); // ...填充挂号记录属性... registrationMapper.insert(reg); return Result.success(reg); }这里的事务是 Spring 声明式事务默认传播行为 REQUIRED扣减号源和插入记录在同一事务中任何一个失败都会回滚不会出现号源少了但挂号记录缺失的情况。3.3 医生接诊与就诊状态流转患者到医院后向分诊台报到医生端就能看到自己的待接诊列表。接诊流程从点击开始接诊开始到填写完诊断信息点击结束接诊结束。整个过程涉及的状态变更如下状态值状态含义触发条件1待就诊患者成功挂号后自动生成2就诊中医生点击开始接诊3已完成医生填写诊断结果并提交4已取消患者在就诊前取消挂号5爽约排班日期过后仍处于待就诊状态由定时任务批量更新就医记录可以和挂号记录分离成不同表因为一次就诊会产生不止一条诊断信息比如某些患者可能需要医生多次复诊。诊断表的主键与挂号记录保持一对一或一对多的关系在设计时加上 reg_id 作为外键方便根据挂号记录反查完整就诊历史。3.4 前端 Vue 与后端的交互协议设计前后端分离项目最怕接口约定不清。我的建议是前后端统一使用一个返回结果封装类比如常见的 Result 类包含 code、message、data 三个字段。所有成功请求 code 为 200业务异常 code 为 500 或自定义编号前端 axios 拦截器统一判断 code 并弹出 message。这样一来前端就不用关心 HTTP 状态码和后端异常堆栈之间的映射关系只需要管好业务层面的状态码。前端路由的权限控制也要配合后端接口权限一起做。简单项目可以不引入复杂权限框架只在后端的 Controller 层通过角色拦截器拦截非法访问。比如医生端的/api/doctor/**接口只有 role_type 为医生的用户才能访问。前端主要是做路由守卫让未登录用户无法进入主页让医生用户无法访问管理后台本质上是提升用户体验真正的安全边界始终应该放在后端。4. 从零到一部署运行完整源码的本地跑通指南4.1 环境准备与版本选择这个项目的本地部署前提是装好 JDK 8、Maven 3.6、MySQL 5.7/8.0、Node.js 14 和 npm。版本选择上要注意一个坑SpringBoot 2.x 搭配 JDK 8 最稳妥如果用到 SpringBoot 3.xJDK 要求至少 17而且 javax 包名全部换成了 jakarta很多老教程会失效项目配置和依赖都要相应调整。我日常推荐学习项目用 SpringBoot 2.7.x JDK 8 的组合兼容性最好遇到问题能搜到的解决方案也最多。MySQL 安装完成后建议统一字符集为 utf8mb4因为患者姓名、医生诊断信息可能包含特殊字符或表情符号utf8 无法完整覆盖。同时设置好时区SET GLOBAL time_zone 8:00;如果不做这一步Java 的 JDBC 连接池在初始化时会报The server time zone value ... is unrecognized之类的错误很影响后续调试。数据库建好后导入项目自带的 SQL 脚本文件通常包含建库、建表、初始化管理员账号三个部分。4.2 后端启动的完整步骤后端项目是标准的 Maven 工程启动流程分三步修改application.yml中的数据库连接信息server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.hospital.entity在项目根目录执行mvn clean package -DskipTests进行打包或者直接在 IDEA 中运行启动类。如果 Maven 依赖下载缓慢建议配置阿里云镜像仓库。很多新手在这步卡很长时间其实是因为默认中央仓库连接不稳定。注意如果控制台报错java.lang.IllegalArgumentException: malformed input八成是字符集问题检查 MySQL 连接参数和数据库表字符集是否都使用了 utf8mb4。4.3 前端环境搭建与启动前端使用 Vue CLI 或 Vite 创建的项目目录结构已经搭好。启动步骤npm install npm run dev如果npm install过程缓慢可以提前切换为淘宝镜像源npm config set registry https://registry.npmmirror.comVue CLI 启动后默认跑在 8080 端口而后端也是 8080 端口直接访问会冲突。处理方案有两种第一种改后端的 server.port 为 8081前端通过 vue.config.js 配置 devServer.proxy 把/api路径代理到http://localhost:8081第二种把前端端口改成 3000。我推荐第一种因为前端访问后端接口时用相对路径/api代码里不写死任何 IP以后部署到生产环境只需要调整 nginx 的代理配置即可。注意跨域问题前后端分离模式下开发必然面临 CORS。通过配置 proxy 就不需要后端开启跨域支持了但如果你在本地直接用两个端口联调就别忘了后端加一个 CorsFilter 或者注解CrossOrigin否则浏览器会拦截所有请求。4.4 生产环境的 Nginx 部署建议如果是毕业设计演示本地联调就足够了。但如果是小型团队要真正上线我建议用 Nginx 同时托管前端静态文件和反向代理后端接口。Nginx 配置的核心片段server { listen 80; server_name your-domain.com; # 前端 Vue 打包后的静态文件目录 root /usr/share/nginx/html; index index.html; # 解决 Vue History 路由刷新 404 的问题 location / { try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }前端打包前先执行npm run build生成 dist 目录把它上传到服务器上再由 Nginx 指向。后端打成 jar 包后使用nohup java -jar hospital-0.0.1-SNAPSHOT.jar 方式启动注意服务器防火墙要放行对应端口。5. 常见问题排查与技术难点实录5.1 数据库时间与排班日期不一致这类系统的高频问题之一是数据库存储的日期时间与本地实际时间相差 8 小时。原因一般有两个JDBC 连接 URL 没有指定 serverTimezone或者 MySQL 服务端时区设置错误。排查方法很直接先看 Java 侧输出时间是否正常再看 MySQL 执行SELECT NOW()的结果是否正常逐步确定问题出在哪个环节。5.2 前后端联调时的接口 404 或 500接口 404 常见原因有后端启动失败、接口路径写错、前端代理配置没生效。接口 500 常见原因有SQL 语句报错、参数类型不匹配、空指针异常。调试时建议在项目里全局配置一个 Logback 输出 SQL 日志logging: level: com.hospital.mapper: debug一旦开启 MyBatis SQL 日志控制台会直接打印出每一条实际执行的 SQL 语句和参数定位问题效率可以提升一个量级。之前有个学员遇到挂号接口一直返回 500开启了 SQL 日志后一眼看到插入语句里把时间字段传成了字符串只用了两分钟就解决了。5.3 Vue 端打包后白屏问题打包后白屏基本可以定位为静态资源路径问题。Vue CLI 项目打包后的资源引用默认是绝对路径/js/...如果部署在服务器的子目录而不是域名根路径就会找不到文件。修改 vue.config.js 中的publicPath为./可以解决相对路径问题这也是我部署每个前端项目时首先调整的配置之一。5.4 号源并发扣减的实战验证这个项目最值得动手测试的环节是并发挂号。先用 Jmeter 或 Postman 设置 100 个并发请求挂同一个医生的号源最后观察两件事remain_number 是否为 0挂号记录是否只有放号数量对应的条数。如果这两个指标对上了说明事务和条件更新是正确生效的如果出现了负数或超额记录说明事务边界可能被破坏了。这个实验做完对数据库并发控制的理解会有质的飞跃面试时也完全拿得出手。5.5 常见问题速查表现象可能原因快速解决启动报端口被占用8080 被其他进程占用使用netstat -ano查 PID 后杀掉前端 npm install 报错依赖缓存损坏或 Node 版本不匹配删除 node_modules 和 package-lock.json 重新 npm install登录后接口 401认证 token 失效或未传 token检查前端的 axios 拦截器是否正确附带 token挂号时报号源已满但数据库还有余号条件更新 SQL 拼接错误打开 SQL 日志检查 remain_number 判断条件科室列表能显示但医生列表空白医生表与用户表外键关联错误检查 doctor_info 表中的 user_id 是否与 sys_user 的 user_id 对应Vue 页面刷新 404路由使用了 history 模式配置 Nginx try_files 或改用 hash 模式6. 复盘个人实战心得与后续功能拓展建议前后端分离的医院挂号就诊系统做完一遍最大的收获不是学会了某个框架的某个注解而是理解了业务流程到系统设计的映射过程。挂号系统的核心是号源状态与订单状态的一致性这类问题在日常开发中很常见比如电商系统的库存扣减、活动系统的奖品发放本质逻辑完全一致。能把挂号系统的事务和并发控制想明白以后做其他业务系统也会顺手很多。我个人在做这类项目时积累了几条实际经验业务实体对象的字段命名和数据库字段命名尽量保持一致的驼峰和下滑线映射关系MyBatis 配置mapUnderscoreToCamelCasetrue以后代码看起来会非常干净。Service 层一定要做参数校验不要依赖前端校验。患者传一个 non-existent 的 schedule_id 进来后端至少要有幂等保障不然恶意刷接口会让数据库产生大量脏数据。日志别嫌多关键节点务必打日志。挂号成功、支付回调、状态流转这种环节打一条结构化日志出了线上问题排查的时间会从一两个小时缩短到十分钟以内。数据库索引不能漏。registration 表的 schedule_id、patient_id、statusschedule 表的 doctor_id 和 schedule_date都是高频查询字段没有索引的话数据量一上来很快就慢到无法接受。这个项目后续的扩展空间也很大。比较实用的方向有三个引入 Redis 做验证码缓存和热点号源计数提升系统的抗并发能力接入实际的支付服务或用模拟支付组件代替当前的手动状态变更让流程更完整增加消息通知模块挂号成功或医生停诊时通过短信或站内信通知患者。如果把这几块加上这个系统从功能完整性和技术深度上都可以对标中型商业项目了写在简历上是非常扎实的项目经历。最后分享一个实战小技巧联调阶段最好不要同时开发前端和后端而是先把后端所有接口写好用 Swagger 或 Postman 文档输出给前端同学或队友然后再集中精力写 Vue 页面。这样接口契约先行前后端各干各的效率比一边写一边改高太多。这个习惯我保持了很多年所有协作项目都因此少踩了很多坑。
返回列表