基于SpringBoot2与Vue3的养老智慧服务平台设计与实践
1. 项目整体设计与技术选型思路先说说这个项目本身。养老智慧服务平台核心要解决的是养老服务过程中的信息孤岛问题——老人的基本信息、健康档案、服务工单、家属沟通记录这些数据以前分散在纸质台账和不同系统里社区工作人员每天光整理信息就要花大量时间。用Java Web技术栈把这个过程线上化让护工、管理员、家属能在同一套系统里协作这是我接手这类项目时首先明确的目标。技术栈选定为SpringBoot2 Vue3 MyBatis-Plus MySQL8.0不是拍脑袋决定的。SpringBoot2在前几年是绝对主流生态成熟招人容易各种坑都有现成答案Vue3配合Composition API写后台管理界面效率很高MyBatis-Plus则把最枯燥的单表CRUD操作简化到了极致开发者能腾出精力处理业务逻辑MySQL8.0在性能和功能上相比5.7有明显提升窗口函数、公用表表达式、更好的JSON支持这些特性在报表统计场景下非常有用。为什么不用SpringBoot3如果你是2025年以后才开工的新项目可以认真考虑SpringBoot3 JDK17的组合。但这个项目基于SpringBoot2不代表落后它意味着更低的迁移成本、更丰富的第三方集成案例、更稳定的生产环境表现。尤其在养老这类对系统稳定性要求较高的公共服务场景选成熟方案比追新更重要。这套系统的核心业务模型大致分四块老人档案管理基本信息、健康状况、紧急联系人、服务工单流转需求发起、派单、执行、回访、健康数据记录血压、血糖、用药提醒等周期性数据、家属互动通知推送、探视预约、在线反馈。每一块都不是复杂的业务逻辑但数据之间关系密集表设计做得好不好直接决定后续开发是顺畅还是反复改表。2. 后端架构与数据库设计拆解2.1 SpringBoot2分层架构实践后端采用经典的四层结构这个结构看着简单但大多数人第一次搭都会在边界划分上犯迷糊。Controller层只做参数接收、调用Service、返回统一结果不做任何业务判断Service层业务逻辑的核心归宿事务控制在这里声明Mapper层继承BaseMapper单表操作基本零SQLEntity层与数据库表字段一一对应驼峰命名自动映射下划线字段我见过不少半路出家的项目Controller里写了三百行业务代码Service层空壳后面要加功能连原开发都理不清头绪。所以分层这件事必须在项目第一天就立好规矩Code Review时重点盯。统一返回结构是另一个容易被低估的设计点。前端的Axios拦截器、后端的全局异常处理器都依赖这个结构。我的习惯是这样的public class ResultT { private Integer code; // 200成功400参数错误500系统异常 private String message; private T data; }配合全局异常处理器Controller里就不需要到处try-catch了。业务异常直接抛出BusinessException由全局处理器统一捕获转成对应JSON返回代码干净很多。2.2 核心数据表设计复盘养老平台的表结构除了常规的用户、角色、权限三件套核心业务表有这几张。我先说设计思路再给关键字段。老人信息表elder_info这是整个系统的数据基石。除了姓名、身份证号、性别、年龄这些基础字段特别要注意这几点紧急联系人要有两个以上且联系人电话单独建字段不做关联表查询入住状态用枚举字段维护0-未入住、1-在住、2-已退住不要用时间字段倒推状态健康标签存的是标签ID拼接的字符串虽然违反第一范式但业务上只是展示用省一次关联查询服务工单表service_order工单是平台的高频操作对象老人家属提交服务需求管理员派单给护工护工上门执行后回传结果。设计时务必包含状态机流转字段0-待派单、1-已派单、2-执行中、3-已完成、4-已取消、5-待评价。这个状态字段的值变化要配合update_time做审计谁在什么时间把单子从待派单改成已派单这些信息对服务投诉追溯非常重要。健康测量记录表health_record养老场景下血压血糖这类数据是周期性采集的一个月一个老人就有几十条记录。设计时要把测量类型、测量值、单位拆开放方便后续做趋势图表。考虑到最多几千个老人的规模完全没有必要分表分库MySQL8.0单表千万级数据量配合合理索引完全扛得住。索引设计方面三张核心表的经验值是这样的elder_info身份证号建唯一索引入住状态建普通索引service_orderelder_id建索引status建索引查询工单列表十有八九按这两个条件查health_recordelder_idmeasure_date建联合索引这个索引能覆盖按老人查历史记录、按日期范围查两条常用路径2.3 MyBatis-Plus提升开发效率的实际姿势MyBatis-Plus在这个项目中的价值怎么强调都不过分。通用CRUD能力让它直接消灭了大约60%的重复Mapper代码。举个例子新增一个老人信息传统MyBatis要写insert语句、定义parameterType、写resultMap现在一行就搞定ElderInfo elderInfo new ElderInfo(); elderInfo.setName(张桂芳); elderInfo.setIdCard(110101194912123456); elderInfo.setStatus(1); elderInfoMapper.insert(elderInfo);插入后自动回填主键ID这个是IdType.AUTO配合数据库自增主键自动完成的连配置都不用额外加。分页查询也是高频操作。MyBatis-Plus的分页插件使用前必须配置拦截器Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }没配这个拦截器就调用分页方法你会发现SQL里根本没有LIMIT语句但代码又不报错——MyBatis-Plus假装分页成功了返回的total字段永远是0。这是新手踩得最密集的坑。Wrapper构造器的使用也有讲究。简单的等值查询用lambdaQuery但涉及日期范围、模糊搜索、多条件拼接时建议用LambdaQueryWrapper显式构造可读性更好LambdaQueryWrapperHealthRecord wrapper Wrappers.lambdaQuery(); wrapper.eq(HealthRecord::getElderId, elderId) .between(HealthRecord::getMeasureDate, startDate, endDate) .orderByDesc(HealthRecord::getMeasureDate);3. 前端Vue3架构与关键页面实现3.1 Composition API与项目工程化组织Vue3前端的管理后台我的组织方式是每个业务模块一个目录模块内按功能拆分文件。这套组织方式在实际开发中维护体验很好src/ ├── api/ # 接口请求统一封装 │ ├── elder.js # 老人管理模块接口 │ └── order.js # 工单管理模块接口 ├── views/ # 页面组件 │ ├── elder/ │ │ ├── ElderList.vue # 老人列表 │ │ └── ElderDetail.vue # 老人详情 ├── components/ # 公共组件 └── router/为什么选Composition API而不是Options API养老平台这类管理后台单页面的逻辑往往比看起来复杂一个老人详情页可能要同时展示基本信息、健康趋势、服务记录三个子模块每个子模块都要请求接口、处理加载状态、响应刷新事件。Composition API允许把这三个模块的逻辑各自封装成function逻辑内聚性比Options API的data/methods分割方式好了太多。响应式数据的处理我统一用ref和reactive配合。基本类型用ref对象用reactiveref底层也是包了一层.value的reactive但语义上更清晰。有一个容易踩的细节reactive无法直接替换整个对象但ref可以。所以在需要整体重置表单时我用ref包裹表单对象重置时直接赋值新的对象即可。3.2 Vue3 Element Plus后台页面实操后台管理系统的UI选型Element Plus是Vue3生态下的稳妥选择。表格、表单、弹窗、日期选择器开箱即用社区资料丰富。这年头不应自己封装组件坐拥成熟组件库还把光阴花在轮子上是对项目交付周期的不负责任。以老人列表页为例我会拆成三个部分搜索区姓名、身份证、入住状态、表格区基础信息展示、分页区。表格列需要展示性别和状态时用枚举字典做映射不要改后端返回的数据结构前端展示层的事留给前端解决const statusMap { 0: 未入住, 1: 在住, 2: 已退住 }表单弹窗校验Element Plus的表单校验规则写清楚后体验不错。身份证号的校验需要自定义validator18位格式、生日合法性、校验位三个层面都要检查。这块不要偷懒养老平台的数据录入人员每天要录几十个老人表单校验是第一道质量闸门。Vue3中父子组件通信父传子用defineProps子传父用defineEmits这是标准做法。遇到跨多层级的共享状态比如登录用户信息使用Pinia管理比Vuex更轻量且TypeScript支持更好。3.3 Axios封装与接口联调细节Axios封装是前端工程质量的分水岭。我的封装包含基础URL配置、请求拦截器附带Token、响应拦截器统一处理code值、401跳转登录、下载文件流场景特殊处理。service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data // 注意返回的是data不是整个result }, error { ElMessage.error(error.message || 网络异常) return Promise.reject(error) } )接口联调阶段最常见的坑跨域配置。开发环境用Vite代理转发在vite.config.js里配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }后端接口统一加/api前缀这个约定必须在项目启动头一天就定死不然代理规则、后端路由、网关配置全部要返工。生产环境部署时由Nginx统一处理转发后端不需要额外支持跨域。4. 数据库环境搭建与部署实录4.1 Docker方式搭建MySQL8.0环境本地开发环境用Docker跑MySQL8.0是最省心可靠的方式。下载安装、配置、卸载都很干净不会污染宿主机。下面是一个生产可用的docker-compose配置version: 3.8 services: mysql8: image: mysql:8.0 container_name: mysql8 environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: elder_care TZ: Asia/Shanghai ports: - 3306:3306 volumes: - ./data:/var/lib/mysql - ./conf:/etc/mysql/conf.d - ./init:/docker-entrypoint-initdb.d command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci关键参数逐个说明数据目录映射到宿主机./data容器删了数据还在这是最底线的安全保证初始化SQL脚本放在./init目录容器首次启动时自动执行建库建表做到环境一键拉起字符集务必指定utf8mb4而非utf8否则遇到特殊字符比如老人姓名里的生僻字会直接报错或存储乱码TZ: Asia/Shanghai设置时区不然数据库时间比北京时间差8小时排查问题时非常抓狂MySQL8.0和5.7的一个差异很多从旧版本迁移过来的人会碰到8.0默认使用caching_sha2_password认证插件一些老版本客户端和部分Java驱动5.1.x不兼容。如果你的项目报Public Key Retrieval is not allowed错误要么在JDBC连接串加allowPublicKeyRetrievaltrue要么创建用户时指定mysql_native_password更推荐前者。4.2 Linux服务器从零部署MySQL8.0生产服务器如果不想用Docker也可直接二进制方式安装。CentOS环境的标准流程是下载rpm包、配置yum源、安装、启动、初始化密码。整个过程里最容易被忽视的是初始化密码这一步。新装MySQL8.0初始root密码随机生成并写入错误日志文件位置在/var/log/mysqld.log。很多人找不到密码是因为没注意日志里temporary password这行grep temporary password /var/log/mysqld.log拿到临时密码登录后MySQL8.0强制要求先改密码而且密码复杂度校验默认开启过于简单的密码会被拒绝。这实际上是安全加固但如果是在内网测试环境可以先调低validate_password策略再设置弱密码生产环境不建议这么做。4.3 项目部署上线全流程前后端分离项目的部署我用的是经典组合前端构建产物交给Nginx托管后端以jar包形式用systemd守护进程运行。先看后端。使用Maven打包mvn clean package -DskipTests产物在target/目录下elder-care.jar。编写systemd服务文件/etc/systemd/system/elder-care.service[Unit] DescriptionElder Care Service Afternetwork.target [Service] Userdeploy ExecStart/usr/bin/java -Xms512m -Xmx1024m -jar /opt/elder-care/elder-care.jar SuccessExitStatus143 Restartalways RestartSec10 [Install] WantedBymulti-user.targetSuccessExitStatus143是容易踩的细节。systemd停止服务时发送SIGTERM信号JVM默认退出码是143如果不加这个参数服务正常停止也会被判定为异常退出。Java应用不设置Restartalways进程崩溃后不会有任何恢复机制。再看前端。Vite构建产物在dist目录直接拷贝到Nginx的html目录server { listen 80; server_name elder.example.com; root /opt/elder-care-frontend/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }前端用了Vue Router的history模式Nginx必须配置try_files $uri $uri/ /index.html这一行不然刷新页面时Nginx找不到对应路由返回404。这个问题十个Vue项目里有八个会踩。5. 常见问题排查与避坑经验5.1 后端启动失败排查清单部署阶段最常出问题的环节集中在数据库连接和端口占用两块。下面是我实际踩坑总结出来的排查顺序按出现频率排序数据库连不上。检查MySQL是否启动、3306端口是否监听、JDBC连接串的账号密码是否正确。这三项占了七成问题。端口被占用。SpringBoot默认8080端口如果服务器上已经有别的服务在跑启动直接报Port already in use。换端口或者kill旧进程二选一。字符集问题。启动时报Unknown character set: utf8mb4说明MySQL版本过低或者字符集配置不对MySQL5.5及以下对utf8mb4支持不全5.6以上才可靠。JDBC驱动版本不匹配。SpringBoot2.4以上默认使用MySQL8.0驱动如果pom里显式引入了老版本驱动会出现各种诡异异常。5.2 前端渲染空白页的三种原因Vue3项目打包上线后白屏几乎是每个团队必经的坑。我从自己的项目经历总结出三类高频原因路由模式与Nginx配置不匹配。这是白屏第一大元凶。前面提到的try_files配置检查顺序放在第一位。静态资源路径不对。Vite默认的base配置是/如果你的站点部署在子路径比如http://ip/health/必须配置base: /health/重新打包。否则页面加载不到JS和CSS文件直接白屏。浏览器兼容问题。如果你的使用群体里有老版本浏览器养老场景还真不能排除这种可能Vite打包默认目标为baseline-widely-available可能包含较新语法。需要低版本兼容时在vite.config.js里配置build.target为es2015并引入vitejs/plugin-legacy。5.3 MyBatis-Plus使用过程中的实战教训第一个教训关于逻辑删除。MyBatis-Plus的逻辑删除配置一旦开启全局生效所有查询自动拼接deleted0条件。但如果你有些表不需要这个机制或者联表查询时走了自定义SQL就容易出现查不到数据的问题。我的建议核心业务表开启逻辑删除没问题但日志表、操作记录表不要开纯粹增加查询负担。第二个教训关于乐观锁。配置了Version字段和乐观锁插件后更新操作会自动拼接版本号条件。但更新失败时不会抛异常只是影响行数为0需要手动判断boolean result elderInfoMapper.updateById(elderInfo) 0; if (!result) { throw new BusinessException(数据已被他人修改请刷新后重试); }不判断这段话乐观锁就形同虚设。第三个教训是insert方法遇到Field xxx doesnt have a default value错误通常不是数据库问题而是实体类字段没有使用自动填充。MyBatis-Plus的TableField(fill FieldFill.INSERT)配合MetaObjectHandler使用创建时间和更新时间的自动填充必须配好这是新手最容易忽略的一步。5.4 养老业务场景特有注意事项这个平台最特殊的地方在于用户群体的核心是老年人及其家属对系统响应速度和操作便捷性的要求和其他行业不太一样。数据录入环节要考虑到操作人员可能是社区工作者而非专业IT人员表单交互不能太复杂。身份证号码输入后即时校验手机号格式实时提示避免到最后提交阶段才报错。老人姓名中包含生僻字是常态数据库要选对字符集前端字体也要覆盖常用生僻字范围。业务数据安全性方面老人健康数据属于敏感个人信息系统内要有完整的操作日志记录。谁查看了某位老人的健康档案什么时间查看的都必须留痕。这个在项目初期就要设计进去后期补加成本很高。权限设计上护工只能看到自己负责的老人数据管理员可以跨区查看家属只能看自家老人的数据这三类角色的数据隔离必须做认真不能简单依赖前端菜单隐藏。系统异常兜底机制也很重要。养老平台一旦线上出故障尤其涉及服务工单流转影响的是老人的实际生活服务。后端服务降级、接口超时提示、失败重试机制这些工程化能力即便在早期版本也要有基础版。我做这个项目时的底线是核心服务不可用时页面至少要给出明确提示而不是白屏让管理员可以人工介入。6. 项目扩展方向与个人实操心得随便聊聊这套系统后续可以怎么演进。第一个方向是消息通知的主动推送能力。目前系统更多是业务数据的管理后台形态但养老场景很需要主动触达家属关注的健康指标出现异常时第一时间推送到手机服务工单状态变化时通知到对应角色。这块可以对接微信公众号模板消息或小程序订阅消息成本不高但对用户体验提升明显。第二个方向是移动端适配。护工在执行服务工单时通常都在现场不可能随身带电脑。目前很多团队直接用H5页面解决也有做成小程序版本的。Vue3的代码在移动端复用率高主要的改造点在于组件密度和信息展示方式这套后台管理系统的业务接口基本可以原样复用。第三个方向是数据可视化大屏。管理驾驶舱在养老服务中心的接待大厅是很好的展示形态老人总数、服务完成率、健康异常预警、护工工作量排行这些数据在平台上都有缺的只是聚合查询接口和前端图表呈现。最后分享几点我个人的实操体会。项目启动的头三天把数据库表结构和全局返回格式定下来后面能少改很多代码。别急着写业务先建骨架骨架正了后面长出来的肉才对。MyBatis-Plus虽然好用但复杂报表查询不要硬用它拼SQL直接写XML或注解SQL更清晰。工具类是为了提升简单场景的效率不是为了绑架复杂场景的表意。Vue3的Composition API写业务逻辑时自定义hooks是对的方向。老人详情页三个模块的独立请求各封装成一个hook页面代码会清爽很多而且这些hook可以被其他页面复用以极低成本。部署上不要图省事把命令敲在命令行里就跑systemd的进程守护和Nginx的配置管理值得在第一天就做好。生产环境没有守护机制一次进程挂掉就能让团队半夜起来。至少我是被这样教育过一次的。Docker和MySQL8.0的搭配基本算是最优解了。