简介:这份资源是面向高校计算机专业学生与Java初学者的毕业设计完整源码包,围绕老年人服药提醒App展开,帮助解决按时按量服药管理这一实际场景问题。项目以Java 1.8为基础,结合Android四大组件、MySQL 5.7+数据库与IntelliJ IDEA或Eclipse开发环境,涵盖用户信息、药物信息与服药计划等模块,并附有数据库脚本与环境部署说明文档。压缩包共299个文件,约48.17MB,其中56个java源文件与58个class文件构成核心业务逻辑,86个jar提供依赖支持,另有24个vue、18个xml、7个js及5个jsp等前端与配置资源,2个sql脚本和2个docx文档辅助环境搭建与数据库初始化。目前已有222人学习下载。通过阅读源码与配套文档,读者可掌握移动端服药提醒的架构设计、数据库表结构设计、接口实现与部署流程,适合作为毕业设计参考或Java实战练习素材。
1. 老年人服药提醒 App:从一份毕业设计源码里拆出真正能跑通的落地路径
做 Java 毕业设计的人,十个里有八个最后会落到「管理系统」上,但老年人服药提醒 App 这个选题不一样——它天然带着移动端、定时调度、数据持久化和用户角色区分四个技术面,比单纯的后台增删改查更能撑起一份像样的毕设。我见过不少同学拿到类似「java毕业设计之老年人服药提醒app的设计与实现源码.zip」这样的压缩包,解压之后对着几十个文件发懵:后端在哪、前端在哪、数据库怎么建、跑起来为什么报错。这篇笔记就是把这个选题从技术选型到本地跑通、再到功能扩展的完整路径拆开讲清楚,适合正在做同类毕设的本科生,也适合想拿它当 Android 入门练手项目的开发者。核心要解决的问题只有一个:让一份服药提醒 App 的源码,在你自己的机器上真正跑起来,并且你知道每一层在干什么。
2. 技术选型与工程结构:为什么这套组合最适合毕设场景
2.1 后端为什么选 Spring Boot 而不是纯 Servlet
拿到一份 Java 毕设源码,第一件事是看后端用什么框架。老年人服药提醒 App 的后端职责其实很清晰:用户注册登录、药品信息管理、服药计划 CRUD、提醒记录查询。如果只用 Servlet + JSP 写,代码量会膨胀得很快,而且每个接口都要手动处理 JSON 序列化和参数绑定,调试成本高。Spring Boot 的价值在于把 Controller、Service、Mapper 三层结构标准化,配合 MyBatis 或 MyBatis-Plus,一个服药计划的增删改查接口大概二十行就能写完。
我一般会先确认源码里有没有pom.xml,打开看依赖。典型的毕设级 Spring Boot 项目会有这几个关键依赖:
<!-- pom.xml 核心依赖片段 --> <dependencies> <!-- Web 层:提供 REST 接口能力 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 持久层:MyBatis-Plus 简化单表 CRUD --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok:减少 getter/setter 样板代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这段依赖说明了几件事:Web starter 负责把@RestController注解的类暴露成 HTTP 接口;MyBatis-Plus 的BaseMapper接口让你不用写 XML 就能完成大部分单表操作;MySQL 驱动负责连接数据库。参数上要注意 MyBatis-Plus 的版本,3.5.x 和 Spring Boot 3.x 搭配时需要用mybatis-plus-spring-boot3-starter,用错了启动直接报ClassNotFoundException,这是最常见的翻车点之一。
2.2 移动端用 Android 原生还是跨平台方案
毕设源码里的移动端通常有两种形态:一种是纯 Android 原生(Java 或 Kotlin),一种是套壳的 WebView 或者 uni-app 编译产物。如果你拿到的源码里app/src/main/java下面有完整的 Activity 和 Adapter 类,那就是原生方案。原生方案的好处是能直接调用 Android 的AlarmManager和NotificationManager做本地定时提醒,这对服药提醒这个场景是刚需。
跨平台方案(比如用 uni-app 写然后打包成 APK)的优势是前端同学上手快,但定时提醒的可靠性会打折扣,因为 WebView 层的定时器在应用退到后台后可能被系统挂起。我的建议是:如果源码是原生 Android,就老老实实跑原生;如果是跨平台,先把提醒功能单独用原生插件补上。判断方法很简单,看AndroidManifest.xml里有没有注册BroadcastReceiver和AlarmManager相关的权限声明。
2.3 数据库表结构:四张核心表撑起整个业务
服药提醒 App 的数据模型不复杂,但表设计直接影响后续接口好不好写。典型的结构是四张表:
| 表名 | 作用 | 关键字段 |
|---|---|---|
| user | 用户信息 | id, username, password, phone, role |
| medicine | 药品信息 | id, user_id, name, dosage, stock |
| plan | 服药计划 | id, user_id, medicine_id, time, repeat_type, status |
| record | 服药记录 | id, plan_id, take_time, status |
plan表里的repeat_type字段决定提醒是每天、每周还是单次,status标记计划是否启用。record表记录实际服药情况,用于后续统计依从性。建表时注意time字段用VARCHAR存"08:00"这种格式比用TIME类型更方便前端展示,这是我在多个类似项目里踩过坑之后的习惯——用TIME类型在 JSON 序列化时经常出现时区偏移,排查起来很费时间。
3. 本地跑通的最小步骤:从解压到看到登录页
3.1 数据库初始化与配置修改
源码压缩包里通常会有一个sql文件夹或者根目录下的.sql文件。第一步是在本地 MySQL 里建库并导入:
# 登录 MySQL 并创建数据库 mysql -u root -p CREATE DATABASE medicine_reminder DEFAULT CHARACTER SET utf8mb4; USE medicine_reminder; SOURCE /path/to/your/sql/init.sql;导入完成后,找到后端项目的application.yml或application.properties,修改数据库连接信息:
# application.yml 数据库配置 spring: datasource: url: jdbc:mysql://localhost:3306/medicine_reminder?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver这里serverTimezone=Asia/Shanghai必须加,否则 MySQL 8.x 连接时会报时区错误。characterEncoding=utf8保证中文药品名不会乱码。改完配置后,用mvn spring-boot:run或者直接在 IDE 里跑Application主类启动后端。看到控制台输出Started Application in x.x seconds就说明后端起来了。
3.2 后端接口自测:用 curl 验证核心链路
后端启动后别急着跑前端,先用 curl 把关键接口过一遍,确认数据链路是通的:
# 测试登录接口,获取 token curl -X POST http://localhost:8080/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"test","password":"123456"}' # 用返回的 token 查询服药计划列表 curl -X GET http://localhost:8080/api/plan/list \ -H "Authorization: Bearer 你的token"如果登录接口返回{"code":200,"data":{"token":"xxx"}}这样的结构,说明 Controller 和 Service 层是通的。如果返回 500,先看控制台异常栈,大概率是数据库字段和实体类属性没对上,比如数据库用下划线命名user_id,实体类用驼峰userId,需要在application.yml里开启 MyBatis-Plus 的驼峰映射:
mybatis-plus: configuration: map-underscore-to-camel-case: true这个配置不加的话,查询结果里user_id对应的userId会是 null,接口返回的数据看起来「缺字段」,新手很容易在这里卡住。
3.3 Android 端编译与真机调试
移动端部分,用 Android Studio 打开app目录所在的工程。第一件事是改BuildConfig或者Constants类里的后端地址,把localhost或127.0.0.1改成你电脑的局域网 IP(比如192.168.1.100),因为手机和电脑不在同一个 localhost 上。改完之后Sync Project with Gradle Files,然后连真机或开模拟器运行。
// Constants.java 中的后端地址配置 public class Constants { // 真机调试时改成电脑的局域网 IP,模拟器用 10.0.2.2 public static final String BASE_URL = "http://192.168.1.100:8080/api/"; }模拟器访问宿主机用10.0.2.2,真机必须用局域网 IP,而且手机和电脑要在同一个 WiFi 下。如果 App 能打开但请求一直转圈,先检查电脑防火墙有没有放行 8080 端口,这是血泪经验——Windows 防火墙默认会拦掉外部设备对本地端口的访问。
4. 定时提醒功能的实现细节与避坑指南
4.1 AlarmManager 与 Notification 的配合方式
服药提醒的核心是「到点弹通知」。Android 里做本地定时提醒,标准做法是AlarmManager+BroadcastReceiver+NotificationManager三件套。AlarmManager负责在指定时间触发一个 PendingIntent,BroadcastReceiver接收后构建通知并展示。
// 设置一个每天 08:00 触发的服药提醒 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, MedicineReminderReceiver.class); intent.putExtra("medicine_name", "阿司匹林"); intent.putExtra("dosage", "1片"); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, requestCode, // 每个计划用不同的 requestCode,避免覆盖 intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); // 计算下一次触发时间 Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 8); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); if (calendar.getTimeInMillis() < System.currentTimeMillis()) { calendar.add(Calendar.DAY_OF_MONTH, 1); } // Android 6.0+ 用 setExactAndAllowWhileIdle 保证低电耗模式下也能触发 alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent );这段代码有几个关键参数需要解释。requestCode必须每个服药计划唯一,否则后设置的会覆盖前面的,导致只有最后一个提醒生效——这是最常见的功能 bug。FLAG_IMMUTABLE在 Android 12+ 是强制要求,不加会直接崩溃。setExactAndAllowWhileIdle比set更可靠,能在 Doze 模式下触发,但系统限制每小时最多触发一次,对于服药提醒这种低频场景完全够用。
4.2 通知渠道与权限适配
Android 8.0 之后通知必须走 NotificationChannel,不建渠道通知直接不显示:
// 在 Application 或主 Activity 中创建通知渠道 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( "medicine_channel", // 渠道 ID "服药提醒", // 渠道名称 NotificationManager.IMPORTANCE_HIGH // 重要级别,HIGH 会弹横幅 ); channel.setDescription("用于展示服药提醒通知"); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); }IMPORTANCE_HIGH才会弹出横幅提醒,IMPORTANCE_DEFAULT只会在状态栏显示图标。Android 13 还需要动态申请POST_NOTIFICATIONS权限,不申请的话通知同样不显示。适配这块的代码量不大,但漏掉任何一个版本判断都会导致「在某个机型上不提醒」,而用户往往就是用那个机型测试的。
4.3 避坑:服药提醒 App 最常见的五个翻车点
现象一:模拟器上提醒正常,真机上完全不弹。原因是国产 ROM(如 MIUI、EMUI)对后台应用有额外的省电限制,AlarmManager被系统拦截。解决办法是引导用户在设置里把 App 加入「自启动白名单」和「电池优化白名单」,代码层面可以用isIgnoringBatteryOptimizations检测并跳转到设置页。
现象二:修改系统时间后提醒错乱。原因是用了System.currentTimeMillis()做相对时间计算,而AlarmManager.RTC_WAKEUP依赖系统墙上时间。解决方式是改用AlarmManager.RTC配合Calendar做绝对时间计算,或者在BroadcastReceiver里重新计算下一次触发时间。
现象三:多个药品的提醒互相覆盖。前面提过,PendingIntent的requestCode重复了。解决方式是用数据库里plan表的id作为requestCode,保证唯一性。
现象四:后端返回的服药时间格式和前端解析不一致。后端用LocalTime序列化成"08:00:00",前端按"08:00"解析,导致ParseException。统一做法是在实体类的时间字段上加@JsonFormat(pattern = "HH:mm"),或者前后端约定统一用字符串传输。
现象五:MySQL 连接数不够导致接口间歇性 500。毕设项目默认连接池(HikariCP)最大连接数是 10,如果前端频繁轮询提醒列表,连接会被占满。在application.yml里适当调大maximum-pool-size,或者把前端的轮询改成下拉刷新。
5. 从能跑到好用:三个值得加的进阶功能
5.1 服药依从性统计接口
基础功能跑通之后,加一个依从性统计能让毕设的完成度明显提升。思路是查record表里某个时间段内status='taken'的记录数,除以plan表里应服次数:
-- 统计某用户近 7 天的服药依从率 SELECT COUNT(CASE WHEN r.status = 'taken' THEN 1 END) AS taken_count, COUNT(*) AS total_count, ROUND(COUNT(CASE WHEN r.status = 'taken' THEN 1 END) / COUNT(*) * 100, 1) AS rate FROM record r JOIN plan p ON r.plan_id = p.id WHERE p.user_id = ? AND r.take_time >= DATE_SUB(NOW(), INTERVAL 7 DAY);这个查询返回一个百分比,前端用进度条展示。注意COUNT(*)在没有任何记录时返回 0,除法会报错,所以要在 Java 层做空值判断,或者在 SQL 里用NULLIF(COUNT(*), 0)兜底。
5.2 药品库存预警
medicine表里的stock字段可以做成库存预警:每次用户标记「已服药」时,对应药品库存减一,当库存低于阈值(比如 3)时,在首页显示提醒。实现上是在record的 Service 层加一段逻辑:
// 标记服药后扣减库存并检查预警 @Transactional public void markAsTaken(Long planId) { Plan plan = planMapper.selectById(planId); Medicine medicine = medicineMapper.selectById(plan.getMedicineId()); // 扣库存,防止扣成负数 int newStock = Math.max(0, medicine.getStock() - 1); medicine.setStock(newStock); medicineMapper.updateById(medicine); // 库存低于阈值时标记预警 if (newStock <= 3) { medicine.setWarning(true); medicineMapper.updateById(medicine); } // 写入服药记录 Record record = new Record(); record.setPlanId(planId); record.setTakeTime(new Date()); record.setStatus("taken"); recordMapper.insert(record); }@Transactional保证扣库存和写记录要么都成功要么都回滚,避免库存扣了但记录没写进去的数据不一致。Math.max(0, ...)防止并发情况下库存变成负数。
5.3 用 WorkManager 替代 AlarmManager 做兼容
如果你的 App 需要兼容 Android 12 以上的严格后台限制,可以考虑用WorkManager做提醒调度。WorkManager的优势是系统会自动选择合适时机执行,不需要手动处理 Doze 模式,缺点是触发时间不够精确,可能延迟几分钟到十几分钟。对于服药提醒这种场景,延迟十几分钟通常可以接受,但如果你要求精确到分钟,还是得用AlarmManager配合setExactAndAllowWhileIdle。
我的习惯是:单次精确提醒用AlarmManager,周期性后台同步(比如从服务器拉取最新的服药计划)用WorkManager,两者各司其职。这个组合在多个项目里验证过,稳定性比纯用其中一个要好。
5.4 一个容易被忽略的细节:时间格式的时区处理
最后说一个我在这个选题上踩过的坑。后端用new Date()存服药记录时间,前端展示时发现比实际时间少了 8 小时。原因是 MySQL 的serverTimezone配成了 UTC,而 Java 应用的默认时区是Asia/Shanghai,两边不一致。解决方式是在 JDBC URL 里明确写serverTimezone=Asia/Shanghai,并且在application.yml里加一行:
spring: jackson: time-zone: Asia/Shanghai date-format: yyyy-MM-dd HH:mm:ss这样 Jackson 序列化Date时会用东八区,前端拿到的就是正确时间。这个配置不加的话,接口返回的时间字段看起来「没错」,但和数据库里的值对不上,排查起来很费时间。希望帮到你。
本文还有配套的精品资源,点击获取