ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue智慧养老平台开发实战:从需求到部署全流程

SpringBoot+Vue智慧养老平台开发实战:从需求到部署全流程 接手这个基于SpringBoot与Vue开发的智慧养老服务平台之前我以为它跟普通的后台管理系统差别不大无非是增删改查套一层管理界面。真正把项目拆开做下去才发现养老场景的特殊性会直接拉高整个系统的复杂度服务对象是老人使用者是家属、护工、运营人员、社区管理员角色的碎片化程度远超普通企业应用数据链路也不只是表单提交还牵扯健康设备、定位设备、音视频通话、紧急救助这类实时性业务。而SpringBoot加Vue这套组合刚好在快速落地和支撑复杂业务之间给了一个比较稳的平衡点这也是我最终选定这个技术栈的原因。这篇文章会从需求拆解、后端建模、前端页面、联调部署到问题排查把整个爱老助老系统的完整开发过程掰开揉碎讲一遍。无论你是拿它当毕业设计还是想给社区、养老机构做一个真实可用的平台这里面的功能规划思路、权限设计、设备对接、文件存储、打包部署方案基本都能直接抄作业。对SpringBoot和Vue怎么前后端分离协作还不熟的人也可以按这篇文章的节奏把项目从零到一拉起来。1. 需求拆解与功能架构设计1.1 智慧养老到底在解决什么问题养老服务平台如果只做老人信息的录入和管理那叫老人台账谈不上智慧。这个项目的核心命题是如何用一套系统把独居老人的日常安全、服务工单流转、家属远程关注、护工上门服务这几件事串成一个闭环。我见过不少同类项目死在功能堆砌上——今天加一个健康问答明天加一个活动报名最后页面一大堆核心业务反而没人用。真正用得起来的养老平台本质上都是在回答三个问题老人状态怎么样健康数据、位置、是否发生异常。老人需要什么帮助助餐、助浴、助医、紧急呼叫。谁来解决、解决得怎么样工单分派、上门记录、家属反馈。所以我在设计这个项目的时候主流程明确为设备采集数据 → 系统分析判断 → 生成服务需求或告警 → 分派工单 → 服务执行与反馈 → 家属同步知情。所有模块都围绕这条主链路展开其他功能全部靠边。1.2 功能模块边界划分按照上面的主流程我把系统划分为六个核心模块每个模块的定位在开工前就要定死模块核心职责典型功能点老人档案老人的基础信息、健康档案、家属关系基本信息、既往病史、紧急联系人、居住地址定位健康监测对接智能手环/血压计等设备采集健康指标心率、血压、血氧数据展示、异常阈值告警位置与安全老人定位、电子围栏、SOS紧急呼叫实时定位、围栏越界告警、一键呼叫服务工单助餐/助浴/助医等服务的预约、派单、完成闭环服务下单、护工抢单、服务记录、评价反馈家属互联家属对老人状态的知情与远程互动健康报告推送、视频通话入口、紧急通知运营管理平台侧的日常运营配置护工管理、服务项目配置、数据统计报表模块边界划分的另一个好处是数据库表结构能跟着模块走后面做权限控制时按模块分配菜单和接口就非常顺。很多新手项目后期改不动就是前期模块没有边界概念所有的功能全堆在几张万能表里。1.3 技术选型为什么是SpringBoot Vue这个项目的前后端分离架构我是在对比过单体JSP方案、纯模板渲染方案之后定的。核心原因有三点。第一前后端分离能并行开发。SpringBoot负责纯接口Vue负责页面交互定义好接口文档之后前端和后端可以同时开工周期明显缩短。对毕设或者小团队来说这个效率优势很致命。第二SpringBoot的生态对快速搭建太友好了。自动装配把大量配置逻辑封装在起步依赖里Spring Security做认证授权、MyBatis-Plus做数据持久化、MinIO做文件存储、Docker打包部署每一环都有成熟的集成方案。项目能跑起来的时间从一两周压缩到两三天。第三Vue的组件化和响应式机制贴合管理端页面的开发习惯。页面碎片多、状态联动频繁比如选择了老人之后健康数据和工单记录要联动刷新Vue的响应式数据绑定能省掉大量手动操作DOM的代码。组件复用也直接老人卡片、工单状态标签、图表组件都可以抽出来反复用。2. 后端架构与核心实现要点2.1 SpringBoot项目工程结构规划后端工程我没有用单模块结构而是直接按多模块Maven工程来组织这算是从第一个版本踩坑得到的教训。早期图省事把所有代码都放在一个模块里结果业务类互相引用没有边界后来接口一多就乱成了线团。参考结构如下parent-pom ├── common // 公共工具、统一返回体、全局异常、常量 ├── system // 登录、权限、用户管理、菜单管理 ├── elderly // 老人档案、健康数据、定位信息 ├── service // 工单服务、护工管理、服务项目 ├── message // 消息推送、通知记录 └── api // 对外接口的Controller层统一入口common模块存放Result统一返回体、BusinessException、全局异常处理器、JWT工具类、日期工具等所有业务模块都依赖它。system模块负责Spring Security的过滤链配置和登录认证因为所有接口都要过权限校验所以它被其他模块依赖。Controller层统一收口在api模块这样做的好处是接口路由清晰而且做接口文档Swagger/Knife4j扫描时不用每个模块单独配。实际编码时Service层尽量面向接口编程Mapper层用MyBatis-Plus的BaseMapper复杂的联表查询才写XML这样常规单表操作几乎不用手写SQL。2.2 核心数据表设计表结构是一个养老平台的骨架我前后调整过三轮才稳定下来。核心表主要有这么几张老人档案表elder_info字段名 类型 说明 id bigint 主键 name varchar(32) 老人姓名 gender tinyint 性别 birth_date date 出生日期 id_card varchar(18) 身份证号 phone varchar(20) 本人电话 address varchar(255) 居住地址 lat decimal(10,6) 纬度 lng decimal(10,6) 经度 health_level tinyint 健康等级1自理 2半自理 3失能 emergency_name varchar(32) 紧急联系人 emergency_phone varchar(20) 紧急联系电话 family_id bigint 关联家属账号 create_time datetime 创建时间注意lat和lng这是做人位置服务的关键字段。很多系统把老人的地址只存成字符串后面做电子围栏和地图撒点的时候就傻了坐标必须要单独存成数值类型配合腾讯地图或高德地图的逆地理编码使用。健康监测记录表health_record字段名 类型 说明 id bigint 主键 elder_id bigint 老人ID device_code varchar(64) 设备编号 heart_rate int 心率 blood_pressure_high int 收缩压 blood_pressure_low int 舒张压 blood_oxygen int 血氧饱和度 measured_at datetime 测量时间这张表是典型的高频写入表。如果设备接入量大了建议按时间分表或分区不然一年几百万条数据会让查询明显变慢。我项目里先做了按月分表的预留逻辑写入时通过elder_id和measured_at双条件路由避免后期返工。服务工单表service_order字段名 类型 说明 id bigint 主键 order_no varchar(32) 工单编号 elder_id bigint 老人ID service_type tinyint 服务类型1助餐 2助浴 3助医 4陪诊 nurse_id bigint 护工ID status tinyint 状态1待接单 2已接单 3服务中 4已完成 5已取消 appoint_time datetime 预约时间 address varchar(255) 服务地址 remark varchar(500) 备注 create_time datetime 创建时间 finish_time datetime 完成时间工单表是业务流转的中枢状态字段的设计直接影响后续的列表筛选和统计报表。我在状态字段上加了索引因为运营后台的工单列表基本都按状态查。2.3 健康监测数据接入与异常告警实现健康监测的常见误区是设备数据到了就存库、存库就展示这样做出来的功能只是个数据仓库对老人和家人来说没有实际价值。真正的价值在于判断异常和触发响应。设备端的对接方式我用的是MQTT协议上报。智能手环等设备通过网关把数据推送到EMQX BrokerSpringBoot后端通过MqttListener订阅主题接收。手环上报的数据格式大概是这样的{ deviceCode: HW-2024-0001, heartRate: 128, bloodOxygen: 91, bloodPressureHigh: 168, bloodPressureLow: 102, measuredAt: 2024-09-21 08:30:00 }收到数据后除了存库还要过一个告警规则引擎。我没有引入独立的规则引擎组件而是用策略模式实现了几种阈值判断心率超过120或低于50触发心率异常告警。血压收缩压超过160或舒张压超过100触发高血压告警。血氧饱和度低于90触发低血氧告警。定位数据判断老人是否离开设定的电子围栏半径。告警产生后要根据老人的health_level和紧急联系人配置决定通知渠道。这里我把通知渠道拆成了短信、App站内信、企业微信应用消息三种。实际项目里企业微信的应用消息比站内信好用得多——家属一般不会每天打开App但企业微信的提醒是能实时弹出来的。在项目里通过企业微信的JS-SDK和服务端API对接发送文本卡片消息家属点击卡片就能跳转到老人详情页。2.4 从自动装配原理到自定义业务组件SpringBoot开发到一定阶段光会用框架是不够的得理解它的自动装配原理否则遇到启动报错、配置不生效就只能干瞪眼。SpringBootApplication实际上是Configuration、EnableAutoConfiguration、ComponentScan的组合。其中EnableAutoConfiguration会读取META-INF/spring.factoriesSpring Boot 2.7之后是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里的配置类列表按条件注解ConditionalOnClass、ConditionalOnProperty决定是否装配对应的Bean。我在项目中基于这套机制做了一个ElderAutoConfiguration把健康告警规则引擎、定位围栏计算服务、护工接单状态机都封装成了自有的自动配置模块。这样在另一个项目里面如果需要复用这套养老业务逻辑只要引入Maven依赖配置文件里写上开关规则引擎就自动生效了。这就是为什么面试总问自动装配原理——它不只是理论的是可以在真实项目里拿来设计公共模块的。3. Vue前端工程与核心页面实战3.1 项目初始化与目录组织前端项目我用的Vite创建相比Vue CLIVite的开发冷启动速度快很多对TypeScript的支持也更干净。创建命令npm create vitelatest elderly-web # 选择 Vue 3 TypeScript 模板 cd elderly-web npm install推荐使用Vue 3的script setup语法。比选项式API更简洁响应式变量直接声明用起来跟写原生JavaScript差不多团队上手成本低。前端目录结构按业务模块拆src ├── api // 接口请求封装按后端模块分文件 ├── assets ├── components // 公共组件 ├── layout // 主布局框架侧边栏、顶栏、面包屑 ├── router // 路由表 动态路由生成逻辑 ├── store // Pinia状态管理 ├── styles └── views // 页面 ├── dashboard // 大屏首页 ├── elderly // 老人档案 ├── health // 健康监测 ├── order // 工单管理 ├── message // 消息通知 └── system // 系统管理封装axios实例是老规矩但我这里要单独说的是token过期处理。养老平台里有大量接口是家属端和运营端共用的Token过期后不能只弹个报错要在拦截器里做统一的刷新逻辑。我用的是双Token方案accessToken有效期2小时refreshToken有效期7天拦截器捕获401错误后自动用refreshToken换取新的accessToken重放原请求。这个体验对家属端App特别重要不然老人家属打开小程序看到登录过期还要重新输手机号收验证码损耗非常大。3.2 适老化UI设计的几个关键点智慧养老平台的前端跟普通管理系统有个本质区别它除了运营人员在电脑上用还有很大几率被老人和家属在手机、平板上用。所以UI设计不能只考虑管理端的密度和信息冗余更要考虑适老化。我在项目里沉淀了几条适用性原则字号最小16px。老人视力下降是普遍的小于16px的文字在手机上看着非常费劲。操作按钮高度不低于44px。这是移动端触控的基本建议值老人的手部精细动作能力下降太小的按钮容易误触。SOS紧急呼叫按钮我特意做成了悬浮固定在右下角红底白字金色描边长按3秒触发避免误触。颜色对比度要够。浅灰字配白底年轻开发觉得好看老人根本看不清。正文用#333333辅助文字用#666666禁用状态才用浅色。关键信息卡片化语音播报。健康数据页面我做了语音播报按钮点击后通过Web Speech API读出血氧、心率是否正常。试运营时发现这个功能的使用率非常高很多独居老人早上起床会点一下听数据。3.3 动态路由与权限控制管理后台的菜单权限是靠动态路由实现的。登录后后端返回当前用户的角色和权限标识列表前端根据这些数据过滤出该角色可见的页面路由通过router.addRoute动态挂载。这里有个非常经典的坑菜单权限和按钮权限要分开。动态路由负责控制你能不能看到这个菜单但一个页面上可能有编辑、删除、审核好几个按钮不同角色的按钮权限不同。我在代码里用了自定义指令v-permissionhealth:export控制按钮显隐后端接口再做一次权限校验。这样就算前端按钮被绕过直接调接口也会被拒安全上才是闭环的。动态路由刷新后白屏是高频问题。原因是addRoute进去的路由存在内存里浏览器刷新后状态丢失。解决方法是把用户角色信息存到本地存储localStorage刷新后从本地读取角色ID重新请求一次该角色的菜单配置再动态生成路由。这个过程要放在路由守卫的beforeEach里做成异步保证路由挂载完成后再放行。3.4 地图定位与视频播放实现老人定位功能我用的是腾讯地图JavaScript API。选它而不是高德原因很简单之前项目里高德地图Web服务端的逆地理编码配额经常不够用腾讯这边对个人开发者的额度更友好。初始化加载!-- public/index.html 中引入 -- script charsetutf-8 srchttps://map.qq.com/api/gljs?v1.expkeyYOUR_KEY/script地图页面里根据经纬度渲染老人的位置标记同时绘制以老人家为中心的电子围栏。摄像头画面接入是这个项目里比较进阶的场景——老人在家里装了一个智能摄像头家属希望能通过平台实时查看画面。摄像头推流后生成的是HLS格式的m3u8地址Web端用hls.js播放import Hls from hls.js export function initHls(videoElement, m3u8Url) { if (Hls.isSupported()) { const hls new Hls() hls.loadSource(m3u8Url) hls.attachMedia(videoElement) } else if (videoElement.canPlayType(application/vnd.apple.mpegurl)) { // iOS Safari 原生支持 HLS videoElement.src m3u8Url } }这个场景的另一个问题是视频流地址通常带时效签名后端统一通过一个接口返回带签名的播放地址前端拿到之后再传给播放器避免地址直接写在页面上导致泄露。4. 前后端联调、私有化存储与容器部署4.1 前后端分离联调中的几个关键配置前端用Vite开发服务器后端跑在8080端口跨域问题绕不开。开发环境我用Vite的代理解决避免生产环境还要处理CORS的预检请求问题// vite.config.ts export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, // 后端接口统一前缀为 /api代理时不需要重写 } } } })生产环境则由Nginx统一反向代理把/api转发到后端的SpringBoot容器。这样整个链路里浏览器始终只跟同源的Nginx通信CORS基本用不上。接口文档我用了Knife4jSwagger的增强版理由就一个接口调试界面可以直接在线测试而且导出的OpenAPI文档可以喂给Apifox自动生成前端类型定义文件。前后端对接的效率提升非常明显前端同事不再拿着一份静态文档手敲TypeScript类型了。4.2 MinIO私有化文件存储接入项目里涉及老人照片、身份证附件、摄像头的抓拍图。本来可以直接用阿里云OSS但考虑到养老数据敏感而且项目可能部署在社区内网的服务器上我选了MinIO做私有化对象存储。MinIO的接入方式相当成熟。服务端安装后SpringBoot里引入依赖dependency groupIdio.minio/groupId artifactIdminio/artifactId version8.5.7/version /dependency配置application.ymlminio: endpoint: http://127.0.0.1:9000 access-key: minioadmin secret-key: minioadmin bucket-name: elderly-system封装上传工具类后有两点经验值得提一是上传时文件名要重命名用UUID或日期随机字符串不要保留用户上传的中文名否则在URL里会有一堆编码且容易出现文件名冲突二是上传成功后返回的对象要包含MinIO预签名的访问地址或临时凭证。桶的权限建议设为私有服务端生成带有效期的预签名URL返回给前端直接展示图片既保护了文件不被爬走又不用把AccessKey暴露给前端。4.3 Docker部署SpringBoot与Vue整套服务部署环境我用的Docker Compose编排整套包含四个容器elder-serverSpringBoot后端elder-webNginx Vue构建产物minio对象存储服务mysql数据库后端DockerfileFROM maven:3.8-openjdk-17 AS build WORKDIR /app COPY pom.xml . COPY common/ common/ COPY system/ system/ # 其他模块省略先复制依赖再复制源码利用Docker构建缓存加速 COPY . . RUN mvn clean package -DskipTests -q FROM openjdk:17-jre-slim WORKDIR /app COPY --frombuild /app/api/target/api.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar, --spring.profiles.activeprod]前端DockerfileFROM node:20-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm install --registryhttps://registry.npmmirror.com COPY . . RUN npm run build FROM nginx:alpine COPY --frombuild /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80关键配置在Nginx的nginx.conf里。重点处理两个问题。一是Vue路由用history模式时刷新页面会出现404。因为前端路由是虚拟的Nginx不知道/elder/detail对应哪个静态文件。配置如下server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location /api/ { proxy_pass http://elder-server:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }try_files $uri $uri/ /index.html;这行就是解决history路由刷新404的关键。二是前端容器要能访问到MinIO。MinIO的预签名URL在生成时如果填的是http://127.0.0.1:9000前端在浏览器里访问不了。这里需要在MinIO配置项里把endpoint换成局域网可访问的IP或域名。这个坑非常隐蔽很多项目部署到服务器上图片突然加载不出来了八成就是这个原因。4.4 多环境配置与SpringBoot配置管理项目分了dev、prod两套环境配置文件通过启动参数--spring.profiles.activeprod切换。数据库连接、MinIO地址、Redis地址、JWT密钥分别放到对应环境文件里。有个小细节application.yml里如果配置了server.port0SpringBoot会随机分配一个可用端口启动多实例部署时防止端口冲突。但生产环境我一般不这么干因为Nginx反代需要固定的上游端口。随机端口更多用于本地多实例联调的测试场景。另一个容易被忽略的问题是banner.txt——SpringBoot启动时那个ASCII字符画。项目交付时我在resources下放了一个定制banner启动日志一眼能看到环境标志运维部署时判断版本和环境非常直观算是一个低成本高体验的小技巧。5. 常见问题与排查技巧实录5.1 SpringBoot版本与依赖冲突SpringBoot 3.x相对2.x变化很大最明显的是javax包名改成了jakarta很多老教程的代码直接搬过来会编译报错。我的建议是小团队做新项目直接上SpringBoot 3.x JDK 17不要回头用2.x但如果你依赖的某个第三方组件还没适配jakarta那就老老实实退回2.7.x别硬扛。遇到过最头疼的问题是依赖冲突。比如项目中同时引入了旧版的commons-lang3和某个中间件自带的传递依赖启动时方法找不到。排查办法我总结了一套流程用mvn dependency:tree看依赖树找到冲突的jar。用mvn dependency:analyze检测未使用和重复的依赖。在pom.xml中用exclusions排除特定传递依赖。必要时用mvn dependency:tree -Dverbose看冲突详情。要注意的是直接删掉不确定用途的传递依赖有可能引发NoClassDefFoundError所以排除前最好确认当前项目的代码里是否真的用到了被排除的类。5.2 拿到Jar包如何反编译排查线上问题线上问题排查时经常遇到一个场景现场环境出Bug但是没有Git仓库权限只能拿到一个可运行的Jar包。这时候需要反编译去看代码到底跑的是什么逻辑。我的做法是用CFR一个Java反编译器社区里也有JD-GUI和Luyten可视化工具。命令行方式最通用java -jar cfr.jar app.jar --outputdir ./src-decompiledCFR会把Jar里的class文件反编译成Java源码文件然后用IDEA打开这个源码目录搜索关键字。值得注意的是反编译出来的代码在泛型、Lambda表达式、switch-case等语法结构上会有变形定位逻辑可以但不能直接当源码改。排查完之后正确的修复流程还是在Git仓库里改代码、重新构建、替换线上Jar包。5.3 Vue依赖安装失败与页面白屏npm install失败是前端开工第一道坎。常见原因有网络问题、Node版本与依赖不兼容、缓存脏数据。在国内环境我基本把npm源换成淘宝镜像npm config set registry https://registry.npmmirror.com如果安装后启动报SyntaxError先检查Node版本Vue 3 Vite通常要求Node 18版本不够就升级Node。还有一个容易被忽略的问题package-lock.json是从其他环境带过来的里面锁定的依赖版本跟你当前环境不匹配。这种情况我一般先删掉锁文件和node_modules重新安装。页面白屏的问题先开浏览器开发者工具看Console报错。最常见的是接口跨域报错其次是路由配置里某个组件路径写错导致加载失败。Vue Devtools插件对排查组件状态问题帮助很大建议Chrome必装。5.4 静态文件与多媒体播放兼容性HLS视频流播放时Chrome和FireFox需要hls.jsSafari原生支持直接指定src。另外如果m3u8地址是HTTP而页面是HTTPS会被浏览器拦截为混合内容导致无法播放。解决方式是把视频播放地址也走HTTPS或者反代转发这个在对接家用摄像头时特别常见。腾讯地图API加载慢的问题也可以用动态加载的方式规避等用户进入定位页面时才插入script标签而不是在入口HTML里直接引。这样首页首屏加载时间能明显下降。5.5 数据安全与误操作恢复养老平台涉及大量老人隐私数据开发时可以偷懒上线了不能偷懒。我的建议是至少做到三条操作日志。设计一张operation_log表记录谁在什么时间对哪条老人数据做了什么操作。用Spring AOP或MyBatis-Plus的元数据填充都行这个功能不能在系统上线后再补。回收站机制。删除老人档案时做逻辑删除deleted字段而不是物理删除。试运营期间家属说老人换个地址要重新登记结果误删了档案如果没有逻辑删除位数据就真的没了。定时备份。MySQL每天凌晨用mysqldump备份全库保留最近7天。配合MinIO的版本控制文件误删也能找回。6. 个人实操总结整个项目做下来我最深的一点体会是养老平台的技术难点不在某个单点技术上而在业务的完整闭环上。单个模块拆出来看健康监测是简单的阈值判断工单管理是普通的状态机流转地图定位是调用第三方API。但把它们串起来覆盖老人状态变化→系统识别→服务介入→家属知情这条完整链路才是这个系统真正值钱的地方。另外如果你也想做类似的毕设或者真实项目建议把开发重心放在两个打通上一是打通数据把健康设备、位置设备、人工录入的数据统一到一个老人档案里二是打通通知让告警消息能顺畅触达家属、护工、运营人员。这两条线做好了系统的实用价值立刻就能体现出来。SpringBoot和Vue在这套体系里只是载体真正贯穿项目始终的是你对养老服务场景本身的理解深度。这个认知比任何框架知识都重要。
返回列表