资讯动态

SpringBoot+Vue+MySQL构建口腔门诊管理系统:从需求到答辩的全流程实战

发布时间:2026/10/13 21:38:16 来源:尧图企业网站定制
先说结论如果你正在准备计算机毕业设计又恰好选了医疗信息管理系统这个方向SpringBoot Vue MySQL 这套组合是我个人认为最稳妥、也最容易被答辩老师认可的技术栈。口腔医院就诊管理系统听起来好像只是“挂号 病历”两个功能拼在一起但实际上手之后才发现真正的难点几乎都集中在业务规则和边界情况上。这篇文章我会把整个项目从需求拆解、技术选型、数据库设计、核心代码实现到排错思路完整过一遍。内容是围绕我个人实际搭过的一套口腔门诊就诊管理平台来写的项目规模不大不小正好适合做本科毕业设计也能当成中小型牙科诊所的信息化改造参考。如果你没有接触过医疗类管理系统也不用紧张我会把每个关键设计的原因都解释清楚包括为什么表要这么建、为什么预约要用事务、为什么权限要分角色而不是简单地判断登录状态。看完之后哪怕你自己从零开始写也有一个非常清晰的路线。1. 项目整体设计与需求拆解1.1 口腔门诊的核心痛点在哪口腔门诊和综合医院的门诊系统有区别。综合医院往往是“大而全”科室多、医生多、检查多系统重点在分诊和检验数据流转。而口腔门诊的业务更集中主要围绕预约、复诊、治疗记录、收费这几个环节打转。我调研了本地几家口腔诊所的实际流程之后发现它们最普遍的痛点是三个第一预约全靠电话和前台手写登记医生排班一变患者到了现场才知道自己的预约被挤掉了体验很差。第二纸质病历分散存放复诊时医生要翻半天才能找到上一次的治疗记录尤其根管治疗、正畸这类持续性治疗记录不连贯会导致治疗方案衔接出问题。第三收费和诊疗记录分离月底对账要人工比对经常出现漏收、错收的情况。这个系统要解决的就是这三个问题。核心定位不是做一套“医疗机构级”的HIS系统而是做一个让小型口腔门诊能真正用起来的管理工具。1.2 角色与核心业务流程梳理我把系统用户划分成四类系统管理员、医生、护士/前台、患者。没有把患者注册做得特别复杂因为口腔门诊的患者通常到店后由前台协助建档但线上预约要求患者自己注册账号这两件事在需求层面是并行的。角色权限的划分直接影响数据库设计和后端接口设计。我用了比较稳妥的RBAC模型没有做细粒度的菜单权限而是按角色直接分配可访问的接口范围。角色核心权限典型操作系统管理员全部权限医生管理、排班管理、数据统计医生诊疗相关查看预约、填写病历、查看历史记录护士/前台业务协助患者建档、挂号收费、排班确认患者自助操作在线预约、查看本人记录整个核心业务流程是患者注册登录 → 查看医生排班 → 选择时间段预约 → 医生确认/系统自动确认 → 患者到店 → 医生填写病历 → 收费 → 完成。这个流程实际上也是数据库外键关系的蓝图后面建表时就是围绕这条主线展开的。1.3 技术选型的取舍逻辑后端用SpringBoot 2.x不用3.x。原因很简单SpringBoot 3要求JDK17部分学校机房和低配服务器跑起来吃力而且大量教学资料、网上教程都基于2.x遇到问题更容易查到解决方案。JDK用的是1.8稳定、生态成熟对毕设项目来说完全够用。前端选了Vue 2 Element UI。有人可能觉得Vue 3都这么成熟了为什么不直接上Vue 3。我的考虑是Vue 2配合Element UI组件库在管理系统这块非常成熟查任何“下拉框、表格、日期选择器”组合的问题都能直接在社区找到现成的写法。Vue 3的Composition API当然好但对毕业设计来说学习成本高而且可能把自己困在“我今天非要搞懂setup语法”的泥潭里。数据库用MySQL 5.7ORM用MyBatis-Plus。MyBatis-Plus最方便的是自带分页插件和条件构造器写简单的CRUD几乎不用手写SQL我后面会把代码拿出来讲。认证授权用JWT没有引入Spring Security那套重框架而是自己写拦截器做Token校验。这个会在第四章详细说原因。2. 系统架构与数据库设计2.1 前后端分离架构搭建项目采用标准的前后端分离结构前端跑在Nginx或者直接Node环境后端用SpringBoot内嵌的Tomcat。前后端通过JSON格式交互接口前缀统一为/api。后端包结构我按“controller → service → mapper → entity”分层没有额外加DTO/VO层因为项目规模不大加太多层反而拖慢开发速度。但Controller层不写业务逻辑这个底线我守住了所有事务性操作全部下沉到Service层。以后答辩老师问你“为什么这么分层”你也能给出合理的回答解耦、可测试、职责单一。前端目录结构也不是随手乱建的。api目录统一放axios请求封装views目录按功能模块建文件夹router目录配好路由守卫。路由守卫这个细节很重要因为前端页面必须根据登录状态和角色做访问控制不然用户可以手动输URL跳转到管理员页面。2.2 核心数据表设计与关系这是整个项目的灵魂。很多同学数据库建表随手建到写SQL和业务层时才发现关联关系理不清回过头再改表结构浪费大量时间。我按业务主线设计了7张核心表外加2张基础配置表。这里把每张表的作用和关键字段列出来直接对照着建库就行。表名用途关键字段user登录账号username, password, role, statuspatient患者信息user_id, name, gender, phone, allergy_infodentist医生信息user_id, name, title, specialty, statusschedule排班记录dentist_id, work_date, start_time, end_time, total_slots, booked_slotsappointment预约挂号patient_id, schedule_id, appt_date, appt_time, statusmedical_record病历记录appointment_id, patient_id, chief_complaint, diagnosis, treatment_planbill收费单patient_id, item_name, amount, status, pay_methoduser表是账号主体patient和dentist都通过user_id关联到user表。这样做的好处是登录认证只用关注user表而具体业务数据分散在patient和dentist里不会互相污染。schedule和appointment是预约闭环的核心。我特意在schedule表里设计了total_slots和booked_slots两个字段前者表示这个时段总共能约几个人后者表示当前已约人数。每次预约成功booked_slots加1当两者相等时不能再约。这个看似简单的设计实际上解决的是号源控制的问题比单独记录一条条预约再统计人数要高效得多而且写SQL判断余量时非常直观。2.3 表结构设计的关键细节建表时有些细节很容易踩坑我逐个说。第一金额字段一律用DECIMAL(10, 2)千万不能用FLOAT或DOUBLE。浮点数在Java和MySQL中都有精度丢失的问题涉及钱的计算必须用定点数。别问我怎么知道的我第一次写的时候用了Double后来发现0.1加0.2等于0.30000000000000004当场血压拉满。第二所有表都带上create_time和update_time字段。一方面做数据统计时能按时间范围筛选另一方面答辩和实际运维排查问题时能清楚看到数据是什么时候写入或变更的。第三status字段要有默认值并配合枚举值说明。比如appointment表里status用0表示待确认、1表示已确认、2表示已完成、3表示已取消用户表的status用0表示启用、1表示禁用。用数字存状态比用字符串好查询快且不易出现大小写不一致的问题。第四逻辑删除代替物理删除。删除排班记录时如果物理删掉这周的统计报表数据就会缺失。所以排班表、预约表、医生表都增加deleted字段MyBatis-Plus自带逻辑删除支持用起来非常方便只是一个注解的事。3. 核心功能实现与实操要点3.1 用户登录与权限控制登录模块用JWT做无状态认证。用户提交用户名密码后后端校验密码是否正确这里必须强调一点密码不能用明文存储必须用BCrypt加密。Spring Security里的BCryptPasswordEncoder可以单独引入不用引入整个Security框架。JWT生成时把用户ID和角色写进Token里过期时间我设置为24小时。为什么定24小时太短的话患者用着用着就掉线体验差太长的话有安全隐患一天一过期前端配合拦截器自动刷新这个时间长度在毕设场景里最稳妥。拦截器实现是核心。我自己写了一个HandlerInterceptor在preHandle方法里从请求头拿Token解析失败就返回401。这里有个坑拦截器会拦截所有/api/**路径包括登录接口本身所以要通过路径判断放行登录、注册等白名单接口。我用一个简单的ArrayList存放白名单路径然后contains判断不用引入复杂的权限框架代码量不多逻辑还非常清晰。角色控制的校验在拦截器里一起做。用户登录后后端接口如果需要特定角色才能访问我就在Controller方法上加自定义注解然后拦截器判断Token里的角色是否符合要求。比如添加医生的接口只有管理员能调患者登录后再怎么折腾也不可能走到那个方法里去。这一点在答辩演示时要专门提因为它是整个系统安全性的关键。3.2 预约挂号模块的实现预约的完整流程是患者先看到医生的排班列表然后选择一个时间端提交预约后端收到请求后校验这个时间段是否还有余量有则创建预约记录并扣减号源没有则直接返回“该时段已约满”。这里最危险的问题就是并发超约。两个人同时看到还剩1个号然后同时点提交如果没有并发控制两个请求都能读到booked_slots1的排班记录然后都判断还有余位最后两张预约都创建成功实际超约了。门诊可能不觉得这有什么但答辩老师最喜欢问的就是“你这个系统并发情况下会不会有问题”。解决并发超约的办法有几种我选了两种用在一个方案里一种是SQL层面的原子更新另外一种是事务隔离。第一层是更新时带上条件判断update schedule set booked_slots booked_slots 1 where id ? and booked_slots total_slots如果返回的影响行数为0说明号源已经满了直接返回预约失败。这一条SQL同时完成了扣减号和判断余量的操作天然是原子的避免了先查再更新的时间差问题。第二层是加Transactional注解包裹整个预约流程。创建预约记录和更新号源必须在同一个事务里任何一个失败都要全部回滚否则会出现预约记录生成了但号源没扣减成功或者号源扣了但预约记录没建的情况。我用了一个测试用例验证先在两个浏览器同时操作同一个号源发现没有事务和原子更新时会超约加上之后只有一条预约能成功另一条被正确拒绝。3.3 就诊记录与电子病历模块病历是整个口腔门诊系统的信息核心。一张病历记录至少要和预约记录关联这样才能追溯这次就诊对应的是哪次预约、哪个医生、哪个时间段。我设计的medical_record表里专门保存appointment_id通过预约记录再关联患者和医生形成一条完整的数据链。填写病历时无非是主诉、诊断、治疗方案、医嘱这几块内容。口腔门诊有别于综合医院的是它的治疗过程经常跨多次就诊比如根管治疗要做两三次正畸要按月复诊。我做的方案是同一患者的病历记录按就诊时间排列患者详情页中可以把历史病历列成时间线医生一点就能看到上次做什么、下次该做什么。还有一个小小的贴心设计在患者信息里增加allergy_info字段专门记录过敏史。口腔治疗中麻药、抗生素等药物过敏是真实风险这个字段在病历页面顶部高亮显示医生打开病历先看到过敏史再动治疗方案这在实际诊所场景里是有价值的不只是为了摆功能。3.4 医生排班模块排班功能是预约的前提。没有排班患者就不知道该约哪个时间段。我把排班设计成按天生成管理员进入排班页面选择医生、选择日期然后设置这个医生的出诊开始时间和结束时间再设置每个时段可预约的人数。从业务逻辑上来讲排班有两点要注意。第一同一医生同一天不能重复排班这个约束在数据库层面用唯一索引实现在应用层面也要加判断我实际两个地方都做了唯一索引兜底应用层提示友好。第二修改排班时如果已有预约记录不能直接删掉这个排班只能把状态改成“已停诊”同时给已预约的患者发送提醒这是我在和诊所沟通时学到的一个细节看起来小但很体现系统完善度。生成排班我做得比较简单就是固定把一天按小时切成时间段。如果你时间充裕可以让管理员自定义时间段长度比如上午8点到12点每30分钟一个号源但这会让后端逻辑复杂不少。按小时切是一种简洁且演示效果不差的折中方案。3.5 收费与统计模块收费模块说简单也简单说复杂也复杂。简单在于就是记录一个费用明细复杂在于它要和就诊记录绑定。我的方案是医生完成病历填写后可以发起收费请求填写收费项目名称和金额生成一张收费单状态为待支付。前台在收费页面可以看到所有待支付的单子点击确认收款后状态变成已支付。全部费用数据沉淀到bill表之后做统计就方便了。管理员的首页数据面板展示三个核心指标当日预约量、当日就诊量、当日总收入。这三个指标分别从appointment表和bill表统计用SQL的聚合查询就能搞定。我用了ECharts做可视化柱状图展示最近七天的收入变化饼图展示不同收费项目占比。答辩的时候数据可视化面板放在首页最显眼的位置演示效果很加分。统计接口要注意日期格式化的问题前端传日期范围时统一用yyyy-MM-dd后端拿LocalDate解析不要用Date旧API真的太容易踩时区坑了。4. 关键代码实战与配置说明4.1 项目配置与启动类创建SpringBoot项目后用application.yml统一管理配置。我贴一份基础配置你直接用把数据库账号密码改一下就能跑起来。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/dental_clinic?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这段配置里mybatis-plus部分两个细节值得说。log-impl用StdOutImpl可以在控制台直接打印完整SQL语句排查问题时作用非常大比DEBUG断点来得快。map-underscore-to-camel-case是让数据库的user_name自动映射到实体的userName省去一堆TableField注解。逻辑删除三行配置是全局的实体类字段标上TableLogic就自动生效。4.2 核心Service层代码示例预约接口是系统的重中之重我把最关键的一段Service方法贴出来讲。这段代码是前面说的并发控制方案的具体实现。Override Transactional(rollbackFor Exception.class) public Result createAppointment(AppointmentCreateDTO dto) { Schedule schedule scheduleMapper.selectById(dto.getScheduleId()); if (schedule null) { return Result.error(排班不存在); } if (schedule.getStatus() ! 0) { return Result.error(排班已停诊无法预约); } if (StrUtil.isNotBlank(dto.getAppointmentTime()) !isTimeInRange(dto.getAppointmentTime(), schedule)) { return Result.error(所选时间不在排班范围内); } Appointment appointment new Appointment(); appointment.setPatientId(dto.getPatientId()); appointment.setScheduleId(schedule.getId()); appointment.setDentistId(schedule.getDentistId()); appointment.setApptDate(schedule.getWorkDate()); appointment.setStatus(0); appointmentMapper.insert(appointment); int updated scheduleMapper.increaseBookedSlots(schedule.getId()); if (updated 0) { throw new RuntimeException(号源已满预约失败); } return Result.success(预约成功); }关键是increaseBookedSlots这个方法它对应上面说的原子更新SQL。在Mapper里写好注解SQL就行。Update(UPDATE schedule SET booked_slots booked_slots 1 WHERE id #{id} AND booked_slots total_slots) int increaseBookedSlots(Param(id) Long id);这段代码的写法建议你直接照抄配合Transactional控制事务。顺序上需要注意先插入预约记录再扣号源扣不动就抛异常让整个事务回滚。如果把扣号源放在插入之前一旦插入预约记录失败扣掉的号源也得手动恢复稍麻烦一些。4.3 前端页面与接口对接前端代码不必全部展开但axios拦截器的逻辑有必要说一下。每次请求前把Token从localStorage取出来添加到请求头这是前后端分离项目没得跑的。响应处理时如果状态码是401就跳转登录页避免用户Token过期后页面白屏。页面设计这块我建议预约页优先使用卡片式展示医生信息排班用日历视图展示可约日期。Element UI自带日历组件el-calendar自定义日期单元格的内容就能展示每天剩余号源。前端这块唯一的建议是不要贪多把表格、表单、弹窗、日历这几个组件用熟比各种花哨的动画效果有用得多。5. 常见问题与排查技巧实录5.1 运行时异常速查表开发过程中踩过的坑确实不少我把典型问题整理成一张表比看堆栈日志来得直接。问题表现根本原因解决思路访问接口返回404路径写错或拦截器配置错误console看后端日志检查请求路径和Controller映射提交预约时总是“号源已满”排班记录booked_slots初始值不对手动修改数据库或写接口统一重置号源返回给前端的时间多出8小时时区不一致连接串加serverTimezoneAsia/Shanghai删除医生后预约记录查询报错外键关联无数据或字段为null保留医生逻辑删除查询时跳过deleted1JWT过期频繁过期时间太短调整为24小时并补充前端自动刷新逻辑第一个问题背后其实是我自己栽过跟头。拦截器放行路径写错导致注册接口都被拦截但前端请求的是/api/auth/register拦截器判断路径不在白名单里就直接返回401。排查半天才发现白名单里的尾斜杠问题register和register/是一样的但字符串contains判断偏偏不认。后来统一用trimTrailingSlash处理一遍才彻底解决。5.2 排班和号源恢复的时序难题排班是典型的时效性业务。修改排班时如果当天已经有患者预约并且状态还是待就诊直接改医生出诊时间会让患者白跑一趟。所以修改排班的Service方法里一定要先查一下有没有关联的预约记录。号源恢复的另一个坑是当预约状态从已确认改为已取消时booked_slots要减回去但改成已完成时不能减。很多同学会漏掉这个判断直接统一减1结果号源越滚越不准。其实准确的做法是写一个定时任务在每天凌晨把过期且未就诊的预约批量标记为已取消同时恢复号源。门诊可能没有太多过期预约但这个逻辑能防止数据越来越脏。我用Spring自带的Scheduled注解写了一个小任务每天凌晨3点执行。为什么定在凌晨3点因为门诊已经下班这时候做数据重置不影响正常业务。5.3 安全细节与数据展示问题安全这块有几个点非常容易被毕业设计忽略但答辩老师会抓着问。第一密码传输必须加密。虽然项目里用了HTTPS不是标配但前端在后端接口加密传输密码保持一致的规范是有必要的。我在前端提交表单前用JS的SHA256哈希一次密码后端拿到哈希值后先做Base64解码再和数据库比对。你不用上全套加密方案但至少不能在浏览器开发者工具里明文看到密码。第二接口返回数据要做脱敏。患者详情接口和列表接口返回同一个实体类时如果直接把手机号全都返回前端的网络面板里就能看到所有患者的电话这是隐私事故。所以列表接口我做了字段裁剪只返回姓名、性别、年龄、最近就诊时间完整信息只有详情页才返回。第三SQL注入的防御。用MyBatis-Plus自带的条件构造器时参数都是预编译的基本安全。但如果你自己拼接SQL字符串嘿嘿那就该被答辩老师锤了。写原生SQL时一律用Param传参不要用${}去替代字段值。6. 写在最后的实操感受这套系统我从零开始写到最后能跑通完整的预约就诊流程前后大概花了三周。第一周做需求梳理和数据库设计第二周完成后端接口第三周补前端页面和联调。如果把所有代码都复用现成模板的话一个基础扎实的同学可以压缩到两周内完成。我个人实际写下来最大的体会是这套项目真正的技术亮点不在某个花哨的算法或者炫酷的界面而在于把门诊业务规则吃透了。预约并发控制怎么用原子更新搞定号源怎么恢复病历怎么和预约关联成闭环这些东西才是答辩老师真正会追问的地方。你就算把代码全部背下来了业务逻辑讲不清楚照样会露馅。最后再分享一个小技巧一定要准备一套完整的演示数据包括一个未来三天的排班、一位有三次就诊历史的患者、几张不同状态的收费单。演示时先登录患者端约一个号再切到医生端接诊、录病历、发起收费最后到管理员端看统计面板数据变化。这套演示流程走下来比你口头讲十分钟架构都管用。如果你正准备用这个题目做毕业设计我的建议是不要急着写代码先花时间把表关系理清楚把预约这条主流程在纸上画通后面的事情就会顺很多。

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

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

免费获取报价 →
↑