news 2026/9/8 18:00:19

Spring Boot轻量级ERP开发实战:从业务建模到部署监控全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot轻量级ERP开发实战:从业务建模到部署监控全解析

1. 项目概述与目标定位

做企业内部管理系统这件事,很多人一听“ERP”就想到那种重武器级的商业套件,但对几十人规模的小公司来说,基于 Spring Boot 从零搭一套轻量 ERP,往往是投入产出比最高的选择。我自己做这个项目,起点是一个计算机方向的开发选题,后来拿它去对真实的小贸易公司业务,跑通了从采购、销售、库存到应收应付的核心闭环。整体开发过程相当折腾,真正有技术含量的不是增删改查,而是业务规则、单据状态、库存流水和对账逻辑之间的关系处理。如果你正犹豫要不要选这方面做项目实践,或者已经在开发中卡住,这篇经验复盘会很有参考价值。

注意:下面我只围绕“小型企业内部管理”这个场景展开,不是写一套面向多公司多组织的重型 ERP。

1.1 项目最终做成了什么样子

这个系统的名字可以理解成一个“SmallERP”内部管理平台。它服务的公司模型很简单:几十个员工,有采购、销售、仓库、财务几个角色,没有复杂的生产制造,也没有多工厂协同。系统最终包含三个核心业务域,每个业务域都有自己的单据流、状态流和账目流:

  • 采购域:供应商资料、采购订单、采购入库单、采购退货单。
  • 销售域:客户资料、销售订单、销售出库单、销售退货单。
  • 库存与财务域:即时库存、出入库流水、盘点单、其他出入库、应收/应付往来款。

系统管理部分则是所有业务系统绕不开的地基:用户、角色、菜单、部门、操作日志,以及客户/供应商/货品档案。整体功能大概二十多张数据表,属于典型的“中后台业务系统”,没有花哨算法,但对建模能力和异常处理能力要求很高。

因为我定位的是小型企业内部管理,不追求大而全。第一版也差点把“生产工单”“MRP运算”“全面预算”都塞进去,后来发现这些需求不明确,做了也是在数据库里多建几张没人维护的僵尸表。于是砍掉非核心诉求,只保留能形成资金与货物流转闭环的模块,整个系统才真正被高频用起来。

1.2 小型企业的 ERP 需求到底是什么

在实际推进中,我最大的体感是:小企业要的往往不是“ERP”三个字,而是“别再让我用 Excel 管理客户和库存了”。他们在意几件很具体的事:

  • 销售单能不能填得快一点,和客户历史价格能否带出。
  • 仓库里每个东西还剩多少,有没有低于安全库存。
  • 某笔采购为什么付了钱货还没到,某客户欠了多少钱、账期多长。
  • 月底能不能直接拉出本月卖了什么、毛利多少。

这些诉求落到系统设计里,就是主数据、单据、库存账、往来账四件事。主数据要统一,单据要能串起来,库存账要实时准确,往来账要能追溯到对应单据。整个开发过程也应该围绕这几条主线去排优先级,而不是上来就琢磨用什么前端组件库、要不要上微服务。

从学习或毕设的角度看,这也是一个特别好的“完整项目”训练:有用户权限、有CRUD、有事务、有缓存、有报表,还有部署上线环节。关键是复杂度适中,既不会像纯后台管理系统那样显得单薄,又不会像高并发系统那样超出个人可驾驭范围。

1.3 哪些人会需要这类项目经验

第一类是正在准备开发类综合项目或毕业实践的同学,需要一个能讲清完整业务闭环的技术案例。第二类是小型公司里想做内部信息化的开发人员,这类项目可以直接做业务改造后落地。第三类是准备转 Java 后端、想用 Spring Boot 做点拿得出手作品的人——你在简历上写一套“基于 Spring Boot 的 ERP 系统”,面试官大概率会追问库存、事务、权限和部署,这几关你能扛住就已经胜过很多只做过 Demo 的候选人了。

我强烈建议不要把这个题目理解成“CRUD 管理系统”。面试或答辩的时候,讲“销售出库越库怎么防”“月底结账和库存对不上怎么排查”远比讲“我用了 Vue 和 Element Plus”更能证明能力。后面各节我会按实际开发顺序,把技术选型、数据建模、业务实现、监控部署和踩坑记录完整展开。

2. 技术选型与系统架构拆解

2.1 技术栈清单与选型理由

整套系统后端基于 Spring Boot,前端采用 Vue 3 + Element Plus,数据库 MySQL 8.0,缓存 Redis,构建工具 Maven。最核心的选型逻辑是“大家都在用,资料多,坑少”,并不在于哪项技术最新。

具体清单如下:

