☰
社区智慧养老监护管理平台:SpringBoot+Vue前后端分离实战解析
2026/9/28 5:30:35 网站建设 项目流程

开头就用从业者的口吻,直接讲这个项目的价值和背景。确保前100字内自然融入核心关键词。

1. 项目背景与整体设计思路

1.1 为什么做“社区智慧养老监护管理平台”

这两年社区养老、居家养老的需求增长非常明显,很多街道和社区都在想办法解决独居老人、高龄老人的日常监护问题。单纯靠人工上门巡查,效率低、覆盖不全,老人突发情况也没法第一时间发现。我手上这个项目,就是围绕这个真实场景做的——一套前后端分离的社区智慧养老监护管理平台,后台用SpringBoot+MyBatis+MySQL,前端用Vue,从基础信息管理到健康数据上报、工单流转,覆盖了社区养老管理里最核心的几条业务线。

这个项目不是那种“为了做而做”的毕业设计Demo,是真正对照社区实际工作流程梳理出来的。社区工作人员需要管理老人档案、记录每日健康状态、跟进上门服务工单,家属需要查看老人的动态,管理人员需要看到统计报表。这些需求堆在一起,技术上就是一个典型的前后端分离管理系统。之所以选SpringBoot+Vue+MyBatis+MySQL这套组合,没有别的原因——它足够主流、招人熟悉、资料多、部署简单,社区级别项目的并发量和数据量,这套组合完全撑得住,而且后续想加功能、换人维护都方便。

1.2 市面上同类系统的问题与技术选型考量

先说市面上一类常见的“养老管理系统”——单页面的JSP+Servlet老项目,或者直接用PHP改的简易后台。这类系统的通病很明显:页面和业务逻辑耦合在一起,改个前端样式要动后端代码,加一个接口要重新部署整个应用,维护成本相当高。而前后端分离的好处就在于,后端只负责提供JSON接口,前端Vue项目独立开发、独立部署,两边通过HTTP通信,谁都不会被对方拖累。

选SpringBoot而不选SSH(Struts+Spring+Hibernate)或者SSM手写配置,是因为SpringBoot把绝大多数的配置都自动化了。原来SSM要写一堆XML配置、配数据源、配事务管理器,SpringBoot里一个application.yml就搞定大半。这对社区级别的快速交付非常重要,毕竟这类项目往往时间紧、需求变化快。Vue选2.x而不是3.x,也是基于生态稳定性的考虑——Element UI对Vue 2的支持最成熟,网上的案例和踩坑记录最多,做管理后台效率最高。MyBatis则是为了SQL可控性,养老平台涉及复杂的多表关联查询和报表统计,手写SQL比JPA自动生成的查询更直观、更好调优。

MySQL就不多说了,社区项目的数据量一般到不了单表百万级的程度,MySQL 5.7或8.0足够,而且部署、备份、迁移都简单,运维压力小。

2. 系统核心模块与数据库设计

2.1 功能模块梳理:从老人档案到工单闭环

在动手写代码之前,我习惯先把业务流程画一遍。这个平台的核心用户有三类:社区管理员、护工/网格员、老人家属。围绕这三类角色,我把系统拆成了六个核心模块:

老人档案管理是最基础的一层,包括老人的基本信息、身份证号、紧急联系人、既往病史、用药情况、家属联系方式。这里特别要注意的是,老人档案的字段要比普通管理系统多不少,比如“是否独居”“是否失能”“紧急联系人电话”这些字段必须单独建,不能混在备注里,否则后面做筛选统计的时候会非常痛苦。

健康监护模块负责每日健康数据的录入和展示。护工上门或老人自助上报体温、血压、血糖、心率这些指标,系统自动生成趋势图。这个模块的数据量最大,也是后面做报表统计的主要数据来源。

服务工单模块是连接护工和老人的桥梁。家属或管理员发起服务需求(比如上门送餐、陪诊、家政),系统生成工单,护工接单、执行、回传结果,管理员可以全程跟踪工单状态。这个模块涉及到状态的多次流转,是后端接口设计里最需要花心思的地方。

