进销存ERP系统源码实践:Spring Boot + Vue3 + 小程序全栈落地
2026/9/8 5:25:37 网站建设 项目流程

简介:一套基于 .NET 与 MsSQL 的完整进销存 ERP 管理系统源码,内置小程序端,适合中小型企业及开发者进行库存、销售、采购等核心业务管理,也可作为 .NET 企业级项目二次开发的参考。源码包共 7267 个文件,总大小约 46.71MB,主要包含后端 C# 相关文件(cs、aspx、ashx)、前端页面与脚本(htm、html、js、css、gif、png、jpg 等),以及小程序相关文件(wxml、wxss、json),目录结构完整,便于定位模块。系统自带电商管理功能,可与公众号、小程序对接,覆盖微信订单、小程序订单、公众号订单及参数设置等场景,销售、采购模块同样齐备。该项目已有多家企业实际使用,成熟稳定,下载后可直接获取全套源码和必要配置,在此基础上进行扩展或定制开发十分高效。目前已有 9655 人学习下载,适合需要部署 ERP/进销存系统或希望研究整套业务源码的开发者参考。 完整可跑的进销存ERP管理系统,从来都不是什么遥不可及的大厂项目。我自己手头这套“源码+小程序”的全套系统,就是从一个真实的中小商贸公司需求里长出来的。仓库要管货、财务要看账、老板要随时在手机上看销售额和毛利,还要能开单、能收款、能打印小票。今天我把这套系统的整体设计思路、源码关键实现、小程序端对接经验,以及我在实盘上线过程中踩过的坑,一并整理出来。不管你是准备拿源码二次开发的技术同学,还是正打算给自家公司或客户搭一套进销存系统的实施者,这篇文章应该都能让你少走不少弯路。

这套系统的代码量不算夸张,但五脏俱全:后端用 Spring Boot 做接口服务,管理后台是 Vue3 的单页应用,移动端配套微信小程序,数据库用 MySQL,缓存和并发控制交给 Redis。三个端全打通,数据从商品建档、采购入库、销售出库、库存盘点、往来对账到利润报表,一整条链路都在闭环跑。

1. 项目定位:一套能直接落地的进销存ERP源码

1.1 这套系统到底解决什么问题

进销存,翻译成大白话就是进货、销售、库存三件事。但实际做业务的时候,这三件事牵扯出来的东西特别多:先要有商品资料、客户档案、供应商档案,然后采购要下单、入库要有审核,销售要开单、要收款,库存要变动、要盘点,财务要算毛利、要管应收应付。任何一个环节断掉,月底对账就是灾难。

我最初拿到这个需求时,客户明确说不要那种“只登记单据”的小打小闹系统,而是要一套覆盖“采购-入库-销售-出库-库存-财务”全流程的管理工具。而且仓库人员普遍年纪偏大,电脑操作不熟练,所以移动端必须能用小程序扫码开单。基于这些约束,系统被拆成了三个端:管理后台负责基础资料、审核、报表;小程序端负责仓库扫码、移动开单、老板看数;后端统一处理业务规则和数据存储。

这套源码的价值在于,它不是教学意义的半成品,而是可以直接部署上线、并且能支撑几十万级商品数的实际系统。新手用它学企业级项目结构,老手拿它做二次开发底座,都合适。

1.2 三个端的分工与真实的用户场景

先看管理后台,它是给管理员、财务、采购和销售主管用的。职责分别是:商品分类维护、供应商和客户管理、采购单审核、销售单审核、入库单确认、退款退货处理、毛利率报表、应收应付账龄分析。PC端屏幕大,适合批量操作和复杂查询。

小程序端是我个人认为这套系统里最有价值的部分,它面对的是仓管员和老板两种截然不同的角色。仓管员在小程序上打开“扫码入库”,对着商品的条码扫一下,系统自动带出商品名称、规格和上次采购价,录入数量就能生成入库单;老板打开小程序看的是今日销售额、回款额、低库存商品预警,以及按客户维度排列的应收账款排行。

后端服务层是一套 RESTful API,负责鉴权、参数校验、业务事务、库存流水、报表聚合。设计上做了几个关键约定:所有单据必须走审核流程,所有库存变动必须写流水,所有对外接口统一返回 Result 包装体。这样的好处是,前端三个端的行为完全一致,不会出现“后台改了库存,小程序上却没变”的情况。

2. 技术选型与源码架构思路

