ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue3+MyBatis+MySQL实战:构建养老智慧服务平台

SpringBoot+Vue3+MyBatis+MySQL实战:构建养老智慧服务平台 1. 项目背景与需求拆解1.1 养老服务平台到底在解决什么问题先说结论这个项目的核心价值不在于“技术有多新”而在于它把一套典型的业务闭环用技术手段跑通了。养老智慧服务平台本质上做的是“养老服务供需两端的信息化接管”——一边是老人、家属、护工、社区管理员另一边是健康档案、工单调度、费用结算、紧急告警这些后端流程。没有这套系统之前很多养老机构靠的是微信群接龙、Excel表格排班、纸质健康档案信息割裂且追溯困难。做这个项目最直接的动机就是替代人工台账。我见过不少实际落地的养老机构护工排班、老人用药提醒、探访记录全是手写或口头交接一旦发生纠纷连基本证据链都拿不出来。所以这类平台通常要求四大核心能力老人档案数字化、服务工单流转、健康数据采集、权限分级管理。技术上自然就倒逼出一个前后端分离架构管理端给机构运营人员用客户端给家属或护工用数据统一沉淀到 MySQL。1.2 这套技术组合的普适性SpringBoot Vue3 MyBatis MySQL在今天的 Java 后端项目里属于最标准的“人民群众组合”。它不是最潮的但一定是最稳的。SpringBoot 负责把服务端组件黏合起来MyBatis 负责 SQL 可控的持久层操作Vue3 负责前端响应式交互MySQL 负责关系型数据存储。对养老平台这类业务来说事务性强、报表需求多、权限模型复杂这套组合的匹配度非常高。我经常跟人讲选技术栈不是选秀是选“出问题后你能查明白的”。SpringBoot 的生态资料最全MyBatis 的 SQL 排查链路最短Vue3 的社区问答量极大MySQL 更是几乎所有 Java 开发者的起点。这个项目的源码能跑通意味着你后续做任何 CRUD 业务系统——进销存、教务管理、物业工单——都能复用同一套骨架。2. 技术选型与架构设计思路2.1 为什么用前后端分离而不是服务端渲染养老平台的访问场景很特殊管理后台是固定设备家属端是手机浏览器护工端可能是平板。如果沿用传统的 Thymeleaf 或 JSP 服务端渲染每次改版都要重新发后端包而且前后端并行开发效率极低。前后端分离的核心收益是后端只出 JSON 接口前端独立部署两端各自迭代。具体到这个项目Vue3 负责的页面包括登录、老人档案列表、工单处理、健康数据图表、排班日历等交互密度不低。用 SPA 模式可以做到局部刷新、组件复用体验上比整页刷新好一个档次。尤其是健康数据图表这类模块如果用服务端渲染前端还得靠 jQuery 手动操作 DOM代码可维护性会很差。2.2 SpringBoot 在后端扮演的角色SpringBoot 在这个项目里最大的价值不是“自动配置”这种概念而是它把项目启动成本降到最低。一个养老平台后台需要 Web 能力、事务管理、定时任务、文件上传、邮件通知比如告警邮件SpringBoot 的 starter 机制让你加依赖就能用不需要像 Spring MVC 时代那样写一堆 XML 配置。我在这个项目里最常用的几个组件spring-boot-starter-web提供 RESTful API 基础spring-boot-starter-validation参数校验避免脏数据入库spring-boot-starter-quartz定时任务比如每天凌晨生成健康日报mybatis-spring-boot-starterMyBatis 与 SpringBoot 的官方整合包这里有个细节容易踩坑SpringBoot 版本和 MyBatis starter 版本存在兼容关系不建议盲目追新。比如 SpringBoot 3.x 要求 MyBatis starter 至少 2.3.x 以上且底层基于 MyBatis 3.5。如果你用 SpringBoot 2.x就不要强行升级 MyBatis 到 3.5.10 以上某些 SQL 方言行为会有细微差异。2.3 MyBatis 在这个场景里的选择理由JPA 和 MyBatis 之争老生常谈但在这个养老平台场景里MyBatis 是更理性选择。原因很简单业务里有大量复杂报表查询和多表关联统计比如“每个护理员本月完成工单数”“老人健康指标趋势”这些 SQL 用 JPQL 写起来非常别扭而 MyBatis 的 XML 映射文件天生就是给这种场景准备的。另外养老平台涉及敏感健康数据很多机构要求数据库层面的留痕和审计。MyBatis 允许你在 XML 里手写 SQL加上拦截器就能实现统一的数据权限过滤比如护工只能看自己负责的老人。这种控制力用 JPA 的 Specification 也能做但可读性和调试便利性差很多。2.4 MySQL 数据库选择的现实考量MySQL 在这个项目里没有悬念。养老平台的数据量级单表百万级已经算很大了MySQL 的 InnoDB 引擎配合合理的索引设计完全够用。而且这类项目大概率要部署在机构内网服务器或小型云主机上MySQL 的部署运维成本最低还不用额外支付软件授权费。数据库设计上我坚持三个原则表名业务化elder_info、care_order、所有表带 create_time/update_time 字段、金额字段用 decimal 而非 float。这些原则看似基础但在实际维护中能救命。尤其金额字段用 float 存账目时间长了必然出现精度漂移财务对不上账就是事故。3. 核心功能模块与数据库模型设计3.1 老人档案模块的设计要点老人档案是养老平台的数据底座几乎所有业务都围绕它展开。档案表除了基本信息姓名、身份证、家属联系方式、入住时间还要考虑扩展字段紧急联系人、既往病史、过敏药物、护理等级。这些字段不能全塞进一张表否则随着机构业务扩展表结构会变得越来越臃肿。我采用的做法是拆成两张表elder_base_info 存固定字段elder_ext_info 存 KV 结构扩展数据。这样做主表查询时不会因为 TEXT 字段拖慢速度扩展字段变更时也不需要频繁 ALTER TABLE。实际上很多机构连“是否失能”“失能等级”这类状态都经常调整把它拆出去后用单独的表记录变更历史更符合运营审计需求。引用块提示elder_base_info 表的主键建议用雪花 ID 而不是自增 ID。原因在于项目后期可能要做多机构数据汇总自增 ID 在数据库迁移或多库合并时容易冲突雪花 ID 能保证全局唯一同时保持索引顺序性。3.2 服务工单流转逻辑服务工单是平台的“血压计”——从家属下单、护工接单、服务完成、满意度评价整条链路决定了平台是否真正产生价值。数据库层面需要四张核心表order_main工单主表、order_item工单明细比如具体服务项目、order_status_log状态流转日志、order_evaluation评价表。状态流转是这里最值得设计的部分。我定义了五态待接单、服务中、已完成、已取消、已关闭。每个状态变更都必须在 order_status_log 里记录操作人、操作时间、变更前状态、变更后状态、备注。这个设计很关键——养老场景里一旦家属投诉“没按约定时间上门服务”运营人员可以通过日志精确还原当时发生了什么。3.3 健康数据采集与展示健康数据模块包括血压、血糖、心率、体温等指标记录。这块的表设计比较容易走极端——有人搞一张超宽表把十几种指标全塞进一行有人搞 EAV 模式每种指标一条记录。我的建议是这种指标数据适合用窄表health_metric_record字段包含 elder_id、metric_type、metric_value、measure_time。一张表覆盖所有指标查询时按 metric_type 过滤即可。这样设计带来的好处是扩展新指标时不需要改表结构。比如机构下个月新增“血氧饱和度”采集直接在枚举里加一个类型就行。缺点是同一时间内多种指标的数据分散在多行前端展示折线图时需要一次查询拿回全部类型再在内存里转成按时间聚合的结构。这个问题在数据量不大几万条时完全无感所以无需过度优化。3.4 排班与护工管理护工管理模块的复杂度常被低估。一个护理员可能同时服务于多位老人排班可能出现冲突而且护理员的考勤直接关联薪资结算。这个模块我拆成了三个部分staff_info护工基本信息、staff_schedule排班计划、staff_attendance考勤打卡。排班表设计时特别要注意“周期规则”字段很多机构的排班是“做六休一”或“上一休一”不能只存某一天的值需要用 cron 表达式或自定义规则字段来存循环模式。考勤这块容易和排班表产生数据冗余。我建议考勤表只存“实际打卡记录”而“应出勤时间”始终从排班计划实时计算。这样如果排班调整不需要回刷历史考勤。这是很多初学开发者容易犯的错误把应出勤时间物化到考勤表里一旦排班变更历史数据就乱了。4. 后端核心实现细节与实操要点4.1 SpringBoot 项目结构规划项目结构决定后续维护体验我推荐按业务模块分包而不是按技术类型分包。比较两种方式按技术分包controller/、service/、mapper/所有业务混在一起文件多了以后非常难定位。这是很多培训项目带出来的坏习惯。按业务分包elder/、order/、staff/、health/每个包内自含 controller、service、mapper。这个项目我采用按业务分包同时保留一个 common 包放全局异常处理、统一返回体、工具类。统一返回体我定义为 Result 包含 code、message、data 三个字段。code 为 0 表示成功非 0 表示业务错误码。这里需要注意不要把 HTTP 状态码直接当业务码用因为业务错误码需要具备自解释性而 HTTP 状态码语义粒度太粗。4.2 MyBatis 映射文件的编写规范MyBatis 的 XML 文件是最容易“写飞”的地方。我总结了三个实用规范实测能有效减少低级 bug。第一所有表名和字段名在 SQL 中显式列出禁止使用 SELECT *。原因不仅是性能更重要的是当表结构变动时SELECT * 会在运行时才暴露问题而显式字段在开发阶段就能通过代码检查发现。第二条件查询统一用动态 SQL并注意 where 标签的用法。很多初学者会在 where 后面手工加 11 来拼接条件这在小数据量场景没问题但会严重影响查询优化器对索引的利用。MyBatis 提供了 标签可以自动去掉多余 and/or。第三批量插入不要用 foreach 拼接单条 insert 语句的方式。在大数据量下这种做法会导致 SQL 超过 MySQL 的 max_allowed_packet 限制。建议改用 MyBatis 的批量模式或者使用 MySQL 的 INSERT INTO ... VALUES (...), (...), (...) 语法控制每批 500-1000 条。4.3 数据权限控制的落地养老平台天然需要数据权限控制护工只能看自己负责的老人护士长可以看整个楼层管理员可以看全机构。这种需求如果在每个 Service 里手工写条件代码会严重重复。我的做法是使用 MyBatis 拦截器在 BaseMapper 层面做统一数据过滤。具体思路自定义注解 DataScope(type elder) 标记在查询方法上拦截器解析当前登录用户的角色自动在 SQL 后面追加 AND elder_id IN (...)。这样业务代码里完全不需要感知权限逻辑权限规则调整时只改拦截器一处即可。需要注意这种方案并不适合所有场景如果系统未来要升级为多租户架构建议直接用 MyBatis-Plus 的租户插件。当前项目的体量下拦截器方案是最轻量的选择。4.4 事务与并发控制养老平台的并发量虽然不高但某些场景的并发问题必须提前预防。最典型的是“工单抢单”——多位护工同时点击接单如果不加控制一个工单可能被多人接走。解决方案有三种思路悲观锁SELECT FOR UPDATE、乐观锁版本号字段、状态机约束UPDATE 语句带状态条件。我的选择是第三种因为工单表本身就有状态字段直接使用 UPDATE order_main SET status 2 WHERE id #{id} AND status 1返回受影响行数如果为 0 则说明已被他人接走。这种方式不引入额外锁开销还能和状态机流转天然结合。另外要注意 Transactional 的使用边界。事务不要开在 Controller 层应该开在 Service 实现类上并且尽量保持事务短小。如果事务内包含远程调用比如调用外部短信接口一旦外部服务响应慢数据库连接会被长期占用。这种情况建议把远程调用移到事务提交之后的事件监听器里。5. 前端 Vue3 实现与工程化细节5.1 前后端分离下的前端架构选择Vue3 项目的工程化第一个决策是用 Composition API 还是 Options API。这个项目的老人管理、工单列表、健康图表等页面都有大量状态联动我推荐使用 Composition API。不是说 Options API 不能写而是组合式函数composables确实能比 mixin 更清晰地组织逻辑。比如健康数据的图表模块可以封装一个 useHealthData composable内部管理查询参数、加载状态、图表实例、数据转换逻辑。在多个页面复用时直接调用 useHealthData() 即可。如果是 Options API要么复制粘贴一大段 methods要么用 mixin 引入不可见的数据来源维护体验相差很大。5.2 与后端接口约定的最佳实践前后端联调最忌讳的是接口约定随意。这个项目我坚持用一套静态的 API 定义文件TS 类型或 JSDoc确保两端同步。后端定义好接口后前端先根据接口文档生成对应的 TypeScript 类型再开始写页面逻辑。这样可以避免“后端返回的字段名是 elderName前端却写成了 name”这类低级问题。具体落地时我用 Axios 封装了一个 request 实例统一处理三件事baseURL 配置、token 注入、错误提示。后端的 Result 结构在响应拦截器里被解包业务代码只需要接收 data 部分。遇到 code ! 0 的情况拦截器统一弹出错误消息并且对 401 做跳转登录处理。这样每个具体接口的函数体非常干净只保留业务逻辑。5.3 权限菜单的动态渲染管理后台的侧边栏菜单通常需要根据登录用户的角色动态生成。这个项目的做法是登录后调用 /api/auth/menus 接口后端根据用户角色返回权限树前端用递归组件渲染成菜单。这个方案比静态路由表好在哪里好处是新增角色时不需要改前端代码。而且按钮级别的权限也可以通过类似机制控制——后端返回的权限标识数组在前端注册一个 v-permission 自定义指令判断元素是否渲染。但要注意前端权限控制只是提升体验不能代替后端权限校验。任何人都可以通过浏览器的开发者工具把隐藏的按钮显示出来所以后端接口必须再次校验操作权限。这个原则我在项目中反复强调。5.4 Vue3 项目打包与部署Vue3 项目的打包很常规npm run build 生成静态文件然后扔到 Nginx 下托管。但有一个关键配置容易踩坑前端路由如果用了 history 模式Nginx 需要配置 try_files 来支持前端路由回退。否则刷新页面就会出现 404。配置示例location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }如果是直接把前端 dist 目录塞进 SpringBoot 的 resources/static 下有一个坑接口路径和静态资源路径不能冲突。建议后端统一设置 server.servlet.context-path/api前端请求统一加到 /api 前缀Nginx 里把 /api 转发到后端服务其余请求指向静态文件。这种部署方式下SpringBoot 内部不需要额外做静态资源映射。6. 前后端联调、部署与环境搭建6.1 本地开发环境搭建要点搭建这套项目要准备四个基础工具JDK 8 或 11SpringBoot 2.x或 JDK 17SpringBoot 3.x、Maven 3.6、Node.js 16、MySQL 5.7。这里有个容易忽视的问题Maven 仓库镜像和 Node 包的源都建议配置国内镜像否则首次构建会卡在依赖下载上。MySQL 版本选择上我建议和项目的 application.yml 里的 driver 版本匹配。SpringBoot 2.x 自带 mysql-connector-java 8.0.x连接 MySQL 5.7 和 8.x 都没问题。但如果你本地安装的是 MySQL 8.x事后部署到生产环境用的却是 MySQL 5.7就可能在 SQL 语法或排序规则上出现差异。所以测试环境和生产环境的数据库版本尽量保持一致。6.2 数据库初始化与种子数据项目源码里通常带一个 init.sql 或者 schema.sql里面包含建表语句和基础数据。我拿到新项目的第一件事不是看代码而是先执行数据库脚本把库跑起来。因为看懂数据模型比看懂代码更高效——数据表之间的关系直接反映了业务关系的设计。种子数据至少要包含管理员账号密码需要 BCrypt 或 MD5 加密后的值、默认角色、菜单权限数据。如果你发现 init.sql 里的密码是明文建议初始化后第一时间改成加密值并测试登录流程。SpringBoot 里集成 spring-security-crypto用 BCryptPasswordEncoder 生成密文而不是用 MD5——MD5 现在可以秒级爆破不适合存密码哈希了。6.3 启动过程的常见报错与排查SpringBoot 项目启动失败第一个要看的是控制台最下方的异常摘要而不是整篇红色日志。常见几类报错端口被占用检查 server.port 配置值用 netstat -ano | findstr 8080 找到占用进程。数据库连接失败大概率是密码或 IP 配置错先确认 application.yml 里的 url 是否带时区参数serverTimezoneAsia/Shanghai。表不存在检查 mapper 对应的实体类和表名是否一致MyBatis 默认并不会自动建表需要手工执行 SQL 脚本。前端启动失败则通常是 node_modules 安装问题。Vue3 项目对 npm 版本有要求npm 7 的依赖树解析规则更严格老项目直接 npm install 可能报 ERESOLVE。此时可以尝试 npm install --legacy-peer-deps或者直接删除 package-lock.json 重新安装。6.4 生产环境部署的推荐方案生产部署我推荐两种方式常规方式后端打包成 jar由 systemd 管理前端打包成静态文件由 Nginx 托管数据库用独立 MySQL 实例。容器化方式后端写 Dockerfile前端用 Nginx 镜像打包MySQL 用官方镜像通过 docker-compose 统一编排。容器化的好处是环境一致性但也带来了额外学习成本。如果团队对 Docker 不熟不建议为了“显得先进”强行容器化。我见过不少项目为了 Docker 而 Docker最后整个团队都在和网络模式、卷挂载做斗争得不偿失。7. 常见问题与排查技巧实录7.1 MyBatis SQL 查询结果映射失败典型症状接口返回的某个字段一直是 null但数据库里明明有值。排查思路按顺序走检查 XML 里的 resultMap看 column 和 property 是否严格对应。特别注意下划线字段和驼峰字段的映射。如果开启了 mapUnderscoreToCamelCasetrue确认实体类字段命名规范性比如 create_time 要对应 createTime。检查是否有多表联查时字段名冲突两个表都有 create_time结果映射到同一个属性上。建议在开发环境开启 MyBatis SQL 日志输出配置方式是在 application.yml 里设置 logging.level.com.project.mapperdebug。这样每次查询都能看到实际执行的 SQL 和参数定位问题效率大大提高。这个配置建议从项目一开始就打开写完再关。7.2 Vue3 页面数据不更新的诡异现象Vue3 的响应式系统基于 Proxy通常不会出现 Vue2 那种新增属性不响应的问题。但有一个常见坑用 reactive 定义对象然后整体替换该对象时会丢失响应式连接。比如const state reactive({ list: [] }) // 请求后 state { list: res.data } // 错误正确写法是修改对象属性而不是整体赋值state.list res.data另一个坑是 ref 在模板里自动解包但在函数内部访问时需要用 .value。如果不小心在 setup 里返回了一个 ref 对象模板中写 {{ userInfo }} 是正确的但如果写 {{ userInfo.name }}并且在 script 里忘了 .value控制台会报 undefined。这类问题通过 ESLint 的 vue/no-ref-as-operand 规则可以大部分拦截。7.3 跨域问题的最终解决方案前后端分离项目联调阶段必遇到跨域。常见表现是浏览器控制台报 CORS 错误Request 是 OPTIONS 方法。解决方案有三个层次临时方案后端写一个 CorsFilter放行所有来源和方法。适合开发环境但生产环境不安全。标准方案在 SpringBoot 里配置 CORS 规则指定允许的 origin。注意不能配置为 *因为涉及携带 cookie 的请求无法通过。最推荐方案生产环境用 Nginx 反向代理让前端请求和后端接口处于同一个域名之下从根上规避跨域。我的经验是跨域问题不要在开发环境过度纠缠浏览器装一个 Allow CORS 插件临时解决生产环境统一交给 Nginx。这样既省时间也符合真实部署架构。7.4 定时任务莫名的重复执行SpringBoot 里用 Scheduled 注解做定时任务有时会出现任务重复执行。原因通常是部署了多个实例或者应用被重复启动。排查方法很简单看日志里是否有多个 Spring 启动横幅——如果有说明有多个进程另一种情况是配置了并行的 TaskScheduler导致同一方法被并发调用。解决方法确保 Scheduled 方法内不依赖实例状态并把锁放到底层保证。最简单的做法是给方法加一个基于数据库的唯一执行记录表任务开始时插入一条 task_execution_logtask_name、execution_time插入成功才执行任务逻辑。这种方式在分布式环境下也能生效比单机的 synchronized 锁可靠得多。8. 项目扩展与二次开发建议8.1 从单体到可扩展架构的演进路径这个项目的基础架构是单体但代码组织方式已经为后续演进留了空间。如果运营数据量快速上涨优先考虑读写分离——MySQL 主从复制SpringBoot 里通过 AbstractRoutingDataSource 实现多数据源路由。这是成本最低的扩容手段比引入微服务全家桶划算得多。如果业务上需要对接第三方系统比如政府监管平台、智能穿戴设备建议在 Service 层之下加一层防腐层 adapter。外部系统的协议变更只影响 adapter 模块主体业务代码不需要改动。这是我在很多实际项目中总结出来的经验——提前留好接口边界。8.2 报表模块的优化空间平台运营一段时间后管理层一定需要一个“数据大屏”或周报月报功能。基于现有 MySQL 数据可以直接写报表 SQL 实现但复杂的统计查询要避免在业务高峰期执行。更稳妥的做法是新建一个只读的统计库同步业务库的数据报表查询只打统计库。同步方案我推荐定时全量同步因为这个体量的数据不需要实时的 Binlog 订阅。每天凌晨同步一次白天统计查询就畅通无阻了。附带的好处是统计库可以做只读权限控制降低误操作风险。8.3 敏感信息的安全加固养老平台涉及老人姓名、身份证、健康状况等个人信息合规要求很高。安全加固至少要做三层数据库层——身份证等敏感字段使用 AES 加密存储接口层——通过报文日志脱敏禁止打印完整身份证号传输层——生产环境必须启用 HTTPS。我见过不少项目把 HTTPS 部署放在最后才考虑结果改造时发现前端资源引用、接口请求地址都写死了 http改起来非常费劲。建议在项目初始化阶段就把 HTTPS 环境列进规划前后端代码里所有地址都使用相对路径或协议相对路径避免这个问题。写在最后做这类业务系统源码分享项目我个人的体会是技术方案永远服务于业务场景。养老服务平台看起来像是一个普通的 CRUD 系统但真正深入后你会发现权限粒度、状态流转、数据审计、敏感信息保护每一处都隐藏着行业的真实约束。这个项目的源码价值不在于“能跑”而在于它提供了一个可以挂载真实业务规则的骨架拿来学习也好、做二次开发也好至少不用从零开始踩一遍坑。最后再分享一个实操小技巧拿到任何一套前后端分离源码第一步不要急着跑。先花半小时把数据库表结构画成 ER 图把每个表的业务含义理清楚再对接口文档看每个 API 操作哪几张表。这个过程比直接 debug 代码高效得多而且能让你从作者视角理解整个系统的设计意图。这套方法你多试几次能力提升速度会非常明显。
返回列表