news 2026/10/31 2:19:19

超市进销存管理系统源码拆解:从用例图到库存联动与二次开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
超市进销存管理系统源码拆解:从用例图到库存联动与二次开发

简介:这份资源是面向超市运营管理者、软件开发初学者及课程设计人员的超市进销存管理系统源码与设计资料,围绕商品进货、销售管理、库存统计、订单管理四大业务模块,解决手工记账效率低、库存数据滞后、订单难追踪等问题。压缩包共103个文件,约2.03MB,以aspx页面、cs后台代码、jpg与gif界面截图为主,辅以css样式、mdf与ldf数据库文件及mdl模型文件,构成一套可运行的Web系统雏形。资源附带用例图,清晰呈现员工、顾客、供应商等参与者与系统功能的交互关系,便于理解业务流程与功能划分。已有4380人学习下载,读者可从中获得进货录入与采购订单生成、销售出库与发票处理、库存阈值预警与周转分析、订单状态追踪等模块的实现思路,并借助用例图完成需求梳理与系统优化,适合作为课程设计或小型超市信息化改造的参考。

1. 从一张用例图说起:超市进销存到底管什么

第一次拿到这个超市进销存管理系统,我先翻的不是代码,而是随包附的那张用例图。原因很简单:进销存这类项目最容易写成“大杂烩”,商品、库存、采购、销售、供应商、会员全塞进去,最后谁也说不清边界在哪。用例图恰好把边界画死了——谁在用系统、每个角色能触发哪些动作,一眼就能对上后面的菜单和接口。这套系统面向的是中小超市、便利店、社区生鲜店这类场景,核心就三件事:进货入库、销售出库、库存盘点,外加商品档案和供应商管理。适合两类人:一是课程设计或毕设需要一套能跑通、结构清晰的进销存参考实现;二是小店主想拿现成代码改一改自己用。下面我按“能干什么、怎么跑、怎么改”的顺序,把这份源码拆开讲。

2. 技术栈与目录结构:先看清骨架再动手

2.1 技术选型与整体分层

这套系统走的是典型的 Java Web 单体路线,后端 Spring Boot + MyBatis-Plus,前端 Thymeleaf 服务端渲染配少量 jQuery,数据库 MySQL 8。选这套组合不是因为它时髦,而是进销存这种以表单、列表、报表为主的业务,服务端渲染反而省事——不用维护前后端两套工程,部署一个 jar 包就能跑。分层上遵循 Controller → Service → Mapper → Entity 四层,DTO 和 VO 单独放,避免实体类被页面字段污染。

常见做法是把权限也塞进这套分层里,这里用的是 Spring Security 做登录和角色拦截,角色分管理员、仓管、收银三种,正好对应用例图里的三个参与者。我一般会先确认三件事:依赖版本是否锁死、数据库脚本是否齐全、有没有默认账号。这三样齐了,跑起来的概率就有八成。

2.2 目录结构逐层说明

解压后根目录大致是这样,我按实际职责标注:

supermarket-ims/ ├── pom.xml # Maven 依赖与打包配置 ├── sql/ │ ├── schema.sql # 建库建表脚本 │ └── data.sql # 初始化数据(角色、默认账号、示例商品) ├── src/main/java/com/example/ims/ │ ├── ImsApplication.java # 启动类 │ ├── config/ # Security、MyBatis、全局异常配置 │ ├── controller/ # 页面跳转与接口入口 │ ├── service/ # 业务逻辑,含事务边界 │ ├── mapper/ # MyBatis-Plus Mapper 接口 │ ├── entity/ # 数据库实体 │ ├── dto/ vo/ # 入参出参对象 │ └── common/ # 统一返回、常量、工具类 └── src/main/resources/ ├── application.yml # 数据源、端口、日志配置 ├── templates/ # Thymeleaf 页面,按模块分目录 └── static/ # css/js/图片

sql目录是很多人会忽略的地方,但它决定了你能不能一次跑通。schema.sql里建表顺序有讲究:先建商品、供应商这类主表,再建入库单、出库单这些带外键的从表,最后建库存流水。data.sql里预置了三个角色的账号和一批示例商品,方便登录后立刻看到数据。config目录下的 Security 配置决定了哪些路径放行、哪些要登录,改权限基本都从这里下手。

2.3 核心数据表关系

进销存的难点不在增删改查,而在库存怎么算准。这套系统的表设计围绕“单据 + 流水”展开:

表名作用关键字段
product商品档案id、name、barcode、category_id、purchase_price、sale_price
supplier供应商id、name、contact、phone
purchase_order采购入库单id、supplier_id、operator_id、total_amount、status、create_time
purchase_item入库单明细id、order_id、product_id、quantity、price
sale_order销售出库单id、operator_id、total_amount、pay_type、create_time
sale_item出库单明细id、order_id、product_id、quantity、price
stock当前库存product_id、quantity、warn_threshold
stock_log库存流水id、product_id、change_type、change_qty、ref_id、create_time

