☰
养老社区管理系统实战:Spring Boot+Vue前后端分离全解析
2026/9/30 4:53:52 网站建设 项目流程

2021年7月,东北大学软件学院2020级的夏季实训正式开题,我们小组拿到的题目是"东软颐养社区系统"。说实话,刚看到这个题的时候,我们心里想的是:这不就是一个带界面的增删改查吗?老人信息、房间床位、护工排班,撑死了再加个收费。直到我们真正开始做需求梳理才发现,事情远没有想象中简单。一个面向养老社区的管理系统,要承接的不只是"一张表存人",而是一整套围绕老人健康、护理服务、园区运营的复杂业务流。

这篇博客是对我们这次实训项目的完整复盘,包括题目的需求拆解、技术选型、数据库设计、核心模块实现,以及我们在联调和部署阶段真实踩过的坑。如果你也是软件工程专业的在校生,正准备做类似的实训或课程设计;或者你在做养老、医疗类信息管理系统,想看看别人是怎么设计和落地的,那这篇内容应该能给你一些参考。

1. 实训题目拆解:一个养老社区系统背后到底管什么

1.1 这个系统不是简单的"老人花名册"

拿到题目第一件事不是写代码,是把题目真正看懂。"东软颐养社区系统"这几个字,拆开看每一个词都有信息量。"颐养"说明它不是医院,是养老场景;"社区"说明它不是单栋楼,而是园区级别的运营;"系统"说明它要覆盖多角色协作,而不是单机工具。

我们当时去查了东软在智慧养老方向的资料,大致明白了背景:这类颐养社区通常有独立房间、公共活动区、医疗护理站,老人住在这里,护工提供日常照护,管理人员负责整体运营。系统要做的就是把线下的这些流程搬到线上,让管理员能看清园区状态,让护工能高效执行任务,让家属能放心。

想清楚这些之后,我们把系统的核心模块圈定为几个必需项:老人档案管理、房间床位管理、员工账号与权限、健康记录管理、护理工单任务、公告活动、简单的数据统计。每个模块背后都有真实的业务诉求,而不是为了凑功能硬加进去的。

1.2 用角色视角画出功能矩阵

需求梳理阶段我们用了最朴素也最有效的方法:把系统中的每个角色列出来,再逐个问"这个角色每天打开系统要干什么"。

  • 系统管理员:维护员工账号、管理楼栋房间和床位、办理老人入住/退住/换房、给护工派发工单、发布园区公告、查看运营数据。
  • 护理人员(护工):查看自己被分配负责的老人列表、录入每日健康巡检数据(血压、心率、体温等)、接收并完成护理工单、标记用药提醒。
  • 家属(访客角色):查看老人基本情况和健康记录(在实训版本里我们做成了给家属开放的部分查询权限,但考虑到复杂度,最终弱化成公告和健康趋势展示)。

这三个角色之间的关系其实是一条服务链:管理员制定规则并分派任务,护工执行服务并记录结果,家属查看服务过程。系统所有功能设计都围绕这条链展开,谁操作什么、数据从哪来、流到哪去,一目了然。

1.3 实训周期内的范围取舍

四周的实训时间不可能把这些全做完,所以我们做了明确的范围切割。支付和财务模块只做成简单的费用记录,不做真实支付流程;排班功能只做人工排班,不做自动排班算法;移动端不做独立App,但前端页面用响应式布局保证能在手机上正常看。

这个取舍过程在实训里非常关键。很多组做到最后没交付,问题不在于技术难度,而在于一开始什么都想要。我们当时的思路是:与其十个模块每个都做到60分,不如五个核心模块做到100分,保证演示的时候每个功能都能跑通,经得起老师追问。

2. 技术栈选型:为什么前后端分离的方案更适合我们这支队伍

2.1 三个备选方案摆在桌上

组里5个人,技术基础参差不齐,有Java学得比较扎实的,也有上学期JavaWeb还在补考的。选型的时候我们列了三个方案放在一起对比:

方案优点缺点适合场景
Spring Boot + Thymeleaf 服务端渲染上手快,一个工程搞定,联调成本低前后端代码耦合,页面效果一般,不好做复杂交互单人开发或两人小团队
Spring Boot + Vue 2 + Element UI 前后端分离页面效果专业,前后端并行开发,分工清晰需要约定接口,联调有成本,前端要学框架4-6人团队,有两周以上开发时间
Python Flask/Django 全家桶开发效率高,代码量少组里没人熟悉Python,后期维护风险大全员Python基础好的队伍

2.2 为什么我们选了Spring Boot + Vue 2

三个方案里,服务端渲染其实最"保险",因为Java是大家的必修课,Thymeleaf模板语法也简单。但我们最后还是选了前后端分离,主要是从三个角度考虑的。

