拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于SpringBoot2与Vue3的养老智慧服务平台设计与实践

基于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,单表操作基本零SQL
  • Entity层:与数据库表字段一一对应,驼峰命名自动映射下划线字段

我见过不少半路出家的项目,Controller里写了三百行业务代码,Service层空壳,后面要加功能连原开发都理不清头绪。所以分层这件事,必须在项目第一天就立好规矩,Code Review时重点盯。

统一返回结构是另一个容易被低估的设计点。前端的Axios拦截器、后端的全局异常处理器都依赖这个结构。我的习惯是这样的:

public class Result<T> { 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_order:elder_id建索引,status建索引,查询工单列表十有八九按这两个条件查
  • health_record:(elder_id,measure_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显式构造,可读性更好:

LambdaQueryWrapper<HealthRecord> 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,对象用reactive,ref底层也是包了一层.value的reactive,但语义上更清晰。有一个容易踩的细节:reactive无法直接替换整个对象,但ref可以。所以在需要整体重置表单时,我用ref包裹表单对象,重置时直接赋值新的对象即可。

3.2 Vue3 + Element Plus后台页面实操

后台管理系统的UI选型,Element Plus是Vue3生态下的稳妥选择。表格、表单、弹窗、日期选择器,开箱即用,社区资料丰富。这年头不应自己封装组件,坐拥成熟组件库还把光阴花在轮子上,是对项目交付周期的不负责任。

以老人列表页为例,我会拆成三个部分:搜索区(姓名、身份证、入住状态)、表格区(基础信息展示)、分页区。表格列需要展示性别和状态时,用枚举字典做映射,不要改后端返回的数据结构,前端展示层的事留给前端解决:

const statusMap = { 0: '未入住', 1: '在住', 2: '已退住' }

表单弹窗校验,Element Plus的表单校验规则写清楚后体验不错。身份证号的校验需要自定义validator,18位格式、生日合法性、校验位三个层面都要检查。这块不要偷懒,养老平台的数据录入人员每天要录几十个老人,表单校验是第一道质量闸门。

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-server=utf8mb4 --collation-server=utf8mb4_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连接串加allowPublicKeyRetrieval=true,要么创建用户时指定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] Description=Elder Care Service After=network.target [Service] User=deploy ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar /opt/elder-care/elder-care.jar SuccessExitStatus=143 Restart=always RestartSec=10 [Install] WantedBy=multi-user.target

SuccessExitStatus=143是容易踩的细节。systemd停止服务时发送SIGTERM信号,JVM默认退出码是143,如果不加这个参数,服务正常停止也会被判定为异常退出。Java应用不设置Restart=always,进程崩溃后不会有任何恢复机制。

再看前端。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 后端启动失败排查清单

部署阶段最常出问题的环节,集中在数据库连接和端口占用两块。下面是我实际踩坑总结出来的排查顺序,按出现频率排序:

  1. 数据库连不上。检查MySQL是否启动、3306端口是否监听、JDBC连接串的账号密码是否正确。这三项占了七成问题。
  2. 端口被占用。SpringBoot默认8080端口,如果服务器上已经有别的服务在跑,启动直接报Port already in use。换端口或者kill旧进程,二选一。
  3. 字符集问题。启动时报Unknown character set: utf8mb4,说明MySQL版本过低或者字符集配置不对,MySQL5.5及以下对utf8mb4支持不全,5.6以上才可靠。
  4. 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的逻辑删除配置一旦开启,全局生效,所有查询自动拼接deleted=0条件。但如果你有些表不需要这个机制,或者联表查询时走了自定义SQL,就容易出现查不到数据的问题。我的建议:核心业务表开启逻辑删除没问题,但日志表、操作记录表不要开,纯粹增加查询负担。

第二个教训关于乐观锁。配置了@Version字段和乐观锁插件后,更新操作会自动拼接版本号条件。但更新失败时不会抛异常,只是影响行数为0,需要手动判断:

boolean result = elderInfoMapper.updateById(elderInfo) > 0; if (!result) { throw new BusinessException("数据已被他人修改,请刷新后重试"); }

不判断这段话,乐观锁就形同虚设。

第三个教训是insert方法遇到Field 'xxx' doesn't 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的搭配基本算是最优解了。

返回列表