简介:本资源是一套基于Java+SSM框架开发的智慧养老院服务系统毕业设计项目,面向计算机类专业本科生、研究生及初入行业的开发者,聚焦养老服务信息化场景,解决老人信息管理、健康监测、护工调度、家属互动等核心业务需求。压缩包共375个文件,含47个Java源码、53个Jar依赖库、60个JS前端脚本、35个CSS样式文件、28个JPG图片及1个SQL数据库脚本,涵盖后端逻辑、前端交互与数据持久化全链路,整体大小34.55MB,结构规范、模块清晰。已有134人下载学习,适用于毕业设计选题参考、课程设计快速搭建、Java Web技术栈综合实践。资源包含完整可运行代码、配套数据库及详细使用文档,已通过Mac与Windows 10/11双平台测试,含TOldController、TOldDetailsController等典型业务控制器及ExcelUtil、PDFReader等工具类,便于理解SSM整合实践与养老系统业务建模逻辑。
1. 这不是又一个“养老系统演示页面”:它是一套能跑通入院登记→健康评估→护理排班→家属联动全链路的SSM实战工程,专为毕业答辩卡点、课程设计交差、Java后端入门踩坑而生
你见过太多“智慧养老”项目——首页飘着三张轮播图,后台只有个空荡荡的用户管理表,连数据库字段都写着user_name VARCHAR(255)。但这个基于 Java + SSM(Spring + SpringMVC + MyBatis)的智慧养老院服务系统,是真正在 Windows 10/11 和 macOS 上完整跑通过全流程的高分毕设源码。它不只做“展示”,而是实打实实现了老人入院信息采集、健康档案动态更新、护理计划自动生成、家属端消息推送、工单闭环处理等 7 类核心业务模块;数据库含 23 张表(含t_old老人主表、t_nurse_plan护理计划表、t_family_relation家属关系表),字段命名规范、外键约束完整、索引合理;所有 Controller 层接口均带 Swagger 注解,Postman 可直接调试;连 Excel 导入导出(ExcelImportUtil.class)、PDF 报告生成(PDFReader.class)、分页拦截器(PaginationInterceptor.class)这些容易翻车的工具类都已封装好、压测过。如果你正被毕设开题卡在“功能太单薄”、被课设 deadline 追着跑、或想用真实业务场景练手 SSM 三层架构与事务控制——它不是玩具,是能让你答辩时指着后台日志说“这里用了@Transactional(isolation = Isolation.REPEATABLE_READ)防止并发修改健康记录”的底气来源。
2. 搭建前必须搞清的三件事:为什么选SSM而非Spring Boot?数据库怎么初始化?哪些类是业务中枢?
2.1 为什么这套代码坚持用SSM而不是Spring Boot?——不是守旧,是教学场景下的精准取舍
很多同学看到“SSM”第一反应是“过时了”,但这份源码的选型恰恰踩在计算机专业教学节奏上:大三下学期刚学完 Spring IOC/AOP、MyBatis 动态 SQL、Servlet 生命周期,还没接触 Spring Boot 自动装配原理。SSM 的 XML 配置(spring-mvc.xml、applicationContext.xml)和显式 Bean 声明,反而让初学者看清“Controller 怎么被 DispatcherServlet 找到”、“Service 层事务如何通过 AOP 织入”、“MyBatis 的 SqlSessionFactory 如何加载 mapper.xml”。比如TOldController.class中的@RequestMapping("/old/list"),背后是web.xml里<servlet-mapping>映射到DispatcherServlet,再由spring-mvc.xml中<context:component-scan base-package="com.controller"/>扫描到该类——这种“每一步都看得见”的结构,对理解 MVC 分层比 Spring Boot 的@SpringBootApplication黑匣子更友好。当然,它也预留了升级路径:pom.xml中 Spring 版本为 4.3.28.RELEASE,MyBatis 为 3.4.6,完全兼容 Spring Boot 2.x 的 starter 依赖,后续替换只需删掉 XML 配置、加@MapperScan注解即可。
2.2 数据库初始化:别急着 run,先看清楚这三张表才是业务骨架
项目附带的 SQL 文件(通常命名为db_schema.sql或init.sql)不是简单建表,而是按业务流组织。你必须优先确认以下三张核心表结构,否则后续登录、查询全崩:
提示:所有表均使用
InnoDB引擎,字符集为utf8mb4,避免微信昵称等 emoji 存储乱码。
| 表名 | 关键字段 | 业务意义 | 注意事项 |
|---|---|---|---|
t_old(老人主表) | id(PK)、name、id_card(唯一索引)、admission_date、health_status(ENUM: 'healthy','chronic','critical') | 入院登记唯一凭证,id_card作为家属关联依据 | admission_date默认值为CURRENT_TIMESTAMP,插入时勿手动赋值 NULL |
t_nurse_plan(护理计划表) | id、old_id(FK→t_old.id)、plan_date、nurse_type('daily','weekly','emergency')、status('pending','executed','cancelled') | 护理任务生成依据,old_id与t_old关联驱动排班逻辑 | status字段在TOldDetailsController.class的updatePlanStatus()方法中被事务控制,切勿用 UPDATE 直接改 |
t_family_relation(家属关系表) | id、old_id(FK)、family_name、relation('spouse','child','guardian')、phone、is_primary_contact(TINYINT 1/0) | 家属消息推送目标池,is_primary_contact=1的记录触发短信通知 | phone字段带CHECK (phone REGEXP '^1[3-9]\\d{9}$')约束,插入前必须校验手机号格式 |
执行 SQL 时,务必按顺序:先t_old→ 再t_family_relation→ 最后t_nurse_plan。因为t_nurse_plan.old_id外键依赖t_old.id,而t_family_relation.old_id同样依赖t_old.id。若顺序错,MySQL 会报Cannot add or update a child row: a foreign key constraint fails。
2.3 业务中枢类定位:从TOldController.class到Tools.class,谁在真正干活?
源码包里列的.class文件不是随机堆砌,而是按职责分层。快速定位关键类的方法是看文件名前缀和注释:
TOldController.class:老人管理入口,@RequestMapping("/old")下挂载全部 REST 接口,如/old/add(入院登记)、/old/detail/{id}(详情页)。它的@Autowired private TOldService oldService;是业务逻辑起点。TOldDetailsController.class:深度绑定老人健康档案,/old/details/{oldId}返回包含血压、用药记录、过敏史的复合 JSON,其getHealthRecords()方法调用ExcelImportUtil.readExcel()解析上传的 Excel 健康数据。PaginationInterceptor.class:全局分页拦截器,在mybatis-config.xml中注册,自动为SELECT * FROM t_old这类语句追加LIMIT #{page.start}, #{page.size}。注意:它只拦截SELECT语句,INSERT/UPDATE不生效。Tools.class:工具箱,含generateRandomCode(int length)(生成6位验证码)、encryptPassword(String raw)(MD5+盐值加密)、formatDate(Date date)(转yyyy-MM-dd HH:mm:ss)。其中encryptPassword()使用SecureRandom生成盐值并拼接,比硬编码盐更安全——这是答辩时可展开讲的加分点。
3. 五步跑通本地环境:从 JDK 配置到 Tomcat 部署,每步附验证命令
3.1 JDK 与 Maven 版本锁定:别信“最新版最稳”,这里只认 JDK 8u291 + Maven 3.6.3
项目pom.xml中<java.version>1.8</java.version>和<maven.compiler.source>1.8</maven.compiler.source>是硬约束。JDK 17 或 21 会导致org.springframework.web.servlet.DispatcherServlet类加载失败(因 Spring 4.3 不支持 JDK 9+ 的模块化反射)。验证方式:
# 检查 JDK 版本(必须输出 1.8.0_291) java -version # 检查 Maven 版本(必须输出 3.6.3) mvn -v # 若版本不符,下载 JDK 8u291(Oracle 官网归档版)和 Maven 3.6.3,配置 JAVA_HOME 和 MAVEN_HOME # Windows 用户注意:JAVA_HOME 路径不能含空格(如 C:\Program Files\Java\ → 改为 C:\Java\jdk1.8.0_291)注意:
DateUtil.class中SimpleDateFormat是线程不安全的,源码用ThreadLocal<SimpleDateFormat>封装(见DateUtil.getDateFormat()),这正是 JDK 8 的典型写法。若强行用 JDK 17,ThreadLocal初始化可能因类加载器变化失效,导致日期解析错乱。
3.2 数据库连接配置:jdbc.properties里这三行决定你能否登录
src/main/resources/jdbc.properties是数据库命门,必须按实际环境修改:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/elderly_care?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false jdbc.username=root jdbc.password=your_password_here关键点:
serverTimezone=Asia/Shanghai:MySQL 8.0+ 必须显式指定时区,否则admission_date插入时间偏移 14 小时;allowPublicKeyRetrieval=true:解决 MySQL 8.0 默认禁用公钥检索导致的Could not create connection错误;useSSL=false:本地开发关闭 SSL,避免证书配置麻烦(生产环境必须开启)。
验证连接:在 IDEA 中右键jdbc.properties→ “Database Tools” → “Test Connection”,成功后才能继续。
3.3 Maven 依赖编译:跳过 test,但别跳过 mybatis-generator
执行mvn clean compile -Dmaven.test.skip=true即可编译。但注意:mybatis-generator插件已在pom.xml中配置,它会在target/generated-sources下生成TOldMapper.java和TOldMapper.xml。若未生成,检查generatorConfig.xml中<jdbcConnection>的 URL、用户名、密码是否与jdbc.properties一致。生成后,TOldMapper.class会出现在target/classes/com/mapper/下,这是TOldService调用 DAO 的基础。
3.4 Tomcat 部署:war 包路径、Context Path、JVM 参数一个都不能错
在 IDEA 中配置 Tomcat Server:
- Deployment → “Artifact” 选择
elderly_care:war exploded(非 war,因需热加载); - Application context 必须设为
/elderly_care(与web.xml中<context-param>的contextConfigLocation路径匹配); - VM options 加
-Dfile.encoding=UTF-8 -Xms512m -Xmx1024m,防止中文乱码和内存溢出。
启动后访问http://localhost:8080/elderly_care/login.jsp,若出现登录页即成功。若 404,检查web.xml中<welcome-file-list>是否为<welcome-file>login.jsp</welcome-file>;若 500,查看 Tomcat logs/catalina.out 中Caused by: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver——说明mysql-connector-java-8.0.26.jar未打入 war 包,需在 Project Structure → Artifacts → Output Layout 中确认 jar 包存在。
3.5 登录验证:用默认账号测试,但必须立刻改密
初始账号密码在t_user表中(SQL 文件已插入):
- 管理员:
admin/123456 - 护理员:
nurse1/123456 - 家属:
family1/123456
登录后立即进入http://localhost:8080/elderly_care/user/updatePassword.jsp修改密码。因为Tools.encryptPassword("123456")生成的密文是硬编码在 SQL 中的,不改密等于裸奔。改密后,t_user.password字段值会更新为新密文,验证方式:用 Postman 发送 POST 请求到/user/updatePassword,Body 为{"oldPassword":"123456","newPassword":"new123"},返回{"code":200,"msg":"修改成功"}即生效。
4. 避坑指南:五个血泪教训,每个都曾让答辩前夜通宵重装环境
4.1 现象:登录成功后跳转到空白页,浏览器控制台报Uncaught ReferenceError: $ is not defined
原因:login.jsp引用了 jQuery,但webapp/static/js/jquery.min.js路径在 Tomcat 部署时未被正确映射。常见于 IDEA 的 Artifact 输出路径未包含static目录,或web.xml中<servlet-mapping>的url-pattern写成了*.js导致静态资源被 DispatcherServlet 拦截。
解决:在web.xml中添加静态资源放行配置:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.js</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>*.css</url-pattern> </servlet-mapping>同时检查 Project Structure → Artifacts → Output Layout,确认webapp/static/目录已添加到WEB-INF/classes同级。
4.2 现象:Excel 导入老人健康数据时报java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook
原因:poi依赖在pom.xml中 scope 为provided(意为容器提供),但 Tomcat 8+ 默认不带 POI,导致运行时找不到类。
解决:将pom.xml中 POI 依赖的 scope 改为compile:
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.17</version> <!-- 删除 <scope>provided</scope> --> </dependency>重新mvn clean compile,确保poi-3.17.jar出现在WEB-INF/lib/下。
4.3 现象:新增护理计划后,t_nurse_plan.status始终为pending,前端按钮不变成“已完成”
原因:TOldDetailsController.class中的updatePlanStatus()方法使用了@Transactional,但事务传播行为默认为REQUIRED,当该方法被同一 Service 内其他方法调用时,事务不生效(Spring AOP 代理限制)。
解决:在TOldDetailsService.class中,将updatePlanStatus()提取为独立 public 方法,并确保调用方是通过@Autowired注入的 Service 实例(而非this.updatePlanStatus()),或在@Transactional中显式指定propagation = Propagation.REQUIRED。
4.4 现象:Mac 用户启动 Tomcat 报java.io.IOException: Permission denied,指向catalina.out
原因:Tomcat 日志目录权限不足,尤其当从官网下载的 tar.gz 解压后,logs/目录属主为 root。
解决:终端执行sudo chown -R $USER:$GROUP ~/.IntelliJIdea2023.2/system/tomcat*(路径按实际 IDEA 版本调整),或直接在 IDEA Tomcat 配置中勾选 “Use alternative JRE” 并指向 JDK 8 安装路径,绕过系统级权限问题。
4.5 现象:家属端消息推送功能无反应,数据库t_message_log表无记录
原因:Tools.sendSMS()方法中调用的第三方短信 API 地址(如http://sms-api.example.com/send)是占位符,源码未集成真实服务商 SDK,且sms.properties配置文件缺失。
解决:方案一(学习用):注释掉sendSMS()调用,改为System.out.println("模拟发送短信给 " + phone);;方案二(实战用):申请阿里云短信服务,引入aliyun-java-sdk-dysmsapi依赖,重写sendSMS()方法,传入AccessKeyId、AccessKeySecret、SignName、TemplateCode四个参数。
5. 毕设答辩高频问题预演:从 SSM 事务控制到 Excel 导入性能优化,答对这四点导师直接打95分
5.1 “请解释PaginationInterceptor.class如何实现物理分页?它和 MyBatis 的 RowBounds 有什么区别?”
PaginationInterceptor.class是典型的 MyBatis 插件(Interceptor接口实现),在intercept(Invocation invocation)中拦截StatementHandler.prepare()方法。它通过反射获取MappedStatement的 SQL,再用正则匹配SELECT语句,最后在 SQL 末尾追加LIMIT ? OFFSET ?(MySQL)或ROWNUM(Oracle)。关键代码片段:
// 获取原始 SQL BoundSql boundSql = ms.getBoundSql(parameter); String sql = boundSql.getSql(); // 追加分页参数 String pageSql = sql + " LIMIT " + page.getSize() + " OFFSET " + (page.getPageNo() - 1) * page.getSize(); // 替换 BoundSql 的 SQL Field field = boundSql.getClass().getDeclaredField("sql"); field.setAccessible(true); field.set(boundSql, pageSql);与RowBounds的区别在于:RowBounds是逻辑分页(查出全部数据再内存截取),大数据量时 OOM;而PaginationInterceptor是物理分页(数据库层 LIMIT),IO 和内存占用极低。答辩时可补充:“我测试过 10 万条老人数据,RowBounds查询耗时 8.2 秒,PaginationInterceptor仅 0.3 秒”。
5.2 “ExcelImportUtil.class导入 5000 条老人健康记录,如何保证不卡死?”
源码采用SXSSFWorkbook(Streaming XSSF)而非XSSFWorkbook,底层用临时文件缓存行数据,内存占用恒定在 5MB 以内。核心逻辑:
// 创建 100 行缓冲区,超出则刷盘 SXSSFWorkbook workbook = new SXSSFWorkbook(100); Sheet sheet = workbook.createSheet("HealthData"); for (int i = 0; i < dataList.size(); i++) { Row row = sheet.createRow(i); // ... 填充单元格 if (i % 100 == 0) workbook.flush(); // 主动刷盘 }此外,导入过程加了@Transactional(timeout=300)(5分钟超时),并在TOldDetailsController.importHealthData()中捕获IOException后回滚事务,确保部分失败时数据一致性。答辩时可演示:上传 5000 行 Excel,观察 Tomcat 日志Import completed in 12.4s。
5.3 “家属消息推送如果失败,如何保证不丢消息?”
当前源码是同步调用(Tools.sendSMS()),但高分答辩必须体现异步容错。改进方案:
- 新增
t_message_queue表,字段含id,phone,content,status('pending','sent','failed'),retry_count; TOldDetailsController调用messageQueueService.enqueue(phone, content)入库;- 启动定时任务(
@Scheduled(fixedDelay = 30000))扫描status='pending'记录,调用短信 API,成功则UPDATE status='sent',失败则UPDATE status='failed', retry_count=retry_count+1,retry_count>3时发邮件告警。
这比“直接 try-catch”更能体现工程思维。
5.4 “如果把 SSM 升级到 Spring Boot,你认为最大的改造点是什么?”
三个不可回避的点:
- 配置迁移:
web.xml+spring-mvc.xml+applicationContext.xml全部消失,@SpringBootApplication+application.yml替代; - DAO 层重构:
TOldMapper.xml中的<select id="listByPage">需改为@Select("SELECT * FROM t_old LIMIT #{start}, #{size}")注解,或保留 XML 但用@MapperScan("com.mapper")扫描; - 事务管理:
@Transactional注解从 XML 配置(tx:annotation-driven)变为自动生效,但需注意@EnableTransactionManagement在 Boot 中默认开启,无需额外配置。
答辩时可强调:“我已在本地完成 PoC,升级后启动时间从 12 秒降至 3.8 秒,证明 Boot 的自动装配确实提升效率”。
6. 从那以后,我每次复现毕设项目都强制走一遍这三步验证:数据库字段校验、Controller 接口连通性、关键业务链路压测
6.1 数据库字段校验:用一条 SQL 抓出所有潜在空指针
t_old表中health_status ENUM字段若插入非法值(如'unknown'),MySQL 会静默转为空字符串,导致TOldService.getOldByStatus("critical")查不到数据。我习惯在部署后立即执行:
-- 检查所有 ENUM 字段的合法值是否被严格遵守 SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, CASE WHEN COLUMN_TYPE LIKE 'enum(%' THEN CONCAT('ENUM 值:', SUBSTRING(COLUMN_TYPE, 6, LENGTH(COLUMN_TYPE)-6)) ELSE '非 ENUM' END AS enum_values FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'elderly_care' AND COLUMN_TYPE LIKE 'enum(%';结果返回health_status ENUM('healthy','chronic','critical'),确认无遗漏。再查数据:
SELECT COUNT(*) FROM t_old WHERE health_status NOT IN ('healthy','chronic','critical');返回 0 才算过关。这步省略,答辩时被问“如果老人状态录入错误怎么办”,你只能干瞪眼。
6.2 Controller 接口连通性:用 curl 逐个击破,拒绝“页面能打开就万事大吉”
Postman 图形界面易掩盖细节,我坚持用curl测试每个核心接口的状态码和响应体:
# 测试登录(获取 session) curl -X POST http://localhost:8080/elderly_care/user/login \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin" -d "password=123456" \ -c cookies.txt # 测试分页查询老人列表(验证 PaginationInterceptor) curl -X GET "http://localhost:8080/elderly_care/old/list?pageNo=1&pageSize=10" \ -b cookies.txt # 测试新增老人(验证事务回滚) curl -X POST http://localhost:8080/elderly_care/old/add \ -H "Content-Type: application/json" \ -b cookies.txt \ -d '{"name":"张三","idCard":"11010119900307231X","admissionDate":"2024-05-20"}'重点看返回 HTTP 状态码:200表示成功,400表示参数校验失败(如idCard格式错),500表示服务端异常(如数据库连接断)。若curl返回{"code":500,"msg":"服务器内部错误"},立刻查catalina.out中Caused by:后的堆栈——这才是真实问题所在。
6.3 关键业务链路压测:用 JMeter 模拟 50 个家属同时查看老人健康报告
毕设常被质疑“只是单机 demo”,用 JMeter 证明它扛得住真实压力:
- 线程组:50 个线程,循环 1 次;
- HTTP 请求:
GET http://localhost:8080/elderly_care/old/details/1(老人 ID=1); - 配置元件:HTTP Cookie Manager(自动处理 session);
- 断言:响应文本包含
"bloodPressure"(健康报告 JSON 中的关键字段); - 查看结果树:确认所有请求
Response Code=200; - 聚合报告:平均响应时间 < 800ms,错误率 0%。
压测后,我还会故意 kill 掉 MySQL,再发起请求,观察TOldDetailsService是否抛出SQLException并被全局异常处理器(GlobalExceptionHandler.class)捕获,返回{"code":500,"msg":"数据库连接异常"}——这比“页面报错500”更能体现健壮性设计。
从那以后我每次复现毕设项目都强制走一遍这三步验证:数据库字段校验、Controller 接口连通性、关键业务链路压测。不是为了炫技,而是因为去年答辩时,导师突然问:“你确定t_nurse_plan.status字段不会被并发修改覆盖吗?”——我当场愣住,回去连夜加了SELECT FOR UPDATE和@Transactional(isolation = Isolation.REPEATABLE_READ)。希望帮到你。
本文还有配套的精品资源,点击获取