2.1 后端为什么选 Spring Boot 而不是别的

现在新手做管理系统,Python 有 FastAPI/Django,Node 有 NestJS,Java 这边其实也有 Spring Boot 和 Solon 等选择。但我最终还是选了 Spring Boot 3.x,原因很简单:这套系统要承载完整的进销存业务,里面涉及复杂的库存流水、多表单据事务、定时任务(如自动生成月度报表)、Excel 导入导出、权限精细控制,这些场景 Spring 生态的组件最成熟,出问题的概率最小,招人也好招。

配合的组件我拉了这几样:MyBatis-Plus 做 ORM,负责单表 CRUD 几乎不用写 SQL;Flyway 管理数据库版本,避免多人开发时字段不一致;Redis 做登录 Token 和库存扣减的分布式锁;Sa-Token 或者 Spring Security 做权限(我实际用的是 Sa-Token,配置轻、注解式权限控制方便)。

这里有一点值得多说:库存操作不能光靠数据库事务,还需要在代码层加锁。我的做法是,扣减库存时用 Redis 锁住当前商品 SKU,再在事务里执行“查库存-校验-扣减-写流水”。这样就算两个仓管同时扫同一件商品,也不会出现超卖。

2.2 管理端和小程序端的技术搭配

管理后台用了 Vue3 + Element Plus + Vite + Pinia + Vue Router。选这个组合理由很直白:Element Plus 的表单组件和表格组件对后台管理场景覆盖得特别好,像商品多规格录入、单据明细行的动态增删,这类交互用现成组件改改就能上,开发效率很高。

小程序端我用了原生微信小程序框架,没去套 uni-app 或者 Taro。原因是这个项目里小程序端功能相对聚焦(扫码、开单、看报表),原生语法完全够用;而且一旦遇到微信特有的 API(比如扫普通条码、蓝牙打印小票),原生调试比跨端框架舒服得多。你要是打算以后同时发支付宝小程序或抖音小程序,那可以换 uni-app,但当前场景原生更稳。

前后端接口这块,我没有用 OpenAPI 生成 SDK,而是手写 TypeScript 接口定义,配了 axios 封装。原因挺实际:项目节奏快,后端接口字段经常微调,手写类型反而灵活,不会因为重新生成代码导致空白扫除。接口统一走/api前缀,JWT 格式的 Token 放在 Header 里,小程序端则用 wx.request 封装一层。

2.3 数据库设计里的关键细节

进销存系统的表不少,但核心表其实就几张:商品表(product)、SKU 表(sku)、仓库表(warehouse)、采购单(purchase_order)、采购单明细(purchase_order_item)、销售单(sale_order)、销售单明细(sale_order_item)、库存表(stock)、库存流水表(stock_flow)、客户表、供应商表。

我重点想讲两个设计约定。

第一个关于 SKU 和库存。商品和 SKU 必须拆成两张表,因为同一个商品可能有多个规格,不同规格的库存是独立计算的。库存表则按“SKU + 仓库”维度存储当前可用库存和锁定库存。所有库存的变动一律只通过库存流水表追加记录,任何业务表都不得直接 UPDATE 库存数字,这从根本上保证了数据可追溯。

第二个关于单据编号。手工录单系统最忌讳编号杂乱。我用了一套“前缀 + 日期 + 序列”的生成器,销售单 SALE202401010001,采购单 PURCHASE202401010001,入库单 STKIN202401010001,出库单 STKOUT202401010001。编号不在数据库自增,而是由服务端统一生成,避免并发重复。

我自己在数据库设计上踩过最大的坑是:一开始把“供应商”和“客户”各建了一张表,结果后续做“既是供应商又是客户”的往来单位时,完全没法处理对账。后来重构时加了一张partner伙伴表,用“类型字段(供应商/客户/二者都是)”来区分,再关联联系人、地址、账户信息。如果你也是从零设计进销存,我强烈建议一开始就做这个统一建模,否则后面对接财务应收应付会非常痛苦。

3. 实操过程:把这套源码跑到生产环境

3.1 快速搭建本地开发环境

拿到源码以后,第一步不是急着看代码,而是把环境跑起来。我习惯的流程是这样:

  1. 安装 JDK 17、MySQL 8.0、Redis 6.x、Node.js 18。
  2. 导入数据库脚本,项目里有个sql/init.sql,直接 source 进去。
  3. 修改application.yml,把数据库账号密码、Redis 地址改成自己的。
  4. 启动后端服务,端口默认 8080,看到 “ERP Server Started” 就说明起来了。
  5. 管理后台前端npm install然后npm run dev,浏览器打开 5173 端口。
  6. 小程序用微信开发者工具导入miniapp目录,appid 先用测试号,后面再换正式。