层次选型主要理由
后端框架Spring Boot 2.7/3.x自动配置能力成熟,内置 Tomcat,Starter 生态完整
持久层MyBatis-Plus单表 CRUD 免写 SQL,复杂查询保留手写 SQL 灵活性
代码校验spring-boot-starter-validationJSR 380 标准,参数校验统一处理
缓存Redis会话、字典、临时库存锁都能覆盖
权限模型自研 RBAC + Sa-Token/Spring Security小系统自研 RBAC 足够,别引入太重安全框架
接口文档springdoc-openapi(或 knife4j)注意不要用老的 springfox,版本冲突多
数据库MySQL 8.0默认 utf8mb4,业务无特殊扩展需求

这里要说一个实际经验:热门项目如果打算长时间维护,尽量保持 Spring Boot 大版本不要太旧。在默认配置文件、依赖坐标和部分 API 上,Boot 3.x 和 2.x 有差异。如果一开始没有明确要用 JDK 17,继续用 Boot 2.7 + JDK 8 也完全没问题,稳定压倒一切。无论如何,不要在开发中途频繁跳大版本,否则会踩到依赖兼容性的连环坑。

2.2 单体架构下的工程分层

面对小型企业内部管理这种规模,微服务是负优化。它带来的注册中心、配置中心、网关、链路追踪等一系列复杂度,对十几个内部用户和一个 MySQL 实例的应用来说完全没有必要。我采用标准的单体分层架构,通过包名来划分边界,所有代码仍然是传统 MVC 结构:

src/main/java ├── common ├── config ├── controller ├── service │ ├── impl ├── mapper ├── entity ├── dto ├── vo ├── enums └── SmallErpApplication.java

common 放统一返回结果 R、分页对象、业务异常类;config 放 MyBatis-Plus、Redis、WebMvc 的配置;controller 只做参数接收与结果转换;service 聚焦业务规则与事务边界;mapper 就是持久层;enums 统一管理单据状态、出入库类型这些常量。

这种结构的好处是每个人一眼就懂,新人接手成本低。不要为了“扩展性”把代码拆成多模块 Maven 工程,除非你已经明确要做一个多项目并存的产品线。对单体应用来说,包结构清晰比模块化工程更实用。

2.3 用 Maven 构建并让依赖可控

如果你是第一次用 Maven,理解不复杂:pom.xml 中声明每个依赖的 groupId、artifactId、version,Maven 会按坐标从中央仓库下载并管理整个依赖树。开发环境命令行运行项目也很简单:

mvn clean package -DskipTests java -jar target/small-erp-server.jar

想要本地热启动开发,也可以直接:

mvn spring-boot:run -Dspring-boot.run.profiles=dev

实际项目中我习惯把所有依赖版本统一写在<properties>里,而不是散落在 dependency 内部。否则容易出现“A jar 引用 B 的旧版本”“本地能跑、打包就报错”的问题。还有个小技巧:用mvn dependency:tree查看依赖冲突,版本冲突时优先用排除依赖而不是全局改版本。Spring Boot 的父工程已经管理了主流第三方库默认版本,自己额外引入组件时尽量选与当前 Boot 版本兼容的版本。

大概就是这么一种思路:不是追求技术栈“看起来高级”,而是要让技术选型处处服务于开发效率和未来可维护性。很多人写到后期觉得代码乱,不是码力不行,而是工程边界没提前划好。

3. 数据库设计与业务建模

3.1 从需求文档到表结构

开发前别急着建表,先把需求文档变成业务流程。拿库存类 ERP 来说,最基础的一条线索是:采购订单 -> 采购入库单 -> 库存增加 -> 应付增加;销售订单 -> 销售出库单 -> 库存减少 -> 应收增加。把这条主线画清楚(文字即可),所有表的关系就出来了。

我整理的表结构可以划分为四组:

  • 组织与权限组:sys_user、sys_role、sys_menu、sys_user_role、sys_role_menu、sys_dept。
  • 基础档案组:base_material、base_category、base_customer、base_supplier、base_warehouse。
  • 业务单据组:pub_purchase_order、pub_sale_order,以及两种对应的入库单/出库单和明细子表。
  • 账务与流水组:stock_current、stock_flow、ar_ap_account、ar_ap_detail。

主表和明细子表是业务系统的标配,比如 sale_order 与 sale_order_item。主表存单据编号、客户、日期、状态、金额合计;明细表存商品、数量、单价、行金额。不要让主表直接把所有商品用逗号拼成一个字段,那样查询和汇总都会变成灾难。

3.2 单据号、金额和时间这三个细节

