ARTICLE DETAIL

资讯详情

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

Java进销存ERP源码实战:Spring Boot从采购到库存全链路解析

Java进销存ERP源码实战:Spring Boot从采购到库存全链路解析 简介这是一套基于Java开发的进销存ERP管理系统源码面向计算机专业毕业设计学生、Java初学者及中小型企业信息化建设者帮助理解企业级应用从采购、销售、库存到财务的完整业务闭环。压缩包共约2000个文件整体50.4MB以png界面截图、js脚本、java源码、css样式、html页面、xml配置为主另含sql建库脚本、properties配置、class编译文件及少量图片与字体资源覆盖前后端与数据库各层。系统采用MVC架构包含库存、销售、采购、财务及报表分析等核心模块支持MySQL、Oracle等多种数据库并具备用户权限管理功能源码可读性与扩展性较好便于二次开发与维护。目前已有207人学习下载。读者可从中获得完整项目结构、数据库设计思路、分层实现方式与权限控制方案既能作为毕业设计参考案例也能为中小企业定制化开发提供可复用的代码基础。1. Java 进销存 ERP 管理系统源码一套能跑通采购到库存的 Spring Boot 实战工程很多做企业信息化的朋友拿到「Java 进销存 ERP 管理系统源码.zip」第一反应是——又是一个只能看不能跑的演示壳子。但这套源码的定位很明确它把采购、销售、库存、财务往来这几条进销存主干链路用 Spring Boot MyBatis 完整串了起来前端配的是 Vue3 后台管理界面属于那种能直接当二次开发底座的工程而不是只贴几张截图的课程作业。适合谁一是接私活需要快速交付一套中小型 ERP 的独立开发者二是想拿真实业务代码练手 MyBatis 动态 SQL 和库存并发控制的 Java 工程师三是企业 IT 想自建轻量进销存替代 Excel 台账的团队。它解决的核心问题就一个把「进货—入库—销售—出库—库存结余」这条链路的数据一致性和事务边界用代码固化下来省去你从零设计表结构的返工。2. 拆开压缩包先看什么工程结构与技术栈选型理由拿到源码别急着mvn spring-boot:run先花十分钟把目录结构和依赖关系摸清楚否则后面报错你连是哪个模块的问题都定位不到。这套工程是典型的前后端分离多模块结构后端一个 Maven 父工程带若干子模块前端独立一个 Vue3 目录。2.1 后端模块划分与依赖关系常见做法是把后端拆成erp-common、erp-system、erp-business、erp-admin四层。erp-common放工具类、统一返回体、全局异常处理erp-system管用户、角色、菜单、权限这些基础能力erp-business才是进销存的核心采购单、销售单、库存流水、往来账都在这里erp-admin是启动模块聚合所有依赖并暴露 Controller。先看父工程的pom.xml确认 Spring Boot 版本和 MyBatis 版本这决定了你后面写代码能用哪些 API!-- 父工程 pom.xml 关键片段 -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.x/version !-- 以实际源码为准2.7 是常见稳定版 -- /parent properties java.version1.8/java.version mybatis-plus.version3.5.x/mybatis-plus.version /properties这里要说明的是Spring Boot 2.7 搭配 JDK 1.8 是这类国内 ERP 源码最常见的组合兼容性好、坑少。如果你本地是 JDK 17直接跑大概率会遇到反射相关的InaccessibleObjectException别硬上先切回 1.8 把工程跑通再说。MyBatis-Plus 的版本决定了你能不能直接用LambdaQueryWrapper3.5 以上才比较顺手。2.2 数据库表结构与进销存核心表进销存系统的灵魂在表结构。这套源码里跟业务强相关的表通常有这么几张我按重要性排一下表名作用关键字段purchase_order采购订单主表供应商、单据状态、总金额purchase_order_item采购明细商品、数量、单价、已入库数sale_order/sale_order_item销售订单及明细客户、数量、已出库数stock库存现存量商品、仓库、当前数量stock_record库存流水出入库类型、变动数量、关联单据重点看stock和stock_record的关系stock是快照stock_record是流水任何一次出入库都必须同时写这两张表且要在同一个事务里。这是判断一套进销存源码是否「能用」的分水岭——只改stock不记流水的后期对账必翻车。2.3 前端 Vue3 工程与接口约定前端目录一般是erp-ui或erp-web用 Vue3 Element Plus Axios。先看src/api目录下的接口封装它和后端 Controller 是一一对应的。接口前缀通常在vite.config.js里配了代理// vite.config.js 代理配置 server: { proxy: { /api: { target: http://localhost:8080, // 后端启动端口 changeOrigin: true } } }参数说明target必须和你后端application.yml里的server.port一致否则前端请求全部 404。changeOrigin: true是为了绕过跨域本地开发必开。很多人前端跑起来一片空白八成是这里端口对不上或者后端根本没启动。3. 把工程跑起来数据库初始化到前后端联调结构看明白了接下来是实打实的启动流程。这一章按「建库 → 改配置 → 起后端 → 起前端 → 验证登录」的顺序走每一步都给你可抄的命令和配置。3.1 数据库初始化与连接配置源码的doc或sql目录下一般有个erp.sql先建库再导入# 登录 MySQL 并创建数据库 mysql -u root -p CREATE DATABASE erp_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; exit # 导入表结构和初始数据 mysql -u root -p erp_db doc/erp.sql字符集一定要用utf8mb4进销存里商品名、客户备注经常有生僻字和 emoji用utf8会插入失败。导入完检查一下核心表有没有数据-- 确认初始数据是否导入成功 SELECT COUNT(*) FROM sys_user; -- 应该有管理员账号 SELECT COUNT(*) FROM stock; -- 库存表可能为空正常然后改后端application.yml的数据源spring: datasource: url: jdbc:mysql://localhost:3306/erp_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.DriverserverTimezoneAsia/Shanghai这个参数别省MySQL 8 不配时区会报The server time zone value is unrecognized这是血泪经验十个新手九个栽在这。3.2 后端启动与常见启动报错配置改完在父工程根目录执行# 先清理再编译避免缓存干扰 mvn clean install -DskipTests # 进入启动模块运行 cd erp-admin mvn spring-boot:run-DskipTests是为了跳过单元测试加速构建第一次跑建议加上。如果启动时报Table erp_db.xxx doesnt exist说明 SQL 没导全回去重新导。如果报端口占用改application.yml里的server.port或者杀掉占用进程# 查占用 8080 的进程 lsof -i:8080 # 或 Windows 下 netstat -ano | findstr 8080启动成功的标志是控制台打印出Started ErpApplication in x.xxx seconds且没有红色 ERROR 堆栈。3.3 前端启动与登录验证前端目录下先装依赖再起服务cd erp-ui npm install # 或 pnpm install / yarn npm run dev启动后浏览器打开控制台提示的地址一般是http://localhost:5173用 SQL 里初始化的管理员账号登录。默认账号密码通常在sys_user表里或者源码 README 有写常见的是admin/admin123这类。登录成功后进「采购管理」新建一张采购单保存后去「库存管理」看对应商品库存有没有增加——这一步能跑通说明整条链路是活的。提示如果登录接口返回 401 或一直转圈先看浏览器 Network 里请求地址是不是/api/login再对比后端 Controller 的RequestMapping前缀多半是代理路径没对上。4. 进销存核心逻辑库存扣减与事务边界怎么处理工程能跑只是及格线真正决定这套源码能不能用于生产的是库存扣减逻辑。进销存最怕的就是超卖和库存对不上这一章拆开看它的实现方式以及你二次开发时该注意什么。4.1 采购入库与库存增加的代码链路采购入库的典型流程是采购单审核通过 → 生成入库单 → 增加库存 → 写库存流水。核心 Service 方法大致长这样Service public class StockServiceImpl implements StockService { Autowired private StockMapper stockMapper; Autowired private StockRecordMapper stockRecordMapper; /** * 入库操作 * param goodsId 商品ID * param warehouseId 仓库ID * param qty 入库数量 * param bizNo 关联业务单号 */ Transactional(rollbackFor Exception.class) // 关键任何异常都回滚 public void stockIn(Long goodsId, Long warehouseId, Integer qty, String bizNo) { // 1. 更新现存量用 SQL 原子自增避免并发覆盖 int updated stockMapper.increaseStock(goodsId, warehouseId, qty); if (updated 0) { // 没有库存记录则先插入 stockMapper.insertInitStock(goodsId, warehouseId, qty); } // 2. 写库存流水 StockRecord record new StockRecord(); record.setGoodsId(goodsId); record.setWarehouseId(warehouseId); record.setChangeQty(qty); record.setBizNo(bizNo); record.setType(IN); stockRecordMapper.insert(record); } }逻辑说明Transactional保证「改库存」和「写流水」要么都成功要么都回滚这是数据一致性的底线。increaseStock对应的 SQL 必须是UPDATE stock SET qty qty #{qty} WHERE ...这种数据库层面的原子自增而不是先SELECT查出来再UPDATE设值——后者在并发下会丢更新是超卖的经典成因。参数说明rollbackFor Exception.class要显式写因为 Spring 默认只对RuntimeException回滚业务里抛的受检异常如果不指定就不会回滚这个坑很隐蔽。4.2 销售出库的库存校验与并发控制出库比入库多一步校验库存够不够。常见写法是在扣减前先查库存但这样有并发窗口。更稳的做法是把校验塞进 UPDATE 的 WHERE 条件里!-- StockMapper.xml 出库扣减 -- update iddecreaseStock UPDATE stock SET qty qty - #{qty} WHERE goods_id #{goodsId} AND warehouse_id #{warehouseId} AND qty #{qty} !-- 库存不足时影响行数为0 -- /updateService 层根据返回的影响行数判断int rows stockMapper.decreaseStock(goodsId, warehouseId, qty); if (rows 0) { throw new BusinessException(库存不足出库失败); }这样即使两个请求同时出库数据库的行锁也会保证只有一个能扣成功另一个qty #{qty}不成立返回 0 行直接抛异常回滚。这比在 Java 层加synchronized靠谱得多分布式部署下也有效。4.3 单据状态机与反审核的坑进销存单据一般有「草稿 → 已审核 → 已入库/已出库」几个状态。反审核撤销是最容易出问题的地方如果一张采购单已经入库增加了库存你直接反审核而不冲减库存库存就虚高了。正确做法是反审核时生成一张反向的库存流水并扣减库存而不是简单改状态字段。看源码里audit和unaudit两个方法是否对称处理了库存这是判断代码质量的关键点。如果源码没做你二次开发时务必补上否则用不了多久账实就不符了。5. 避坑与排查这套源码落地时最容易翻车的五个点源码能跑不代表能直接用下面这几条是我实际部署和二次开发时踩过的按「现象 → 原因 → 解决」给你列清楚。5.1 启动报时区或驱动类找不到现象启动直接抛java.sql.SQLException: The server time zone value xxx is unrecognized或ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因前者是 JDBC URL 没配serverTimezone后者是 MySQL 驱动版本和driver-class-name不匹配MySQL 5 用com.mysql.jdbc.Driver8 用com.mysql.cj.jdbc.Driver。解决URL 补上serverTimezoneAsia/Shanghai驱动类按实际 MySQL 版本改pom 里mysql-connector-java版本也要对应。5.2 前端请求全部 404现象页面能打开但所有接口报 404Network 里请求地址是http://localhost:5173/api/xxx。原因Vite 代理没生效或target端口写错。解决确认vite.config.js里proxy配置的target和后端端口一致改完必须重启npm run dev热更新不会重载代理配置。5.3 库存出现负数现象出库后库存变成负数。原因出库扣减没加qty #{qty}条件或者校验和扣减分了两步存在并发窗口。解决把库存校验合并进 UPDATE 的 WHERE 子句用影响行数判断参考 4.2 的写法。5.4 中文乱码现象商品名、客户名存进数据库变成问号。原因数据库、表、连接三层字符集不统一。解决建库用utf8mb4JDBC URL 加characterEncodingutf8检查my.cnf里character-set-server是不是utf8mb4。三层都对齐才不会乱。5.5 打包部署后静态资源 404现象本地跑正常打成 jar 部署到服务器后前端页面白屏。原因前后端分离工程后端 jar 里没有前端静态资源或者 Nginx 没配好转发。解决要么前端npm run build后把dist丢给 Nginx 托管并反代/api到后端要么把dist拷进后端resources/static一起打包。生产环境推荐前者职责清晰。6. 二次开发进阶基于这套源码扩展自定义单据与权限跑通、避坑之后多数人的下一步是加自己的业务单据比如「调拨单」「盘点单」。这一章讲怎么顺着源码的既有模式扩展而不是另起炉灶。6.1 照着现有单据复制一套新单据进销存源码的单据模块高度同构扩展新单据最省力的方式是「抄」。以加一张「库存调拨单」为例步骤是先在数据库建transfer_order和transfer_order_item两张表字段参照purchase_order改然后复制PurchaseOrderController、PurchaseOrderService、PurchaseOrderMapper三件套全局替换类名和表名最后在前端src/views下复制采购单页面改接口路径和字段。这样一套下来半天能出一个能跑的新单据比从零写快得多。6.2 权限控制到按钮级别这套源码的权限一般基于角色 菜单细一点能做到按钮级。核心是sys_menu表里的perms字段和前端v-hasPermi指令。给新单据加权限时在sys_menu插入一条记录perms填business:transfer:add这种标识前端按钮上加v-hasPermi[business:transfer:add]后端接口上加PreAuthorize(ss.hasPermi(business:transfer:add))。三处标识必须完全一致错一个字符权限就不生效这个我吃过亏后来每次都用复制粘贴而不是手敲。6.3 验证扩展是否成功的检查清单扩展完别急着交付按这个顺序验一遍新单据能否正常新增保存看数据库主表和明细表是否都有数据审核后库存是否正确变动调拨是出库仓减、入库仓增两边都要对库存流水是否生成了两条对应记录无权限账号登录后按钮是否隐藏、直接调接口是否返回 403。这几步都过了才算真正扩展成功。从那以后我每次拿到一套进销存源码都强制先跑一遍「采购入库 → 销售出库 → 查库存流水」这条最小闭环再动任何一行代码。因为库存逻辑是这套系统的黑匣子不先确认它是准的后面加再多功能都是建在沙子上。希望这套源码和上面的拆解能帮到你少走点我当年走过的弯路。本文还有配套的精品资源点击获取
返回列表