news 2026/10/8 2:10:59

SpringBoot医院预约挂号系统开发实战:架构设计、并发防超卖与部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot医院预约挂号系统开发实战:架构设计、并发防超卖与部署

1. 医院预约痛点与这套SpringBoot系统实际要解决的问题

先说个我当年的感受:为什么计算机毕业设计题目里,医疗类项目永远是大热门?因为围绕它是能"稀里哗啦"列出一堆需求的——预约挂号、医生排班、电子病历、药品管理、收费结算、统计分析……功能点足够多,逻辑足够复杂,做出来也容易向评委展示"你的系统确实在解决现实问题"。

但问题恰恰出在这里。我见过太多做医疗项目的同学,一上来就朝"大而全"这个方向冲,结果到了中期答辩的时候,排班逻辑还没想明白、预约并发压根没考虑,项目直接烂尾。所以如果你正在考虑做这个题目,第一件事不是打开IDEA写登录注册,而是先想明白一件事:你的系统到底要在哪个环节上真正解决用户的痛点。

拿医院场景来说,痛点其实非常集中。线下挂号的体验大家都懂:早上五六点到窗口排队、专家号放出来几分钟就没了、挂错科室还得来回折腾。一个互联网便民服务系统,核心价值应该落在"把线下的不确定变成线上的确定"上——患者打开手机就能看到今天的号源情况、选定时间段、在线支付、到点直接去诊室;医生可以提前维护自己的排班,避免像以前那样靠护士手工登记。预约管理平台的本质是一个"确定性调度系统",而SpringBoot恰好是搭建这种系统的很顺手的技术底座。

这个平台适合谁来参考?我建议这几类人重点关注:第一,正在做SpringBoot毕业设计、需要一套完整可复现架构的同学;第二,想从"会写CRUD"过渡到"会设计业务系统"的Java初学者;第三,哪怕你不是做医疗方向,这个项目里的排班模型、预约防并发、角色权限控制,抽出来放在会议室预约、场馆预订、培训报名这些场景里完全可以复用。

我当时的方案做的是三层角色体系:患者端(微信小程序风格的前端页面)、医生端(排班和接诊记录管理)、管理员端(基础数据配置和运营统计),前后端分离,后端就是SpringBoot单体应用加MySQL。今天这篇文章会把整条链路拆开讲清楚,从架构选型到数据库设计,再到并发控制和部署复盘,你完全可以照着这份思路复现一版属于自己的系统。

2. 技术选型逻辑与项目分层结构,先别急着写代码

技术选型这个环节,很多同学上来就是SpringBoot选最新版、前端随便套一个模板、数据库表想到哪写到哪。等到开发中期,问题集中爆发:某个依赖和SpringBoot版本不兼容、前端请求跨域、MyBatis-Plus和分页插件打架……这些都是"选型阶段欠的债,开发阶段还"。

2.1 SpringBoot版本怎么选,为什么我用2.7.x而不是3.x

打开Spring Initializr,默认给的SpringBoot版本可能是3.x甚至更新的版本。如果你直接用它搭项目,后面大概率会遇到这些坑:3.x基于Jakarta EE,很多老教程里的javax.servlet包要全改成jakarta.servlet;部分第三方starter还没适配,比如某些短信SDK;MyBatis-Plus也得用专门的3.5.x以上版本才能兼容。

所以我的建议非常直接:毕业设计项目,用SpringBoot 2.7.x系列,别追新。我自己用的是2.7.18,这个版本稳定性好,网上资料最齐全,遇到的任何报错几乎都能搜到解决方案。不是说你不能用3.x,而是在毕设有限的周期里,把精力花在业务逻辑上远比花在环境适配上有价值。

配套的技术栈我列个表出来,都是经过实测稳妥的组合:

组件选型用途说明版本建议
后端框架SpringBoot主业务接口开发2.7.18
持久层框架MyBatis-Plus单表CRUD不用写SQL,复杂查询手写XML3.5.5
数据库MySQL核心业务数据存储5.7或8.0均可
权限框架Spring Security + JWT登录认证与接口鉴权Spring Security 5.7
接口文档Knife4j自动生成在线接口文档,答辩演示利器4.x
前端框架Vue3 + Element Plus后台管理界面开发Vue 3.4 + Element Plus 2.x
构建工具Maven项目依赖管理和打包3.8.x

