news 2026/9/30 10:37:11

SpringBoot药店管理系统课设毕设:库存扣减与处方药校验实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot药店管理系统课设毕设:库存扣减与处方药校验实战

简介:这份资源是面向高校计算机相关专业学生与Java Web初学者的一套药店管理系统课程设计/毕业设计完整方案,基于SpringBoot框架实现,适合需要完成课设毕设或想通过真实项目入门SpringBoot开发的人群。压缩包约15.96MB,内含论文、开题报告、数据库脚本、说明文档及可运行源码,覆盖用户管理、药品管理、订单管理、库存管理等核心模块,并涉及Spring Security或JWT鉴权、Spring Data JPA或MyBatis持久化、事务管理等技术要点。说明文档与项目说明书可帮助读者快速配置环境、理解系统架构与接口设计,源码则便于对照学习控制器、服务、模型、视图各层的组织方式。目前已有68人学习下载,适合希望系统掌握SpringBoot实践应用、了解药店业务流程与软件开发全流程的读者参考。

1. 药店管理系统课设毕设:从一份可运行源码里能学到什么

很多同学拿到「课设毕设springboot基于Java web的药店管理系统」这类题目时,第一反应是去搜一份能跑的源码,改改包名、换个数据库密码,能启动、能截图、能写进论文就算交差。但真正答辩被追问「库存扣减怎么保证不超卖」「处方药和OTC药品的销售逻辑有什么区别」「为什么用SpringBoot而不是Servlet」时,往往答不上来。这篇笔记不打算复述某份源码的目录结构,而是顺着这个标题,把一套药店管理系统从技术选型、数据库设计、核心业务实现到部署排错完整拆一遍,让你既能照着搭出一个能跑的系统,也能在答辩和后续面试里讲清楚每个决策背后的理由。

药店管理系统本质上是一个带库存约束的进销存系统,比普通CRUD的图书管理、学生管理要复杂一档:药品有批号、有效期、处方药/非处方药分类,销售时要校验库存和处方,采购入库要更新批次,过期药品要能预警。这些才是这个题目真正值得做的地方,也是面试官和答辩老师会盯的点。适合正在做Java web方向课设毕设的本科生,也适合想用一个完整项目复习SpringBoot + MyBatis + MySQL这套组合的初级开发者。下面按「先立住技术选型 → 再动手建库建表 → 实现核心业务 → 排坑 → 进阶」的顺序推进,每一步都给到能直接抄的命令和代码。

2. 技术选型与工程骨架:为什么是SpringBoot而不是纯Servlet

2.1 药店管理系统的分层与依赖取舍

一套能写进毕设、也能经得起追问的药店管理系统,常见做法是经典三层:Controller 接请求、Service 写业务、Mapper 落库。SpringBoot 在这里的价值不是「新」,而是把 Tomcat 内嵌、依赖注入、事务管理、JSON 序列化这些重复劳动一次性配好,让你把精力放在药品库存这种真正的业务逻辑上。如果坚持用纯 Servlet + JSP,光是配置 web.xml、手动管理连接池、手写 JSON 转换就能吃掉大半时间,而且事务边界很难控制——药品销售时「扣库存 + 写销售单 + 写流水」必须在一个事务里,Servlet 里手写connection.setAutoCommit(false)很容易漏。

选型上我一般会这样定:SpringBoot 2.7.x(3.x 要求 JDK17,很多学校机房还是 JDK8,别踩这个坑)、MyBatis 或 MyBatis-Plus 做持久层、MySQL 5.7/8.0、前端用 Thymeleaf 或直接前后端分离走 Vue。热词里提到的「springboot vue前后端分离」是现在毕设的主流形态,但如果时间紧,Thymeleaf 服务端渲染能省掉跨域、Token 传递一堆事,先把业务跑通更重要。

依赖清单(pom.xml 关键部分):

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <!-- Web 层,内嵌 Tomcat --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 持久层,MyBatis 官方 starter --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <!-- MySQL 驱动,8.0 用 com.mysql.cj.jdbc.Driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- 参数校验,药品表单必填项靠它 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>