告警管理模块是我觉得最有价值的模块。当健康数据超过预设阈值,或者老人超过设定时间没有活动记录,系统自动生成告警,推送给管理员和家属。这块要做好,关键在于阈值可配置,不能写死在代码里。

统计报表模块面向管理层,按街道、社区、时间段统计老人数量、服务次数、告警次数、健康数据达标率。这些报表数据全部来自前面几个模块的累积,所以数据库设计的时候就必须考虑统计维度。

系统管理模块包括用户管理、角色权限、操作日志。我用的是简单的RBAC模型,管理员、护工、家属三种角色,菜单权限和按钮权限分开控制。

2.2 数据库表设计要点与ER关系

数据库设计我踩过不少坑,这里把关键经验直接列出来。核心表一共12张,我挑几张重点说一下。

老人档案表(elder_info),主键用自增id,老人编号单独建一个字段(比如elder_no,规则是ED+地区码+序列号),这样方便对接外部系统。身份证号、手机号这些敏感字段在数据库中明文存储问题不大,但接口返回时必须做脱敏处理,不能把完整身份证号直接丢给前端。

健康数据表(health_record),这是数据量最大的表。字段包括老人ID、体温、收缩压、舒张压、心率、血氧、血糖、记录时间、记录人。设计时一定要把记录的record_time建成索引,因为后续的告警查询和时间范围统计全部依赖这个字段。我实测过,不加索引的情况下,十万条数据按时间范围查询就要一两秒,加上索引之后毫秒级返回。

工单表(service_order),状态字段用tinyint存数字状态码,0待接单、1进行中、2已完成、3已取消、4已评价。为什么不用字符串?因为数字状态码在前后端都比较好映射,枚举类一写,前端只需要一个字典表就能渲染对应的标签。工单创建时间、接单时间、完成时间三个时间字段都要有,后面统计平均响应时长、平均完成时长就靠它们。

告警记录表(alert_record),包含老人ID、告警类型(1健康异常、2长时间无活动、3设备离线)、告警等级、内容描述、处理状态、处理人、处理时间。告警表的处理状态默认是0未处理,管理员处理之后置为1,这个状态必须和工单模块解耦——就是说告警可以手工关闭,不强制生成工单,避免流程僵化。

下面用表格把这12张表的情况列一下,方便照抄:

表名核心字段用途说明
sys_user用户名、密码(BCrypt)、角色ID、状态登录用户表,管理员/护工/家属共用
sys_role角色编码、角色名称三种角色:admin、worker、family
elder_info姓名、身份证号、住址、紧急联系人、病史老人基础档案
family_relation老人ID、家属ID、关系老人和家属的多对多关联
health_record老人ID、各项健康指标、记录时间每日健康数据
service_order工单号、老人ID、类型、状态、时间戳服务工单流转
service_item服务项目名称、价格、时长服务目录维护
alert_record老人ID、类型、等级、处理状态告警记录
device_info设备编号、类型、绑定老人ID智能设备绑定(预留)
device_data设备ID、数据类型、数值、时间设备上报数据(预留)
statistics_daily日期、老人总数、服务次数、告警次数每日统计汇总
sys_log操作人、操作类型、IP、时间、详情操作日志

老人表和家属表我特意做了关联表family_relation而不是直接在老人表里加“家属ID”字段,原因是一个老人可能绑定多个家属(儿子、女儿、老伴),一对多放主表里会冗余,而且后期换绑麻烦。

2.3 扩展模块预留:设备对接的思考

项目里我预留了device_info和device_data两张表,当时是想对接智能手环、跌倒报警器这类硬件设备。虽然第一版没有真正对接硬件,但表结构提前留好了,等真有设备接入的时候不用改表。字段设计上,设备数据表只存最原始的上报数据,不做任何业务判断,业务判断全部放在后端逻辑里——这么做的好处是,如果后续换设备厂商,数据格式有差异,只需要改设备解析层,业务逻辑完全不用动。

