☰
基于 Spring Boot 的智能药箱系统:Java 毕设全栈实战与部署指南
2026/10/4 2:30:35 网站建设 项目流程

做了几个 Spring Boot 方向的 Java 毕设项目之后,我最推荐拿来练手的就是“智能药箱系统”。这个题目是典型的 JavaWeb 全栈实战:后端用 Spring Boot,前端配 Vue 或者 Thymeleaf,数据库走 MySQL,核心业务围绕“服药时间提醒”展开。功能边界清晰,难度适中,数据模型也好设计,关键是答辩的时候特别好讲——既有业务场景,又有可演示的代码逻辑,还不至于复杂到要把人绕晕。项目打包好了完整源码、LW(论文文档)、部署说明和演示视频,等于一份毕设该有的交付物一次性配齐。这篇就结合我做下来的完整过程,把设计思路、核心模块实现、部署步骤和踩过的坑全部整理出来,给正在做 Java 毕设的同学一个可以直接照着落地的参考。

先说说这个系统到底在解决什么问题。家里有老人需要长期服药的,最头疼的就是漏服、错服、重复服药,子女上班没法时刻盯着。药箱系统把“药”和“提醒”串起来:家属帮老人建档、维护药品信息、设置每天哪几个时间点吃什么药,到点了系统推送提醒,老人可以手动确认“已服药”,家属端能看到记录。听起来简单,但落到代码上要处理用户权限、用药计划、定时扫描、消息推送、记录统计这一整套逻辑。这恰恰就是毕业设计需要的体量——工作量足够,技术点覆盖全面,又不会失控。

1. 项目整体设计与选型思路

1.1 为什么选“智能药箱”作为毕设题目

选毕设题目有个基本原则:业务场景自己要有感知,技术栈要能展示水平,工作量要可控。智能药箱的优势就在于它是个“看得见摸得着”的物联网式业务系统,不像纯电商那样一堆订单购物车模板感太重,也不像纯后台管理系统那样干巴巴的没有亮点。

服药提醒这件事存在明显的“家庭协作”属性,所以角色划分天然就是多角色的:患者(被提醒的人)、家属(维护计划和查看记录的人)、管理员(维护药品字典和系统数据)。多角色就意味着需要权限控制,这是答辩评委一定会关注的点。另外系统里还涉及库存预警、药品有效期提醒、服药统计报表这些业务功能,随便一个都能撑起一个模块的论述,整体做完,功能清单拿出去是完全够看的。

从技术面的角度,这个项目涉及后端 CRUD、定时任务、消息推送、文件上传、数据统计,每个点都是 Java 面试里常被追问的内容,做完之后对面试也有直接帮助。

1.2 Spring Boot 技术栈选型与理由

后端框架直接用 Spring Boot。我这里用的是Spring Boot 2.7.x,搭配JDK 8,没有上 3.x。为什么这么选?Spring Boot 3.x 强制要求 JDK 17,API 上javax包全换成了jakarta,很多老教程和老依赖会出现兼容问题。毕设最怕的不是功能多,而是环境配不通、代码跑不起来。2.7.x 是 2.x 系列的最后一个稳定大版本,技术成熟、资料丰富、兼容性最好,等系统跑通了,你自然明白版本升级是怎么回事。

其他组件选型:

组件选型说明
持久层MyBatis-Plus内置单表 CRUD,省掉大量重复的 XML 编写
数据库MySQL 5.7/8.0开源、普及率高,答辩环境一般都有
权限Spring Security + JWT接口安全、角色控制,技术亮点
前端Vue 3 + Element Plus前后端分离,开发体验好
定时任务Spring 原生 @Scheduled单体系统不需要引入重型的 Quartz
构建Maven毕设标配,别用 Gradle 增加没必要的复杂度
辅助Lombok、Hutool省样板代码,集成一些工具方法

用 MyBatis-Plus 做持久层真的省事。单表的增删改查几乎不用写 SQL,调用IService自带的方法就行。比如用药计划表的新增,直接在 Service 里save(plan)就能落库。省出来的时间全花在业务逻辑上,性价比非常高。

