简介:这是一套基于SpringBoot+Vue的医疗服务系统完整源码与数据库,面向计算机、通信、人工智能、自动化等相关专业的在校学生与教师,尤其适合作为毕业设计、期末课程设计或课程大作业的参考方案。项目为个人毕设作品,答辩评审分达98分,代码经过调试测试可正常运行,基础较好的学习者还能在此基础上修改调整,实现不同功能。压缩包共141个文件,约12.72MB,以32个Java后端源码、28个Vue前端组件、2个SQL数据库脚本为核心,辅以JS、XML、CSS、HTML、JSON等配置与页面资源,以及PNG、JPG、GIF等界面截图和图标素材,结构完整、层次清晰。目前已有72人学习关注。通过该资源,读者可获取一套可直接运行的前后端分离项目范例,理解SpringBoot接口设计与Vue组件化开发的协作方式,掌握数据库表结构设计与前后端联调思路,并借鉴高分毕设的目录组织与代码规范,为自身课题开发与答辩准备提供实用参考。
1. 从一份医疗服务系统源码说起:SpringBoot+Vue 这套组合到底能跑出什么
打开招聘软件搜“Java 开发”,十个岗位里有六个写着“熟悉 SpringBoot + Vue 前后端分离开发”。这不是巧合。医院挂号、在线问诊、体检预约、电子病历查询——这些场景背后跑的系统,绝大多数就是这套技术栈搭出来的。一份完整的医疗服务系统源码,通常包含患者端、医生端、管理员端三个角色,覆盖科室管理、排班、挂号、缴费、病历记录这几条核心业务线。数据库用 MySQL,后端 SpringBoot 提供 REST 接口,前端 Vue 做单页应用,中间用 MyBatis 或 MyBatis-Plus 做 ORM 映射。这套组合之所以成为毕业设计和企业级项目的“主力军”,原因很直接:生态成熟、文档多、出问题能搜到答案。但“能跑起来”和“能讲清楚”之间,差着一整套工程细节。接下来按实际落地顺序,把这份源码从环境搭建到业务闭环拆开讲。
2. 环境搭建与项目骨架:把 SpringBoot 后端和 Vue 前端同时跑起来
2.1 后端依赖版本怎么选才不翻车
拿到一份 SpringBoot 项目源码,第一件事不是急着mvn spring-boot:run,而是先看pom.xml里的版本组合。SpringBoot 2.x 和 3.x 的差异很大,最典型的是 3.x 要求 JDK 17 起步,且javax.*包全部迁移到了jakarta.*。如果源码里写的是javax.servlet,你本地用 JDK 17 跑就会直接编译报错。
我一般会先确认三个版本号:SpringBoot 版本、JDK 版本、MySQL 驱动版本。常见的稳定组合是 SpringBoot 2.7.x + JDK 8/11 + MySQL 8.0 驱动。如果源码用的是 SpringBoot 3.x,那就老老实实上 JDK 17。
<!-- pom.xml 关键依赖片段 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.6</version> <!-- 版本决定了 JDK 下限和依赖兼容范围 --> </parent> <properties> <java.version>1.8</java.version> <!-- 与 SpringBoot 2.7.x 匹配 --> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> <!-- MyBatis-Plus 版本需与 SpringBoot 版本对齐 --> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> </dependencies>参数说明:spring-boot-starter-parent的版本决定了整个项目的依赖管理基线,不要随意改。MyBatis-Plus 3.5.x 系列兼容 SpringBoot 2.3 到 2.7,如果升到 SpringBoot 3.x 则需要 MyBatis-Plus 3.5.3 以上版本。MySQL 驱动 8.x 的连接 URL 必须带时区参数,否则启动时报The server time zone value is unrecognized。
2.2 application.yml 里四个必须改的配置项
源码里的配置文件通常是开发者本地的环境,直接拿来用大概率连不上数据库。以下四个位置必须逐项核对:
server: port: 8080 # 如果本机 8080 被占用,改成 8081 或 9090 spring: datasource: url: jdbc:mysql://localhost:3306/medical_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false username: root # 改成你本地 MySQL 的用户名 password: your_password # 改成你本地 MySQL 的密码 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 10MB # 病历附件上传大小限制 max-request-size: 20MB mybatis-plus: mapper-locations: classpath:mapper/*.xml # XML 映射文件路径,路径不对会报 Invalid bound statement configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发阶段打印 SQL,上线关掉逻辑说明:serverTimezone=Asia/Shanghai不加的话,MySQL 8 驱动会报时区异常。mapper-locations如果和实际目录结构不匹配,MyBatis 找不到 XML 文件,运行时报Invalid bound statement (not found),这个错误新手排查半天都找不到原因。log-impl设为StdOutImpl可以在控制台看到完整 SQL,调接口时非常有用,但生产环境一定要注释掉,否则日志量爆炸。
2.3 Vue 前端安装依赖与联调配置
前端部分,先确认package.json里的 Vue 版本。Vue 2 和 Vue 3 的脚手架命令不同,Vue 2 用vue-cli,Vue 3 用vite或@vue/cli5.x。拿到源码后进前端目录,先看有没有node_modules,没有就执行安装。
# 进入前端目录 cd medical-frontend # 查看 package.json 确认 Vue 版本和依赖 cat package.json | grep -E '"vue"|"axios"|"element-ui"|"element-plus"' # 安装依赖(Vue 2 项目建议用 npm,Vue 3 + Vite 项目可以用 pnpm) npm install # 启动开发服务器 npm run serve # Vue CLI 项目 npm run dev # Vite 项目安装完成后,关键一步是改接口代理。前端默认请求localhost:8080,如果后端端口改了,或者需要跨域代理,在vue.config.js(Vue CLI)或vite.config.js(Vite)里配置:
// vue.config.js(Vue CLI 项目) module.exports = { devServer: { port: 8081, // 前端开发服务器端口 proxy: { '/api': { target: 'http://localhost:8080', // 后端实际地址 changeOrigin: true, pathRewrite: { '^/api': '' } // 去掉 /api 前缀再转发 } } } }参数说明:changeOrigin: true用于绕过浏览器同源策略,pathRewrite决定了前端请求路径和后端接口路径的映射关系。如果后端接口本身就以/api开头,那pathRewrite就不需要。这一步配错,前端页面能打开但所有数据都是空的,控制台报 404 或 CORS 错误。
3. 数据库设计与核心表结构:医疗服务系统的数据模型怎么落地
3.1 从挂号业务倒推表关系
医疗服务系统的数据库设计,核心围绕“患者—医生—科室—排班—挂号—病历”这条链路展开。不要一上来就建十几张表,先把主流程跑通。最常见的五张核心表是:
| 表名 | 作用 | 关键字段 |
|---|---|---|
patient | 患者信息 | id, name, id_card, phone, password |
doctor | 医生信息 | id, name, department_id, title, intro |
department | 科室 | id, name, location, description |
schedule | 排班 | id, doctor_id, date, time_slot, max_num, current_num |
appointment | 挂号记录 | id, patient_id, schedule_id, status, create_time |
schedule表里的max_num和current_num是挂号业务的核心——每次挂号成功,current_num加一,当current_num >= max_num时不允许再挂。这个逻辑放在 Service 层用事务控制,不能只靠前端判断。
-- 挂号核心操作:乐观锁防止超挂 UPDATE schedule SET current_num = current_num + 1 WHERE id = #{scheduleId} AND current_num < max_num; -- 返回影响行数为 0 说明已满,抛出业务异常逻辑说明:这条 UPDATE 语句把“判断是否已满”和“递增计数”合并成一个原子操作,避免了先查后改的并发问题。如果返回的影响行数是 0,说明号源已满,Service 层直接抛异常回滚。这是医疗服务系统里最典型的并发场景,面试时也经常被问到。
3.2 数据库初始化脚本的执行顺序
源码里通常带一个.sql文件,导入时注意字符集和执行顺序:
# 创建数据库并指定字符集 mysql -u root -p -e "CREATE DATABASE medical_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 导入表结构和初始数据 mysql -u root -p medical_db < medical_db.sql # 验证导入结果 mysql -u root -p -e "USE medical_db; SHOW TABLES; SELECT COUNT(*) FROM doctor;"注意:utf8mb4而不是utf8,因为病历里可能有生僻字或特殊符号。导入前先看 SQL 文件里有没有DROP TABLE IF EXISTS,有的话确认不需要保留旧数据再执行。如果导入报Unknown collation: 'utf8mb4_0900_ai_ci',说明你的 MySQL 版本低于 8.0,需要把 SQL 文件里的utf8mb4_0900_ai_ci替换成utf8mb4_general_ci。
4. 核心业务接口开发:挂号、排班、病历三个模块的代码实现
4.1 挂号接口的完整调用链
挂号是医疗服务系统里最核心也最容易出问题的接口。一个完整的挂号请求要经过:前端选科室 → 选医生 → 选排班 → 确认挂号 → 后端校验 → 写挂号记录 → 更新排班计数。后端 Controller 层代码大致如下:
@RestController @RequestMapping("/api/appointment") public class AppointmentController { @Autowired private AppointmentService appointmentService; @PostMapping("/create") public Result create(@RequestBody AppointmentDTO dto) { // dto 包含 patientId, scheduleId // Service 层负责事务控制和并发校验 return appointmentService.createAppointment(dto); } }Service 层的核心逻辑:
@Service public class AppointmentServiceImpl implements AppointmentService { @Autowired private ScheduleMapper scheduleMapper; @Autowired private AppointmentMapper appointmentMapper; @Override @Transactional(rollbackFor = Exception.class) // 任何异常都回滚 public Result createAppointment(AppointmentDTO dto) { // 第一步:扣减号源(原子操作) int affected = scheduleMapper.decrementSlot(dto.getScheduleId()); if (affected == 0) { return Result.fail("该时段号源已满"); } // 第二步:写入挂号记录 Appointment appointment = new Appointment(); appointment.setPatientId(dto.getPatientId()); appointment.setScheduleId(dto.getScheduleId()); appointment.setStatus(0); // 0=待就诊 appointment.setCreateTime(new Date()); appointmentMapper.insert(appointment); return Result.success("挂号成功"); } }参数说明:@Transactional(rollbackFor = Exception.class)确保扣号和写记录要么都成功要么都回滚。decrementSlot对应前面那条 UPDATE 语句。status字段用整数枚举:0 待就诊、1 已就诊、2 已取消。前端根据 status 显示不同按钮。
4.2 排班查询与 Vue 前端的联动
医生排班查询接口需要按日期和科室过滤,返回的数据结构要方便前端渲染成时间轴或表格:
@GetMapping("/list") public Result list(@RequestParam Long departmentId, @RequestParam String date) { // 查询指定科室、指定日期的所有排班 List<ScheduleVO> list = scheduleService.listByDeptAndDate(departmentId, date); return Result.success(list); }前端 Vue 组件里用 axios 调用:
// 排班查询组件 async fetchSchedules() { try { const res = await axios.get('/api/schedule/list', { params: { departmentId: this.selectedDept, date: this.selectedDate } }); this.schedules = res.data.data; // 赋值给表格数据源 } catch (error) { this.$message.error('排班查询失败,请稍后重试'); } }逻辑说明:params里的参数名必须和后端@RequestParam的名称一致,否则后端收到 null。res.data.data是因为后端统一返回了Result包装类,第一层data是 axios 的响应体,第二层data才是业务数据。这个嵌套层级搞错的话,页面显示空白但不报错,排查起来很费时间。
4.3 病历模块的权限控制
病历数据涉及隐私,不是所有角色都能看。患者只能看自己的病历,医生只能看自己接诊过的患者的病历,管理员可以看全部。这种权限控制放在 Service 层做,不要只靠前端隐藏菜单。
public Result getMedicalRecord(Long recordId, Long currentUserId, String role) { MedicalRecord record = medicalRecordMapper.selectById(recordId); if (record == null) { return Result.fail("病历不存在"); } // 患者只能看自己的 if ("patient".equals(role) && !record.getPatientId().equals(currentUserId)) { return Result.fail("无权查看该病历"); } // 医生只能看自己接诊的 if ("doctor".equals(role) && !record.getDoctorId().equals(currentUserId)) { return Result.fail("无权查看该病历"); } return Result.success(record); }注意:currentUserId和role应该从 JWT token 或 Session 里取,不能从前端传参,否则用户可以伪造身份。这是安全底线,毕业设计答辩时老师也常问这一点。
5. 避坑与排查:这份源码跑不起来时先查这五个地方
5.1 启动报 “Invalid bound statement (not found)”
现象:SpringBoot 启动成功,但调接口时报org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。
原因:MyBatis 或 MyBatis-Plus 没有找到 Mapper XML 文件。常见情况有三种:XML 文件不在resources/mapper/目录下;application.yml里mapper-locations路径写错;XML 文件的namespace和 Mapper 接口的全限定名不一致。
解决:先确认target/classes/mapper/目录下有没有 XML 文件(编译后),如果没有,检查pom.xml的<resources>配置是否包含了**/*.xml。然后在application.yml里把mapper-locations改成classpath*:mapper/**/*.xml,注意classpath*和classpath的区别——前者会扫描所有 classpath 路径。
5.2 前端页面能打开但数据全是空的
现象:Vue 页面正常渲染,但表格、列表、下拉框都没有数据,浏览器控制台报 404 或 CORS 错误。
原因:接口代理没配好,或者后端没启动。先看 Network 面板里请求的 URL 是什么,如果是http://localhost:8081/api/xxx返回 404,说明代理没生效或pathRewrite写错了。如果报 CORS,说明请求直接打到了后端端口而不是走代理。
解决:确认vue.config.js里proxy配置的target是后端实际地址,pathRewrite和实际接口路径匹配。改完配置文件必须重启npm run serve,热更新不会重新加载代理配置。
5.3 MySQL 连接报 “Public Key Retrieval is not allowed”
现象:启动时连接 MySQL 报com.mysql.cj.exceptions.CJException: Public Key Retrieval is not allowed。
原因:MySQL 8.0 默认使用caching_sha2_password认证插件,某些驱动版本需要显式允许公钥检索。
解决:在 JDBC URL 后面加allowPublicKeyRetrieval=true。完整 URL 示例:jdbc:mysql://localhost:3306/medical_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true。
5.4 挂号接口并发下出现超挂
现象:压测或多人同时挂号时,实际挂号人数超过了max_num。
原因:Service 层用了“先查再改”的逻辑,两个线程同时查到current_num < max_num,然后都执行了加一操作。
解决:把判断和更新合并成一条原子 SQL(如 3.1 节所示),或者用SELECT ... FOR UPDATE加行锁。推荐前者,性能更好。如果用了 Redis 做缓存,还要注意缓存和数据库的一致性,先更新数据库再删缓存。
5.5 Vue 打包后部署到 Nginx 刷新 404
现象:npm run build后把dist目录放到 Nginx,首页能打开,但刷新子路由页面报 404。
原因:Vue Router 的 history 模式下,Nginx 找不到对应的物理文件。
解决:在 Nginx 配置里加try_files $uri $uri/ /index.html;,把所有找不到的路径都指向index.html,由前端路由接管。如果用的是 hash 模式则不会有这个问题,但 URL 里会带#,体验稍差。
6. 从能跑到能讲:把这份源码变成你自己的项目经验
把项目跑起来只是第一步。答辩或面试时,面试官不会只看你演示功能,他会问“挂号并发怎么处理的”“JWT token 过期了怎么办”“数据库索引加在哪几个字段上”。这些问题,源码里不一定有答案,但你必须能说出来。
我的习惯是:跑通之后,挑一个核心接口,从 Controller 到 Service 到 Mapper 到 SQL,完整画一遍调用链,标出每一层做了什么、可能出什么问题。比如挂号接口,Controller 做参数校验,Service 做事务和并发控制,Mapper 做原子更新,SQL 层用current_num < max_num兜底。这条链路上任何一个环节出问题,现象是什么、怎么排查,都过一遍。
再进一步,可以给项目加两个源码里没有但实际工作中常用的东西:一是接口限流,用 Guava RateLimiter 或 Redis 计数器,防止恶意刷号;二是操作日志,用 AOP 切面记录谁在什么时间做了什么操作,病历修改这类敏感操作必须留痕。这两个点加进去,项目就从“毕业设计”变成了“有工程思维的作品”。
最后说一个血泪教训:不要等到答辩前三天才开始跑源码。数据库版本、JDK 版本、Node 版本,任何一个不匹配都可能卡你一整天。提前两周把环境搭好,留足时间踩坑和补漏。希望帮到你。
本文还有配套的精品资源,点击获取