
简介这份资源是面向超市运营管理者、软件开发初学者及课程设计人员的超市进销存管理系统源码与设计资料围绕商品进货、销售管理、库存统计、订单管理四大业务模块解决手工记账效率低、库存数据滞后、订单难追踪等问题。压缩包共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_pricesupplier供应商id、name、contact、phonepurchase_order采购入库单id、supplier_id、operator_id、total_amount、status、create_timepurchase_item入库单明细id、order_id、product_id、quantity、pricesale_order销售出库单id、operator_id、total_amount、pay_type、create_timesale_item出库单明细id、order_id、product_id、quantity、pricestock当前库存product_id、quantity、warn_thresholdstock_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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver server: port: 8080serverTimezone必须显式指定不然入库单的创建时间会比实际早或晚八小时报表按日期统计时全是错的这个坑我踩过不止一次。改完在项目根目录执行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 iddeductStock UPDATE stock SET quantity quantity - #{qty} WHERE product_id #{productId} AND quantity #{qty} /updateWHERE 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 里漏了characterEncodingutf8。解决方法是删库重建建库语句带上字符集连接串补全参数。已经导入的数据没法简单修复重来最干净。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表相关的代码都强制走一遍“入库—出库—对账”三步宁可多花十分钟也不想半夜被店主电话叫起来查库存。希望这套拆解能帮你少走点弯路把这份源码真正用起来。本文还有配套的精品资源点击获取