2.2 后端分层:Controller到底该不该写业务逻辑

我见过不少同学的代码风格是"所有逻辑全堆在Controller里"——查数据、判断状态、写返回结果全部混在一起,一个方法上百行。这种代码跑起来没问题,但答辩的时候面试老师翻几下源码就皱眉头了。

标准的做法是四层结构:

  • Controller层:只做参数接收和数据响应,把请求转发给Service层
  • Service层:业务逻辑的核心位置,比如排班冲突检测、预约名额校验都在这里做
  • Mapper层(DAO层):数据库操作,继承BaseMapper,复杂查询用XML
  • Entity层:数据库表对应的实体对象

我项目里的包结构大致长这样,你可以直接参考:

com.medical.platform ├── controller // 接口入口,如 PatientController, DoctorController ├── service // 业务接口 + impl实现类 ├── mapper // MyBatis-Plus 数据访问 ├── entity // 数据库实体映射 ├── dto / vo // 前端接收参数 / 前端展示数据 ├── config // 跨域配置、Security配置、Knife4j配置 ├── utils // JWT工具、日期工具、统一返回封装 └── exception // 全局异常处理器

我把dto和vo单独拆开的体会是:千万别把前端的请求参数直接绑定到Entity上,否则你会被字段校验搞得焦头烂额,还会造成不必要的数据库字段暴露。举个例子,用户注册时前端传的是username、password,但数据库用户表里还有role、createTime,直接绑定就会出现越权赋值的安全漏洞。

2.3 前后端联调时最多踩的跨域与统一返回结构

前后端分离开发的时候,前端的端口通常是5173或者8080,后端跑在8081,一旦前端发起AJAX请求就会触发跨域问题。解决办法在SpringBoot里加一个WebMvcConfigurer配置,重写addCorsMappings方法,允许指定路径跨域放行就行。这个代码非常简单,网上一搜一大堆,但我建议你在config包里专门建一个CorsConfig类,而不是用@CrossOrigin注解一个接口一个接口地加,因为那样太零散了。

和跨域配套的还有一个很关键的设计——统一返回结构。我定义了一个Result类,包含三个字段:code(状态码)、message(提示信息)、data(业务数据)。所有接口返回的都统一是这个结构。这样做的好处是在前端可以用同一套拦截逻辑处理响应,比如code不等于200就全局弹错误提示,不用每个接口单独处理。这个细节在做前端联调的时候体验差距非常大。

3. 核心业务模块拆解:预约、排班、挂号三条主线

医疗系统的业务复杂度比普通管理系统高一个档次,因为涉及到状态流转。我开发时发现,其实整个系统就是围绕三条主线展开的:管理端配置基础数据、医生端维护排班、患者端预约挂号。

3.1 管理端:科室、医生、号源三者的绑定机制

很多同学做这个系统时会忽略一个关键设计:科室、医生、号源这三者不是三个孤立表,而是一套层层绑定的关系。内科下面挂5个医生,医生A在2025年6月10日上午有排班,这个排班决定了他当天上午有几个号源、每个号源几点到几点。

我先说管理端的核心功能。管理员登录后主要负责维护基础数据:科室管理(新增科室、科室简介)、医生管理(录入医生信息,绑定所属科室和职称)、排班管理(查看全院排班情况,必要时人工干预)。这些功能本质上是后面所有预约流程的数据源头——没有科室和医生数据,患者端就什么都看不到。

还有一点比较容易遗漏:医生的账号往往也是管理员在医生管理模块里创建的。我做的方案是管理员录入医生信息时,系统自动生成初始账号(手机号)和初始密码,医生首次登录后强制修改密码。这样免去了手动到数据库里insert记录的尴尬。

3.2 医生端:排班自动生成规则与接诊记录闭环

医生端的核心功能是排班维护和患者接诊记录。这里我踩过一个大坑:最初我把排班做成了医生手动选择"某天某个时间段是否出诊",然后数据存成一个"开启排班状态"的布尔字段。结果测试的时候发现,预约完成后医生无法区分号源是否已经有人约走,因为排班只记录了"当天出不出诊",没记录"筛出诊的细分时段里还剩几个号"。