这里面最容易出的问题就是“报表服务器连接不上”或者“数据库连接失败”。我之前帮同事排查过一次,现象是前端登录正常,但一点报表模块就报连接失败。最后发现是因为报表模块里有独立的数据源配置,默认指向了application-report.yml里写死的数据库地址,没有跟着主配置走。改成一个外部化的动态数据源路由就好。

3.2 初始化基础资料的正确顺序

系统启动后,不要急着录单据。基础资料的维护顺序有讲究:先建计量单位、商品分类;再建仓库、往来单位(供应商和客户);然后建商品SKU,录入初始库存;最后建操作员并分配权限。

有个常见误区是上来就导商品Excel,结果供应商还没建,商品表里只能留空供应商字段,后面做采购入库时对不上账。我的建议是:先用 Excel 模板把往来单位导进去,再去导商品,顺序不能反。系统中我专门写了导入校验逻辑,如果商品引用了不存在的供应商编码,那一行会被标红跳过,并生成错误报告下载。

初始化库存这件事也值得多说一句。不要直接在库存表里 insert 初始数据,而是用“期初库存入库单”来导入,每行写明商品、仓库、数量和单价,审核后系统自动生成一条入库流水。这样做的好处是,你的账面上有一张实实在在的期初单,以后对账时能直接追溯。

3.3 管理端和小程序端联调的关键配置

前后端联调最烦的是跨域和地址配置。管理端 Vite 开发服务器配了代理,/api前缀的请求全部转发到后端;生产环境则用 Nginx 做反向代理,同时托管前端静态文件。小程序端不一样,它没有浏览器跨域的概念,但它要求请求的域名必须是 HTTPS,并且在小程序后台配置 request 合法域名。

我联调时的固定操作是:先用 ngrok 或者内网穿透工具把本地后端暴露成 HTTPS 临时域名,然后在小程序开发者工具里把“不校验合法域名”打开,直接用临时域名调本地接口。真正上线前再换成正式域名,同时把后端的 Nginx 配好证书。整个过程如果你跳过,直接拿http://localhost:8080去小程序里请求,半天都连不上。

4. 核心业务代码实现:哪些地方最值得细看

4.1 出入库和库存流水的事务实现

在进销存里,最核心的方法就是“库存变更”。我写的StockService.changeStock()是这个系统的命脉方法,它负责接收 skuId、warehouseId、变更数量、关联单据号、业务类型,然后执行一段事务逻辑。核心伪代码大概是:

@Transactional(rollbackFor = Exception.class) public void changeStock(StockChangeDTO dto) { String lockKey = "stock:sku:" + dto.getSkuId(); boolean locked = redisLock.tryLock(lockKey, 3, TimeUnit.SECONDS); if (!locked) { throw new BizException("库存操作繁忙,请重试"); } try { Stock stock = stockMapper.selectForUpdate(dto.getSkuId(), dto.getWarehouseId()); if (dto.getChangeType() == OUTBOUND) { if (stock.getAvailableStock() < dto.getQuantity()) { throw new BizException("可用库存不足"); } stock.setAvailableStock(stock.getAvailableStock() - dto.getQuantity()); stock.setFrozenStock(stock.getFrozenStock() - dto.getQuantity()); } else { stock.setAvailableStock(stock.getAvailableStock() + dto.getQuantity()); stock.setTotalStock(stock.getTotalStock() + dto.getQuantity()); } stockMapper.updateById(stock); stockFlowMapper.insert(buildFlow(dto)); } finally { redisLock.unlock(lockKey); } }

这里有两个细节必须做到位:一是selectForUpdate要加数据库行锁,二是 Redis 锁的 key 必须和数据库行锁的粒度一致。我试过一次只加 Redis 锁不加行锁,两个服务实例同时操作时,虽然 Redis 锁挡住了大部分冲突,但极端情况下数据库层面的更新丢失还是会发生。两边都加,才算是双保险。

4.2 单据“审核-反审核”的状态机处理

单据不能删,只能作废;单据状态通过审核流转。这是我给这套系统定的铁律。采购单的状态有:待审核、已审核、已入库、已作废。销售单的状态有:待审核、已审核、部分出库、已出库、已作废。