3. 后端核心实现与关键代码解析

3.1 SpringBoot项目结构与统一响应封装

后端工程我按常见的分层结构组织:controller、service、mapper、entity、dto、config、common。common里放统一返回结果、异常处理、工具类。这里有一个每个项目都必须做的东西——统一响应体。

我接口的返回结构固定是:

{ "code": 200, "message": "success", "data": {} }

对应Java代码就是一个泛型类:

public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("success"); result.setData(data); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }

这样做的好处是前端axios拦截器只需要判断code是不是200,就能统一处理所有接口的返回。如果哪个接口忘了包Result,直接返回裸数据,前端处理逻辑就会变得七零八落。我后来把所有Controller的返回值都改成Result<T>,再配合一个全局异常处理器把业务异常转成标准格式,接口层一下子干净了。

全局异常处理用@RestControllerAdvice,这个不复杂,但对体验提升非常明显。比如参数校验失败、业务逻辑里主动抛出的BusinessException,统统在全局异常处理器里转成统一格式返回。

3.2 MyBatis配置与分页插件接入

MyBatis在这个项目里我用的是XML Mapper方式。第一版想偷懒用MyBatis-Plus,但考虑到这个项目要给初学者参考,用纯MyBatis更能看到SQL本身,所以还是选了XML。配置方面有几个细节容易踩坑:

驼峰映射必须开。在application.yml里加:

mybatis: configuration: map-underscore-to-camel-case: true

否则数据库字段create_time映射到Java属性createTime会全部变成null,排查起来非常隐蔽。

分页插件用的是MyBatis自带的PageHelper。接入分页非常简单,引入依赖后,在application.yml配一个拦截器,然后查询前写PageHelper.startPage(pageNum, pageSize),紧接着的第一次查询就会被自动加上LIMIT。这里有个常见的坑:PageHelper.startPage()必须紧跟查询语句,中间不能插入其他SQL操作,否则分页会不生效或者作用到错误的查询上。

分页返回的数据结构我用一个PageResult类封装,包含total(总记录数)、list(当前页数据)、pageNum、pageSize。前端分页组件只需要这四个字段就能完整渲染。

3.3 健康数据趋势查询与告警阈值判断

健康数据模块的接口有两个重点:一是按时间范围查某个老人的全部健康指标,二是判断数据是否触发告警。

按时间范围查询,SQL主要是:

SELECT * FROM health_record WHERE elder_id = #{elderId} AND record_time BETWEEN #{startTime} AND #{endTime} ORDER BY record_time DESC

这个查询在数据量上来以后必须走record_time索引,否则会越查越慢。我在测试环境特意灌了20万条测试数据验证,加了索引后查询时间稳定在100毫秒以内。

告警阈值的判断我写在了HealthAlertService里,规则是:收缩压超过160或者低于90,舒张压超过100或者低于60,心率超过120或者低于50,体温超过37.3,就触发对应类型的告警。阈值没有写死在代码里,而是存在数据库配置表里,管理员可以在系统管理模块调整。后续要调整标准,不用重新发版。

3.4 JWT登录与权限拦截的实现细节

登录模块用的是JWT(JSON Web Token)。用户登录成功后,后端签发一个Token,有效期我设的是24小时,前端把Token存在localStorage里面,每次请求在Authorization头带上。后端用一个拦截器统一校验Token、解析用户ID和角色,然后把用户信息放到ThreadLocal里面,方便在Service层获取当前登录人。

这里有几个细节需要注意:

Token过期处理。JWT本身过期后前端拿到的接口会返回401,我前端axios拦截器里做统一处理:收到401就跳转登录页并清空本地存储。但这样体验有点生硬,用户可能正在填表单,突然就被踢出去了。后来我用了双Token方案——一个短期Token(2小时)+ 一个刷新Token(7天),短期Token过期后用刷新Token换取新的短期Token,用户无感知续期。这个方案在社区项目里够用,代码也不复杂。

权限控制。接口级别的权限我用的@RequiresRole("admin")这种自定义注解加拦截器实现,比引入Spring Security全家桶轻量很多。社区项目角色就三种,权限层级不深,轻量方案维护成本低。