单据号设计必须从第一天就固定规则。为了兼顾可读和唯一,我采用“前缀 + 日期 + 流水号”的方式,比如入库单RK20250703001、出库单CK20250703001,订单和财务往来单同理。这种编号不要依赖 MySQL 单独一张 seq 表在高并发下生成,简单可靠的做法是借助 Redis INCR 生成流水号,然后保存到订单表中;如果不想依赖 Redis,也可以用yyyyMMdd+ 雪花ID 后几位,但可读性会差一些。

金额类字段必须用DECIMAL,不能简单用 double 或者 float 存价格。比如某商品单价是 19.90 元,数量 3 件,单价用浮点数算可能得到 59.699999,展示时四舍五入能糊弄过去,但累计到月底对账就会差几分钱。Java 侧对应实体属性用BigDecimal,计算用add/multiply,格式化和截取用setScale(2, RoundingMode.HALF_UP)

时间字段也是容易出问题的地方。MySQL 连接串要把serverTimezone=Asia/ShanghaiuseUnicode=true&characterEncoding=utf8配好;Java 侧日期时间优先用LocalDateTime,接口接收用@JsonFormat统一格式,不要在每个查询里手动拼日期字符串。

3.3 设计时容易踩的反直觉点

有几个点我在开发中吃了不少亏:

  • 库存不要只有一张总数表。除了 stock_current,还必须有一张 stock_flow 流水表。数总表容易,但查“某商品某天之后发生了什么”时,没有流水就寸步难行。所有库存增减都必须在同一事务里同时写流水和更新总量。
  • 明细子表必须存“下单时的快照字段”,比如商品名称、规格、销售单价。商品档案随时可能改名,如果报表直接 join 档案表,历史单据显示的商品名就跟着变了,无法追溯。
  • 逻辑删除要慎用。MyBatis-Plus 默认逻辑删除会影响唯一索引校验,比如商品编码唯一约束配上逻辑删除后,A 删除再用原名新建 B 时,如果唯一索引建在 encoding 字段上就会冲突。建议核心主数据和单据表都加删除状态字段,而真正防止唯一约束冲突时,需要把“是否已删除”一起放进联合唯一索引里。
  • 一个客户既是供应商又是客户的情况并不少。早期的设计里要把这两份档案合并成 partner,用角色类型区分,否则同一家公司录两遍,后期应收应付容易混。

4. 核心业务闭环:从采购到收付款的实现

4.1 采购入库:事务与库存账目联动

采购入库是整个库存体系最容易乱的一环,因为“先下单,后到货”意味着采购单是预占状态,入库单又是实际发生。系统的设计是采购订单作为前置凭证,采购入库单作为实际业务动作,一张采购单允许分批到货,每批对应一张入库单。

入库的核心代码逻辑可以简化成这样的服务方法:

