SpringBoot+Vue医院档案管理系统:毕设实战与部署全解析
每年到这个时候总有一批人开始为毕业设计挠头也有不少想转行做Java开发的朋友在找练手项目。我后台也经常收到这类私信问有没有既能过答辩、又能写进简历、最好还能让面试官多看两眼的完整项目。今天聊的这个SpringBootVue医院档案管理系统管理平台正好踩在这些需求点上。先把这个项目说清楚它是一套典型的前后端分离项目后端用SpringBoot整合MyBatis-Plus操作MySQL数据库前端用Vue 2配合Element-UI搭界面解决的核心问题是医院里患者档案、病历、科室医生等信息的数字化管理。为什么说它适合毕设、课设或者学习练手因为这个体量下的项目刚好能覆盖Java后端和前端Vue的完整开发链路——从数据库设计、RESTful接口开发到前端页面渲染、联调部署一整套走下来你对“一个Web系统是怎么从0到1跑起来的”会有直观认识。无论你是学生准备答辩还是自学Java打算找工作这个项目都能当做一个完整的实战样本。接下来我会从项目拆解、技术实现、部署启动到问题排查把整条链路掰开揉碎讲清楚最后还会额外聊聊答辩和简历包装的经验。1. 项目整体拆解这个系统到底在管什么1.1 核心业务模型医院档案管理系统核心管的就是“档案”。结合常见的毕设需求整个系统的业务模型通常围绕三类角色展开管理员、医生、护士或者前台登记人员。不同角色登录后看到的功能不一样这在前端路由守卫和后端接口权限上都会体现。从业务实体上看系统要管理的数据主体有这些患者档案姓名、性别、年龄、身份证号、联系方式、家庭住址、过敏史、既往病史等基础信息病历记录患者就诊产生的历次病历包括主诉、诊断结果、医嘱、检查检验数据科室信息医院内部科室的维护比如内科、外科、妇产科、儿科等医生信息姓名、职称、所属科室、排班情况、擅长方向系统用户登录账号、密码、角色、状态启用/禁用这套业务模型非常标准也很适合拿来做教学案例。你会发现这些实体之间的关系并不复杂但是足够让你练习到“一对多”“多对一”的关联查询比如查询某个患者的历次病历记录、按科室分组统计医生人数这些都是在课程设计里经常出现的需求点。我见过一些同学把系统设计得花里胡哨又是消息推送又是在线问诊结果开发到一半做不完最后草草提交。如果你也是第一次做这类项目我的建议是先从上面这几个核心实体出发把“患者档案的增删改查”“病历管理”“登录权限”做扎实了再考虑扩展功能。1.2 为什么选 SpringBoot Vue 这套组合说句实在话现在的Web开发圈子后端你绕不开SpringBoot前端你绕不开Vue或者React。毕设选型除了考虑能不能做出来还要考虑答辩时的说服力、写在简历上的含金量。SpringBoot的优势不用多讲自动配置帮我们省掉了大量XML配置一个SpringBoot项目可以通过spring-boot-starter-web快速构建Web服务内置Tomcat打包成jar就能直接跑这对学生党来说非常友好。医院档案管理系统这种体量用SpringBoot是标准答案。前端选Vue本质上是因为它的学习曲线在三大框架里是比较平缓的。Vue的双向数据绑定、组件化开发、Vue Router管理路由这些概念学起来不需要太深的前端功底。配合Element-UI这套组件库你可以快速拼出后台管理系统的页面包括表格、弹窗、表单、分页、日期选择器这些常用组件都能直接用。再来说MySQL这个没什么可犹豫的开源免费、资料多、面试常问是当前Java技术栈里的“标配数据库”。它的InnoDB引擎支持事务对于医院档案系统里“一个患者基本信息他的一条病历记录”这种需要同时写入多个表的场景事务能保证数据的一致性。这套技术栈还有一个隐性优势面试官熟悉。去面试Java开发岗位你说你做过SpringBootVue的项目对方大概率会顺着往下问“项目表怎么设计的”“接口怎么鉴权的”“遇到过什么线上问题”这些都是你可以在项目里提前准备好的提问点。1.3 功能模块划分与页面结构从前端路由的角度这个项目的页面结构大概长这样登录页面用户名密码登录成功后跳转到首页首页/仪表盘显示系统统计信息比如患者总数、今日就诊人次、科室数量患者档案管理患者列表、条件搜索按姓名/身份证号、新增/编辑/删除患者、查看详情病历管理按患者关联病历列表、新增病历、编辑诊断信息科室管理科室列表、新增/编辑科室、科室下的医生列表医生管理医生信息维护、按科室筛选、设置账号绑定系统用户管理管理员维护用户账号、重置密码、启停用账号每个页面基本都对应一个后端Controller的一组接口这就是前后端分离项目最典型的结构。你按这个结构去开发心里会有非常清晰的脉络不会东一榔头西一棒子。2. 核心功能与关键技术实现2.1 后端架构分层设计与关键接口SpringBoot项目开发模块分层是最基本的功夫。我习惯把代码按照下面的分包结构来组织清晰而且便于答辩讲解com.hospital.archives ├── controller // 接口层接收前端请求 ├── service // 业务逻辑层处理具体业务 ├── mapper // 数据访问层操作数据库 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象接口入参出参 ├── config // 配置类比如跨域、拦截器 ├── common // 公共类统一返回结果、异常处理 └── utils // 工具类比如JWT工具Controller层只做参数接收和结果返回不写业务逻辑Service层处理业务Mapper层用MyBatis-Plus操作数据库。这样分层的最大好处是答辩老师问“这段查询逻辑写在哪里”的时候你可以很明确地说出代码所在的位置体现你的工程素养。核心的几个接口设计大致是这些接口路径请求方式功能说明/api/loginPOST登录认证返回JWT令牌/api/patient/pageGET分页查询患者列表/api/patientPOST新增患者档案/api/patient/{id}PUT更新患者档案/api/patient/{id}DELETE删除患者档案/api/medical-record/page/{patientId}GET按患者分页查询病历/api/dept/listGET科室列表/api/doctor/pageGET分页查询医生信息拿“分页查询患者列表”来说用MyBatis-Plus的Page对象和LambdaQueryWrapper几行代码就能搞定GetMapping(/page) public ResultIPagePatient page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, String keyword) { PagePatient page new Page(pageNum, pageSize); LambdaQueryWrapperPatient wrapper new LambdaQueryWrapper(); if (StringUtils.hasText(keyword)) { wrapper.like(Patient::getName, keyword) .or().like(Patient::getIdCard, keyword); } wrapper.orderByDesc(Patient::getCreateTime); return Result.success(patientService.page(page, wrapper)); }这段代码虽然简单但里面包含了好几个可以扩展讲的点为什么要用LambdaQueryWrapper而不是直接写SQL答案是它既能避免硬编码字段名、编译期就能发现错误又兼顾了简单的动态条件拼接。如果查询逻辑再复杂一些再考虑在Mapper.xml里写自定义SQL。2.2 数据库设计档案表这么建才合理数据库设计是这种管理系统的灵魂也是答辩时老师大概率会问的部分。我直接把常用的建表SQL结构给你你自己做的时候可以在这个基础上调整。患者档案表是核心表CREATE TABLE patient ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, patient_no varchar(32) NOT NULL COMMENT 档案编号, name varchar(50) NOT NULL COMMENT 姓名, gender tinyint(1) DEFAULT NULL COMMENT 性别 1男 0女, age int(11) DEFAULT NULL COMMENT 年龄, id_card varchar(18) DEFAULT NULL COMMENT 身份证号, phone varchar(11) DEFAULT NULL COMMENT 手机号, address varchar(255) DEFAULT NULL COMMENT 家庭住址, allergy_history varchar(255) DEFAULT NULL COMMENT 过敏史, past_history varchar(500) DEFAULT NULL COMMENT 既往病史, status tinyint(1) DEFAULT 1 COMMENT 状态 1有效 0删除, create_time datetime DEFAULT NULL COMMENT 创建时间, update_time datetime DEFAULT NULL COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY idx_patient_no (patient_no), KEY idx_name (name) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT患者档案表;病历记录表与患者表是多对一的关系外键逻辑体现在patient_id上CREATE TABLE medical_record ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, patient_id bigint(20) NOT NULL COMMENT 患者ID, diagnosis varchar(500) DEFAULT NULL COMMENT 诊断结果, complaint varchar(500) DEFAULT NULL COMMENT 主诉, advice varchar(500) DEFAULT NULL COMMENT 医嘱, doctor_id bigint(20) DEFAULT NULL COMMENT 接诊医生ID, visit_time datetime DEFAULT NULL COMMENT 就诊时间, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (id), KEY idx_patient_id (patient_id) ) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8mb4 COMMENT病历记录表;这里有几个设计细节你在答辩时拿出来讲会非常加分第一个是逻辑删除。表中的status字段不是摆设删除患者档案时做的是UPDATE patient SET status 0 WHERE id ?而不是物理DELETE。原因很简单医院系统的数据有审计需求患者来过医院、看过病这些记录不能随便抹掉。MyBatis-Plus里可以通过TableLogic注解非常优雅地实现逻辑删除。第二个是档案编号patient_no的生成方式。可以用时间戳加随机数也可以用数据库自增ID拼接固定前缀比如P2024120110001。这个编号拉出来就是“2024年12月1日第10001个建档”的意思方便统计和检索。第三是字符集选择utf8mb4。我知道很多老教程还在用utf8但当患者姓名里出现生僻字或者特殊表情字符时utf8会直接报错或者乱码。你装MySQL 8.0的时候默认字符集就是utf8mb4所以建表时统一带上这个参数省心不少。2.3 前端页面与交互逻辑Vue前端部分我用Vue 2 Element-UI这套经典组合来讲解因为网上资料最多遇到问题基本都能查到解决方案。项目初始化用Vue CLI或者Vite都可以但如果你要兼容性最好、问题最少Vue CLI 4.x配Node 14/16是稳妥的选择。前端目录结构大概是这样的src ├── api // 接口请求封装 │ ├── patient.js │ ├── record.js │ └── login.js ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // Vuex状态管理 ├── views // 页面组件 │ ├── Login.vue │ ├── Dashboard.vue │ ├── patient │ │ ├── PatientList.vue │ │ └── PatientEdit.vue │ ├── record │ │ └── RecordList.vue │ └── dept │ └── DeptList.vue └── utils └── request.js // axios封装request.js是整个前后端联调的关键我通常在里面做三件事统一请求baseURL、附带token到请求头、统一处理错误码并弹出提示。代码大概长这样import axios from axios import { Message } from element-ui import router from /router const request axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { Message.error(登录状态已过期请重新登录) localStorage.removeItem(token) router.push(/login) } else { Message.error(error.message) } return Promise.reject(error) } ) export default request这段封装代码建议你理解透因为它是面试时很容易被追问的话题为什么用拦截器token过期了前端怎么处理你只要把上面的逻辑讲清楚面试官基本能确认你确实写过真实项目而不是只会跑通Demo。患者列表页面用Element-UI的el-table展示配合el-pagination做分页搜索区用el-input和el-select组合。核心交互逻辑就是点击搜索按钮时把搜索条件绑定到查询参数重新调用接口拉取第一页数据。2.4 登录鉴权JWT方案登录鉴权这块我强烈建议直接用JWT。前后端分离项目里Session方案需要处理跨域Cookie还要配置CORS的allowCredentials麻烦不说在离线和分布式部署场景下也很容易出现会话不同步的问题。JWT的方案是用户登录成功后后端用密钥生成一个包含用户ID、用户名、角色等信息的令牌返回给前端前端把令牌存到localStorage之后每次请求都在Authorization头里带上这个令牌后端通过拦截器验证令牌解析出用户信息后放行请求。SpringBoot后端配合jjwt库实现非常简单Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire}) private Long expire; public String generateToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expire)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }拦截器里对/api/login放行其余接口校验token。这里有一个容易踩的坑如果前端请求带上了token但因为过期或签名错误解析失败你需要在拦截器里返回401状态码而不是抛异常让Spring默认处理成500。前端的响应拦截器拿到401后才会做“跳回登录页”的逻辑你要是返回500前端就只会弹一个“系统错误”的提示用户体验很差答辩演示的时候也很尴尬。3. 环境准备与项目部署实操3.1 JDK、Maven、Node、MySQL环境准备先说你本机需要准备的环境。我把版本和用途都列出来你照着配就行。JDK 1.8或者JDK 11推荐JDK 8因为SpringBoot 2.x系列对它支持最好网上资料也最多Maven 3.6以上用来管理后端依赖只用它打包就够了Node.js 14.x或者16.x建议别用太高版本比如Node 20老项目依赖可能出兼容性问题MySQL 8.0安装时注意设置root密码字符集选utf8mb4环境变量配置这个事每年都有无数人卡在这里。JDK配JAVA_HOME和PATHMaven配MAVEN_HOME和PATHNode安装包一般会自动配好。配完之后在命令行分别输入java -version、mvn -v、node -v验证一下绿色版本号出来就说明没问题。MySQL 8.0的安装配置我多说两句。下载的时候选MySQL Installer for Windows那个社区版就行安装类型选Server only也可以后面用命令行或者可视化工具连。安装过程中会要求设置root密码千万别设完就忘后续数据库初始化要用。如果你的项目需要本地运行Navicat或者MySQL Workbench二选一装一个用来导入SQL脚本和查看数据。3.2 数据库初始化与后端启动拿到项目源码后初次启动的流程最重要的就是“先建库、再导表结构、最后改配置”。第一步在MySQL里创建数据库CREATE DATABASE hospital_archives DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第二步把项目里提供的hospital_archives.sql导入进去。用命令行的话mysql -u root -p hospital_archives hospital_archives.sql第三步修改后端配置文件application.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/hospital_archives?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你自己的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: status logic-delete-value: 1 logic-not-delete-value: 0这里配置了MyBatis-Plus的逻辑删除也就是说所有带status字段的表执行删除操作时都会自动转成更新语句。这个配置最后两行非常重要logic-delete-field声明的是实体类里的哪个字段是逻辑删除字段。第四步在项目根目录执行mvn spring-boot:run或者先打包再运行mvn clean package -DskipTests java -jar target/hospital-archives-0.0.1-SNAPSHOT.jar看到类似Started HospitalArchivesApplication in 3.21 seconds的日志说明后端启动成功。建议装上Lombok插件不然实体类的getter/setter会报红虽然不影响运行但看着闹心。3.3 前端启动与联调配置前端部分进入项目前端目录后先安装依赖npm install如果npm install报错优先检查Node版本是不是太新可以用nvm切到Node 14再试。还不行就换镜像源npm config set registry https://registry.npmmirror.com依赖装好后启动开发服务器npm run serve默认端口是8080但后端也占了8080所以这里要把前端端口改掉。在vue.config.js里配置module.exports { devServer: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这个proxy配置是关键。前端页面访问/api/patient/page开发服务器会把请求代理到http://localhost:8080/api/patient/page。这就解决了跨域问题因为浏览器看起来请求是同源的。这是前后端分离联调时最常用的方案比在Vue里配置完整的baseURL要简单可靠得多。启动完成后浏览器访问http://localhost:3000用管理员账号登录就能看到系统首页了。需要额外提醒一点代理配置只对开发环境生效。如果项目要部署到服务器上你需要用Nginx把前端构建产物npm run build生成的dist目录和后端接口放在同一个域名下通过Nginx的location /api配置反向代理到后端服务。这个属于部署层面的进阶操作毕设一般演示到本地运行就足够但如果写在简历里建议把Nginx部署也理解了。4. 常见问题与排查技巧实录4.1 后端启动类问题后端启动失败是遇到最多的问题而且90%都出在数据库连接上。具体表现是启动日志里报Cannot create PoolableConnectionFactory常见原因有三个。第一个是MySQL服务没启动Windows下到“服务”里手动启动MySQL服务第二个是application.yml里的密码不对检查一下有没有写错密码第三个是驱动类问题如果你用的MySQL 8.0驱动类必须写com.mysql.cj.jdbc.Driver而不是老版本的com.mysql.jdbc.Driver连接串里还必须加上serverTimezoneAsia/Shanghai否则会报时区错误。端口被占用也很常见。如果8080端口被其他进程占了后端启动会报Port 8080 was already in use。Windows下用命令查出占用进程netstat -ano | findstr 8080然后任务管理器里结束对应PID的进程或者在application.yml里换一个端口。还有一个很隐蔽的坑如果你的JDK版本是17而SpringBoot是2.x打包时经常会出现Unable to load class这种问题。SpringBoot 2.x基于JDK 8开发虽然也能跑在JDK 17上但会遇到java.lang.reflect.InaccessibleObjectException这类反射访问异常。最稳妥的方案就是JDK 8配合SpringBoot 2.7.x省心省力。4.2 前端联调与兼容性问题前端最常踩的坑是页面能打开但接口请求报404或者跨域错误。404问题一般出在接口路径写错。打开浏览器的F12开发者工具切换到大标题Network面板看请求的URL完整路径是什么跟前端request.js里的baseURL和后端Controller里的RequestMapping拼接后的地址对一下。这里有一个很容易迷惑的点如果你在前端请求路径里写了/api/patient/page但控制器里已经有一个/api前缀合起来就会变成/api/api/patient/page自然404。跨域问题在配置了proxy之后基本不会再出现。如果你不用proxy而直接用axios.defaults.baseURL http://localhost:8080那一定会遇到CORS跨域错误。解决方式是后端写一个CorsConfig配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }但我更推荐的方案是用vue.config.js的proxy代理因为跨域配置能少写一点。两种方案你至少理解一种答辩时被问到“前后端联调怎么解决跨域”才能答上来。页面白屏问题大概率是Element-UI没按需引入后没注册组件或者路由配置里的component路径写错。Vue Router报[Vue warn]: Failed to resolve component错误时优先检查路由表里的组件路径是否能对上实际文件位置。4.3 答辩与文档准备的加分技巧项目做完了怎么把它讲得有说服力这可能是比写代码更重要的环节。重点准备这三块内容第一块是数据库设计思路。从“系统需要管理哪些数据”出发解释为什么患者和病历要拆成两张表为什么要用逻辑删除而不是物理删除为什么字符集要用utf8mb4。把这些讲清楚就算成功了一半。第二块是接口设计规范。准备一两个典型接口讲清楚从浏览器到Controller再到Service再到Mapper的完整调用链路。比方说“分页查询患者列表”这个接口前端传pageNum和pageSize后端返回什么格式的数据前端怎么解析渲染成表格。这个链路能讲明白说明你不是在背代码而是真的理解了系统的运行流程。第三块是JWT鉴权机制。从“为什么不用Session”到“JWT的组成结构”到“前端的token存储方式和拦截器处理逻辑”这是一条完整的知识链。这三个问题串起来面试官对你的评价不会低。还有一个小建议把项目跑起来之后多演示几遍“新增患者→为患者添加病历→按条件搜索患者→查看医生和科室统计”这条完整流程确保每一步都顺畅。答辩演示环节最怕的就是现场报错提前演练能帮你规避绝大多数低级问题。写在最后我这些年接触过不少做毕设和自学Java的同学发现一个共性大家不是缺资料而是缺一个“能跑通的完整闭环”。一个项目能不能跑起来和代码写得好不好是两回事但能把一个项目从头到尾跑通再把这个过程中的技术点理解到位你获得的成长会远超刷几十道面试题。这套SpringBootVue的医院档案管理系统源码如果你拿到的版本存在一些小Bug我的建议是不要慌那反而是最好的学习素材。顺着报错信息一层层往下查你会把SpringBoot的启动流程、MyBatis的执行机制、Vue的生命周期都串起来。最后还是想强调一句源码可以帮你节省造轮子的时间但千万不要整个项目直接交上去当作业。你要做的是看懂它的设计思路改掉一批接口让它变成“你自己的系统”比如给患者档案增加一个导出Excel功能或者给病历模块增加一个附件上传功能甚至只是把前端主题色改得更统一。这些微创新会让你的毕设或者简历瞬间有了辨识度。