资讯详情

从零跑通医院挂号系统:SpringBoot+Vue全栈部署与开发实战

📅 2026/10/21 21:13:56 | 华诺云谱 👁 阅读
从零跑通医院挂号系统:SpringBoot+Vue全栈部署与开发实战
线上医院挂号系统信息管理系统这几个词拆开看一半是业务挂号一半是技术信息管理系统。落到技术圈里它通常就是一套 SpringBoot后端 Vue前端 MySQL 的标准全栈项目标题那四个字“可直接运行”也确实不是随便写写——这类源码本身就是一个完整可交付的产物只是很多人拿到的第一反应是“双击就能跑”结果卡在环境上。这篇文章我就从实际部署和调试的角度把整套系统从业务流程、数据库设计、后端接口、前端页面到本地跑通全流程拆一遍给准备用它做课程设计、毕设、培训练手或者入职前熟悉全栈开发的读者一个可以直接照着走的参考。1. 先弄明白这套系统在管什么三类角色与一条主流程1.1 三类角色和对应的功能边界挂号系统表面上是“患者点两下、挂个号”但作为信息管理系统它背后有清晰的权限边界。整套系统里通常存在三类角色分别对应不同的操作面患者端前台用户注册、登录、浏览医院科室、筛选医生、查看排班、提交预约、取消挂号、查看自己的挂号记录和处理状态。医生端查看在自己名下的某一天或某一时段的患者列表对患者进行“确认就诊”或“标记爽约/未到”操作。管理端系统管理员维护科室信息、维护医生资料和所属科室、为医生生成排班、设置每个排班的号源总数、查询全量挂号记录、管理用户账号。这三类角色在同一套系统里前端通过路由和菜单区分入口后端通过登录角色标识控制接口权限。常见设计是患者走前台页面医生和管理员走后台页面共用同一套登录接口登录后根据角色跳转到不同首页。1.2 从“想挂号”到“看完病”的主流程核心主流程不复杂但每一环都有对应的数据支撑患者登录进入医院首页按照科室分类找到对应科室。科室页面展示该科室下的医生列表包含职称、简介、挂号费。点击某个医生进入医生详情或排班页这里能看到未来若干天的排班情况某天上午/下午是否有号、剩余号源数。选择一个还有余号的时段提交预约系统生成一条挂号记录状态为“待就诊”。就诊日到来患者在线下或按医院流程完成实际就诊医生在医生端标记“已完成”如果患者未到医生可以标记“爽约”或由系统在时间过期后自动置为“爽约”。患者随时可以在“我的挂号”里查看当前状态也能在“待就诊”状态下取消挂号。整个流程里最容易被忽略但也是最核心的业务保障是第一步到第四步之间的“号源防超挂”。排班表中的剩余号数必须被严格扣减不能用“先查询后更新”的普通逻辑否则并发场景下一定会出现超挂。后文我会单独用一个章节讲清楚。2. 跑起来之前先过一遍版本组合、目录结构与配置文件2.1 这套源码大概率会用什么版本组合市面上这类项目源码数量很大技术版本也略有差异但大部分遵循一套约定俗成的组合软件常见版本说明JDK1.8SpringBoot 2.x 的最稳定搭档很多老项目升到高版本 JDK 后反而会出现依赖兼容问题SpringBoot2.3.x ~ 2.7.x2.x 生态最成熟网上资料最多遇到问题好查MyBatis / MyBatis-Plus3.x带 Plus 的版本更适合课程设计和快速开发自带分页插件MySQL5.7 或 8.05.7 兼容性最好8.0 要注意驱动版本和时区配置Node.js14.x ~ 16.x对应 Vue2 Element UI 的依赖安装太新的 Node 版本容易在 npm install 阶段报 node-sass 兼容错误Vue2.x Element UI老项目最常见少数用 Vue3 Element Plus原理相通拿到源码后第一步别急着启动先打开根目录下的pom.xml和package.json确认 SpringBoot 版本和 Vue 版本再对照本机环境。很多人一上来直接npm install发现终端一片报错绝大多数是 Node 版本和 node-sass 或 node-gyp 编译版本不匹配后面我会单独讲处理办法。2.2 目录结构先找到这几样东西一份标准的 SpringBoot Vue 前后端分离项目通常会分两个大目录backend或server和frontend或web。后端典型的包结构是controller / service / mapper / entity / config / common前端顶层目录是src/views、src/components、src/router、src/api。运行前建议先完成三件事找到数据库初始化脚本。通常是根目录下的db.sql、init.sql或sql/hospital.sql里面是建库、建表和初始数据语句。找到后端配置文件。src/main/resources/application.yml或.properties里面管理着数据库连接、端口、JWT 密钥等关键信息。找到前端代理配置。对开发环境来说vue.config.js里的devServer.proxy决定了前端请求如何转发到后端。2.3 配置文件里的“隐藏坑位”后端application.yml最需要改的是数据源部分server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver这里有三个高频问题密码不是数据库真实密码导致登录失败MySQL 是 5.7 但驱动配置写成了com.mysql.cj.jdbc.Driver8.0 驱动导致连接报错serverTimezone没有设置导致时间类数据偏差八小时。前端代理配置大致是这样module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }前端的请求地址都以/api开头开发服务器收到后转发到后端的 8080 端口。这里要注意前后端端口不能一样否则本地开发服务器和后端会有一个起不来。3. 数据库设计是挂号系统的命门核心表结构与号源扣减3.1 六张核心表能覆盖绝大多数需求这套系统的表设计思路非常集中核心就是“人—科室—医生—排班—挂号”。下面列出最常用到的六张表及其关键字段也是初始化脚本里最需要留意的部分数据表关键字段作用说明sys_userid、username、password、real_name、phone、role统一用户表role 用来区分 patient / doctor / admindepartmentid、dep_name、dep_desc、sort科室表按医院实际科室维护doctorid、user_id、dep_id、title、intro、reg_fee医生信息表关联用户表账号和科室scheduleid、doctor_id、work_date、period、total、remain排班表period 表示上午/下午remain 是当前剩余号源registrationid、order_no、patient_id、doctor_id、schedule_id、visit_date、period、status挂号记录表状态为待就诊/已完成/已取消/爽约operate_logid、user_id、action、create_time可选记录关键操作便于排查外键关系也很直观doctor.user_id - sys_user.iddoctor.dep_id - department.idschedule.doctor_id - doctor.idregistration.patient_id - sys_user.idregistration.schedule_id也可以冗余关联排班。需要注意一个实践细节registration表里最好冗余存一份doctor_name、dep_name和reg_fee这样患者在“我的挂号”列表查询时不需要反复 JOIN 三张表对只做课程设计或小规模部署来说性能更友好代码也更简单。3.2 号源扣减为什么必须用“原子更新”排班表里的remain是整套系统并发正确性的核心。最典型的反面写法是// 错误示范先查剩余号再判断最后更新 Schedule schedule scheduleMapper.selectById(scheduleId); if (schedule.getRemain() 0) { schedule.setRemain(schedule.getRemain() - 1); scheduleMapper.updateById(schedule); }这段代码在只有一个用户访问时没有任何问题但只要有两个以上患者同时点提交挂号A 和 B 都查到remain 5各自都执行了减一最终数据库里只减了 1实际却可能发出两张号这就是超挂。正确做法是使用原子的条件更新UPDATE schedule SET remain remain - 1 WHERE id #{scheduleId} AND remain 0;通过 MyBatis 的 Mapper 方法执行这条语句返回影响行数。如果影响行数为 1说明扣减成功继续向registration表插入挂号记录如果影响行数为 0说明remain已经是 0 或者排班不存在直接返回“号源不足”。扣减这一步即使不依赖数据库行锁和事务也能靠 SQL 本身的原子性挡住绝大多数并发这也是线上系统最常用的兜底方案。3.3 排班生成与退号恢复排班由管理员在后台生成通常一个医生一天分上午、下午两个时段每个时段对应一条schedule记录。管理员设置total总号数时remain初始等于total。前端在医生排班页展示的就是按work_date period查询出来的记录集合。取消挂号时逻辑上与挂号是镜像关系先把挂号记录状态改成“已取消”再把对应schedule.remain 1。这里同样需要放在一个事务里否则可能出现“号退了但号源没恢复”或相反的情况。比较容易被忽略的是“过期未就诊”的处理。项目如果做了定时任务通常会每分钟或每小时扫描一次registration把那些visit_date period时间已经过去、且状态仍然为“待就诊”的记录置为“爽约”同时不再恢复号源。如果没有定时任务也可以在医生端手动标记。绝大多数源码会选择简单的后者因为课程设计和练手项目不需要引入 XXL-Job 这类重型调度框架一个Scheduled固定延迟扫描就够用了。4. SpringBoot后端的关键实现接口规范、鉴权与事务处理4.1 统一返回体与全局异常处理前后端分离项目里后端返回给前端的数据格式必须统一否则前端 Axios 拦截器没法做通用处理。常见设计是统一返回体Resultpublic class ResultT { private Integer code; private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.code 200; r.msg 操作成功; r.data data; return r; } public static T ResultT error(Integer code, String msg) { ResultT r new Result(); r.code code; r.msg msg; return r; } }配合一个全局异常处理器用RestControllerAdvice捕获业务异常和系统异常统一转换成上面的格式返回。这样做的好处有两个前端只需要在响应拦截器里判断一次code就能做全局错误提示后端业务代码里可以放心抛出业务异常不需要到处写 try-catch。4.2 JWT登录鉴权与角色控制登录逻辑不复杂但身份认证几乎是这类系统最重要的安全基础。基本流程是患者或管理员提交用户名密码。后端校验用户是否存在、密码是否匹配密码通常用 BCrypt 加密不能明文存库。校验通过后生成一个 JWT把用户 id、用户名、角色放到 token 的 payload 里设置过期时间返回给前端。前端把 token 存到localStorage每次请求在请求头带上Authorization: Bearer token。后端用拦截器HandlerInterceptor校验 token解析出用户信息放行或返回 401。角色控制常见做法是自定义一个RequireRole注解在 Controller 方法上标注允许访问的角色配合拦截器在解析完 token 后做比对。比如“医生确认就诊”的接口只允许role doctor调用患者角色调用就直接拒绝。这样比单纯前端隐藏菜单要安全得多因为接口地址始终暴露在外面任何人可以绕过页面直接调。4.3 挂号接口事务边界比你想的更宽挂号接口是整个后端最核心的业务方法代码结构大致如下Transactional(rollbackFor Exception.class) public ResultString register(RegisterDTO dto) { // 1. 校验排班是否存在、是否还有号 int rows scheduleMapper.decreaseRemain(dto.getScheduleId()); if (rows 0) { return Result.error(500, 当前号源已挂完请选择其他时段); } // 2. 生成挂号记录 Registration reg new Registration(); reg.setOrderNo(generateOrderNo()); reg.setPatientId(dto.getPatientId()); reg.setDoctorId(dto.getDoctorId()); reg.setScheduleId(dto.getScheduleId()); reg.setVisitDate(dto.getVisitDate()); reg.setPeriod(dto.getPeriod()); reg.setStatus(待就诊); registrationMapper.insert(reg); // 3. 返回成功 return Result.success(reg.getOrderNo()); }这个方法必须加Transactional并且要注意两点。第一decreaseRemain用的 SQL 前面已经说过是UPDATE ... SET remain remain - 1 WHERE id? AND remain0这个 update 操作不能放在插入记录之后顺序必须是“先扣号源再插挂号记录”否则可能出现记录插成功了但号没扣掉的情况。第二rollbackFor要设置为Exception.class否则默认只回滚 RuntimeException某些自定义异常可能导致事务没生效。挂号单号生成也有讲究直接用UUID.randomUUID()会得到一个 36 位字符串展示给用户不友好。多数项目用yyyyMMddHHmmss 4位随机数生成 18 位左右的单号再在表里加唯一索引兜底重复概率极低。4.4 医生端与患者端的状态流转接口医生端的核心接口是“查询某日患者列表”和“更新就诊状态”。查询时注意按visit_date period过滤并按挂号时间排序更新状态时通常从“待就诊”改成“已完成”或“爽约”。这里要做一个状态校验如果挂号记录已经取消不能再被改成已完成否则会出现逻辑矛盾。患者端大多是查询类接口比如“我的挂号列表”这类接口建议用 MyBatis-Plus 分页插件返回IPage结构前端配合el-pagination展示避免一次查全表把毫无意义的性能压力给到数据库。排序按create_time DESC让最近的挂号和状态变更排在最前面。5. Vue前端与后端对接路由权限、请求封装与挂号页面的实现思路5.1 路由设计与动态菜单前端最常见的入口结构是/login、/首页或患者端、/admin管理端、/doctor医生端。用 Vue Router 的beforeEach做全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path /login) { next(); return; } if (!token) { next(/login); return; } // 根据角色判断是否进入对应模块 const role localStorage.getItem(role); if (to.path.startsWith(/admin) role ! admin) { next(/); return; } next(); });这样的写法比在菜单里硬编码更安全登录后菜单根据role动态渲染。很多源码还会做一个补充登录接口返回用户信息时前端把username、role、realName一并存进localStorage页面里直接显示真实姓名避免每次刷新都要重新调“获取用户信息”接口。5.2 Axios 封装与请求拦截所有请求建议统一走一个request.js封装核心思路是拦截器axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }); axios.interceptors.response.use( response { const res response.data; if (res.code 200) { return res.data; } else if (res.code 401) { localStorage.clear(); router.push(/login); return Promise.reject(new Error(res.msg)); } else { Message.error(res.msg); return Promise.reject(new Error(res.msg)); } }, error { Message.error(网络请求异常); return Promise.reject(error); } );封装完成后的业务调用很简洁比如挂号提交export function createRegistration(data) { return request({ url: /api/registration, method: post, data }); }响应拦截器里一个容易被忽略的细节是约号接口失败时后端返回的是Result.errorHTTP 状态码通常还是 200所以必须在response拦截器里先判断code而不是只在error分支处理。很多新手在这里漏掉结果后端明明返回了“号源不足”前端却一直以为是网络错误。5.3 核心页面的实现要点对患者端来说最重要的三个页面是科室列表页、医生列表页、医生排班与提交挂号页。科室列表页一般是一个el-card网格点击科室卡片后跳转到/doctor?depIdxxx。医生列表页用el-table或卡片列表展示职称、挂号费、简介并标注该医生是否还有可预约排班。排班页是核心交互区通常会先加载医生未来七天内的排班数据按日期和上午/下午展示有排班且有剩余号蓝色按钮显示“剩余 N 号”可点击。有排班但剩余 0灰色禁用显示“已满”。没有排班日期按钮置灰显示“无排班”或不显示。点击可预约按钮后弹出确认对话框显示患者姓名、医生、就诊时间、挂号费点击确认后调用创建挂号接口。提交后页面跳转到“我的挂号”让用户能立刻看到这条记录。“我的挂号”列表页用el-tabs或状态筛选标签区分“全部 / 待就诊 / 已完成 / 已取消 / 爽约”。状态字段在前端做一次映射不同状态用不同颜色的el-tag展示例如待就诊显示蓝色已完成显示绿色已取消显示灰色爽约显示红色。这样用户一眼就能看出下一步要做什么。6. 本地部署实测从建库到页面可用的完整过程与常见报错6.1 先按顺序走一遍启动链路本地跑通这类源码核心就四步。第一步建库用 MySQL 命令行或图形工具执行数据库脚本mysql -uroot -p source /你的目录/db.sql;如果脚本里已经包含CREATE DATABASE语句就不用手动建库如果没有先建一个同名数据库再导入。导入后检查几张核心表是否生成尤其注意是否有初始管理员账号和测试医生数据很多项目的注册功能只开放给患者管理员账号是写死在 SQL 里的。第二步改配置打开application.yml更新数据库用户名密码。如果 MySQL 是 8.0 版本建议确认驱动版本在pom.xml里能匹配如果连接报Public Key Retrieval is not allowed在url后面加allowPublicKeyRetrievaltrueuseSSLfalse。第三步启动后端在 IDEA 里直接运行main方法或者用命令行mvn spring-boot:run看到 Tomcat started 的日志且端口未被占用说明后端启动成功。可以先用浏览器或接口工具访问一个公开接口验证比如登录接口看是否返回 JSON。第四步启动前端进入前端目录执行npm install npm run servenpm install在这个阶段是最容易出问题的尤其是 Node 版本过高的环境报错大多集中在node-sass的编译安装。解决办法是要么安装与项目匹配的 Node 版本要么把package.json里的node-sass依赖换成sassdart-sass然后重新安装依赖。全部启动完成后浏览器访问前端地址比如http://localhost:8081先用管理员账号登录后台生成几个科室、医生、排班再到前台用患者账号注册并预约一次整个链路就通了。6.2 我遇到过的几个高频报错与排查思路报错现象常见原因处理建议Access denied for user rootlocalhostapplication.yml数据库密码不对或权限不足核对数据库密码或新建一个赋权账号Table xxx doesnt existSQL 脚本未执行或执行了不同的数据库确认连接的是哪张库重新导入脚本Port 8080 was already in use后端端口被其他进程占用换端口或杀掉占用进程前端页面空白、接口 404前端代理没生效或请求路径不对检查vue.config.js的 proxy 与 Axios baseURL 是否一致Invalid bound statement (not found)Mapper XML 没有扫描到检查MapperScan路径以及 XML 文件是否在 classpath 下npm install 时 node-sass 报错Node 版本与 node-sass 不兼容降低 Node 版本或切换为 dart-sass登录成功后依旧跳回登录页路由守卫或 token 存储逻辑有问题打印 localStorage 内容检查拦截器是否解析 token这些报错里最值得说的是“前端接口 404”。很多人以为代码问题实际上只是开发环境代理配置写错比如前端请求/api/login但 proxy 的target指向了 9090而后端实际在 8080。遇到这类问题先打开浏览器开发者工具的 Network 面板看请求 URL 和后端端口基本一眼就能定位。6.3 二开时值得优先扩展的几个方向把整套系统跑通之后如果你想在此基础上做二次开发我建议优先看这几个扩展点号源模板与批量排班。目前生成排班大多靠管理员一条条选择日期和时段体验很原始。可以新增一个“排班模板表”按周设置医生出诊规则然后用定时任务批量生成未来七天的排班记录体验会有质的提升。在线支付占位。很多源码的挂号流程走到“提交成功”就结束没有对接真正支付。可以加入一个支付订单表把缴费状态挂到挂号记录上为真实场景补全闭环。短信或站内信提醒。在“挂号成功”“就诊前一天”这两个节点发个模板消息对真实医院场景来说非常关键也能作为项目答辩里的亮点功能。患者病历与历史就诊记录。把每次就诊信息沉淀成病历表再扩展一个医生端“写病历”页面整个系统的业务纵深就拉开了。二开的时候还有一个宏观建议优先在现有表结构上增量加字段和加表尽量不要改动原有核心表的主键和外键关系。因为这个项目是你拿来作为基础的骨架如果上来就把sys_user表改成独立的三张用户表后续所有关联查询都要跟着动维护成本会瞬间变大。最后分享一个我自己的操作习惯拿到任何一份 Java Vue 的源码我不会先急着启动而是先把数据库脚本、application.yml、vue.config.js和路由文件通读一遍。这样做的好处是启动报错的时候你脑子里已经有一张“配置地图”大概率一眼就能判断问题出在哪一层而不是逐个去试。这套挂号系统作为典型的前后端分离项目流程标准化程度很高按顺序把环境和链路跑通后剩下的无论是改功能还是加模块都会顺很多。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