关键点在于stock只存当前数量,stock_log记录每一次增减的来源单据。这样库存对不上时,能顺着流水倒查是哪张单子出的问题,而不是对着一个数字干瞪眼。warn_threshold是库存预警线,低于它就标红提醒补货。

3. 跑起来:从建库到登录的完整步骤

3.1 环境准备与数据库初始化

先确认本机有 JDK 17、Maven 3.8+、MySQL 8。版本别乱升,Spring Boot 3.x 对 JDK 版本卡得比较死,用 JDK 8 会直接编译失败。数据库初始化我习惯用命令行,避免图形工具编码问题:

# 登录 MySQL 并创建库,字符集必须 utf8mb4,否则商品名带表情或生僻字会乱码 mysql -u root -p -e "CREATE DATABASE ims DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;" # 依次导入建表和初始化数据,注意顺序不能反 mysql -u root -p ims < sql/schema.sql mysql -u root -p ims < sql/data.sql

导入完执行SHOW TABLES;确认八张表都在。如果data.sql报外键约束错误,多半是schema.sql没导全或者中途失败,重新建库再来一遍最省事。

3.2 配置数据源与启动

打开application.yml,改数据源这一段:

spring: datasource: url: jdbc:mysql://localhost:3306/ims?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver server: port: 8080

serverTimezone必须显式指定,不然入库单的创建时间会比实际早或晚八小时,报表按日期统计时全是错的,这个坑我踩过不止一次。改完在项目根目录执行:

mvn clean package -DskipTests java -jar target/supermarket-ims-1.0.0.jar

看到Started ImsApplication就说明起来了,浏览器访问http://localhost:8080,用data.sql里预置的管理员账号登录。如果页面能开但登录报 403,去config下的 Security 配置看登录路径有没有被误拦。

3.3 一次完整业务闭环验证

跑通不等于跑对,我一般会走一遍最小闭环来验证:新建一个商品 → 建采购单入库 100 件 → 查库存是否变 100 → 建销售单出库 30 件 → 查库存是否变 70 → 看库存流水是否有两条记录。这一圈走完,说明单据、明细、库存、流水四者的联动是通的。任何一环数字对不上,问题基本出在 Service 层的事务或库存更新逻辑上,下一章会具体讲。

4. 核心模块拆解:入库、出库与库存联动

4.1 采购入库的业务链路

入库不是简单往stock表加个数,它要同时写主单、明细、库存、流水四张表,任何一步失败都得回滚。Service 层是整个链路的中枢:

@Service public class PurchaseService { @Autowired private PurchaseOrderMapper orderMapper; @Autowired private PurchaseItemMapper itemMapper; @Autowired private StockMapper stockMapper; @Autowired private StockLogMapper logMapper; // 事务注解保证四张表要么全成功要么全回滚 @Transactional(rollbackFor = Exception.class) public void createPurchase(PurchaseDTO dto, Long operatorId) { // 1. 写主单,状态置为已入库 PurchaseOrder order = new PurchaseOrder(); order.setSupplierId(dto.getSupplierId()); order.setOperatorId(operatorId); order.setStatus("FINISHED"); order.setCreateTime(LocalDateTime.now()); orderMapper.insert(order); BigDecimal total = BigDecimal.ZERO; for (PurchaseItemDTO item : dto.getItems()) { // 2. 写明细,累加总金额 PurchaseItem pi = new PurchaseItem(); pi.setOrderId(order.getId()); pi.setProductId(item.getProductId()); pi.setQuantity(item.getQuantity()); pi.setPrice(item.getPrice()); itemMapper.insert(pi); total = total.add(item.getPrice().multiply( BigDecimal.valueOf(item.getQuantity()))); // 3. 更新库存:存在则加,不存在则初始化 Stock stock = stockMapper.selectById(item.getProductId()); if (stock == null) { stock = new Stock(item.getProductId(), item.getQuantity(), 10); stockMapper.insert(stock); } else { stock.setQuantity(stock.getQuantity() + item.getQuantity()); stockMapper.updateById(stock); } // 4. 记流水,refId 指向主单,方便倒查 StockLog log = new StockLog(); log.setProductId(item.getProductId()); log.setChangeType("PURCHASE"); log.setChangeQty(item.getQuantity()); log.setRefId(order.getId()); log.setCreateTime(LocalDateTime.now()); logMapper.insert(log); } // 5. 回填总金额 order.setTotalAmount(total); orderMapper.updateById(order); } }

这段代码有几个参数值得说清:status用字符串常量而不是枚举,是为了数据库里直接可读,代价是拼写错误编译期发现不了,我一般会在common里定义常量类统一管理。changeQty入库记正数、出库记负数,这样流水求和就等于当前库存,对账时一条 SQL 就能验证。rollbackFor = Exception.class不能省,默认只回滚运行时异常,受检异常会导致部分表写入成功,库存就脏了。

4.2 销售出库与库存校验

出库比入库多一道关卡:库存够不够。这里最容易翻车的是并发——两个收银同时卖最后一件,都查到库存为 1,都扣成功,库存变 -1。常见做法是在更新时加条件,用数据库的行锁兜底:

@Transactional(rollbackFor = Exception.class) public void createSale(SaleDTO dto, Long operatorId) { SaleOrder order = new SaleOrder(); order.setOperatorId(operatorId); order.setPayType(dto.getPayType()); order.setCreateTime(LocalDateTime.now()); orderMapper.insert(order); for (SaleItemDTO item : dto.getItems()) { // 带条件的更新:库存充足才扣减,返回影响行数 int affected = stockMapper.deductStock( item.getProductId(), item.getQuantity()); if (affected == 0) { // 库存不足直接抛异常,事务回滚,前面的主单也撤销 throw new BizException("商品库存不足:" + item.getProductId()); } // 写明细与流水,changeQty 记负数 // ... 省略与入库对称的写入逻辑 } }

对应的 Mapper SQL 是关键:

<update id="deductStock"> UPDATE stock SET quantity = quantity - #{qty} WHERE product_id = #{productId} AND quantity >= #{qty} </update>

WHERE quantity >= #{qty}这一句就是防超卖的核心,把校验和扣减合成一个原子操作,交给数据库保证一致性。返回 0 说明库存不够,抛业务异常让整个事务回滚。这比先查再扣的两步写法可靠得多,后者在高并发下必然出问题。

4.3 库存预警与流水对账

库存预警的逻辑放在查询层,列表页按quantity < warn_threshold过滤标红即可,不需要定时任务。真正值得写的是对账——当店主说“系统库存和实际对不上”时,怎么快速定位:

-- 用流水汇总反推库存,与 stock 表比对,找出差异商品 SELECT s.product_id, s.quantity AS current_qty, COALESCE(SUM(l.change_qty), 0) AS log_qty FROM stock s LEFT JOIN stock_log l ON s.product_id = l.product_id GROUP BY s.product_id, s.quantity HAVING s.quantity <> COALESCE(SUM(l.change_qty), 0);

正常情况下这条 SQL 应该返回空。有结果说明某次库存更新没记流水,或者流水记了但库存没改,多半是某段业务代码漏了写stock_log。我一般把这条查询做成后台的一个“库存对账”页面,出问题时点一下就能看到差异商品,比翻日志快得多。

5. 避坑与常见问题排查

5.1 登录后菜单空白或 403

现象是能进登录页,输对账号密码后跳转成功,但菜单点进去全是 403。原因通常是 Spring Security 的角色前缀没对上——数据库里存的是ADMIN,配置里写的是ROLE_ADMIN,框架匹配时差一个前缀就全拦。解决方法是统一在data.sql里给角色加ROLE_前缀,或者在 Security 配置里用hasAuthority("ADMIN")而不是hasRole。改完记得清浏览器缓存重新登录。

5.2 入库成功但库存没变

现象是采购单列表能看到新单子,但商品库存纹丝不动。原因多半是@Transactional加在了 Controller 上而不是 Service 上,或者同类内部方法调用导致事务注解失效。Spring 的代理机制只对跨类调用生效,this.xxx()这种自调用不走代理,事务等于没加。解决办法是把事务方法抽到独立 Service,通过注入调用,别在同一个类里自己调自己。

5.3 金额出现 0.30000000000000004

现象是采购总金额或销售小计出现一长串小数。原因是用了double做金额运算。进销存里所有金额字段必须用BigDecimal,且构造时用字符串new BigDecimal("9.9")而不是new BigDecimal(9.9),后者会把浮点误差带进来。数据库对应字段用DECIMAL(10,2),别用FLOAT。

5.4 中文商品名导入后变问号

现象是data.sql导入后商品名全是乱码或问号。原因是建库时没指定utf8mb4,或者 JDBC URL 里漏了characterEncoding=utf8。解决方法是删库重建,建库语句带上字符集,连接串补全参数。已经导入的数据没法简单修复,重来最干净。

5.5 打包后启动报找不到主类

现象是mvn package成功,java -jar却报no main manifest attribute。原因是pom.xml里没配spring-boot-maven-plugin,打出来的是普通 jar 不是可执行 jar。在build节点补上插件配置,重新mvn clean package即可。这个插件还会把依赖一起打进去,所以产物体积会明显变大,属正常现象。

6. 二次开发:从能跑到好用要改哪几处

拿到这套代码,多数人不会满足于跑通,而是想改成自己店能用的样子。我按改动收益从高到低排一下。第一处是商品分类和条码。原生的商品档案只有分类 ID,没有分类管理页面,实际用起来很不方便。建议加一张category表和一个简单的树形管理页,条码字段加唯一索引,收银时用扫码枪直接查商品,比手输快一个量级。第二处是销售端的支付方式。现在pay_type只是个字符串,微信、支付宝、现金、会员卡都混在一起,想统计各渠道流水就得自己解析。改成枚举加一张支付流水表,后续接对账会轻松很多。

第三处是报表。系统目前只有列表,没有按日、按月的销售汇总和毛利分析。我的做法是加一个ReportController,用几条聚合 SQL 直接出数,不引入复杂的报表引擎:

-- 按日统计销售额与毛利,sale_price 与 purchase_price 的差即毛利 SELECT DATE(o.create_time) AS day, SUM(i.quantity * i.price) AS revenue, SUM(i.quantity * (i.price - p.purchase_price)) AS gross_profit FROM sale_order o JOIN sale_item i ON o.id = i.order_id JOIN product p ON i.product_id = p.id WHERE o.create_time >= #{start} AND o.create_time < #{end} GROUP BY DATE(o.create_time) ORDER BY day;

这条 SQL 里purchase_price取的是商品当前进价,严格来说应该取销售当时的成本价,但中小超市对精度要求没那么高,用当前进价近似完全够用。真要精确,就得在sale_item里冗余一个成本价字段,下单时快照进去。

改造时有个习惯我一直保持:每加一个功能,先在用例图上补一个用例,再动代码。用例图不是画给老师看的,它是你和业务方对齐边界的工具,改着改着发现某个用例牵扯三个模块,就该停下来想想是不是拆得不对。还有一点,改库存相关逻辑后,一定跑一遍第 3 章那个最小闭环,再执行一次对账 SQL 确认返回空。从那以后我每次动stock表相关的代码,都强制走一遍“入库—出库—对账”三步,宁可多花十分钟,也不想半夜被店主电话叫起来查库存。希望这套拆解能帮你少走点弯路,把这份源码真正用起来。

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

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

Swift内购支付工具:StoreKit 1与2统一封装,解决审核被拒与丢单问题

简介&#xff1a;面向iOS开发者的Swift内购支付实践工程&#xff0c;聚焦应用内购买&#xff08;IAP&#xff09;从产品配置到交易回调的完整链路&#xff0c;适合已具备Swift基础、希望打通StoreKit接入与收据验证环节的中级开发者参考。压缩包共31个文件、约66KB&#xff0c;…

作者头像 李华
网站建设 2026/10/31 2:17:46

C语言学习笔记——常见操作符和表达式求值

C语言学习笔记——常见操作符和表达式求值 Hello大家好&#xff0c;又是我Mii&#xff0c;最近几天Mii又学习了常见操作符和表达式求值&#xff0c;所以Mii我又来写一篇文章来分享Mii学习到的知识了&#xff0c;话不多说直接开写(&#xff40;・ω・) 上次Mii学习了常亮和变量的…

作者头像 李华
网站建设 2026/10/31 2:17:10

继承和接口(Java)

继承继承是一种由已有的类创建新类的机制。利用继承可以先定义一个共有属性的一般类&#xff0c;根据该一般类再定义具有特殊属性的子类&#xff0c;子类继承一般类的属性和行为&#xff0c;并根据需要增加它自己的新的属性和行为。特点&#xff1a;java不支持多重继承&#xf…

作者头像 李华
网站建设 2026/10/31 2:16:26

山区落石目标检测数据集 助力边坡落石风险智能监测 AI深度学习第10837期

文章目录山区落石目标检测数据集&#xff0c;助力边坡落石风险智能监测数据概括应用价值小白入手训练代码山区落石目标检测数据集&#xff0c;助力边坡落石风险智能监测 边坡落石检测是山区道路地质安防的重要方向&#xff0c;这份山区落石目标检测数据集&#xff0c;面向边坡…

作者头像 李华
网站建设 2026/10/31 2:15:38

Claude 桌面端已经更新,为什么还报错?我把两个问题一起修好了

我明明已经下载了新版 Claude 桌面端&#xff0c;发一句问候&#xff0c;界面却只给我一个 Invalid request。 排查之后才发现&#xff0c;桌面端实际启动的 Claude Code 还是 2.1.237&#xff0c;而当前网关要求的最低版本是 2.1.280。修好这一层&#xff0c;界面又提示 Haiku…

作者头像 李华