☰
SpringBoot+Vue医院挂号预约系统:部署、排班与支付全流程实战
2026/10/14 21:36:36 网站建设 项目流程

简介:医院挂号预约系统是一套基于SpringBoot与Vue构建的现代化医疗服务平台源码包,面向医疗信息化开发学习者、高校毕设学生以及需要快速搭建预约挂号场景的Java全栈工程师。系统覆盖患者在线挂号、医生排班、科室查询、预约取消、费用支付、报告查询和消息推送等全流程业务,适合用于理解前后端分离架构、权限控制与医疗业务闭环。资源包共1076个文件,大小24.35MB,以132个Vue页面、112个Java后端源码、131个JS脚本为主,辅以SVG/PNG图标、JSON/XML配置、YML环境配置及CSS样式等,前端、后端、配置与静态资源层次清晰。压缩包内含构建与运行批处理文件,下载后可按流程完成环境安装、启动和打包,快速进入代码阅读与二次开发。当前已有49人学习下载,尤其适合用来对照完整工程梳理挂号业务、改造排班逻辑或直接作为毕业设计项目基础。

1. 医院挂号预约系统部署实战:先搞清楚这个 SpringBoot + Vue 全流程项目到底在解决什么

做医疗信息化这些年,我见过太多挂号系统的“翻车现场”——高峰期页面卡死、号源扣错、医生排班改完患者还按老时间来、支付成功后订单状态丢了。这套基于 SpringBoot 和 Vue 的医院挂号预约系统,本质上就是用一套标准化的前后端分离架构,把患者端的在线挂号、科室医生查询、报告下载,和医院端的排班管理、号源发放、预约核销串成一条闭环。它最值钱的地方不在界面好看,而在把“号源”“订单”“支付回调”“排班”这几个状态机理清了。

对谁有用?一类是做毕业设计或课设的同学,你需要一个能演示、答辩能跑通全流程的技术栈;另一类是中小型诊所、民营医院的信息化负责人,想用低成本先搭一套能用的预约入口,避免一上来就上十几万的商用 HIS。这个项目的定位恰好卡在“能跑通完整业务”和“代码量可控、二开容易”之间,适合作为业务基座二次开发。这篇笔记我会按真实落地的顺序来拆:先讲这套系统的分层设计和数据流,再带你一步步把项目跑起来,然后深入说说排班、扣号、支付回调、消息推送这几个最容易出幺蛾子的模块,最后把我在部署和联调过程中踩过的高频坑给你列全,能帮你少熬几个通宵。

2. 拆解一套前后端分离的挂号系统:从三层架构到核心数据流

2.1 为什么是 SpringBoot + Vue,而不是单体 JSP 或纯前端方案

如果你经历过 JSP 时代的老 HIS,应该能理解那种维护噩梦——前端写在后端里,改个按钮样式都要重启 Tomcat。SpringBoot + Vue 的组合在中小型医疗项目里几乎是事实标准,原因很实在:SpringBoot 负责把后端拆成独立的 RESTful 服务,解决了医生排班、号源扣减这类高并发写操作的稳定性问题;Vue 负责把患者端、医生端、管理端抽成独立页面,交互响应快,而且和移动端 H5 复用一套接口。

从数据流来看,这套系统的核心链路是“患者选科室/医生 → 查看排班 → 锁定号源 → 生成订单 → 支付 → 回调写库 → 消息通知”。任何一个环节断了,都会造成号源被占用但订单没生成、或者支付成功但排班没扣号。因此选 SpringBoot 还有一个关键理由:它的事务管理非常成熟,能在“扣号源”和“创建订单”之间保证要么都成功、要么都回滚。Vue 侧则通过路由守卫和状态管理来控制页面访问权限,比如患者端不能直接访问管理端的排班编辑接口,这是前后端分离下必须自建的一道安全边界。

2.2 看项目的目录结构:拿到压缩包后第一步是分清前后端边界

从你下载的 zip 解压后,第一件事不是急着配数据库,而是先看清楚工程边界。常见做法是包内分为backend(SpringBoot 工程)和frontend(Vue 工程)两个根目录。后端按业务模块分包,比如controller、service、mapper、entity、config;前端则按views(页面)、api(接口封装)、router(路由)、store(状态管理)划分。

