基于Spring Boot+Vue的智能药箱系统:药品管理、库存预警与用药提醒实战解析

发布时间:2026/10/17 4:53:58
基于Spring Boot+Vue的智能药箱系统:药品管理、库存预警与用药提醒实战解析
智能药箱听名字像是物联网硬件项目实际做出来才明白它更像是一个“药品领域的进销存提醒服务”的管理系统。作为计算机毕业设计选题Spring BootVue这套前后端分离的技术组合加上药品录入、库存预警、用药提醒这些贴近真实场景的业务模块既能展示扎实的CRUD基本功又能体现定时任务、权限控制、可视化统计这类加分项是性价比很高的一个方向。这篇内容我把整个项目的拆解思路、核心实现和调试中容易踩的坑完整梳理一遍方便正在做类似选题的同学直接参考。1. 项目整体思路与选题定位1.1 这个系统到底解决什么问题先别急着打开代码。任何一个毕业设计项目动手之前必须把“系统给谁用、用来干嘛”想清楚。智能药箱系统表面上是管理一个物理药箱实际上可以拆成两个层面的需求家庭个人用户层面家里老人慢性病多药盒里堆了一堆药经常忘记吃、重复买、放过期。需要一个工具来记录药品信息、设置服药计划、到点提醒、库存不够时预警。药品管理层面不管是家庭小药箱还是药店/医务室药品都有入库、出库、库存盘点、有效期管理这些流程。系统本质上是一个带业务规则的药品信息管理系统。明白了这一点你就能在答辩时把“为什么做这个系统”讲得很有底气——它不是简单的增删改查而是围绕药品生命周期入库→存储→服用/出库→补货→过期处理做了一套完整的业务闭环。1.2 为什么技术栈选了Spring Boot Vue现在的Java毕设十有八九是Spring Boot Vue前后端分离。这个组合确实是最稳的选择我拆开讲讲原因后端Spring Boot内置Tomcat不用单独配置服务器Starter机制让依赖管理变得极其简单加上Spring生态自带的事务管理、AOP、定时任务支持做药品提醒这类功能非常顺手。毕设答辩时面试官或评委老师基本都懂这套沟通成本低。前端VueVue 3或者Vue 2配合Element Plus组件库做后台管理界面效率极高。表格、表单、弹窗、选项卡都现成的一个药品管理页面半天就能拼出来。Vue的双向绑定和数据响应式机制做搜索筛选、联动选择这种交互特别方便。前后端分离的优势接口文档定好前后端可以并行开发部署时也可以分开前端扔Nginx后端打包成Jar跑起来就行。还有个很实在的好处——答辩演示的时候前端挂了可以单独修后端崩了也不影响静态页面讲解这是我在实践中发现的小优势。1.3 项目的功能边界怎么定我见过很多同学一上来就想做“大而全”结果卡死在某个模块上。以智能药箱系统为例我建议功能边界这样划分按MVP优先级排序第一优先级必做用户登录注册、药品信息CRUD、入库/出库记录、库存列表、有效期展示。第二优先级体现含金量库存预警低于阈值变色提醒、过期药品自动识别通过Scheduled定时任务扫库、服药提醒每天定时推送消息、用药记录打卡。第三优先级锦上添花数据统计ECharts展示药品分类占比、库存消耗趋势、家庭成员多用户权限、药品二维码扫码识别、导出Excel报表。如果时间紧张第一优先级做完就能毕业第二梯队的定时任务和预警做好就是“亮点功能”第三梯队属于加分项做不做得看个人进度。清醒地评估自己能力比盲目堆功能强得多。2. 核心业务模块与数据交互流程拆解2.1 药品管理模块的底层逻辑药品是整个系统的核心实体但它不是孤立的一张表。我拆解一下围绕药品的数据流你照着这个思路去设计后端接口逻辑会非常清晰。药品信息表drug_info建议字段字段名类型说明idbigint主键drug_namevarchar药品名称batch_novarchar批号用于区分不同批次specvarchar规格比如“0.5g*24片”manufacturervarchar生产厂家production_datedate生产日期expire_datedate有效期至核心字段stock_quantityint当前库存数量min_stockint库存预警阈值categoryvarchar药品分类心脑血管/感冒/肠胃等create_timedatetime创建时间注意这里的批号字段非常关键。同一款药品不同批次的过期时间可能不一样所以药品信息表要允许同一名称多条记录不能简单用“药品名”做唯一约束。很多初学者在这里踩坑用药品名关联库存结果不同批次的药全混在一起了。业务操作主要分三类入库新增一条药品记录或已有批次上增加库存同时写一条入库记录明细入库数量、来源、操作人。出库减少对应批次的库存数量写一条出库记录服用/取药/销毁等类型。预警查询时自动计算当前库存是否 min_stock以及当前日期是否接近expire_date比如30天内前端展示预警标识。2.2 用药提醒模块的设计思路这是智能药箱系统区别于普通库存管理系统的地方。它本质上是一个定时任务 提醒记录的组合。设计思路是用户可以给自己或者家庭成员建立“用药计划”例如每天早上8点吃降压药1片、晚上7点吃阿司匹林1片。每条计划包含关联的药品ID用药时间时分每次剂量用药人家庭成员是否启用后端用一个Scheduled(cron 0 0/30 * * * ?)的任务每30分钟扫描一次用药计划表把“当前时间到了但今日还没记录”的计划捞出来生成一条提醒记录可以是站内消息形式演示时在首页消息列表展示也可以预留对接微信/短信通知的扩展点。这里有个常见误区有人想在数据库里存“下次提醒时间”然后定时任务去查“时间到了没”。但用户调整服药时间后这个字段很难同步处理。更优雅的做法是通过Cron表达式或执行规则推算比如计划表存的是“HH:mm:ss”的用药时间每次跑批时用当前时间比对加上“今日是否已用药”的标记来判断要不要提醒这样用户改时间后下一次跑批自动生效不需要额外维护状态字段。2.3 多角色权限划分与数据隔离大多数系统会有两个角色管理员和普通用户。普通用户只能操作自己名下的药箱/药品数据看自己的提醒和记录。管理员可以查看全部用户列表、药品总览、统计数据甚至停用某个违规账号。在Spring Boot里实现RBAC权限模型最简单的做法就是用户表加一个role字段1管理员0普通用户。登录接口返回JWT令牌时把角色信息放进Payload里。后端写一个拦截器或AOP注解如RequireAdmin在Controller方法上标注需要的权限拦截器从token解析角色没权限就返回403。前端Vue侧配合做路由守卫根据角色动态生成菜单没有管理员权限的账号登录后根本看不到管理入口。这块做好了答辩时讲“系统安全性设计”就有实质内容可讲而不是干巴巴的一句“我们用了JWT”。2.4 数据统计模块怎么做得好看又实用毕设答辩时评委老师大概率会问“系统有没有数据分析相关功能”。统计模块不需要复杂算法但要用图表把业务跑起来的数据展示出来。我建议做三个维度的统计药品分类占比饼状图展示心脑血管类、感冒类、肠胃类等各占库存总量的比例用ECharts的一行配置就能实现。库存消耗趋势折线图统计近30天每天出库了多少件药品观察用药高峰期。近效期药品TOP10表格进度条把距离过期时间最短的10条药品列出来用进度条组件展示剩余天数占比。统计数据的SQL要写对聚合类查询建议放在后端Service层做前端只负责调接口拿数据组装图表。ECharts的配置项非常多但常用的其实就那么几个title、legend、xAxis、yAxis、series复制官网示例改改数据就行不需要背配置。3. 核心技术实现与关键参数选型3.1 后端项目结构与架构分层我自己的习惯是严格按照controller → service → serviceImpl → mapper → entity分层这个结构老套但最清晰。针对这个系统我推荐这样组织包结构com.example.medbox ├── common // 通用返回结果、常量、异常处理 │ ├── Result.java │ ├── ResultCode.java │ └── GlobalExceptionHandler.java ├── config // 配置类拦截器、跨域、定时任务 ├── controller // 接口层 │ ├── DrugController.java │ ├── StockRecordController.java │ ├── RemindPlanController.java │ └── UserController.java ├── entity // 数据库实体类 ├── mapper // MyBatis-Plus的Mapper接口 ├── service // 业务接口 ├── service.impl // 业务实现 └── utils // JWT工具类、日期工具类这样的好处是Controller层只接收参数和返回结果所有业务判断都写在Service里后期扩展也好找地方。比如药品入库和库存扣减必须在一个事务里直接在ServiceImpl方法上标Transactional即可不要散落在Controller层各个位置。3.2 数据库表设计要点与关联关系核心表建议设计5~6张别贪多表名作用关键关联sys_user用户表与药品、计划关联drug_info药品信息表关联用户IDstock_record出入库记录表关联药品ID和用户IDremind_plan服药计划表关联药品IDremind_log提醒/打卡记录表关联计划IDoperation_log操作日志表可选记录关键操作注意一个细节stock_record出入库记录表建议设计为双向通用用一个type字段区分1入库、2出库。这样查询历史流水时只需要一条SQL按时间排序不用维护两张结构几乎一样的表。关联关系上所有业务表都加上user_id字段用用户的ID做数据隔离查询条件。不要用Java代码去过滤数据而是在SQL的条件里直接写WHERE user_id #{userId}既避免全表扫描也防止越权看到别人的数据。3.3 JWT登录认证的核心配置JWTJSON Web Token做登录认证是Java毕设的标配。原理不复杂关键是签名密钥和过期时间的设置。在application.yml中jwt: secret: your-secret-key-change-it expire: 86400000 # 24小时单位毫秒生成的Token放在HTTP请求头的Authorization字段中格式为Bearer xxxxxx。后端拦截器里解析Tokenpublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 String uri request.getRequestURI(); if (uri.contains(/user/login) || uri.contains(/user/register)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { // 返回未认证状态 response.setStatus(401); return false; } try { Claims claims JwtUtil.parseToken(token.substring(7)); request.setAttribute(userId, claims.get(userId)); return true; } catch (Exception e) { response.setStatus(401); return false; } }这里有一个实践心得JWT密钥千万不要明文写死在代码里起码放到配置文件中。曾经见过一个项目把密钥硬编码在工具类里结果所有用这套模板的同学生成的Token都一样换个人登录还能用别人的身份这是严重的安全漏洞答辩的时候被问到就非常被动。3.4 跨域处理与前端联调配置前后端分离开发时前端跑在http://localhost:5173Vite默认端口后端跑在http://localhost:8080两个端口不一样浏览器会拦截跨域请求。解决办法有三种后端加CORS全局配置推荐写一个配置类实现WebMvcConfigurer在addCorsMappings方法里放行所有来源。前端Vite代理在vite.config.js里配置proxy把/api开头的请求代理到后端。Nginx反向代理部署时统一入口效果最好。开发阶段用第一种最快部署阶段用第三种这个顺序能帮你省下大量排查问题的精力。跨域配置参考Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }需要注意如果开启了allowCredentials(true)前端axios请求就不能只设请求头还要在axios实例中配置withCredentials: true否则会丢失Cookie相关的认证信息。3.5 定时任务实现过期与库存预警Spring Boot的定时任务用起来非常无脑在主启动类加EnableScheduling然后在某个Service方法上写Scheduled注解即可。Component public class DrugWarnTask { Autowired private DrugInfoService drugInfoService; // 每天凌晨2点执行一次检查所有药品是否临期 Scheduled(cron 0 0 2 * * ?) public void checkExpireDate() { ListDrugInfo allDrugs drugInfoService.list(); for (DrugInfo drug : allDrugs) { long days calculateDays(drug.getExpireDate()); if (days 30 days 0) { // 记录预警信息 drugInfoService.saveWarnMessage(drug); } } } }Cron表达式的格式是秒 分 时 日 月 星期写错是常有的事。“0 0 2 * * ?”代表每天凌晨2点执行看不太懂的时候上工具网站验证一下比反复测试效率高得多。优化细节如果药品数据量大全表扫描效率低。可以考虑在SQL层面直接筛选SELECT * FROM drug_info WHERE expire_date BETWEEN NOW() AND DATE_ADD(NOW(), INTERVAL 30 DAY)让数据库帮你过滤Java代码只管拿结果处理。4. 实操过程与核心环节落地4.1 开发环境版本组合与初始化这个系统我用过的稳定版本组合如下你直接参照避免踩版本坑工具版本说明JDK1.8 或 11更推荐JDK 11Spring Boot 2.7完全支持Maven3.8不用最新版3.8最稳Spring Boot2.7.x不要直接上Spring Boot 3有些依赖不兼容MyBatis-Plus3.5.x配合代码生成器能省一半时间MySQL8.0注意时区配置Node.js16Vite 4需要Vue3.x Vite或者Vue 2 Element UI都行Element Plus最新版组件丰富环境初始化有一个值得注意的点MySQL连接串一定要加时区参数。jdbc:mysql://localhost:3306/medbox?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai不加serverTimezone大概率报错尤其在MySQL 8.0版本下。4.2 后端快速生成与核心接口开发MyBatis-Plus配合代码生成器可以把单表CRUD代码自动生成出来但这只适合标准增删改查业务接口还是得手写。以“药品入库”为例完整时序是前端提交入库表单字段包括药品信息入库数量。后端DrugController接收DrugDTO。Service判断该药品是否已存在按名称批号。存在在原库存上增加数量。不存在创建新药品记录初始库存为入库数量。写入stock_record表type为1入库记录数量和操作时间。返回成功结果。这段逻辑花10分钟就能写完但要注意并发场景如果两个请求同时入库同一个批次库存更新可能出错。解决方案很简单SQL层面用原子操作UPDATE drug_info SET stock_quantity stock_quantity #{num} WHERE id #{id}而不是先查再set这个细节在答辩时提出来很加分。4.3 前端页面搭建与关键组件实现前端页面我建议用Vue 3 Vite Element Plus Axios Vue Router Pinia或Vuex这套组合。核心页面也就5个登录页、首页数据看板、药品管理页、出入库记录页、提醒计划页加一个用户管理页用于管理员。药品管理页是重头戏。列表展示用el-table顶部搜索区放药品名称输入框和分类下拉框右侧放“新增”“入库”“出库”按钮。一个很实用的技巧药品列表的状态列用el-tag组件展示。库存 min_stock时显示“库存不足”红色标签expire_date距今 30天时显示“临期”黄色标签正常则显示“正常”绿色标签。这种视觉反馈是评委最容易记住的功能点。Axios请求封装的代码参考import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动附带JWT request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) // 响应拦截器统一处理401跳转登录 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response?.status 401) { ElMessage.error(登录已过期请重新登录) router.push(/login) } return Promise.reject(error) } ) export default request把拦截器做好的好处是后续所有接口调用的代码都简洁干净不需要每个页面都处理Token和错误状态。这个属于“一次配置长期受益”的环节。4.4 药品过期自动预警与首页看板联动预警功能要做成“首页直接能看见”不要藏在二级页面里。我的建议是首页放一个“温馨提示卡片”优先展示三种预警信息未来30天内到期的药品名称剩余天数。库存低于阈值的药品名称当前库存量。今日待服药计划来自提醒任务生成的数据。后端提供一个聚合接口比如/drug/count/status分别返回三类数据的数量前端卡片展示数字再提供一个列表接口点开卡片能看到详细清单。这个聚合查询可以用一条SQL带条件统计也可以用多个Service方法分别查询组成一个Map返回看数据量决定。毕设场景数据量不大直接多次查询然后组装即可简单直接。4.5 部署演示前的自检清单毕设演示翻车往往不是功能没做出来而是环境问题。我整理了一份演示前自检清单[ ] MySQL服务已启动数据库脚本执行成功。[ ] 后端Jar包重新打包mvn clean package -DskipTests本地端口没被占用。[ ] 前端npm run build产物能正常访问接口地址配置正确。[ ] 测试账号能登录且预置了至少10条药品数据其中包含几条临期和库存不足的方便演示预警效果。[ ] 定时任务触发的提醒消息确保今天已经由跑批生成了一条。第4条特别重要。演示系统之前一定要先准备一套“刚好能展示亮点”的测试数据。如果数据库里全是正常的药品预警功能演示时冷场了真的很尴尬。5. 常见问题与排查技巧实录5.1 接口报跨域错误现象前端页面打不开接口浏览器控制台提示No Access-Control-Allow-Origin header is present。排查思路确认是否走了后端CORS配置Spring Boot 2.7的CORS配置写法和我上面给的代码基本一致。确认请求方式是否为OPTIONS预检预检请求被拦截器拦截也会报跨域错误需要在拦截器中对OPTIONS请求直接放行。如果使用的是前端代理确认vite.config.js的proxy配置已经生效修改后要重启前端服务。解法优先用前端代理解决开发阶段的跨域生产部署用Nginx统一入口后端CORS配置作为兜底方案。5.2 登录成功但获取不到用户信息现象JWT签发成功前端保存Token刷新页面后接口请求401。排查思路这多半是Token解析或传递环节的问题。先看前端请求头里的Authorization字段是否携带了Token可以在浏览器F12的Network面板确认。再看后端拦截器里request.getHeader(Authorization)取到的值是否包含Bearer前缀大小写混淆。还有一个小众问题JWT的密钥不匹配。如果后端配置里改过jwt.secret但前端缓存的Token还是旧密钥签发的解析必然失败。解决方法是重新登录一次。5.3 MyBatis-Plus分页查询失效现象使用MyBatis-Plus的Page对象查询返回的总条数正确但实际列表只有前面前几条。排查思路缺少分页插件MyBatis-Plus使用分页功能必须配置PaginationInnerInterceptor否则分页逻辑不会生效。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }很多教程里都提过要配置这个插件但很多同学直接从网上拷代码时漏掉了这一环。排查时先看控制台有没有打印出LIMIT关键字没有就是插件没配置上。5.4 前端表格时间字段显示异常现象后端返回的时间字段正常比如2025-06-01 12:30:00但前端页面显示成了数字时间戳。排查思路这是JSON序列化格式问题。Java端LocalDateTime默认序列化成数组或时间戳需要在application.yml里配置统一格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8如果用了JsonFormat注解在某几个字段上也要确保pattern一致避免有的页面有格式、有的页面没格式。5.5 定时任务不生效现象启动类加了EnableScheduling任务方法也写了Scheduled但到了时间就是没执行。排查思路检查任务类是否被Spring扫描到确保所在包在启动类扫描范围内或者组件上加了Component。检查Cron表达式是否写错。举个例子“每天中午12点执行”的表达式是0 0 12 * * ?很多人把“秒”位漏掉了写成0 12 * * ?意思就变成每秒的第0秒每分钟12秒执行完全不是预期效果。如果任务方法内部有异常被吞掉也会出现“看起来没执行”。建议在方法内加日志输出异常往上抛方便定位。5.6 生产环境部署刷新页面404现象前端打包后用Nginx部署首页能打开但直接访问某个路由页面刷新浏览器就404。排查思路Vue Router默认是history模式地址中没有#号需要Nginx配置try_files回退到index.htmllocation / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; }这个问题高频率出现调好了部署基本就稳了。如果不想自己配Nginx也可以用hash模式地址栏有个#号但演示时不太好看。5.7 代码打包后前端访问后端接口404现象本地开发接口都通打包部署后用Nginx访问前端页面接口却404。排查思路前端静态文件里的接口地址可能是相对路径导致请求打到了Nginx服务器的某个路径下而没转发到后端。解决方法是前端统一把接口baseURL配置为/prod-api这类带环境标识的路径。Nginx做代理把/prod-api开头的请求转发到http://localhost:8080。生产环境部署的代理配置location /prod-api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }5.8 时间不足时的优先级取舍如果临近提交功能还有缺口我给你的建议优先级是保住登录注册、药品CRUD、出入库记录这是系统根基。保住库存预警、临期提醒这是智能化的体现功能实现成本低。争取图表统计ECharts复制示例改数据就行。放弃扫码识别、短信通知、多端小程序这些需要外部依赖开发成本和不可控因素都高毕设阶段性价比低。答辩时把已实现功能讲得逻辑自洽远比重功能多但全是半成品有用得多。6. 项目演示与毕业设计答辩的经验补充6.1 演示脚本要提前走一遍不要低估答辩当天“紧张”对操作的影响。提前写一页演示脚本按顺序列出操作步骤用测试账号登录展示首页预警卡片。进入药品管理页搜索某药品展示列表和状态标签。点击入库新增一条记录展示库存联动变化。点击出库展示库存减少。进入提醒计划页展示已生成的今日提醒。切管理员账号展示用户管理和统计图表。照着脚本过两遍比临场发挥了半天才发现库存数据不对要强得多。6.2 答辩常见问题怎么应答根据我带过的毕设经验评委大概率会问这几类问题提前把回答准备到位“为什么选这个课题”回答思路人口老龄化趋势家庭用药管理痛点药品流通管理需求。结合具体场景说不要空谈。“库存预警怎么实现的”回答思路先讲阈值字段设计min_stock再讲查询时状态判断逻辑最后补充定时任务前端视觉反馈的组合。“JWT安全性和过期策略”回答思路讲清单密钥存储、过期时间设置、拦截器校验逻辑顺带提一下后续可以引入Spring Security增强。“系统最大的难点是什么”回答思路不要说“没什么难点”。可以讲“不同批次药品的库存管理和有效期并发更新问题”把自己踩过坑又解决了的过程讲清楚这个回答比任何吹嘘都有说服力。“系统还有哪些可扩展的地方”回答思路对接语音提醒、开发微信小程序、引入低库存自动下单、对接挂号平台电子处方等方向很多选两三个说透即可。6.3 说明文档与设计文档的写作建议毕设交付除了代码最重要的就是设计文档和说明文档。建议结构如下系统概述背景、意义、目标。需求分析功能需求表、用例图。系统设计架构图、功能模块图、数据库ER图、表结构说明。核心功能实现关键代码片段逻辑说明。系统测试测试用例表、测试结果截图。一个实用技巧截图比文字管用。功能页面截图、数据库执行结果截图、接口测试截图都尽力补充进去。文档的排版和完整度在评选中占的比重超乎想象这一块花时间不亏。6.4 个人经验总结智能药箱这类基于Spring Boot Vue的管理系统做出来的同学很多但真正拉开差距的不在技术复杂度而在于业务逻辑的完整性和演示效果的自洽性。把这个“药品从入库到过期”的闭环讲圆了再把提醒和预警这两个亮点功能做扎实最后把文档整理清爽就是一个能拿得出手的优秀毕设项目。我在实际开发中最大的体会是多花20%的精力把边界情况和异常流程处理好比如重复入库、库存扣到负数、跨天提醒在答辩和评审时的观感会远远好于一个功能很多但细节粗糙的系统。