☰
Springboot+Vue敬老院管理系统开发复盘:从需求到答辩全流程
2026/10/24 4:40:50 网站建设 项目流程

去年下半年接了一个真实的小项目——某家民办敬老院想做信息化改造,之前一直靠纸质台账和Excel表格维护老人信息,家属来访记录也是笔头记,一到月底对账就手忙脚乱。前前后后花了大概三周时间,把一套基于Springboot+Vue的管理系统从零搭起来,也就是后来整理成论文和答辩PPT的那个"敬老院管理系统"项目。这篇博文就把整个项目的完整复盘写出来,从需求拆解、技术选型、数据库设计,到后端鉴权、前端动态路由,再到开发过程中踩过的几个深坑,以及论文和PPT该怎么组织素材,一次性说清楚。

适合谁看:正在做毕业设计、想找前后端分离实战项目的同学,刚入职需要独立承担小型管理系统开发的初级开发者,以及带学生做课设想给一份靠谱参考的导师。

1. 立项阶段:敬老院的真需求和管理系统的设计边界

1.1 业务现状和核心痛点

先说业务场景。这家敬老院规模不算大,常年入住老人一百二十多人,配套护工、保洁、厨房加起来三十来人。原来的管理模式非常原始:

  • 老人信息:入院登记填纸质表,然后抄进Excel,照片贴墙上
  • 用药提醒:护士每天按打印出来的表格人工核对,容易漏
  • 家属来访:门卫本子登记,事后想找人查某天谁来过,翻半天
  • 费用账目:床位费、护理费、伙食费混在一起,月底财务手工算

所以真正的需求不是"做个系统",而是解决三件事:老人信息要能快速查、用药和探访流程要能记录留痕、费用要能自动统计。这也是我后来写论文时需求分析部分的原始素材。

1.2 角色权限体系的设计思路

管理系统的权限设计,关键看角色分工。敬老院里的角色不复杂,我最终划分为四类:

角色核心诉求权限范围
系统管理员管人、管床位、管账号全部模块,含系统设置
前台/行政接待家属、登记来访、办理入住退住老人档案、来访登记、入住管理
护士/护工记录健康、查看用药提醒、反馈异常健康档案、用药列表、异常上报
家属访客(只读账号)查看老人近况、缴费记录仅限本老人相关的档案和费用

这里有个容易被忽略的点:家属账号虽然访问范围窄,但录入是刚需。我一开始把它砍掉了,后来沟通发现很多老人子女在外地,每次打电话问情况效率极低,所以专门加了一个轻量级的家属端。技术上不做单独App,就在Vue项目里做多角色路由,靠登录返回的角色字段动态生成菜单。

1.3 功能模块的全景拆分

整个系统按业务闭环分成七个模块:

  1. 老人档案管理:基本信息、亲属关系、入住时间、床位号、照片
  2. 入住退住流程:床位分配、押金登记、退住清算
  3. 健康与用药管理:体检记录、慢性病标签、用药计划和执行打卡
  4. 护工排班管理:按楼层排班、换班申请
  5. 来访登记管理:预约、登记、离开时间、黑名单
  6. 费用管理:床位费/护理费/伙食费项目化,月度账单自动生成
  7. 数据看板:入住率、年龄分布、当月费用汇总

模块别贪多。我见过很多同学做毕设把系统设计成十几个模块,结果每个都是空壳子。敬老院系统的核心就两个词:档案和流程,其他都是围绕这两件事打转。

2. 技术选型:为什么是Springboot+Vue,以及工程目录怎么搭

2.1 框架选型的真实考量

这套技术栈选得很保守,但恰恰是毕设和企业小项目里最稳的组合。

后端的Springboot,本质上是把Spring生态里那些繁琐的XML配置全部自动化了。内嵌的Tomcat让部署变得极其简单,直接java -jar就能跑。对敬老院这种数据量不大、并发不高的系统来说,它的性能完全够用,而且社区资料最多,遇到问题一搜一大把。

前端Vue全家桶就不用多说了,关键优势是组件化。后台管理页面的形态高度相似:左边侧边栏、顶部面包屑、中间表格加表单弹窗。用Element UI的表格、表单、Dialog组件一套组合拳,开发效率非常高。加上Vue Router和Vuex,路由权限和数据共享都很好处理。

