每年毕业设计季,“基于Spring Boot的智能药箱系统”都是Java方向的高频选题。这个题目火有火的道理:它背后是物联网设备、后端服务、前端管理三件事的交叉,既能展示框架基本功,又能聊出点“智能”的味道。但越热门的题目越容易两极分化——有人把智能药箱做成了普通药房的增删改查,答辩时连“服药提醒是怎么触发的”都解释不清;有人却能从设备上报、定时任务、预警推送一路讲到数据库索引优化,评委点头的频率完全不一样。这篇不打算写成功能清单,而是一次从选题到交付的完整复盘,重点覆盖Spring Boot服务端设计、硬件联调、远程调试和资料整理,适合正准备动手做毕设、或正在评估“源码+文档+远程调试”项目方案的读者。
1. 为什么选“智能药箱”作为Spring Boot毕业设计:选题红利和投入产出比
1.1 这个题目能同时兼顾技术深度和展示效果
智能药箱难吗?单看后端,无非是用户、药品、药箱、用药计划几张表的增删改查;但如果只做到这一步,论文和答辩都会很单薄。真正值得做的是它的业务闭环:到了服药时间,服务端要判断“该提醒谁、以什么方式提醒”;用户取药之后,设备上报事件,服务端要更新库存、生成服药记录;如果没取药,还要进入逾期未服药的流程。这个闭环天然包含了定时任务、消息推送、接口设计、状态机和异常处理,每一个点都是答辩时可以深入展开的素材。
从技术栈上看,Spring Boot是后端主框架,搭配MyBatis-Plus操作数据库,权限用JWT,提醒用定时任务加邮件或WebSocket,前端用Vue写管理后台和小程序页面,设备端用ESP32或ESP8266上报HTTP请求。一套做下来,简历上可以写的技术关键词会非常密集。更重要的是,智能药箱有实际的演示价值:摆一个真实的药箱模型在现场,按键触发取药,后台实时显示记录,这种效果比纯软件项目的PPT截图直观得多。
1.2 工作量拆解:一张表看清你要动哪些模块
很多人拿到题目第一反应是“先写代码”,这是错误动作。正确做法是先拆工作量。我用一张表整理过智能药箱系统最常见的模块分配,你对照自己的能力和时间,基本能算出需要多久:
| 模块 | 核心功能 | 技术考点 | 建议优先级 |
|---|---|---|---|
| 用户与权限 | 注册登录、家人授权、角色切换 | JWT、拦截器、密码加密 | 高 |
| 药品与库存 | 药品CRUD、库存扣减、低库存预警 | MyBatis-Plus、事务 | 高 |
| 用药计划 | 每日计划、到点提醒、状态流转 | @Scheduled / Quartz | 高 |
| 服药记录 | 设备上报、记录查询、补录 | REST接口设计 | 高 |
| 消息通知 | 邮件、WebSocket、小程序订阅消息 | JavaMailSender、WebSocket | 中 |
| 硬件接入 | 设备绑定、数据上报、心跳检测 | HTTP/JSON、验签 | 中 |
| 数据统计 | 按时服药率、近7天趋势图 | ECharts、聚合SQL | 中低 |
| 管理后台 | 用户管理、数据看板 | Vue + Element UI | 中 |
如果你是单人完成,建议把前四项做到完整,后面四项做成可演示的简化版本。答辩老师更关心“是否跑通了完整链路”,而不是每个模块都堆满按钮。
1.3 技术栈基本盘:版本和框架的一次性选择
做毕业设计最忌讳“求新”。Spring Boot 3.x虽然已经出了很久,但它要求JDK 17以上,很多老教程里的代码在javax和jakarta包名上会直接报错。我的建议是坚定不移地使用Spring Boot 2.7.x + JDK 8,这不是守旧,而是为了把精力放在业务实现上。MyBatis-Plus选3.5.x版本,对Spring Boot 2.7兼容很好;数据库用MySQL 5.7或8.0都行,连接串里一定要写serverTimezone=Asia/Shanghai。
前端如果自己做,Vue 2 + Element UI最容易上手,网上资料最多;如果不想写前端,也可以直接做一个Spring Boot渲染Thymeleaf的简化版,但展示效果会差一些。Redis属于加分项,用来做缓存和分布式锁,没有硬件资源时可以不引入,把单机逻辑跑通、答辩时能说清楚“为什么这里可以加Redis”就够了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构和数据模型:先画清楚边界,再开始写接口
2.1 药箱端、服务端、用户端三端之间的数据流
一个真实智能药箱系统的数据流大概是这样的:用户在小程序或管理后台绑定药箱,然后创建用药计划;Spring Boot服务端保存计划后,通过定时任务扫描当前时间是否进入提醒窗口。如果到了时间,服务端推送提醒给用户和绑定的家人,药箱设备也会通过轮询或者长连接收到“打开对应药仓”的指令。用户取药后,药箱上的重力传感器或红外传感器检测到动作,把“哪个药仓、什么时间、取出多少”上报给服务端,服务端更新库存并生成一条服药记录。如果用户没有按时取药,服务端还要根据逾期策略做二次提醒。
这个数据流有一个容易忽略的点:设备上报和用户手动操作是并存的。比如用户提前把药拿出来,或者取错了药仓再放回去,设备上报的语义会变得复杂。我建议在系统设计阶段就把“取药事件”定义为独立实体,而不是在用药计划表上直接打勾,否则后续统计服药记录时会非常痛苦。
2.2 数据库表的必备清单
我见过很多智能药箱项目的数据库只有四张表:用户、药品、计划、记录。不是不能跑,而是很多业务细节没法表达。最少需要以下几张表:
- user:用户基本信息,包含手机号、密码、角色,用户表里最好加一个user_type字段区分管理员、普通用户、家人。
- cabinet:药箱表,记录药箱编号、设备编号、名称、安装地点、状态。
- cabinet_user_bind:药箱和用户的绑定关系,一个药箱允许绑定多个用户,家人通过绑定关系查看服药情况。
- medicine:药品表,包含药品名称、规格、库存总量、预警阈值、存放药仓编号、图片URL。
- medication_plan:用药计划表,包含计划时间、剂量、用药方式、状态、创建人、执行人。
- medication_record:服药记录表,记录实际取药时间、取药数量、取药结果,设备上报后插入。
- medicine_stock_log:库存流水表,每次库存变动都写一条,便于溯源。
- notification_log:推送记录表,记录向谁推送、推送方式、推送内容、是否成功。
- device_heartbeat:设备心跳表,记录设备最后在线时间,用于判断离线。
这些表设计好之后,有几类字段别漏:所有表建议有create_time和update_time;业务状态字段不要用布尔值直接表示,比如plan_status用字符串“PENDING”、“DONE”、“MISSED”、“CANCELED”,后续扩展状态会容易很多。
2.3 状态字段设计:用药计划的状态机
用药计划是最容易出业务漏洞的地方。我建议把状态设计成一张状态机并在文档里画清楚:
- 待执行:计划已创建,尚未到提醒时间。
- 已提醒:已触发推送,但用户还没上报取药。
- 已完成:设备上报或用户手动确认服药。
- 已错过:超过允许时间窗口仍然未服药。
- 已取消:手动取消或药品停用。
另外要区分“计划时间”和“实际执行时间”,两者都要存。很多人只存一个“计划时间”,导致错过提醒后无法判断“这个人到底是晚了一小时,还是根本没吃”。为了支持“延迟服药”这类现象,可以增加一个提醒窗口字段,比如允许在计划时间前后30分钟内取药。过了窗口就算逾期。
2.4 业务上的一个易错点:库存扣减要按“取药行为”而不是“计划时间”
这个坑非常经典。初学者会在定时任务触发时直接把库存减掉,看起来逻辑顺了,实际上完全错误。因为提醒是提醒,取药是取药,中间有不确定性。比如到点提醒了,但用户没有取药,库存不能扣;用户提前半小时就取药了,计划还没触发,但库存已经少了一颗。正确的做法是:只把“设备上报的取药事件”作为库存扣减的依据,定时任务只负责生成提醒和改变用药计划状态,不直接操作库存。这样才能保证库存数据和物理药箱里的药量一致。
3. Spring Boot 服务端落地:从依赖配置到核心业务实现
3.1 项目初始化:依赖选哪些,版本怎么定
创建项目时,建议直接用Spring Initializr生成,Group填com.example,Artifact填smart-medicine-cabinet,语言Java,版本2.7.18。核心依赖尽量精简,下面这份pom内容足够跑通主要功能:
xml复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
如果要用JWT,再加jjwt相关依赖;要用Swagger生成接口文档,加knife4j依赖。但我不建议一开始就把所有依赖都加上,等业务写到哪一步再加,这样能减少版本冲突的排查范围。
3.2 统一返回体、异常和跨域:先把“壳”做稳
写接口前,先统一返回体。很多人每个Controller都自己new一个Map返回,到后期前后端联调会乱到怀疑人生。我习惯定义Result类,包含code、message、data三个字段,成功返回200,业务异常返回具体业务码,系统异常返回500。再用@RestControllerAdvice统一捕获异常,防止错误堆栈直接暴露给前端。
跨域问题也建议提前解决。开发环境前端在8081端口,后端在8080端口,必然跨域。可以直接在配置类里实现WebMvcConfigurer,重写addCorsMappings,允许所有来源;线上部署如果前后端同源,可以再收紧。
3.3 用药提醒的定时任务:@Scheduled够用的边界在哪
智能药箱最常见的实现是扫描数据库里的用药计划,到点触发提醒。用Spring Boot自带的@Scheduled就能实现,核心是把“提醒条件”写成一条可查询的SQL。
java复制@Component
@Slf4j
public class MedicationRemindTask {
@Resource
private MedicationPlanService medicationPlanService;
@Resource
private NotificationService notificationService;
@Scheduled(cron = "0 0/1 * * * ?")
public void scanPlan() {
LocalDateTime now = LocalDateTime.now();
List<MedicationPlan> plans = medicationPlanService.findNeedRemind(now);
for (MedicationPlan plan : plans) {
try {
notificationService.pushRemind(plan);
medicationPlanService.markReminded(plan.getId(), now);
} catch (Exception e) {
log.error("推送提醒失败, planId={}", plan.getId(), e);
}
}
}
}
这里有个细节:扫描频率不要太高,每分钟一次就够,避免重复推送。同时要记录“已提醒”状态,避免同一个计划这分钟推一次、下分钟又推一次。如果毕设需要做更复杂的动态调度,比如用户自定义多个提醒时间,再用Quartz也不迟;对大多数演示场景,@Scheduled加一个状态字段是最稳妥的方案。
3.4 提醒推送落地:邮件、WebSocket、订阅消息怎么选
提醒推送是智能药箱的“智能感”来源,但选错渠道会很痛苦。
| 渠道 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 邮件 | 免费、实现简单、稳定 | 实时性一般 | 毕业设计兜底方案 |
| WebSocket | 实时性好、不用额外费用 | 必须保持连接 | 网页管理端实时通知 |
| 小程序订阅消息 | 用户触达好 | 需要小程序资质、模板审核 | 有真实产品化需求时 |
| 短信 | 即时性强 | 付费、模板审核折腾 | 不适合预算有限的毕设 |
我给的最优解是:网页端用WebSocket推送,邮件作为备选。JavaMailSender在Spring Boot里配置很简单,只需要在application.yml里填邮箱的SMTP信息。给用户推送时,同时写notification_log表,这样答辩时能展示“通知记录”的历史数据。
3.5 JWT + 拦截器:设备端和用户端要分开认证
用户端用JWT,这没什么好说的。容易被忽略的是设备端认证,智能药箱不是浏览器,不能用Cookie,也不该让设备拿用户的JWT到处请求。我建议给每台药箱分配deviceNo和deviceSecret,设备上报数据时用HMAC算法生成签名,后端按设备编号查询密钥并验签。用户端接口和设备端接口要用不同的拦截器路径,比如 /api/user/** 用JWT,/api/device/** 用签名校验。这样设计既安全,答辩时还可以讲“如何防止非法设备伪造上报数据”。
4. 硬件接入、接口联调和远程调试:智能药箱真正“智能”的关键环节
4.1 硬件选型:别一上来就做机械臂
很多学生想把药箱做成全自动出药机,用步进电机加机械臂把药推出来。这个想法很好,但工作量可能让你无法按时毕业。毕业设计更合适的方案是“智能药箱 + 半自动取药”:用ESP32或ESP8266开发板,配合舵机控制药仓门、重力传感器检测取药动作、OLED屏幕显示服药信息。ESP8266的优势是便宜、支持HTTP请求,可以直接把事件POST到Spring Boot接口,省去串口协议解析的麻烦。
如果你完全不想碰硬件,也可以做一个模拟硬件端的小程序页面,通过按钮模拟设备上报。但我会建议至少保留一个真实的设备端演示,哪怕只用面包板和LED灯实现“到点亮灯、按键模拟取药”,也比纯软件截图更有说服力。
4.2 设备上报接口:直接上JSON,别用TCP私有协议
设备端往Spring Boot上报数据,最好的方式就是HTTP + JSON。接口可以设计成:
json复制POST /api/device/report
Content-Type: application/json
{
"deviceNo": "CAB-001",
"action": "TAKE",
"medicineCode": "MED-1001",
"slotNo": 1,
"timestamp": 1735689600,
"sign": "a1b2c3d4e5f67890"
}
后端收到后,先验签,再根据deviceNo找到对应的药箱,根据medicineCode和slotNo找到药品,最后插入服药记录并扣减库存。要注意的是,设备上报的时间戳有可能和服务器时间不一致,不能直接用数据库当前时间当作实际服药时间,应该以设备上的操作时间为准,但要允许一定误差范围。
4.3 远程调试怎么搭:IDEA Remote Debug 和端口映射
远程调试是这套毕业设计很实际的场景:代码在学生本机,但答辩教室、导师演示、联调环境不在同一个局域网。第一种做法是IDEA的远程调试功能。Spring Boot启动时增加JVM参数:
bash复制java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar smart-cabinet.jar
然后在IDEA里配置Remote JVM Debug,Host填服务器IP,Port填5005,启动Debug后就能像本地一样打断点。需要注意JDK 8的address写法是address=5005,JDK 9以上可以写address=*:5005;云服务器还要在安全组和防火墙里开放5005端口,否则会一直卡在“Connect timed out”。
第二种做法是用端口映射工具,把本机8080端口映射出一个公网地址。这样宿舍里的设备端、手机上跑的小程序、甚至导师的浏览器都能直接访问。这类工具很多,选一个免费额度够用就行。调试完之后记得把映射关掉,不然本机服务会一直暴露在公网上,这是很多学生容易忽略的安全细节。
4.4 断网重连、心跳和补报:看起来是IoT,其实考的是幂等
设备上报最怕重复。药箱断网后数据积压,网络恢复后一次性把几次取药事件全部补报上来,后端如果没有去重逻辑,库存会扣得莫名其妙。解决办法有两个:一是在设备上报请求里增加requestId作为全局唯一标识,后端用requestId查重;二是用deviceHeartbeat表记录心跳,超过阈值判定设备离线,等恢复后再触发一次“补报补偿”。这两个点写进论文,评委基本都会觉得你考虑到了真实场景。
5. 毕业设计最容易翻车的点:我的踩坑清单和避坑思路
5.1 Spring Boot版本选太高,老代码直接不兼容
Spring Boot 3.x把javax.servlet改成了jakarta.servlet,很多教程里的代码直接编译不过。如果你没有充足时间踩兼容性的坑,直接锁定Spring Boot 2.7.18,这是2.x系列的最后一版,稳定且资料多。同理,MyBatis-Plus选3.5.x,别选刚从4.0改名的新版本。
5.2 定时任务在多实例下重复执行
如果你图省事把系统同时部署在一台服务器和本地电脑上,两台都在跑同一个@Scheduled任务,用户会收到重复提醒。毕业设计阶段只跑一个实例就好;如果答辩老师问“高并发场景怎么办”,你可以回答“用Redis分布式锁或数据库锁保证同一时刻只有一个实例执行任务”,提前准备一段实现思路即可。
5.3 MySQL时区和服务器时区不一致导致提醒提前或延后一小时
这个坑我印象太深了。本地数据库连接串没写serverTimezone,服务器时区是UTC,结果所有用药计划到点提醒都慢了一小时。配置里这样写:
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/smart_cabinet?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
代码里所有时间字段都用LocalDateTime,不要用Date加字符串拼时间,否则前后端时区问题会一个接一个。
5.4 本地能跑,答辩机上一个数据库连不上
很多演示环境用的是别人的电脑,MySQL版本不一样,SQL文件导入后字符集乱码。解决方法是准备一个一键启动脚本,SQL文件里所有表名和字段名固定用utf8mb4,导入前确认数据库编码。项目能跑不等于可复现,宁可多花半天把环境打包好。
5.5 远程调试时防火墙和云安全组没开端口
排查顺序很重要:先在本机curl localhost:5005,再在云服务器上curl 127.0.0.1:5005,最后用外部电脑telnet公网IP的5005端口。很多时候不是Spring Boot的问题,而是云服务器安全组入方向规则没有添加端口。建议调试时把远程Debug端口设置成一个不常用端口,并在用完立刻关闭。
5.6 演示数据太假,一眼被看出是临时造的
不要在数据库里放“测试1”“测试2”这种数据。提前用常见的感冒药、降压药、维生素造一批真实感强的记录,安排好一条完整的故事线:药箱绑定了一位老人,家属推送了一条用药计划,到点提醒后有人在网页端看到待办,取药过后库存减少、记录出现。答辩时按这条线演示,效果会比临时点按钮强很多。
6. 从源码到文档:让这套项目成为能交付的毕业设计
6.1 源码结构怎么组织才算“规范”
很多人的源码目录是自己一路写出来的,结果要交文档时发现连自己都找不到代码。建议从一开始就按标准结构组织:
text复制smart-medicine-cabinet/
├── sql/
│ └── smart_cabinet.sql
├── src/
│ ├── main/
│ │ ├── java/com/example/cabinet/
│ │ │ ├── common/ // 统一返回体、异常、常量
│ │ │ ├── config/ // 跨域、拦截器、WebSocket配置
│ │ │ ├── controller/ // 接口层
│ │ │ ├── dto/ // 入参对象
│ │ │ ├── entity/ // 实体类
│ │ │ ├── job/ // 定时任务
│ │ │ ├── mapper/ // MyBatis-Plus Mapper
│ │ │ ├── service/ // 业务层
│ │ │ └── vo/ // 返回对象
│ │ └── resources/
│ │ ├── mapper/ // XML文件
│ │ └── application.yml
│ └── test/
└── docs/
└── 设计文档.md
这种结构的好处是Controller只做参数接收和结果转发,业务逻辑全在Service层,答辩时老师问“某个业务怎么实现的”,你可以直接定位到Service方法,印象分会高不少。
6.2 文档写作顺序:先画流程,再写设计,最后补接口文档
我见过很多人把文档留到最后一周,对着代码空想,写出来全是流水账。正确顺序是先画业务流程,把“绑定药箱、创建计划、到点提醒、取药上报、库存更新”这个主链路用一张图理顺,再写需求分析和数据库设计,最后用Swagger或knife4j自动生成接口文档。接口文档能自动化就自动化,手写几十个接口的请求参数既累又容易漏。
6.3 “全包定制”到底包什么:需求评估和范围控制
“源码+文档+远程调试+全包定制”这类说法在毕业设计市场里很常见,如果材料是自己参考整理的,更应该有边界意识。无论是帮别人出方案,还是评估别人给的方案,都需要一份需求确认清单:用户角色有哪几种?药箱是否需要真实硬件?提醒方式选择邮件、短信还是小程序?药品字段除了名称、规格,是否需要图片和条码?这些不确认清楚,需求会在一周内连续变三次。
我一般会把系统拆成“必做、选做、演示”三个优先级。必做是登录、用药计划、提醒、服药记录、库存预警,选做是家属监管和数据统计,演示是硬件上报和WebSocket实时通知。先完成必做,再一步一步加选做,避免一开始陷在“要不要做App”这种问题里。
6.4 一个最实用的建议:把关键流程录成演示视频
答辩现场最容易翻车的不是代码,而是网络和设备。你以为能现场演示硬件,结果开发板没电,或者教室Wi-Fi把内网服务拦了。建议提前用录屏软件把三段核心流程录成视频:第一段是登录并绑定药箱,第二段是创建用药计划并触发到点提醒,第三段是设备取药上报后库存和记录同步变化。每段控制在两分钟以内,答辩时先放视频,再现场操作半个关键流程,既稳又有说服力。这也是我每次带项目复盘时都会提醒别人做的一步,关键时刻真的能救场。