3.5 文件上传与XSS过滤

项目里有一个需求是上传老人的体检报告PDF。文件上传本身用MultipartFile接收,存到服务器本地目录,数据库只存文件路径。这里有两个敏感点:

一是文件类型校验。MultipartFile的getContentType()是可以通过改扩展名骗过的,所以除了检查扩展名,还要检查文件头Magic Number。PDF的文件头是%PDF,图片的JPEG头是FF D8 FF。我在工具类里专门写了一个读取文件头校验的方法,安全第一。

二是XSS过滤。老人档案的备注、地址这些字段是用户输入的,如果不过滤,可能被插入恶意脚本。我写了一个全局的XSS过滤器,对请求参数做HTML标签转义处理,把<script>这类内容变成安全的转义字符。这个过滤器在前后端分离项目里特别容易被忽略,因为很多人的精力都在接口调试上,但安全问题不能欠账。

4. 前端Vue实现与前后端联调实战

4.1 Vue项目搭建与Element UI引入

前端我用Vue CLI创建项目,注意Node版本必须和Vue CLI匹配。我踩过一次坑:Node 17以上版本跑Vue 2项目会报opensslErrorStack的错误,原因是Webpack 4用的OpenSSL算法在新版Node里被废弃了。解决方案是在package.json里加一行:

"scripts": { "serve": "set NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve" }

Windows下用set,Mac/Linux下用export NODE_OPTIONS=--openssl-legacy-provider。这个问题几乎每个用新Node跑老Vue项目的人都会遇到,写在这里给后来人省点时间。

UI组件库我选Element UI,按需引入。管理后台的页面套路很固定:左侧菜单栏+顶部导航栏+主内容区,用el-container一搭就出来了,省时省力。路由用Vue Router,模式用history还是hash要看部署方式——本地开发用history好看,但部署到Nginx必须做try_files配置,否则刷新页面会404。为了少踩坑,我第一版用的hash模式,URL里带个#,不太好看但省心。上线后改成history,Nginx那边加上:

location / { try_files $uri $uri/ /index.html; }

4.2 axios封装与请求拦截器

前端所有请求都走统一封装的axios实例。我在utils/request.js里做了三件事:设置baseURL、请求拦截器加Token、响应拦截器统一处理错误。

import axios from 'axios' const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 15000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { this.$message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response && error.response.status === 401) { this.$router.push('/login') } return Promise.reject(error) } )

开发环境的跨域问题,用Vue CLI的devServer.proxy解决,代理配置如下:

devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } }

后端接口统一以/api开头,前端请求/api/elder/list,代理转发到后端http://localhost:8080/elder/list。这样前端代码不需要关心真实的后端地址,换环境只改环境变量就行。我实际项目中production环境的地址配置在.env.production文件里,部署时替换成服务器IP或域名即可。

4.3 核心页面实现:老人档案表格与健康趋势图

老人档案管理页面是典型的CRUD页面。列表用el-table渲染,点击“详情”弹窗展示完整档案信息。新增和编辑共用一个弹窗表单,通过dialog的title字段区分是在新增还是编辑。这里有个细节:表单校验规则要和服务端一致,不然前端校验通过、后端又把请求打回来,体验很割裂。

健康趋势图我用了echarts,一个折线图展示某位老人最近30天的血压变化。ECharts的用法很简单,难点在于后端返回的数据格式要和图表组件匹配。我让后端返回一个对象数组,每个元素包含date、highPressure、lowPressure三个字段,前端直接映射到ECharts的series里。图表的tooltip格式化函数里我加了单位,血压显示“mmHg”,体温显示“°C”,这些细节虽然小,但是真正给老人家属看的时候很重要,数据可读性直接影响信任度。

4.4 工单流转与状态管理

