ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

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而不是纯Servlet2.1 药店管理系统的分层与依赖取舍一套能写进毕设、也能经得起追问的药店管理系统常见做法是经典三层Controller 接请求、Service 写业务、Mapper 落库。SpringBoot 在这里的价值不是「新」而是把 Tomcat 内嵌、依赖注入、事务管理、JSON 序列化这些重复劳动一次性配好让你把精力放在药品库存这种真正的业务逻辑上。如果坚持用纯 Servlet JSP光是配置 web.xml、手动管理连接池、手写 JSON 转换就能吃掉大半时间而且事务边界很难控制——药品销售时「扣库存 写销售单 写流水」必须在一个事务里Servlet 里手写connection.setAutoCommit(false)很容易漏。选型上我一般会这样定SpringBoot 2.7.x3.x 要求 JDK17很多学校机房还是 JDK8别踩这个坑、MyBatis 或 MyBatis-Plus 做持久层、MySQL 5.7/8.0、前端用 Thymeleaf 或直接前后端分离走 Vue。热词里提到的「springboot vue前后端分离」是现在毕设的主流形态但如果时间紧Thymeleaf 服务端渲染能省掉跨域、Token 传递一堆事先把业务跑通更重要。依赖清单pom.xml 关键部分parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies !-- Web 层内嵌 Tomcat -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 持久层MyBatis 官方 starter -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency !-- MySQL 驱动8.0 用 com.mysql.cj.jdbc.Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- 参数校验药品表单必填项靠它 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-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.ymlserver: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pharmacy?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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 - drugNameserverTimezone不写会报时区错误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 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) -- 有效期预警走这个索引 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;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. 按有效期升序取批次先进先出 ListDrugBatch 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 SQLupdate iddeductStock 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) ListBatchVO 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 里加useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai同时确认建表用的是 utf8mb4。时区问题如果只加serverTimezoneUTC会差 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/staticThymeleaf 模板放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 次数据会不会乱」。药店管理系统的价值不在页面多漂亮而在库存和金额这些数字永远对得上。把这句话刻在脑子里你的课设就不只是能跑而是能讲、能扛追问。希望帮到你。本文还有配套的精品资源点击获取
返回列表