第一是分工效率。5个人如果全写Java后端的Thymeleaf模板,根本没法并行开发,一个人改了首页模板另一个人就会冲突。前后端分离之后,两个同学负责Vue前端,三个同学负责Java后端,各自的代码仓库互不干扰。

第二是演示效果。实训答辩的评委不会看你的代码结构,他们看的是页面。Element UI 的表单、表格、弹窗组件做出来的管理后台,比服务端渲染的纯HTML页面专业太多,这个优势在最终评分时非常明显。

第三是贴近企业真实开发模式。当时2021年找实习的热门方向就是Java后端 + Vue前端,这套技术在校园招聘里基本是标配。实训不止是为了拿学分,简历上能写"熟悉前后端分离开发,掌握Spring Boot和Vue技术栈"也是实打实的收获。

2.3 锁定的版本和初始化配置

版本问题是我们踩的第一个坑,这里直接把我们最终稳定运行的版本列出来,后来做类似项目的同学可以直接抄:

  • 后端:JDK 1.8、Spring Boot 2.5.2、MyBatis-Plus 3.4.3、MySQL 5.7、JWT 0.9.1
  • 前端:Vue 2.6.14、Element UI 2.15.8、Axios 0.21.1、ECharts 5.1.2
  • 工具:Maven 3.8、Node.js 14.x、IDEA + VSCode

这里特别提醒一下JWT 0.9.1这个版本,它在JDK 1.8下会报ClassNotFoundException: javax.xml.bind.DatatypeConverter,需要在pom.xml里手动补一个依赖:

<dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency>

另外MyBatis-Plus 3.4.x版本的分页插件和旧版本配置方式不一样,需要用PaginationInnerInterceptor,这个在跑分页查询之前就要配好,不然后端接口一直返回全量数据,前端分页怎么都做不对。

3. 数据库建模:一张elder表怎么长出一棵业务树

3.1 先梳理核心表,再动手建库

我们第一版数据库是在一个晚上赶出来的,结果第二天就被指导老师问住了:你健康记录和护理工单这两张表,怎么确定它属于哪一位老人?我们才发现很多表之间根本没有外键关联,逻辑上完全站不住脚。

后来重新梳理了一遍业务,最终稳定的表结构是这样的:

表名关键字段说明
sys_userid, username, password, real_name, role, status员工账号,role区分admin和nurse
elderlyid, name, gender, birth_date, phone, address, emergency_contact, status老人档案主表
roomid, building_no, room_no, floor, room_type, status楼栋房间
bedid, room_id, bed_no, status房间内的床位
elderly_roomid, elderly_id, bed_id, check_in_time, check_out_time入住关系表
health_recordid, elderly_id, record_time, high_pressure, low_pressure, heart_rate, blood_sugar, temperature, note健康巡检记录
nursing_taskid, elderly_id, assigner_id, executor_id, task_type, content, status, create_time, finish_time护理工单
noticeid, title, content, publish_time, publisher_id公告

这个表设计里有几个细节是我们在踩坑之后才加上的,单独说一下。

3.2 老人和床位的关系:为什么不能直接存一个room_id

最开始的设计里,elderly表直接存了room_id和bed_id两个字段,看起来简单。但后来发现两个问题:一是老人换房的时候要UPDATE原表,把之前和历史数据关联全部覆盖掉了;二是如果未来要统计"这个床位住过哪些老人",根本没有记录。

所以我们拆了一张elderly_room关系表,把入住时间、退住时间单独存。这样老人当前住哪张床、床位历史入住记录、换房记录全部都能查到。这是一个典型的"用空间换可追溯性"的设计,在养老这种需要长期跟踪的场景下非常实用。

3.3 健康记录表:指标字段必须拆开,不能塞JSON

这个是我们组内讨论最久的一个设计点。一开始有同学提议:health_record表存一个indicators字段,把所有健康指标用JSON字符串存进去,反正前端展示的时候也是解析完再画图,这样表结构简单,后面加指标类型也不用改表。

这个方案被我们否了,原因是数据库里存JSON会导致严重的问题:没法按指标做范围查询。比如"查一下最近一个月所有血压偏高的老人",如果血压值埋在JSON里,SQL根本写不出来,只能把记录全部查出来在Java内存里过滤,数据量一大就废了。所以我们把高压、低压、心率、血糖、体温这些常用指标都拆成了独立字段。折线图查最近30天的数据,一条带WHERE high_pressure > 140的SQL就能解决。

3.4 工单状态用状态机来管理