这段配置的逻辑是:spring-boot-starter-web负责把 DispatcherServlet、Jackson、内嵌容器都拉进来;mybatis-spring-boot-starter让 Mapper 接口能被自动扫描注册,省掉SqlSessionFactory的手工配置;validation 用来在 Controller 层拦截药品名称、价格、库存这些字段的非法输入。参数上唯一要注意的是版本对齐——SpringBoot 2.7.x 配 MyBatis starter 2.3.x 是稳的,别混用 3.x 的 starter,会出现NoSuchMethodError。

2.2 用 IDEA 或命令行把骨架跑起来

如果你用 IDEA,直接 Spring Initializr 勾选 Spring Web、MyBatis Framework、MySQL Driver 即可。命令行党可以用 curl 拉骨架,或者干脆手写。启动类长这样:

@SpringBootApplication @MapperScan("com.example.pharmacy.mapper") // 扫描 Mapper 接口,漏了会报找不到 Bean public class PharmacyApplication { public static void main(String[] args) { SpringApplication.run(PharmacyApplication.class, args); } }

@MapperScan是最容易漏的一行,漏了之后启动不报错,但一调用就抛Field xxxMapper required a bean,新手经常在这里卡半天。配置文件application.yml:

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pharmacy?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml # XML 映射文件位置 configuration: map-underscore-to-camel-case: true # 下划线转驼峰,drug_name -> drugName

serverTimezone不写会报时区错误,map-underscore-to-camel-case不写会导致数据库drug_name映射不到实体drugName,查出来全是 null。这两个是血泪经验级别的必配项。跑通的标准是访问http://localhost:8080不报 404、控制台没有数据源异常,此时骨架就算立住了。

3. 数据库设计:药品、批次、库存三张表怎么拆

3.1 从业务反推表结构

药店管理最容易设计错的地方,是把「药品」和「库存」混成一张表。正确的拆法是:药品表存药品的静态属性(名称、规格、厂家、是否处方药),批次表存每一批进货的批号、生产日期、有效期、进价,库存表或直接在批次表上用数量字段表示当前剩余。销售时按批次先进先出(FIFO)扣减,这样才能支持有效期预警和批次追溯。

核心表结构:

表名关键字段说明
drugid, drug_name, spec, manufacturer, is_rx, price药品基础信息,is_rx 标记处方药
drug_batchid, drug_id, batch_no, produce_date, expire_date, cost_price, quantity批次与库存,quantity 为剩余量
sale_orderid, order_no, total_amount, create_time, operator销售主单
sale_itemid, order_id, batch_id, quantity, unit_price销售明细,关联具体批次
purchase_orderid, supplier, total_amount, status采购单,status 控制入库流程

这样拆的好处是:库存扣减落在drug_batch.quantity上,天然支持多批次;有效期预警只需SELECT * FROM drug_batch WHERE expire_date < DATE_ADD(NOW(), INTERVAL 30 DAY);批次追溯通过 sale_item 反查 batch_no 即可。

3.2 建表 SQL 与索引

