简介这份资源是一套面向高校计算机专业毕业设计场景的医疗就诊平台完整论文文档适合正在准备毕业设计选题、撰写论文或需要参考系统设计思路的本科生与指导教师。文档围绕患者预约就医、医生出诊管理、挂号与病例记录、药品入库与开药单管理等核心业务展开涵盖需求分析、平台设计、数据库建模与系统实现等完整章节并给出 Java、SpringBoot、Vue、MySQL、Tomcat、B/S 架构等技术选型说明可作为同类课题的选题依据与写作范本。压缩包内共 1 个 docx 文件约 3.62MB含摘要、中英文关键词、目录及各章节正文结构规范、可直接借鉴目录框架与文档组织方式。目前已有 61 人学习下载适合作为毕业设计论文模板与医疗信息化类系统的设计参考1. SpringBoot 医疗就诊平台从论文到能跑的挂号流程很多毕业设计里的医疗就诊平台代码能编译、页面能打开但一走到「患者挂号 → 医生接诊 → 开具药单 → 药品扣库存」这条主链路就露馅要么挂号状态永远停在待就诊要么病历和开药单对不上号要么并发下药品库存扣成负数。这套系统按角色拆成三块——患者端负责搜索医生、预约挂号、查病历和开药单医生端负责出诊排班、处理挂号、开具处方管理员端维护科室、药品、公告和用户。技术栈是 Java SpringBoot MySQL数据访问用 MyBatis前端 Vue打包后跑在 Tomcat 或 SpringBoot 内嵌容器里。下面按「先建对表再打通接口最后排并发和慢查询」的顺序把这份资源拆成能复现的步骤。2. 医疗就诊平台的模块拆解与 MySQL 库表设计动手写代码前先把角色和数据关系定死否则后期改表比改代码还痛苦。医疗就诊平台的功能点看着多归到数据层面其实就是几张核心表在互相引用理清引用方向接口自然好写。2.1 患者、医生、管理员三种角色的数据边界患者能碰到的数据是自己的挂号记录、病历、开药单、收藏和留言医生能碰的是自己名下的出诊排班、被挂号的记录、名下患者的病历和开药单管理员什么都能看但日常操作集中在科室、药品、公告和用户维护上。这三类角色的边界建议直接落到数据库字段上而不是只在页面上做隐藏。我的习惯是给每张业务表都带一个userid或doctorid查询时用当前登录身份拼where条件而不是在 Service 里根据角色if-else拼十几种组合。这样一点改动就是给 Mapper 加一个条件权限收敛也更清晰。常见做法是再建一张 token 表承接登录态字段包含userid、username、tablename、role、token、addtime、expiratedtime。tablename用来标记这个账号属于哪张用户表role直接决定后续拦截器放不放行避免每次请求都去查三张用户表判断身份。2.2 SpringBoot 依赖引入与 application.yml 配置依赖这块不用堆太多SpringBoot 的自动装配会处理大半。核心只需要 web、mybatis、mysql 驱动和分页插件剩下按需加。!-- pom.xml 关键依赖版本按 SpringBoot 父工程统一管理 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency引入 mybatis-spring-boot-starter 后SpringBoot 会自动扫描Mapper注解的接口并完成 SqlSessionFactory 装配不用再手写 XML 版的SqlSessionFactoryBean。mysql-connector-j是新版坐标老的mysql-connector-java在 8.0 之后逐步弃用别混着写。配置文件里我一般显式打开驼峰映射否则patient_name这种字段映射到patientName会一直返回 null排查起来很浪费时间。# application.yml server: port: 8080 servlet: context-path: /medical spring: datasource: url: jdbc:mysql://localhost:3306/medical_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: configuration: map-underscore-to-camel-case: true # 下划线转驼峰避免字段映射丢失 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发期打印 SQL把log-impl打开是开发期习惯编译一跑就能看到真实 SQL 和参数比猜快得多。上线前记得关掉或换成日志框架StdOut 在生产里会拖性能。2.3 挂号信息、患者病例、药品和开药单的表结构表设计是这套系统的骨架下面列几个最关键的字段类型和长度参考常见实现。表名关键字段说明doctorid、doctor_name、department_id、title、good_at、avatar医生基础信息关联科室outpatient_scheduleid、doctor_id、visit_date、period、total_quota、left_quota出诊排班含剩余号源registrationid、patient_id、doctor_id、schedule_id、status、visit_time挂号记录status 决定流转patient_caseid、patient_id、doctor_id、registration_id、diagnosis、create_time患者病历与挂号一对一drugid、drug_name、spec、stock、price药品基础信息与库存prescriptionid、case_id、patient_id、doctor_id、drug_ids、total_price开药单主表挂号表里schedule_id必须指向具体某天的某个时段而不是只挂到医生身上。这样号源判断、签到、过期取消都能精确到时段也方便后续加「同一天同一医生不能重复挂」这类约束。药品表单独维护stock与开药单通过明细表关联不要直接把药品塞进开药单一行里。2.4 实体类与 MyBatis 映射写法实体类直接用注解映射省掉 XML改字段和改 SQL 在同一屏内完成。// Registration.java Data public class Registration { private Long id; private Long patientId; // 挂号患者 private Long doctorId; // 接诊医生 private Long scheduleId; // 对应出诊排班 private Integer status; // 0待就诊 1已就诊 2已取消 private Date visitTime; }Data来自 Lombok用之前确认 IDE 装了插件并开启了注解处理否则提示 getter/setter 找不到。如果项目不便引 Lombok就手写别在答辩环境临时装插件。// RegistrationMapper.java Mapper public interface RegistrationMapper { Select(select * from registration where patient_id #{patientId} order by visit_time desc) ListRegistration listByPatient(Long patientId); Update(update registration set status #{status} where id #{id}) int updateStatus(Param(id) Long id, Param(status) Integer status); }注解版适合这种简单 CRUD查询一复杂就得换 XML 或SelectProvider否则字符串拼接会失控。判断标准很简单SQL 里出现超过两个动态条件就拆到 XML 去写。3. 挂号预约与开药单链路的接口实现表和实体就位后真正费脑的是状态流转和一致性。挂号、就诊、开药单、库存扣减是一条链任何一段没兜住数据都会对不上。3.1 挂号信息的状态机设计不要把挂号状态当成普通字段随手改先用一个小枚举把合法流转固定下来。当前状态允许动作目标状态触发角色0 待就诊医生接诊1 已就诊医生0 待就诊患者取消未过就诊时间2 已取消患者1 已就诊开具药单保持 1医生2 已取消不允许再改保持 2——有了这张表Service 里只做一件事校验当前状态是否在允许的来源状态里。不在就直接抛业务异常别静默改状态。3.2 挂号接口与号源扣减的代码实现号源扣减是最容易出并发问题的地方。简单写法先扣后查会超卖正确姿势是在一条更新语句里带条件。Service public class RegistrationService { Autowired private OutpatientScheduleMapper scheduleMapper; Autowired private RegistrationMapper registrationMapper; // 挂号先乐观扣号源成功再写挂号记录 Transactional(rollbackFor Exception.class) public void register(Long patientId, Long scheduleId) { // 关键left_quota 0 作为更新条件避免并发超卖 int affected scheduleMapper.decreaseQuota(scheduleId); if (affected 0) { throw new BizException(该时段号源已满); } Registration reg new Registration(); reg.setPatientId(patientId); reg.setScheduleId(scheduleId); reg.setStatus(0); reg.setVisitTime(new Date()); registrationMapper.insert(reg); } }Transactional(rollbackFor Exception.class)里的rollbackFor不能省默认只对运行时异常回滚业务里抛的受检异常会让号源扣了但挂号没写。扣号源那条 SQL 长这样-- OutpatientScheduleMapper.decreaseQuota update outpatient_schedule set left_quota left_quota - 1 where id #{scheduleId} and left_quota 0left_quota 0放进 where 是整套并发控制的关键它让数据库的行锁帮我们判断而不是先在 Java 里select出来判断再更新。affected 0就代表这轮竞争没抢到直接抛出业务异常让事务回滚。3.3 开药单与药品库存的联动医生开具药单时必须同时做两件事写开药单明细、扣药品库存。这里的顺序反了会导致库存在失败时白扣或白加。Transactional(rollbackFor Exception.class) public void createPrescription(Long caseId, ListDrugItem items) { for (DrugItem item : items) { // 先扣库存stock 数量 才允许扣 int affected drugMapper.deductStock(item.getDrugId(), item.getNum()); if (affected 0) { throw new BizException(药品库存不足 item.getDrugName()); } prescriptionItemMapper.insert(caseId, item); } }deductStock同样在 SQL 里带stock #{num}条件逻辑和第 3.2 节一致。这样只要有一味药库存不够整个事务回滚前面扣掉的库存也一并还原不会再出现开了单子但发不出药的情况。3.4 患者病例与就诊信息的追踪病历表建议与挂号记录用registration_id一对一关联而不是只挂到患者身上。原因是同一位患者和同一位医生之间可能有多条挂号记录只有带上registration_id才能精确回放某一次就诊。病历写入放在医生点击「接诊」之后与挂号状态改成 1 放在同一个事务里保证「有就诊记录就一定有对应病历」。4. 鉴权、排班与留言栏的前后端联调实战接口单个能跑通不代表整套联调顺畅。这一层最容易踩的坑集中在登录态、时间字段和跨域上。4.1 token 表鉴权与角色拦截器登录成功后把 token 写入 token 表同时返回给前端。后续请求用拦截器统一校验不用每个接口都写一遍身份判断。// AuthInterceptor.java public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Token); if (token null || token.isEmpty()) { return reject(response, 未登录); } TokenEntity entity tokenMapper.findByToken(token); if (entity null || entity.getExpiratedtime().before(new Date())) { return reject(response, 登录已过期); } // 把角色和用户 id 塞进 ThreadLocal供后续 Service 取用 UserContext.set(entity.getUserid(), entity.getRole()); return true; }expiratedtime的过期判断用before(new Date())要留意服务器时区和数据库时区是否一致应用配置里写了Asia/Shanghai数据库连接串也带上时区两边不一致时会提前过期或永不过期。ThreadLocal记得在afterCompletion里remove否则线程池复用会串号。4.2 出诊医生排班与科室的关联查询排班列表是患者端首屏最常查的数据字段横跨排班表和医生表直接在 Mapper 里做联表比在 Service 里拼两段更省事。-- 按科室和日期查可挂号医生 select s.id as scheduleId, d.doctor_name, d.title, s.period, s.left_quota, s.visit_date from outpatient_schedule s join doctor d on s.doctor_id d.id where d.department_id #{departmentId} and s.visit_date #{visitDate} and s.left_quota 0 order by s.period条件里带上left_quota 0患者端就不会点进去才发现没号少一次无谓交互。visit_date用DATE类型而不是datetime跨天签到、过期判断都靠它不要在这个字段上偷懒存字符串。4.3 留言栏与评论回复的表设计留言栏在原文里对应「出诊医生评论表」字段有refid、userid、avatarurl、nickname、content、reply。常见的扩展做法是再加一个parentid支持二级回复前端按parentid组织树形结构。// 留言分页查询 Select(select * from doctor_comment where refid #{doctorId} and parentid 0 order by addtime desc limit #{offset}, #{size}) ListDoctorComment pageByDoctor(Param(doctorId) Long doctorId, Param(offset) int offset, Param(size) int size);分页只查一级留言二级回复单独按parentid in (…)批量查避免 join 出笛卡尔积把一级留言重复出来。这也是留言、评论类功能最常见的性能优化点。4.4 Vue 前端对接与跨域配置前端跑在 8081、后端在 8080 时浏览器会拦请求。开发期在 SpringBoot 里加全局 CORS比让前端配代理更直白。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true); } }allowedOriginPatterns是 Spring 5.3 之后的写法allowedOrigins(*)在allowCredentials(true)下会被拒。开发期放开、上线收窄到具体域名这一步前后端都要改一次别把星号带上生产。5. 医疗就诊平台的并发挂号与慢查询排查技巧并发场景下最常出现的三类问题号源超卖、库存扣负、同一人多条挂号。前两个靠前面写过的条件更新基本能兜住第三条靠唯一索引。-- 同一患者对同一排班只允许一条有效挂号 alter table registration add unique key uk_patient_schedule (patient_id, schedule_id);加了这条索引后重复提交会在数据库层被拦下Service 只要捕获DuplicateKeyException翻译成「请勿重复挂号」即可不用在应用层再查一次。注意取消后的记录也占着唯一键如果业务允许取消后重新挂就得把status也纳入唯一键或者改用逻辑删除字段参与组合。慢查询先看执行计划别急着加缓存。explain select * from registration where patient_id 1001 and status 0 order by visit_time desc;重点看三列type是否是ref或rangekey是否用上索引rows扫描行数是否明显偏大。挂号表上建idx_patient_status (patient_id, status, visit_time)能一次覆盖这类查询。药品表按drug_name模糊搜索时like %xx%走不了索引数据量上来后常见做法是换成前缀匹配或者引入分词具体方案视数据规模选择。还有一个小技巧是给「最近挂号」这类高频查询加分页上限默认查最近 30 条。前端下拉加载再翻页比一次性select *全表返回稳得多。毕业设计数据量小不设上限看不出问题一旦换真实数据量第一个卡的就是这种没有边界的列表接口。本文还有配套的精品资源点击获取