nursing_task表里的status字段我们定义为0待处理 / 1进行中 / 2已完成 / 3已取消。这个状态流转是有方向的:待处理可以转进行中,也可以转已取消;进行中只能转已完成,不能跳回待处理。

这个约束除了在代码里做校验,数据库层面也可以加一个CHECK约束兜底。我们当时为了赶进度没有加数据库约束,结果联调的时候就出现了前端连续点击两次按钮、工单状态被直接从待处理改成已完成的情况。如果重来一遍,数据库约束一定加上。

4. 核心功能落地:从登录到工单闭环的完整实现

4.1 JWT登录认证与角色权限控制

登录模块是整个系统的入口,也是我们花时间最多的地方。我们的方案是:后端用JWT生成token,前端把token存在localStorage里,每次请求在Axios拦截器中塞进Header。

JWT的工具类核心代码大概是这样的思路:

public class JwtUtil { private static final String SECRET = "your-secret-key"; public static String generateToken(Integer userId, String username, String role) { return Jwts.builder() .claim("userId", userId) .claim("username", username) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }

后端实现了一个HandlerInterceptor,在preHandle里统一从Header取token、解析、把userId和role放进Request作用域,然后放行。管理员接口路径我们统一设计成/admin/**前缀,拦截器里根据role字段判断能不能访问。这里有个我们踩过的坑,后面单独讲。

前端Vue这边,路由守卫里对需要登录的页面做了判断,token不存在就跳转到登录页。Axios拦截器里对401状态码做了统一处理,token过期就清除本地登录态,强制跳回登录页。

4.2 MyBatis-Plus 实现老人档案分页与条件检索

老人档案的列表页是典型的分页+多条件查询。我们使用了MyBatis-Plus的selectPage配合LambdaQueryWrapper,代码非常简洁:

public PageResult<Elderly> pageElderly(Integer pageNum, Integer pageSize, String name, Integer status) { Page<Elderly> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Elderly> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(name), Elderly::getName, name) .eq(status != null, Elderly::getStatus, status) .orderByDesc(Elderly::getCreateTime); elderlyMapper.selectPage(page, wrapper); return new PageResult<>(page.getTotal(), page.getRecords()); }

前端配合Element UI的el-table和el-pagination,一个完整的检索页面很快就能拉起来。值得注意的一个小细节是身份证号的唯一性校验,我们除了在业务代码里查重,还在数据库字段上加了唯一索引,双重保险,防止多用户并发提交时插入了重复数据。

4.3 健康趋势图:ECharts画最近30天血压变化

这个模块是我们演示时最出效果的功能。实现思路不复杂:后端提供一个接口,传入elderlyId,返回最近30天的健康记录列表,前端用ECharts折线图展示。

我们画了两条折线,收缩压和舒张压,横轴是日期,纵轴是mmHg。为了图好看,后端返回数据时就按日期排好序,前端拿到之后直接用split拆成两个series。另外我们在ECharts的配置里加了markLine标记正常血压范围,超出范围的数据点会显示成红色,视觉效果非常直观。

这个功能在答辩时被老师专门夸过,因为它是真正的"数据可视化"而不是简单列表。其实实现难度不高,关键是有这个意识:把健康数据用图表呈现,比密密麻麻的表格更有信息量。

刚跑通健康趋势图的当天,我们其实还遇到了一个数据问题:测试数据里的高压值是随机的,偶尔会出来“高压300”这种明显超标的值,调度图上一根针一样扎上去,看起来很吓人。后来我们在录入数据的地方加了简单的数值范围校验,高压在70-250之间,超出就提示输入不合法,数据质量才正常。

4.4 护理工单的完整闭环:派单、接单、完成、统计

工单模块是系统业务逻辑最重的地方。管理员的视角是"派单",选择老人、选择护工、填写任务内容;护工的视角是"我的任务",默认查询executor_id = 当前登录用户id的工单,点击"开始处理"把状态从待处理变成进行中,处理完点"完成"再填一段完成备注。

我们做了一个小小的统计接口:查询当前登录护工今日待办数量、今日已完成数量、平均完成时长。这个接口在护工端首页展示成三个统计卡片,实训演示的时候显得业务完整度特别高。具体实现就是基于nursing_task表的executor_id和create_time做分组统计,SQL并不复杂。

有一点值得提:工单完成时间我们用的不是系统当前时间,而是在护工点击"完成"按钮那一瞬间由后端在事务里更新。这个字段看似不起眼,但它支撑了后面"平均完成时长"的计算,也方便管理员后续看每个护工的工作量。

5. 联调与上线的真实翻车现场

5.1 接口字段没对齐,前后端对着页面吵了一个小时

前后端分离开发最痛的点就是联调。我们联调阶段第一个大坑出现在新增老人接口。后端Java实体类的字段是createTime,数据库字段是create_time,MyBatis-Plus默认开启了驼峰转换,这没问题;但前端同学从Element UI表单里拿到的字段名也是createTime,他以为是数据库字段,就在提交参数里传了一个create_time。

结果后端接收到的实体类里createTime为null,插入数据库的时候直接报错"字段create_time cannot be null"。当时前后端各执一词,前端说"我明明传了create_time",后端说"我要的是createTime",排查了快一个小时才发现是命名规范的问题。

从那之后我们立了个规矩:联调之前先过一遍接口文档,字段名以Java实体类为准,前端不自己脑补字段名。后来我们把Swagger接上了,直接在页面上看每个接口的请求参数示例,这种问题再没出现过。

5.2 权限漏洞:护工账号直接调用管理员接口

这个是实训过程中最惊险的一个bug,发生在答辩前第三天。我们测试的时候偶然发现,用护工账号登录之后,直接在后端接口地址里输入/admin/user/list,居然能查到全部员工列表。

原因很弱智:拦截器里只做了token是否存在、是否过期的校验,根本没判断当前用户的角色。也就是说,只要是个登录用户,管你是管理员还是护工,都能访问所有接口。这就是典型的"只做了登录验证,没做授权验证"。

修复很快,在拦截器的preHandle里增加角色判断:requestURI以/admin/开头时,解析token里的role字段,必须是admin才能放行。但我们复盘的时候发现,之所以会犯这个错,是因为我们一开始安全设计的重心全在"怎么生成token""怎么防止token伪造"上,反而忽略了最基础的越权问题。这给我们上了一课:安全不只是加密算法,更是每一个接口上的访问控制。

5.3 部署到服务器:中文乱码、端口占用、后台启动

答辩前我们需要把系统部署到云服务器上。买了最低配的2核4G云主机,装好MySQL、JDK、Nginx、Node环境,一个坑接一个坑。

第一个坑是MySQL建库没有指定字符集,导致页面上录入的中文全部变成问号。建库时一定要指定编码:

CREATE DATABASE yanglao DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

第二个坑是Nginx默认监听80端口,后端Spring Boot默认8080端口。服务器安全组只放行了80端口,前端能打开,但请求转发到8080时被安全组拦了。解决办法是Nginx配置反向代理,把所有/api/开头的请求都转发到服务器的8080端口。这既解决了端口问题,也顺便做了前后端域名统一,前端请求不需要写跨域配置。

第三个坑是jar包启动方式。我们最开始用java -jar前台启动,一关SSH窗口服务就断了。后来改用nohup放后台:

nohup java -jar yanglao-system.jar > app.log 2>&1 &

这个命令把日志输出到app.log文件,关掉终端服务也不会断。排查问题时直接tail -f app.log看报错信息,比在IDEA控制台里看方便得多。

6. 实训结束后,我的三个复盘认知

6.1 需求分析花的时间越久,后面返工越少

我们第一版数据库当天晚上就建完了,后来两周里改了三次表结构。每一次改动都牵动后端实体类、前端表单、接口文档一起变,成本极高。后来才明白,建表之前应该先把业务流程图和数据流图画清楚,确认每个数据从哪来、到哪去、谁修改、谁删除。这些工作看起来不产生代码,但能省下后面无数改bug的时间。

6.2 接口文档是团队协作里的最大公约数

前后端分离的团队里,接口文档不仅仅是"记录了接口长什么样",更是前后端双方对业务理解的统一约定。我们一开始用Markdown写接口文档,后来换成了Swagger,前端同学可以直接在页面上看参数示例和响应结果。哪怕时间紧张,接口文档也绝对不能省,这是我们在联调上吃过最大亏的地方。

6.3 一个完整的闭环,比十个零散页面更有说服力

实训答辩我们会看其他组的展示,发现有的组做了十几个页面,但每个模块之间没有任何数据联动,明显是各写各的。而我们只做了五个核心模块,但它们是串起来的:管理员录入老人档案、分配床位、创建健康记录模板、派发护理工单;护工登录看到自己负责的老人和任务,完成任务后状态回写;管理员在统计页能看到完成率。这个完整的数据闭环,让我们在答辩时讲起来非常有底气,老师也觉得这是一个整体系统,而不是一堆页面的拼凑。

最后再说一个实用的小经验:答辩前一天,我们组专门把整套流程从登录开始走了一遍,拿了一份真实的测试数据(包括一位老人完整的入住、健康记录、工单记录),然后录屏保留。演示的时候即使现场网络出问题,也可以用录屏兜底。这个习惯后来我在工作里也一直保留,每次都帮我避免了在关键场合翻车的尴尬。

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

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

立即咨询