
医院网上预约挂号系统这个Java毕设题目这几年一直有不少学生来问尤其到了春季答辩季几乎每周都能看到“基于springbootvue”、“预约挂号”、“源码文档”这几个关键词凑在一起。我前前后后帮人调试过不少版本也亲手整理过整套交付物今天这篇就把这类项目的核心设计、后端实现、前端联调、文档准备以及一条龙定制中常见的加需求场景一次讲透。如果你正准备做类似题目或者正在纠结“这个系统到底要做到什么程度才够毕业设计标准”这篇文章可以当作一份项目复盘来看。系统本身不复杂但把业务逻辑理清楚、把代码结构写干净、把文档和代码讲解准备好这些环节比堆功能更重要。1. 医院预约挂号这个选题为什么适合Java毕设又不适合盲目加功能先聊一个很多学生都会忽略的问题这类题目看着简单背后实际上是一套完整的“业务闭环”系统天然包含用户端、医生端、管理端非常适合展示Java后端和Vue前端的技术能力。医院网上预约挂号系统的核心是患者注册登录后可以按科室、医生、日期查看排班选择时段预约挂号医生可以查看自己的预约列表、更新排班、填写诊断或停诊管理员则管理科室、医生账号、排班规则、预约记录和系统基础数据。这看起来只是几张大表的关系但一旦深入做涉及的问题就比想象中多。因为它是毕设不是商业项目所以并不需要做成真正的医疗级系统也不需要对接医院内部HIS系统。但这不意味着功能可以做得太浅。很多学生的第一版设计只有“用户选个医生点预约”这一个流程缺少取消预约、排班冲突、状态流转、权限区分这类细节。答辩时老师一问“预约后医生怎么处理”“患者能不能取消”“取消后号源要不要释放”就会卡住。所以这类系统的价值在于它有足够复杂的业务状态但又没有复杂到需要团队协作才能写完。它适合展示你对Spring Boot的理解、对数据表设计的理解、对Vue组件化和路由的理解同时又不会因为业务过于庞大而影响毕业设计的按时交付。选题热本身不是问题问题是不少人的实现方式太“学生化”只关注界面不关注逻辑。这也是我写这篇文章想优先纠正的事情。2. 先把角色和业务流理顺再谈代码我之前帮一个学生排查预约功能报错时发现他把所有的业务逻辑都写在Controller里排班表和预约表没有明确的外键关系患者明明预约的是上午号源数据库里却显示成下午。后来我把数据表重新梳理了一遍把所有状态字段列成一张状态流转表问题一下就清晰了。这里就把这套梳理方法分享出来。2.1 三种角色决定了三种不同的页面和权限这个系统里最基本的角色是三个患者端、医生端、管理员端。有些项目还会单独做“科室管理员”或者“院长”角色但作为毕设三种角色已经够用了。患者端的核心页面是首页展示科室和公告、科室列表、医生排班表、预约操作、我的预约、个人中心、取消预约。医生端排班管理、预约患者列表、处理停诊。管理员端科室管理、医生管理、患者管理、排班审核、预约记录查询、系统统计。角色的存在意味着前端需要有路由守卫后端需要做权限拦截。很多学生的项目里同一个用户既能看到患者的菜单又能看到管理员的菜单这就是角色权限没有做好。2.2 核心数据表设计医生、科室、排班、预约我翻过不少这套题目的参考代码数据库表基本会围绕这几张表展开表名核心字段作用departmentid、name、intro、status科室信息doctorid、name、department_id、title、intro、avatar医生信息scheduleid、doctor_id、work_date、period、total、used、status医生排班与号源appointmentid、patient_id、schedule_id、appointment_no、status、create_time患者预约记录userid、username、password、role、real_name、phone登录账号与身份这里最需要多说两句的是排班表schedule和预约表appointment。排班表里的 period 字段一般用来区分上午、下午或者具体时间段。不要只存一个字符串比如“上午”比较好的做法是用枚举值1 代表上午2 代表下午再配合一个开始时间和结束时间方便前端展示时段。total 表示这个排班的总号源数used 表示已预约数量预约成功后 used 加一取消预约后 used 减一。status 字段用来标记这个排班是否被停诊停诊之后前端不应该再展示可预约的按钮。预约表需要记录到底是哪个患者、哪个排班、预约时间、预约编号和状态。预约编号我用过 UUID也用过时间加随机数的形式但在毕设里可以直接用数据库自增id因为系统规模不大不需要体现并发能力。预约状态可以设计为0 待就诊、1 已完成、2 已取消、3 已退号具体看你要不要做“就诊后标记完成”的流程。排班、预约、医生、科室之间的关系最好在表设计文档里画清楚。虽然不能在你的博文里用Mermaid但你做毕设文档时可以用PowerDesigner或者Navicat反向生成ER图放在论文里答辩时很加分。2.3 预约状态流转里最容易答不上来的一环老师答辩时最爱问的一句话是“患者取消预约之后号源怎么处理”很多人答不上来是因为他根本没有做取消预约。或者做了但只是删掉了一条预约记录并没有把排班表里的 used 减回去。正确的做法是取消预约时同时修改预约状态和排班表的剩余号源。这一步鼓励同学们在Service里用事务注解来处理因为涉及两张表的修改。如果在高并发场景下还要考虑超卖问题比如两个患者同时抢最后一个号源。毕设阶段不用上消息队列也不一定要Redis分布式锁但你至少可以在预约前用一个带条件的更新语句来防超卖比如UPDATE schedule SET used used 1 WHERE id ? AND used total如果这个SQL返回的影响行数是0就说明号源已经被抢完了这一次预约应该失败。这是我在代码讲解时非常喜欢讲的一个点因为它既简单又能体现对并发的理解。3. Spring Boot 后端实现从配置到预约接口哪些细节值得认真对待后端部分Spring Boot加MyBatis Plus是这类毕设的标准配置。Spring Boot负责提供接口MyBatis Plus负责操作数据库只要前两步的数据表设计没有大问题写接口的过程其实很快。3.1 项目结构和依赖版本很多学生拿到手的第一版项目代码都堆在几个类里。Controller里直接写业务逻辑Service层空着Mapper只写了接口没有XML。这样做虽然能跑但文档和答辩会很吃亏。我更推荐用标准的分层结构来组织项目com.example.hospital ├── controller ├── service │ └── impl ├── mapper ├── entity ├── dto ├── vo ├── config ├── common │ ├── Result │ └── exception依赖版本这里提示一个常见坑Spring Boot 版本不要盲目选最新毕设项目建议直接选稳定版本比如 2.7.x。原因很简单网上绝大多数参考资料、教程、老师给的代码模板都基于2.x你如果选了一个3.x版本很多配置类和旧依赖可能会有兼容问题。比如 springfox 的 Swagger 配置、某些加密工具类在3.x里就要换实现。不要给自己答辩前无故增加排错成本。pom.xml 里的核心依赖大致是这样dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency3.2 统一返回和全局异常我给这套项目写后端的第一件事永远是先定义一个统一返回对象而不是急着去写任何接口。格式通常长这样public class ResultT { private Integer code; private String message; private T data; }接口返回时直接返回 Result.success(data)出错时返回 Result.error(500, 系统错误)。同时写一个全局异常处理器这样代码里就不用到处写 try-catch业务异常直接通过自定义异常抛出即可。这个设计虽然很小但答辩时能体现你对“后端规范”的理解也是代码讲解里最好的开场。3.3 MyBatis Plus 的使用MyBatis Plus 在毕设项目里最大的意义就是帮我们省掉了大量简单的单表增删改查。你只需要定义实体类继承它的 BaseMapper基本的 CRUD 方法就都有了。很多同学在网上搜到过“mybatisplus根据java实体类生成创建表的sql语句”实际上MyBatis Plus官方有一个代码生成器可以根据数据库表生成实体、Mapper、Service反过来也能基于实体类生成建表语句这点在写代码前设计表的时候非常方便。比如实体类可以这样定义Data TableName(schedule) public class Schedule { TableId(type IdType.AUTO) private Integer id; private Integer doctorId; private String workDate; private Integer period; private Integer total; private Integer used; private Integer status; }这里注意注解里声明的表名和字段名需要和数据库一致驼峰和下划线之间MyBatis Plus 会自动开启驼峰映射但如果你在数据库里用了 create_time在实体里最好也要对应成 createTime否则返回给前端时字段名会不一致。3.4 预约接口的业务逻辑怎么写才不丢分预约接口是整套系统的命门也是代码讲解环节最值得展开的部分。我建议在 Service 层实现这个方法时把逻辑分成清晰的步骤校验患者身份和排班是否存在校验排班是否处于可预约状态校验是否已经预约过同一时段使用条件更新占用号源插入预约记录返回预约信息。注意第3步很多人不做也就是“重复预约校验”。同一患者对同一个医生的同一个时段只能有一个有效预约这个逻辑不写患者反复点按钮就会生成多条预约记录。参考代码如下只抽取核心部分Transactional public Appointment createAppointment(AppointmentCreateDTO dto) { Schedule schedule scheduleMapper.selectById(dto.getScheduleId()); if (schedule null || schedule.getStatus() 0) { throw new BusinessException(该排班不可预约); } Long count appointmentMapper.selectCount( new LambdaQueryWrapperAppointment() .eq(Appointment::getPatientId, dto.getPatientId()) .eq(Appointment::getScheduleId, dto.getScheduleId()) .in(Appointment::getStatus, 0, 1)); if (count 0) { throw new BusinessException(您已预约过该时段请勿重复预约); } int updated scheduleMapper.update(null, new LambdaUpdateWrapperSchedule() .eq(Schedule::getId, dto.getScheduleId()) .eq(Schedule::getStatus, 1) .apply(used total) .setSql(used used 1)); if (updated 0) { throw new BusinessException(号源已被约满请更换时段); } Appointment appointment new Appointment(); appointment.setPatientId(dto.getPatientId()); appointment.setScheduleId(dto.getScheduleId()); appointment.setStatus(0); appointmentMapper.insert(appointment); return appointment; }这段代码里最值得讲给老师听的就是号源占用方式它不是先查询再更新而是直接用条件更新保证原子性这样就能避免两个人同时预约同一个号源时出现超卖。理解了这个点后端这块基本就站得住脚了。4. Vue 前端搭建环境配置、路由、组件和接口联调前端部分这些年问的人最多的问题集中在Vue环境安装、路由写法、组件间传值这些。其实只要把工程结构理清了前端并不难。这里就按照实际搭建顺序来讲。4.1 环境怎么装版本怎么避坑很多同学第一次跑Vue项目卡在node_modules安装阶段。我的建议很简单不要用太新的Node版本也不要用太旧的。如果你的Vue CLI是4.x或者5.xNode 16到18之间通常问题不大Node 20 反而容易出现 OpenSSL 相关的报错。创建项目时我用的是npm install -g vue/cli vue create hospital-web选默认包含Router和Vuex的模板或者手动勾选Router和Babel都可以。创建完成后先把默认启动跑起来再开始改页面。不要一上来就复制别人一整个项目那样出了问题根本不知道从哪查。如果你要用Vite也可以npm create vitelatest hospital-web -- --template vue用Vite的话启动更快但要注意生态兼容性。毕设我通常更推荐Vue CLI因为它老项目兼容性好找到的参考资料也更多。4.2 路由和权限控制前端路由配置很简单核心是定义路径和组件的关系const routes [ { path: /, component: Home }, { path: /doctor/:id, component: DoctorDetail }, { path: /admin, component: AdminLayout, meta: { role: ADMIN } }, { path: /appointment/list, component: MyAppointment, meta: { role: PATIENT } } ];权限控制在路由守卫里做判断本地存储的登录用户角色是否满足路由 meta 里 requirements 的要求不满足就重定向到首页。这里顺手讲一下前端“路由权限”和后端“接口权限”的区别。前端路由守卫是提升用户体验用的比如用户没登录点开个人中心时直接跳登录页真正安全的后端防护才能拦住恶意请求。因此后端接口在Controller上也要加角色校验不能只依赖前端控制。4.3 组件和插槽在页面里的实际应用Vue组件化在预约挂号系统里最常见的例子就是“医生卡片”和“时段列表”。比如科室列表页面每个医生展示的卡片包括头像、姓名、职称、擅长和“预约挂号”按钮这部分适合封装成一个DoctorCard组件。关于Vue插槽很多同学学的时候觉得抽象其实它就是一个组件内部留出来的占位区。比如医院首页有个公告栏组件公告内容的样式在不同页面里可能完全不同这时候插槽就比传一堆富文本更优雅template div classnotice-panel h3公告栏/h3 slot/slot /div /template在使用时每个页面往里面丢不同的公告内容即可。答辩时如果能主动提一下“这里使用插槽是为了让组件在不同页面有不同内容展现”老师会认为你确实不是只会复制代码。4.4 axios封装和请求流程前端对接后端的姿势我见过最乱的情况是每个页面都写了一遍axios请求没有统一的URL管理也没有统一的错误处理。建议在项目里建一个 request.js 来做封装import axios from axios; import router from /router; const service axios.create({ baseURL: /api, timeout: 10000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }); service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { if (res.code 401) { router.push(/login); } return Promise.reject(new Error(res.message)); } return res.data; }, error { return Promise.reject(error); } );这样写的好处是接口调用时只需要关心业务数据比如const scheduleList await request.get(/schedule/list, { params: { doctorId } });很多人在做“预约成功之后页面状态不更新”这种问题其实多半是请求完忘了重新获取数据或者使用了刷新整个页面这种不太优雅的方式。正确的做法是在预约成功回调里更新当前页面的响应式数据例如重新请求一次排班接口让剩余号源数字立刻变化。另外前后端联调注意跨域配置。Spring Boot 后端可以加一个 CorsFilter或者在前端配置代理我比较推荐前端的 vite.config.js / vue.config.js 里把 /api 代理到后端地址devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端代码里就统一用 /api 开头不会写出乱七八糟的http://ip:8080硬编码地址。5. 文档、代码讲解和一条龙定制怎么准备才经得起答辩很多学生把精力都花在写代码上文档和代码讲解拖到最后一天才赶。实际上答辩时论文和讲解占的比重往往不低。这里就重点讲交付物怎么弄。5.1 毕业设计文档到底要写什么毕设文档一般包含摘要、绪论、需求分析、系统设计、数据库设计、系统实现、系统测试、总结。针对预约挂号系统有几块特别值得认真写。需求分析部分把三类角色分别要做什么写清楚每个功能配上用例图不要只复制网上模板。系统设计部分要画出系统架构图、功能结构图。数据库设计部分重点写表结构和ER图最好把每个字段的含义说明白例如“status 字段 0 表示待就诊1 表示已完成”。系统测试部分不要只写“测试通过”可以整理一个测试表格包含测试用例编号、测试步骤、预期结果、实际结果。比如预约重复时系统提示“您已预约过该时段”这就是一个完整的测试用例。文档里还可以加上技术选型理由比如“后端使用Spring Boot原因是简化了配置内置Tomcat适合快速开发”“前端使用Vue原因是组件化开发便于维护”。这些内容虽然基础但只要你写出自己的理解比交一篇千篇一律的模板强很多。5.2 代码讲解时怎么讲才能不啰嗦又有条理代码讲解通常有10到15分钟时间建议主线就是“从用户操作到数据落库”讲一遍。你可以打开系统从患者注册登录开始操作然后说“患者提交挂号后前端会通过axios把表单数据发送给后端的预约ControllerController再转发给ServiceService里先校验排班状态、再执行更新号源、最后插入预约记录”。这条链路讲下来就把前端交互、后端接口、数据库表都串起来了老师听起来会觉得你对整个项目有全局认知。讲到具体代码时不要念代码而是挑核心讲。比如预约接口里防止超卖的更新语句统一返回格式全局异常处理这三个点足够让老师觉得你有工程意识。5.3 一条龙定制中的加需求清单我经常接到类似“能不能加一个短信提醒”“能不能支持微信支付”“能不能导出Excel报表”这样的需求。作为毕设加需求的前提是不破坏原有主体同时能讲清实现方式。比较常见的增改方向有需求实现方向备注医生停诊排班表加 status停诊时批量短信通知已预约患者短信可用阿里云短信 SDK也可以留接口文档说明就诊记录新增 diagnosis 表关联预约记录医生端填写就诊记录数据统计后端提供统计接口前端用 ECharts 画柱状图和饼图管理员端首页展示通知公告新增 notice 表后台维护公告患者首页展示支付功能模拟支付页面代替真实支付避免签约第三方支付商户的麻烦每加一个功能都要更新对应的文档和数据库设计。不要项目代码改了一轮文档却停留在旧的系统结构这是答辩时最明显的硬伤。6. 我踩过的坑从Spring Boot版本到前端打包整理成几条可复用的经验这些年我处理过太多“哪里都正常但就是跑不起来”的情况。这些问题有些是环境原因有些是代码规范问题。挑几条最典型的说。6.1 Java版本和Spring Boot版本不匹配有人用 Spring Boot 2.7 配了 Java 17结果启动时控制台疯狂报错。Spring Boot 2.x 官方推荐 Java 8 或 11所以不要觉得 Java 版本越新越合适。如果你本机只有 JDK 17要么装一个 JDK 8 并切换环境变量要么直接用 Spring Boot 3.x 并换用 mybatis-plus-spring-boot3-starter。这个坑非常基础但每年都有学生在答辩前一周卡住。6.2 事务注解失效在预约服务类上加了 Transactional但方法没有走 Spring 代理比如同类里另一个方法调用它事务是不生效的。还有的人把 Transactional 加在 Controller 方法上虽然不是不行但不够规范。最好将事务边界放在 Service 层并且通过 Controller 调用 Service 的公开方法。6.3 前后端字段命名不一致后端返回给前端的字段是 createTime前端却用 create_time 去取结果页面一直显示空白控制台也没报错。这种问题最坑因为不涉及语法错误。解决方法是前后端约好统一使用驼峰命名或者在后端配置全局下划线转驼峰映射。Spring Boot 里只需要在 application.yml 加mybatis-plus: configuration: map-underscore-to-camel-case: true绝大多数情况下就能避免这类问题。6.4 Vite 打包后刷新404前端打包成 dist放到服务器或者 Nginx 之后刷新页面出现404这是典型的 history 路由模式问题。解决方法是部署时将前端路由改成 hash 模式或者在 Nginx 配置 try_files 指令让所有请求都进入 index.html。毕设部署演示时我通常推荐直接改 hash 模式修改最省事也不影响演示效果。6.5 时间字段显示多 8 小时数据库里存的是正确时间前端却显示成了 8 小时后的时间大多数是时区导致的问题。后端连接数据库的 URL 里加上 serverTimezoneAsia/Shanghai前端再处理一下日期显示的时区基本就能解决。如果在代码里直接使用 LocalDateTimeJackson 默认的序列化格式也要配置一下不然可能是一串数字而不是可读的日期字符串。6.6 提交前最后检查一遍的清单每次要交付源码之前我会自己按这个顺序走一遍配置数据库启动后端服务启动前端项目注册一个患者账号预约一次取消一次再切换管理员账号查看预约记录最后让医生账号把排班停诊患者端确认无法预约。这套流程其实就模拟了核心业务闭环走通了项目交付基本就稳了。我自己在做这个系统的过程中最深的一点感受是毕设项目不需要有多炫酷的技术但它需要像一个完整的产品该有的状态变化、该有的边界判断、该有的交付物一样都不缺。做到这一步不管是自己用还是作为整套源码加文档分享给同学心里都有底气。这套流程也成了我后来做所有同类系统的一条固定检查线。