拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

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

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工具、日期工具、统一返回结构
  • resources:application.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.x,JDK 8和JDK 11都能跑。

Maven建议用3.6.0以上,低于这个版本下载依赖时可能出现兼容问题。MySQL建议用5.7或者8.0,要注意8.0对驱动类名和时区处理跟5.7不一样。如果是MySQL 8.0,连接串里一般要带serverTimezone=Asia/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/你的库名?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
  • spring.datasource.username和password:改成你本地MySQL的用户名密码
  • spring.redis.host和port:改成Redis所在地址,默认localhost:6379
  • 如果项目里用了文件上传,看有没有upload-path类似的字段,改成自己的目录

改连接串的时候,一定要把useUnicode=true&characterEncoding=utf8这两个参数加上。很多乱码问题不是代码不行,而是连接参数没有指定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的,不需要再装服务器容器。

部署到服务器的操作流程我实际运维过,建议按下面几步来:

  1. 服务器上安装JDK 1.8、MySQL和Redis,和本地环境保持一致。
  2. 把数据库脚本在服务器MySQL上导入,或者从本地导出一份最新的数据库再导入。
  3. 把jar包放到一个固定目录,比如/opt/pharmacy-system/。
  4. 使用nohup java -jar pharmacy-system.jar > output.log 2>&1 &启动。
  5. 查看端口,执行netstat或ss命令确认进程正常监听。
  6. 通过公网或内网IP访问系统,验收一下登录和核心业务。

生产环境有一条经验建议:单独建一个普通用户来跑jar,不要直接用root,权限隔离更安全。对接数据库时,账号也要单独建,不要用root连业务库。这两个习惯花不了两分钟,但能少很多隐患。

6.2 后续功能扩展思路

一个进销存系统做到这个程度已经能运转,但真正用起来之后,需求往往才会涌出来。我遇到过最多、也最容易实现的三类扩展是:

  • 会员与营销:在销售单上挂会员信息,增加会员价、积分累计、充值赠送。改动不大,主要是给sale_order表加会员字段,再写一个积分流水表。
  • 统计报表增强:增加图形化Dashboard,用定时任务汇总每日销售额、毛利、近效期药品占比,再对接一个前端图表库。对药店管理者来说,这种报表价值很高。
  • 多仓与配送:现在只做总部门店两级库存,如果连锁规模变大,可以再加一个配送中心仓,库存调拨逻辑从两节点变成多节点,本质就是把调拨单扩展成“发货-在途-收货”三段状态。

这些扩展的根基都在表结构和库存流水上。只要你没有破坏drug_batch的批次级库存模型,后面的改动都是往上面加东西,不需要推倒重来。这也是我当时极力推荐保留批次表独立设计的原因。

再分享一个实际运维体会:这种带源码和论文的完整项目,最忌讳的是拿来就改、改了就乱。正确的打开方式是先花一个下午把数据库ER关系画出来,再花一个上午读一遍service层核心代码,等心里有整体地图了,再动手改第一个页面。前面说的SQL脚本导入、JWT登录、批号扣减、定时预警四条链路,是你跑读完一遍之后印象最深的四个点,把它们吃透了,这套系统的源码对你来说就不再是“别人的代码”,而是可以自由揉捏的积木了。

返回列表