资讯动态

SpringBoot+Vue+MySQL医院挂号就诊系统实战:从业务拆解到部署答辩

发布时间:2026/10/14 16:48:38 来源:尧图企业网站定制
毕业设计季后台总有人私信问同一类问题拿到了一个SpringBootVueMySQL的医院挂号就诊系统源码却不知道从哪开始看论文不知道怎么写部署文档照着做又报错。这个选题确实是计算机专业毕业设计里的常客因为它的业务链条完整、技术栈主流、演示效果好从挂号到就诊再到管理后台能讲的东西很多。我今天就把这个项目从技术拆解到落地部署完整过一遍包含我实际开发中踩过的坑、后端核心流程的设计思路、前端页面的联动逻辑以及论文答辩怎么把项目讲清楚。无论你是刚拿到源码还没跑起来还是想自己从头写一遍这篇文章都能给你一份可操作的参考。1. 医院挂号就诊系统的业务拆解与技术选型逻辑1.1 挂号系统真正要解决的三个核心问题很多人看到医院挂号就诊系统几个字第一反应是这不就是个增删改查吗但真正动手做过就会发现这套系统有三个核心问题远比表面复杂。第一个问题是号源管理。一个医生一天只放30个号上午15个下午15个患者挂了一个号号源就要立刻扣减患者取消挂号号源要释放回来。这里牵扯到并发场景下的数据一致性也就是多个人同时挂最后一个号的时候系统不能超卖。第二个问题是排班规则。医生不是天天都在需要一张排班表来定义什么时间在哪个科室出诊号源是挂在排班记录上的。排班是系统的中枢数据前端选医生、选日期、选时段最后落到具体排班记录上挂号。排班数据设计得合理业务才能顺畅地跑起来。第三个问题是角色权限。这个系统里有三种身份患者、医生、管理员。患者要能注册登录、查医生、预约挂号、取消挂号、查看就诊记录医生要能查看自己的排班、填写病历、处理患者记录管理员要管理科室、医生信息、排班和公告。每种角色看到的功能、操作的接口完全不同这直接决定了前后端的权限控制方案。如果只做单表增删改查那确实简单但毕业设计答辩时老师一眼就能看出来系统有没有深度。我在设计时把上述三个问题作为核心来建模系统才真正有了业务的味道。1.2 为什么是SpringBootVueMySQL的组合这个技术栈在毕业设计中的普及率非常高因为它是一个稳妥且能讲出东西的组合。后端选Spring Boot是因为它对MyBatis、Spring MVC、事务管理、拦截器这些都有非常成熟的整合方案。写一个预约挂号接口声明式事务一行注解就搞定不需要像早期JavaWeb那样手动管理连接和事务。同时Spring Boot的自动配置让项目启动成本极低对毕业设计这种需要快速出成果的场景非常友好。前端选Vue是因为它的响应式数据绑定和组件化开发非常适合这种多页面、多角色的管理系统。路由用Vue Router管理状态用Vuex或Pinia管理页面直接用Element UI等组件库搭建。表单、表格、弹窗这些医院管理系统里高频出现的东西组件库直接覆盖前端工作量能砍掉一大半。MySQL作为关系型数据库对这个项目来说是够用且稳妥的。挂号系统涉及大量的表关联用户-挂号记录-排班-医生-科室MySQL的事务支持InnoDB引擎和多表关联查询能力正好匹配。相比直接用PostgreSQL或者SQL ServerMySQL的资料最多出问题时最容易找到解决方案。这个组合还有一个很现实的好处答辩时技术点覆盖广。Spring Boot能讲IOC、AOP、自动配置Vue能讲生命周期、组件通信、路由守卫数据库能讲事务隔离、索引设计、ER模型。每个环节都有可深入提问的余地不用担心没东西讲。1.3 系统角色与权限模型这个系统的权限模型我采用基于角色的访问控制RBAC。用户表里有个角色字段后端基于JWT里的角色信息做接口级权限控制前端基于路由守卫和菜单渲染做页面级权限控制双重校验。patients患者能操作注册登录、浏览科室和医生、查询排班、挂号、取消挂号、查看自己的挂号记录和诊断记录。doctors医生能操作查看个人排班、查看患者挂号列表、填写诊断建议、修改个人简介。admins管理员能操作科室管理、医生账号管理、排班管理、公告管理、挂号记录查询、前台所有数据的维护。权限逻辑在后端的实现就是拦截器注解在JWT校验通过之后再从token里解析出角色进行判断。这里我建议权限判断不要让前端来拍板前端隐藏菜单只是体验层面的处理真正的安全校验必须放在后端接口层。2. 后端Spring Boot工程的核心实现思路2.1 工程结构与模块划分拿到源码后先别看代码细节先把工程结构看清楚。我自己的项目后端结构大致是这样的hospital-backend ├── src/main/java/com/hospital │ ├── controller // 控制层接收请求返回结果 │ │ ├── AdminController.java │ │ ├── AuthController.java │ │ ├── DepartmentController.java │ │ ├── DoctorController.java │ │ ├── AppointmentController.java │ │ └── ScheduleController.java │ ├── service // 业务层核心的业务逻辑都在这里 │ ├── mapper // MyBatis接口层与XML映射文件对应 │ ├── entity // 实体类对应数据库表 │ ├── dto // 数据传输对象避免实体类直接暴露给前端 │ ├── config // 配置类跨域、拦截器、WebMvc │ ├── common // 通用类Result封装、业务异常、常量 │ └── util // 工具类JWT工具、MD5工具 ├── src/main/resources │ ├── mapper // MyBatis的XML文件 │ └── application.yml // 项目配置 └── pom.xml这里我特别想强调一个细节entity和dto不要混用。比如挂号返回给前端的数据除了挂号记录本身还需要关联患者姓名、医生姓名、科室名称、排班时间直接用实体类接收很容易出现多余的字段或者嵌套混乱。我当时单独写了AppointmentVO来聚合这些信息MyBatis在XML里写关联查询映射到VO上前端拿数据非常方便后端结构也清晰。这个设计在论文的系统设计章节里也是一个可以单独展开写的亮点。2.2 用户登录与JWT权限控制的落地细节登录模块是整个系统的入口这里有一个我实际开发中深有体会的点不要用明文存密码。哪怕毕业设计也必须做密码加密我用的是MD5固定盐的简单做法虽然不是最安全方案但足以体现安全意识。如果要更严谨一点可以用BCryptSpring Security里自带。用户登录后后端逻辑是根据用户名查出用户记录。对前端传入的密码做同样的MD5盐处理比对数据库存储值。校验角色、状态是否正常。生成JWT token把用户ID和角色放进去设置过期时间。返回token和用户基本信息给前端。JWT工具类的核心代码逻辑大致是public String generateToken(Integer userId, String role) { return Jwts.builder() .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 12)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); }token生成之后前端每发一个请求都会在header里带上后端用一个拦截器统一校验。我在WebConfig里注册拦截器并配置了放行路径比如/api/auth/login、/api/auth/register放行其余所有/api/**都要验证token。校验代码的核心逻辑是String token request.getHeader(Authorization).replace(Bearer , ); Claims claims Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody(); request.setAttribute(userId, claims.get(userId));WebConfig里的拦截器注册写法Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login, /api/auth/register); }角色权限的接口级控制我采用的是在拦截器基础上再加一层业务校验。比如管理员接口在Controller里判断role.equals(admin)不满足就抛自定义业务异常前端统一弹提示。这种方式比引入完整的Spring Security要轻量得多毕业设计足够用答辩时也能说清楚为什么这么设计。2.3 预约挂号核心流程号源怎么扣减与释放这一步是整个后端代码里最容易翻车的地方也是我一直觉得最像业务的一段代码。挂号动作发生在哪个表上不是直接在排班表schedule里减字段而是通过往挂号记录表appointment插入一条状态为已预约的记录来完成的。号源是否充足由排班表里的available_number字段控制而这一切必须在一个事务里完成否则会出现记录插进去了号源没扣掉或者反过来。流程说白了是这样校验挂号记录是否重复防止同一患者同一时段重复挂号。查询排班表拿到该排班记录使用SELECT ... FOR UPDATE锁住这一行。判断available_number是否大于0不大于则抛出号源已满。如果充足先更新排班表的available_number available_number - 1。再插入挂号记录状态为已挂号。提交事务。任何一步失败整体回滚。FOR UPDATE这里要重点说明在高并发下两个请求同时读到同一个排班记录都发现available_number 1如果不加锁最后可能插入两条挂号记录但排班的号源已经变成0了这就是超卖。MySQL的SELECT ... FOR UPDATE会对选中的行加排他锁第二个事务只能等第一个事务提交或回滚后才能继续这就保证了号源扣减是串行的。我还在挂号接口上加了Transactional注解这个方法内所有的数据库操作都在同一个事务里执行。代码大致是Transactional(rollbackFor Exception.class) public Result createAppointment(AppointmentDTO dto) { Schedule schedule scheduleMapper.selectByIdForUpdate(dto.getScheduleId()); if (schedule null) { throw new BusinessException(排班不存在); } if (schedule.getAvailableNumber() 0) { throw new BusinessException(号源已满); } Appointment appointment new Appointment(); appointment.setPatientId(dto.getPatientId()); appointment.setScheduleId(dto.getScheduleId()); appointment.setStatus(1); // 1代表已挂号 appointment.setCreateTime(new Date()); if (!appointmentMapper.insert(appointment)) { throw new BusinessException(挂号失败); } scheduleMapper.decreaseAvailableNumber(schedule.getId()); return Result.success(挂号成功); }对应的XML就是update iddecreaseAvailableNumber UPDATE schedule SET available_number available_number - 1 WHERE id #{id} /update取消挂号就是反着来先更新挂号记录状态为已取消再把排班的available_number 1。同样要用事务包起来否则号源可能不会释放。这块逻辑我在论文里用了很大篇幅描述写时序图、写事务特性、举例分析并发场景。答辩时如果老师问并发下会不会出问题直接讲这个FOR UPDATE的锁机制就能回答得很扎实。2.4 MySQL表设计哪些表是关键数据库是这个系统最核心的交付物之一我在设计时一共规划了六张核心表和两张辅助表。这里的每张表都对应一个明确的业务模块。表名核心字段作用usersid, username, password, role, name, phone统一用户表用role区分患者/医生/管理员departmentsid, name, intro科室表doctorsid, user_id, department_id, title, intro医生信息表扩展用户表中role为医生的人schedulesid, doctor_id, department_id, date, period, total_number, available_number排班表号源扣减的核心表appointmentsid, patient_id, schedule_id, status, create_time挂号记录表状态区分已挂号/已取消/已完成medical_recordsid, appointment_id, diagnosis, prescription, create_time就诊记录表医生填写病历辅助表主要是announcements公告和feedback留言反馈能丰富系统功能展示论文里也好写功能模块。这里要特别提一下users表的设计。很多新手会把患者、医生、管理员各建一张表导致登录要查三张表、外键关联混乱。我采用的是单表多角色用role字段区分医生再通过doctor表扩展专属信息。这样登录逻辑统一在一个入口前端也方便根据角色渲染不同菜单是很多真实管理系统都在用的做法。appointments表和schedules表之间是schedule_id关联查询患者挂号的完整信息需要连接四张表SELECT a.id, a.status, a.create_time, s.date, s.period, d2.name AS doctor_name, dep.name AS department_name FROM appointment a JOIN schedule s ON a.schedule_id s.id JOIN doctors d1 ON s.doctor_id d1.id JOIN users d2 ON d1.user_id d2.id JOIN department dep ON s.department_id dep.id WHERE a.patient_id #{patientId}这种多表关联查询在毕业设计里很加分因为它展示了你对数据库设计的理解而不是只会在单表里查数据。3. 前端Vue部分的架构与页面联动3.1 前端工程结构与路由设计拿到前端的源码同样先看目录结构我习惯的项目组织方式是hospital-frontend ├── src │ ├── api // 接口请求文件按模块拆分 │ │ ├── auth.js │ │ ├── appointment.js │ │ ├── department.js │ │ └── ... │ ├── router // 路由配置 │ ├── store // 状态管理存token和用户信息 │ ├── views // 页面组件 │ │ ├── patient // 患者端页面 │ │ ├── doctor // 医生端页面 │ │ └── admin // 管理端页面 │ ├── components // 公共组件 │ ├── utils/request.js // axios封装 │ └── App.vue ├── package.json └── vite.config.js路由设计上我会把公共页面登录、注册、首页和权限页面患者后台、医生后台、管理后台分开。用动态路由或路由守卫来控制访问。我在前端里给每个需要权限的route配置了meta.roles字段比如{ path: /patient, component: () import(../views/patient/PatientLayout.vue), meta: { roles: [patient] }, children: [ { path: appointment, component: () import(../views/patient/MyAppointment.vue) } ] }3.2 axios封装与接口对接的注意点前后端分离项目中axios封装是必须做的一步。我封装了utils/request.js统一处理了三个东西请求头带token、响应统一解包、错误统一提示。import axios from axios import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理业务码 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { alert(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } ) export default request注意这里的baseURL我配的是/api而不是完整路径。这是因为我在vite.config.js里配了开发服务器代理把所有/api开头的请求转发到后端的localhost:8080这样做的好处是前端代码里不需要写死后端地址打包部署的时候也方便通过Nginx统一转发。3.3 核心页面的交互逻辑从选科室到支付确认患者端的核心操作流程是首页看科室 → 科室页看医生列表 → 医生详情选排班日期和时段 → 确认挂号 → 在我的挂号里看到记录。这个流程看着简单但每个页面之间是需要传递数据的。我用的是 Vue Router 的query参数适合传ID加上 Vuex/Pinia 保存当前患者信息来实现。举个例子科室列表页点击某个科室跳转到医生列表页this.$router.push({ path: /doctors, query: { departmentId: this.currentDepartment.id } })医生列表页在created钩子里通过this.$route.query.departmentId调接口查对应科室的医生。医生卡片上点排班按钮走同样的方式传医生ID到排班页。排班页是整个流程里信息量最大的页面。患者要能从日历插件里选日期下方展示这个医生该日期的两个时段上午/下午以及剩余号源。如果号源为0按钮置灰不可点。这里要重点检查数据响应式更新患者挂号成功后返回上一页号源数必须在重新加载后看到变化。如果我用的是动态数据未重置的状态容易让界面显示残影所以每次进入排班页都强制重新调用接口获取最新排班数据。整个交互流我建议结合图来讲解但做项目时最重要的还是把流程拆成接口GET /api/department/list查科室GET /api/doctor/list?departmentIdx查医生GET /api/schedule/list?doctorIdxdate2025-xx-xx查排班POST /api/appointment/create提交挂号GET /api/appointment/my查询我的挂号五个接口一条完整业务链前端页面全部围绕这些接口来组织。3.4 路由守卫三种角色如何共用一套登录态权限控制只做后端校验是不够的前端还需要给用户好的体验。我用Vue Router的全局前置守卫来实现页面的访问控制。核心逻辑是判断当前路由是否需要登录如果需要登录并且没有token就跳转到登录页如果已经登录再根据路由的meta.roles判断当前用户角色是否有权限进入如果没有权限就跳转到对应的默认页面。router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.path /login || to.path /register) { next() return } if (!token) { next(/login) return } if (to.meta.roles !to.meta.roles.includes(role)) { next(/error) return } next() })菜单渲染也同理admin布局里只有管理员才能看到排班管理科室管理菜单项我用v-if判断角色来渲染。这种权限设计虽然比较简单但覆盖了毕业设计需要的场景。4. 从源码到跑通环境搭建与部署全流程4.1 环境准备清单这一步是很多人卡住的地方因为环境版本不一致导致的报错非常多。我建议直接用以下版本组合都是实测稳定搭配工具推荐版本说明JDK1.8 或 11别用JDK 17跑老项目SpringBoot 2.x会有兼容问题Maven3.8.x编译和管理依赖Node.js16.x 或 18.xVue 2建议16Vue 3Vite建议18MySQL5.7 或 8.0注意8.0的时区和认证插件问题IDEIntelliJ IDEA后端首选前端可以用VS Code关键点如果拿到的是SpringBoot 2.x项目JDK最好用8或11不要直接上JDK 17。因为老版本Spring Boot在高版本JDK下启动经常报一些奇怪的反射错误排查起来相当头疼。前端如果用了Vue 2 Element UINode版本太高会导致node-sass安装失败建议用Node 16。4.2 数据库初始化与脚本执行源码压缩包里通常有个hospital.sql或类似名字的脚本。导入步骤很简单但有几个细节容易出错。先创建数据库CREATE DATABASE IF NOT EXISTS hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital; source /path/to/hospital.sql;如果你是用的图形化工具直接用Navicat或DataGrip导入SQL文件也可以。注意一个常见问题如果SQL脚本里有中文注释并且数据库字符集不是utf8mb4导入时可能出现乱码或者报错。解决办法是执行前先确认目标数据库的字符集和排序规则如果脚本里已经带了CREATE DATABASE语句通常不会有这个问题。导入完成后检查一下users表里是否有初始账号。一般情况下脚本里会预置admin账号比如admin / 123456但保险起见自己再插入一个测试账号方便后续登录验证。4.3 后端启动步骤与常见报错启动后端之前第一步是改application.yml里的配置。重点检查三个东西数据库地址、端口、库名。如果MySQL跑在云服务器上一定要改IP。MySQL用户名和密码。本地默认是root但密码要跟你自己的一致。端口配置。默认8080容易冲突可以改成8200之类的。常见的启动报错我列几个端口被占用。启动直接报Port was already in useWindows下可以用netstat -ano | findstr 8080找到占用进程并确认是不是自己前一次启动残留的Java进程。解决方法是关掉旧进程或者修改配置文件里的端口。数据库连接失败。报Access denied for user rootlocalhost说明密码错了报Communications link failure说明MySQL服务没开或者端口不对。MySQL 8.0用户还可能遇到Public Key Retrieval is not allowed需要在连接URL上加上allowPublicKeyRetrievaltrue。时区问题。MySQL 8.0默认时区导致Server返回的日期时间差了8小时。连接URL里加上serverTimezoneAsia/Shanghai就能解决。启动成功的标志是控制台看到Started HospitalApplication in x.xxx seconds以及SpringBoot的默认Banner。看到这句后可以用Postman先测试一下登录接口确认后端整体是通的。4.4 前端启动步骤与跨域处理前端启动相对简单。进入前端目录后npm install npm run serve # Vue 2 项目 npm run dev # Vue 3 Vite 项目npm install是前端最大的坑之一。默认源是国外源经常会出现安装进度卡着不动然后报各种ERESOLVE或者node-sass安装失败的错误。解决办法是把npm源切到国内镜像npm config set registry https://registry.npmmirror.com老项目如果是node-sass相关报错还有一种办法是设置环境变量跳过二进制文件下载npm config set sass_binary_site https://npm.taobao.org/mirrors/node-sassnpm install 成功后启动前一定确认Vite或Webpack的代理配置是否正确。以Vite为例export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })如果代理没配前端访问/api/login会请求到前端自己5173端口返回的是index.html控制台会报404或者接口内容解析失败。这是非常常见的没跑通原因。4.5 本地演示怎么准备数据这一点我要单独拿出来说因为很多人把系统跑通之后对着一个空数据库演示效果特别干。答辩前我愿意花一小时去造数据。具体操作是在departments表里插入内科、外科、儿科、妇产科等10个左右的科室。给每个科室分配2-3个医生医生名字可以起一些贴近日常的。医生账号的user_id要跟真实注册的用户关联。给未来一周的每一天都安排排班上午下午各放出20个号。用两个患者账号挂号一个挂两个号一个挂一个号再取消一个形成已挂号和已取消的对比数据。在medical_records里给至少一个已完成的挂号填上诊断结论。数据越真实演示越有说服力。老师看到的不再是空壳页面而是一套像模像样的系统。5. 论文写作与答辩准备的补充建议5.1 论文结构怎么组织拿到源码后最容易犯的错误是论文照抄别人的模板。我建议论文结构严格跟着自己项目的实际情况走大致分为第一章绪论写背景意义、国内外研究现状注意不要编造真实文献参考来源要严谨。第二章需求分析写业务需求、功能需求、非功能需求、可行性分析。第三章系统设计写总体架构、功能模块设计、数据库概念结构设计ER图、逻辑结构设计、物理表结构。第四章系统实现按前端、后端、核心流程来写把登录模块、挂号模块、排班模块的实现过程配上核心代码片段。第五章系统测试写测试环境、功能测试用例、结果分析。每一章都要和自己写的代码对应上。比如数据库设计章节里的表结构可以直接把SQL脚本中的CREATE TABLE语句整理成表格放到论文里系统实现章节里的核心代码从自己的项目里挑关键方法贴进去不要贴得太多每段代码后面加一句自己的设计说明。论文里最加分的部分是流程图和数据流图。用画图工具画清楚患者挂号的活动图管理员排班的时序图系统总体架构图答辩时一图胜千言。5.2 答辩时容易被问到的问题我模拟了几个高频问题提前把答案背下来为什么用JWT而不用Session因为前后端分离架构下后端不保存会话状态更利于扩展JWT自带用户信息天然支持移动端和Web端。但JWT也有缺点比如无法在后端主动过期所以要设置合理有效期。并发挂号怎么防止超卖回答思路数据库行锁SELECT ... FOR UPDATE 事务 状态字段控制。可以展开讲一下如果不加锁会出现什么问题。数据库表之间关系有哪些用户可以对应医生表1对1科室和医生是1对多排班和挂号记录是1对多这些关系就是外键设计的依据。系统有什么不足之处千万别回答没有不足。可以说当前系统没有引入消息队列高并发场景下担心数据库压力后续可以引入Redis缓存号源用乐观锁或分布式锁进一步优化这个答案既显得你有深度又留了优化空间。5.3 把项目讲成自己的源码复盘清单拿到别人写的源码最怕答辩时一问三不知。我建议按下面的清单过一遍确保每个模块都能说清楚项目整体启动流程是什么样的前端后端各依赖什么环境。登录接口从请求到返回的完整链路。预约挂号接口在哪几个表上做了什么操作。JWT的token在哪里解析、怎么校验、过期了怎么处理。前端路由守卫的逻辑代码能默写。管理员能操作的每个功能对应哪个数据库表。如果你打算让代码更像自己写的可以做一个表层修改统一修改包名把com.hospital改成自己定义的包名修改系统名称和Logo修改数据库表前缀增加一个自定义功能比如体检套餐、报告查询、留言板。这些改动消耗的时间不多但答辩时体现在论文里的创新点就立住了。我自己的习惯是拿到任何一个新项目的第一件事不是跑起来而是把核心业务表的关系画一遍。把表之间的关系理顺了整个系统的运转逻辑就清楚了大半后面无论看代码还是写论文都会顺很多。上次有个同学跟我聊说他花了两个晚上排错就是因为写死的前端端口和后端不一致弄到最后发现是后端启动时把端口改成了8200但代理还指向8080。这种问题其实很常见我在文章里标出来的那几处配置检查点你跑之前逐条过一遍能省掉非常多的排查时间。这个项目如果只是部署跑通那它只是一个作业但如果你愿意把事务、锁、权限控制、表设计这些细节全部吃透它在你的简历上就会变成一段实打实的项目经验。希望这篇文章能帮你在毕业设计这条路上走得顺一点。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