每一张单据的审核动作都会触发业务逻辑。采购单审核通过后,系统会自动生成一张待入库的入库单;入库确认后增加库存。销售单审核通过后,系统会把对应商品加入“锁定库存”,直到出库完成后再扣减可用库存。这个“锁定库存”的设计极其重要,它解决了多人同时抢单时“都以为有货”的假象。

反审核是所有进销存系统里最容易出 bug 的地方。我的处理方法是:反审核不能跳过已发生的下级流程。比如销售单已经出库了,你想反审核,系统会提示“请先删除出库单”。哪怕是管理员角色,也需要在权限设置里单独开启“强制反审核”才会放行。这个限制看似繁琐,但避免了大量账实不符的问题。

4.3 报表统计用不对方式就会卡死

进销存的报表往往跑在几万条单据明细上,如果用 MyBatis-Plus 的list()把所有明细拉到内存再聚合,系统基本就废了。我在统计模块里全部改成了 SQL 级别的聚合。以“销售毛利报表”为例,核心 SQL 类似:

SELECT DATE_FORMAT(s.pay_time, '%Y-%m-%d') AS day, s.partner_id, p.name AS partner_name, SUM(i.sale_amount) AS sale_amount, SUM(i.sale_cost) AS sale_cost, SUM(i.sale_amount - i.sale_cost) AS gross_profit FROM sale_order s LEFT JOIN sale_order_item i ON s.id = i.order_id LEFT JOIN partner p ON s.partner_id = p.id WHERE s.status IN ('SHIPPED', 'DONE') AND s.pay_time >= #{startTime} AND s.pay_time < #{endTime} GROUP BY day, s.partner_id ORDER BY day DESC

这种聚合查询在数据量大时也有短板,所以我又加了一层 Redis 缓存,把按天、按月的统计结果缓存 10 分钟。老板看日报时命中缓存,速度几乎秒开;后台的流水明细查询则走 MySQL 索引,不混在一起。

5. 小程序端实现和微信支付对接实录

5.1 小程序登录鉴权和扫码开单的细节

小程序端的登录流程和 Web 端不一样,不能用账号密码登录,而是走wx.login()获取 code,后端拿 code 去微信接口换 openid,再用 openid 关联系统用户。如果在系统里已经绑定过员工账号,就直接返回 Token;如果没绑定,则弹出绑定页面要求输入手机号和管理密码。

首次扫码开单时,我建议在商品基础数据里就把条码字段维护全。入库和出库的扫码动作打通了微信的wx.scanCode接口,扫到的条码直接作为 sku 编码去模糊查询商品。这里有个细节:市面上的商品条码格式很多,有些是纯数字的 EAN-13,有些是企业自编的二维码,所以后端查询的时候不要做死板相等匹配,要用“完全匹配->包含匹配->模糊匹配”的三级策略,否则很容易出现“扫不出来”的情况。

5.2 小程序微信支付 V3 对接的踩坑记录

微信支付这块我前后折腾了将近一天,主要问题集中在签名和证书上。现在小程序支付对接的是 V3 版本接口,它在请求头里要求带上Authorization: WECHATPAY2-SHA256-RSA2048的签名信息,这个签名格式比 V2 单纯 MD5 复杂得多。

关键的坑在于:商户私钥要用商户API私钥,而不是APIv3密钥。两者不是一回事。前者是商户平台生成的 RSA 私钥,用来给请求做签名;后者是 AES 密钥,用来解密支付回调的报文。签名时需要先拼接请求方法 + 请求路径 + 时间戳 + 随机串 + 请求体的字符串,再用 SHA256WithRSA 算法签名,然后把签名结果和商户号、证书序列号一起放进请求头。

我把自己用的签名工具类封装好了之后,测试支付一次性通过。但第二天发现线上联调时,支付回调的验签又报错了,当时排查了很久才发现是因为微信的证书序列号要从证书里动态读,而不是写死。后来我写了一个证书加载器,定时从微信平台拉取最新证书,问题才彻底解决。要是你也在对接支付,建议直接从源码里的WxPayService看起,签名、加密、回调解密、退款逻辑都在里面。

5.3 移动端的消息通知和待办提醒

