ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Spring Boot药店管理系统毕设实战:库存扣减、处方药拦截与订单号生成

Spring Boot药店管理系统毕设实战:库存扣减、处方药拦截与订单号生成 简介这份资源是面向课程设计与毕业设计场景的SpringBoot药店管理系统完整项目包适合正在学习Java Web开发、需要实战案例巩固框架应用的学生与初级开发者。项目以SpringBoot为核心结合Spring MVC、Spring Data JPA或MyBatis实现用户权限、药品信息、订单流转与库存更新等模块并配套论文、开题报告、数据库脚本与说明文档帮助读者理解从需求分析到部署运行的完整流程。压缩包约15.96MB内含源码、SQL脚本、说明文档及项目说明书等文件覆盖控制器、服务、模型与视图等分层结构便于对照学习ORM映射、事务管理与接口设计。目前已有68人学习关注可作为课程设计选题参考或毕设基础框架通过阅读与运行代码快速掌握SpringBoot在真实业务中的落地方式同时借鉴开题与论文的写作思路。1. 药店管理系统毕设从“能跑就行”到“答辩能过”的分水岭每年到了课设毕设季问得最多的一类问题就是基于 Spring Boot 的 Java Web 药店管理系统到底做成什么样才算过关我见过太多同学把源码压缩包解压、改个数据库密码、跑起来截几张图就交差结果答辩时被老师三连问——库存扣减怎么保证不超卖、处方药和 OTC 的销售逻辑怎么区分、并发下订单号怎么生成——直接卡壳。这个标题背后其实藏着两条线一条是“跑起来”一条是“讲得清”。跑起来靠的是 Spring Boot 的自动配置和一套能用的 CRUD讲得清靠的是你对药品批次、库存流水、销售单据这三张核心表的理解。这篇文章不打算给你灌框架概念而是按一个真实可交付的毕设标准把环境搭建、表结构设计、核心业务代码、以及答辩前必须自己排查的坑一层层拆开。适合正在做 Java Web 课设、手里已经有一个能跑的 Spring Boot 项目但说不清细节的同学也适合想拿它当模板改造成其他进销存系统的开发者。2. 环境与骨架把 Spring Boot 药店项目在本地跑通的最小步骤2.1 版本选型为什么我不建议你追最新的 Spring Boot热搜里“springboot版本太高”是个高频抱怨这不是玄学。很多毕设模板用的是 Spring Boot 2.x 配 JDK 8你本地装了 JDK 17 或者 21直接跑会报Unsupported class file major version或者 MyBatis 的反射调用被模块系统拦住。我的建议很明确毕设项目优先用 JDK 8 或 JDK 11Spring Boot 锁在 2.7.x 这条线上。原因有三个第一2.7.x 是 2.x 的最后一个稳定分支社区资料最全你搜到的绝大多数药店管理系统教程都基于它第二Spring Security 在 2.7 里的配置方式还是WebSecurityConfigurerAdapter那套网上抄得到3.x 改成 Lambda 之后很多老教程直接失效第三MyBatis-Plus 3.5.x 对 2.7 的兼容性经过大量项目验证分页插件、逻辑删除这些开箱即用。如果你已经用了 Spring Boot 3.x也不是不能做但要接受两个现实javax.servlet全部换成jakarta.servlet所有涉及HttpServletRequest、Filter的代码都要改包名另外 MyBatis-Plus 要升到 3.5.3 以上才支持。对于毕设周期只有两三周的情况这个迁移成本不值得。2.2 建库建表药品、库存、销售三张核心表的字段设计药店管理系统和普通商品管理最大的区别在于药品有批号、有效期、处方药标识库存要按批次管理销售要能追溯到具体批号。很多模板只建了一张drug表加一个stock字段这是答辩时最容易被问倒的地方。下面是我一般会用的最小可用表结构直接给 SQL。-- 药品基础信息表 CREATE TABLE drug ( id bigint NOT NULL AUTO_INCREMENT, drug_name varchar(100) NOT NULL COMMENT 药品名称, drug_code varchar(50) NOT NULL COMMENT 药品编码唯一, spec varchar(50) DEFAULT NULL COMMENT 规格如 0.25g*24粒, unit varchar(10) DEFAULT 盒 COMMENT 单位, is_rx tinyint DEFAULT 0 COMMENT 是否处方药 0否 1是, manufacturer varchar(100) DEFAULT NULL COMMENT 生产厂家, approval_no varchar(50) DEFAULT NULL COMMENT 国药准字, price decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 零售价, status tinyint DEFAULT 1 COMMENT 1上架 0下架, PRIMARY KEY (id), UNIQUE KEY uk_drug_code (drug_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 库存批次表一个药品可以有多个批次 CREATE TABLE stock_batch ( id bigint NOT NULL AUTO_INCREMENT, drug_id bigint NOT NULL, batch_no varchar(50) NOT NULL COMMENT 生产批号, produce_date date DEFAULT NULL, expire_date date NOT NULL COMMENT 有效期至, quantity int NOT NULL DEFAULT 0 COMMENT 当前批次库存, cost_price decimal(10,2) DEFAULT NULL COMMENT 进价, PRIMARY KEY (id), KEY idx_drug_expire (drug_id,expire_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 销售单表 CREATE TABLE sale_order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, total_amount decimal(10,2) NOT NULL, operator_id bigint DEFAULT NULL COMMENT 收银员, create_time datetime DEFAULT CURRENT_TIMESTAMP, pay_type tinyint DEFAULT 1 COMMENT 1现金 2扫码 3医保, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这三张表的逻辑关系是drug存药品档案stock_batch按批号存实际库存sale_order记录销售主单还需要一张sale_order_item明细表关联到具体批次。为什么一定要拆批次因为药品过期是硬约束同一盒药不同批号有效期不同销售时必须按“先进先出、近效期先出”扣减。如果你只用一个stock字段答辩老师问一句“过期药品怎么处理”你就没法答。2.3 配置文件application.yml 里必须改的四个地方拿到源码后第一件事不是急着mvn spring-boot:run而是打开application.yml逐项核对。下面是我整理的最小配置模板四个关键点用注释标出。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/pharmacy?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 thymeleaf: cache: false # 开发阶段关缓存改页面不用重启 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true # 下划线转驼峰别关 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL排错用第一个要改的是url里的时区不写serverTimezone在 MySQL 8 上会报时区错误。第二个是password别用空密码有些 MySQL 8 默认不允许。第三个是map-underscore-to-camel-case药店系统字段大量用下划线命名关掉这个你的drugName永远查不出值。第四个是log-impl开发阶段一定要开SQL 打出来你才知道分页有没有生效、条件有没有拼错。上线前再把日志级别调回info。3. 核心业务落地库存扣减、处方药拦截、订单号生成怎么写3.1 库存扣减为什么 update 语句必须带 quantity 条件库存扣减是药店系统最容易翻车的地方。我见过一个模板的写法是先select查出库存在 Java 里判断if (stock num)然后再update stock stock - num。这个写法在单机低并发下没问题但答辩时老师只要问一句“两个人同时买最后一盒药会怎样”你就得承认会超卖。正确的做法是把判断和扣减合并到一条 SQL 里利用数据库的行锁。// StockBatchMapper.java Update(UPDATE stock_batch SET quantity quantity - #{num} WHERE id #{batchId} AND quantity #{num}) int deductStock(Param(batchId) Long batchId, Param(num) Integer num);调用时判断返回值返回 0 说明库存不足直接抛业务异常回滚事务。Service public class SaleService { Autowired private StockBatchMapper stockBatchMapper; Transactional(rollbackFor Exception.class) public void sell(Long batchId, Integer num) { int rows stockBatchMapper.deductStock(batchId, num); if (rows 0) { throw new BizException(库存不足或批次不存在); } // 后续写销售单、明细 } }逻辑说明quantity #{num}这个条件放在UPDATE的WHERE里MySQL 在执行时会对该行加排他锁两个并发请求只有一个能更新成功另一个返回 0 行。参数上batchId是批次主键num是购买数量两个都是必传。注意Transactional的rollbackFor要写Exception.class否则默认只回滚运行时异常业务异常不回滚库存扣了单没生成就麻烦了。3.2 处方药拦截在 Service 层做校验而不是前端处方药is_rx 1不能随便卖这是药店系统的合规底线。很多模板只在前端加个提示后端接口裸奔答辩时被问到就是硬伤。我的做法是在销售 Service 里强制校验并且要求传入处方编号。public void checkRx(Long drugId, String prescriptionNo) { Drug drug drugMapper.selectById(drugId); if (drug null) { throw new BizException(药品不存在); } if (drug.getIsRx() 1) { if (prescriptionNo null || prescriptionNo.trim().isEmpty()) { throw new BizException(处方药必须录入处方编号); } // 这里可以扩展到处方表校验毕设阶段校验非空即可 } }参数说明drugId来自前端选择的药品prescriptionNo是收银员手工录入的处方编号。这段代码放在sell方法开头扣库存之前执行。为什么不在 Controller 做因为 Controller 只负责接参和返回业务规则集中在 Service 才方便复用和测试。答辩时你可以主动说“处方药校验在 Service 层前端绕过也拦得住”这是个加分点。3.3 订单号生成时间戳加随机数够不够用订单号生成看着简单但用System.currentTimeMillis()裸拼在高并发下会重复。毕设场景并发不高但你要能说出方案。我一般用“时间戳 用户ID后四位 三位随机数”的格式长度控制在 20 位以内。public String generateOrderNo(Long operatorId) { String time new SimpleDateFormat(yyyyMMddHHmmss).format(new Date()); String uid String.format(%04d, operatorId % 10000); String rand String.format(%03d, new Random().nextInt(1000)); return time uid rand; }逻辑说明time精确到秒uid取操作员 ID 后四位避免同一秒内不同收银员冲突rand三位随机数降低同秒同人重复概率。参数上operatorId是当前登录用户 ID。如果要求更严格可以在订单表order_no上加唯一索引插入冲突时重试一次。注意SimpleDateFormat不是线程安全的如果做成静态常量要加ThreadLocal毕设里每次 new 一个最简单别在这里炫技。4. 避坑与排查药店管理系统跑不起来时先看这五条4.1 启动报 “Table ‘pharmacy.xxx’ doesn‘t exist”现象项目启动正常一访问列表页就报表不存在。原因源码里的 SQL 脚本没执行或者执行到了错误的数据库。解决找到resources目录下的.sql文件确认CREATE DATABASE和USE语句指向的库名和application.yml里url的库名一致然后手动在客户端里执行一遍。别依赖 Spring Boot 自动建表MyBatis-Plus 不会帮你建表。4.2 页面能打开但数据全是 null现象列表页有表格框架但每一行字段都是空。原因map-underscore-to-camel-case没开或者实体类字段名和数据库列名对不上。解决先确认配置为true再检查实体类有没有加TableField注解映射。比如数据库列drug_name实体类字段drugName开了驼峰转换就能自动映射如果实体类写的是name那就必须加TableField(drug_name)。4.3 库存扣成负数现象卖了几单之后库存变成负的。原因扣减 SQL 没带quantity #{num}条件或者扣减和查询不在同一个事务里。解决按 3.1 的写法改 SQL并确认Transactional加在 public 方法上、同类内部调用不生效Spring AOP 代理问题。如果是在同一个类里 A 方法调 B 方法B 的事务不会生效要把 B 抽到另一个 Service。4.4 中文乱码现象药品名称存进去是问号。原因数据库字符集不是utf8mb4或者 JDBC url 没加characterEncodingutf8。解决建库时指定DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_general_ciurl 里补上useUnicodetruecharacterEncodingutf8。已经建好的库可以用ALTER DATABASE pharmacy CHARACTER SET utf8mb4;改但表要单独改。4.5 登录后跳转 404现象输入账号密码点登录浏览器跳到/index显示 404。原因Thymeleaf 模板放在templates下但 Controller 返回的视图名不对或者 Spring Security 拦截了静态资源。解决先看 Controller 返回值是不是return index;而不是return /index;再看templates目录下有没有index.html。如果是 Security 问题在配置类里放行/css/**、/js/**、/images/**。5. 答辩前自检三个能让你多拿分的验证动作第一个动作是打开控制台看 SQL。把log-impl开着走一遍“新增药品 → 入库 → 销售 → 退货”的完整流程看每条 SQL 是不是符合预期。特别是销售那一步你应该看到UPDATE stock_batch SET quantity quantity - ? WHERE id ? AND quantity ?这条语句参数依次是数量、批次 ID、数量。如果看到的是先SELECT再UPDATE说明你用的还是老模板赶紧改。第二个动作是手工造一条过期批次数据验证系统会不会把它卖出去。在stock_batch里插一条expire_date是昨天的记录然后走销售流程。如果你的系统没有拦截答辩时老师问“过期药怎么处理”你就只能沉默。最简单的做法是在查询可售批次时加AND expire_date CURDATE()销售时如果选中的批次已过期直接拒绝。第三个动作是模拟并发。毕设不需要真的压测但你可以开两个浏览器窗口用两个账号同时买同一批次最后一盒药看会不会两个都成功。如果两个都成功且库存变成 -1说明你的扣减 SQL 没加条件。这个演示在答辩时主动做出来比你说一百句“我考虑了并发”都有用。我自己的习惯是每次改完业务代码先把这三个动作跑一遍再提交。血泪经验是很多看起来“应该没问题”的地方一跑就露馅。希望帮到你。本文还有配套的精品资源点击获取
返回列表