1.3 系统整体架构与角色权限设计

系统定位是“前后端分离的单体应用”,前端部署在 Nginx,后端是一个 Spring Boot 的 jar 包,数据库独立部署。项目的目录结构按模块分包:

src/main/java/com/example/medbox ├── config // 全局配置、安全配置、定时任务配置 ├── controller // 接口层 ├── service // 业务层 ├── mapper // 数据访问层 ├── entity // 实体类 ├── dto // 前端交互数据对象 ├── common // 统一返回结果、异常处理 └── task // 定时提醒任务

角色权限核心设计成三个:

角色核心能力权限特点
患者(Patient)查看自己的提醒消息、确认服药、查看用药计划数据范围仅限本人
家属(Family)维护患者档案、维护药品和用药计划、查看服药记录和提醒报告可操作多个已绑定患者
管理员(Admin)药品字典维护、用户管理、系统参数配置后端管理功能全开

权限这块用 Spring Security + JWT 实现。登录成功后签发 Token,前端请求头里带Authorization,后端通过拦截器把当前用户信息放入上下文中。接口上通过@PreAuthorize("hasRole('FAMILY')")这种注解控制访问权限。这里一个小建议:角色权限不要做太复杂,能讲清楚 JWT 的校验流程和拦截原理就够了,过度的权限模型会让代码变得臃肿,答辩也不好讲。

2. 核心功能拆解与数据库设计

2.1 功能模块拆解

整个系统从使用流程上分,可以拆成六大功能模块:

  1. 用户管理与登录注册:注册以后默认是“家属”角色,支持绑定患者档案,一个家属可以绑定多位老人,比如自己的父母。
  2. 药品档案管理:录入药品名称、规格、生产日期、有效期、用法、库存数量。管理员维护的“药品字典”提供下拉选择,家属录入时不用重复输入。
  3. 用药计划管理:这是核心业务。家属为患者选择药品,设置服用剂量、服药方式、每日服药次数和对应的时间点。支持“每天固定时间点”(如 08:00、20:00)和“每隔 N 小时”两种模式。
  4. 定时提醒任务:后台每分钟扫描一次当前时间需要触发的用药计划,生成提醒消息,并通过站内消息、邮件或 WebSocket 推送给患者和家属。
  5. 服药记录与统计:患者点击“确认已服药”后生成记录。没确认的可以标记“漏服”。家属端可以按日期/药品维度查看依从率统计。
  6. 库存和有效期预警:药品库存低于阈值时生成预警;距离过期失效不足 30 天的,在首页红字提醒。

模块之间是有依赖链条的:药品档案是基础,用药计划依赖药品,定时任务依赖计划,记录又依赖任务推动。这种“链式依赖”本身就是很好的答辩素材,可以画数据流转图,也可以讲清业务闭环。

2.2 数据库核心表设计

数据库设计是答辩一定会被问的环节。智能药箱系统我设计了 8 张核心表,这里展示最关键的几张。

第一张是用户表sys_user:

CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '密码(BCrypt)', `real_name` varchar(50) DEFAULT '' COMMENT '真实姓名', `phone` varchar(20) DEFAULT '' COMMENT '手机号', `role` varchar(20) NOT NULL DEFAULT 'FAMILY' COMMENT '角色', `status` tinyint DEFAULT '1' COMMENT '状态: 1正常 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

第二张是药品表med_medicine,字段包含通用信息。注意expire_date一定要存DATE类型,这样日期比较直接用 SQL 就能算,不用在 Java 里做字符串比较。

第三张是用药计划表med_plan,这是整个系统的核心表:

CREATE TABLE `med_plan` ( `id` bigint NOT NULL AUTO_INCREMENT, `patient_id` bigint NOT NULL COMMENT '患者ID', `medicine_id` bigint NOT NULL COMMENT '药品ID', `dose` varchar(50) DEFAULT '' COMMENT '每次剂量,如: 1片/5ml', `plan_type` tinyint NOT NULL DEFAULT '1' COMMENT '1-固定时间点 2-间隔小时', `remind_time` varchar(20) DEFAULT '' COMMENT '固定时间点,格式HH:mm', `interval_hours` int DEFAULT NULL COMMENT '间隔小时数', `start_time` datetime NOT NULL COMMENT '开始日期', `end_time` datetime DEFAULT NULL COMMENT '结束日期', `status` tinyint DEFAULT '1' COMMENT '1启用 0停用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用药计划表';

remind_time我用的是varchar,直接存字符串“08:00”这种格式。为什么不拆成小时和分钟两个字段?因为解析LocalTime太方便了,一行代码的事。而“间隔小时”模式在定时扫描的时候单独处理,不依赖remind_time字段。

第四张是服药记录表med_take_record:

CREATE TABLE `med_take_record` ( `id` bigint NOT NULL AUTO_INCREMENT, `plan_id` bigint NOT NULL COMMENT '计划ID', `patient_id` bigint NOT NULL, `medicine_id` bigint NOT NULL, `remind_time` datetime NOT NULL COMMENT '计划提醒时间', `take_time` datetime DEFAULT NULL COMMENT '实际确认服药时间', `status` tinyint DEFAULT '0' COMMENT '0-未处理 1-已服药 2-已漏服', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服药记录表';

这张表是把“定时扫描”和“用户确认”连接起来的枢纽。定时任务扫描到某个计划该提醒了,先插入一条status=0的记录,同时在提醒消息表里生成一条消息。等用户点击“已服药”,把这条记录的status改成1。如果一直没点,那么到晚上 22:00 或者第二天凌晨统一跑一个补偿任务,把当天还未处理的记录标记为2(漏服)。

2.3 服药提醒时间的规则设计细节

提醒规则是整个项目最核心的业务逻辑,也是答辩最容易展开讲的地方。我把它拆成两个模式:

固定时间点模式:用户选择了“一天两次”,分别在 08:00 和 20:00。存储层面就是把plan_type设为 1,然后在remind_time字段用逗号拼接存储,比如"08:00,20:00"。虽然不够范式化,但对于毕设来说最直观,查询时解析一下字符串就行。定时任务每分钟扫描一次:

String sql = "SELECT * FROM med_plan WHERE plan_type = 1 AND status = 1 " + "AND ".concat(parseRemindTimeCondition());

实际用 MyBatis-Plus 写的话,就是查出所有启用的固定时间点计划,在内存里面做LocalTime.now()和计划时间比对。因为数据量很小,这种内存比对方式完全没问题,而且逻辑透明好讲。

间隔小时模式:比如“每隔 6 小时一次”,那么今天 08:00 第一次服药,下次就是 14:00、20:00……这个模式就不能单靠计划表存时间实现了,需要结合“最后服药时间”(last_take_time)。实现思路是:每次用户确认服药后,更新该计划的last_take_time为当前时间;定时扫描时检查now - last_take_time >= interval_hours,如果满足就生成新的提醒。这样设置有个好处,用户连续漏服两次后,系统依然能基于最后一次服药时间继续推算出下一轮提醒,不会出现时间堆积错乱。

这里有一个我实际开发中踩过的坑:如果“固定时间点”和“间隔小时”共用一张计划表,查询时就要特别注意plan_type的过滤条件。最开始我把两类的判断条件混在一起写,结果“固定时间点”的计划也会被“间隔小时”分支扫到,导致重复提醒。后面把查询条件抽成了两个独立的方法,一个查plan_type = 1,一个查plan_type = 2,互不影响,问题彻底解决。

3. 关键模块实操实现与项目部署

3.1 基于 Spring 原生 @Scheduled 的定时提醒任务

定时任务是整个系统的“发动机”。实现方式就是在启动类上加上@EnableScheduling,然后在任务类方法上标@Scheduled(cron = "0 0/1 * * * ?"),让方法每分钟执行一次。

@Component @Slf4j public class RemindTask { @Autowired private MedPlanService medPlanService; @Autowired private TakeRecordService takeRecordService; @Scheduled(cron = "0 0/1 * * * ?") public void scanRemind() { List<MedPlan> plans = medPlanService.listEnabledPlans(); LocalTime now = LocalTime.now(); for (MedPlan plan : plans) { if (plan.getPlanType() == 1) { handleFixedTimePlan(plan, now); } else if (plan.getPlanType() == 2) { handleIntervalPlan(plan); } } } private void handleFixedTimePlan(MedPlan plan, LocalTime now) { String remindTimes = plan.getRemindTime(); // "08:00,20:00" String[] times = remindTimes.split(","); for (String time : times) { LocalTime target = LocalTime.parse(time.trim()); if (now.getHour() == target.getHour() && now.getMinute() == target.getMinute()) { generateRemind(plan, LocalDateTime.now()); } } } }

扫描逻辑简单来说就是“每个整分钟把当前时间与计划时间比对,相等就触发”。有一个细节必须注意:方法的执行时间如果恰好跨分钟,比如 08:00:59 才执行到这条计划,比对getMinute()就出问题了。我现在的写法是把分钟号存到一个Set里做去重,同一个计划在一分钟内最多只触发一次,避免重复推送。实现方式是加一个内存标记,比如在计划实体上维护一个lastRemindDate字段,每次生成提醒前判断是否已经有当天的记录。

3.2 提醒消息多渠道触达设计

提醒生成之后,怎么触达到用户?我做了三层设计:

  1. 站内消息:写入med_message表,前端首页右上角红点提示,点击进去能看到提醒详情。这是毕设最稳妥的展示方式,也方便录演示视频。
  2. WebSocket 实时推送:患者和家属端保持 WebSocket 连接,服务端一旦生成新提醒,立刻推送到前端弹窗,同时播放提示音。自己写 WebSocket 其实不复杂,用 Spring 的TextWebSocketHandler+ 拦截器鉴权就行。
  3. 第三方通知(预留接口):短信、微信通知属于成熟服务,个人项目申请不到服务商资质,所以这块我用接口预留的方式处理,定义好NotifySender接口,先写一个LogNotifySender把消息打到控制台模拟发送。这样论文里可以写“支持接入阿里云短信/微信公众号模板消息”,答辩时也能自圆其说。

这里要特别强调一下设计上的取舍:不要为了“看起来高级”硬上消息中间件。RabbitMQ、Kafka 这些组件在单体毕设里根本没有场景,引入反而会让自己陷入环境搭建的泥潭。一个接口 + 两个实现类,既展示了面向接口编程的思维,又不会影响系统稳定性。

3.3 前后端联调与 IDEA 配置要点

后端接口开发完成后,用 IDEA 启动 Spring Boot,默认端口 8080。有几点经验参考:

IDEA 配置启动端口:默认情况下 Spring Boot 的启动端口从application.yml读取:

server: port: 8080 servlet: context-path: /api

我习惯加一个context-path: /api,这样后端接口统一带/api前缀,前端代理时就不容易和后端静态资源路径冲突。如果想在 IDEA 里临时设置端口,可以在Run/Debug Configurations的VM options里加-Dserver.port=8081,覆盖 yml 配置。这个技巧在端口被占用时特别好用。

前端 Vue 联调代理:开发环境下,在 Vue 项目的vite.config.js里配置代理,避免跨域:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

生产环境把 Vue 打包进 Spring Boot:这个是很多同学卡住的点。毕设为了部署方便,往往不单独部署前端服务,而是把 Vue 打包后的dist/目录塞进 Spring Boot 的src/main/resources/static/,这样直接启动 jar 包就能访问页面。注意几个地方:打包前要把接口请求地址改为相对路径;Vue Router 要用createWebHashHistory(),不要用createWebHistory(),否则刷新页面会 404;打包后的index.html里引用的 JS/CSS 路径必须是./开头。这几个坑我都踩过,前两个尤其隐蔽。

3.4 项目打包与 Linux 服务器部署全流程

毕设评审通常要求系统能现场跑起来。我用的部署方式是:打包成 jar 包 + Nginx 反向代理 + 宝塔面板管理 MySQL。

后端打包:

mvn clean package -DskipTests

打包出来的 jar 在target/目录下,把这个文件传到服务器,用命令启动:

java -jar medbox-1.0.0.jar --spring.profiles.active=prod

生产环境配置文件挂在同目录下的application-prod.yml,并设置数据库连接。需要注意 MySQL 必须开启远程连接,或者在部署机本地装 MySQL 并用上数据库账号密码。

Nginx 配置如下:

server { listen 80; server_name your-domain.com; location / { root /home/www/medbox-front; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

前端页面走 Nginx 的静态文件服务,后端接口走反向代理。一个值得注意的坑:proxy_pass的 URI 要写成http://127.0.0.1:8080/api/而不是http://127.0.0.1:8080/,否则后端接口路由就丢了/api前缀。

部署完成后的自测路径也很固定:先访问前端页面确认能打开 → 再看登录接口能否通 → 再测核心的创建用药计划和提醒消息。用 Chrome 的 Network 面板能看到请求是否 200、响应结构是否正常,这一步能省下大量排查时间。

4. 毕设常见问题与答辩避坑实录

4.1 Spring Boot 版本与 JDK 兼容性大坑

这个可能是做毕设时遇到最多的问题,尤其最近几年新教程全在推 Spring Boot 3.x,但很多同学本机还装着 JDK 8。Spring Boot 3.x 必须 JDK 17+。如果你下载的源码是 3.x 版本,本机却是 JDK 8,那启动必然报UnsupportedClassVersionError。

还有更隐蔽的:Spring Boot 3.x 把javax.servlet全部改成了jakarta.servlet。如果你的代码里还有:

import javax.servlet.http.HttpServletRequest;

在 3.x 环境下一定会编译失败。但这个问题在 2.x 环境是正常的。所以拿到项目源码第一件事,就是检查pom.xml里的 Spring Boot 版本和本机 JDK 版本,对照表整理如下:

Spring Boot 版本JDK 要求javax/jakartaMyBatis-Plus 适配版本
2.7.xJDK 8 或 11javax3.5.x
3.2.xJDK 17 或 21jakarta3.5.3.2+ 或使用 mybatis-plus-spring-boot3-starter

如果只知道“用最新版”,很容易就把自己坑进去。我的建议很直接:毕设环境统一用 Spring Boot 2.7.x + JDK 8,绝对稳定。这个组合能覆盖绝大多数教程和源码案例。

4.2 定时任务阻塞与重复提醒问题

@Scheduled默认情况下是单线程串行执行的。什么意思?如果你在系统里写了两个定时任务,任务 A 执行耗时 5 分钟,那么任务 B 就算到了触发时间,也必须等任务 A 跑完才能执行。我的系统刚开始没注意这个问题,导出的统计报表任务和提醒任务互相卡,提醒全乱套了。

解决办法是在配置类里自定义一个线程池来隔离任务:

@Configuration @EnableScheduling public class SchedulingConfig implements SchedulingConfigurer { @Bean public TaskScheduler taskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(5); scheduler.setThreadNamePrefix("medbox-task-"); return scheduler; } }

线程池设成 5,提醒、统计、库存预警各跑各的,互不干扰。这个配置建议所有做定时任务的同学直接抄下来,不会出错。

另一个问题就是重复提醒。如果不做任何清洗,假设服务器性能有问题,某一分钟内的任务跑了两次,那患者就会收到两条完全一样的提醒消息。我的方案是在生成提醒前,查一下med_message表里是否已经存在“该计划 + 该提醒日期 + 未读取”的记录,存在就跳过。这相当于在消息生成层面做了一次幂等。

4.3 数据库乱码与时间差问题

中文乱码是老生常谈,但每次都能坑到人。MySQL 建表字符集必须统一用utf8mb4,如果你的服务器 MySQL 默认字符集是latin1,连接字符串要加上参数:

jdbc:mysql://localhost:3306/medbox?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

时间问题更值得注意。服药提醒是一个对时间极度敏感的系统,如果服务器时区是 UTC,而你的业务用北京时间,那么定时扫描出来的时间会差 8 个小时,早上 8 点变成中午 16 点。所以在application.yml中一定要设置:

spring: jackson: time-zone: Asia/Shanghai date-format: yyyy-MM-dd HH:mm:ss

同时在 MySQL 连接 URL 里加serverTimezone=Asia/Shanghai。这两处都设置好,时间戳才不会有偏差。

4.4 论文 LW 撰写与答辩高频问题

论文这部分,很多同学是“项目做完了,文档不知道怎么写”。智能药箱这个项目的 LW 章节结构,我的经验是这么安排:绪论(背景意义、国内外现状)、相关技术介绍(Spring Boot、MyBatis-Plus、Vue、JWT)、系统需求分析(功能性需求、非功能性需求、用例图)、系统设计(架构设计、数据库设计、接口设计)、系统实现(各模块截图 + 核心代码讲解)、系统测试(功能测试表、性能测试数据)、总结与展望。这七章足够撑起一篇条理清晰的毕设论文了。

写论文一定要记着:每个功能模块要有截图 + 代码片段 + 文字说明,三者缺一不可。评审老师不可能一行行读代码,但他们会看你怎么描述自己的代码。描述时多写“通过 XX 实现了 XX,这样设计的原因是 XX”,这种句式最贴合学术写作习惯。

答辩的提问方向,我根据自己的经验列一下高频题目:

高频问题回答要点
为什么用 Spring Boot 不用 SSM?自动配置、内置容器、starter 生态、开发效率
定时任务怎么实现?会不会阻塞?@Scheduled 扫描 + 线程池;对比 Quartz 差异
如果提醒消息发送失败怎么办?消息表留状态位,失败重试;预留第三方接口
系统最多支持多少用户?单体架构下 500 并发以内;性能优化可以上 Redis 缓存
数据库为什么这么设计?从业务模型出发解释表关系、主外键逻辑

4.5 拿到“完整源码+一条龙”之后,你真正要做的事

现在网上很多标题挂着“完整源码+LW+部署说明+演示视频”,我理解大家的心理:想省事。但能做完整套项目交付,不等于你能答辩通过。光把项目跑起来只是第一步,要真正吃透它,我建议你在拿到项目后按这个顺序走一遍:

  1. 先跑通:按部署说明把项目在本地 IDEA 里启动起来,数据库导入 SQL,接口全部测一遍。
  2. 画架构图:自己不看源码,把系统的功能模块图、数据库 ER 图画出来。画不出来的地方就是你还没搞懂的地方。
  3. 改一个需求:找一个不起眼的小功能自己动手改一遍,比如把“固定时间点”改成支持“周一到周五执行”。这个“微改造”会在答辩时成为你的护身符——老师一听你能现场改代码,基本不会再追问过深。
  4. 提前模拟答辩:把上面那张问题表打印出来,找同学模拟提问,提前组织回答口径。

演示视频的录制也有讲究。我录的时候发现,直接把屏幕一录容易乱,后来统一了口径:先展示登录 → 添加药品 → 给患者创建用药计划 → 设置一个马上到点的提醒 → 快速演示推送和确认服药 → 打开统计页看报表。一条业务主线走完,整个过程不超过 5 分钟,但系统的每个核心功能都覆盖到了。演示前一定要检查系统时间和你设置的提醒时间差,不然等提醒等了两分钟没反应,现场会很尴尬。

我个人在实际操作中最大的体会是:智能药箱这个项目,难点不在功能多,而在于“提醒”这个业务对准确性要求极高,在任务调度、时间比对、幂等去重这些细节上多花心思,项目的质感会明显上一个档次。最后再分享一个小技巧——给用药计划表加一个test_mode测试开关字段,演示时把提醒间隔临时调成 30 秒,录出来的视频节奏紧凑,看起来效果专业很多。这个字段虽然不复杂,但在答辩现场是真能救场的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询