hospital-register/ ├── backend/ # SpringBoot 后端工程 │ ├── src/main/java/ # Java 源码 │ ├── src/main/resources/ # 配置文件与 MyBatis XML │ └── pom.xml # Maven 依赖管理 └── frontend/ # Vue 前端工程 ├── src/views/ # 页面组件 ├── src/api/ # axios 接口封装 ├── src/router/ # 前端路由 └── package.json # npm 依赖

这里要特别提醒:新手最容易犯的错是直接双击 backend 下的某个启动类,然后抱怨“怎么跑不起来”。正确顺序是先确认 Maven 依赖能拉下来,再看 database 初始化脚本的位置——通常放在backend/src/main/resources/db/或sql/目录下,里面会有schema.sql和data.sql。这决定了你后续导入数据库时用的是哪份脚本,而不是从网上随便找一份。

2.3 捋清角色权限和核心业务对象:排班、号源、订单、支付单

这套系统的业务对象并不多,但各自的状态流转必须拎清。患者端核心对象是“预约订单”,它有待支付 → 已支付/待就诊 → 已完成 → 已取消四种主状态;医生排班对象是“排班表”,它关联了科室、医生、出诊时段和总号源数;真正控制并发的是“号源明细表”,它把每个排班时段拆成单号,每个号有独立状态(可约/锁定/已约/停用)。

权限上,管理端(管理员)负责维护科室、医生信息,创建和调整排班;医生端只查看自己的排班和就诊患者列表;患者端通过小程序或 H5 完成查询、挂号、取消等操作。这套 RBAC 设计决定了后端的接口鉴权策略:Spring Security 或拦截器按角色放行 URL,前端路由按角色渲染菜单——这就是为什么标题里“患者在线挂号、医生排班管理、科室查询”能在同一套系统里并存而不互相越权。

3. 本地快速跑通这套系统:从环境准备到前后端联调完整步骤

3.1 环境清单与版本选型:建议直接照抄这套组合

在开始之前,先把环境准备好。这套项目不同版本依赖差别很大,我建议按以下组合来装,能最大程度避免依赖冲突:

组件推荐版本说明
JDK1.8 或 11看 pom.xml 里java.version属性,大多数项目用 1.8
Maven3.6+用于后端依赖管理和打包
Node.js14.x 或 16.xVue 2 项目建议 14,Vue 3 建议 16+
MySQL5.7 或 8.0注意 utf8mb4 字符集,否则中文乱码
Redis5.x+用于验证码、Token 或热点排班缓存

建议先检查 pom.xml 里 Spring Boot 的 parent 版本——如果是 2.x,JDK8 完全够用;如果是 3.x,那必须 JDK17+。很多同学拿到的压缩包 Boot 版本是 2.3 或 2.4,却装了 JDK17,导致启动直接报错,这个坑后面避坑章还会细说。

3.2 数据库初始化:执行 SQL 脚本的正确顺序

数据库导入看起来简单,坑却不少。解压后找到 SQL 脚本,我一般是新建一个数据库名,然后用命令行导入,而不是用 Navicat 直接跑整个文件——因为脚本里可能包含存储过程、触发器或指定库名的语句,图形化工具执行容易中断。

# 进入 MySQL 命令行 mysql -u root -p # 创建数据库,注意字符集 CREATE DATABASE hospital_registration DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后用 source 导入脚本(先结构后数据) mysql -u root -p hospital_registration < schema.sql mysql -u root -p hospital_registration < data.sql

这段命令做了两件事:创建了hospital_registration数据库并指定 utf8mb4 字符集,然后按“先表结构、后模拟数据”的顺序导入。这里的data.sql是开发用的种子数据——它包含科室列表、几个医生的账号、排班记录。不要跳这一步,否则登录后看不到任何医生和排班,你会误以为项目出了问题。导入完成后,用一条 SQL 验证一下数据是否完整:SELECT COUNT(*) FROM doctor;,返回大于 0 就说明正常。

