给计算机专业的学生做毕设指导这几年,我见得太多次凌晨三点在群里问“为什么我的服务器起不来”的场面了。如果你正在为基于Spring Boot的智能药箱系统头疼,别急——这篇文章就是为你准备的。从一个完整的毕设交付包出发,我会把服药时间提醒这个核心功能,从需求分析一路讲到代码实现、本地部署和服务器上线。这套思路适合谁?计算机专业准备毕设的学生、想拿Spring Boot练手做实战项目的初学者,甚至是想快速搭建物联网+健康类Demo的开发者,都能在里面找到能直接抄的作业。
需要先说明一点,我拆解的不是一个“只给你一堆文件”的空壳项目。完整源码、论文文档(毕设圈里常说的LW)、部署说明、演示视频,这四个部分是一个能通过答辩的毕设缺一不可的。下面我会从需求侧和代码侧两条线把整个项目拆透,尤其会重点讲大家最容易出问题的定时提醒和部署环节。
1. 项目整体拆解:智能药箱系统到底在做什么
1.1 从标题看需求:这个系统解决了什么问题
智能药箱系统,字面理解就是给“吃药”这件事加一个管理工具。对年轻人来说,按时吃药可能不是问题,但对慢性病患者、独居老人、需要长期服药的群体来说,记错时间、漏服、重复服用是常态。这个系统的核心价值,就是让“几点该吃什么药”这件事,从人脑记忆变成系统自动触发。标题里特意强调“服药时间提醒”,说明这个功能是整个项目的灵魂,其他模块都是围绕它做支撑的。
放到毕业设计的语境里,这个选题之所以热门,是因为它踩中了三个点:第一,需求清晰,评委一看就懂,不需要花五分钟解释业务概念;第二,技术栈成熟,Spring Boot + MyBatis + MySQL这套组合在毕设里几乎是标准答案,资料多、踩坑少;第三,可扩展性强,后续无论是加硬件联动、加小程序前端,还是引入统计分析,都能在论文里写出彩。
从实际应用来看,这个系统也不算悬空。很多智能药箱产品已经在市面上卖了,核心逻辑就是“药箱带显示和提醒功能,后台能设置服药计划”。毕设版本不需要做出实体硬件,用软件模拟数据流把提醒链路打通,就足够证明你掌握了一个完整系统的设计能力。
1.2 完整交付包里都需要哪些东西
标题最后那串“源码+LW+部署说明+演示视频”,其实已经说明了合格毕设的组成结构。先说源码,它不是一个文档,而是一个可以直接启动的工程,包含后端Java代码、数据库脚本、配置文件。很多学生拿到源码后第一件事是运行,这是对的,但我的建议是:一定要按自己的思路重新走一遍关键代码,尤其是提醒逻辑,不然答辩的时候一句“用了Spring自带的@Scheduled”就解释不清楚了。
然后说LW,也就是论文文档。论文不是把源码复制粘贴进去,而是要讲清楚“为什么要设计这些表”“为什么选Spring Boot而不是SSM”“提醒模块的可扩展性体现在哪”。部署说明则是让你能在自己的电脑或服务器上复现整个环境的操作手册,从JDK安装到MySQL配置,每一步都要能照着做。演示视频则是答辩前录制的系统操作录屏,时长控制在五到八分钟,把核心流程走一遍。这四个部分合在一起,才叫完整的交付。
这里我也要提醒一句:市面上确实有人宣称“全bao一条龙”,但如果你只是拿到文件却不知道项目怎么跑、核心逻辑在哪,答辩时被问到“为什么这里要加事务”这种问题,很容易当场冷场。所以我更倾向于把这份交付包理解成“参考实现”,而不是“免检答案”。
1.3 功能模块设计与业务流程闭环
我把这套系统的功能模块拆给大家看,照着这个清单去核对你的源码是不是齐全。
- 用户模块:注册、登录、个人信息维护。这里要注意角色区分,比如患者和家属或管理员最好用不同角色,对应不同的操作权限。
- 药品模块:药品信息的增删改查,包括药品名称、规格、库存数量、生产日期、有效期。
- 服药计划模块:这是核心中的核心。用户为某个药品创建一条计划,设置开始日期、结束日期、服用时间点、每次剂量。
- 提醒记录模块:每次定时任务触发后,都生成一条提醒记录,记录“应提醒时间”和“实际发送时间”,方便追溯。
- 服药确认模块:用户收到提醒后,在页面点击“我已服药”,系统记录确认时间;如果超时未确认,可以做二次提醒或者标记为漏服。
业务流程闭环是这样走的:管理员添加用户 → 用户登录后添加药品 → 为药品创建服药计划 → 系统定时器扫描当天计划 → 到达时间点推送提醒 → 用户确认服药 → 系统记录反馈 → 超时未确认生成漏服记录。这个闭环讲清楚,论文的需求分析部分就完成了一大半。
为了让你在核对源码时更快上手,我把模块与后端接口、数据库表对应起来,做成了一张速查表:
| 功能模块 | 核心接口 | 数据库表 | 关键状态 |
|---|---|---|---|
| 用户管理 | /api/user/register、/api/user/login | sys_user | 角色字段 role |
| 药品管理 | /api/drug/add、/api/drug/list | drug_info | 库存 stock |
| 服药计划 | /api/plan/add、/api/plan/list | med_plan | 计划状态 status |
| 提醒记录 | /api/remind/list | remind_log | 待提醒/已发送 |
| 服药确认 | /api/remind/confirm | remind_log | 已确认/已超时 |
小结一下,这一章最想强调的是一个逻辑链条:有清晰的需求和模块边界,后面的代码才不会写成一团乱麻。很多毕设翻车,不是代码跑不起来,而是自己在答辩时根本讲不清系统到底有哪些功能、这些功能之间怎么协作。所以动手写代码前,先把这个模块清单和闭环流程在脑子里过一遍,比什么都重要。
2. 核心技术解析:服药时间提醒是怎么实现的
2.1 定时任务的三种常用方案选型
实现服药提醒,本质上就是一个定时任务业务。Spring Boot生态里,常见的有三种做法。
方案一:@Scheduled注解。这是最简单的方式,在方法上标注@Scheduled(cron = "0 0 8 * * ?"),配合Spring Boot启动类上的@EnableScheduling,就能让服务在每小时、每天、每月的固定时间点执行任务。优点是零依赖,不需要额外配置文件;缺点是任务逻辑全部写死在代码里,不方便动态调整。比如用户新增一条服药计划,你得想办法重建调度器,或者用一个全局扫描任务来代替。
方案二:Quartz调度框架。Quartz提供了Job、Trigger、Scheduler这套完整模型,支持数据库持久化,可以把调度状态存到表里。相比@Scheduled,它最大的好处是“动态创建任务”,用户新增一个服药计划时,我们可以直接在运行时创建一条Trigger,到点触发Job。缺点是学习曲线陡一些,表和配置也更多。
方案三:xxl-job等分布式调度平台。这类工具适合微服务、多节点场景,提供可视化管理界面、失败重试、日志面板。对于毕设来说,属于杀鸡用牛刀,但可以在论文的“系统扩展”章节提一笔,表明你了解分布式场景下的任务调度方案。
我的建议是:毕设首选用@Scheduled加数据库存储待执行计划的方式,用一个每分钟触发一次的定时器,去数据库里查接下来几分钟内有没有该提醒的记录。这样做的好处是只有一个扫描任务,逻辑简单,而且新增服药计划不需要动态操作调度器,只需要往表里插一条数据就行。等你有富余时间,再去研究Quartz的动态Trigger作为加分点。
2.2 提醒消息的推送通道怎么选
定时任务只是一个触发器,真正“触达用户”的是消息推送通道。这里有几个层次。
第一层是站内消息。系统里建立一张提醒表,定时任务往表里插入待提醒记录,前端页面通过WebSocket或者短轮询接受到期消息。这种方案成本最低,演示效果直观,因为用户可以在系统里直接看到“该吃药了”的弹窗。如果你的前端用了Vue或React,可以配合WebSocket做一个实时通知角标。
第二层是邮件通知。用Spring Boot自带的JavaMailSender,配置好邮箱SMTP参数,就能发送HTML邮件。它适合演示“离线通知”场景,但国内环境对邮件敏感度一般,容易被丢进垃圾箱,而且演示时如果邮件延时,会影响答辩节奏。
第三层是短信或IM通知。接阿里云短信、腾讯云短信或者钉钉、企业微信机器人都行。这类渠道需要申请模板、配置AccessKey,配置过程本身就是一个很好的论文素材,能体现你接触了真实第三方接口。但要注意,短信通常要充值,如果只是为了演示,很多服务商有免费测试额度,记得提前准备。
如果这个项目想和硬件结合,还可以让定时任务触发树莓派上的继电器,或者通过语音模块播放“请按时服药”的提示音。这部分可以放进扩展设计里,核心实现阶段不必依赖它。我的建议是:把站内消息作为核心提醒通道,用WebSocket做实时推送;邮件作为一个备选通道,演示时两种效果都能展示。短信和IM在论文里写“预留接口”,避免演示时因为网络或平台审核出问题。
2.3 数据库设计思路与核心表结构
服药提醒的核心数据模型,我倾向于这几张表。
sys_user用户表:id、username、password(加密存储)、role、phone。drug_info药品表:id、user_id、name、specification、stock、validity_date。med_plan服药计划表:id、user_id、drug_id、start_date、end_date、dosage、status、remind_times,例如“08:00,12:00,18:00”。remind_log提醒记录表:id、plan_id、remind_time、actual_send_time、status,区分未提醒、已提醒、已确认、漏服,以及confirm_time。
这里特别提醒几个设计细节。第一,remind_times用字符串存多个时间点,代码里split一下就能处理。但如果你需要精确到“每个时间点独立启停”,那就拆分成med_plan_detail子表,一条计划对应多条时间点记录。我见过很多同学在这个取舍上纠结,我的建议是:表结构尽量往“能应对动态变化”的方向走,也就是一张计划主表加一张时间点子表,这样将来被问到“同一个药品一天吃两次但今天只吃一次怎么办”,你能给出状态调整方案,而不是支支吾吾。
第二,时间字段用LocalDateTime或LocalTime,别用字符串比较大小。第三,remind_log里加一个window_minutes字段,表示提醒后多少分钟内需要确认,超过就标记漏服。这个字段很关键,能体现你考虑了真实用药场景中的缓冲需求。
2.4 为什么"提醒"不能只靠一条定时任务
我见过很多初版代码是这么写的:在配置里写死一个cron表达式,到点触发一次任务,然后就不管了。这在演示Demo里没问题,但放在毕业设计里就很容易被答辩老师追问。真实场景下会出现这些情况:一个用户早上8点的药,但是系统在7点50才创建这条计划,怎么办?用户前一条记录还没确认,后一条时间又到了,要不要发重复提醒?用户出差去了另一个时区,提醒时间要不要跟着变?周末是否跳过?
所以“提醒”应该是一个有状态、有判断的服务,而不是一个简单的定时器。在实际项目里,我会这样做:
- 用一个每分钟或每30秒扫描的任务,查询所有状态为“进行中”的计划。
- 把每条计划的提醒时间点解析出来,和当前时间做差值计算,落在未来5分钟内就生成提醒记录。
- 对已生成提醒但用户在窗口期内未确认的记录,生成二次提醒或漏服记录。
- 用MySQL的
updated_at和乐观锁版本号,避免多个实例同时扫描同一批数据导致重复提醒。
核心逻辑是:定时任务只做“扫描”,真正“发送提醒”的动作由状态机驱动。这样做的好处是,即使某个时间点服务器刚好重启,重启后扫描任务也能把遗漏的提醒补上。这个思路我强烈建议写进论文,它是整个系统的技术亮点,也是区分“背代码”和“懂设计”的分水岭。
3. 实操过程:从零到一搭建Spring Boot项目
3.1 环境准备与项目初始化
说再多理论,不如直接把工程搭起来走一遍。先列一下环境清单:JDK 8或11,Spring Boot 2.x推荐8,2.7以上推荐11,如果你本机是17,直接上Spring Boot 2.7.x或3.x都行;Maven 3.6以上;IDEA,社区版或旗舰版都够用;MySQL 5.7或8.0;Postman或Apifox做接口测试。
项目初始化有两种路径。第一种,打开IDEA选择Spring Initializr,group填com.example,artifact填smart-medicine-box,依赖勾选Spring Web、MyBatis Framework或MyBatis-Plus、MySQL Driver、Lombok。第二种,直接去start.spring.io下载一个压缩包,解压后用IDEA打开。注意生成时Spring Boot版本别选太新的,有些和MyBatis的兼容性还没磨合好,稳妥点选2.7.x。
初始化后,把默认的application.properties改成application.yml,并配置好数据源:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/smart_medicine?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true这里我要特意提一句,serverTimezone=Asia/Shanghai和useSSL=false这两个参数,是解决“本地连接MySQL报错”的常青选项。很多同学一打开项目就报Communications link failure,八成是时区或SSL配置不对。这个坑我在后面排查表里还会再列一次,因为它太常出现了。
3.2 核心代码实战:实体、Mapper、Service、Controller
我们用最常见的代码风格演示一遍核心链路。先用Lombok建实体类:
@Data @TableName("med_plan") public class MedPlan { @TableId(type = IdType.AUTO) private Long id; private Long userId; private Long drugId; private LocalDate startDate; private LocalDate endDate; private String dosage; private String remindTimes; private Integer status; // 0-未启用 1-进行中 2-已结束 }然后是Service层的核心方法,我建议命名为checkAndSendReminders()。这里用一个每分钟触发的@Scheduled方法扫描未来5分钟内需要提醒的记录:
@Service public class ReminderService { @Resource private MedPlanMapper medPlanMapper; @Resource private RemindLogMapper remindLogMapper; @Scheduled(cron = "0 * * * * ?") public void scanUpcomingReminders() { // 1. 查询所有进行中的计划 List<MedPlan> activePlans = medPlanMapper.selectActivePlans(); LocalDateTime now = LocalDateTime.now(); for (MedPlan plan : activePlans) { // 2. 只处理当天仍在计划期内的计划 if (!isWithinPlanPeriod(plan, now.toLocalDate())) { continue; } // 3. 解析时间点,判断是否落在未来5分钟窗口内 for (LocalTime remindTime : parseRemindTimes(plan.getRemindTimes())) { LocalDateTime scheduledDateTime = LocalDateTime.of(now.toLocalDate(), remindTime); long diffMinutes = ChronoUnit.MINUTES.between(now, scheduledDateTime); if (diffMinutes >= 0 && diffMinutes <= 5 && !isAlreadyGenerated(plan.getId(), scheduledDateTime)) { sendReminder(plan, scheduledDateTime); } } } } }这段代码代表了整套系统的核心调度思路。isAlreadyGenerated方法需要去remind_log表里查一下,防止同一分钟重复插入记录;sendReminder内部负责把提醒记录落库,同时通过WebSocket或第三方通道推送消息。实际项目里还要加分布式锁,这里为了看起来直观,我用的是单机场景。如果论文想写“多实例部署”,就给这个Service加一层@Lock或基于数据库的乐观锁控制,并把这部分放在“系统可靠性设计”一节里展示。
Controller层通常设计成RESTful接口,比方说/api/plan/add、/api/plan/list、/api/remind/confirm。前端Vue项目里的按钮,本质上就是调用这些接口。这里不再贴完整的Controller代码,但必须提醒大家:接口里要做参数校验,比如结束日期不能早于开始日期,药品ID必须存在,这些同样是评审老师容易追问的点。
3.3 数据库脚本与初始化数据
建表时,我建议在doc/sql/目录下放一个init.sql。创建数据库和核心表的脚本如下:
CREATE DATABASE IF NOT EXISTS smart_medicine DEFAULT CHARSET utf8mb4; USE smart_medicine; CREATE TABLE `med_plan` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '用户ID', `drug_id` bigint(20) NOT NULL COMMENT '药品ID', `start_date` date NOT NULL, `end_date` date DEFAULT NULL, `dosage` varchar(50) DEFAULT NULL COMMENT '每次剂量', `remind_times` varchar(100) NOT NULL COMMENT '提醒时间点,逗号分隔', `status` tinyint(4) DEFAULT '1' COMMENT '0-未启用 1-进行中 2-已结束', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服药计划表';这里我特意加上时间字段和索引。索引在数据量上来之后非常重要,虽然毕设数据量小看不出来,但答辩时老师可能会问“为什么给user_id加索引”,你就可以回答“该字段是高频查询条件”,并且可以在explain里展示一下执行计划,非常加分。
remind_log表的核心字段这里不再用SQL贴一遍,但有一个关键点要强调:status的取值范围建议定为0-待提醒、1-已发送、2-已确认、3-已超时,并且加一个confirm_time。这组状态在论文的时序图里对应得很清晰,配合2.4节的状态机逻辑,整个系统的动态行为就能完整呈现。
3.4 接口联调与功能验证
项目启动后,用Postman先测几个核心接口。第一,注册用户,注意密码要用BCrypt加密存储,不要明文入库。第二,添加药品,返回药品ID。第三,创建服药计划,设置remindTimes为“08:00,12:00,18:00”。第四,等定时任务到点,观察控制台或数据库里是否生成了提醒记录。第五,调用确认接口,模拟用户点击“我已服药”。
整个联调过程里,最需要耐心的部分是WebSocket连接验证。前端页面打开后,后端的提醒一旦发送,浏览器要能收到实时消息。如果发现前端连不上,先检查后端的WebSocket端点和前端配置的地址是否一致,再检查是否被网关或跨域拦截。跨域问题在前后端分离项目里非常常见,解决方案就是在配置类里加一个CorsRegistry:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true); } }联动验证通过后,建议把测试截图放进论文的“系统功能展示”章节,这是很多导师最喜欢看的素材。截图要带着接口返回JSON和控制台日志一起截,比单纯截个页面显得更有说服力。
4. 部署说明与常见问题排查实录
4.1 本地打包与运行
部署的第一步是打包。在项目根目录执行mvn clean package -DskipTests,结束后在target/目录下会出现一个smart-medicine-box-0.0.1-SNAPSHOT.jar。本地运行可以直接java -jar target/smart-medicine-box-0.0.1-SNAPSHOT.jar,前提是本机的MySQL服务已经启动,并且数据库脚本已经执行过。
这里我遇到过很多次的问题:打包时测试类报错,或者测试类启动Spring上下文连不上数据库。所以打包命令里-DskipTests是必需品,但注意这只会跳过测试执行,不会跳过编译。如果连编译都过不了,就得检查依赖里是否缺了Lombok注解处理器,或者JDK版本不一致。
打包出来后,用java -jar启动时如果报“端口被占用”,可以用lsof -i:8080在Linux或Mac下查看,在Windows下用netstat -ano | findstr 8080找到占用进程,kill掉再重新启动。对于演示环境,也可以改server.port换一个端口,比如8081。这个操作非常简单,但在答辩现场用得非常频繁,建议提前练一遍。
4.2 Linux服务器部署实战
如果你的演示环境是一台云服务器或者本地虚拟机,部署步骤大概是这样的。先把JDK和MySQL装好,然后上传jar包到/opt/app/目录。创建一个启动脚本start.sh:
#!/bin/bash APP_NAME=smart-medicine-box-0.0.1-SNAPSHOT.jar nohup java -Xms256m -Xmx512m -jar /opt/app/$APP_NAME > /opt/app/logs/app.log 2>&1 & echo "PID: $!"用nohup启动的好处是,即使关闭SSH会话,服务也不会停。查看日志用tail -f /opt/app/logs/app.log。如果要停止服务,用ps -ef | grep smart-medicine找到PID,执行kill -9 PID。
MySQL远程连接这一节是很多同学的痛。如果你在本地用Navicat连不上服务器数据库,先检查云服务器安全组或防火墙是否放行了3306端口,再检查MySQL用户是否允许远程主机访问。一个比较稳妥的配置是,不开放3306端口,而是在服务器上直接执行SQL脚本,让Spring Boot连接本地的localhost:3306,这样更安全,演示也不受影响。
如果你还要部署前端静态页面,可以利用Nginx做反向代理,把/api路径代理到后端的8080端口:
server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这个配置解决了一个最常见的部署问题:前端页面和后端接口不在同一个域名端口下,导致跨域或路径错乱。用Nginx统一入口后,前后端就变成了同源访问,省掉很多麻烦。
4.3 高频问题与排查方法速查表
我整理了一张毕设调试中最常遇到的排查表,希望能帮你省下几个小时的搜索引擎时间。
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 启动报Failed to configure a DataSource | 未配置数据源或依赖冲突 | 检查application.yml的url、username、password,确认mysql驱动存在 |
| 连接数据库超时 | 时区、SSL或网络问题 | URL加serverTimezone=Asia/Shanghai和useSSL=false |
| 定时任务到点不执行 | 启动类没加@EnableScheduling | 在启动类上补注解;检查cron表达式写的是否正确 |
| 前端跨域报错 | 未配置CORS | 添加CorsConfig或加Spring的跨域注解 |
| WebSocket连接闪断 | 端口被代理占用或鉴权失败 | 检查前端连接地址、是否有网关拦截 |
| 项目启动慢且启动后CPU高 | 依赖了太多不需要的Starter | 去掉无用依赖,避免启动时加载不必要的自动配置 |
| 中文乱码 | 数据库字符集不对 | 建库用utf8mb4,URL加characterEncoding=utf8 |
| 数据库表字段与实体映射不上 | MyBatis驼峰转换没开 | 配置map-underscore-to-camel-case: true |
这张表里的每一个坑,都是我实际跑项目时遇到过不止一次的。尤其第二行和第三行,出现频率最高,很多人熬夜排查到凌晨,最后发现只是少了一个注解或者一个URL参数。其实只要养成一个习惯:启动报错先看完整堆栈,别只看第一行和最后一行,很多答案就在中间那几行Caused by里面。
5. 论文(LW)写作与演示视频录制要点
5.1 与源码对应的论文结构安排
一套合格的毕设论文,目录基本是固定的:摘要、绪论、相关技术、需求分析、系统设计、系统实现、系统测试、总结与展望。但很多同学拿到源码后不知道论文怎么写,我分享一个实操技巧:把源码当成论文的“素材库”。每写到一章,就去源码里找对应的设计证据。例如写“相关技术”时,把Spring Boot、MyBatis-Plus、WebSocket的用途说清楚;写“数据库设计”时,直接引用init.sql里的DDL语句作为附录;写“系统核心实现”时,贴上ReminderService核心方法,并配上状态流转说明。
论文里最容易暴露的问题是“章节之间断层”。前文说设计了WebSocket实时提醒,后面实现里却没有对应代码;前面说支持用户角色权限,实现里却只有一张user表。这个问题的根源是论文和代码没有对齐。这里我建议做一张“需求跟踪表”,每条需求对应一个接口、一个页面和一个测试结果,写论文的时候照着这张表逐项描述,逻辑就缜密了。
我还可以分享一个小技巧:在论文的“系统测试”章节里,不要只贴全绿的测试通过截图,最好加一张边界场景的测试表格。比如“计划结束日期当天的提醒是否正常生成”“同一时间多条药品计划是否同时提醒”“漏服后二次提醒与用户确认是否会冲突”。这些表格一放上去,论文的完成度立刻就不一样了。
5.2 演示视频怎么录才不会翻车
演示视频是答辩前最后一道防线,时长控制在5到8分钟,画质720p以上,分辨率1920x1080。录制前先把系统数据清空,从干净状态开始演示,不要让评委看到一堆测试垃圾数据。演示顺序建议是这样:首页 → 注册或登录 → 添加药品 → 创建服药计划 → 演示定时提醒触发 → 确认服药 → 查看提醒记录 → 展示后台统计数据。
这里有个现场问题:等待定时提醒触发可能要等好几分钟,视频里不能干等。我的办法是,把系统时间调整到计划时间前几分钟,或者预留一个测试接口直接触发sendReminder,然后用剪辑软件把等待过程剪掉,只保留页面弹出提醒的那一瞬间。录制时麦克风声音要清楚,语速不要太快,每演示完一个步骤停顿两秒,给答辩老师看清楚操作位置。
录制工具方面,我习惯用OBS Studio录屏,再用剪映做简单剪辑和加字幕。字幕不是必须的,但如果讲解有口音,还是建议加上。视频里不要出现真实密码或敏感信息,造一些测试数据即可,比如“用户张三、药品阿莫西林、每天08:00/12:00/18:00”。最后导出时,把文件命名成“系统演示.mp4”,放到论文的附录或者压缩包里,方便老师直接查看。
6. 实战避坑经验与扩展建议
6.1 我踩过的一些坑
第一个坑是“重复提醒”。最开始我用固定cron表达式,比如@Scheduled(cron = "0 0 8 * * ?"),结果用户改了计划时间后,旧任务还在跑,新任务没建出来,导致8点发出两条完全一样的通知。后来改成扫描方案后,这个问题就消失了,因为扫描方案只认数据库当前状态,数据库里改了什么,扫描任务就按什么执行。
第二个坑是“时区错位”。数据库里存的时间是CST,Java里读出来变成UTC,导致提醒差8小时,用户明明8点的药,系统11点才提醒。排查后我在JDBC连接串里固定了时区,同时在代码里统一用Asia/Shanghai作为系统默认时区,这才消停。这个坑特别隐蔽,因为本地测试时可能一切正常,部署到服务器后时间就对不上了。
第三个坑是“论文查重”。很多同学把网上的模板和相似系统的文字直接粘到论文里,查重率飙到80%以上,非常危险。我的建议是:论文里所有的技术原理描述用自己的话重写,代码部分尽量用短代码块展示,避免大段粘贴。因为查重系统对“核心功能描述”查得很严,多写点自己项目的界面交互细节,反而容易过。
6.2 给这个项目做加分的扩展方向
如果时间和精力允许,我推荐从这几个方向给系统加分。第一,加一个小程序或APP前端,让提醒真正跟手;第二,给后端接入一个简单的Redis缓存,存用户的最近服药记录,在接口性能上做对比数据;第三,引入硬件模拟,比如通过串口控制一个微型舵机或LED灯,体现“智能药箱”的“智能”属性;第四,在论文里加一章“系统可靠性设计”,讲一下定时任务幂等、数据库事务和日志埋点。
我特别推荐第二种和第四种,因为它们不依赖额外硬件,而且论文里能写的东西非常丰富。比如Redis缓存章节,你可以测一下从MySQL查和从Redis查的耗时差异,把折线图放进论文,这就是一个非常扎实的实验数据。这种细节往往就是答辩老师认可你的关键。扩展方向不要贪多,选一个做深,比列十个没做的好得多。
最后再分享一个我常年跟学生强调的心得:毕设最重要的是“讲得出逻辑”,而不是“代码多华丽”。哪怕你只是用Spring Boot的@Scheduled加一个扫描任务实现了提醒,只要你能把为什么要扫描、为什么用这个时间窗口、漏服怎么处理讲清楚,就已经超过很多人了。拿到别人的源码或者参考项目,第一件事不是改文档,而是自己在本地把项目跑起来,再对照代码走一遍提醒链路。当你亲手把一条提醒记录从数据库补出来,这个项目才算真正是你的,答辩的时候也就自然有底气了。