ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue敬老院管理系统毕设实战指南

SpringBoot+Vue敬老院管理系统毕设实战指南 简介这是一套面向计算机专业本科生的高分毕业设计级敬老院管理系统源码适用于毕设开发、课程设计及前端Vue后端Spring Boot全栈实战训练。系统覆盖老人信息管理、护理排班、健康档案、家属沟通等核心业务模块代码经导师指导与多轮调试已通过98分答辩验收可直接部署使用。资源包共593个文件含195个Java后端逻辑文件、67个Vue组件页面、161个SVG图标资源、67张JPG/PNG界面素材及配套XML配置、JS交互脚本、YML环境配置等结构完整、职责清晰压缩后仅13.43MB轻量易导入。目前已有108人学习下载配套包含.bat一键启动脚本、.vue.bak备份文件及完整项目工程结构含mvnw、pom.xml、src目录体系便于理解前后端分离架构与实际工程规范是入门企业级Web开发的优质实践样本。1. 敬老院管理系统为什么是 SpringBoot Vue 毕设的「稳赢组合」它真能跑通老人健康监测、护工排班、家属联动这三件硬事这不是一个“套模板就能交差”的毕设项目。你打开 GitHub 或某源码平台搜“敬老院管理系统”会发现大量项目卡在「登录页能进首页空白点击床位管理直接 404」——表面是 Vue 页面渲染失败根子却是 SpringBoot 后端没暴露/api/beds接口或 MyBatis 查询语句里status ?写成了status ?。真正高分毕设的分水岭不在界面有多炫而在老人跌倒告警能否触发短信通知、护工交接班记录能否按时间轴回溯、家属端查看用药记录时是否自动脱敏身份证号。这套系统必须同时扛住三类压力一是真实业务逻辑的嵌套比如“今日未巡房老人”要关联护理计划实时定位历史异常标记二是多角色权限的细粒度控制管理员可删数据护工只能改状态家属只读不可导出三是部署后不靠重启就能热更新排班表。我带过 12 届毕设凡是最终答辩被追问“如果张奶奶凌晨三点心率突降系统怎么闭环响应”的同学代码里都藏着一个Scheduled(fixedDelay 30000)定时任务 阿里云短信 SDK 的硬编码调用。别信“前后端分离就万事大吉”Vue 里v-ifuser.role family看似安全但后端 Controller 少写一行PreAuthorize(hasRole(FAMILY))家属账号就能 curl 出全院用药清单。这篇笔记就从你 clone 下来那个 zip 包开始手把手拆解怎么让这套代码从“能编译”变成“敢上线试运行”。2. 本地跑通最小可运行系统SpringBoot 后端启动 Vue 前端联调的 5 个关键动作2.1 检查 JDK 和 Node.js 版本不是越高越好而是要匹配 pom.xml 和 package.json 的真实依赖很多同学一上来就java -version显示 21node -v显示 20然后mvn clean install报错Unsupported class file major version 65。这不是环境问题是版本错配。打开项目根目录下的pom.xml找到java.version标签——常见高分毕设用的是1.8或11再打开frontend/package.json看engines字段里的node值通常是16.14.0或18.17.0。血泪经验SpringBoot 2.7.x 默认要求 JDK 8–17Vue CLI 4.x 要求 Node.js 12–18强行用 JDK 21 会导致 Lombok 注解处理器失效Data类编译后没有 getter 方法后端返回空对象。正确做法是# 查看当前 JDK java -version # 若为 21切换到 JDK 11以 macOS 为例Linux/Windows 同理 export JAVA_HOME$(/usr/libexec/java_home -v 11) # 查看 Node.js node -v # 若为 20用 nvm 切换提前安装 nvm nvm install 16.14.0 nvm use 16.14.0提示pom.xml中spring-boot.version是核心线索。若为2.7.18JDK 必须 ≤17若为3.1.5则需 JDK 17。不要凭直觉猜直接 grepgrep spring-boot.version pom.xml。2.2 后端数据库初始化H2 内存库仅用于演示MySQL 才是毕设答辩现场的真实考题源码里通常自带src/main/resources/application-h2.yml和application-mysql.yml。但新手常犯的错是只改了application.yml里的spring.profiles.active: h2却没删掉 H2 控制台路径/h2-console——结果答辩时老师点开控制台看到全是测试数据问“你们敬老院真用内存数据库存老人病历”当场扣分。正确路径是安装 MySQL 8.0推荐 Docker 快速启动docker run -d --name mysql-elderly -p 3306:3306 -e MYSQL_ROOT_PASSWORDelderly123 -e MYSQL_DATABASEelderly_db -v $(pwd)/mysql-data:/var/lib/mysql -d mysql:8.0修改application-mysql.ymlspring: datasource: url: jdbc:mysql://localhost:3306/elderly_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai username: root password: elderly123 sql: init: mode: always # 强制每次启动执行 schema.sql确保src/main/resources/sql/下有schema.sql建表和data.sql初始数据其中data.sql至少包含 3 条模拟老人记录、2 名护工、1 名管理员。注意schema.sql中elderly_info表必须含id_card_no VARCHAR(18)字段且加COMMENT 身份证号脱敏存储——这是答辩时老师必问“如何保护隐私”的伏笔。2.3 Vue 前端代理配置解决跨域不是加proxy就完事而是要对齐后端 API 前缀vue.config.js里常见的错误写法devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }看似正确但实际请求发出去是GET /api/beds而后端 Controller 的RequestMapping(/api/beds)却定义在BedController.java里——问题在于SpringBoot 默认的server.servlet.context-path是/但很多毕设源码为了区分环境把后端 API 统一加了/elderly前缀。你得先确认后端application.yml是否有server: servlet: context-path: /elderly如果有前端代理必须同步proxy: { /elderly: { // 关键不是 /api target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/elderly: } // 把 /elderly 去掉否则后端收不到 } }然后 Vue 组件里调用接口时写axios.get(/elderly/api/beds) // 代理后实际请求 http://localhost:8080/api/beds2.4 启动顺序与端口检查后端必须先于前端启动且端口不能被 IDEA 或 Chrome 占用常见翻车场景npm run serve启动后报Error: listen EADDRINUSE: address already in use :::8080。你以为是后端占了 8080其实可能是 Chrome 浏览器某个调试进程在监听。标准启动流程先启动后端IDEA 中右键ElderlyApplication.java→ Run观察控制台输出Tomcat started on port(s): 8080 (http)打开http://localhost:8080/elderly/swagger-ui.html确认 Swagger 文档能加载说明后端 API 已就绪。再启动前端cd frontend npm install # 若首次运行确保 node_modules 完整 npm run serve若仍报端口占用用命令查谁在用# macOS/Linux lsof -i :8080 # Windows netstat -ano | findstr :8080提示Swagger UI 是你的第一道验证关卡。点开/api/elderly/health/list接口执行 Try it out返回{code:200,data:[...]}才算后端真正活了。别急着切 Vue 页面。3. 核心业务模块落地老人档案、护工排班、家属端联动的三层数据流实现3.1 老人档案模块用 MyBatis Plus 实现动态条件查询 敏感字段脱敏敬老院最怕“查不到人”——不是数据库没数据而是查询条件太死板。比如老师问“请查出所有患有高血压且近 7 天未做血压测量的老人”传统 SQL 写法要拼WHERE disease LIKE %高血压% AND last_bp_time DATE_SUB(NOW(), INTERVAL 7 DAY)但业务变化后last_bp_time可能变成last_vital_sign_time字段名一改SQL 就崩。高分做法是用 MyBatis Plus 的QueryWrapper动态构建// ElderlyService.java public PageElderlyInfo listByConditions(ElderlyQuery query, PageElderlyInfo page) { QueryWrapperElderlyInfo wrapper new QueryWrapper(); if (StringUtils.isNotBlank(query.getDisease())) { wrapper.like(disease, query.getDisease()); } if (query.getLastBpDays() ! null query.getLastBpDays() 0) { wrapper.lt(last_bp_time, DateUtils.addDays(new Date(), -query.getLastBpDays())); } if (query.getStatus() ! null) { wrapper.eq(status, query.getStatus()); } return this.page(page, wrapper); }参数说明ElderlyQuery是自定义 DTO含disease疾病关键词、lastBpDays血压未测天数、status在院/出院。这样前端传{ lastBpDays: 7 }后端自动转成时间条件字段名变更只需改 DTO不碰 SQL。身份证号脱敏是答辩加分项在ElderlyInfo实体类中用TableField(select false)隐藏原始字段另加idCardMasked计算属性TableField(exist false) private String idCardMasked; JsonIgnore private String idCardNo; // 数据库真实字段 public String getIdCardMasked() { if (StringUtils.isNotBlank(idCardNo)) { return idCardNo.substring(0, 3) **** idCardNo.substring(13); } return ; }前端直接取idCardMasked永远看不到完整号。3.2 护工排班模块用 Quartz 实现周排班自动生成 手动微调覆盖排班不是“填表格”而是“规则引擎”。毕设里常见错误是用ListShift存一周排班手动增删——结果老师问“如果护工王芳下周二请假怎么快速调整全周排班”答不上来。正确方案是分两层基础排班规则Quartz 定时任务每周日凌晨 2 点自动生成下周排班表人工覆盖层数据库 override 表当王芳请假插入一条override记录优先级高于规则// WeeklyScheduleJob.java Component DisallowConcurrentExecution public class WeeklyScheduleJob implements Job { Override public void execute(JobExecutionContext context) throws JobExecutionException { // 1. 查询所有在职护工 ListNurse nurses nurseService.list(new QueryWrapperNurse().eq(status, 1)); // 2. 按轮班规则生成下周排班如早班 3 人晚班 2 人每人每周休 2 天 ListShiftPlan plans generateWeeklyPlans(nurses); // 3. 保存到 shift_plan 表但先清空本周数据 shiftPlanService.remove(new QueryWrapperShiftPlan().ge(date, LocalDate.now())); shiftPlanService.saveBatch(plans); } }覆盖逻辑在 Service 层public ShiftPlan getShiftPlan(LocalDate date, Long nurseId) { // 先查 override 表 ShiftOverride override shiftOverrideService.getOne( new QueryWrapperShiftOverride().eq(date, date).eq(nurse_id, nurseId) ); if (override ! null) { return convertToPlan(override); // 返回覆盖后的班次 } // 再查基础排班表 return shiftPlanService.getOne( new QueryWrapperShiftPlan().eq(date, date).eq(nurse_id, nurseId) ); }表结构关键点shift_override表必须含date,nurse_id,shift_type,status1已排班0请假且加唯一索引(date, nurse_id)。答辩时老师点开数据库看到shift_override有数据就知道你实现了“人工干预”。3.3 家属端联动WebSocket 实时推送 JWT 权限隔离家属最关心“老人现在在哪、是否安全”。很多源码用轮询setInterval(() axios.get(/api/elderly/location), 5000)但老师会问“100 个家属同时轮询服务器扛得住”——答案是否定的。高分方案是 WebSocket JWT 鉴权后端启用 WebSocketConfiguration EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new ElderlyLocationHandler(), /ws/location) .setAllowedOrigins(*); } }前端建立连接时携带 JWT// login success, get token const token response.data.token; const ws new WebSocket(ws://localhost:8080/elderly/ws/location?token${token}); ws.onmessage (event) { const data JSON.parse(event.data); if (data.elderlyId this.elderlyId) { this.location data.position; // 实时更新地图坐标 } };后端 Handler 中校验 Token 并绑定家属与老人关系public class ElderlyLocationHandler extends TextWebSocketHandler { Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { String token session.getUri().getQuery(); // 解析 JWT获取家属 ID 和关联的老人 ID Long familyId JwtUtil.parseFamilyId(token); Long elderlyId familyElderlyService.getElderlyIdByFamily(familyId); // 将 session 存入 Mapkey 为 elderlyId sessionRegistry.put(elderlyId, session); } }关键细节JWT 必须含family_id和elderly_ids家属可查看的老人 ID 数组否则无法做权限隔离。答辩时老师会要求你演示“家属 A 登录后只能看到自己父母的位置看不到邻居张奶奶”。4. 高频避坑指南5 个让毕设答辩当场卡壳的致命细节4.1 现象Vue 页面显示 “Network Error”但后端日志无任何请求记录原因前端axios默认 baseURL 是/而你后端server.servlet.context-path设为/elderly导致请求发到http://localhost:8080/api/beds404而非http://localhost:8080/elderly/api/beds。解决在frontend/src/utils/request.js中显式设置 baseURLconst request axios.create({ baseURL: process.env.NODE_ENV production ? /elderly // 生产环境走 Nginx 代理/elderly 前缀由 Nginx 去掉 : http://localhost:8080/elderly, // 开发环境直接指向后端 timeout: 10000 });4.2 现象Swagger UI 能访问但点击接口 “Try it out” 报 401 Unauthorized原因Spring Security 默认拦截所有/api/**请求而 Swagger 的/v3/api-docs和/swagger-ui.html未放行。解决在SecurityConfig.java中添加放行规则Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(/elderly/v3/api-docs/**, /elderly/swagger-ui.html, /elderly/webjars/**, /elderly/swagger-resources/**).permitAll() .antMatchers(/elderly/api/**).authenticated() .and() .formLogin().disable() .csrf().disable(); }注意antMatchers的路径必须带/elderly前缀否则放行无效。4.3 现象MySQL 启动后SpringBoot 报Access denied for user root172.17.0.1原因Docker 容器内网 IP如172.17.0.1未被 MySQL 用户授权rootlocalhost不等于root%。解决进入容器执行授权docker exec -it mysql-elderly mysql -uroot -pelderly123 mysql CREATE USER root% IDENTIFIED BY elderly123; mysql GRANT ALL PRIVILEGES ON *.* TO root% WITH GRANT OPTION; mysql FLUSH PRIVILEGES;4.4 现象家属登录后Vue 控制台报Cannot read property name of undefined原因前端 Vuex store 中state.familyInfo初始化为空对象{}但组件里直接写{{ familyInfo.name }}而name是异步加载的。解决在组件中用v-if或可选链!-- 错误 -- div{{ familyInfo.name }}/div !-- 正确 -- div v-iffamilyInfo{{ familyInfo.name }}/div !-- 或 -- div{{ familyInfo?.name || 加载中... }}/div同时在store/modules/family.js的state中预设默认值state: { familyInfo: { id: null, name: , phone: , elderlyList: [] } }4.5 现象打包后npm run build生成的dist文件放到 Nginx 报 404原因Vue Router 默认用history模式Nginx 未配置try_files导致/elderly/beds这类路由找不到静态文件。解决修改nginx.conflocation /elderly { alias /path/to/dist/; try_files $uri $uri/ /elderly/index.html; # 关键把所有路径 fallback 到 index.html }并确保vue.config.js中publicPath设置为/elderly/module.exports { publicPath: process.env.NODE_ENV production ? /elderly/ : / }5. 毕设答辩前的终极验证清单3 个必须现场演示的“灵魂拷问”场景5.1 场景一老人突发跌倒系统如何完成“检测→告警→处置→归档”闭环这不是让你背流程而是现场操作。老师会说“现在模拟张奶奶在 302 房间跌倒请演示系统响应。” 你必须触发检测在数据库elderly_location表中手动更新张奶奶id101的status为FALLupdated_time为当前时间验证告警后端控制台应打印Fall detected for elderly 101, sending SMS...查看sms_log表确认有新记录content含“张奶奶于[时间]在302房间跌倒请立即处理”护工手机收到短信提前配好阿里云短信模板测试用13800138000处置与归档护工登录系统在“告警中心”点击“已处理”状态变为HANDLED查看incident_record表该事件handled_time不为空handler_id为护工 ID家属端 App或网页收到 WebSocket 推送“张奶奶跌倒事件已处理”。关键点整个过程必须在 2 分钟内完成且每一步都有数据库或日志证据。别只说“我们设计了这个流程”要让老师亲眼看到sms_log.created_time和incident_record.handled_time的时间戳。5.2 场景二护工交接班时如何保证“上一班未完成事项”自动带入下一班老师会质疑“交接班只是换个人怎么确保责任不丢失” 你需要打开shift_handover表展示idshift_datefrom_nurse_idto_nurse_idunfinished_tasksstatus1232024-06-15201202[{task:给李爷爷喂药,done:false},{task:检查301空调,done:true}]PENDING然后演示护工 201 在下班前勾选“给李爷爷喂药”为未完成提交护工 202 登录后首页“待办事项”列表自动出现该条202 点击“完成”数据库unfinished_tasks中该项done变为true且status更新为COMPLETED。技术要点unfinished_tasks字段用 JSON 类型MySQL 5.7避免建关联表。答辩时老师会问“JSON 字段怎么建索引”答“用虚拟列 函数索引例如ALTER TABLE shift_handover ADD COLUMN task_done TINYINT AS (JSON_EXTRACT(unfinished_tasks, $[0].done)) STORED; CREATE INDEX idx_task_done ON shift_handover(task_done);”5.3 场景三家属通过微信扫码查看老人用药记录如何防止截图外泄这是隐私保护的硬核考点。你必须演示扫码流程用手机微信扫http://your-domain.com/elderly/family/login?codeabc123跳转到家属登录页展示脱敏效果进入“用药记录”每条记录显示药品名阿司匹林肠溶片 ✅用量100mg ✅开药医生张**主任 ❌姓氏星号职称身份证号110101****123456 ❌前后各掩码证明防截图打开浏览器开发者工具 → Elements → 找到用药记录 DOM确认classmasked-idcard的 span 里原始数据是style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;" />
返回列表