后来我把排班模型改成了两个层次:排班日期表(doctor_schedule)和号源表(schedule_slot)。排班日期表记录"某医生在2025-06-10上午出诊,总号源20个",号源表根据规则自动生成20条具体记录,每条记录一个时间段,状态为"可预约/已被预约"。这样患者在预约时,实际是锁定一条具体的号源,而不是给排班日期表"减1",并发控制才能真正落实。

医生的接诊流程我建议做一个"今日待诊患者列表"。患者预约成功后,医生端能按日期看到当天预约了他的哪些患者、就诊序号是多少、病历主诉是什么。接诊完成后可以勾选"就诊完成"。这个闭环虽然逻辑简单,但是答辩时的加分项,因为它体现出了"平台不仅仅是预约工具,还能辅助医生进行日常接诊管理"。

3.3 患者端:从选科室到拿号,整个流程的状态变化

患者端的流程设计是整个系统的门面。我做的是一个仿小程序的页面风格,核心路径是这样的:

  1. 患者注册/登录,维护个人基本信息
  2. 首页按科室分类浏览科室列表
  3. 进入科室详情,查看科室下所有医生
  4. 进入医生详情,选择要预约的日期,系统自动列出该医生当天的号源时间段
  5. 选中一个"可预约"的时间段,确认预约
  6. 预约成功后,在"我的预约"里查看预约记录和就诊二维码(实际可以直接显示预约状态)

这里必须想清楚预约单的状态流转设计。我的方案用了五个状态:待就诊、已完成、已取消、爽约、已过期。状态之间的流转规则很明确:

  • 待就诊的状态下,患者可以取消预约,状态变为已取消,同时释放号源供其他患者预约
  • 医生点击就诊完成,待就诊变为已完成
  • 预约日期已过但患者没有到诊,待就诊自动或定时变为爽约(或者已过期,二者可以二选一,不必都保留)

这些状态管理,我发现用后端的一个StatusEnum配合状态流转Service来做最清晰,不要用散落在各文件里的if-else判断,否则后期加需求的时候你根本不知道哪里需要改。

3.4 管理员统计面板与运营数据展示

统计功能我不建议你一开始就花大力气做炫酷图表。先保证几个核心指标的数据能查出来再谈图表——今日预约总数、各科室预约量排行、医生接诊量统计、患者爽约率。我当时的时间不够,没有在前端集成ECharts图表库,而是做了一个简单的数字卡片统计页,展示今日预约数、累计注册患者数、在线医生数。答辩的时候老师基本不会纠结图表是否炫酷,更看重你的统计口径是否清晰。

4. 数据库表设计:核心业务表怎么用最小集合支撑整个系统

数据库设计这个环节,我看了很多同学交上来的设计文档,最大的问题就是表多到离谱却互相不用。我最初的数据库设计文档里甚至写过20多张表,后来真正落地的只有10张核心表,其他的纯属画饼。这里的经验是:表的数量要跟着业务流程走,不跟着想象力走。

4.1 核心表结构与字段设计要点

我列出10张真正贯穿整个系统的表,这已经是能够支撑完整业务闭环的最小集合:

表名核心字段职责说明
userid, username, password, real_name, phone, role, avatar统一用户表,通过role区分患者/医生/管理员,避免建三张用户表
departmentid, dept_name, dept_intro, status科室表,管理端维护
doctor_infoid, user_id, dept_id, title, introduction, good_at医生详情表,扩展User表中医生的专业信息
scheduleid, doctor_id, dept_id, work_date, period, total, available排班日期表,记录某天某时段的出诊和号源总量
schedule_slotid, schedule_id, start_time, end_time, status号源时间段表,具体到"几点几分到几点几分"
reservationid, patient_id, doctor_id, slot_id, schedule_id, visit_date, status, create_time预约订单表,整个系统的核心流水记录
medical_recordid, patient_id, doctor_id, diagnosis, prescription, create_time就诊记录表,医生接诊后填写
announcementid, title, content, create_time系统公告,管理端发布
feedbackid, user_id, content, status患者反馈,管理端查看
operation_logid, user_id, action, detail, create_time操作日志表,记录关键操作行为

有几个地方需要特别说明。第一个是user表用角色区分三种人群。为什么不分三张表?因为Spring Security做登录认证时,只需要一张用户表查账号密码,如果拆成三张表,登录逻辑就得先判断"这个账号是医生还是患者",再决定去哪个表里查,非常别扭。统一用户表加上role字段,登录时一次性查出来,后续的权限控制交给Spring Security的角色判断机制,开发效率高很多。

