简介:这是一套面向Java后端开发者与中小企业信息化建设者的进销存ERP管理系统源码,适合用于二次开发、课程设计或企业级项目参考。系统覆盖零售、采购、销售、仓库、财务及报表查询等核心模块,并支持预付款、收入支出、仓库调拨、组装拆卸与订单等特色业务,库存状况与出入库统计报表一应俱全,角色权限控制可精确到每个按钮和菜单。技术栈采用SpringBoot 2.0.0与Mybatis 1.3.2,前端基于Jquery、EasyUI与AdminLTE,日志使用Log4j,构建工具为Maven,数据库为MySQL 5.7,兼容Windows与Linux环境。压缩包共约2000个文件,包含251个java源码、335个class、260个js、250个css、244个html及大量png、gif等界面素材,整体约52.83MB,结构完整。目前已有746人学习下载,可帮助读者快速理解进销存业务逻辑与权限设计,节省从零搭建系统的时间。
1. 一套 Java 进销存 ERP 源码,到底能帮你省下多少造轮子的时间
如果你接过中小型商贸公司的私活,或者在小团队里从零搭过库存、采购、销售这套系统,大概率经历过这种局面:需求文档写得像散文,客户张口就要“能开单、能查库存、能看利润”,真动手时才发现光是商品的多单位换算、批次成本、往来对账就够写半个月。这套 Java 进销存 ERP 管理系统源码,解决的就是这个从零造轮子的阶段。它把采购、销售、库存、财务、基础资料这几条主线用 Java 技术栈串了起来,适合两类人:一是需要快速交付一套可用系统的开发者,二是想拿一个完整业务闭环项目练手、补足“只会写增删改查”短板的人。源码本身不是玩具 demo,它的价值在于业务表结构和单据流转逻辑已经成型,你要做的是读懂它、跑起来、按自己的场景改。
2. 先看清这套源码的技术骨架:为什么是 Java + 分层架构
2.1 技术栈选型与目录结构
拿到一套源码,我习惯先看它用什么技术栈、怎么分层,而不是急着点运行。进销存 ERP 这类系统对事务一致性要求高,采购入库要同时改库存、写流水、更新往来账,选 Java 生态基本是稳妥路线。常见做法是 Spring Boot 做 Web 层,MyBatis 或 MyBatis-Plus 做持久层,MySQL 存业务数据,前端可能是 Thymeleaf 服务端渲染,也可能是前后端分离的 Vue 后台。这套源码属于典型的 Java Web 分层结构,大致会看到这些目录:
| 目录/文件 | 作用 |
|---|---|
src/main/java/controller | 接收前端请求,做参数校验和路由 |
src/main/java/service | 业务逻辑,单据审核、库存扣减在这里 |
src/main/java/mapper | 数据库访问接口,对应 XML 映射 |
src/main/resources/mapper | MyBatis 的 SQL 映射文件 |
src/main/resources/application.yml | 数据库连接、端口、日志配置 |
sql/或doc/ | 建表脚本和初始化数据 |
先确认 JDK 版本,Java 8 和 Java 17 在语法和依赖上差异不小,跑不起来十有八九是版本对不上。我一般会先执行java -version和mvn -v,把环境对齐再往下走。
2.2 核心业务表结构怎么读
进销存 ERP 的复杂度不在代码,在表关系。你不需要一上来读完所有表,先抓住四张核心表就能理解大半:商品表、库存表、采购/销售单据主表、单据明细表。库存表通常按“商品 + 仓库”维度存一条记录,单据明细表记录每一行的数量、单价、金额。真正要命的是成本核算方式——是移动加权平均还是先进先出,这决定了库存流水表怎么设计。
-- 库存表典型结构,按商品和仓库维度唯一 CREATE TABLE stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, goods_id BIGINT NOT NULL COMMENT '商品ID', warehouse_id BIGINT NOT NULL COMMENT '仓库ID', quantity DECIMAL(18,4) DEFAULT 0 COMMENT '当前库存数量', cost_price DECIMAL(18,4) DEFAULT 0 COMMENT '当前成本单价', UNIQUE KEY uk_goods_warehouse (goods_id, warehouse_id) );这段建表语句的关键在UNIQUE KEY,它保证同一商品在同一仓库只有一条库存记录,避免并发下出现重复行。quantity用DECIMAL而不是INT,是因为很多商品按重量、长度计量,存在小数。cost_price单独存一份,是为了出库时能直接取成本,不用每次反算。读源码时先找到这张表对应的 Service,看它在采购入库和销售出库时怎么被更新,业务主线就清楚了。
3. 把源码跑起来:数据库、配置、启动三步走
3.1 建库与导入初始化脚本
跑不起来最常见的原因不是代码问题,是数据库没准备好。先建一个空库,字符集用utf8mb4,否则商品名里的特殊字符会乱码。
# 登录 MySQL 后执行 CREATE DATABASE erp_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后找到源码里的 SQL 脚本导入。注意脚本里可能包含建表语句和初始数据两部分,有的项目会拆成schema.sql和data.sql,按顺序执行。导入完用SHOW TABLES;确认表数量对得上,再挑一张基础表SELECT COUNT(*)看看有没有初始数据。如果脚本报外键约束错误,多半是执行顺序不对,先建被引用的表。
3.2 改配置文件里的连接参数
配置文件是源码和你本地环境之间的唯一接口,改错一个字符就起不来。
spring: datasource: url: jdbc:mysql://localhost:3306/erp_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.DriverserverTimezone必须显式指定,否则 MySQL 8 驱动会报时区异常,这是血泪经验。driver-class-name在 MySQL 8 里是com.mysql.cj.jdbc.Driver,老版本是com.mysql.jdbc.Driver,写错直接启动失败。改完配置别急着启动,先确认数据库服务在跑、端口没被占用。
3.3 启动与首次登录验证
用 Maven 启动是最稳的方式,比在 IDE 里点运行更容易暴露依赖问题。
# 在项目根目录执行,跳过测试加快启动 mvn clean spring-boot:run -DskipTests看到控制台输出Started Application in x seconds就算起来了。浏览器访问http://localhost:8080,用初始化数据里的管理员账号登录。如果登录后菜单空白,多半是权限表数据没导全;如果接口 404,检查context-path配置。第一次跑通后,我建议立刻做一次采购入库到销售出库的完整单据流转,这是验证系统是否真正可用的最快方式。
4. 单据流转与库存扣减:源码里最值得抠的两段逻辑
4.1 采购入库如何影响库存和成本
采购入库是整个系统的入口,它决定了后续出库成本的基准。源码里这段逻辑通常写在采购单审核的 Service 方法里,核心动作有三个:写库存流水、更新库存表、更新往来账。
// 采购入库审核核心逻辑(示意) @Transactional(rollbackFor = Exception.class) public void auditPurchaseIn(Long orderId) { PurchaseOrder order = orderMapper.selectById(orderId); for (PurchaseItem item : order.getItems()) { // 1. 写库存流水,记录本次入库 StockFlow flow = new StockFlow(); flow.setGoodsId(item.getGoodsId()); flow.setWarehouseId(order.getWarehouseId()); flow.setQuantity(item.getQuantity()); flow.setType("PURCHASE_IN"); stockFlowMapper.insert(flow); // 2. 更新库存表,存在则累加,不存在则新增 stockMapper.increaseStock(item.getGoodsId(), order.getWarehouseId(), item.getQuantity(), item.getPrice()); } // 3. 更新供应商往来账 supplierAccountMapper.increasePayable(order.getSupplierId(), order.getTotalAmount()); }@Transactional是这段代码的命门,三个动作必须在一个事务里,任何一步失败都要回滚,否则库存加了账没记,对账时就是黑匣子。increaseStock一般用INSERT ... ON DUPLICATE KEY UPDATE实现,靠前面说的唯一索引保证并发安全。item.getPrice()传进去作为入库成本,移动加权平均就是在这里重算cost_price的。读这段时重点看成本单价的计算公式,不同项目差异最大就在这。
4.2 销售出库与库存不足的处理
销售出库比入库多一层校验:库存够不够。源码里通常先查库存再扣减,但高并发下“先查后扣”有超卖风险。
// 销售出库扣减库存,带库存校验 public void auditSaleOut(Long orderId) { SaleOrder order = saleOrderMapper.selectById(orderId); for (SaleItem item : order.getItems()) { // 条件更新:库存充足才扣减,返回影响行数 int rows = stockMapper.decreaseStock(item.getGoodsId(), order.getWarehouseId(), item.getQuantity()); if (rows == 0) { throw new BusinessException("商品库存不足:" + item.getGoodsName()); } // 按当前成本价写销售出库流水 stockFlowMapper.insertSaleFlow(item, order.getWarehouseId()); } }decreaseStock的 SQL 里一定带AND quantity >= #{quantity}条件,靠数据库行锁保证不超卖,这比在 Java 里加锁可靠。返回 0 行说明库存不够,直接抛异常触发回滚。出库成本取的是库存表当前的cost_price,所以成本核算方式一变,这里的结果就全变。如果你要做高并发场景,这段是重点优化对象,常见做法是加库存预占或者用消息队列削峰。
5. 避坑与排查:跑这套源码最容易翻车的五个地方
5.1 启动报时区或驱动类错误
现象:启动直接抛The server time zone value is unrecognized或Cannot load driver class。原因:MySQL 8 驱动要求显式指定时区,且驱动类名变了。解决:连接串加serverTimezone=Asia/Shanghai,驱动类改成com.mysql.cj.jdbc.Driver,同时确认pom.xml里 MySQL 驱动版本和本地数据库匹配。
5.2 中文乱码
现象:商品名、客户名存进去变成问号。原因:数据库、表、连接三处字符集不一致。解决:建库用utf8mb4,连接串加characterEncoding=utf8,检查表字段字符集。三处统一后重启,已乱码的数据要重新录入。
5.3 单据审核后库存没变
现象:采购单审核成功,但库存表数量没动。原因:多半是事务没生效,比如@Transactional方法被同类内部调用,代理失效。解决:确认审核方法是从 Controller 直接调用的 Service 方法,没有被本类其他方法内部调用绕过代理。这是最隐蔽的坑,日志里看不出任何异常。
5.4 并发下库存出现负数
现象:压测时库存扣成负数。原因:扣减 SQL 没带库存充足条件,或者用了“先查后扣”两步操作。解决:改成条件更新UPDATE stock SET quantity = quantity - #{qty} WHERE goods_id = ? AND warehouse_id = ? AND quantity >= #{qty},用影响行数判断成败。
5.5 初始化数据登录不了
现象:系统起来了,但默认账号密码登录失败。原因:密码在数据库里是加密存储的,初始化脚本里的密文和你以为的明文对不上。解决:找到密码加密工具类,用它重新生成一个密文更新到用户表,或者直接看源码里注册逻辑用的加密方式。别去猜明文,直接按加密规则生成。
6. 二次开发前先做的一件事:用一条完整单据验证成本链路
改这套源码之前,我强烈建议先不动代码,用一条完整业务链路把成本算清楚,否则你改完库存逻辑都不知道对不对。具体做法:建一个商品,初始库存为零;做一笔采购入库,数量 10,单价 5;再做一笔采购入库,数量 10,单价 7;然后销售出库 5 个,看系统算出的出库成本是多少。移动加权平均下应该是 (10×5+10×7)/20 = 6,出库成本 30。如果算出来是 25 或 35,说明成本核算方式和你以为的不一样,这时候再去翻源码里的成本计算代码,目标就明确了。
-- 验证成本链路:查库存流水和当前成本价 SELECT goods_id, quantity, cost_price FROM stock WHERE goods_id = 1; SELECT * FROM stock_flow WHERE goods_id = 1 ORDER BY create_time;这两条查询能让你一眼看出每次入库后成本价怎么变、出库时取的哪个价。我一般会把这个验证做成一个固定的测试用例,每次改完库存相关代码就跑一遍。还有个进阶技巧:把库存流水表按时间倒序看,如果发现同一笔单据写了两条流水,多半是审核逻辑被重复触发,这是二次开发时最容易引入的 bug。从那以后我每次接手这类进销存源码,都强制先跑一遍这条成本链路再动代码,省下的排查时间远比这一步多。希望帮到你。
本文还有配套的精品资源,点击获取