SpringBoot连锁药店进销存系统设计与批次库存效期管理实现

发布时间:2026/9/30 4:33:25
SpringBoot连锁药店进销存系统设计与批次库存效期管理实现 说实话连锁药店的进销存系统我前后经手过好几套SpringBoot这套是目前我觉得最省心、最适合拿来学习或二次改造的版本。项目名义上是“Springboot连锁药店进销存业务系统”实际上包含的不只是运行代码还配套了完整数据库脚本、调试部署说明和一篇一万字以上的论文文档属于典型的“毕业设计实际业务”双重功能项目。很多人拿到这种项目第一反应是到处找教程、看视频其实效率极低——最靠谱的方式是先看数据库建模再看核心交易链路最后才碰接口和前端页面这个顺序我后面会详细讲。这套系统解决的是连锁药店日常经营中最核心的问题总部和多个门店之间药品如何采购、怎么入库、卖出去之后库存如何扣减、快到期的药怎么提前预警以及每一笔的进价售价怎么变成毛利报表。它不像普通商超进销存那样只管“商品-库存-订单”药品得多管几个维度批号、生产日期、有效期至、产地、批准文号、配送门店。这些字段一旦设计好后面的效期预警和批次成本计算都会顺畅很多。如果你是刚接触SpringBoot开发或者准备用这类项目做课程设计、入职练手、接私活底子这篇文章都能帮你在拿到项目后少踩一半的坑。1. 连锁药店进销存为什么难做这套系统重点解决了什么1.1 药品流转的特殊性批号、效期、多门店很多做过传统商城库存系统的人第一次接触药店进销存会有点懵。普通商品只管SKU和库存数量但药品的每一次入库都带着制药厂的生产批号和生产日期同一款药可能有两批货价格不同、效期不同卖的时候理论上要先进先出先把快过期的批次卖掉。现实中药店采购也是一批一批进来如果系统不做批次级库存只做总量库存那效期管理就是空谈。这套系统把药品主数据和库存数据拆开了。药品基本信息表里存放通用名、商品名、规格、生产厂家、批准文号、零售价这类相对固定的属性而具体到每一批货单独用批次表记录入库批号、生产日期、有效期至、进货价、批次库存量。这样做的好处非常直接销售出库时可以按批号锁定库存过期的批次能被定时任务筛选出来并禁止销售盘点时也能精确到某个批号缺多少。1.2 系统开发者视角的业务全景从开发角度看这套系统覆盖了药店进销存最常见的几个业务角色总部管理员、门店员工、采购员、仓库管理员。每个角色登录后看到的菜单和操作权限不一样这也是项目里面权限设计的重点。业务链路大致是采购员新建采购单选择供应商和药品保存后由管理员审核审核通过后仓库做实物验收系统自动生成入库单同时为每一批药品生成库存批次记录门店销售时收银员选择药品、输入数量系统按先进先出扣减对应批次的库存生成销售出库流水如果门店缺货总部仓库可以通过调拨单把货发给门店门店收货后再入到自己的库存。整个过程还穿插了退货单采购退货、销售退货、盘点单盘盈盘亏、效期预警和财务报表。这其实就是一家连锁药店最真实的日常经营循环。项目没有做复杂的互联网营销、会员积分、DTP药房那套东西而是把进销存这条主线做扎实了反而更贴近多数中小型连锁药店的需要也更容易看明白表与表之间的关联。1.3 交付物里除了源码还有什么标题里写了“程序源码数据库调试部署开发环境”这个表述很多人不理解具体是什么意思。我按实际交付内容拆开解释一下程序打包好的可运行交付版本通常是一个可直接执行的jar包或war包放在服务器上就能启动适合不想从源码编译的人。源码完整后端工程包含所有Java代码、资源配置文件、前端页面或接口调用文件适合学习和二次开发。数据库一个完整的SQL脚本通常几十张表带初始数据导入MySQL后就能跑通页面。调试部署常见的是部署说明书或者操作文档包含JDK配置、MySQL建库、连接参数修改、启动命令等步骤。论文文档一万字以上的Word或PDF内容一般是需求分析、系统设计、数据库设计、系统实现、测试、总结这一套标准结构可以直接用来参考写自己的毕业设计文档。这里有个经验建议拿到项目先别急着启动先把SQL脚本用Navicat打开把表结构从头到尾翻一遍大概理解每张表是干什么的再去跑代码你会觉得整个系统瞬间透明了。2. SpringBoot技术方案组合选型依据与工程结构拆解2.1 为什么选用SpringBoot作为主框架现在做管理类业务系统SpringBoot几乎是默认选项这套系统选它自然有理由。SpringBoot最核心的价值不是某个功能有多强而是把Spring生态里面那些繁琐的XML配置全部收敛成了自动配置。一个连锁药店进销存系统涉及的组件不少Web接口、数据持久层、事务管理、权限认证、定时任务如果用传统SSM一套配置写下来少说要一两天SpringBoot里大多就是加一个依赖加一个注解的事。更关键的是社区成熟度。SpringBoot相关的教程、问答、封装组件非常多遇到任何报错基本都能搜到解决方案。对于课程设计和企业小型项目来说技术选型最怕的不是功能做不出来而是出了问题没人理。选SpringBoot相当于站在了一条非常宽的路上。另外SpringBoot 2.x版本自带的嵌入式Tomcat让打包部署几乎零成本。项目在开发机器上直接执行main方法启动验证通过后执行mvn package打成jar包扔到服务器任何一个目录java -jar命令一跑就完事不需要额外安装和配置Tomcat环境。这也是它成为中小型系统首选的原因。2.2 数据持久层与缓存组件的搭配这套系统典型的持久层方案是MyBatis或MyBatis-Plus配合MySQL。MyBatis-Plus比原生MyBatis舒服在两点单表CRUD基本不用写SQL继承BaseMapper后直接调用insert、selectPage这些方法就行分页插件写起来很省事进销存系统里面的采购单列表、销售流水列表全都是分页查询用它的IPage接口配合分页插件代码量可以压缩不少。数据库选MySQL 5.7或8.0都能运行。需要注意字符集必须设为utf8mb4不然后面在备注字段或者供应商中文名称上很容易出现乱码。我见过有人用默认latin1建库结果系统所有中文全是问号追到源头就是建库时没有显式指定字符集。缓存组件方面项目里一般会有Redis主要用来存登录令牌、用户session和部分热点数据。药店系统的实时性要求不算极端Redis在这里更多是降低数据库并发压力。如果你本地没装Redis启动时候发现报连接失败不要慌看一下application.yml里Redis相关配置要么把Redis启动起来要么临时把缓存策略切到本地内存两种方式都可以先跑通项目。2.3 工程目录结构与模块职责拿到源码后先看顶层目录结构。常规的SpringBoot工程会比较清晰controller接收请求、参数校验、返回结果service业务逻辑采购、销售、库存、报表都在这层mapper/dao数据库交互MyBatis的Mapper接口和XML文件entity/domain数据库实体类对应每张表dto数据传输对象用于分页请求、前端表单提交等config配置类比如MyBatis分页插件、跨域配置、拦截器utils工具类比如JWT工具、日期工具、统一返回结构resourcesapplication.yml、SQL脚本、Mapper XML、前端页面或静态资源看项目不要从头到尾一行行读而是按业务优先看service层的几个大类就行入库相关的服务类、出库相关的服务类、报表统计类。这三个覆盖面最广把它们的逻辑看懂这套系统你就掌握了八成。3. 数据库模型设计药品批次、效期与门店库存的核心表3.1 商品主数据与批次的拆分逻辑数据库是这个项目的灵魂。我之前带人做改造的时候发现新人最容易出的问题就是脑子里面有“一张药品表搞定一切”的惯性思维。实际上连锁药店进销存药品主数据表管的是“这件商品是什么”批次库存表管的是“这批货具体哪来的、什么价、什么效期、剩多少”。核心表差不多是这样表名作用关键字段drug药品主数据药品编码、通用名、规格、厂家、批准文号、零售价、会员价、储存条件drug_batch批次库存药品编码、入库单号、批号、生产日期、有效期至、进货价、批次库存、门店编码supplier供应商名称、联系人、电话、地址purchase_order采购单主表单号、供应商、采购日期、审核状态、金额合计purchase_order_item采购单明细药品编码、采购数量、进货价、小计stock_in入库单关联采购单、入库类型、经办人、入库日期stock_out出库单关联销售单或调拨单、出库类型、门店sale_order销售单主表单号、门店、收银员、销售日期、应收金额sale_order_item销售明细药品、批次号、销售单价、数量、折扣stock_transfer调拨单调出门店、调入门店、药品、数量、状态stock_check盘点单盘点批次、账面数量、实盘数量、差异、门店stock_log库存流水药品、批次、变动类型、入出数量、关联单号、操作时间你注意看这个结构最核心的一点是“库存永远属于批次批次永远带效期”。销售出库时除了扣减药品总库存更重要的是从具体的批次上扣这样效期和成本才准确。3.2 采购、销售、盘点三类核心单据表单据表的设计遵循“主表明细表”的标准模式。采购单主表负责记住这笔采购是和哪个供应商发生的总共多少钱审核到什么状态明细表负责记清楚买了哪几种药、各多少数量、什么进价。库存变动不是直接改药品表而是通过主表和明细确认后生成入库单再由入库单生成批次记录。销售单道理完全一样。收银台提交销售单主表记门店、收银员、应收实收明细表记每一行药品对应的批次号。这样设计的好处是每一笔库存流水都能追溯来源出库记录和批次号一一对应。后期对账时不用猜直接拿sale_order_item关联drug_batch就能算出这个月某个品种卖了哪个批次的货、成本是多少。盘点逻辑也值得一说。盘点流程一般是先按门店和药品生成盘点单里面记录账面批次库存仓库实际清点后输入实盘数量系统自动算差异数确认后生成盘盈或盘亏调整单同步冲减或补增对应批次的库存。这套系统把差异均摊到批次不会出现“总数对上了但批次乱了”这种尴尬情况。3.3 SQL脚本初始化时的注意事项拿到项目里的SQL文件导入时务必按顺序来。一般脚本会分成建库语句、建表语句、初始数据三部分。先说建库语句脚本里通常有CREATE DATABASE和USE目的是让MySQL自动创建药店库。如果你习惯先手动建库再选择运行脚本那就要看清库名是否一致不一致会导致后边的表全部建到别的库去。再说初始数据。项目里如果带一个管理员账号一般写在insert语句中密码通常是MD5加密或者BCrypt加密后的值。如果你不知道自己设的初始密码是什么密文最简单的方式是看论文文档或部署文档里写了什么那里一般会说明默认账号密码。如果没有说明就去Java代码里找注册接口或者初始化密码的逻辑看它对密码做了什么加密处理再拿工具生成一段相同的密文替换进去。导入完成后用SELECT * FROM user验证一下确认中文不乱码、记录能查到再继续启动后端。千万别一口气RUN全部脚本后看都不看就去登录数据库层出问题后面所有模块都会跟着报错。4. 核心业务逻辑实现细节从登录鉴权到效期预警4.1 基于JWT的登录认证与权限控制连锁药店系统不像普通博客网站不同角色的权限差异非常大。采购员不能审核采购单收银员不能做采购入库总部管理员才能看全部门店报表。项目里常见的方案是JWT拦截器。逻辑大概是用户登录时携带用户名密码后端校验通过后生成一个带角色信息的JWT令牌前端每次请求都在请求头里带上这个令牌后端拦截器解析令牌并判断当前用户拥有的角色再对接口做权限拦截。这个方案的好处是不用像传统Session那样依赖服务器内存前后端分离也好部署。实际开发中要特别注意令牌过期时间的设置。这套系统如果是给门店员工从上班开到下班的令牌过期时间至少要覆盖一个班次但也不要太长否则权限改了用户还是旧权限会有安全隐患。我个人习惯设成12小时加一个合理的Token刷新机制既能保证白天连续营业不频繁掉线又能避免令牌长期有效导致的风险。4.2 采购验收流程批次号如何自动生成批次号生成是整个入库环节最容易被忽略又最关键的点。很多人以为批次号是用户手输的其实在真正的业务里手工输入容易出错尤其发票单上面的供应商批号和库房拆包后的批号可能不一致。这套系统里比较合理的做法是入库时系统自动生成一条内部批次号比如用日期加随机数再把药厂的原批号单独存成一个字段比如厂家批号这样既能对账又能追溯。生成批次号的代码逻辑一般是获取当前日期字符串加上流水序号形如20240517001。每次入库都在事务里查询当天已用到的最大序号加一后作为新批次。这么做既能保证唯一性又能在报表上直接看出是哪天入的货。入库事务里要做三件事更新采购单的审核状态、往drug_batch表插入新批次记录、往stock_log表写入一条入库流水。这三步必须在同一个事务里完成否则会出现采购单显示已入库但库存没有增加的不一致问题。Spring的Transactional注解就用在入库service方法上底层报错会自动回滚省掉不少事。4.3 销售出库批次扣减与库存流水销售出库逻辑的难点不在扣库存这个动作上而在“从哪个批次扣”。药店的正确做法是先进先出也就是先入库的批次先卖这样可以降低过期损耗。实现上通常是对该药品的所有批次按有效期至升序排序然后逐个批次判断库存是否足够从最早到期的批次开始扣减不够再扣下一批。这段逻辑看起来不难但坑也不少。最典型的是并发问题两个收银员同时卖同一批号各自都查到了剩余数量足够结果双双扣减库存变负数。解决办法有两种一种是对批次表加行锁SQL里用SELECT ... FOR UPDATE锁住批次记录再扣减另一种是用乐观锁在批次表加一个version字段更新的时候带版本号判断版本变了就重试。对药店这种中低并发的收银场景行锁方案更简单直接。销售退货也走类似逻辑不过是反向操作查到原销售单的明细按原批次把库存加回来。这里比较麻烦的是退货时药品可能已经过期了过期的批次能不能退、退到哪个批次不同门店规则不一样。做项目时按论文里写明的“过期批次不允许退货入库只能做报损处理”来实现是最稳妥的。4.4 近效期预警定时任务效期预警是药店系统区别于普通进销存系统的招牌功能。实现原理不复杂用Spring自带的Scheduled注解每天固定时间执行一次定时任务扫描drug_batch表里所有未售完的批次把有效期在90天以内、60天以内、30天以内的分别打上不同预警级别把过期批次标记为锁定状态。关键点是批次的锁定不能只靠定时任务。因为销售出库是实时发生的如果正好在两次定时任务之间一批药过期了销售接口必须同步判断有效期过期的批次直接不让出库。我见过有人只在生成预警报告时判断没在销售逻辑里拦截结果页面显示预警了但货还能卖出去这是绝对不允许的。预警展示一般放在系统首页用一个统计面板显示近效期药品数量、过期批次数量点击可以跳转到详细列表列表里可以直接看到哪个门店、哪个批次、还有多少天到期。这个功能对药店的日常运营价值非常高也是论文文档里值得重点写的一个模块。4.5 成本核算与毛利统计药店经营者最关心的不只是卖了多少钱而是这批货赚了多少。所以系统在销售明细里保留每个批次对应的进货价字段统计毛利时用销售单价减去进货成本乘以数量累加得到单笔毛利。报表按日、按月、按门店、按药品维度展示。有一点要注意进货价必须取自批次记录而不能从药品主数据表里取“标准进价”。因为同一款药不同批次进价可能不一样。一批采购价是8块另一批是7块5售价相同的情况下毛利的差别只有用批次价才能算清楚。这套系统的成本核算选了移动加权平均还是逐批先进先出要看论文里怎么定义但不管哪种只要把成本来源固定在批次记录上口径就不会乱。5. 本地调试部署全流程环境准备、脚本导入与故障处理5.1 JDK、Maven、MySQL环境版本匹配本地调试的第一步是把环境对齐版本不匹配会让你浪费大量时间。这套系统基于SpringBoot 2.x通常要求JDK 1.8个别新版本会要求JDK 11以上。拿到项目先看pom.xml文件里的spring-boot-starter-parent版本号如果版本是2.3.x或2.4.x建议直接用JDK 8如果是2.7.xJDK 8和JDK 11都能跑。Maven建议用3.6.0以上低于这个版本下载依赖时可能出现兼容问题。MySQL建议用5.7或者8.0要注意8.0对驱动类名和时区处理跟5.7不一样。如果是MySQL 8.0连接串里一般要带serverTimezoneAsia/Shanghai否则Hibernate或MyBatis在拿时间字段时报错别问我是怎么知道的这坑我踩过。Java环境配置好之后打开命令行执行java -version确认显示的是1.8再继续。Maven执行mvn -v确认本地仓库路径没有用默认C盘用户目录那种奇怪位置建议在settings.xml里配一个单独目录顺便换上阿里云镜像不然后续依赖下载能把人卡哭。5.2 application.yml配置关键项项目跑起来之前一定要先打开src/main/resources/application.yml把它当成开机首选项里面有几个参数必须改成自己的spring.datasource.url改成jdbc:mysql://localhost:3306/你的库名?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghaispring.datasource.username和password改成你本地MySQL的用户名密码spring.redis.host和port改成Redis所在地址默认localhost:6379如果项目里用了文件上传看有没有upload-path类似的字段改成自己的目录改连接串的时候一定要把useUnicodetruecharacterEncodingutf8这两个参数加上。很多乱码问题不是代码不行而是连接参数没有指定UTF-8编码。5.3 导入数据库脚本与初始账号数据库脚本导入我推荐用Navicat或DataGrip两个工具都支持直接运行SQL文件。打开Navicat后新建连接连上本地MySQL右键选择“运行SQL文件”选中项目里的数据库脚本执行完后刷新左侧列表应该能看到新建的库和表。导入完成后去管理员那张表看一下初始账号。如果密码是加密的密文直接用即可但务必确认用户名和密文对应的明文密码在文档里有说明。如果没说明启动后端后用Postman调一次登录接口输入文档里常见的默认密码比如admin或123456看是否返回成功。只要登录成功后端配置基本就没问题了。有一类问题要注意数据库连接密码如果包含特殊字符比如、#这些写在yml里要加引号或者做转义否则Spring解析时会出错提示“Failed to configure a DataSource”半天排查不到原因。5.4 启动、自测与常见报错排查后端启动非常简单在项目根目录执行mvn spring-boot:run或者编译后运行jar包。看日志时不要只等最后一行要盯两个关键日志一个是Tomcat started on port(s): 8080另一个是在它前面出现的“Initialized Spring Boot application大概用了多少秒”。出现这两个就说明启动成功。启动失败常见就几种原因端口被占用报Web server failed to start。解决办法是改端口或者杀掉占用进程。数据库连不上报Cannot create PoolableConnectionFactory。先检查MySQL服务是否启动再用客户端工具实际连一次排除用户名密码问题。Redis连接不上报Unable to connect to Redis。本地没有Redis就启动一下或者把Redis相关配置注释掉并调整缓存实现。中文乱码可能是字符集问题按前面说的检查建库字符集和连接参数。启动成功后我的自测顺序是先用Postman调登录接口看返回token然后带token调一个分页查询接口比如查询药品列表看返回数据是否正常最后打开浏览器访问前端页面走一遍采购入库和销售出库流程。三条链路通了整套系统基本就处于可用状态了。预算时间上如果环境全新我一般按一个半小时来估环境配置30分钟数据库导入30分钟启动加联调30分钟。超过这个时间还没跑起来大概率是配置层面的低级问题别硬扛回头重新检查yml和SQL导入过程。6. 打包上线与二次开发项目拿到手之后怎么继续做6.1 打jar包部署到服务器本地跑通只是第一步如果你要发到服务器或者交给别人验收就需要打包部署。SpringBoot项目打包非常简单在项目根目录执行mvn clean package -DskipTests成功后target目录下会生成一个xxx.jar文件。这个jar是自带Tomcat的不需要再装服务器容器。部署到服务器的操作流程我实际运维过建议按下面几步来服务器上安装JDK 1.8、MySQL和Redis和本地环境保持一致。把数据库脚本在服务器MySQL上导入或者从本地导出一份最新的数据库再导入。把jar包放到一个固定目录比如/opt/pharmacy-system/。使用nohup java -jar pharmacy-system.jar output.log 21 启动。查看端口执行netstat或ss命令确认进程正常监听。通过公网或内网IP访问系统验收一下登录和核心业务。生产环境有一条经验建议单独建一个普通用户来跑jar不要直接用root权限隔离更安全。对接数据库时账号也要单独建不要用root连业务库。这两个习惯花不了两分钟但能少很多隐患。6.2 后续功能扩展思路一个进销存系统做到这个程度已经能运转但真正用起来之后需求往往才会涌出来。我遇到过最多、也最容易实现的三类扩展是会员与营销在销售单上挂会员信息增加会员价、积分累计、充值赠送。改动不大主要是给sale_order表加会员字段再写一个积分流水表。统计报表增强增加图形化Dashboard用定时任务汇总每日销售额、毛利、近效期药品占比再对接一个前端图表库。对药店管理者来说这种报表价值很高。多仓与配送现在只做总部门店两级库存如果连锁规模变大可以再加一个配送中心仓库存调拨逻辑从两节点变成多节点本质就是把调拨单扩展成“发货-在途-收货”三段状态。这些扩展的根基都在表结构和库存流水上。只要你没有破坏drug_batch的批次级库存模型后面的改动都是往上面加东西不需要推倒重来。这也是我当时极力推荐保留批次表独立设计的原因。再分享一个实际运维体会这种带源码和论文的完整项目最忌讳的是拿来就改、改了就乱。正确的打开方式是先花一个下午把数据库ER关系画出来再花一个上午读一遍service层核心代码等心里有整体地图了再动手改第一个页面。前面说的SQL脚本导入、JWT登录、批号扣减、定时预警四条链路是你跑读完一遍之后印象最深的四个点把它们吃透了这套系统的源码对你来说就不再是“别人的代码”而是可以自由揉捏的积木了。