3.3 后端启动:改配置、装依赖、跑起来

数据库就绪后,进入后端工程目录,重点检查application.yml(或application.properties)。你需要修改的是数据库账号密码、Redis 地址,以及一个最容易忘的配置——server.port和context-path。很多项目的接口前缀是/api,如果前端 axios 的 baseURL 写的是http://localhost:8080/api,而后端context-path没设置,接口会全部 404。

server: port: 8080 servlet: context-path: /api # 通常项目会配这个,所有接口自动加前缀 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_registration?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 database: 0

配置改好之后,用 Maven 启动。这里我习惯在 IDEA 里导入pom.xml等待依赖解析,但如果是纯命令行环境,用下面的命令一样能跑:

cd backend mvn spring-boot:run

看到类似Started Application in 15.432 seconds的日志时,后端就起来了。不过先别急着测接口——你需要验证前端能不能访问到这个服务。用浏览器直接访问http://localhost:8080/api/captcha如果返回一张图片,说明后端和数据库已经完全打通。

3.4 前端跑通:设置代理、安装依赖、启动开发服务器

前端项目跑不起来的原因,八成出在 Node 版本和依赖安装失败上。进入frontend目录后,先看package.json里的scripts字段,确定开发服务器启动命令是npm run serve还是npm run dev。Vue CLI 创建的项目通常是前者。

启动前还要确认一件事:前端访问后端接口的方式。开发环境下,Vue 的vue.config.js配置了 devServer 代理,我们需要把/api的请求代理到后端 8080 端口,以避免跨域问题。

// vue.config.js 核心配置 module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };

这段配置的意思是:前端跑在 8081 端口,当浏览器发起以/api开头的请求时,开发服务器把它代理转发到 8080——也就是 SpringBoot 服务。这样就绕过了浏览器的同源策略限制。不要在生产环境这么干,生产环境一般用 Nginx 做反向代理,把/api直接转给后端服务。配置完成后依次执行:

npm install # 安装依赖,网络慢可以换淘宝镜像源 npm run serve # 启动开发服务器

如果npm install过程报错,通常是 node-sass 编译失败——Vue 2 项目特别常见。解决方式是卸载 node_modules 后用npm install --registry=https://registry.npmmirror.com重装,而不是去升级 Node 版本。

启动成功后,访问http://localhost:8081,应该能打开登录页。用种子数据里的测试账号登录。如果页面能正常打开、切换路由不报错、能拉到接口数据,那这套系统就已经在你本地“活”过来了。

4. 核心业务链路怎么实现:排班、挂号、支付、报告与消息推送的落地细节

4.1 医生排班管理:页面操作背后的“时段拆分 + 号源生成”逻辑

排班管理是整套系统的源头——排班出错,后面所有挂号逻辑都白搭。后台管理端的排班页面看起来是一次简单的“选择科室、医生、日期、时段、放号数”,但保存时后端做的事情远不止插入一条记录。

