SpringBoot+Vue+MyBatis养老公寓管理系统从设计到部署 📅 发布时间:2026/9/18 22:07:28 👁 浏览次数: 1. 项目整体设计与技术选型思路做养老公寓管理系统这类项目最容易踩的坑不是代码写不出来而是“先动手写代码后补设计”结果业务逻辑越滚越乱最后变成一堆互相矛盾的接口。我拿到“大健康养老公寓管理系统”这个需求时第一反应是先想清楚这个系统到底要给谁用解决什么问题哪些模块是核心哪些只是锦上添花。养老公寓的业务大致有这么几条线老人入住、健康档案、日常护理、床位管理、家属沟通、员工考勤以及公寓经营层面的收费和统计。如果全部铺开做开发周期会非常长。我的做法是先把“老人-护理-健康”这条主线做扎实再补上“家属-员工”的权限线和“统计报表”的决策线。整条线跑通之后系统就已经具备实际落地能力了。技术选型上我最终确定了SpringBoot Vue MyBatis MySQL这套组合。原因很直接SpringBoot负责后端接口和业务逻辑Vue负责前端交互MyBatis负责数据库操作MySQL负责数据存储。每一层都有非常成熟的生态出了问题能找到大量参考资料而且对于团队协作或者单人开发都很友好。1.1 为什么选SpringBootVue前后端分离过去很多管理系统用JSP或者Thymeleaf做服务端渲染页面和服务端代码耦合在一起改个按钮样式都得重启服务。前后端分离之后后端只提供JSON接口前端通过HTTP请求拿数据渲染页面两者通过接口文档协作开发效率提升非常明显。SpringBoot的优势在于“约定大于配置”它把Spring生态里繁琐的XML配置大幅简化内嵌Tomcat打出的jar包直接java -jar就能运行。对于中小型管理系统来说这种开箱即用的体验太重要了。项目里我用了SpringBoot 2.7.x版本因为2.7还在维护期内兼容性也稳定。如果选3.x需要注意javax包名改成jakarta很多老教程不适用容易卡在导入依赖那一步。Vue这边我用的是Vue 2 Element UI。虽然Vue 3和Element Plus已经是主流但养老公寓管理系统这类偏内部管理的项目Vue 2的稳定生态和大量现成组件能省下不少造轮子的时间。当然如果是从零开始学直接用Vue 3也完全没问题核心思路是一样的组件化、数据驱动、路由控制页面跳转。1.2 数据持久层为什么是MyBatis而不是JPA市面上做持久层主要有两种选择Spring Data JPA和MyBatis。JPA的特点是自动建表、根据方法名生成SQL开发速度快但遇到复杂多表联查时要么写JPQL要么直接上原生SQL反而绕圈子。MyBatis则把SQL完全交到开发者手里动态SQL能力非常强尤其在养老系统这种多条件查询密集的场景下if、where、foreach标签组合起来可以轻松处理“查老人列表时按姓名、楼层、护理等级、入住状态任意组合筛选”的需求。我遇到过一个真实案例养老公寓需要一个护理记录查询页面筛选条件包括老人姓名、护工姓名、护理类型、开始时间、结束时间、是否异常六个条件全部可选。用JPA写方法名会发现组合数量爆炸而MyBatis里一段动态SQL就解决了select idlistCareRecords resultTypecom.demo.entity.CareRecord SELECT cr.*, e.real_name AS nurse_name, o.name AS old_name FROM care_record cr LEFT JOIN employee e ON cr.nurse_id e.id LEFT JOIN elder o ON cr.elder_id o.id where if testelderName ! null and elderName ! AND o.name LIKE CONCAT(%, #{elderName}, %) /if if testnurseName ! null and nurseName ! AND e.real_name LIKE CONCAT(%, #{nurseName}, %) /if if testcareType ! null and careType ! AND cr.care_type #{careType} /if if teststartTime ! null AND cr.create_time gt; #{startTime} /if if testendTime ! null AND cr.create_time lt; #{endTime} /if if testisAbnormal ! null AND cr.is_abnormal #{isAbnormal} /if /where ORDER BY cr.create_time DESC /select这段代码既灵活又能保证SQL可读性配合MyBatis的二级缓存还能减少重复查询的压力。所以我的结论是像养老公寓这种业务逻辑清晰、报表查询较多、SQL需要精细把控的项目MyBatis比JPA更顺手。1.3 MySQL在养老业务场景下的存储设计MySQL作为关系型数据库在养老公寓场景下非常适合因为数据之间的关联性很强一个老人属于一个房间一个房间属于一个楼层一个护理任务关联老人和护工这些都是一对一、一对多的关系。用MySQL的外键逻辑或业务逻辑维护这些关系非常自然。建表时我遵循几个原则每张表必须有主键id统一用bigint自增时间字段统一用datetime金额字段用decimal(10,2)状态字段用tinyint比如老人入住状态0表示未入住、1表示在住、2表示退住。字符集统一utf8mb4避免老人姓名里出现生僻字时乱码。存储引擎用InnoDB支持事务护理记录这种重要数据坚决不能丢。医疗健康数据我单独建了health_record表记录老人的血压、心率、血糖、体温等指标。考虑到老人每天可能测量多次这些数据是按时间维度增长的前期数据量不大MySQL完全够用。如果后续数据量上来了可以再考虑按时间分表或者引入时序数据库但当前阶段不需要过度设计。2. 环境准备与项目初始化很多初学者会卡在环境搭建这一步尤其是SpringBoot版本和MySQL安装。我尽量把实际操作的每一步都讲清楚减少大家搜教程的时间。2.1 开发环境版本选型这套系统推荐的环境组合是JDK 1.8如果SpringBoot 2.7建议JDK8如果想用新特性也可以JDK11Maven 3.6Node.js 14Vue2项目够用MySQL 5.7或8.0IDEA 2020建议用2022以后版本有个非常常见的坑SpringBoot版本太高导致依赖下载失败。很多人直接创建最新版SpringBoot比如3.2.x然后发现javax.servlet包找不到因为SpringBoot 3.x已经迁移到Jakarta EE了。大部分老项目、老教程用的还是javax复制过来的代码全都报红。我的建议是学习阶段使用SpringBoot 2.7.18它是2.x系列的最后一个版本既稳定又保留大量教程生态。如果你一定要用3.x那就同时升级JDK17并把所有javax替换为jakartapom.xml里依赖也是。这听起来不难但实际改起来会冒出各种兼容问题对新手不友好。2.2 IDEA创建SpringBoot项目实操在IDEA里新建项目时选择Spring Initializr然后设置Group比如com.demo、Artifact比如elder-care-system。Java版本选8SpringBoot版本选2.7.18。依赖这里我建议先选这几个Spring Web提供接口能力MyBatis FrameworkMyBatis依赖MySQL Driver连接数据库Lombok简化实体类代码如果IDEA新建项目时网络连接不上start.spring.io可以用阿里云的镜像地址https://start.aliyun.com。在Settings里把这个URL填到Spring Initializr的Service URL处创建速度会快很多。项目创建完成后pom.xml中需要加上Druid连接池依赖比默认的HikariCP更适合监控数据库连接状态。我一般用Alibaba Druid原因是可以配置监控页面很方便排查慢SQL。2.3 Vue环境搭建与依赖安装Vue前端项目我习惯用Vue CLI创建。先安装Node.js然后在命令行执行node -v npm -v确认Node安装成功后全局安装Vue CLInpm install -g vue/cli vue --version然后创建项目vue create elder-care-web这里会让你选预设选择Manually select features然后勾选Router、Vuex、CSS Pre-processorsVue版本选2.x。安装完成后进入项目目录cd elder-care-web npm run serve如果npm安装依赖特别慢可以切换淘宝镜像源npm config set registry https://registry.npmmirror.com要注意一件事vue项目依赖安装时经常出现node-sass安装失败因为node-sass需要下载二进制文件容易超时。解决办法是使用sass或dart-sass替代。在创建项目时选择CSS Pre-processors后面安装依赖别选node-sass直接用sassnpm install sass1.63.6 -D这样编译速度快也不容易出问题。2.4 数据库初始化与连接配置MySQL安装好以后需要创建数据库和用户。我用MySQL 8.0安装时选了utf8mb4字符集。创建数据库执行CREATE DATABASE elder_care DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE elder_care;然后把准备好的建表SQL全部导入一次性把elder、employee、room、care_record、health_record、user、role等核心表建好。这里提醒一句导入SQL前要确认当前连接的不是系统库不然把表建到mysql库里会非常麻烦。后端配置数据库连接在application.yml里server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/elder_care?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password type: com.alibaba.druid.pool.DruidDataSource jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.demo.entity configuration: map-underscore-to-camel-case: true一定要注意url里的参数serverTimezone必须指定Asia/Shanghai不然日期查询差8个小时useSSLfalse避免连接警告zeroDateTimeBehaviorconvertToNull解决数据库里零日期值报错。3. 核心业务模块实现系统骨架搭好后真正的工作量在业务模块。我把养老公寓的核心模块拆成四块来聊。3.1 老人档案与健康数据管理老人档案是整个系统的基础入住老人的所有业务都围绕档案展开。字段包括姓名、性别、身份证号、生日、联系方式、紧急联系人、入住日期、所属房间、护理等级、既往病史、过敏史、日常用药等等。老人健康数据这块我设计了一个独立的健康记录表每天由护工或护士录入老人的体征数据包括体温、血压数据、心率、血糖、血氧、服药情况等。录入表单用Vue实现后端接口接收后写入health_record表。MyBatis在这里最常用的操作是分页查询。我用PageHelper它是MyBatis的分页插件使用非常简单PageHelper.startPage(pageNum, pageSize); ListElder list elderMapper.selectElderList(condition); PageInfoElder pageInfo new PageInfo(list);PageHelper的原理是在执行SQL前自动拼接limit语句所以一定要确保PageHelper.startPage后面紧跟的查询语句就是要分页的查询中间不能插入其他查询否则分页会失效。健康趋势图是家属比较关心的功能。前端使用ECharts后端提供趋势数据接口查询指定老人最近30天的血压或心率记录返回日期和数值数组前端渲染成折线图。这样家属和护士能直观看到老人的健康走向。3.2 床位与护理任务调度养老公寓的床位管理包含楼层、房间、床位三层结构。楼层实体有楼层编号和管理员房间实体有房间号、房间类型单人间/双人间、朝向、楼层床位实体有床位号、床位状态空闲/已入住/维修、当前入住老人ID。护理任务调度是系统里业务逻辑最复杂的一块。护理等级一般分为自理、半自理、全护理不同等级对应不同护理频次和内容。我设计了一个任务池每天早上系统根据所有在住老人的护理等级自动生成当天的护理任务比如半自理老人每天需要3次巡视全护理老人每天需要4次翻身、2次擦洗、按需喂饭。护工登录后看到分配给自己的任务列表每完成一项点击“完成”同时填写护理记录。护理记录回写到care_record表供家属端和护士长端查看。这里用到了MyBatis的一对多查询一个护工有多条护理任务查询时使用collection映射。在mapper XML里这样写resultMap idEmployeeTaskMap typecom.demo.entity.EmployeeVO id propertyid columnemployee_id/ result propertyrealName columnreal_name/ collection propertytaskList ofTypecom.demo.entity.CareTask id propertyid columntask_id/ result propertytaskContent columntask_content/ result propertytaskStatus columntask_status/ /collection /resultMap这种设计避免了N1查询问题一条SQL就能把员工及其任务列表全部查出来。注意关联查询时如果任务表也可能关联老人要小心resultMap嵌套链太多层会让人头晕建议拆成多段查询再在Service层组装。3.3 家属端与员工端权限设计养老公寓管理系统的用户角色至少有四种系统管理员、护士长、护工、家属。不同角色看到的菜单和操作权限完全不同。我用SpringSecurity JWT实现认证和授权。用户登录成功后后端签发一个JWT token前端存到localStorage里每次请求在axios拦截器中带上tokenaxios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization Bearer token; } return config; });后端写一个JWT过滤器解析token并设置登录用户上下文。对于权限控制我在接口上用PreAuthorize注解比如PreAuthorize(hasRole(ADMIN)) GetMapping(/admin/all) public Result getAllElders() { return Result.success(elderService.listAll()); }家属角色只能通过专属接口查看自家老人的信息和护理记录不能看到其他老人的数据。实现方式是在接口里根据当前登录用户的绑定关系过滤数据public ListCareRecord getFamilyCareRecords(Long familyUserId) { Long elderId familyUserMapper.selectElderIdByUserId(familyUserId); return careRecordMapper.selectByElderId(elderId); }这种设计在业务上更好控制也避免因为前端按钮显示问题导致越权。3.4 报表统计与数据可视化报表模块是给公寓管理层看的重点统计入住率、护理任务完成率、异常事件数量、月度收入。我用ECharts做前端图表后端提供统计接口。入住率统计需要联查房间表和老人表统计每个楼层房间总数、已入住数量然后计算比例SELECT floor_id, COUNT(*) AS total_rooms, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS occupied_rooms FROM room GROUP BY floor_id;后台返回给前端前端用饼状图展示。这类统计SQL要特别注意索引和group by的结合如果房间数量不大问题不大但如果以后扩展可以在room表加复合索引floor_id status。护理任务完成率统计比较复杂因为任务有全天、半天、按需等多种周期。我先统计全天总任务数再统计当日已完成任务数相除得到完成率。要注意不同护理等级的任务权重不同比如全护理老人的翻身任务比普通巡视更重要所以我在统计里加入了任务权重系数避免完成率虚高。4. 前后端联调与关键问题排查联调是最容易出现问题的阶段。我把自己实际遇到并解决的几个典型问题完整记录下来。4.1 跨域问题与统一响应封装前端开发服务器跑在8081端口后端接口在8080端口浏览器会拦截跨域请求。解决方式是在后端写一个配置类允许跨域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); } }同时所有接口统一返回Result对象包含code、message、data三个字段。前端axios在响应拦截器统一处理axios.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 { Message.error(error.message || 网络异常); return Promise.reject(error); } );这样避免每个页面都写一遍错误提示整个项目的错误处理逻辑也非常统一。4.2 MyBatis动态SQL与缓存使用MyBatis一级缓存是SqlSession级别的默认开启同一个SqlSession内相同查询会命中缓存但Spring整合后每次操作可能开启新SqlSession所以一级缓存作用不大。二级缓存是Mapper级别的可以跨SqlSession使用但养老系统里很多数据是实时变化的比如床位状态如果缓存没有及时清理页面会显示脏数据。我的建议是只在字典表、护理类型这类几乎不变的数据上开启二级缓存其余业务数据不要开。cache evictionLRU flushInterval60000 size512 readOnlytrue/同时要理解MyBatis的动态SQL拼接顺序。比如更新老人信息时要动态更新字段使用set标签update idupdateElderSelective UPDATE elder set if testname ! nullname #{name},/if if testroomId ! nullroom_id #{roomId},/if if testcareLevel ! nullcare_level #{careLevel},/if /set WHERE id #{id} /updateset标签会自动去掉最后多余的逗号非常省心。4.3 视频流播放m3u8集成细节养老公寓里往往会部署摄像头家属希望能在App或网页上查看老人公共活动区域的实时画面。这个需求在管理系统里通常通过集成视频流服务来实现。很多监控设备或流媒体服务器会输出m3u8格式的视频流地址前端直接用video.js或hls.js播放。我在项目里用hls.js播放m3u8核心代码是if (Hls.isSupported()) { const hls new Hls(); hls.loadSource(videoUrl); hls.attachMedia(videoElement); hls.on(Hls.Events.MANIFEST_PARSED, () { videoElement.play(); }); }但这里有个关键问题如果后端的接口路径没有配置允许跨域视频请求会被拦截。而且m3u8切片请求如果带鉴权头某些播放器不支持自定义头所以通常的做法是后端增加一个转发代理接口把鉴权信息拼接在URL上或者通过Session校验。部署层面如果使用Nginx代理视频流需要在Nginx配置里关闭对m3u8文件的缓存并设置正确的Content-Typelocation ~ \.m3u8$ { add_header Cache-Control no-cache; types { application/vnd.apple.mpegurl m3u8; } } location ~ \.ts$ { add_header Cache-Control no-cache; }这样H5播放器才能流畅播放。4.4 部署上线与打包优化前后端分离的项目部署分别处理。后端打包成jar包前端构建成dist目录静态文件用Nginx托管。后端打包前需要确认pom.xml中包含spring-boot-maven-pluginbuild plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build在IDEA右侧Maven面板执行clean package生成的jar包在target目录。服务器上运行nohup java -jar elder-care-system.jar --spring.profiles.activeprod log.out 21 前端打包npm run build生成dist目录后把dist里的文件上传到Nginx的html目录。Nginx配置里要处理Vue Router的history模式否则刷新页面会404location / { try_files $uri $uri/ /index.html; }同时把/api反向代理到后端服务location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }我遇到过前端打包后布局异常的问题往往是因为图片或字体文件路径写死成了绝对路径导致部署到子目录时资源找不到。解决办法是在vue.config.js里设置publicPath为相对路径module.exports { publicPath: ./, productionSourceMap: false, devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };5. 常见问题速查与避坑清单把我在整个项目过程中印象比较深的坑整理成下面的表格方便遇到问题时快速对照。问题现象可能原因解决方法IDEA创建SpringBoot项目卡住默认连接Spring官网超时改成阿里云镜像start.aliyun.com启动报Failed to configure a DataSource数据库连接配置错误或依赖缺失检查application.yml、mysql驱动依赖前端npm install报node-sass错误node-sass二进制下载失败替换为sassdart-sass查询中文乱码数据库、表、连接串字符集不一致统一utf8mb4url加characterEncodingutf8MyBatis打印不出SQL缺少日志配置application.yml加配置输出sql日志Vue打包后刷新404前端路由history模式Nginx未配置Nginx中添加try_files前后端联调跨域端口不同未配置允许跨域后端加CorsConfig或前端devServer代理日期时间少8小时时区未设置JDBC连接参数serverTimezoneAsia/Shanghai分页查询数据不准PageHelper使用方式不对startPage后紧跟查询语句接口权限被绕过只做了前端菜单隐藏后端未加鉴权后端接口加PreAuthorize或自定义鉴权上传的图片无法访问静态资源映射路径不对配置WebMvcConfigurer映射本地磁盘目录这里面最容易被忽视的是“接口权限被绕过”的问题。很多人只在前端根据角色隐藏菜单但后端接口仍然能被直接调用非常危险。一定要在后端Controller层加权限校验至少要做角色判断不能光靠前端界面做限制。另外MyBatis日志配置是排查SQL的重要手段。在application.yml中加入mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这样每次执行SQL都会在控制台打印完整SQL和参数排查多表查询错误会快很多。生产环境记得关闭这个日志避免性能损耗。6. 从开发到交付的几点心得这个养老公寓管理系统做完之后我最大的感受是技术本身并不难难的是把业务流程想清楚。比如护理任务调度如果没和护士长反复确认“半自理老人一天到底巡几次”代码写出来也只能是空中楼阁。所以不论你是在学习源码还是准备自己动手做一个类似系统一定要先去了解业务最好能画出业务流程图哪怕只是草稿都能帮你在建表时少走很多弯路。实际用下来还有一个小技巧值得分享在老人档案模块添加“用药提醒”功能时我一开始把每天多次的用药时间设计成了字段列表后来发现很难扩展。改成独立的medicine_reminder表一个老人对应多条用药计划每条计划有药品名、剂量、时间段早/中/晚、备注这样扩展性就好很多。这类需求设计中“一对多”永远比“多字段”更合理。最后再补充一个部署细节我用Docker部署MySQL和Nginx时发现Docker容器时区默认是UTC导致日志时间比实际时间晚8小时。解决方案是在docker run的时候加上-e TZAsia/Shanghai或者在docker-compose.yml里环境变量配置TZ。这类问题不遇到一次很难注意到但一旦遇到就很头疼。如果你是第一次接触这种前后端分离的管理系统从源码开始学习的话我建议先跑通数据库初始化再启动后端最后启动前端按“登录接口 - 老人列表 - 新增老人 - 护理记录”这个链路逐步断点调试。把这条链路跑通你对SpringBoot和Vue的分工协作就会有非常具体的理解。之后再按模块去扩展功能会顺手很多。