CREATE TABLE drug ( id BIGINT PRIMARY KEY AUTO_INCREMENT, drug_name VARCHAR(100) NOT NULL, spec VARCHAR(50), manufacturer VARCHAR(100), is_rx TINYINT DEFAULT 0 COMMENT '0非处方 1处方', price DECIMAL(10,2) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE drug_batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, drug_id BIGINT NOT NULL, batch_no VARCHAR(50) NOT NULL, produce_date DATE, expire_date DATE NOT NULL, cost_price DECIMAL(10,2), quantity INT NOT NULL DEFAULT 0, INDEX idx_drug_id (drug_id), INDEX idx_expire (expire_date) -- 有效期预警走这个索引 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

idx_expire这个索引是给预警查询用的,不加的话数据量一上来全表扫描会明显变慢。quantity用 INT 而不是无符号,是因为扣减时如果逻辑写错出现负数,无符号会直接报错中断事务,而有符号能让你在日志里看到 -1 这种异常值,方便定位问题——这是排查思路上的取舍,不是设计缺陷。

提示:字符集统一用 utf8mb4,药品名里出现生僻字或特殊符号时 utf8 会插入失败。

4. 核心业务实现:库存扣减与处方药校验

4.1 销售下单的事务与防超卖

销售是这套系统的心脏。一次销售要完成:校验处方药是否有处方、按 FIFO 选批次、扣减库存、写销售单和明细。整个过程必须在一个事务里,且扣库存要用带条件的 UPDATE 防止并发超卖。

@Service public class SaleService { @Autowired private DrugBatchMapper batchMapper; @Autowired private SaleOrderMapper orderMapper; @Transactional(rollbackFor = Exception.class) // 任何异常都回滚 public void sell(Long drugId, int num, boolean hasPrescription) { // 1. 处方药校验,非处方药直接放行 Drug drug = batchMapper.selectDrugById(drugId); if (drug.getIsRx() == 1 && !hasPrescription) { throw new BizException("处方药必须凭处方销售"); } // 2. 按有效期升序取批次,先进先出 List<DrugBatch> batches = batchMapper.selectAvailableBatches(drugId); int remain = num; for (DrugBatch b : batches) { if (remain <= 0) break; int deduct = Math.min(remain, b.getQuantity()); // 3. 带条件的更新,quantity >= deduct 才扣,防止并发扣成负数 int rows = batchMapper.deductStock(b.getId(), deduct); if (rows == 0) { throw new BizException("库存不足,批次 " + b.getBatchNo()); } remain -= deduct; } if (remain > 0) { throw new BizException("总库存不足"); } // 4. 写销售单 orderMapper.insertOrder(drugId, num); } }

对应的 Mapper SQL:

<update id="deductStock"> UPDATE drug_batch SET quantity = quantity - #{deduct} WHERE id = #{batchId} AND quantity >= #{deduct} </update>

关键在AND quantity >= #{deduct}这个条件。它把「判断库存够不够」和「扣减」合并成一条原子 SQL,靠数据库行锁保证并发安全。如果先 SELECT 查库存再 UPDATE,两个请求同时查到库存 1,就会都扣成功变成 -1,这就是超卖。@Transactional的rollbackFor = Exception.class也不能省,默认只回滚 RuntimeException,自定义的受检异常不回滚会留下脏数据。

4.2 有效期预警与采购入库

有效期预警是个定时任务或手动触发的查询,逻辑简单但很能体现业务理解:

@Select("SELECT b.*, d.drug_name FROM drug_batch b " + "JOIN drug d ON b.drug_id = d.id " + "WHERE b.expire_date <= DATE_ADD(CURDATE(), INTERVAL #{days} DAY) " + "AND b.quantity > 0 ORDER BY b.expire_date") List<BatchVO> selectExpiringBatches(@Param("days") int days);

days参数一般设 30 或 90,代表提前多少天预警。quantity > 0是为了过滤掉已经卖完的批次,否则预警列表里全是历史空批次,没有意义。采购入库则是反向操作:新增批次记录或对已有批次加数量,同时写采购单。入库时要注意批号唯一性——同一药品同一批号重复入库应该合并数量而不是插两条,否则 FIFO 逻辑会乱。

5. 避坑与排查:那些让系统跑不起来的细节

5.1 启动就报数据源或 Mapper 找不到

现象:启动日志抛Failed to configure a DataSource或调用接口时Invalid bound statement (not found)。原因通常是application.yml里数据源配置的缩进错了(YAML 对缩进敏感),或者mapper-locations路径和实际 XML 位置对不上。解决:先确认spring.datasource下的 url/username/password 是同级缩进,再确认 XML 放在src/main/resources/mapper/下且mapper-locations写的是classpath:mapper/*.xml。Invalid bound statement还有可能是 Mapper 接口方法名和 XML 里的 id 不一致,逐字对一遍。

5.2 中文乱码与时间差 8 小时

现象:药品名存进去变成问号,或者创建时间比实际早/晚 8 小时。原因是数据库连接没指定字符集和时区。解决:JDBC url 里加useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai,同时确认建表用的是 utf8mb4。时区问题如果只加serverTimezone=UTC会差 8 小时,必须写Asia/Shanghai。

5.3 事务不回滚导致库存扣了单没生成

现象:库存扣减成功,但销售单没写进去,数据不一致。原因是@Transactional加在了 private 方法上,或者同类内部方法直接调用(this.xxx())绕过了代理。解决:事务方法必须是 public,且通过 Spring 注入的 Bean 调用,不能在同一个类里自调用。另外确认异常类型在rollbackFor范围内。

5.4 前端传参后端收不到

现象:POST 提交药品表单,后端@RequestBody收到的字段全是 null。原因是前端发的是application/x-www-form-urlencoded,而后端按 JSON 解析。解决:要么前端改成JSON.stringify并设Content-Type: application/json,要么后端改用@ModelAttribute接收表单。前后端分离项目里这个错最常见,抓包看 Content-Type 一眼就能定位。

5.5 打包成 jar 后访问 404

现象:IDEA 里跑得好好的,mvn package后java -jar启动,接口全 404。原因是静态资源或模板没被打进 jar,或者 Controller 扫描路径不对。解决:确认@SpringBootApplication所在包是所有 Controller 的父包,静态资源放src/main/resources/static,Thymeleaf 模板放templates。打包后可以用jar tf target/xxx.jar | grep templates确认文件在不在里面。

6. 从能跑到能讲:把项目变成答辩和面试的加分项

系统跑通只是及格线,真正拉开差距的是你能不能讲清楚设计取舍。我一般会建议在基础功能之上加两个能体现思考的点。第一个是库存扣减的并发验证:写一个简单的压测脚本,用 JMeter 或干脆用 Java 起 50 个线程同时下单同一批次,观察最终库存是否等于初始值减去成功订单数。如果出现负数或对不上,说明你的条件 UPDATE 或事务边界有问题。这个实验做一遍,答辩时讲「我用带条件的原子更新防止超卖」就非常有底气。

// 并发验证片段:50 个线程抢 10 件库存 ExecutorService pool = Executors.newFixedThreadPool(50); CountDownLatch latch = new CountDownLatch(1); AtomicInteger success = new AtomicInteger(); for (int i = 0; i < 50; i++) { pool.submit(() -> { try { latch.await(); // 等发令枪,保证同时开抢 saleService.sell(1L, 1, true); success.incrementAndGet(); } catch (Exception e) { // 库存不足的异常,正常 } }); } latch.countDown(); pool.shutdown(); pool.awaitTermination(10, TimeUnit.SECONDS); System.out.println("成功下单数:" + success.get()); // 期望等于 10

第二个是有效期预警的可视化:把 30 天内到期的批次做成首页红黄灯列表,答辩演示时非常直观。进阶方向还可以接 MinIO 存药品图片、用全局过滤器处理上传文件的 XSS 风险,这些热词里提到的点都能作为扩展写进论文的「系统优化」章节,但前提是核心业务先扎实。

最后说个我自己的习惯:每写完一个 Service 方法,我都会问自己一句「如果这个方法被同时调用 100 次,数据会不会乱」。药店管理系统的价值不在页面多漂亮,而在库存和金额这些数字永远对得上。把这句话刻在脑子里,你的课设就不只是能跑,而是能讲、能扛追问。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 10:36:17

真实打架检测数据集:1000张图+三格式标签+YOLO11跨平台训练

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 10:34:19

字节三面挂了!多 Agent 编排四连问我都答上了,却没答到点上

我说你把面试过程尽量还原给我。他答得很细。前两轮都过了——项目、JVM、并发&#xff0c;答得都不错。三面是架构面。面试官先让他讲手上那个 AI 项目&#xff0c;他讲了大概十分钟——四个 Agent&#xff1a;理解意图、检索知识库、生成回答、质量校验&#xff0c;串起来跑。…

作者头像 李华
网站建设 2026/9/30 10:34:08

FPGA功耗优化实战:五个技巧解决发烫与续航问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 10:28:51

子网掩码计算与配置实战:从二进制逻辑到Windows/Linux命令行

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 10:27:42

矩阵起源硅谷举办 AI 产业领袖晚宴

从算力、智能到生产力 当地时间9月21日晚&#xff0c;矩阵起源&#xff08;MatrixOrigin&#xff09;在硅谷举办“从算力、智能到生产力”AI产业领袖晚宴&#xff0c;招待到访硅谷、赴英伟达总部交流的中国AI产业企业访问团&#xff0c;与产业伙伴共话企业AI的落地与合作。中鼎…

作者头像 李华