@Service public class ScheduleService { @Transactional public void createSchedule(ScheduleCreateDTO dto) { // 1. 校验该医生在同时间段是否已有排班,避免重复 Schedule existing = scheduleMapper.selectByDoctorAndTimeSlot( dto.getDoctorId(), dto.getDate(), dto.getTimeSlot()); if (existing != null) { throw new BusinessException("该医生在此时间段已有排班"); } // 2. 插入排班主记录 Schedule schedule = new Schedule(); schedule.setDoctorId(dto.getDoctorId()); schedule.setDate(dto.getDate()); schedule.setTimeSlot(dto.getTimeSlot()); schedule.setTotalCount(dto.getTotalCount()); schedule.setRemainCount(dto.getTotalCount()); scheduleMapper.insert(schedule); } }

这个逻辑的重点在@Transactional——排班的创建是一个整体:插入排班主记录的同时,必须初始化号源明细。有些项目没做“号源明细表”,只在排班表里放了一个remain_count字段,靠“UPDATE … SET remain_count = remain_count - 1 WHERE remain_count > 0”来扣号,这在小并发下没问题。但如果项目里已经有schedule_detail号源明细表,排班创建时还需要循环为每个时段生成可用号源记录。保存成功只是第一步,要看号源是否真正生成,去数据库里查询schedule_detail表,确认每个排班 ID 下都有对应数量的号源记录。

4.2 在线挂号与号源扣减:并发场景下“先锁号还是先下单”的选择

挂号模块是整个系统的核心难点。如果一个患者选了一个 9:00 的号,在他支付之前,另一个患者也点了这个号,系统应该怎么处理?常见做法是用户在提交订单时,后端先“锁定”号源(把号源明细状态从“可约”改为“锁定中”),然后给用户 15 分钟支付时间,超时自动释放。这里我用“乐观锁 + 状态条件更新”的方式来防止超卖:

-- 锁定号源的核心 SQL:只有当状态为 0(可约)时才更新为 2(锁定中) UPDATE schedule_detail SET status = 2, lock_time = NOW() WHERE id = #{detailId} AND status = 0

这条 SQL 的巧妙之处在于用WHERE id = ? AND status = 0做了一个原子化的条件检查——如果两个请求同时来到数据库,InnoDB 的行锁会让其中一个更新失败,表现在业务上就是“号源已被抢到”。UPDATE返回的影响行数如果是 0,说明锁号失败,直接提示用户该号源已被预约。这里要注意,在代码里必须把“锁号”和“创建订单”放在同一个本地事务中,不能让两者之间隔着耗时的网络调用。

支付环节如果是接入微信或支付宝,核心动作是处理异步回调——支付成功的通知是服务器异步推送的,不是前端跳转后立刻生效。要写的回调逻辑通常是这三步:验签(确认通知来自微信/支付宝)、查单(主动向支付平台确认订单状态)、改状态(将订单置为“已支付”并更新号源状态为“已约”)。这里最容易出错的是幂等处理——支付通知可能重复推送多次,如果回调里没做“先查订单状态,已支付则直接返回成功”,会导致号源明细被重复修改。

4.3 报告查询与消息推送:文件存储方案选型 + 通知触达的两种方式

报告查询模块,技术上要解决的核心问题是“报告文件存哪、怎么按权限取”。小型项目里,最常见做法是不把 PDF 或图片存进数据库——那样库会迅速膨胀,而是存在服务端磁盘目录,数据库里只保存文件路径。患者查询报告时,后端根据订单号和患者 ID 校验权限,然后通过接口返回文件流或临时 URL。

@GetMapping("/reports/download") public ResponseEntity<Resource> downloadReport(@RequestParam Long reportId, @RequestParam Long patientId) { // 1. 校验该报告是否属于该患者 Report report = reportMapper.selectById(reportId); if (report == null || !report.getPatientId().equals(patientId)) { throw new BusinessException("报告不存在或无权访问"); } // 2. 按存储路径读取文件并返回 Path filePath = Paths.get(report.getFilePath()); Resource resource = new FileSystemResource(filePath); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + report.getFileName()) .body(resource); }

这段代码里的重点不是“读文件返回”,而是第一步的权限校验——报告是医疗敏感数据,不能像普通文件下载一样通过 URL 直接访问,必须带着患者 ID 参数并实时验证归属关系。消息推送方面,常规落地方案是两种:一种是基于 WebSocket 做实时通知(比如轮到号了、医生停诊提醒);另一种是接入短信或微信服务号模板消息。项目里一般会抽象一个MessageService接口,短信、站内信、微信模板消息分别实现,这样患者能收到“挂号成功通知”,医生能收到“有新患者预约”的提醒。如果只是本地演示,建议先把站内信(用户消息表)跑通就足够。

5. 部署时最容易翻车的 9 个高频坑:现象、原因与解决方案

我在指导同学部署这套系统的过程中,遇到过不少让我印象深刻的“血泪经验”。这些问题非常集中,基本上来来回回就是那么几个——这里直接给你按“现象 → 原因 → 解决”写清楚,能帮你省下大量排错时间。

5.1 登录后接口报 404,页面白屏

  • 现象:前端页面能打开,但登录后拿不到用户信息,控制台里大量 404 请求。
  • 原因:后端context-path配置了/api,前端 axios 的baseURL也写了/api,代理转发时把路径拼重复了。比如真实后端接口是/api/user/info,代理转发目标却变成http://localhost:8080/api/api/user/info。
  • 解决:检查vue.config.js的代理 pathRewrite 是否把/api前缀重写掉,或者统一设置 axiosbaseURL: '/api'且 proxy target 指向http://localhost:8080不带/api。记住一个原则:代理一旦配置,前端发请求用/api/xxx,后端接收的是/api/xxx而不是把前缀叠加两次。

5.2 数据库中文乱码,患者姓名变成问号

  • 现象:页面显示“???”或乱码,数据库里的中文也存不进去。
  • 原因:MySQL 数据库和表的字符集不是 utf8mb4,或者 JDBC 连接参数里没写characterEncoding=utf8mb4。
  • 解决:建库语句必须带DEFAULT CHARACTER SET utf8mb4;如果库建好了,用ALTER TABLE … CONVERT TO CHARACTER SET utf8mb4;转换。然后在application.yml的 JDBC URL 上加上characterEncoding=utf8mb4,完成后端重启,重新导一遍数据。

5.3 Maven 依赖下载极慢或直接失败

  • 现象:IDEA 里 pom.xml 一直解析中,或者mvn spring-boot:run报依赖下载失败。
  • 原因:本地 Maven 全局配置指向了默认中央仓库,网络受限。
  • 解决:在~/.m2/settings.xml里配置阿里云镜像。<mirrorOf>配成*让它拦截所有仓库请求。改完之后删掉本地仓库里损坏的.lastUpdated后缀文件,重新mvn clean install。

5.4 排班创建成功但前端看不到号源

  • 现象:管理端创建了排班,前端患者端查询该医生时却显示“暂无号源”。
  • 原因:排班主表有数据,但号源明细表没有同步生成。有些项目创建排班和生成号源是分成两个接口调用的,前端只调用了第一个、漏掉了第二个。
  • 解决:排班创建成功后,再调用“生成号源”的接口;或者在代码里把两步合并到一个事务方法中。用前面 4.1 里的createSchedule改造,把号源初始化也放进事务里,让“保存排班 + 生成号源”不可分割。

5.5 支付成功后订单状态不变,一直“待支付”

  • 现象:患者支付跳转回来,页面显示已支付,但系统订单状态没变,号源也没锁定。
  • 原因:没配支付回调地址,或者回调请求被context-path拦截。支付平台的异步通知是通过外网地址访问你本地服务的,本地环境常用内网穿透工具解决;回调地址如果写错,通知根本到不了Controller。
  • 解决:先在代码里确认异步通知的 Controller 路径和context-path配置一致。本地联调建议在支付平台配置回调地址时,把地址拼成http://你的穿透域名/api/pay/notify,并写一个日志输出request.getParameter("result"),看通知到底有没有进来。

5.6 前端打包后访问接口地址错误

  • 现象:用npm run build打包部署到 Nginx 后,登录页面打不开,接口请求报net::ERR_CONNECTION_REFUSED。
  • 原因:开发环境的代理配置在生产包不生效。前端打包后,/api请求直接发到当前域名下,但 Nginx 没有配置对应的反向代理规则。
  • 解决:在 Nginx 配置里加location /api/ { proxy_pass http://后端服务地址; }。注意这个坑:带斜杠和不带斜杠的 proxy_pass 行为完全不同,不带斜杠会把原始 URI 拼到代理地址后面,带斜杠则丢弃前缀。按你的后端 context-path 规则来匹配。

5.7 Redis 连不上导致登录验证码无法加载

  • 现象:登录页打开正常,但验证码图片始终加载不出来或提示“服务异常”。
  • 原因:项目把验证码存储在 Redis 中,Redis 未启动或配置的密码不对。
  • 解决:本地电脑先启动 Redis 服务(Windows 请先检查进程启动方式,排查是否有端口占用和内存限制问题),然后用redis-cli ping验证能返回PONG。接着回看application.yml的 Redis 密码配置,为空就不要写密码字段,否则连接时携带空密码会认证失败。

5.8 医生端看不到排班数据,管理端却是正常的

  • 现象:管理员登录后台能看到所有排班,切到医生账号登录,排班列表是空的或只有别人不相关的排班。
  • 原因:接口没按当前登录人的 ID 过滤数据。要排查是不是前端请求时带了医生 ID,而后端 Service 层没把doctorId拼进查询条件——很有可能是硬编码了一个测试 ID,导致换账号就失效。
  • 解决:找到后端排班列表接口的入参,改成从 Token 或 Session 中获取登录医生的 ID,而不是让前端传一个人人都能改的doctorId参数。

5.9 取消预约后号源没有释放

  • 现象:患者取消预约,订单状态变为“已取消”,但相应的号源明细还是“已约”状态,排班余号没有回加。
  • 原因:取消订单的逻辑里只改了订单表,没同步修改号源状态和排班余号,两个表的数据一致性被破坏。
  • 解决:在取消预约的服务方法上加@Transactional,按顺序做三件事:更新订单状态为取消、把号源明细状态改回“可约”、把排班表的remainCount加一。这三步跟前面的支付回调一样,属于“要么全做,要么全不做”的场景,不能省事务。

6. 上线前要做的一次全面自检:验证业务闭环、排查性能隐患、做好压力测试准备

系统能在本地跑通,只是万里长征走了第一步。要在真实环境里投入使用——哪怕只是一个小范围试点——有一组验证清单必须过一遍,这直接决定项目上线后会不会成为“黑匣子”。

6.1 把全流程当剧本“演”一遍,而不是只点按钮

我建议你不要满足于“页面能跳转、数据能查询”,而是用测试账号把整个生命周期走完,像排练一样过五关。第一关:患者注册登录,修改个人资料,验证 Token 在刷新后是否失效。第二关:患者查询科室和医生,选一个排班号源,提交预约订单。第三关:在“待支付”状态下,再开一个浏览器登录另一个账号抢同一个号源,确认系统提示“号源已被锁定或约满”。第四关:支付模拟通过后,检查订单状态变为“已支付”,排班余号减少,号源明细状态变为“已约”。第五关:患者取消预约,回查号源是否释放、余号是否回加。如果这五关都顺畅走完,业务主链路的正确性就基本得到验证。

6.2 关键接口做一次简单性能基准

挂号系统的核心瓶颈在“锁号”接口和“查询排班”接口。本地可以用测试工具模拟 50 个并发用户同时提交挂号请求,观察两个核心指标:一是有没有超卖(最终号源约出数量是否会超过排班总数),二是接口平均响应时间是否在可接受范围内。如果你的项目用了“先锁后单”模式,50 并发下一般不会有超卖,但响应时间会明显上升。记得留意数据库连接池配置maximum-pool-size是否太小——默认 10 的话,并发一高就会出现等待获取连接的报错。

6.3 上线前必须改掉的两个“测试期坏习惯”

最后提醒两个非常现实的问题。一个是关闭 MyBatis SQL 日志:开发时打印 SQL 很爽,但生产环境每个请求都打印 SQL 会拖垮磁盘 IO,也容易泄露库表结构;线上建议把mybatis.configuration.log-impl改为Slf4jImpl并调低日志级别。另一个是管理端口令必须改:项目自带的默认管理员密码千万不能留着,这是很多系统被“脱库”的直接原因。密码至少 12 位,且要开启管理端的登录验证码——别嫌麻烦,这是成本最低的安全防护。

做个真实的小结:这套 SpringBoot + Vue 的挂号系统,覆盖了从患者端到管理端的完整链路,代码层面的“围墙”已经给你砌好了,但业务参数和部署环境这层“门窗”需要你自己调。把它跑通只需要一个下午,但真正能用起来、扛得住真实流量、不丢单不乱扣号,靠的是你对事务边界和状态流转的理解。希望这篇走心笔记帮到你——后续二开过程中,遇到具体的报错或者拿不准的设计取舍,欢迎回来交流,我们下次接着聊。

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

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

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

立即咨询