第二个是schedule_slot号源表的设计。有人可能会问,我直接用schedule表里一个available字段做减法不就得了,为什么还要单独建一张号源表?因为只有单独把时间细分到具体的号源记录,"防重复预约"才能从我后面讲的数据库行锁来入手。如果只是做字段减法,你得自己写"更新前先查一次,判断大于0再更新"的逻辑,这一步在高并发下极容易超卖。

4.2 设计索引和约束时容易被忽视的细节

光把表建出来远远不够,索引设计才是数据库性能的分水岭。我做压测的时候发现预约提交特别慢,用EXPLAIN看了执行计划,发现每次预约时查询reservation表的全表扫描要几毫秒,看似不多,但高并发场景会被放大成为瓶颈。接下来的处理很简单:给外键字段加上普通索引即可。

需要加索引的字段组合推荐:reservation表的patient_id + visit_date组合索引(用于查询患者某日期范围的预约记录)、slot_id唯一索引(从数据库层防止同一个号源被重复预约,这是最后一道安全锁)、schedule_slot表的schedule_id索引(查询某排班下的所有号源时用)、schedule表的doctor_id + work_date索引(医生排班查询用)。

我重点说下slot_id唯一索引的意义:虽然我在Service层已经通过"状态更新为已预约影响行数"来判断是否并发冲突,但数据库的唯一索引是兜底方案。哪怕代码逻辑有漏洞、两条请求同时通过了Service层判断,数据库层的唯一约束也会挡下第二条写入,直接报Duplicate Entry异常。日志记录下这个异常,服务不会崩溃,用户会看到"该号源已被约走"的友好提示,这三层防护配合在一起,才是稳妥的方案。

4.3 统一用户表vs拆分三张表的最终结论

开发时我一直在这两种方案间犹豫,最终结论上面也提到了:统一用户表。但要注意,统一user表之后,"医生信息"并不应该全部塞到user表里。医生的职称、擅长领域、简介这些专业属性,应该单独放doctor_info表,通过user_id关联。同理,患者的身份证号、病历号这类信息如果有特殊需求,建议也单独拆表,而不是把用户表变成一个什么都装的大仓库。这样做的好处是User信息是通用的,但医疗专业字段可以按角色定制,后续哪怕加一个护理人员角色,也不需要动user表的基本结构。

5. 预约秒级响应的并发控制:防超卖才是系统的灵魂

如果让我用一个词概括这个项目里最有技术含量的部分,我会毫不犹豫地说:并发控制。原因很简单,几乎所有评委老师看到"预约挂号系统"这个题目时,第一个想到的提问就是:同一个医生最后一个号,两个患者同时提交预约,你系统怎么保证不超卖?

5.1 乐观锁思路:update语句自带判断更新行数

我的方案结合实际做了折中处理。先说第一层——数据库行锁乐观方案。每次预约的时候,不是先查号源是否可预约,再执行插入,而是直接执行一条带条件的更新SQL:

UPDATE schedule_slot SET status = 1 WHERE id = #{slotId} AND status = 0;

这条SQL的妙处在于它同时做了两件事:判断当前状态是否为"可预约",以及将状态改为"已被预约"。MySQL默认行级锁机制会保证同一条记录在同一时刻只有一个事务能更新成功,所以两个患者同时提交时,只有一个能拿到"更新了1行"的结果(即AffectedRows = 1),另一个人拿到的是"AffectedRows = 0"。这就是数据库层面的防超卖核心逻辑。

拿到更新后的AffectedRows之后,服务端判断:如果AffectedRows == 1,说明号源已经锁定成功,继续执行创建预约记录的插入操作;如果等于0,直接返回"该号源已被预约,请选择其他时间段"。

5.2 为什么第一版会被超卖:AffectedRows被忽略的坑

哪怕方案已经这么"标准",我第一版代码里还是出现了超卖问题。问题出在哪?我把AffectedRows的判断放在了if里,但没有把更新逻辑和插入逻辑放在同一个事务中。于是发生了这样的情况:线程A执行更新,AffectedRows=1;线程B也执行更新,AffectedRows=0,B被拦截。这看起来没问题,但A在更新成功之后、插入预约记录之前,事务还没提交,B的更新虽然AffectedRows=0但对A没有任何阻塞。如果A的插入失败了,事务回滚,但号源状态已经改了……不对,回滚会把更新也回滚掉,所以问题不在这里。

