Spring Boot+Vue社区养老监护平台:从接口设计到部署的完整实战解析
简介面向毕业设计、期末大作业及前后端分离项目案例学习这套基于Spring Boot与Vue.js的社区智慧养老监护管理平台源码完整演示了用户管理、健康监测、紧急呼叫、生活服务预约、亲情互动、数据分析与报告等核心模块的实现路径。适合希望掌握Spring Boot与Vue整合、理解智慧养老业务逻辑的开发者参考也可作为软件工程实践与二次开发的蓝本。包内共434个文件主体包含133个Java后端源码、42个Vue前端组件、161个SVG图标资源同时提供SQL数据库初始化脚本、XML与yml配置文件、MP4操作演示视频和bat一键构建启动脚本压缩包整体约32.94MB目录结构清晰便于本地部署、通读代码和按需扩展。目前已有156人学习下载这份可运行的真实工程能帮助读者快速理解权限分配、数据交互与业务闭环借助演示视频和构建脚本可显著降低环境搭建与二次开发的上手成本。1. 社区养老监护平台为什么适合用 Spring Boot Vue 落地社区养老服务站每天要面对的不只是老人档案还有健康异常记录、物资领用流水、家属远程问询。这些数据散在纸质台账里很难在突发情况发生时快速拼出完整上下文。Spring Boot 把后端表结构快速变成标准 REST 接口内嵌 Tomcat 后连 Web 容器都不用单独搭Vue 负责把老人档案、服务工单、预警消息拆成独立组件页面局部刷新就能响应状态变化。这套源码以“监护管理平台”为界把用户端和管理端放进同一个工程对毕业设计参考价值和期末大作业的完成度要求都很合适。要真正吃透它建议按“接口设计 → 前端对接 → 部署脚本 → 业务闭环”的路径走一遍。2. Spring Boot 后端骨架四个 Controller 的命名规则与职责边界拿到 zip 解压后先别急着双击运行。把src/main/java目录展开第一件值得做的事是看 Controller 层——无论后端技术栈怎么包装Controller 就是整个平台的“门卫”。这个源码包里出现了四个 Java 文件CommonController.java、YonghuController.java、DictionaryController.java、WuziController.java。看到命名就能推测出业务表至少有用户表、字典表、物资表这三类公共接口则被收进了CommonController。先看懂这四个类后面所有页面的数据流都能对得上。2.1 从 Controller 命名还原出后端的分层思路在阅读代码之前先建立一个分类视角。Controller 怎么命名基本决定了项目的业务边界这个包里的职责划分是CommonController公共能力集合比如登录后获取当前用户、读取字典、通用下拉数据。YonghuController用户域包含老人、家属、管理员的注册、登录和资料操作。DictionaryController数据字典维护口增删改查“字典类型下的选项”。WuziController物资域面向社区服务站内的物资登记和领用记录。这类命名规范的好处是看到 URL 前缀就能猜到业务归属比如POST /yonghu/login不用翻代码都知道是用户登录。对毕业设计而言这种结构放在答辩 PPT 上也容易讲清楚对生产项目来说它方便后续按域拆服务因为边界已经划出来了。请求场景进入哪个 Controller典型路径老人登录平台YonghuControllerPOST /yonghu/login页面加载健康类型下拉DictionaryControllerGET /dictionary/page查询回显当前登录人CommonControllerGET /common/userinfo登记一批轮椅物资WuziControllerPOST /wuzi/add2.2 CommonController公共接口层常见的两种设计陷阱打开CommonController.java后看到的是一组“与具体业务无关”的方法。最容易踩的坑有两个一是把多张表的通用查询全部往这个类里塞最终变成几百行的“上帝类”二是公共接口的返回结构不统一前端拿到null时还要单独判断。合理做法是让CommonController只承担横切能力可以参考下面这种结构RestController RequestMapping(/common) public class CommonController { Resource private YonghuService yonghuService; Resource private DictionaryService dictionaryService; /** 前端在路由跳转后调用拿到当前登录用户信息 */ GetMapping(/userinfo) public ResultYonghu userinfo(HttpServletRequest request) { Long userId (Long) request.getSession().getAttribute(userId); if (userId null) { return Result.error(401, 登录状态已失效); } return Result.ok(yonghuService.getById(userId)); } /** 通用字典查询/common/dict/{type} */ GetMapping(/dict/{type}) public ResultListDictionary dict(PathVariable String type) { return Result.ok(dictionaryService.list( new LambdaQueryWrapperDictionary() .eq(Dictionary::getDictType, type) .orderByAsc(Dictionary::getSort))); } }逻辑说明userinfo方法从 session 中取当前登录用户的 ID再回表查出完整资料因为前端每次刷新页面都不会保留 Vuex 里的用户信息重新拉一次是必要的。dict方法把字典表中某个dictType下的所有选项按sort排序返回。两个方法都只做查询不涉及具体业务的增删改所以放在公共 Controller 中是合理的。要特别留意Result包装类。这套源码里的所有接口统一返回code/msg/data三段式结构前端拦截器只要判断code200就能进入正常流程否则弹出msg。后续扩展权限校验或者埋点都要基于这个统一返回结构做千万不要单独为某个接口返回裸 JSON 对象。2.3 YonghuController用户角色设计是养老平台最核心的表结构养老监护平台里的“用户”不只代表账号还要区分老人、家属、管理员三种角色。在YonghuController的实现里通常用一个role字段配合数据字典区分登录接口的写法大致如下RestController RequestMapping(/yonghu) public class YonghuController { Resource private YonghuService yonghuService; PostMapping(/login) public ResultYonghu login(RequestBody Yonghu user, HttpSession session) { Yonghu db yonghuService.login(user.getUsername(), user.getPassword()); if (db null) { return Result.error(用户名或密码错误); } session.setAttribute(userId, db.getId()); session.setAttribute(role, db.getRole()); return Result.ok(db); } /** 家属绑定老人一个家属可绑多个老人 */ PostMapping(/bind) public Result bind(RequestBody BindRequest req, HttpSession session) { // 校验当前登录人身份省略具体逻辑 return Result.ok(); } }逻辑说明login方法把用户标识放进了 session后续所有需要登录态的接口都能从 session 中取到当前操作人的输入。bind方法解决的是“家属如何看到老人健康数据”的常见需求老人与家属是多对多关系中间表必不可少。这里要提醒如果把平台做厚建议把role从简单字符串升级为role permission双字段因为管理员可能还要细分出护士、护工、站长等子角色不同子角色可操作的模块权限并不一致。数据字典在这里起到约束作用——role字段存的是1/2/3这样的数字展示时才由字典翻译成“老人/家属/管理员”中文文本。这样新增角色时不用改表结构只往字典表插入数据即可生效。2.4 DictionaryController为什么下拉选项不能在前端写死再来看DictionaryController.java。这个类表面上是标准 CRUD但它决定了整个平台的可配置性。假设“服务类型”下拉框有五个选项如果这些选项写死在 Vue 页面里那么调整一个选项名也要改前端、重新打包、重新发布。走字典表之后运营人员只需要在字典管理页面加一行记录前端下次加载接口时就能拿到新选项。对应建表 SQL 常见是这样的CREATE TABLE dictionary ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dict_type VARCHAR(64) NOT NULL COMMENT 字典类型如 service_type, item_key VARCHAR(32) NOT NULL COMMENT 选项值如 1, item_value VARCHAR(128) NOT NULL COMMENT 展示文本如 助餐服务, sort INT DEFAULT 0 COMMENT 排序权重, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT 数据字典表;这条 SQL 里每个字段都有实际用途dict_type是逻辑分组把“服务类型”“性别”“角色”等不同枚举隔开item_key是真正落到业务表里的值item_value是用户看到的文本sort控制下拉选项的顺序。后期做列表查询时用LEFT JOIN字典表就可以把状态值翻译成中文省去在 Java 里写 switch-case 翻译。提示字典字段建议用dict_type而不要用type避免个别 MySQL 版本对保留字的处理差异导致建表失败。3. Vue 前端挂载与适老化样式index.html、style.css 和 axios 封装的配合后端接口就绪后前端要把这些接口用起来。Vue 侧的入口文件是index.html虽然这个文件在源码里看起来内容很少但它是整个单页应用的起点。Vue CLI 构建后会把组件编译成 JavaScript 文件再在index.html的挂载点进行渲染。zip 里同时存在style.css和index.html前者管全局外观后者管入口结构二者缺一不可。3.1 index.html 为什么只是个“空壳”在 Vue CLI 3/4 项目里源码的public/index.html经过打包后会生成包含资源引用信息的dist/index.html。它的核心内容只有三部分!DOCTYPE html html langzh-CN head meta charsetutf-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / title社区智慧养老监护管理平台/title link relstylesheet href/css/style.css / /head body div idapp/div script src/js/chunk-vendors.js/script script src/js/app.js/script /body /html这个结构说明了两件事。第一#app是 Vue 实例的唯一挂载点组件树全部从这里长出来。第二chunk-vendors.js和app.js分别对应第三方依赖与业务代码拆分之后浏览器可以优先缓存不常变动的依赖包。如果项目使用 Vue Router 的 history 模式打包后的index.html还需要配合后端一段“找不到路径时回退到 index.html”的转发规则否则直接刷新子路由页面会 404。3.2 style.css 里的适老化视觉规范老年人的视觉特点决定了平台不能照搬普通后台管理系统的设计参数。对比常规后台与适老化设计时我一般关注这几个数值设计参数普通后台系统适老化调整原因基础字号14px16-18px老花眼普遍字号太小直接劝退正文行高1.51.8行距过密时老人阅读容易串行主文字对比度4.5:17:1以上对比度低时按钮文字看不清操作按钮高度32px44px以上点击命中率低误操作概率高弹窗提示时长2s4s左右老人反应慢提示要留足阅读时间这个对比说明style.css在这里承担的是可用性职责。源码里可能看到.el-button--primary被覆写成更大内边距正是为了匹配老人的操作习惯。需要注意不要全局覆盖 Element UI 的所有组件样式只覆盖高频组件即可否则升级 UI 库时会出现大面积样式冲突。3.3 axios 请求封装前端接口层的第一道关卡前端与后端交互的请求层通常放在utils/request.js里。它将 axios 实例化后设置baseURL并在拦截器里统一处理错误码这样各个页面就不需要重复写 try/catch 逻辑。import axios from axios const request axios.create({ baseURL: /api, timeout: 8000 }) request.interceptors.response.use( (response) { const res response.data // 后端统一返回 { code, msg, data }非 200 直接报错 if (res.code ! 200) { alert(res.msg || 请求失败请稍后重试) return Promise.reject(new Error(res.msg)) } return res.data }, (error) { // 401 说明登录状态失效跳回登录页 if (error.response error.response.status 401) { window.location.hash #/login } return Promise.reject(error) } ) export default request逻辑说明baseURL配置为/api后开发环境经由前端代理转发到 Spring Boot 的 8080 端口生产环境则由 Nginx 反向代理完成相同动作。response拦截器中统一判断code让业务代码只拿到干净的data页面代码会简洁很多。有一点容易被忽略超时时间固定为 8 秒如果老人网络环境不好或者接口接入了耗时较长的报表导出操作需要单独用request.get(url, { timeout: 15000 })覆盖默认值。4. 部署三连1-install.bat、2-run.bat、3-build.bat 完整执行手册把源码跑起来是很多人拿到 zip 后的第一诉求而这个项目的启动方式很直观——zip 里提供了三个 Windows 批处理脚本。按文件名从 1 到 3 执行依次是安装依赖、启动后端、构建前端。先弄清楚每个脚本做了什么再动手否则报错时不知道问题出在哪一层。4.1 三个脚本的分工与执行顺序脚本名阶段核心动作执行频率1-install.bat安装检查 Java/Maven/Node 环境并装依赖首次拉取代码时2-run.bat运行启动 Spring Boot内含 Tomcat每次调试后端3-build.bat构建打包 Vue 前端并拷贝到后端 static 目录准备上线前这里有一个常见误用有人把3-build.bat当作“一键启动”双击执行结果发现后端没起来。因为前端打包只是生成静态文件后端进程同样要启动才能通过 8080 端口对外提供完整服务。4.2 1-install.bat检查 JDK、Maven 与 npm批处理脚本的开头通常是验证环境变量再决定是否继续执行echo off chcp 65001 nul echo [1/3] 检测 Java 环境... java -version nul 21 if errorlevel 1 ( echo 未检测到 JDK请先安装 JDK 1.8 或 11 并配置 JAVA_HOME goto :end ) echo [2/3] 安装后端依赖... call mvn clean install -DskipTests if errorlevel 1 goto :end echo [3/3] 安装前端依赖... pushd frontend call npm install if errorlevel 1 goto :end popd echo 依赖安装完成 :end pause逻辑说明java -version nul 21把版本信息丢弃只检查命令是否存在返回码非 0 说明 JDK 没配好。mvn clean install -DskipTests跳过测试并编译安装所有后端模块。前端用npm install安装package.json里声明的依赖首次执行要下载大量包耗时从几十秒到几分钟不等。pushd/popd用于临时切换目录避免影响当前命令行上下文。4.3 2-run.bat把后端跑起来echo off chcp 65001 nul echo 正在启动社区智慧养老监护管理平台后端服务... rem 使用 Maven 插件直接启动服务端口默认 8080 call mvn spring-boot:run执行mvn spring-boot:run后Spring Boot 会先加载数据库连接配置、初始化连接池再启动内置 Tomcat。看到 “Started Application in x.xxx seconds” 日志说明服务起来了此时访问http://localhost:8080如果能加载出页面说明前后端已经联通。4.4 3-build.bat前端打包并回写 static 目录生产环境通常不会让前端开发服务器参与运行而是把 Vue 构建出的dist目录交给 Spring Boot 托管。脚本内容常见如下echo off chcp 65001 nul cd /d %~dp0 echo 正在构建前端... pushd frontend call npm run build if errorlevel 1 goto :end xcopy /E /Y /I dist ..\src\main\resources\static popd echo 前端已构建并拷贝至 static 目录 :end pause逻辑说明npm run build等价于执行vue-cli-service build把源码压缩转译生成dist目录。之后用xcopy /E /Y /I把整个dist递归复制到 Spring Boot 的资源目录。再次通过2-run.bat启动时直接访问 8080 端口即可同时获得静态页面和接口服务。注意SQL 依赖脚本需要手动导入数据库否则启动阶段连接池初始化就会报错这是最常被忽略的一步。4.5 启动失败的常见原因对照表现象可能原因处理方式java不是内部或外部命令JDK 未安装或 PATH 未配置安装 JDK 并配置 JAVA_HOME 和 PATH端口 8080 被占用上次启动的实例未关闭用netstat -ano | findstr 8080找到 PID 再关闭npm install长时间卡住默认源在国外下载慢执行npm config set registry https://registry.npmmirror.com数据库连不上配置没有改成自己本机 MySQL修改application.yml的 url/username/password启动后页面空白前端 dist 没拷到 static 目录重新执行3-build.bat排错时要先判断报错发生在哪一层再检查环境变量、端口、数据库连接这三个高发点。从异常堆栈里搜索Access denied或Communications link failure基本能直接锁定数据库问题。5. 养老物资业务闭环WuziController 里库存扣减与用户校验的实现前面讨论了用户、字典、前端和部署现在回到业务域把WuziController.java单独拿出来看。物资管理是社区养老服务里容易被低估的模块——轮椅、助行器、护理用品这些物资如果只登记不使用管理就变成一堆库存数字如果只使用不登记月底盘点一定对不上账。所以这个 Controller 背后至少藏着两张关键表物资表和物资领用记录表。5.1 物资表结构的设计起点一个最小可用的物资表通常包含这些字段CREATE TABLE wuzi ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 物资名称, wuzi_type VARCHAR(32) NOT NULL COMMENT 物资类型字典wuzi_type, stock INT NOT NULL DEFAULT 0 COMMENT 当前库存, unit VARCHAR(16) COMMENT 单位如 台/箱/盒, status VARCHAR(8) DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE wuzi_lingyong ( id BIGINT PRIMARY KEY AUTO_INCREMENT, yonghu_id BIGINT NOT NULL COMMENT 领取人也支持家属代领, wuzi_id BIGINT NOT NULL COMMENT 物资ID, num INT NOT NULL COMMENT 领取数量, status VARCHAR(8) DEFAULT 1 COMMENT 状态字典lingyong_status, remark VARCHAR(255) COMMENT 备注比如代领原因, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );表结构里没有物理外键这是刻意为之。业务开发中通常只建索引不加外键因为外键在插入和更新时带来的锁竞争对这种并发不高的管理平台没有多大收益反而让后续分库分表变得麻烦。查询时通过LEFT JOIN关联别表数据一致性由 Service 层通过事务保证。5.2 领用接口校验用户、检查库存、扣减数量的三段式事务物资领用是最典型的“写多表”操作实现时要分步骤判断PostMapping(/lingyong) Transactional(rollbackFor Exception.class) public ResultString lingyong(RequestBody WuziLingyong body) { // 1. 校验老人存在且角色正确 Yonghu yonghu yonghuService.getById(body.getYonghuId()); if (yonghu null || !1.equals(yonghu.getRole())) { return Result.error(领取人不存在或不是老人账号); } // 2. 校验库存足够 Wuzi wuzi wuziService.getById(body.getWuziId()); if (wuzi null || wuzi.getStock() body.getNum()) { return Result.error(库存不足当前剩余 (wuzi null ? 0 : wuzi.getStock())); } // 3. 写入领用单并扣减库存 lingyongService.save(body); wuzi.setStock(wuzi.getStock() - body.getNum()); wuziService.updateById(wuzi); return Result.ok(登记成功); }逻辑说明第一步先确认领取人是老人角色而非管理员实际项目中如果允许家属代领还要再查一次家属与老人的绑定关系。第二步做库存预判避免出现负数库存如果想把并发安全性再提高一档可以改成UPDATE wuzi SET stock stock - #{num} WHERE id #{id} AND stock #{num}用受影响行数判断成败。第三步用Transactional(rollbackFor Exception.class)保证“写领用单”和“扣库存”要么同时成功要么同时回滚防止出现领用记录生成了但库存没减的脏数据。提示事务注解要写在 public 方法上并且不要在同 Class 内部直接调用否则 Spring 代理失效会导致事务不生效。5.3 列表查询字典翻译与用户信息联查领用记录表里存的是yonghu_id、wuzi_id、status这些编号前端列表页直接展示数字没法看后端返回前需要做翻译用一条 SQL 完成联查和字典翻译SELECT l.id, u.name AS yonghu_name, w.name AS wuzi_name, d.item_value AS status_text, l.num, l.create_time FROM wuzi_lingyong l LEFT JOIN yonghu u ON l.yonghu_id u.id LEFT JOIN wuzi w ON l.wuzi_id w.id LEFT JOIN dictionary d ON d.dict_type lingyong_status AND d.item_key l.status ORDER BY l.create_time DESC LIMIT 0, 10这个查询里最容易踩的坑是对LEFT JOIN的误用。字典表的连接条件除了匹配dict_type还必须匹配item_key两个条件缺一个就会出现行数放大或者翻译错乱。要注意字典条件要放在 ON 子句而不是 WHERE 子句一旦放到 WHERE左连接会退化成内连接查不到对应字典的状态码会被直接过滤掉列表数据便不完整。6. 健康监测主动预警接入定时任务、WebSocket 推送与 Vue 侧展示源码已经具备基础的档案和物资管理能力但真正让平台有“监护”感觉的是健康数据异常时能主动冒出的预警提示。这个进阶功能可以在现有架构上直接扩展实现路径分为三段模拟数据采集、阈值判断推送、前端实时展示。6.1 用 Scheduled 模拟心率采集Spring Boot 内置定时任务能力启动类上加EnableScheduling后方法上加Scheduled(fixedDelay 30000)即可每隔 30 秒执行一次Component public class HealthCollectTask { Scheduled(fixedDelay 30000) public void collectHeartRate() { // 模拟从硬件网关读取老人手环心率值 int heartRate 60 new Random().nextInt(40); if (heartRate 95) { warningService.push(心率过快, heartRate); } } }逻辑说明fixedDelay是任务结束到下次开始的时间间隔适合这种周期性轮询的采集场景。真实硬件接入时把模拟值替换成 MQTT 或 HTTP 上报即可。6.2 用 WebSocket 推送异常消息页面需要实时弹出预警用轮询不够即时。Spring Boot 集成 WebSocket 后通过SimpMessagingTemplate做服务端推送MessageMapping(/warning) public void handleWarning(String yonghuId) { template.convertAndSendToUser(yonghuId, /queue/warning, 健康数据异常请尽快核查); }逻辑说明convertAndSendToUser会把消息推送给指定用户前端在 Vue 组件中订阅/user/queue/warning即可接收。这样家属端和管理端能同时收到同一老人手环的异常状态管理员点开即可定位到对应老人的档案记录。6.3 预警记录持久化与列表置顶预警表最小结构是id, yonghu_id, metric_type, warning_value, status, create_time。查询时按create_time DESC倒序展示再通过字典翻译metric_type为“心率/血压/血氧”等中文标签。这样即使 WebSocket 消息因网络原因迟到管理员依然能在预警列表中看到已经落库的历史记录不会造成告警丢失。本文还有配套的精品资源点击获取