工单模块前端最复杂的地方在于状态流转。不同状态下,按钮是不同的:待接单状态,护工看到的是“接单”按钮;进行中状态,看到的是“完成”按钮;管理员看到的是“取消”按钮。同一个按钮在不同状态下要么隐藏要么禁用,这个逻辑我用Vue的计算属性来处理,而不是在模板里写一堆v-if。

工单列表我做了筛选条件:按状态筛选、按服务类型筛选、按时间范围筛选。这里的筛选全部走后端接口参数,前端只负责把参数拼到请求里。筛选条件多了以后,要把查询参数集中放在一个对象里管理,不能分散在各个data属性里,否则重置筛选条件时会漏掉参数。

5. 部署教程与常见问题排查

5.1 本地环境搭建:从JDK到MySQL

先讲本地开发环境的准备。JDK必须用1.8,SpringBoot 2.x对JDK 8的支持最稳定,用JDK 11也没有问题,但JDK 17会有一堆兼容性麻烦(SpringBoot 2.x对一些库的反射调用在JDK 17下面会被模块化限制拦住)。Maven用3.6以上,Node用14或16,MySQL用5.7或8.0都可以。

MySQL安装的时候,有几个关键的配置项容易漏:

字符集必须设置成utf8mb4,不是utf8。utf8在MySQL里最多存3个字节,一些生僻字和emoji会存不进去,报错信息还不直观。

时区要设置成Asia/Shanghai。如果数据库时区不对,后端Java连接时候区不一致,查询出来的时间会比实际慢8小时或者快8小时,这种问题排查起来非常痛苦。

数据库建库建表我用了一个init.sql脚本,里面包含建库语句、建表语句和初始数据。初始数据很重要——没有管理员账号,系统跑起来也登不进去。我初始化的管理员账号是admin/admin123,密码是用BCrypt加密后的字符串存进去的,在这个地方要特别提醒:如果你在前端登录的时候报密码错误,先检查一下数据库里存的密码是不是BCrypt格式,这个项目里用明文存密码的做法不要学。

5.2 前后端打包与Nginx部署全流程

部署我走的经典路线:后端打成Jar包运行,前端构建成静态文件放到Nginx里托管,Nginx反向代理后端接口。

后端打包是Maven的package命令,生成target/xxx.jar。运行:

java -jar community-care-server.jar --server.port=8080 --spring.profiles.active=prod

生产环境的配置文件我还单独建了一个application-prod.yml,数据源地址、Redis地址(如果用了)、日志级别都和生产环境匹配,通过--spring.profiles.active=prod指定。

前端构建:

npm run build

生成dist目录,把整个dist目录拷贝到服务器的/usr/share/nginx/html下,然后修改Nginx配置。除了前面说的try_files,还要配置接口反向代理:

location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

注意proxy_pass后面的URL末尾有斜杠http://127.0.0.1:8080/,这代表把/api/前缀去掉再转发。如果写成http://127.0.0.1:8080(末尾没斜杠),就会保留/api前缀,后端Controller的/api路径映射对不上就404了。这个问题比很多人想象中更常见,排查的时候先看Nginx的error.log,基本上一眼就能定位。

5.3 常见问题速查表

开发调试过程中我遇到的问题不少,整理成表格方便排查:

问题现象可能原因解决方案
前端请求接口报404Nginxproxy_pass末尾斜杠问题检查代理配置,确认是否要保留/api前缀
登录接口报401Token过期或未携带检查前端拦截器是否在请求头带上Token
数据库查询中文乱码MySQL字符集没设成utf8mb4修改数据库/表字符集,重建连接
分页数据不对PageHelper.startPage()位置不对确保紧跟第一条SQL查询语句
上传文件大小超限SpringBoot默认限制1MB配置spring.servlet.multipart.max-file-size和max-request-size
前端刷新页面404Nginx没配置try_files加上try_files $uri $uri/ /index.html;
端口被占用8080被其他程序占用换端口或杀掉占用进程
时间差8小时数据库时区和JVM时区不一致数据库设置Asia/Shanghai,JVM加-Duser.timezone=Asia/Shanghai

5.4 部署后的日常维护心得