实际出问题的地方更隐蔽:我用的是MyBatis-Plus的updateById,它默认只根据主键更新字段,不会带AND status = 0这个条件。所以两个线程同时调updateById,可能都会成功执行(一个把状态改成1,另一个把状态改成1,覆盖写),AffectedRows都是1,结果就是超卖。后来我改成写XML自定义SQL,再加上@Transactional事务注解才算稳定。这个坑提醒了我一个关键原则:并发控制的SQL必须显式写出条件,不要依赖ORM框架的默认行为。

5.3 事务边界设计与GlobelException兜底

事务边界的正确姿势是:开启一个事务,事务里包含"锁定号源"和"创建预约记录"两步。任何一步失败,整个事务回滚,号源状态恢复成可预约,用户得到提示。我在ReservationService.createOrder()这个方法上加了@Transactional(rollbackFor = Exception.class),rollbackFor指定为Exception.class是因为Spring默认只回滚RuntimeException,如果你在方法里抛的是自定义的业务异常(我习惯继承RuntimeException),默认也能回滚,所以这个设置更多是防御性的。

还有一个必须预告的点:数据库唯一索引会兜住最后一层极限并发。我在reservation表给slot_id字段(实际是slot_id + visit_date)设置了唯一索引。哪怕Service层逻辑出错、两个事务同时通过了AffectedRows判断,第二个插入会直接抛出DuplicateKeyException。我会在全局异常处理器里捕获这个异常,转成用户友好的提示。

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(DuplicateKeyException.class) public Result handleDuplicateKeyException(DuplicateKeyException e) { return Result.error("该时间段已被预约,请选择其他时段"); } }

全局异常处理不只是为了这个场景,它还能统一处理参数校验异常、业务异常、系统异常,避免用户直接看到一坨500报错堆栈。答辩演示时,如果你能主动演示"两个浏览器同时抢最后一个号,一个成功一个提示繁忙"这个场景,项目说服力会强很多。

6. 权限控制与JWT登录认证:三种角色怎么做到各看各的数据

身份认证和权限控制是第二个容易被答辩老师追问的点。我先说明一下Spring Security在这里的角色——做登录认证和接口访问控制,而JWT负责无状态的会话保持。

6.1 JWT登录认证的完整链路

流程大概是这样:用户输入账号密码,请求打到/api/auth/login,后端用authenticationManager.authenticate()做校验,成功之后用JWT工具类生成一个token返回给前端。前端把token存到localStorage,之后每次请求都在Authorization头里带上Bearer token。

后端这边,我写了一个JwtAuthenticationFilter,继承OncePerRequestFilter,拦截所有请求,解析token、校验签名和过期时间、把用户名取出来加载用户的权限信息,放到SecurityContext里。这样后面的接口鉴权就能用了。

这个链路如果纯靠背代码,大概率会在细节上翻车。我推荐先理解三个核心问题:

  1. JWT里存什么?只存用户ID和用户名,不要存密码等敏感信息。token本身只是携带信息的签名凭证,不是保险箱。
  2. token过期了怎么办?最简单的方案是过期时间设置长一些(比如7天),前端每次请求发现401就跳转登录页。复杂一点的方案是刷新的refresh_token,但毕设项目里没有太大必要。
  3. Spring Security的密码加密必须用BCrypt。直接用MD5存储密码,答辩时老师会觉得你不专业。Spring Security自带BCryptPasswordEncoder,每次加密的密文都不相同,因为会掺入随机盐,安全等级完全不同。

6.2 角色权限控制:@PreAuthorize的正确用法

我用的是注解式权限控制。在Controller的方法上加@PreAuthorize("hasRole('ADMIN')"),意思是只有ADMIN角色才能调用这个接口。这个方法非常直观,前端路由也能做一层角色控制,但后端必须留一道闸门。

在配置类里,我开启@EnableGlobalMethodSecurity(prePostEnabled = true),然后在接口上做声明。有三条最常用的规则:

  • @PreAuthorize("hasRole('ADMIN')"):管理员专属接口,比如维护科室
  • @PreAuthorize("hasAnyRole('ADMIN','DOCTOR')"):管理员和医生都能调的接口,比如排班管理
  • @PreAuthorize("hasRole('PATIENT')"):患者接口,比如创建预约