小程序端还有一块容易被忽略但实际使用体验提升很大的功能,那就是消息通知。我在系统里引入了微信订阅消息,配置的是“待办事项提醒”模板。采购主管在 PC 端新建了采购单,仓管员的小程序就会收到“您有一笔新的入库任务待处理”的通知;销售出库单审核通过后,销售员也会收到提醒。

订阅消息有个特性:用户必须在小程序里主动点击“允许订阅”后,下一次才能推送一条。也就是说,你不能靠它做连续轰炸,只能做关键节点的提醒。我的方案是在用户每次进入待办列表时,弹窗申请订阅,拿到一次权限就发一条,用户体验相对自然。如果你在项目里也用到订阅消息,记得把模板 ID 放到服务端统一配置,别写死在小程序前端,否则后期想换模板很麻烦。

6. 常见问题与排查技巧实录

6.1 库存对不上,先查流水再查锁

定位问题时有个优先级铁律:先查库存流水表,再看业务表。我遇到过“销售出库之后库存反而增加了”的诡异问题,排查到最后发现是某个接口重复调用了changeStock(),一次是出库扣减,另一次是作废单时回补库存,两个逻辑在并发下交错执行,流水账面上数据都对,但净库存变了。

解决办法也简单:给流水表加一个“业务单据号+类型”的唯一索引,同一张销售单明细只允许产生一条出库流水。这样一来,重复调用直接报唯一键冲突,问题自然浮出水面。这也是为什么我一直强调,源码里的create_order流程必须经过代码审查,不能让写单和改库存分成两个没有强关联的独立事务。

6.2 报表数据加载慢,优化思路要分层

报表变慢是每个老系统都躲不过的问题。我的优化顺序是:先看数据库索引,再看查询方式,最后上缓存。

具体到这个项目,最有效的优化是把sale_order_item表里的order_time字段改成二级索引,并且在大结果集查询时强制走索引,避免 type 变成 ALL。其次是停掉所有前端懒加载导致的多请求并发统计,把日报和周报接口串行化,减少瞬时数据库压力。最后才是上 Redis 缓存,并且给缓存设置合理的过期时间。

如果你拿到的源码里没有这些优化,建议照着这个顺序补一遍。个人实测,索引补上之后,报表接口从 3 秒变成 0.8 秒,缓存加上之后基本稳定在 0.2 秒内。

6.3 权限管理混乱,需要按角色重新梳理接口

源码里内置了超级管理员、老板、采购、销售、仓管、财务六种角色,但不同业务场景下角色权限会重叠。比如有的客户要求仓管员也能看成本价格,有的要求财务也可以录入采购单。这种需求不应该靠改数据库的 role 字段硬编码,而应该在权限模块里用“菜单权限 + 数据权限 + 按钮权限”三层模型来配置。

我在实际过程中,把后端的接口按 Restful 风格重新做了一遍权限标注,例如@SaCheckPermission("sale:order:audit")这种形式,管理后台的按钮则用v-permission指令控制显隐。维护起来比一开始写死 if-else 轻松得多。

7. 二次开发建议:这套源码要落地还需要补什么

如果你打算把这套源码拿去给客户部署或者直接自己运营,我觉得最需要补的是三块内容。

第一块是打印模板的定制。进销存系统百分百要对接打印机,销售小票、采购单、价签模板都每个客户要求不一样。源码里用的是通用 80mm 热敏打印模板,换客户时要能在后台编辑模板变量。

第二块是移动端重新打包发布。小程序正式上线需要注册企业主体、申请 AppID、配置服务类目。个人开发者在测试阶段可以用测试号,但正式发布不建议挂个人号,否则支付功能很容易被限制。我在对接微信支付时看到过“由于小程序违规,支付功能暂时无法使用”的提示,基本上都是和主体资质、类目选择有关系,务必在上线前把资质准备齐全。

第三块是订单导入的容错。真实业务里,Excel 导入是每天都会发生的操作,但客户粘贴的数据总是千奇百怪。源码里已经有导入模板和错误回显,但建议再补一层“重复数据校验”和“批量更新库存”的功能,尤其是老客户想要从旧系统迁移数据的时候,一个好的导入模块能帮你节省大量实施时间。

最后再分享一个我个人的习惯:接手任何一套 ERP 源码,都先通读一遍数据库表结构,然后画一张“单据流转图”贴在工位上。等你看懂采购单如何变成入库单、销售单如何影响库存和应收款,整套源码的骨架也就真正装进脑子里了。后面无论改什么功能,都不容易跑偏。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询