资讯动态

Spring Boot乡村养老管理系统:架构解析与二次开发实践

发布时间:2026/10/20 15:31:57 来源:尧图企业网站定制
简介这是一份基于Spring Boot框架构建的乡村养老服务管理系统完整源码主要面向计算机专业学生、Java开发入门者以及需要快速搭建养老管理类项目的工程师。系统预设管理员、老人、医疗人员、乡村志愿者四类角色覆盖账户创建与登录验证、养老服务申请与记录查询、老人健康信息维护、志愿者陪伴照料及系统数据管理等功能业务链路贴合乡村养老场景。资源压缩包共926个文件、约33.73MB内容以Java后端源码、Vue前端组件、JavaScript脚本、CSS/HTML页面及SVG图片资源为主同时包含SQL数据库初始化脚本、Maven/YAML配置以及bat一键安装、运行、打包脚本前端可见Element UI、Bootstrap等样式文件便于分析页面布局与组件用法。目前已获43人浏览/学习适合用作课程设计、毕业设计或Spring BootVue全栈项目的参考案例。解压后可借助脚本快速启动配合源码与数据库脚本可以完整理解用户权限、服务申请、健康管理等模块的前后端交互逻辑及项目目录结构。1. 这是套什么系统一个能直接跑起来的乡村养老服务管理后端如果你接手过养老或民政相关的信息化项目应该知道这一类系统的痛点老人档案散落在纸质台账里健康数据靠护工口头交接服务工单靠微信群接龙月底对账要翻一上午聊天记录。这套基于 Spring Boot 的乡村养老服务管理系统就是把上述流程搬到一个后台里。它带完整的源码、数据库脚本和配置前端页面是服务端渲染模式后端用 Spring Boot 搭建能管老人档案、健康评估、服务工单、护工排班和收费记录适合做毕业设计、课程设计也适合乡镇级养老服务站点做内部管理使用。对刚接触 Spring Boot 的开发者来说它是一份能跑通全流程的参考实现从登录鉴权到数据导出都有现成代码可抄。2. 系统架构与核心模块拆开看模块、表结构和选型理由2.1 功能模块划分五个核心业务域这套系统的业务逻辑不复杂但胜在模块划分清楚。我拿到源码后先从控制器和实体类反推了一遍业务域大致分成五块。第一块是老人档案管理这是全系统的数据基础。它不止维护老人姓名、年龄、住址等基础信息还记录了居住状况、有无慢性病、紧急联系人、是否独居等字段。这些字段直接影响后续的服务派单逻辑比如系统在创建工单时会根据老人的“独居”和“慢性病”标记弹出高关怀提示。第二块是健康管理包括体温、血压、血糖等周期性的健康测量记录以及定期的健康评估。健康数据在列表页做了图标展示方便护工一眼看出异常趋势。这部分代码里用到了定时任务每天早上自动把连续三天血压偏高的老人汇总到提醒列表里。第三块是服务工单管理。护工或管理员接到照料任务后创建工单填写服务类别、上门时间、服务时长结束后录入回执。工单状态机是待接单、服务中、已完成、已取消四种。这一块是系统里联表查询最多的部分因为需要同时关联老人、护工和计费信息。第四块是护工与排班管理。护工账号由管理员创建有基础信息、技能标签和可服务区域。排班按周生成每天一个班次一个护工只能排在一个班次上如果冲突会给出提示。第五块是收费与统计。服务完成后按单价和时长自动生成收费记录月底汇总成报表。统计页面提供了按村、按服务类型的汇总支持导出 Excel。这个导出功能是很多类似系统里容易被忽略的但实际使用中几乎每周都要用到。2.2 技术栈与选型为什么这套组合适合中小型管理系统这套系统后端用的是 Spring Boot 2.x持久层选了 MyBatis-Plus前端是 Thymeleaf 加 Bootstrap数据库是 MySQL 5.7。这个组合在中小型管理系统里非常典型理由也很直白。Spring Boot 负责把整合配置收敛到最少。你不需要手动写一堆 XML 配置只需要在 application.yml 里指定数据源、MyBatis 的映射路径和端口就能启动一个可用的 Web 服务。这对新手来说是最友好的入门姿势。MyBatis-Plus 相比原生 MyBatis 的价值在于内置了通用 Mapper 和分页插件。这套系统里大部分单表查询不需要手写 SQL直接调用 selectById、selectPage 就能完成。但对于多表联查它仍然保留了 XML 自定义 SQL 的方式。我数了一下resources/mapper 目录下大概有二十多个 XML 文件都是那种带动态条件的联表查询。前端用 Thymeleaf 是因为它和 Spring Boot 配合最自然服务端渲染出来的页面可以直接用 EL 表达式取值不需要单独启前端服务。这对部署环境有限的项目特别合适一个 jar 包加一个 MySQL 就能跑不用考虑跨域和 Node 环境的问题。有人可能觉得这个技术栈不够“新”但我作为一线开发者的感受是项目的稳定性和可维护性比技术新鲜感更重要。这套系统里没有花哨的微服务和前后端分离它核心把业务逻辑理清楚让一个 Java 基础尚可的人三天内能上手改造这比什么都重要。2.3 数据库设计核心表与关键字段说明数据库脚本在 sql 目录下文件名是 neighborhood_care.sql里面建了十五张表。这里挑四张核心表说说设计思路。老人的基础表叫 elder_info主键是 elder_id 自增。除了姓名、性别、出生日期这些常规字段外有一个字段值得注意care_level照护等级。这个字段是字符串类型取值是“自理、半失能、失能”三档。很多业务逻辑都以这个字段为分支比如服务单价、推荐服务类别、月度统计口径。如果你想扩展系统这个字段是优先要维护的。健康表叫 elder_health_record用 elder_id 关联老人字段包括 record_date、blood_pressure_systolic、blood_pressure_diastolic、blood_sugar、temperature。这里有个设计细节日期字段是 String 类型存储格式为 yyyy-MM-dd。这样做的好处是前端展示和导入导出不需要做格式转换坏处是范围查询必须用字符串比较好在日期格式保证了字典序where record_date between 2024-01-01 and 2024-01-31 这个条件在 MyBatis 里能正确执行。服务工单表叫 service_order字段有 order_no、elder_id、worker_id、service_type、service_date、duration_minutes、status 和 amount。service_type 用数字编号关联字典表而不是直接存中文。这样设计的原因是后期如果要改服务类别的名称只需要改字典表不用改业务表里的数据。我在实际项目里见过直接把中文写死在业务表里的表后面改名的时候所有 SQL 都要带上新名称非常痛苦。收费表叫 charge_record它并不直接存“应该收多少钱”而是存储 service_order_id、unit_price、amount、charge_status。金额计算发生在创建工单的时机而不是在收费确认的时机。这样做能保证统计报表的金额始终以服务完成时的价格为准不受后续调价影响。这套表设计谈不上精巧但胜在规整。外键没有写在数据库里全部靠代码逻辑维护关联关系避免线上误删。如果你打算二次开发建议沿用这个习惯不要把物理外键加上否则删档案时会连带冻结一堆关联数据。3. 本地跑起来环境准备、配置与启动流程3.1 环境准备清单与版本匹配这个项目我用本地环境跑过一次先说结论按照它的 README 指引正常情况下半小时内能启动。但前提是环境匹配这里列出我验证过的版本组合。JDK 要用 1.8不要用 11 或 17。虽然 Spring Boot 2.x 支持高版本 JDK但这个项目的 Maven 编译配置里没有显式指定 release 版本我一开始用 JDK 17 编译结果报了几处反射调用的警告虽然不影响运行但日志里全是红色告警后面换回 JDK 8 就干净了。MySQL 用 5.7 或 8.0 均可但要留意时区配置。如果是 MySQL 8.0连接串里要带 serverTimezoneAsia/Shanghai否则插入时间字段会报错。我用的是 8.0.28连接串加了参数后一切正常。Maven 用 3.6 以上即可依赖下载时如果卡在某个包去阿里云私服镜像找不要死等中央仓库。项目里没有引入特别偏门的依赖镜像切换后三分钟内能拉完。前端资源不需要单独安装Thymeleaf 模板和静态资源都在 src/main/resources 下打 jar 包时会一并打包。3.2 配置数据源与初始化数据库拿到源码后不需要急着改代码先把数据库建好。用 Navicat 或命令行执行 sql 目录下的脚本mysql -u root -p sql/neighborhood_care.sql脚本执行后会自动创建数据库和全部表同时写入基础数据包括一个管理员账号和几个演示用的老人、护工记录。管理员账号的用户名是 admin密码是 123456不过第一次登录后建议立刻改掉。接着改 application.yml 里的数据源配置。默认配置是spring: datasource: url: jdbc:mysql://localhost:3306/neighborhood_care?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver这里把 password 改成你自己的 MySQL 密码其他保持不动。driver-class-name 用的是 mysql-connector-java 8.x 的写法如果你的本地是 MySQL 5.7这个驱动也兼容不需要换。还有一个容易忽略的配置mybatis-plus: mapper-locations: classpath*:mapper/*.xml type-aliases-package: com.example.neighborhood.entity configuration: map-underscore-to-camel-case: truemap-underscore-to-camel-case 打开后数据库里的 elder_id 会自动映射到 Java 实体里的 elderId省去一大堆 TableField 注解。如果这个配置没打开你会在运行时报字段找不到的错原因就是驼峰和下划线没对应上。3.3 启动项目并完成第一次登录在项目根目录执行 Maven 命令启动mvn spring-boot:run或者先用 package 打包再跑 jarmvn clean package -DskipTests java -jar target/neighborhood-care-1.0.jar启动日志里看到 “Started Application in xx seconds” 字样后打开浏览器访问 http://localhost:8080/login进入登录页。输入 admin 和 123456如果跳转到控制台首页说明这套系统的核心链路已经通了。控制台首页上有几个指标卡片老人总数、本月工单数、待处理工单数、本月收费金额。这些数字来自首页的统计数据接口如果数据库脚本正确这些数字不会是零而是演示数据里预置的值。我建议你在这里先花十分钟点一遍菜单老人档案、健康记录、工单管理、护工排班、收费统计。通过页面操作了解每个功能的前置条件比如创建工单时必须选一个老人和护工而排班必须在一个已验证的护工账号下添加。顺着页面把每条链路点一遍后面改代码时心里才有底。4. 二次开发要点改数据库、加字段、调权限的三个切入点4.1 给老人表增加“亲属联系电话”字段的完整流程实际使用中最常遇到的需求就是把老人亲属的联系方式更细化。我在拆这个项目时就试着加了一个“亲属联系电话”字段这里把流程记录下来。第一步修改数据库表。用 SQL 给 elder_info 表增加字段ALTER TABLE elder_info ADD COLUMN relative_phone VARCHAR(20) DEFAULT NULL COMMENT 亲属联系电话;第二步修改实体类 ElderInfo。在 Java 类里增加属性private String relativePhone;因为开启了驼峰映射这个属性会自动映射到表中的 relative_phone 列不需要额外写注解。第三步修改前端模板。在老人新增/编辑表单里加一个输入框注意表单项的 name 要和实体属性一致div classform-group label forrelativePhone亲属联系电话/label input typetext classform-control idrelativePhone namerelativePhone / /div第四步在列表页展示该字段。老人列表使用的是 Thymeleaf 的 th:each 循环可以直接在对应列加上td th:text${elder.relativePhone}未填写/td这一步没有改任何 SQL 和 Mapper XML因为列表查询是 MyBatis-Plus 的 selectList 自动生成的只查询实体映射的字段。如果你加了字段但列表里不展示通常只是模板的问题不需要动后端接口。这套流程的关键在于 MyBatis-Plus 的通用方法会自动感知实体类的变化只要你写了属性并保证驼峰映射打开增删改查全部自动带上新字段。这是它比原生 MyBatis 省事的地方。4.2 权限控制与账号扩展逻辑项目里默认只有管理员和护工两种角色。权限拦截是通过 Spring MVC 的 HandlerInterceptor 实现的登录拦截器在 interceptor 包下。先看它的接口路径匹配模式registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /css/**, /js/**, /images/**);这里把静态资源排除了所以页面能正常加载。如果你想新增一个“村医”角色既要能查看健康数据又不能操作收费模块做法是新增角色表字段或者在 interceptor 里加角色判断。我一般会这样做在 Session 中保存登录对象的角色 code然后在 LoginInterceptor 的 preHandle 方法里检查是否已登录再在具体 Controller 的方法上加自定义注解例如 RequireRole(doctor)。这里给出一个简化样例Component public class RoleInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod method (HandlerMethod) handler; RequireRole roleAnnotation method.getMethodAnnotation(RequireRole.class); if (roleAnnotation ! null) { Object currentUser request.getSession().getAttribute(currentUser); if (currentUser null) { response.sendRedirect(/login); return false; } String roleCode (String) request.getSession().getAttribute(roleCode); if (!roleAnnotation.value().equals(roleCode)) { response.setStatus(403); return false; } } } return true; } }这段代码的核心逻辑是先判断处理方法上是否标注了 RequireRole如果标注了就取出 Session 里的角色编码与注解值比对不一致则返回 403。这种方式比在业务代码里写一堆 if 判断要清爽也便于后续扩展更多角色。实际改造时你还需要在登录逻辑里把角色编码塞进 Session并注册这个拦截器到 WebMvcConfigurer。4.3 把分页查询改成多条件组合查询项目里很多列表页默认只支持按名字模糊查询比如老人列表。但真实业务中你可能需要按村、按照护等级、按独居状态组合筛选。这个改造点在 Mapper 的 XML 文件里。找到 ElderInfoMapper.xml里面有这样一段动态查询select idselectElderPage resultTypecom.example.neighborhood.entity.ElderInfo SELECT * FROM elder_info where if testelderName ! null and elderName ! AND elder_name LIKE CONCAT(%, #{elderName}, %) /if if testvillage ! null and village ! AND village #{village} /if if testcareLevel ! null and careLevel ! AND care_level #{careLevel} /if /where ORDER BY elder_id DESC /select这段 SQL 里 标签会自动去掉多余的 AND避免出现 WHERE AND 这种语法错误。三个 if 条件分别对应老人姓名、所属村、照护等级。如果前端传入的 village 为空MyBatis 会渲染空条件不会干扰查询结果。我把这个查询方法对应的 Service 层代码也看一下public PageResultElderInfo queryElderPage(int page, int size, String elderName, String village, String careLevel) { PageElderInfo pageParam new Page(page, size); QueryWrapperElderInfo wrapper new QueryWrapper(); wrapper.like(StringUtils.hasText(elderName), elder_name, elderName); wrapper.eq(StringUtils.hasText(village), village, village); wrapper.eq(StringUtils.hasText(careLevel), care_level, careLevel); wrapper.orderByDesc(elder_id); return new PageResult(page, size, elderInfoMapper.selectPage(pageParam, wrapper)); }这里用了 MyBatis-Plus 的 QueryWrapper条件构造方法带有布尔参数false 时不拼接该条件。这种方式能避免写一堆嵌套 if-else。如果你更喜欢把 SQL 写在 XML 里完全可以忽略这段写法两种方式在这个项目里都能用关键在于别混用免得后续维护时逻辑不清晰。5. 常见坑与排查部署和联调中踩过的四个问题5.1 登录后页面样式全丢控制台报 404现象输对账号密码能跳转到控制台但页面上的 CSS、JS 全部加载不出来浏览器的 Network 面板里静态资源请求全部 404。原因项目里的静态资源访问路径带了一个 context-path。在 application.yml 中有这样一行server: servlet: context-path: /care如果这个配置打开了那么所有页面的静态资源路径就变成了 /care/css/xxx.css但 Thymeleaf 模板里写死的路径是 /css/xxx.css没有自动加前缀。登录页因为在 excludePathPatterns 里包含了 /css/**实际上还是原始路径但跳转后的页面却走了带前缀的路径。解决有两种处理方式。一是把模板里的静态资源引用改成带前缀的写法例如link th:href{/care/css/bootstrap.min.css} relstylesheet /二是干脆去掉 context-path 配置保持默认根路径简单省事。我实际选择的是去掉 context-path因为部署时用 Nginx 转发到根路径更顺手不需要额外处理静态资源。5.2 工单列表分页数据重复每页都出现同一批记录现象管理员在工单管理页点击分页发现第二页和第一页的数据有重叠有些记录反复出现。原因这个查工单列表的 SQL 里没有排序条件MyBatis-Plus 的 selectPage 在底层生成 ORDER BY 时使用了默认主键排序但主键不是严格单调递增时MySQL 的优化器会采用不同的索引路径导致相同记录在不同请求间出现错位。解决在工单查询的 QueryWrapper 或者 XML SQL 中必须显式加上排序字段。我整改后的一致写法是wrapper.orderByDesc(create_time);同时在 create_time 字段上加普通索引。这样分页的数据顺序是稳定的翻页不会出现乱序。注意排序字段必须是唯一性足够高的字段不建议只用 id 因为自增 id 已经被删过的记录补丁序列顺序不连续会导致优化器选择不可预知。5.3 上传护工头像后图片无法显示现象在护工管理页上传头像接口返回成功数据库里也存了路径但页面上 img 标签的地址始终 404。原因项目把上传文件保存到了本地的临时目录但重启应用后临时目录被清理此外静态资源映射没有把上传目录映射为可访问路径。代码里往往是这样写的uploadPath System.getProperty(java.io.tmpdir) /upload/;这样存储路径不稳定且默认静态资源映射不包含这个目录。解决在 application.yml 里配置自定义的本地存储路径并静态映射到 URL 路径。例如app: upload-dir: /data/care/upload spring: web: resources: static-locations: file:${app.upload-dir},classpath:/static/然后使用 WebMvcConfigurer 添加 resource handlerOverride public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadDir /); }这样图片路径固定为 /upload/xxx.jpg且重启后依然可访问。同时建议把上传目录放到项目部署路径之外避免打包时被覆盖。5.4 导出 Excel 时中文文件名乱码现象点击导出工单报表下载下来的文件里中文表名和 Sheet 名乱码部分 Excel 软件打开后内容正常但文件名乱码。原因后端设置下载响应头的编码方式不正确。典型的错误写法是直接用response.setHeader(Content-Disposition, attachment; filename fileName);这里没有把 URLEncoder 编码转成 RFC 5987 的格式。浏览器解析文件名时需要特殊处理。解决我用的方案是文件名用 URLEncoder 编码后拼接文件名*示例代码如下String encodedFileName URLEncoder.encode(fileName, UTF-8).replace(, %20); response.setHeader(Content-Disposition, attachment; filename*UTF-8 encodedFileName);同时注意导出内容的编码也要通过 response.setCharacterEncoding(UTF-8) 设置。这个坑不复杂但几乎每个包含导出功能的项目都会踩一次建议直接套用这段写法。6. 验证与进阶用接口用例验收再扩展报表与地图能力在把这个系统交付或继续开发之前我习惯做一轮接口层面的验证。不要仅盯着页面点来点去而是用 Postman 或 curl 直接调后端接口确认关键数据链路是通的。这套系统的接口路径没有封装成一个独立模块但顺着 Controller 层的 RequestMapping 梳理能整理出几个核心接口。第一个必验的是登录接口。POST /login 携带用户名密码成功后会在 Session 中写入登录态并返回一个重定向地址。如果你后续要开发移动端或小程序需要把登录改成返回 Token 的方式这里更推荐在现有代码基础上加一个 JWT 工具类不多动原有逻辑。第二个必验的是工单创建接口。POST /order/create 携带 elderId、workerId、serviceType、serviceDate成功后返回 orderNo。我写了一个简单的验证脚本curl -X POST http://localhost:8080/order/create \ -H Content-Type: application/x-www-form-urlencoded \ -d elderId3workerId1serviceType2serviceDate2025-02-01durationMinutes60返回 JSON 中带有 orderNo 且 status 为 200说明工单链路正常。这时候你再去 MySQL 里查 service_order 表能看到金额已经按服务时长计算并写入 amount 字段。这就是我之前提到的“创建工单即计算金额”的设计。进阶功能方面我推荐两个方向。第一个是导出报表的增强项目已有的 Excel 导出是基于简单列表你可以升级为带汇总行的统计表在底部加一列合计金额。这是实际使用中反馈最强烈的需求。第二个是地图展示在老人档案中维护经纬度字段后接入 Leaflet 或高德地图 JS API在控制台页面上渲染老人分布标记点方便管理者直观了解各村的老人数量。这不是一个复杂的开发任务但价值感很强值得优先做。另外验证系统健壮性时建议检查一下定时任务。项目里用 Scheduled 注解实现了每天清晨对健康异常数据的汇总。这个任务默认是开启的你在本地启动时会看到日志里输出“health check task executed”。如果你不希望测试环境也跑这个任务可以在 application.yml 中通过配置项控制app: scheduler: enabled: false在代码里使用 ConditionalOnProperty 注解就能实现开关控制这样不会影响生产环境的调度。我从这个资源里最深的体会是一个合格的管理系统核心价值不在于用了多前沿的框架而在于业务闭环是否完整。这套乡村养老系统把档案、健康、工单、收费、统计串成一条线每个环节都有数据落库没有断档这正是很多课程设计里最欠缺的部分。从那以后我每次接手类似系统都强制先走一遍完整的业务链路再决定改什么而不是一上来就对着代码猜。希望这份拆解能帮你在复现和改造这个项目时少走一些弯路。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