除了角色维度,数据维度也需要控制。比如患者登录后只能查到自己创建的预约记录、医生只能查到自己名下的排班,这在查询条件里直接加上登录者的ID即可。如果你想在注解层面实现"只能操作自己的数据",就得自己扩展Spring Security的权限判断逻辑,毕设阶段不推荐,复杂度高很多,在Service层直接判断就足够了。

6.3 密码加密和登录校验的常见翻车现场

这一块我要郑重提醒一下,当年我自己就翻过车:最初为了方便,用户注册时密码直接用明文存数据库,想着毕设而已。后来被同学提醒了一句:"老师会看数据库的",我才意识到这简直是送分题变送命题。换成BCryptPasswordEncoder之后简洁又安全,注册的时候调用encode(),登录的时候调用matches(),代码只要改两行,难度几乎为零,体验是完全不同层面的。

另一个常见翻车点是前端密码传输。如果用户在前端输入密码后用HTTP明文传到后端,那就算后端做了BCrypt也白搭——抓包就能抓到真实密码。实际的正式系统要用HTTPS,开发阶段没法申请证书可以先在局域网里跑,但至少要有意识:浏览器控制台里看到的payload密码要尽量脱敏。可以在前端先把密码做一次SHA256摘要,后端再对摘要做BCrypt。这个双保险虽然毕设可能用不到,但答辩你一说出来,档次就上去了。

7. 前端页面结构、交互链路与接口对接

说完后端,我来快速过一遍前端的搭建思路。这也是很多同学头疼的点——前后端分离的开发模式,看着简单,真把页面串起来总是问题不断。

7.1 页面路由设计与角色视图分离

我的前端用的Vue3 + Element Plus + Vue Router + Pinia。整体按角色拆分为三个入口:

  • 患者端路由:首页(科室列表)、医生列表页、医生详情页(含号源选择)、个人中心(我的预约/我的病历/个人信息)
  • 医生端路由:工作台(今日待诊患者列表)、排班管理(查看排班/维护排班)、个人信息
  • 管理端路由:科室管理、医生管理、排班总览、公告管理、反馈管理、统计面板

前端的路由守卫在登录后判断用户角色,然后重定向到对应首页。比如患者登录后默认跳转/patient/home,医生登录后跳转/doctor/workbench。这个设计一个是用户体验好,另一个是后端接口只做权限控制的策略下,前端也无脑暴露不存在的入口。

关于前端代码,一个能传递给新手的建议是:把接口请求封装到一个统一模块里。我配置了axios的baseURL和请求拦截器,每次请求自动带上token,响应统一做code != 200的错误提示。这个封装做完后,写页面组件时会轻松很多。

7.2 最影响开发效率的两个联调细节

第一个是Mock数据。前端同学在等后端接口时,用mock数据先渲染页面是常态。但我的建议是,前后端同时开发时,尽量让前端先按约定的JSON结构做假数据,接口写好后再替换,这个模式能帮双方把接口字段定义提前对齐,减少后期返工。

