简介这是一套完整的SpringBootVue.js医疗管理系统实战项目源码面向Java与前端初学者及全栈开发者聚焦医院挂号、患者管理、药品库存与医生排班等核心业务场景助力快速掌握前后端分离开发全流程。资源包共138个文件涵盖32个Java后端接口与业务逻辑代码、28个Vue组件化页面如预约挂号页、患者档案页、29个PNG/JPG界面素材、8个JS工具脚本、2个SQL建表与初始化脚本以及yml、properties等配置文件完整支撑本地一键部署压缩包大小为12.74MB结构清晰含标准MavenVue CLI工程目录。已有6421人学习下载读者可直接导入IDEA与VS Code运行调试获取可商用的前后端协同范例、数据库设计思路、RESTful接口规范实现及AxiosJWT鉴权集成方案是理解医疗行业系统架构与工程落地的优质学习样本。1. 为什么一个“SpringBootVue医疗管理系统”压缩包比十篇论文更能教会你前后端分离落地你打开这个.zip文件里面不是空泛的“系统架构图”或“功能模块说明”而是一套能直接mvn clean install编译、npm run serve启动、连上本地 MySQL 就能挂号开药的真实业务流——患者管理、医生排班、门诊记录、药品库存、处方审核全在src/main/java和src/views里扎堆跑。这不是教学 Demo是真实医院信息科压测过并发挂号的最小可行系统MVP连数据库初始化脚本都按CREATE TABLE IF NOT EXISTS写死避免你手抖删库后对着报错发呆。它解决的不是“怎么学 SpringBoot”而是“怎么让 Vue 页面调通 SpringBoot 接口、怎么把医生排班表从 MySQL 查出来再渲染成可拖拽日历、怎么让处方提交时前端校验后端幂等数据库事务三重兜底”。适合两类人刚写完“Hello World”想啃真实项目的应届生以及被甲方催着三天上线挂号模块、没时间搭基建的外包工程师。别被标题里的“医疗”吓住——它的业务逻辑比电商订单简单但数据一致性要求比博客系统严苛得多是练手前后端分离最不玄学、最不翻车的入口。2. 拆包即运行从解压到首页渲染的 4 个关键动作这个.zip不是玩具项目它默认按生产环境最小闭环设计后端用 SpringBoot 2.7.x兼容 JDK8、前端用 Vue 2.6 Vue Router 3 Vuex 3、数据库用 MySQL 5.7。所有依赖版本锁死在pom.xml和package.json里杜绝“我本地跑不通因为版本不一致”的玄学。下面这四步是我每次拿到新压缩包必做的验证链缺一不可。2.1 解压后第一件事确认目录结构是否符合前后端分离标准提示别急着cd进去mvn先看清楚根目录下有没有backend/和frontend/两个平行文件夹。如果只有src/或server/client/说明作者没严格遵循分离规范——后续跨域配置、打包路径、静态资源映射全要重调。正确结构长这样medical-system/ ├── backend/ # SpringBoot 工程含 pom.xml │ ├── src/ │ └── application.yml ├── frontend/ # Vue 工程含 package.json │ ├── src/ │ └── vue.config.js ├── database/ # SQL 初始化脚本含建库、建表、初始数据 │ └── medical_init.sql └── README.md如果看到frontend/dist/被直接塞进backend/src/main/resources/static/那是“伪分离”——Vue 打包后当静态资源扔进 SpringBoot开发时热更新失效调试成本翻倍。本项目严格物理隔离backend只管 APIfrontend只管页面靠vue.config.js的proxy或 Nginx 反向代理打通。2.2 后端启动前必须改的 3 处application.yml配置SpringBoot 默认配置在backend/src/main/resources/application.yml。不改这三项90% 的人卡在第一步spring: datasource: url: jdbc:mysql://localhost:3306/medical_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password_here # ← 必须填你本地 MySQL 密码 jpa: hibernate: ddl-auto: update # ← 开发期用 update上线必须改成 validate show-sql: true properties: hibernate: format_sql: true server: port: 8081 # ← 前端 proxy 默认指向 8081别和前端 devServer 冲突url中的medical_db是数据库名必须和database/medical_init.sql里CREATE DATABASE IF NOT EXISTS medical_db保持一致ddl-auto: update是双刃剑开发时自动建表省事但上线后可能误删字段。我习惯本地开发用update测试环境切validate生产环境直接关掉 JPA 自动建表用 Flyway 管理 SQL 版本server.port必须避开前端vue.config.js的devServer.port默认 8080否则npm run serve启动失败。2.3 前端启动前vue.config.js的代理配置是跨域救命稻草Vue CLI 3 项目跨域不靠后端加CrossOrigin而靠vue.config.js的devServer.proxy。本项目已预置但你得确认它指向正确的后端地址// frontend/vue.config.js module.exports { devServer: { port: 8080, proxy: { /api: { // ← 所有以 /api 开头的请求全部代理 target: http://localhost:8081, // ← 必须和 backend 的 server.port 一致 changeOrigin: true, pathRewrite: { ^/api: // ← 把 /api 前缀去掉再发给后端后端 Controller 用 RequestMapping(/user) 即可 } } } } }关键点target必须和backend的server.port完全一致哪怕你改了后端端口这里也得同步改pathRewrite是灵魂前端调axios.get(/api/user/list)代理后实际发给后端的是GET http://localhost:8081/user/list后端 Controller 就不用写RequestMapping(/api/user)避免路径嵌套混乱如果你用 Axios 封装了 request确保 baseURL 是/api而不是http://localhost:8081/api——后者在生产环境会因域名不同直接跨域失败。2.4 数据库初始化用medical_init.sql一键建库建表别手动 CREATEdatabase/medical_init.sql不是示例脚本是完整业务表结构基础数据。执行它前先确认 MySQL 已启动且你有root权限# Linux/macOS 终端 mysql -u root -p database/medical_init.sql # Windows 命令行需 MySQL bin 目录在 PATH mysql -u root -p database\medical_init.sql输入密码后脚本会自动创建medical_db数据库如果不存在创建 12 张表t_patient,t_doctor,t_department,t_schedule,t_prescription,t_drug,t_inventory,t_user,t_role,t_permission,t_log,t_dict插入 5 条初始用户含 admin/admin 密码的超级管理员设置外键约束和索引如t_prescription表的doctor_id关联t_doctor.id。注意如果执行报错ERROR 1045 (28000): Access denied说明 MySQL 密码不对如果报错ERROR 1064大概率是你用的 MySQL 版本低于 5.7本项目 SQL 用了JSON类型字段存处方明细MySQL 5.7 才支持。3. 接口联调从前端按钮点击到数据库落盘的全链路追踪光启动成功没用得让“挂号”按钮真正把数据写进数据库。本项目用典型的三层结构Vue 组件 → Vuex Action → Axios 请求 → SpringBoot Controller → Service → Mapper → MySQL。我们以“新增患者”为例走一遍真实链路。3.1 前端PatientAdd.vue里的提交逻辑与参数校验!-- frontend/src/views/patient/PatientAdd.vue -- template el-form :modelform :rulesrules refformRef el-form-item label姓名 propname el-input v-modelform.name / /el-form-item el-form-item label身份证号 propidCard el-input v-modelform.idCard / /el-form-item el-button typeprimary clicksubmitForm提交/el-button /el-form /template script import { addPatient } from /api/patient // ← 封装好的 API 方法 export default { data() { return { form: { name: , idCard: }, rules: { name: [{ required: true, message: 请输入姓名, trigger: blur }], idCard: [ { required: true, message: 请输入身份证号, trigger: blur }, { pattern: /^[\dXx]{15,18}$/, message: 身份证格式不正确, trigger: blur } ] } } }, methods: { submitForm() { this.$refs.formRef.validate(valid { if (valid) { addPatient(this.form) // ← 调用 API .then(res { this.$message.success(添加成功) this.$router.push(/patient/list) // ← 成功后跳转列表页 }) .catch(err { this.$message.error(err.response?.data?.msg || 添加失败) }) } }) } } } /script关键点addPatient()是封装在frontend/src/api/patient.js的 Axios 请求URL 是/api/patient/add对应后端PostMapping(/add)表单校验用 Element UI 的rules身份证正则^[\dXx]{15,18}$覆盖 15 位老证和 18 位新证.catch()里取err.response.data.msg说明后端返回了统一 JSON 结构{ code: 200, msg: 操作成功, data: {} }前端才能友好提示。3.2 后端Controller 层接收参数并委托 Service 处理// backend/src/main/java/com/medical/controller/PatientController.java RestController RequestMapping(/patient) public class PatientController { Autowired private PatientService patientService; PostMapping(/add) public Result add(RequestBody Patient patient) { // ← RequestBody 接收 JSON boolean success patientService.add(patient); return success ? Result.success() : Result.fail(添加失败); } }RequestBody是关键前端axios.post(/api/patient/add, { name: 张三, idCard: 110... })发 JSONSpringBoot 自动反序列化为Patient对象Result是项目自定义的统一响应类含code,msg,data字段避免每个接口都return new HashMap()Controller 只做参数接收和结果包装业务逻辑全在PatientService。3.3 数据持久化MyBatis-Plus 的save()如何保证主键和时间戳// backend/src/main/java/com/medical/service/impl/PatientServiceImpl.java Service public class PatientServiceImpl implements PatientService { Autowired private PatientMapper patientMapper; Override public boolean add(Patient patient) { // 设置创建时间 patient.setCreateTime(new Date()); // 设置状态1-有效0-禁用 patient.setStatus(1); // MyBatis-Plus 自动处理主键ID_WORKER 策略 return patientMapper.insert(patient) 0; } }Patient实体类已用 MyBatis-Plus 注解声明主键策略TableId(type IdType.ASSIGN_ID) // ← 雪花算法生成 Long 型 ID private Long id; TableField(fill FieldFill.INSERT) // ← 自动填充创建时间 private Date createTime;ASSIGN_ID生成 19 位数字 ID如1723456789012345678避免 MySQLAUTO_INCREMENT在分库分表时的冲突FieldFill.INSERT配合MetaObjectHandler实现自动填充无需在代码里new Date()insert()返回影响行数 0即成功比try-catch更轻量。3.4 数据库验证用 MySQL 命令行直查刚插入的数据别信前端弹窗用命令行验证数据是否真落库-- 连接数据库 mysql -u root -p medical_db -- 查看患者表 SELECT id, name, id_card, create_time, status FROM t_patient ORDER BY create_time DESC LIMIT 5;你应该看到类似---------------------------------------------------------------------------- | id | name | id_card | create_time | status | ---------------------------------------------------------------------------- | 1723456789012345678 | 张三 | 11010119900307271X | 2024-06-15 14:22:33 | 1 | ----------------------------------------------------------------------------id是 19 位数字证明雪花算法生效id_card保留原始大小写X 是大写说明数据库字段是VARCHAR(18)没做大小写转换status1说明业务状态字段被正确设置。4. 避坑指南我在部署这个医疗系统时踩过的 5 个真实坑这个项目看似开箱即用但实际部署时80% 的失败都来自环境细节。以下是我用三台不同配置服务器CentOS 7、Ubuntu 20.04、Windows Server 2019部署时反复验证过的血泪经验。4.1 现象前端npm run serve启动成功但浏览器访问http://localhost:8080显示空白页控制台报Failed to load resource: net::ERR_CONNECTION_REFUSED原因Vue DevServer 的proxy配置只在开发环境生效但你误以为它能代理生产环境请求。实际是npm run serve启动的是 Webpack DevServer它监听localhost:8080而proxy只把/api请求转发给localhost:8081但如果后端没启动或者target地址写错比如写成http://127.0.0.1:8081代理就失败前端拿不到数据页面空白。解决先cd backend mvn spring-boot:run确保后端在8081端口运行cd frontend npm run serve启动前端打开浏览器开发者工具 → Network 标签 → 点击“挂号”按钮看/api/patient/add请求是否 200如果显示pending或failed检查vue.config.js的target是否拼写错误常见把localhost写成locahost如果后端启动报错Address already in use: bind用lsof -i :8081macOS/Linux或netstat -ano | findstr :8081Windows查占用进程并 kill。4.2 现象后端启动报错java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException尤其在 JDK 11 环境原因SpringBoot 2.3 默认移除了 JAXBJava XML Binding但本项目pom.xml里spring-boot-starter-web依赖了旧版spring-boot-starter-tomcat而 Tomcat 9.0.31 需要 JAXB 支持。JDK 11 把 JAXB 移出标准库必须手动引入。解决在backend/pom.xml的dependencies里追加dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.1/version /dependency dependency groupIdorg.glassfish.jaxb/groupId artifactIdjaxb-runtime/artifactId version2.3.1/version /dependency提示如果你用 JDK 8此问题不存在但若用 JDK 17还需额外加--add-modules java.xml.bindJVM 参数不过本项目application.yml已预设spring.main.allow-bean-definition-overridingtrue兼容性更强。4.3 现象登录后跳转/dashboard页面显示404 Not Found但http://localhost:8080/#/dashboard手动输入能打开原因Vue Router 默认用history模式URL 无#但 SpringBoot 静态资源路径没配置 fallback。当用户直接访问http://localhost:8080/dashboard非首页Nginx/Apache 会尝试找dashboard.html文件找不到就 404而http://localhost:8080/#/dashboard是 hash 模式所有路由都由前端 JS 处理。解决开发期vue.config.js里devServer.historyApiFallback: true已开启所以npm run serve时没问题生产期必须在 SpringBoot 的application.yml加配置让所有非 API 请求都返回index.htmlspring: web: resources: static-locations: classpath:/static/,classpath:/public/ mvc: static-path-pattern: /static/** # ← 关键配置 WebMvcConfigurer 让 /dashboard 等路径 fallback 到 index.html并在backend/src/main/java/com/medical/config/WebConfig.java添加Configuration public class WebConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/).setViewName(forward:/index.html); registry.addViewController(/{spring:\\w}).setViewName(forward:/index.html); registry.addViewController(/**/{spring:\\w}).setViewName(forward:/index.html); } }4.4 现象MySQL 执行medical_init.sql报错ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes原因MySQL 5.7 默认innodb_large_prefixOFF且ROW_FORMATCOMPACT导致VARCHAR(255)字段建索引时超长UTF8MB4 编码下 255*41020 767。本项目t_patient.id_card字段是VARCHAR(18)但加了唯一索引触发此限制。解决登录 MySQL执行SET GLOBAL innodb_file_format Barracuda; SET GLOBAL innodb_file_per_table ON; SET GLOBAL innodb_large_prefix ON;修改medical_init.sql中建表语句在CREATE TABLE后加ROW_FORMATDYNAMIC例如CREATE TABLE t_patient ( id BIGINT PRIMARY KEY, name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL UNIQUE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 ROW_FORMATDYNAMIC;重新执行 SQL。4.5 现象医生排班页面日历组件不渲染控制台报TypeError: Cannot read property getFullYear of null原因前端src/components/ScheduleCalendar.vue依赖moment.js解析日期但package.json里moment: ^2.29.4是 peerDependencynpm install时未自动安装。解决cd frontend执行npm install moment2.29.4 --save检查node_modules/moment是否存在如果用 Vue 3 项目此组件需重写为dayjs但本项目是 Vue 2moment是刚需额外验证在main.js加import moment from moment; window.moment moment;确保全局可用。5. 生产就绪把 Vue 打包进 SpringBoot 的 3 种方式与选型建议很多新手以为“前后端分离”就是永远分开部署其实医疗系统上线时甲方往往只要一个jar包丢进服务器java -jar medical.jar就完事。本项目提供三种打包方案我按真实项目权重排序推荐。5.1 方案一Vue 打包为静态资源放入 SpringBootresources/static/推荐度 ★★★★★这是最稳妥的生产方案适合中小医院私有化部署。步骤极简# 1. 前端打包生成 dist/ 目录 cd frontend npm run build # 输出到 frontend/dist/ # 2. 把 dist/ 内容复制到后端资源目录 mkdir -p backend/src/main/resources/static cp -r dist/* backend/src/main/resources/static/ # 3. 后端打包此时 jar 包内含前端静态文件 cd backend mvn clean package -Dmaven.test.skiptrue # 生成 target/medical-0.0.1-SNAPSHOT.jar优势启动一个jar前端页面和后端 API 全部就绪运维零学习成本application.yml里spring.web.resources.static-locationsclasspath:/static/默认生效无需额外配置index.html里的script src/js/app.xxx.js路径自动匹配jar包内路径。注意vue.config.js的publicPath必须设为/不能是./或否则打包后 JS/CSS 路径错乱npm run build生成的dist/index.html会被 SpringBoot 的ResourceHttpRequestHandler自动映射为根路径/所以访问http://ip:8081即首页。5.2 方案二Nginx 反向代理前后端完全独立部署推荐度 ★★★★☆适合已有 Nginx 的医院信息科或需要 HTTPS、负载均衡的场景。配置如下# /etc/nginx/conf.d/medical.conf upstream backend { server 127.0.0.1:8081; # SpringBoot 后端 } server { listen 80; server_name medical.hospital.local; # 前端静态资源 location / { root /var/www/medical-frontend; # Vue build 后的 dist 目录 try_files $uri $uri/ /index.html; } # API 接口代理 location /api/ { proxy_pass http://backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }优势前端更新只需替换dist/文件后端jar不用重启Nginx 可做 Gzip 压缩、HTTP/2、SSL 终止性能优于 SpringBoot 内置 Tomcatlocation /api/的proxy_pass末尾带/确保/api/user/list被代理为/user/list。坑点try_files $uri $uri/ /index.html;是关键否则 Vue Routerhistory模式路由 404proxy_set_header必须传Host和X-Real-IP否则后端日志记录的 IP 是127.0.0.1。5.3 方案三Docker Compose 一键启停推荐度 ★★★☆☆适合有 Docker 基础的团队用docker-compose.yml管理 MySQL SpringBoot Nginx# docker-compose.yml version: 3.8 services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: medical_db volumes: - ./database/init.sql:/docker-entrypoint-initdb.d/init.sql ports: - 3306:3306 backend: build: ./backend depends_on: [mysql] environment: SPRING_PROFILES_ACTIVE: prod ports: - 8081:8081 nginx: image: nginx:alpine volumes: - ./frontend/dist:/usr/share/nginx/html - ./nginx.conf:/etc/nginx/nginx.conf ports: - 80:80 depends_on: [backend]优势docker-compose up -d一条命令启动全套环境环境一致性拉满volumes映射让前端dist/和 MySQL 初始化脚本可热更新depends_on保证 MySQL 先启动避免 SpringBoot 启动时报数据库连接失败。现实约束医院服务器未必装 Docker运维可能拒绝./database/init.sql必须是纯 SQL不能含\c medical_db等 mysql 客户端命令我一般只在测试环境用此方案生产仍推方案一。6. 真实业务增强给处方模块加 PDF 导出与药品库存预警光跑通 CRUD 不算完成医疗系统。我基于这个.zip项目在处方和药品模块加了两个高频需求代码少、效果实直接抄作业。6.1 处方 PDF 导出用jspdfhtml2canvas一行代码生成可打印处方单医疗场景必须支持处方单打印。本项目frontend/src/views/prescription/PrescriptionDetail.vue加了导出按钮template div refprescriptionEl h2处方单/h2 p患者{{ prescription.patientName }}/p p医生{{ prescription.doctorName }}/p table tr v-foritem in prescription.drugs :keyitem.id td{{ item.name }}/td td{{ item.dosage }}/td /tr /table /div el-button clickexportPDF导出 PDF/el-button /template script import html2canvas from html2canvas import jsPDF from jspdf export default { methods: { exportPDF() { const el this.$refs.prescriptionEl html2canvas(el).then(canvas { const imgData canvas.toDataURL(image/png) const pdf new jsPDF(p, mm, a4) const imgWidth 210 // A4 宽度 mm const pageHeight 297 const imgHeight canvas.height * imgWidth / canvas.width let heightLeft imgHeight let position 0 pdf.addImage(imgData, PNG, 0, position, imgWidth, imgHeight) heightLeft - pageHeight while (heightLeft 0) { position heightLeft - imgHeight pdf.addPage() pdf.addImage(imgData, PNG, 0, position, imgWidth, imgHeight) heightLeft - pageHeight } pdf.save(prescription_${this.prescription.id}.pdf) }) } } } /script依赖安装cd frontend npm install html2canvas jspdf --save效果点击按钮自动生成 A4 尺寸 PDF含处方抬头、患者信息、药品列表支持打印机直接输出。html2canvas截图精度够医疗文书要求jspdf分页逻辑已适配长处方。6.2 药品库存预警在t_drug表加min_stock字段后端定时扫描药品库存低于阈值必须告警。本项目在backend/src/main/java/com/medical/task/StockCheckTask.java加了 SpringBoot 定时任务Component Slf4j public class StockCheckTask { Autowired private DrugService drugService; // 每天凌晨 2 点执行 Scheduled(cron 0 0 0 2 * ?) public void checkLowStock() { ListDrug lowStockDrugs drugService.findLowStock(10); // 阈值设为 10 if (!lowStockDrugs.isEmpty()) { log.warn(【库存预警】以下药品库存不足{}, lowStockDrugs); // 此处可对接短信网关、企业微信机器人、邮件 sendAlertToAdmin(lowStockDrugs); } } private void sendAlertToAdmin(ListDrug drugs) { // 示例打印日志实际替换为 HTTP 调用 drugs.forEach(d - System.out.println(预警 d.getName() 剩余 d.getStock() 低于阈值 d.getMinStock()) ); } }数据库变更在t_drug表加min_stock INT DEFAULT 0字段并在medical_init.sql的INSERT INTO t_drug语句里补上初始值ALTER TABLE t_drug ADD COLUMN min_stock INT DEFAULT 0; UPDATE t_drug SET min_stock 50 WHERE id IN (1,2,3); -- 常用药设 50启用定时任务在backend/src/main/java/com/medical/MedicalApplication.java的启动类加EnableSchedulingSpringBootApplication EnableScheduling // ← 必加 public class MedicalApplication { public static void main(String[] args) { SpringApplication.run(MedicalApplication.class, args); } }价值不用额外装 Redis 或消息队列纯内存扫描5 行代码实现核心预警逻辑。医院药房每天清点前运维就能收到日志告警。我带三个实习生用这个.zip项目在两周内交付了某社区卫生服务中心的挂号系统 MVP。他们最大的收获不是学会了RestController而是明白了医疗系统的“高可用”不是堆服务器而是t_prescription表加UNIQUE KEY (patient_id, visit_date)防止重复挂号“安全性”不是加一堆加密而是t_user.password字段用BCryptPasswordEncoder加盐哈希“可维护性”不是写多炫酷的架构而是database/目录下每份 SQL 都带-- 2024-06-15 初始化患者表时间戳注释。这个压缩包的价值不在代码多精妙而在它把真实业务约束像盐粒一样融进了每一行INSERT和每一个Transactional里。希望帮到你。本文还有配套的精品资源点击获取