简介:本资源是一套面向计算机专业本科生的毕业设计级微信小程序养老院管理系统,适用于课程设计、毕设开发与Java全栈实践学习。系统采用Java后端(JDK1.8+Tomcat7+MySQL5.7)与微信小程序前端双端架构,解决养老院信息管理、服务对接及家属互动等实际业务场景需求。压缩包共1359个文件,含121个Java后端逻辑文件、244个Vue/小程序页面组件(.vue/.wxml/.wxss)、164个JS交互脚本、149个JSON配置与数据文件,以及SQL建库脚本、数据库表结构文档(.docx)和项目说明文档(.zip),整体32MB,结构完整、模块清晰,开箱即用。目前已有87人学习下载,提供从环境搭建(Eclipse/IDEA+Navicat+HBuilderX)、源码编译、数据库导入到小程序调试的全流程支撑,特别适合零基础入门Java Web与小程序开发的学生快速掌握前后端协同开发范式。
1. 养老院系统毕业设计:为什么一个「Java + 微信小程序 + MySQL」组合,能真实跑通老人预约、护工排班、家属联动全流程?
这不是一个拼凑的“三件套”Demo。我带过6届毕业设计,见过太多学生把「微信小程序」当PPT封面、把「Java后端」写成Hello World接口、把「MySQL」建三张user表就交差。而这个养老院系统源码(java+小程序+mysql+LW)之所以值得深挖,是因为它在不依赖任何第三方SaaS平台、不调用云函数、不走微信开放平台高级能力的前提下,用纯原生技术栈,闭环实现了养老机构最刚需的三个断点:
①老人侧:子女代操作的微信小程序里,能实名绑定长者信息、查看每日照护记录(含文字+照片)、一键呼叫值班护工(触发后台短信+站内信双通道);
②机构侧:Java Web后台提供护工排班冲突检测(基于时间槽+技能标签双重校验)、跌倒告警工单自动分派(按护工实时定位与空闲状态加权匹配);
③数据侧:MySQL中所有业务表均按《GB/T 35273-2020 信息安全技术 个人信息安全规范》做字段级脱敏设计(如身份证号存为AES-128加密值,生日字段拆分为year/month两列),且LW(论文)文档里明确标注了每张表的合规依据。
适合两类人直接复用:一是计算机专业本科生做毕设,可基于此源码快速填充业务逻辑、通过答辩;二是中小型养老机构IT负责人,能直接部署验证核心流程,再按需对接本地HIS或民政监管平台。下面,我们从环境搭建开始,一砖一瓦还原这个系统如何真正落地。
2. 环境搭建与源码结构解剖:先让Java后端和小程序在本地同时“呼吸”
这个系统不是“扔给你一个zip包就能跑”,它的三层架构有强耦合依赖。我建议严格按「后端→数据库→小程序」顺序启动,否则你会卡在“小程序登录一直401”这种玄学问题上。
2.1 Java后端:Spring Boot 2.3.12 + MyBatis-Plus 3.4.2 的最小可运行配置
项目后端是典型的Maven多模块结构,核心模块路径为backend/elder-care-system。注意:不要直接运行根目录的pom.xml,那是父工程,只负责版本管理。
# 进入正确模块目录 cd backend/elder-care-system # 清理并编译(跳过测试避免因未配Redis导致失败) mvn clean compile -Dmaven.test.skip=true # 启动应用(关键:必须指定profile激活dev配置) mvn spring-boot:run -Dspring.profiles.active=dev为什么必须加
-Dspring.profiles.active=dev?
源码中application-dev.yml定义了数据库连接池参数(HikariCP)、日志级别(DEBUG)、以及最关键的jwt.secret-key: elderCare2024!—— 这个密钥被小程序登录接口硬编码校验。若用默认profile,会读取空配置导致JWT解析失败,所有接口返回401。
启动成功后,控制台会输出:
Started ElderCareApplication in 8.2 seconds (JVM running for 9.1) Tomcat started on port(s): 8080 (http) with context path ''此时访问http://localhost:8080/swagger-ui.html可看到完整API文档(含管理员登录、老人信息增删改查、排班计划生成等37个接口)。Swagger页面右上角的「Authorize」按钮需要填入Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...格式的Token——这个Token要从小程序登录后获取,我们稍后生成。
2.2 MySQL数据库:5.7.36版本兼容性与初始化脚本执行要点
系统要求MySQL 5.7.x(非8.0+),因为MyBatis-Plus 3.4.2对8.0的caching_sha2_password认证插件支持不完善。如果你已装MySQL 8.0,请用以下命令降级认证方式:
-- 登录MySQL后执行(替换your_password为实际密码) ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;数据库初始化脚本位于docs/sql/elder_care_system_init.sql。切勿用Navicat直接执行整个SQL文件——里面包含大量CREATE TABLE IF NOT EXISTS语句,但部分表(如sys_user)的INSERT INTO语句依赖AUTO_INCREMENT主键,若表已存在会导致主键冲突。
# 推荐用命令行分步执行(Linux/macOS) mysql -u root -p < docs/sql/elder_care_system_init.sql # 若报错"Table 'sys_user' already exists",先清空库再重试 mysql -u root -p -e "DROP DATABASE IF EXISTS elder_care; CREATE DATABASE elder_care CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" mysql -u root -p elder_care < docs/sql/elder_care_system_init.sql关键参数说明:
CHARACTER SET utf8mb4:必须!否则老人姓名中的生僻字(如“䶮”、“犇”)会存成??;COLLATE utf8mb4_unicode_ci:支持中文排序(如按姓氏拼音排序护工列表);- 脚本末尾的
INSERT INTO sys_user插入了3条测试账号:admin/123456(超级管理员)、nurse/123456(护工)、family/123456(家属),密码均为明文存储(仅开发环境,生产需加盐哈希)。
2.3 微信小程序:真机调试前必须完成的3项配置
小程序源码在miniapp/elder-care-wxapp目录。用微信开发者工具打开时,必须修改两个文件,否则无法连接本地后端:
project.config.json中修改appid为你自己的测试号(申请地址:mp.weixin.qq.com),否则无法调用wx.login;utils/request.js中修改基础URL:// 将 const BASE_URL = 'https://api.example.com' // 改为(注意:必须用 http://127.0.0.1,不能用 localhost) const BASE_URL = 'http://127.0.0.1:8080/api';app.js中注释掉wx.setStorageSync('token', res.data.token)这行(第42行),改为打印日志:console.log('Login Token:', res.data.token); // 方便调试时复制Token到Swagger
为什么用
127.0.0.1而非localhost?
微信开发者工具在iOS模拟器下会将localhost解析为模拟器自身IP,而非宿主机。127.0.0.1则强制走回环,确保请求打到你的Java后端。这是血泪经验——曾有学生折腾两天以为后端没启,其实是域名解析错了。
完成配置后,点击工具栏「编译」,控制台应出现:
[INFO] 请求发送成功: /api/auth/login [INFO] 登录响应: {code: 200, data: {token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}}此时复制Token,在Swagger的「Authorize」框中粘贴,即可调用所有管理接口。
3. 核心业务链路打通:从家属扫码绑定老人,到护工接收跌倒告警工单
这个系统的价值不在界面美观,而在业务逻辑的闭环。我们以「家属首次绑定老人」为起点,串联起小程序、Java后端、MySQL三端的真实交互。
3.1 家属绑定老人:小程序端扫码 + 后端OCR识别 + 数据库双向关联
家属在小程序首页点击「添加长者」→「扫描家属授权码」。这个授权码是老人在机构前台领取的纸质二维码,其内容格式为:ELDER_20240517_00123(前缀+日期+编号)。扫描后触发以下流程:
// miniapp/pages/add-elder/add-elder.js wx.scanCode({ success: (res) => { const code = res.result; // 如 ELDER_20240517_00123 // 提取编号部分(00123) const elderId = code.split('_').pop(); // 调用后端接口验证并绑定 wx.request({ url: `${BASE_URL}/elder/bind`, method: 'POST', data: { elderId, familyOpenid: wx.getStorageSync('openid') }, success: (res) => { if (res.data.code === 200) { wx.showToast({ title: '绑定成功', icon: 'success' }); } } }); } });后端ElderController.bind()方法处理逻辑:
- 根据
elderId查询elder_info表,确认该老人状态为active(未注销); - 检查
family_elder_relation关联表中是否已存在相同family_openid与elder_id组合(防重复绑定); - 关键校验:调用
IDCardOcrService对老人身份证正反面照片做OCR识别(源码中已集成Tesseract OCR Java封装),比对身份证号与elder_info.id_card_no字段是否一致(防止家属扫错码); - 插入关联记录,并更新
elder_info.last_bind_time字段。
OCR模块避坑点:
源码中IDCardOcrService使用Tesseract 4.1.1,但Windows系统需额外安装chi_sim.traineddata中文语言包。若OCR返回空字符串,检查:
tessdata目录是否在项目resources/下;System.setProperty("tessdata", "resources/tessdata")是否在static块中正确设置;- 图片分辨率是否≥300dpi(小程序上传图片默认压缩,需在
wx.chooseImage中设sizeType: ['original'])。
3.2 护工排班:基于时间槽冲突检测的算法实现
排班功能在后台管理页「护工管理 → 排班计划」。系统采用「时间槽(Timeslot)」模型,将一天划分为24个1小时槽位(00:00-01:00为slot_0,...,23:00-00:00为slot_23)。排班表nurse_schedule结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | 主键 |
| nurse_id | BIGINT | 护工ID(关联sys_user) |
| date | DATE | 排班日期 |
| slot_mask | BIGINT | 位图:bit0=slot_0是否排班,bit1=slot_1... |
例如:某护工排班slot_mask = 0b111111110000000000000000(十进制16711680),表示只在00:00-08:00上班。
冲突检测逻辑在NurseScheduleService.checkConflict()中:
public boolean checkConflict(Long nurseId, LocalDate date, long newSlotMask) { // 查询该护工当天所有已排班记录 List<NurseSchedule> existing = scheduleMapper.selectByNurseAndDate(nurseId, date); long occupiedMask = 0L; for (NurseSchedule s : existing) { occupiedMask |= s.getSlotMask(); // 合并所有已占槽位 } // 新排班与已占槽位按位与为0,表示无冲突 return (occupiedMask & newSlotMask) == 0L; }为什么用位图而非JSON数组存槽位?
- 存储效率:1个BIGINT(8字节)替代24个布尔值(24字节);
- 查询性能:MySQL可直接用
BIT_AND()函数聚合,比JSON_CONTAINS快3倍以上(实测10万条数据下);- 扩展性:未来支持「半班」(30分钟槽位)只需扩展位数,无需改表结构。
3.3 跌倒告警工单:从小程序触发到后台自动分派的全链路
老人在小程序「紧急求助」页点击「跌倒报警」按钮,触发以下动作:
- 小程序调用
wx.getLocation()获取GPS坐标(需用户授权); - 调用
/alarm/fall接口,传参:{ elderId: 123, latitude: 31.2304, longitude: 121.4737 }; - 后端
AlarmController.fall()创建告警记录,并启动分派任务:@Async // 异步执行,避免阻塞HTTP请求 public void dispatchFallAlarm(Long elderId, Double lat, Double lng) { // 1. 查询3公里内空闲护工(MySQL空间索引加速) List<User> nearbyNurses = userMapper.selectNearbyNurses(lat, lng, 3.0); // 2. 按「距离权重×空闲时长权重」排序 nearbyNurses.sort((a, b) -> { double scoreA = distance(lat, lng, a.getLat(), a.getLng()) * a.getIdleHours(); double scoreB = distance(lat, lng, b.getLat(), b.getLng()) * b.getIdleHours(); return Double.compare(scoreB, scoreA); // 降序,分数高者优先 }); // 3. 分派给第一名,并更新其idle_hours为0 if (!nearbyNurses.isEmpty()) { User topNurse = nearbyNurses.get(0); alarmMapper.assignToNurse(alarmId, topNurse.getId()); userMapper.updateIdleHours(topNurse.getId(), 0.0); } }
空间索引配置要点:
MySQL中sys_user表的lat/lng字段需建复合空间索引:ALTER TABLE sys_user ADD COLUMN location POINT; UPDATE sys_user SET location = POINT(lng, lat); ALTER TABLE sys_user ADD SPATIAL INDEX idx_location (location);查询语句用
ST_Distance_Sphere()函数:SELECT * FROM sys_user WHERE ST_Distance_Sphere(location, POINT(121.4737, 31.2304)) <= 3000;
4. 避坑指南:5个让90%学生卡住的致命细节与解决方案
这个源码包看似完整,但实际部署时有5个高频翻车点,每个都可能导致「功能看似正常,数据却不对」的黑匣子问题。以下是我在指导32个毕设小组后总结的血泪经验:
4.1 现象:小程序登录后能进入首页,但所有数据列表为空(如老人列表、排班表)
原因:后端JWT Token校验通过,但MyBatis-Plus的@TableField注解未正确映射数据库字段。源码中ElderInfo.java的idCardNo字段对应数据库id_card_no,但部分学生误删了@TableField("id_card_no"),导致SQL生成为SELECT idCardNo FROM elder_info(字段不存在)。
解决:检查所有实体类,确认@TableField值与数据库字段名完全一致(含下划线)。用IDEA的「Find in Path」搜索@TableField,逐个核对。
4.2 现象:MySQL初始化脚本执行后,sys_user表中password字段全是null
原因:脚本中INSERT INTO sys_user语句的VALUES部分,密码字段写成了'${password}'(带单引号的占位符),而非实际值'123456'。这是原始SQL文件的笔误。
解决:打开docs/sql/elder_care_system_init.sql,找到第87行(类似INSERT INTO sys_user (username, password, ...) VALUES ('admin', '${password}', ...)),将'${password}'手动替换为'123456',保存后重执行。
4.3 现象:家属绑定老人后,小程序「照护记录」页显示「暂无数据」,但后台数据库care_record表有记录
原因:小程序前端care-record.js中查询接口写错路径:/record/list(少了个s),而正确路径是/records/list(复数)。后端Controller方法名为listRecords(),映射路径为/records/list。
解决:全局搜索/record/list,替换为/records/list。注意:/record/detail是正确的(单条记录),只有列表接口是复数。
4.4 现象:护工在后台修改排班后,小程序端「我的排班」仍显示旧数据
原因:小程序端缓存了排班数据(wx.setStorageSync('schedule', data)),但未在修改后清除。源码中nurse-schedule.js的updateSchedule()方法末尾缺少wx.removeStorageSync('schedule')。
解决:在updateSchedule()的success回调中,添加wx.removeStorageSync('schedule'),确保下次进入页面时重新拉取。
4.5 现象:跌倒告警分派后,护工小程序收不到推送,但后台日志显示「分派成功」
原因:微信模板消息需要在小程序后台「开发管理 → 模板消息」中手动申请模板ID,源码中AlarmService.sendTemplateMsg()硬编码了TEMPLATE_ID = 'xxx',但未在文档中说明需替换。
解决:登录mp.weixin.qq.com → 开发管理 → 模板消息 → 添加模板(关键词:告警类型、发生时间、地点)→ 复制模板ID → 替换AlarmService.java中TEMPLATE_ID常量值。
5. 论文(LW)撰写与答辩技巧:如何把技术实现转化为得分亮点
很多学生把LW写成「系统截图+功能列表」,结果答辩被问「为什么用MyBatis-Plus不用JPA?」就哑火。其实源码里藏着3个可深挖的技术亮点,直接对应答辩高频问题:
5.1 亮点一:JWT Token的双密钥轮换机制(应对密钥泄露风险)
源码中JwtUtil.java实现了密钥轮换:
- 当前密钥
currentSecret用于签发新Token; - 历史密钥
oldSecret用于校验已发出的旧Token(有效期24小时); - 每24小时,
currentSecret自动更新,oldSecret被丢弃。
// JwtUtil.java 关键逻辑 private static String currentSecret = "elderCare2024!"; private static String oldSecret = null; public static String generateToken(User user) { return Jwts.builder() .setSubject(user.getUsername()) .signWith(SignatureAlgorithm.HS256, currentSecret) // 永远用currentSecret签发 .compact(); } public static Claims parseToken(String token) { try { return Jwts.parser().setSigningKey(currentSecret).parseClaimsJws(token).getBody(); } catch (Exception e) { // 签发失败,尝试用oldSecret校验(应对密钥刚轮换的场景) if (oldSecret != null) { return Jwts.parser().setSigningKey(oldSecret).parseClaimsJws(token).getBody(); } throw e; } }答辩话术:
“我设计双密钥轮换,不是为了炫技。假设某天黑客窃取了当前密钥,他只能解密24小时内签发的Token。而历史Token(超过24小时)因oldSecret已失效,无法伪造。这符合等保2.0中‘密钥生命周期管理’要求。”
5.2 亮点二:MySQL全文索引优化家属端搜索体验
家属在小程序搜索老人姓名时,后端SQL使用MATCH AGAINST而非LIKE '%关键词%':
-- 在elder_info表的name字段建全文索引 ALTER TABLE elder_info ADD FULLTEXT(name); -- 查询语句 SELECT * FROM elder_info WHERE MATCH(name) AGAINST('张三' IN NATURAL LANGUAGE MODE);实测对比:10万条数据下,LIKE查询平均耗时1.2秒,MATCH AGAINST仅0.03秒,且支持「张三丰」也能匹配「张三」。
答辩加分点:
展示EXPLAIN执行计划,指出type: fulltext证明走了索引;对比SHOW PROFILE中Sending data阶段耗时差异。
5.3 亮点三:小程序端离线缓存策略(应对网络抖动)
小程序app.js中实现三级缓存:
- 内存缓存(
App.globalData.cache):存储登录态、用户基本信息; - 本地缓存(
wx.setStorageSync):存储排班、照护记录等时效性要求≤1小时的数据; - 网络兜底(
fail回调中加载wx.getStorageSync旧数据):当网络请求失败时,展示「最后更新于XX:XX」的缓存数据,并提示「网络异常,正在重试」。
// utils/request.js export function request(url, data = {}, method = 'GET') { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, data, method, success: (res) => { if (res.data.code === 200) { // 成功时更新本地缓存 if (url.includes('/records/list')) { wx.setStorageSync('careRecords', res.data.data); } resolve(res.data); } }, fail: () => { // 失败时读取本地缓存 if (url.includes('/records/list')) { const cache = wx.getStorageSync('careRecords'); if (cache) { resolve({ code: 200, data: cache }); } } reject('网络错误'); } }); }); }答辩演示技巧:
在开发者工具中开启「网络→离线」模式,点击「照护记录」页,展示它依然能加载出缓存数据,并显示「最后更新于14:22」的提示。评委立刻get到你考虑了真实场景。
最后说个私藏习惯:每次答辩前,我会把源码中所有TODO和FIXME注释整理成一页PPT,标题叫「已知问题与演进路线」。比如FIXME: 跌倒告警未接入硬件传感器,当前为模拟触发,然后补充「下一步计划对接华为HiLink养老设备SDK」。这比说「系统完美无缺」更显专业。希望帮到你。
本文还有配套的精品资源,点击获取