第二个是接口命名规范。后端Controller的路径前缀要统一,我采用/api/xxx作为前缀,再从业务域细分:/api/auth/**(登录注册)、/api/patient/**(患者端)、/api/doctor/**(医生端)、/api/admin/**(管理端)。再加上Knife4j自动生成的接口文档,前端完全不需要反复问后端"这个接口传什么参数",自己查文档就能对接。这串流程在答辩的时候给老师演示一遍"在线文档自动生成",至少是半个加分项。

7.3 状态展示与用户提示的细节设计

预约流程里的状态展示是最容易做出体验感的细节。我的做法是:预约按钮的可用性直接由号源状态的status决定。status=0显示"可预约",按钮可点;status=1显示"已约满",按钮置灰。用户点击预约成功后,原按钮立刻变置灰并显示"我已预约",这需要前端在拿到成功后刷新号源列表数据。

另外要把"等待响应"时用户的操作心态考虑进去。提交预约按钮在请求发出后要加loading状态,防止用户连点两次导致重复提交(后端虽然会拦截,但前端主动防止体验更好)。这些都是小细节,但整套体验下来和"能跑就行"的差距就拉开了。

8. 从Startup到答辩演示,一套稳过验收的清单

8.1 后端打包部署的具体操作

后端项目开发完成后,我用Maven打包成jar包在服务器上用java -jar方式跑的。重点说几个打包过程中经常遇到的问题:

第一,application.yml里数据库连接配置一定要用环境变量或者写死成服务器的地址,不要用localhost。我最初本地联调时全用localhost,部署到服务器后忘记改,结果接口一连就报连接拒绝。用环境变量的话,打包时不用改代码,启动命令里带--spring.datasource.url=xxx覆盖即可。

第二,静态资源(前端打包后的dist文件)可以单独部署在Nginx上,也可以直接把前端的dist目录里的文件拷贝到后端resources/static目录下,统一由SpringBoot提供访问。毕设答辩、演示这种场景,前后端一起打包成单jar包其实是最省事的方案——一启动就是一个完整的系统,不用扯Nginx配置。当然,如果你要展示后端能力,还是Nginx加jar包分开部署更专业。两种方案我分别踩过,各有优劣,关键看你的演示环境。

第三,如果是演示环境只有一台电脑,直接mvn clean package -DskipTests打包,然后java -jar medical-platform.jar跑起来,浏览器访问http://localhost:8081即可。首次启动前建议执行一个初始化SQL脚本,把管理员账号(admin/admin123)、测试医生账号、测试患者账号都提前insert好,演示时直接登录即可。

8.2 功能测试的关键用例,特别是并发场景怎么演示

测试报告是毕设文档里必须交的,但很多同学到最后都是随便写写。我给一份实际有价值的验收用例清单,覆盖核心业务场景:

测试项操作步骤预期结果
手机号注册前端注册页输入未注册手机号+密码注册成功返回token,自动登录
重复手机号注册再次注册相同手机号提示"该手机号已注册"
科室列表展示患者登录后进入首页按科室卡片展示所有科室
医生详情页排班展示点击科室下某医生展示该医生未来7天的排班和今日号源余量
正常预约选择一个可预约号源,确认预约预约成功,号源变已约满,我的预约里出现记录
重复预约同一号源用两个账号先后预约同一时段前者成功,后者提示"该时段已被约"
取消预约在待就诊状态点取消状态变已取消,同时号源释放为可预约
医生查看今日待诊医生登录进入工作台展示今天预约该医生的患者列表
医生填写就诊记录点击某一患者"完成接诊"预约状态变为已完成,生成一条病历记录
权限控制患者token访问管理员接口返回403无权访问

并发场景的演示,正常演示环境一般没有特别大的流量压测工具,我的实操经验是开两个浏览器窗口,用两个不同的患者账号,同时对一个号源发起预约请求。但纯手工操作很难做到"完全同时"——实际可以写一个简单的JMeter脚本或者Postman集合,同时发两个请求。后端日志里能看到一个成功一个失败的记录,把这个日志截图放进测试报告里,非常有说服力。

8.3 答辩时最容易被打断的3个问题,提前做好准备

答辩老师见过的毕业设计千篇一律,每个人都会做一个商城、一个管理系统、一个预约平台。为了不被问穿,提前把下面三个问题准备好:

问题一:为什么你的系统需要预约号源表,直接在排班表里扣库存不行吗?

答:设计号源表是为了保证时段级的精确控制和并发安全。如果只是排班表里做一个total和available的减法,两个用户同时下单时,大概率出现读取到同一个available值,然后都更新成available-1,超卖难以避免。每个号源对应一条数据库记录,每个预约请求对应一条UPDATE ... WHERE status = 0语句,数据库行锁天然挡住了冲突。这个回答能同时展示你对数据库模型和并发控制的理解。

问题二:你的JWT放在header里,如何防止被窃取?

答:项目实际部署在局域网内,风险相对可控。同时我做了两层保障:第一,前端用axios拦截器统一在Authorization头携带token;第二,后端对异常JWT在全局异常处理器统一拦截返回401。生产环境进一步防范的话,可以使用HTTPS传输(防止中间人抓包)、设置合理的token过期时间(降低被盗用后的有效期)、前端不把token存localStorage而是存内存(避免XSS脚本读取)。

问题三:如果医院有一百个医生同时放号,你的系统会不会崩?

答:单体SpringBoot应用在这个规模下完全没问题。100个医生并发放号本质是100条不同记录的更新,MySQL行级锁天然支持这种场景。如果到了几千家医院、每天几十万预约的体量,当前架构肯定要演进为微服务、消息队列削峰、Redis缓存。但作为毕业设计,我会强调"单体架构已经覆盖了系统的核心业务场景和边界情况"。

这三个回答里,最重要的是把"为什么这样设计"说清楚,老师通常不会追问超出你能力范围的细节,但会通过你对自己设计逻辑的熟悉程度来评估这个毕业设计是不是你自己做的。

9. 复盘一下我做这个项目最花时间的三件事

整个项目从需求分析到答辩通过,前后花了大概7周。复盘下来,最花时间的三件事:第一,排班模型从"字段减法"改造成"号源表行级锁"的过程,前前后后折腾了将近两周,中间还推翻重来过一次;第二,前端联调时因为接口字段不一致反复返工,后来用了Knife4j文档+统一返回结构才好一些;第三,权限控制踩坑,Spring Security的过滤器链配置说复杂不复杂,但对新手来说"为什么我的接口全被拦截了""为什么放行了还是不能访问"这类问题特别消耗时间。

如果你时间紧,我建议把重心放在这个顺序上:先把登录、排班、预约、状态流转这条核心链路跑通(这是系统的骨架),再补医生接诊、病历记录(这是完整的业务闭环),最后才是公告、反馈、统计这些锦上添花的功能。别一上来就把公告管理、角色权限这些外围功能全部搭好,然后发现核心预约流程还有bug,那种返工是最痛苦的。

还有一个小技巧想分享给各位:项目里的每一个核心业务模块,都建议单独写一个Markdown文档记录设计思路。比如排班模块为什么要用号源表、预约状态机的流转规则、并发控制的三层防线,记录下来之后,最后写毕业设计文档时基本可以整段迁移,根本不愁字数不够。而且答辩前复习时拿这个文档临时扫一遍,心里会踏实很多。

医疗系统这个题目,无论做预约、挂号还是便民服务,底层的核心逻辑都是"资源调度和状态管理"。把排班看成一个资源池,把预约看成资源锁定操作,把就诊看成状态流转的终点——想明白了这一层,你收获的就不仅是一个数据库CRUD系统,而是一套可以迁移到任意"预订类"业务场景的系统设计能力。这个收获,在后续无论是工作中还是在别的项目中,都能反复用上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 2:09:17

在通信与控制层面,VM 和 VP 的定位差异体现得最为彻底,VM 的目标是“让 PLC 工程师自己就能搞定视觉通信”,VP 的目标是“给系统集成商最大的控制权”

在通信与控制层面,VM 和 VP 的定位差异体现得最为彻底。VM 的目标是“让 PLC 工程师自己就能搞定视觉通信”,VP 的目标是“给系统集成商最大的控制权”。 VM:面向 PLC 工程师的“开箱即用”通信 VM 把通信做成了图形化配置,不需要…

作者头像 李华
网站建设 2026/10/8 2:09:16

Java版RDT 3.0教学工程:可调试的停等ARQ协议实现

简介:本资源是面向计算机网络课程学习者与教学实践者的RDT 3.0协议仿真实验包,聚焦可靠数据传输核心机制的教学理解与代码实现。压缩包共16个文件,含5个class类文件、4个Java源码文件(实现发送端/接收端逻辑)、2个txt文…

作者头像 李华
网站建设 2026/10/8 2:09:04

WebSocket全栈实践:单连接搞定文本、视频与语音即时通讯

简介:基于WebSocket的浏览器端文本、视频、语音即时通讯示例项目,适合Web开发初学者和需要快速接入实时通讯能力的工程师。资源完整提供Java后端与JavaScript前端源码,包含HTML交互页面、CSS样式以及Dockerfile等环境配置,共111个…

作者头像 李华
网站建设 2026/10/8 2:08:44

Agent-Reach:让AI Agent真正能动手的Python CLI工具

1. 为什么我要自己撸一个 Agent-Reach先说结论:Agent-Reach 是我在过去几个月里反复折腾出来的一个命令行工具,核心目标只有一个——让 AI Agent 真正能"伸手"够到外部世界。你可能已经用过不少 Agent 框架,它们能思考、能规划、能…

作者头像 李华
网站建设 2026/10/8 2:08:00

SpringCloud微服务---MybatisPlus

微服务是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。快速入门入门案例需求:基于课前资料提供的项目,实现下列功能: 新增用户功能根据id查询用户根据id批量查询用户根据id更新用户根据id删除用户1.…

作者头像 李华