@Transactional(rollbackFor = Exception.class) public void inbound(InboundCreateRequest req) { String orderNo = req.getPurchaseOrderNo(); PurchaseOrder order = purchaseOrderMapper.selectByNo(orderNo); if (order == null || !"SO_CONFIRMED".equals(order.getStatus())) { throw new BizException("采购单不存在或状态不允许入库"); } // 逐条校验本次到货数量不能超过订单剩余数量 for (InboundItem item : req.getItems()) { verifyRemainQty(orderNo, item.getMaterialId(), item.getQty()); } // 1. 生成入库单主表与明细 Long inboundId = saveInbound(order, req); // 2. 写库存流水并更新即时库存 for (InboundItem item : req.getItems()) { StockFlow flow = new StockFlow(); flow.setBizType("PURCHASE_IN"); flow.setBizNo(getInboundNo(inboundId)); flow.setMaterialId(item.getMaterialId()); flow.setQty(item.getQty()); stockFlowMapper.insert(flow); stockCurrentService.increase(item.getMaterialId(), item.getQty()); } // 3. 累计到供应商应付 arApService.addPayable(req.getSupplierId(), req.getAmount(), getInboundNo(inboundId)); }

@Transactional(rollbackFor = Exception.class)非常关键。如果中间任何一步失败,只有事务回滚,才不会出现“入库单不存在但库存已经增加了”的不一致状态。小系统能保证事务边界做对,很多数据错乱问题就能在源头消除。

入库单生成后,下一个动作是生成应付款。采购入库业务并不是指订单本身形成应付,而是以“已入库且已确认”为依据确认应付,因为只有真正验收入库的货物才产生应付义务。即使采购单金额 10 万、分批只到了 2 万,应付也只能增加 2 万,剩余部分仍挂在采购订单剩余数量上等待后续到货。

4.2 销售出库:锁库存与超卖控制

销售出库与采购入库在流程结构上镜像,但多了“超卖”风险。库存不是无限资源,多人同时开单时就可能卖超。最简单的防超卖方式是在更新即时库存时做条件更新,而不是先 SELECT 出来在 Java 里做减法再 UPDATE:

@Update("UPDATE stock_current SET qty = qty - #{qty}, updated_at = NOW() " + "WHERE material_id = #{materialId} AND qty >= #{qty}") int reduceStock(@Param("materialId") Long materialId, @Param("qty") BigDecimal qty);

执行 update 的影响行数为 0,说明库存扣减失败,直接抛“库存不足”异常即可。这种“扣减余额校验放在 UPDATE 条件中”的方式天然具备原子性,比 select for update 或悲观锁更容易避免死锁,适合大多数库存扣减场景。

出库环节还涉及一个业务判断:销售单是否允许超卖。我当时和业务方确认后决定:一般商品不允许负库存出库,特殊打印品可在配置中允许。这个完全可以做成系统参数stock.allow_negative,而不是写死在代码里。但在做负数库存功能前,请先想清楚负库存会影响什么:它会让“平均成本计算”和“月底结存金额”毫无意义,还可能让应收账款数量核对非常混乱,所以默认还是别开。

4.3 财务对账与往来款核销

应收应付是所有 ERP 里最容易和业务“打架”的部分,因为财务视角和业务视角天然不同。业务说“这个客户欠我们 5 万”,财务实际会说“这 5 万里有 3 万已经超期 30 天”。系统把往来款做成两张基础表:应收应付主表管“某一客户当前总欠款”,明细表记录每一笔业务发生时形成的应收应付变动。

回款的核销处理要按明细匹配:

  1. 客户打来 13000 元回款;
  2. 系统在 ar_ap_detail 中按时间顺序/单据号找到未核销的应收明细;
  3. 优先核销账期最久的一张销售出库单的应收;
  4. 如果 13000 覆盖了某张 10000 的单据,还余 3000 继续核销下一张;
  5. 每笔明细记录核销金额、核销时间与对应回款单号,方便审计。

这套核销逻辑不要用实时汇总表去硬算,因为 ERP 的时间轴一旦跨月,就涉及期初、本期增加、本期核销、余额四要素。用主表 + 明细表再加一张回款单表,基本能覆盖小型企业所有往来需求了。

4.4 前端接口设计与权限控制

小团队若没有专职前端,我建议后端接口尽量设计得“为页面而写”,不要一个通用查询接口包打天下。例如“销售开单页”需要一个聚合了客户历史价格、库存余量、最近报价的接口,尽管它看起来复合得不太 RESTful,但对页面开发效率提升很大。

权限控制采用 RBAC:用户 -> 角色 -> 菜单/操作权限。后端在需要控权的 Controller 方法上用自定义注解做权限校验,比如@RequiresPermission("sale:order:create"),避免所有接口裸奔。菜单权限做到前端路由显隐只是一部分,真正安全还是后端的接口权限校验要拦得住,否则用户直接调接口就能越过按钮限制。一个小公司 ERP 把菜单、按钮和数据范围控制到“用户自己的部门/全部”粒度就足够了,不需要复杂的字段级脱敏。

5. 监控、缓存与部署运维

5.1 用 Actuator 和 Micrometer 做监控

系统能跑只是第一步,能不能稳定跑是另一回事。Spring Boot Actuator 配合 Micrometer 是项目上线初期性价比最高的监控方案。它就是两个 Starter 的事:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>

配置文件里打开端点并暴露指标:

management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 17:58:05

Java程序员AI实战指南:四条路线从辅助编码到Agent开发

如果你是一个有几年经验的Java程序员&#xff0c;最近一年大概率已经感受到一种隐约的焦虑&#xff1a;身边的同事开始用AI写代码&#xff0c;GitHub上AI辅助提交的代码量暴涨&#xff0c;招聘JD里悄悄多了一行“熟悉AI应用开发者优先”。这波浪潮来得太快&#xff0c;快到很多…

作者头像 李华
网站建设 2026/9/8 17:51:53

加速度传感器时域积分成位移:漂移消除与工程实践

简介&#xff1a;针对工程振动与信号处理中的位移获取需求&#xff0c;这份压缩包提供了一套基于MATLAB的实用工具脚本。包内共5个m文件&#xff0c;整体仅4KB&#xff0c;轻量易用&#xff0c;包含数据预处理、核心数值积分以及多个功能变体&#xff0c;用户只需输入加速度时程…

作者头像 李华
网站建设 2026/9/8 17:49:49

Android Studio订餐系统开发:从数据库设计到RecyclerView实战

简介&#xff1a;这是一份使用Android Studio开发的订餐应用完整源码&#xff0c;项目采用Material Design设计语言&#xff0c;界面风格贴近安卓5.0之后内置应用&#xff0c;适合安卓初学者、移动开发课程设计或毕业设计作为参考。压缩包共包含690个文件&#xff0c;体积30.69…

作者头像 李华