前后端分离还有一个隐性好处:养老院的电脑是老机器,浏览器性能一般般。前端资源用Nginx一挂,页面加载速度甩开传统JSP项目几条街。

2.2 后端工程目录规划

Springboot项目我用的是标准分层结构:

com.example.eldercare ├── config // 跨域配置、MyBatisPlus配置、Knife4j配置 ├── controller // 接口层 ├── service // 业务逻辑层 ├── mapper // 数据访问层 ├── entity // 数据库实体 ├── dto // 前端交互对象,接收和返回 ├── common // 统一返回结果、异常处理、常量 └── utils // JWT工具类、时间处理工具

一个很实用的建议:entity和dto一定要分开。我见过太多项目直接用实体类接收前端传参,前端多传一个字段就会出问题。用dto做隔离,接口边界清晰,字段校验也好写。

2.3 数据库表设计的心得

数据库是这类系统的命脉。我最终用了11张表,核心的表设计逻辑如下。

**老人档案表(elder_info)**是主表,字段包括老人姓名、性别、身份证号、出生日期、家属电话、入住日期、床位号、护理等级、头像地址、状态(在住/退住/外出)。

**床位表(bed_info)**单独拆出来,和老人档案通过bed_id关联。为什么拆?因为床位有"空闲/占用/维修"三种状态,而且涉及楼层、房间号、床号的三级定位,拆开以后统计入住率非常方便:

SELECT COUNT(*) AS total_bed, SUM(CASE WHEN status = 'OCCUPIED' THEN 1 ELSE 0 END) AS occupied_bed FROM bed_info

**用药计划表(medication_plan)和用药执行表(medication_log)**是一对多的关系。plan表记录"哪位老人、什么药、每天几次、什么时段",log表记录"护工实际在哪个时间点打卡确认了"。这个设计模仿了医生处方和护士执行单的模式。

来访登记表字段有老人id、访客姓名、电话号码、关系、来访时间、离开时间、备注。这里要注意加一个visitor_type字段,区分"亲属探访"和"外部人员(如推销、维修)",后者往往需要重点关注。

数据库统一用utf8mb4编码,因为这个库要存老人姓名里的生僻字。身份证号不能直接当主键,因为有极少数老人身份证缺失的情况,用自增id做主键,身份证号加唯一索引。

3. 后端关键模块的实现细节和原理

3.1 基于JWT的登录鉴权

前后端分离项目里最经典的登录方案就是JWT。流程不复杂:用户输入账号密码登录成功后,后端验证密码,生成一个带过期时间的token返回,前端存到localStorage,后续每次请求在请求头里带上。后端拦截器解析token,拿不到或过期就返回401。

我用JJWT库来生成和解析token,密钥放在application.yml里统一管理。为什么是JWT而不是Session?因为前后端分离之后,后端接口可能是多个服务实例部署的,Session共享要额外引入Redis,对一个敬老院系统来说纯属过度设计。JWT无状态,后端不用存会话,扩展性和维护成本都更好。

关键代码片段,token生成逻辑:

public String generateToken(Long userId, String username, String role) { long now = System.currentTimeMillis(); return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuedAt(new Date(now)) .setExpiration(new Date(now + TOKEN_EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }

拦截器里除了校验token是否合法,还要把用户信息塞到Request作用域里,方便后面的接口直接从上下文里取当前操作人是谁。这个点很实用,因为后面所有的操作日志、来访登记和用药打卡,都需要记录操作人,如果每次都从前端传名字,就存在被伪造的风险。只有具备读、写权限拦截器和自定义注解配合,才能真正在controller层语义化地声明权限。

3.2 老人档案的上传和逻辑删除

老人在入住时要把身份证拍照、头像照片一起录进系统。照片我想过存Base64字符串直接进MySQL,后来否掉了——一张照片Base64之后体积膨胀约30%,一百多个人就有几十兆数据,MySQL吃不消。正确做法是文件用本机磁盘或OSS存储,数据库只存访问路径。

于是我在系统配置里定义了一个虚拟路径映射:

file.upload.path=/data/eldercare/upload/

然后定义一个Spring MVC的资源配置,把/upload/**映射到本机磁盘路径。这样上传的图片可以在前端直接通过URL访问,部署时改配置换路径就行。

逻辑删除也是必须的。老人退住后,档案不能直接物理删除,万一以后子女要查发票、查历史记录呢。所以所有表都加了deleted字段,用MyBatis-Plus的@TableLogic注解实现全局逻辑删除。

但这个设计埋了一个雷,后面会单独讲。

3.3 方案:定时任务加执行确认的用药提醒

用药提醒是护士们最关心的功能。最开始的需求是"到点弹窗提醒",我本来想用WebSocket推送,后来发现想复杂了。

敬老院的真实场景是:药房把每天的药按人分装好,护工推车去各个房间发药。护工要的不是手机上弹一条消息,而是一份按时段排列的任务清单,几点该去哪位老人房间发什么药,发完打卡确认。

所以最终设计是:

  1. 后台维护每位老人的用药计划,录入药品名称、剂量、频次(如"每日三次")、时段(早/中/晚)
  2. 系统提供一个"今日用药任务"接口,根据系统当前日期动态生成当天的任务列表
  3. 护工手机上(实际就是平板上)打开任务列表,按老人和时段逐项打卡
  4. 打卡记录写入medication_log表,如果有漏打卡,第二天看板会提示异常

这个方案绕开了实时推送的复杂度,用最朴素的"按需查询"解决了问题。每天用药任务由SQL关联查询动态生成,不落地存储,设计简洁又容易理解。写论文的时候,这个模块的流程图和数据流图画起来也最清晰。

3.4 费用模块的月度账单自动生成

费用管理一开始预算做得很细,后来发现进度根本跟不上。重新梳理后,我用一句话概括了真正的需求:每个老人每月该交多少钱,系统要能算出来,财务能导出对账单,子女能在线看到明细。

费用项目就是三个:床位费、护理费、伙食费。其中护理费按老人护理等级(自理/半护理/全护理)取不同单价。

月度账单的生成用的是月度跑批的逻辑:每月1号系统自动扫描所有在住老人,按入住天数和费用项目生成当月的账单明细。为了实现这个逻辑,我设计了fee_order(账单主表)和fee_item(账单明细表)两张表。主表记录老人、账单月份、总金额、缴费状态,明细表记录每条费用项目的名称、单价、天数、小计。

这个月哪天入住、哪天退住的计算,最核心的SQL是DATEDIFF函数结合日期边界判断。退住那天要不要收全天费用?需求沟通时确定的是"按天收费,入住当天算一天,退住当天不算"。所以生成账单时的计算规则就固定成:

// 当月计费天数 = 月末日 - 入住日期 + 1,若跨月则按自然月截断 int billedDays = calculateBilledDays(checkInDate, billMonthStart);

注意这里存在一个边界:如果老人月底入住,当月计费天数不满一个月,必须按实际天数计算,不能直接按全月。这个坑在测试阶段真实遇到过,写成测试用例挂上去,后面改逻辑不怕回归。

4. 前端Vue开发实战:怎么搭出顺手的管理界面

4.1 axios封装与请求拦截

Vue前端的第一步不是写页面,而是把网络请求层封装好。管理后台的所有接口,都走统一的axios实例。

封装的两个核心诉求:请求时自动带token,响应时统一处理错误码。

service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = token } return config }) service.interceptors.response.use( response => { const res = response.data // 业务层约定:code===200 代表成功 if (res.code !== 200) { Message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response && error.response.status === 401) { // token失效,清空本地存储并跳转登录页 localStorage.clear() router.push('/login') } Message.error('网络异常,请稍后重试') return Promise.reject(error) } )

这套设计的好处是,业务组件里写请求时只需关注成功后的数据回调,不需要重复做错误弹窗。401统一跳登录,也避免了很多"系统报错但你不知道为啥要重新登录"的困惑。

4.2 动态路由和左侧菜单的实现

后台系统的菜单权限如果不做,每个角色进去都看到一堆按钮,体验很糟糕。我在前端做的方案是:

登录成功拿到用户信息,里面带有角色标识,前端维护一个"角色-菜单"的映射表。比如护工角色能访问的菜单路径集合是:

const roleMenus = { ADMIN: ['/dashboard', '/elder', '/bed', '/medication', '/visit', '/fee', '/system'], NURSE: ['/dashboard', '/elder/detail', '/medication/task'], FRONT_DESK: ['/elder', '/visit', '/fee/query'] }

在Vue Router全局守卫里,每次路由跳转前都判断当前用户是否拥有该路由的访问权限,没有就重定向到404或者首页。

菜单则通过Vuex绑定,根据当前用户角色动态渲染。效果就是:同一个系统,管理员进去看到七大模块,护工进去只看到看板和任务清单,前端小姑娘看到的则是来访登记和老人查询。

4.3 数据看板:ECharts统计图表的实用性设计

数据看板页是用ECharts做的,核心图表有三个:

  1. 月度入住率趋势折线图:X轴是1到12月,Y轴是百分比
  2. 老人年龄分布饼图:年龄段分组统计
  3. 当月费用汇总柱状图:按费用项目分类汇总

这里有个细节:统计数据接口返回的数据结构,需要严格按照前端图表库需要的结构设计。比如饼图需要的是[{name: '70-79岁', value: 45}, {name: '80-89岁', value: 62}],那我后端接口就直接返回这个结构,不要返回原始列表让前端自己算。看着简单,但好多新手后端就是不懂这个,非得把压力全给前端,导致前端代码又臭又长。

ECharts的图表在数据加载完成后需要调一次setOption,我的做法是watch一个数据变量,数据到位就重新渲染,同时记得把chart实例用dispose销毁再重建,否则页面切换后内存泄漏、图表错乱的情况特别容易出现。

5. 开发过程中的深坑复盘:每一个都让人印象深刻

5.1 跨域问题:不是配置一下CORS就完事

本项目前后端完全分离,前端跑在8080端口,后端跑在9090端口。未处理跨域时,前端请求直接被浏览器拦截。网上主流答案是写个跨域配置类,后端加上@CrossOrigin或配置CorsFilter。

但我实际开发时遇到的是另一个隐蔽问题:自带拦截器会拦截OPTIONS预检请求。CORS对于带自定义Header(如Authorization)的请求,浏览器会先发一个OPTIONS请求询问服务器支持哪些方法、哪些头。如果拦截器直接对OPTIONS也做JWT校验,就会返回401,浏览器一看不支持就直接报跨域错误了。

正确做法是拦截器里先判断请求方法,OPTIONS请求直接放行:

if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; }

这个坑花了将近半天才定位到,因为浏览器控制台只显示"CORS error",完全没有提示是401导致的。

5.2 数据库逻辑删除撞上唯一索引

这是整个项目里最经典的坑。老人表里的身份证号字段做了唯一索引,用MyBatis-Plus的逻辑删除之后,程序里执行插入操作,第一次正常,第二次就报"Duplicate entry"。

原因很简单:逻辑删除是UPDATE语句把deleted从0改成1,行还在表里,身份证号字段依然占着唯一索引。第二次插入同一个人时,数据库层面还是认为有一条相同的身份证存在。

解决方案有三个:

  1. 唯一索引改为复合索引,把deleted字段加进去。这是网上流传最广的方案,其实有问题,因为逻辑删除之后deleted标记的是1,如果用0/1标记,两条已删除记录仍然冲突。

  2. 进阶方案:deleted字段存时间戳。未删除为0,已删除为当前毫秒值。这样复合唯一索引(id_card, deleted)就不会冲突。

  3. 物理删除替代逻辑删除:对一些确认不会再恢复的数据(比如测试数据),直接物理删除。

我最后选了方案2。这也是写论文时一个非常不错的分析素材,能体现实战中的细节考量。

5.3 前后端时间相差8小时的偏移问题

时间字段在前端页面显示,明明是中午12点录入的,页面上却显示凌晨4点。原因是后端序列化时默认把时间按UTC时区处理,前端浏览器按本地时区解析,一减就是8小时。

如果你用Jackson做序列化,需要在application.yml里强制指定时间格式和时区:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

用MyBatis-Plus的时候,还要注意实体字段上加上@TableField(fill = FieldFill.INSERT_INSERT)配合自动填充创建时间。我见过很多项目花在排查"明明数据库里是对的,页面就是差8小时"这个问题的时间,足够把整个系统重新开发一遍。

5.4 Element UI表格数据量大时的渲染卡顿

一百多条老人数据不算多,但当ElderList页面打开时,如果要渲染的表格列太多(照片、姓名、身份证、家属电话、入住日期、床位、状态、操作按钮),老电脑的浏览器还是会卡顿。

我用的优化方案是:表格开启筛选和排序,数据来自后端分页接口,前端表格用el-pagination组件控制每页显示数量,默认每页10条。同时el-table-column中的操作列用fixed="right"固定,避免横向滚动时操作按钮看不见。

另外一个容易忽略的点是图片列。老人头像别直接用图片URL渲染原生img标签,用el-table的<template #default="{ row }">包一层懒加载,配合图片压缩,页面速度会有明显提升。

6. 论文结构与答辩PPT的组织思路

6.1 论文编排的骨架

标题和素材都是现成的,论文结构我按标准的软件工程论文套路走,六章:

  1. 绪论:背景(老龄化、养老机构信息化趋势)、国内外现状、论文工作与组织结构
  2. 相关技术:Springboot、Vue、MyBatis-Plus、MySQL、JWT。这里不需要长篇大论,每项技术写清楚它解决什么问题就行
  3. 系统分析:可行性分析、功能需求、非功能需求、用例图、数据流图
  4. 系统设计:总体架构、功能模块设计、数据库设计(E-R图、表结构)
  5. 系统实现:核心模块界面截图、关键代码、功能描述
  6. 系统测试:测试环境、测试用例表、测试结果与分析

写论文最忌讳的是大段粘贴代码,老师看的是你为什么这么设计。比如JWT那段,你要写的是"传统Session方案存在什么问题,因此选择JWT",而不是贴生成token的十行代码。这也是我在前面反复强调设计理由的原因。

6.2 PPT的演示主线

答辩PPT不要做几十页,控制在12页左右就好,主线是这样的:

第1页:封面,标题+姓名学号 第2页:项目背景和意义,一张图说清现状 第3页:系统功能架构图 第4页:技术栈选型及理由 第5页:数据库设计,放E-R图或核心表结构 第6页:核心功能1的界面截图+流程说明(推荐用药管理) 第7页:核心功能2的界面截图(推荐来访登记和数据看板) 第8页:系统测试结果 第9-10页:总结与展望 最后一页:谢谢批评指正

PPT上少放字,放截图,放大图。答辩老师基本不会逐字读,他们看的是你有没有想清楚系统是干嘛的、技术选型合不合理、功能跑不跑得通。

6.3 答辩现场准备好的几个问题

有些问题你不在系统设计阶段想清楚,现场很容易卡壳。

第一个:**这个系统相比普通的管理系统有什么特色?**答案不是"用了Springboot",而是"针对敬老院业务设计了用药任务动态生成机制,以及家属只读账号这样的垂直场景"。

第二个:**数据库表之间关系是什么?**这个要能当场画出来E-R图,老人-床位-账单之间的一对多关系要讲清楚。

第三个:**如果老人数量扩展到一千人,系统会不会崩?**你要能说出来瓶颈在哪:数据库连接池和服务器带宽,同时对查询量大的接口建议加Redis缓存。

这些问题提前理好,答辩时的底气完全不一样。我见过有人做完系统,却答不出"为什么要在老人表里加护理等级字段"这种最基本的问题,那就是纯粹没走心。

写在最后

系统全部跑通上线之后回头再看,这个项目真正花时间的不是写代码,而是理解和梳理业务。敬老院的信息化和普通企业管理有一个明显差异:使用者是护工和前台,很多人对电脑不熟悉,界面一定要简单直观,按钮一定要大,操作路径一定要短。技术上的Springboot+Vue只是工具,能不能让那些爷爷奶奶的子女在外地随时查到父母的近况、能不能让护工按时按点少漏一次药,才是这个系统真正的价值。

如果你正打算做类似的基于Springboot+Vue的管理系统,我的建议是先踏踏实实去找一个真实的业务场景聊一遍,把流程理清楚,再动手建表。技术是通的,业务是活的,把业务吃透,后面的论文和答辩自然就有东西写了。

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

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

立即咨询