1. 项目背景与系统定位
养老院现在不缺床位,缺的是管理效率。我接触过不少养老机构的信息化项目,普遍现状是:纸质档案堆满柜子、护理记录靠手写交接、家属问老人情况只能打值班电话、月底对账能对到半夜。智慧养老中心管理系统就是要把这一摊子事搬到线上,让老人信息、健康数据、护理任务、床位状态、费用账单全部在一个系统里流转起来。
这套系统基于 Java Web 技术栈,核心组合是 SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0。后端提供 RESTful API,前端用 Vue3 做单页应用,数据库用 MySQL8.0 承载全部业务数据。系统覆盖养老中心最关键的几块业务:老人档案管理、入住退住流程、护理任务分配与记录、健康数据采集与预警、床位状态管理、费用计算与账单生成,以及家属端的日常互动。
做这套系统的目标很明确:让养老中心运营者在办公室就能看清全院状态,护理员用手机就能接单和写护理记录,家属通过网页随时了解老人的情况,管理者月底一键生成财务报表。这套系统不是学术项目,而是能直接落地上线、能在真实养老机构里跑起来的业务系统。本文会把设计与实现过程完整拆开,适合正在做类似管理系统开发、或者想把 SpringBoot2 + Vue3 全栈技能串起来的同学参考。
有人可能会问,市面上现成的养老管理系统也不少,为什么要自己开发一套?因为通用产品很难贴合具体机构的运营流程,有的机构按楼层分护理区,有的按老人自理能力分级,有的需要对接智能手环和床垫监测设备。自己开发一套,一方面能把定制需求做透,另一方面也能彻底掌握整个技术栈,后续维护和扩展都有底。
2. 技术选型与架构设计思路
2.1 后端为什么选 SpringBoot2 + MyBatis-Plus
SpringBoot2 在这个项目里其实是经过权衡的。SpringBoot3 已经出了相当一段时间,但稳定性、生态兼容性和团队熟悉度综合考虑之后,还是选了 SpringBoot2.7.x 这个非常成熟的版本。Java 8 配合 SpringBoot2,是目前企业级项目里最稳的组合,各种第三方库支持齐全,出了问题能找到的解决方案也最多。如果你后续没有强制要求用 SpringBoot3 和 Jakarta EE,那 SpringBoot2 仍然是个非常务实的选择。
MyBatis-Plus 就更有理由了。它不是把 MyBatis 换掉,而是在 MyBatis 之上做增强,内置了单表 CRUD、分页插件、代码生成器、逻辑删除、乐观锁等能力。在这个系统里,老人档案、家属账号、护理记录、费用明细这些表的结构都很规整,80% 的数据库操作是单表增删改查。用 MyBatis-Plus 可以让这部分代码量缩到原来的三分之一左右。更重要的是,它的 BaseMapper 提供了通用方法,业务代码里只需要写多表关联和复杂查询的 SQL,开发效率提升非常明显。
在项目分层上,我采用了比较经典的四层结构:Controller 负责接收请求和参数校验,Service 层处理业务逻辑,Mapper 层负责数据库访问,实体和 DTO 负责数据传递。没有引入过于复杂的领域驱动设计,因为这个系统的业务复杂度和团队协作规模决定了,简单清晰的分层更能保证代码的可维护性。实际开发中很多人喜欢把每个 Service 都写一个接口再写一个实现类,这个项目里我直接用了实现类,代码量更少,看的人也更容易理解。
2.2 前端为什么选 Vue3 而非 Vue2
前端技术栈选 Vue3 是趋势,也是刚需。Vue3 的组合式 API(Composition API)跟 Vue2 的选项式 API 相比,最大的优势是把同一业务逻辑的代码组织在一起,而不是分散在 data、methods、computed 里。在养老系统的老人详情页,老人基本信息、健康指标、护理记录、费用信息都集中在同一个页面展示,用组合式 API 把这些逻辑按功能拆分,代码清晰度会好很多。
Vue3 的响应式系统也做了重写,基于 Proxy 实现,性能比 Vue2 的 Object.defineProperty 高出不少。实际体感是页面数据更新更流畅,内存占用更低。配合 Vite 做开发服务器,热更新的速度是 Webpack 时代没法比的,改一个组件基本毫秒级刷新,开发体验好了很多。
在这个系统里,前端配合了 Element Plus 做组件库、Pinia 做状态管理、Vue Router 做路由管理、Axios 做 HTTP 请求封装。这套组合是 Vue3 生态里最主流的搭配,不是最花哨的,但是最稳的。Element Plus 的表格、表单、弹窗、日期选择、分页这些组件,在后台管理系统里使用频率极高,成熟度很高,基本能满足所有页面需求。
2.3 数据库选型 MySQL8.0 的理由
MySQL8.0 相比 5.7 版本有几个实际利益点。第一是支持窗口函数,做费用统计、护理记录排名这类查询时可以写得非常优雅。第二是支持公共表表达式(CTE),递归查询员工组织架构时很方便。第三是默认字符集已经是 utf8mb4,更加正规地支持 emoji 和生僻字,老人的姓名和备注信息里确实会出现这些字符。
MySQL8.0 的另一个优势是数据字典更好用、性能有所提升。在养老系统里,最怕的就是数据丢失和查询卡顿,MySQL8.0 的稳定性在同类产品里属于第一梯队。当然,用 MySQL8.0 也踩过一些坑,比如默认的认证插件从 mysql_native_password 改成了 caching_sha2_password,老版本的连接工具连不上,这个我在后面的常见问题部分会详细说。
3. 核心业务模块设计与实现
3.1 老人档案与健康管理模块
老人档案是整个系统的数据基础。设计数据库表时,elder 表不光要存姓名、性别、身份证号、联系方式这些基本字段,还要考虑老人的健康状态、自理能力等级、紧急联系人、既往病史、药物过敏史等养老行业特有的字段。这些字段在后续的护理计划制定、风险评估和费用计算中都会被用到。
健康管理是这个模块的关键。系统里记录了老人在院期间的生命体征数据,包括血压、心率、血糖、体温、血氧等指标。这些数据有两条来源,一条是护理员定时测量后手动录入,另一条是智能手环或者体征监测床垫通过接口自动上报。对于异常数据,系统需要实时预警,比如收缩压超过 160 或者心率低于 50,就要触发预警工单,提醒值班人员去查看老人状况。
数据表设计上,health_record 表用来存体征数据,字段包括老人 ID、指标类型、指标值、测量时间、测量人、设备编号。预警规则我建议不要写死在代码里,而是放在系统配置表里,不同养老中心可以参考的阈值不一样,管理员在后台可以灵活调整。这个设计在实际部署时帮了大忙,有一家合作机构要求血压高值预警阈值是 150,另一家要求是 160,改配置就行,不需要动代码。
3.2 床位管理与入住流程
床位管理很容易被忽视,实际上这是养老中心运营里最容易出乱子的环节。床位状态至少要有几种:空置、已入住、预入住、维修、停用。每个床位还要关联房型和护理级别,因为不同护理级别的收费标准是不一样的。
设计床位表的时候要注意一个细节:床位和老人不是简单的 1 对 1 关系,因为存在换床、暂时离院、转院等场景。所以我在 bed 表里用 status 字段表示当前状态,在入住记录表 stay_record 里维护老人与床位的实际占用历史。这样查询"这个床位现在的老人是谁"和"这位老人住过哪几个床位"都很快,报表统计也不容易出问题。
入住流程走的是状态机。老人在入住登记时,先是预入住状态,这个时候会录入健康档案、签署电子合同、确定护理等级和收费标准。正式入住后,床位状态改为已入住,护理任务开始每天自动生成。退住的时候要走退住流程,系统自动结算费用、释放床位、归档档案,这个过程在整个项目里属于业务流程最复杂的部分,状态机设计得好不好,直接决定系统好不好用。
3.3 护理任务与工单流转
护理任务模块是使用频率最高的模块。系统根据老人的护理等级自动生成每日护理计划,比如全自理老人每天查房两次,半失能老人每天助浴、翻身、测量血压,全失能老人每两小时需要翻身一次并记录皮肤情况。护理员登录系统后看到自己的任务列表,完成一项就标记一项,标记时还可以补充文字备注和拍照上传,比如拍下老人皮肤状况的照片,作为后续追溯的依据。
工单流转这块我觉得是体现系统价值的地方。当健康设备上报异常数据,或者护理员在记录中发现老人身体有异常,可以在系统里创建工单,工单会自动分配给值班医生或者护士长。工单状态有待处理、处理中、已完成、已关闭四种,每一步操作都有时间和操作人记录。这样可以形成一套完整的闭环,管理者可以从工单处理时长来考核响应效率。
这类功能看起来简单,但真正做好需要配一套灵活的任务规则引擎。我在实现时用了策略模式,不同的护理等级对应不同的任务生成策略,后续如果有新的护理项目和新的护理等级,只需要新增策略类,不需要改动整个任务生成逻辑。这块设计在代码评审的时候被同事认同,后来也确实派上了用场。
3.4 费用管理与账单模块
养老中心的费用构成很杂,包含床位费、护理费、餐饮费、医疗费、物料消耗费等等。每个项目的计算方式还不一样,床位费按天计算,护理费按月计算,餐饮费按顿扣减,医疗费按实际发生结算。如果靠人来算,很容易出错又耗时间。
费用模块我采用的是"费用项目 + 计费规则"的设计思路。系统里维护一个费用项目表,每个项目可以设置计费方式,是按天、按月、按次还是按实际金额。老人入住登记后,系统根据护理等级和床位类型自动生成当月的初始账单。后续每天的餐费、按摩理疗费、药品费,由各业务模块自动生成费用流水,月底统一汇总生成缴费单。
在实际开发中,费用计算最容易出问题是跨月结算和退住结算。比如老人 15 号入住,首月费用要从 15 号算到月底;退住那天发生的一些临时费用,要能实时结算出来。我使用了 MySQL8.0 的窗口函数和日期函数来处理这类结算,SQL 写起来会比 5.7 时代简洁很多。比如计算某位老人本月应住的自然天数,用 DATEDIFF 和 LAST_DAY 组合就能算得很准确。
4. 项目落地实操:从零搭建与关键配置
4.1 开发环境准备与版本锁定
这个项目的环境搭建看起来简单,但版本锁定很重要。前后端分离开发时,最怕的是各人环境不一致导致代码在你本地跑得好好的,到别人电脑上就起不来。所以第一步就是把版本固定下来。
后端环境:JDK 1.8、Maven 3.8.x、SpringBoot 2.7.18、MyBatis-Plus 3.5.3、MySQL 8.0.32。前端环境:Node.js 18 LTS、npm 9.x、Vite 4.x、Vue 3.4.x。数据库工具用 Navicat 16 或者新版 MySQL Workbench 都可以。我建议把 Maven 的 setting.xml 里镜像源配成阿里云镜像,Node 的 npm registry 也换成国内的镜像源,不然下载依赖会浪费大量时间。
JDK 1.8 和 SpringBoot 2.7 的搭配我用了很多次,非常稳定。有人可能觉得 1.8 太老,但在企业级后台管理系统领域,这组合依然是存量最大、问题最少的。如果你想体验更新的语法特性,用 JDK 17 配合 SpringBoot 3 也未尝不可,但是很多基于 JDK 8 的第三方库可能需要升级,连带成本会高一些,这个要根据自己团队情况来选择。
4.2 后端项目骨架搭建
创建 SpringBoot 项目,直接用 IDEA 的 Spring Initializr 也能做,但我更喜欢手动建 Maven 项目,这样更清楚每个依赖是干什么的。项目结构是这样的:
smart-care-parent/ ├── pom.xml └── src/main/java/com/smartcare/ ├── SmartCareApplication.java ├── config/ │ ├── MybatisPlusConfig.java │ ├── CorsConfig.java │ └── WebMvcConfig.java ├── controller/ ├── service/ ├── mapper/ ├── entity/ ├── dto/ ├── vo/ └── common/ ├── Result.java ├── ResultCode.java └── exception/pom.xml 里的核心依赖配置大概是这个思路:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <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</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.20</version> </dependency> </dependencies>application.yml 里的关键配置要注意几点。数据库连接串必须带 serverTimezone=Asia/Shanghai 参数,否则 MySQL 8.0 的时区问题会导致时间字段偏移。Druid 连接池的初始化大小建议设置成 5,最大活跃连接数 20,这个对中小型管理系统来说足够了。MyBatis-Plus 需要开启 map-underscore-to-camel-case,这样数据库的下划线字段能自动映射到驼峰属性。
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/smart_care?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 5 max-active: 20 min-idle: 5 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0MyBatis-Plus 的分页插件是必须配置的,不然分页查询会不生效。我单独建了一个 MybatisPlusConfig 配置类,把分页插件和乐观锁插件都注册进去。
4.3 前端项目搭建与环境配置
前端用 Vite 创建 Vue3 项目,命令行里执行 npm create vite@latest smart-care-web -- --template vue 就能生成项目骨架。随后安装项目的核心依赖,Element Plus、Pinia、Vue Router、Axios 这几样是必备的。Element Plus 的按需自动导入推荐使用 unplugin-auto-import 和 unplugin-vue-components 这两个插件,配合起来非常方便,不用在 main.js 里全量引入组件库,打包体积也能小不少。
Vite 配置的开发代理很关键,能解决跨域问题。开发环境下前端运行在 5173 端口,后端 API 运行在 8080 端口,如果没有代理,浏览器会直接拦截跨域请求。在 vite.config.js 里配置代理,将所有 /api 开头的请求转发到 8080:
export default defineConfig({ plugins: [vue(), AutoImport({ resolvers: [ElementPlusResolver()] })], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })Axios 封装时要统一处理请求拦截器和响应拦截器。请求拦截器里带上 Token,响应拦截器里统一解包后端返回的数据格式,如果后端返回的状态码是 401 就跳转到登录页,返回 500 就弹出错误提示。这样业务代码里不需要每个请求都写一遍错误处理,干净很多。
前端路由需要做权限控制。不同角色登录系统后,看到的菜单和页面是不一样的:管理员能看到所有菜单,护理员只能看到任务和记录相关页面,财务人员只能看到费用和账单页面。我用的方案是在路由 meta 字段里标记需要的角色,然后在路由守卫里做判断。Vue3 的 Router 4 版本守卫写法和 Vue2 不太一样,但逻辑类似。
4.4 数据库设计与初始化脚本
数据库设计是这类管理系统最见功夫的地方。我按业务域把表分成四类:基础数据表、老人相关表、运营相关表、系统相关表。基础数据表包括部门表、员工表、角色表、床位表、房间表;老人相关表是 elder、elder_family、health_record、nursing_record;运营相关表是 stay_record、nursing_task、charge_item、bill;系统相关表是 user、role、menu、operation_log。
设计表结构时有两个点值得强调。第一个是软删除,业务数据千万不要物理删除,比如误删一个老人档案,后续牵扯的费用和护理记录就断了。MyBatis-Plus 的 @TableLogic 注解就是干这个的,查询的时候自动过滤已删除数据。第二个是时间字段统一用 datetime 类型,不要用 timestamp,虽然 timestamp 空间更小,但是有 2038 年问题,而且时区处理起来更麻烦。
权限设计我用了经典的 RBAC 模型,用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。后面如果要给不同员工开不同权限,只需要在界面上勾选角色对应的菜单即可,非常灵活。
5. 实战中常见问题与排查技巧
5.1 MySQL8.0 连接失败和时区问题
很多人在连接 MySQL8.0 时报错 Access denied for user,或者是 Public Key Retrieval is not allowed,大多数情况都是认证插件的问题。MySQL8.0 默认新创建的用户使用 caching_sha2_password 认证方式,而很多老版本连接驱动和可视化工具不支持。解决办法是修改用户的认证方式:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;还有一个高频报错是拒绝连接,serverTimezone 配置不对会出现日期数据偏差,或者在连接串里没加 allowPublicKeyRetrieval=true。我的建议是所有 MySQL8.0 连接串都统一加上参数:useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true,这三个参数能避开绝大多数连接层面的问题。另外驱动包要确认是 mysql-connector-java 8.0 以上,老版本驱动连 8.0 数据库会有兼容问题。
5.2 前后端联调时的跨域和登录问题
前后端分离项目,跨域是最常见的问题。虽然 vite 代理在开发环境已经解决了跨域,但部署到服务器上的时候是 Nginx 反向代理,还需要在 Nginx 配置里把 /api 的请求转发到后端服务。千万不要图省事直接在后端代码里配置全局跨域(CorsConfig),那样会把接口暴露给任意域名调用,存在安全隐患。
登录这块踩过的坑是 Token 过期后前端没有处理。系统上线后出现过一个问题:用户登录放那儿半小时,Token 过期了,再点操作接口返回 401,但因为前端没有统一拦截,用户看到的是白屏或者一堆报错。后来在响应拦截器里统一判断,401 就跳转到登录页并弹出提示"登录状态已过期,请重新登录",这个问题就迎刃而解了。
5.3 MyBatis-Plus 的常见使用误区
MyBatis-Plus 虽然简单,但有几个坑我几乎每次用都会遇到。第一个是分页插件必须配置 PaginationInnerInterceptor,否则调用 selectPage 方法时你会发现返回的总条数是 0,数据只能查出一页。第二个是逻辑删除字段配置后,用自定义 SQL 的时候要记得自己在 SQL 里加 deleted = 0 条件,MyBatis-Plus 只对内置方法生效,自定义 SQL 需要额外处理。
还有一个容易坑人的地方:字段名从数据库到实体映射时,如果数据库字段是 order_no,实体属性是 orderNo,mybatis-plus 开启驼峰映射后可以正常匹配,但如果实体属性名是 no,就匹配不上了。这种问题往往不报错,只是查出来的字段是 null,排查起来比较费时间。建议在实体类里遇到这类特殊字段,直接用 @TableField("order_no") 显式指定,免得猜。
5.4 部署上线时的资源与性能优化
部署环境我建议用 Linux 服务器 + Nginx 反代 + 后端 jar 包的方式。前端打包后生成 dist 目录,放在 Nginx 的 html 目录下,配置好代理转发即可。后端直接 java -jar 运行,如果追求更稳妥,可以用 systemd 配置成系统服务,开机自启加自动重启都有了。
数据库连接池的大小要根据服务器配置来调整,不要盲目调大。连接池过大反而会增加数据库的压力。按照这个系统的并发量,20 个连接足够用了,预留一些余量就行。另外上线前一定要把 MyBatis 的 SQL 日志关掉,不然每个请求都往日志里打印 SQL,既慢又吃磁盘空间。
写在最后
这套智慧养老中心管理系统开发下来,我个人最大的体会是:技术上真正难的不是某个单独的技术点,而是把多个技术栈串起来,同时在业务上想得足够深。SpringBoot2、Vue3、MyBatis-Plus、MySQL8.0 各自的官方文档都很完善,但比例怎么配、表结构怎么设计、业务流程怎么闭环,这些需要实际做项目才能积累经验。
如果你也准备做类似的系统,我的建议是先不要急着写代码,花两三天时间把业务流程捋清楚。养老中心的业务看起来简单,实际上各种边角场景特别多,把流程建模做好了,代码层面就是体力活。另外记得留一份详尽的数据库设计文档,后面任何一个模块的改动,都可能牵动好几张表,文档跟不上会非常痛苦。
最后分享一个小技巧:开发这种管理类系统时,统一封装一套通用的前端表格页面组件和表单弹窗组件,能把新页面的开发时间压缩一半。这套系统后期的护理记录页面、工单列表页面、费用流水页面,基本都是复用这套组件搭出来的。项目源码和文档都在仓库里,有需要的同学可以直接拉下来参考,结合实际业务改一改就能用起来。