系统部署上线以后,日常维护有几件事是必做的。第一是MySQL的定时备份,我用crontab每天凌晨3点执行一次mysqldump,备份文件保留最近7天,另外每周末做一次全量备份。社区项目数据量虽然不大,但老人档案和健康数据一旦丢失,后果很严重,备份这件事真的不能省。

第二是日志监控。SpringBoot默认的日志输出到控制台,重启就丢了。我配置了logback-spring.xml,按天生成日志文件,保留30天,同时把错误日志单独输出到一个文件,排查问题的时候直接看错误日志,效率高很多。

第三是Nginx的访问日志。如果家属反馈“系统进不去”,先看Nginx的访问日志,判断请求有没有到Nginx,直接就能区分是前端、后端还是网络的问题,排查路径清晰很多。

6. 源码结构说明与二次开发建议

6.1 后端源码模块导航

拿到源码之后,建议按这个顺序去读:先看pom.xml了解依赖,再看application.yml了解配置,然后从Controller层往下走——先看接口定义,再看Service实现,最后看Mapper XML里的SQL。新手最容易犯的错误是拿到代码就先翻entity实体类,翻完就懵了,因为你不知道这些实体类在哪里被使用。

几个关键的包路径说清楚:

  • com.care.controller:所有REST接口入口,命名规则是XxxController
  • com.care.service:业务逻辑层,接口定义在XxxService,实现在impl子包
  • com.care.mapper:MyBatis的Mapper接口
  • resources/mapper:Mapper XML文件,与Mapper接口一一对应
  • com.care.common:通用类,包括Result、异常处理、工具类
  • com.care.config:配置类,包括CORS配置、拦截器注册、文件上传配置

改代码的时候要记住一条铁律:Controller只做参数接收和结果返回,不做业务逻辑;业务逻辑全部放Service层;SQL全部写在Mapper XML里。前一个项目的教训就是Controller里写了一堆判断逻辑,后面想复用根本没法抽出来,只能重写。

6.2 前后端分离技术栈的扩展方向

这个项目做完以后,往上扩展的方向其实很多。最实用的一个方向是接入MinIO做对象存储,把体检报告PDF、老人照片等文件从服务器本地磁盘迁移到MinIO,好处是文件不占用应用服务器的磁盘空间,而且MinIO的Bucket权限可以做到细粒度的控制,比本地目录安全。我之前做技术调研的时候已经验证过SpringBoot集成MinIO的完整流程,整体不复杂,核心就是引入依赖、配置Endpoint和密钥、然后调用putObject和getObject接口。

另一个方向是引入定时任务框架。现在的告警检查逻辑是实时判断的,也就是数据录入的时候立刻判断。但“长时间无活动”这个告警类型需要定期扫描,比如每30分钟检查一次:如果某位老人的最后活动时间距当前超过24小时,就生成告警。这个用Spring的@Scheduled注解就能实现,不需要引入Quartz,几百行代码就能搞定。

6.3 给新手的实践建议

如果你是用这个项目练手或者写毕设,我建议不要只是把代码跑起来就完事,试着做这几件事:第一,把数据库表结构画成ER图,理解每张表的关联关系,这是面试时候最常被问的东西;第二,自己动手改一个功能,比如给健康数据表加一个“体重”字段,从前端表单到后端接口到数据库全程走一遍,改完你就真正理解了这个项目;第三,把Nginx部署流程自己在虚拟机上完整走一遍,这个经验在真实工作环境里比写一万行CRUD都值钱。

最后说说我自己做这套系统的体会。社区智慧养老这个方向,技术本身不算多前沿,难的是把老人的真实需求转换成系统的业务流程。做这套系统的时候,我最大的收获不是SpringBoot或者Vue的技术熟练度,而是理解了怎么把一个看似零散的需求,通过数据库设计、接口设计、状态流转设计串成一条完整的线。这种从需求到落地的完整链路,是框架和语法之外更值钱的东西。当你把一个模块从无到有做出来、部署上线、被人实际使用的时候,那种满足感确实是写多少个Demo都给不了的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询