生产管理系统源码拆解:工单、报工与库存扣减全链路解析 简介这份生产管理系统源代码面向制造型企业的信息化建设者、软件开发学习者与需要二次开发的技术团队提供一套覆盖生产计划、物料需求计划、库存管理、生产进度跟踪、质量控制、订单管理、报表分析、系统集成及权限管理等核心模块的完整实现帮助读者理解制造流程与信息系统如何落地衔接。资源包共165个文件以97个asp动态页面为主体配合16个js脚本、9个css样式与25个gif、11个jpg界面素材另含htm页面、txt说明及db、mdb数据库文件压缩包约1.33MB目录按业务模块组织便于对照功能定位代码。目前已有2007人学习下载适合具备一定编程与数据库基础、希望从源码层面掌握生产管理系统设计思路的开发者参考也可作为企业评估二次开发可行性与系统扩展性的实践样本。1. 生产管理系统源码拆解从工单下达到报工入库的完整链路车间里最常见的场景是这样的计划员在 Excel 里排好工单打印出来发到产线班组长干完活拿笔在纸上记数量晚上文员再一条条录回表格。订单一多工单状态就对不上物料领了多少、报废多少、哪道工序卡住了全靠人问。这套生产管理系统源代码要解决的就是这个断层——把工单、物料、工序、报工、库存串成一条能查、能追、能算的数据链。它适合两类人一类是想给中小工厂做轻量 MES 的开发者拿来改改就能上线另一类是学习者想找一个业务闭环完整、模块划分清晰的管理系统源码把权限、审批、库存扣减这些常见套路一次看明白。下面按「能干什么 → 怎么跑 → 怎么改」的顺序拆。2. 技术栈与目录结构一套典型的前后端分离管理后台2.1 技术选型与分层思路这类生产管理系统源码常见做法是前后端分离 关系型数据库。后端负责业务规则和事务前端负责表单和看板数据库保证工单、库存这类强一致数据不出错。从项目定位看它属于「业务管理系统」而非高并发系统所以选型上优先考虑开发效率和可维护性而不是极致性能。后端我一般会看到 Spring Boot 或同类框架打底配合 MyBatis 做 SQL 映射因为生产管理里大量是「多表关联查询 条件筛选」手写 SQL 比 ORM 自动生成更可控。权限用 Spring Security 或 Shiro菜单和按钮级权限都落在数据库里。前端多为 Vue 或 React 的管理后台模板配合 Element UI / Ant Design 这类组件库表格、表单、弹窗开箱即用。数据库用 MySQL缓存用 Redis 存登录态和字典数据。层次常见技术在本项目中的职责前端Vue 组件库 Axios工单表单、报工看板、库存列表后端Spring Boot MyBatis业务规则、事务、接口权限Spring Security / Shiro登录、菜单、按钮权限数据库MySQL工单、物料、库存、用户数据缓存Redis登录态、字典、工单号生成需要说明的是具体版本号以源码里的pom.xml和package.json为准不同分支可能有差异跑之前先看这两个文件别照搬网上的版本。2.2 目录结构怎么读拿到源码先别急着跑花十分钟把目录扫一遍后面改代码能省很多时间。典型结构大致如下production-management/ ├── backend/ # 后端服务 │ ├── src/main/java/ │ │ ├── controller/ # 接口层按模块分 │ │ ├── service/ # 业务逻辑 │ │ ├── mapper/ # MyBatis 接口 │ │ ├── entity/ # 数据库实体 │ │ └── config/ # 权限、跨域、拦截器配置 │ ├── src/main/resources/ │ │ ├── mapper/ # XML SQL 文件 │ │ ├── application.yml # 数据源、Redis 配置 │ │ └── sql/ # 建表与初始化数据 │ └── pom.xml ├── frontend/ # 前端工程 │ ├── src/ │ │ ├── api/ # 接口封装 │ │ ├── views/ # 页面按业务模块分 │ │ ├── router/ # 路由与菜单 │ │ └── store/ # 全局状态 │ └── package.json └── README.md读目录时重点看三处controller里有哪些接口能反推系统功能边界sql目录里的建表语句能看清数据模型和表关系views下的页面目录能对上菜单结构。这三处看完系统大概长什么样心里就有数了。2.3 核心数据模型生产管理的核心是几张主表加关联表。工单表存生产任务物料表存原料和成品工序表定义工艺路线报工表记录每道工序的实际产出库存表存实时数量。表之间靠工单号、物料编码、工序编码关联。-- 工单主表一条记录代表一个生产任务 CREATE TABLE work_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, -- 工单号业务唯一键 product_id BIGINT NOT NULL, -- 产品物料ID plan_qty DECIMAL(12,2) NOT NULL, -- 计划数量 finished_qty DECIMAL(12,2) DEFAULT 0, -- 已完工数量 status TINYINT DEFAULT 0, -- 0待生产 1生产中 2已完工 3已关闭 plan_start DATE, -- 计划开工日 plan_end DATE, -- 计划完工日 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 报工记录每道工序每次报工一条 CREATE TABLE work_report ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, -- 关联工单 process_id BIGINT NOT NULL, -- 工序ID report_qty DECIMAL(12,2) NOT NULL, -- 本次报工数量 defect_qty DECIMAL(12,2) DEFAULT 0, -- 不良数量 operator VARCHAR(32), -- 报工人 report_time DATETIME DEFAULT CURRENT_TIMESTAMP );order_no加唯一索引是必须的工单号重复会导致后续报工、库存扣减全乱。finished_qty和status是冗余字段报工时同步更新避免每次查工单进度都去聚合报工表——这是管理系统的常见取舍用一点冗余换查询性能。defect_qty单独存不良数方便后面算良率别和报工数混在一起。3. 本地跑起来环境准备、建库与启动顺序3.1 环境与依赖检查跑之前先把环境对齐版本不匹配是新手翻车最多的地方。需要准备 JDK 8 或 11看pom.xml里的java.version、Maven 3.6、MySQL 5.7 或 8.0、Redis、Node.js 14。装完挨个验证java -version mvn -v mysql --version redis-cli ping # 返回 PONG 说明 Redis 正常 node -v npm -vredis-cli ping不通就先别往下走登录态和字典缓存都依赖它后面接口会报连接超时排查起来反而绕远。MySQL 字符集建议建库时就定成utf8mb4生产管理里物料名称、备注常带中文和特殊符号字符集不对会出现乱码或插入失败。3.2 建库与导入初始数据源码的resources/sql目录下一般有建表脚本和初始化数据。先建库再导入# 登录 MySQL 后执行 CREATE DATABASE production_mgmt DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 命令行导入注意路径换成自己的 mysql -u root -p production_mgmt backend/src/main/resources/sql/schema.sql mysql -u root -p production_mgmt backend/src/main/resources/sql/data.sqlschema.sql建表data.sql灌初始数据通常包含管理员账号、角色、菜单、字典。导入后查一下关键表有没有数据SELECT COUNT(*) FROM sys_user; -- 应有管理员账号 SELECT COUNT(*) FROM sys_menu; -- 菜单不为空否则登录后左侧空白菜单表为空是常见问题登录成功但页面没功能多半是data.sql没导全或导错库。3.3 改配置与启动后端打开application.yml把数据源、Redis、端口改成自己环境的server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/production_mgmt?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 redis: host: localhost port: 6379serverTimezone一定要写不写的话日期字段可能差 8 小时工单计划日期对不上。改完在backend目录下启动mvn clean package -DskipTests java -jar target/*.jar # 或者开发模式 mvn spring-boot:run看到启动日志里 Tomcat 端口监听、数据库连接池初始化成功后端就算起来了。用curl http://localhost:8080/actuator/health或直接访问登录接口验证一下。3.4 启动前端并登录前端在frontend目录先装依赖再跑cd frontend npm install # 依赖多时耐心等失败先换镜像源 npm run dev启动后控制台会给本地地址一般是http://localhost:80或http://localhost:3000。前端请求后端的地址通常在.env.development或vue.config.js的 proxy 里配置确认指向http://localhost:8080。用初始管理员账号登录进去先看菜单是否完整、工单列表能否打开这两步通了环境就算跑通了。提示前后端分离项目跨域问题很常见如果登录接口报 CORS先看后端有没有配跨域再看前端 proxy 是否生效别急着改浏览器设置。4. 核心模块拆解工单、报工与库存扣减怎么串4.1 工单创建与状态流转工单是整个系统的起点。创建工单时校验产品是否存在、计划数量是否大于零生成唯一工单号初始状态为待生产。状态流转有明确规则待生产 → 生产中首次报工触发→ 已完工完工数量达到计划数量→ 已关闭人工终止。状态不能随意跳比如已关闭的工单不能再报工。// 工单创建核心是校验 生成单号 落库 public Long createWorkOrder(WorkOrderDTO dto) { // 1. 产品必须存在且启用 Product product productMapper.selectById(dto.getProductId()); if (product null || product.getStatus() 0) { throw new BizException(产品不存在或已停用); } // 2. 计划数量必须为正 if (dto.getPlanQty() null || dto.getPlanQty().compareTo(BigDecimal.ZERO) 0) { throw new BizException(计划数量必须大于0); } WorkOrder order new WorkOrder(); order.setOrderNo(generateOrderNo()); // 如 WO 日期 序列 order.setProductId(dto.getProductId()); order.setPlanQty(dto.getPlanQty()); order.setFinishedQty(BigDecimal.ZERO); order.setStatus(0); // 待生产 order.setPlanStart(dto.getPlanStart()); order.setPlanEnd(dto.getPlanEnd()); workOrderMapper.insert(order); return order.getId(); }generateOrderNo()常见做法是「前缀 yyyyMMdd Redis 自增序列」用 Redis 自增而不是数据库自增是为了避免并发下查最大值再拼串的竞态。status用数字枚举而不是字符串查询和索引都更省。这里抛BizException而不是返回错误码是为了让全局异常处理器统一转成前端能识别的结构。4.2 报工接口与完工数量累加报工是业务最密集的地方要校验工单状态、累加完工数量、更新工单状态、记录报工明细还要触发库存变动。这一串操作必须在一个事务里任何一步失败都回滚否则会出现「报了工但库存没扣」的脏数据。Transactional(rollbackFor Exception.class) public void reportWork(WorkReportDTO dto) { WorkOrder order workOrderMapper.selectById(dto.getOrderId()); if (order null) throw new BizException(工单不存在); if (order.getStatus() 3) throw new BizException(工单已关闭不能报工); if (dto.getReportQty().compareTo(BigDecimal.ZERO) 0) { throw new BizException(报工数量必须大于0); } // 1. 写报工明细 WorkReport report new WorkReport(); report.setOrderId(dto.getOrderId()); report.setProcessId(dto.getProcessId()); report.setReportQty(dto.getReportQty()); report.setDefectQty(dto.getDefectQty()); report.setOperator(dto.getOperator()); workReportMapper.insert(report); // 2. 累加完工数量判断是否完工 BigDecimal finished order.getFinishedQty().add(dto.getReportQty()); order.setFinishedQty(finished); if (finished.compareTo(order.getPlanQty()) 0) { order.setStatus(2); // 已完工 } else if (order.getStatus() 0) { order.setStatus(1); // 首次报工转生产中 } workOrderMapper.updateById(order); // 3. 扣减原料库存按 BOM 用量 inventoryService.deductByOrder(order, dto.getReportQty()); }Transactional的rollbackFor Exception.class不能省默认只回滚运行时异常业务里抛的受检异常不会回滚这是血泪经验。完工判断用而不是因为报工数量可能超计划用等号会漏判。库存扣减放在最后是因为它依赖前面算出的实际报工量顺序反了会扣错数。4.3 库存扣减与并发控制库存扣减是这类系统最容易出问题的地方。两个工单同时报工、扣同一批原料如果先查库存再更新中间会有时间窗导致超扣。常见做法是用「条件更新 影响行数判断」把校验和扣减合成一条 SQL!-- InventoryMapper.xml扣减库存库存不足时影响行数为0 -- update iddeductStock UPDATE inventory SET qty qty - #{deductQty}, update_time NOW() WHERE material_id #{materialId} AND qty #{deductQty} /update// 服务层判断影响行数为0说明库存不足 int rows inventoryMapper.deductStock(materialId, deductQty); if (rows 0) { throw new BizException(物料库存不足扣减失败); }WHERE qty #{deductQty}是并发安全的关键数据库行锁保证同一行更新串行执行库存不够时更新不生效返回 0 行业务层据此抛异常回滚整个报工事务。这比「先 select 再 update」可靠得多后者在并发下必然超扣。如果 BOM 有多个物料逐个扣减任一失败整体回滚。4.4 权限与菜单加载管理系统的权限一般分三层登录认证、菜单可见性、按钮操作权限。登录成功后后端返回用户角色和权限标识前端根据权限标识控制按钮显隐后端接口再用注解校验两层都要有只做前端隐藏等于没做。// 接口级权限校验权限标识与菜单表对应 PreAuthorize(ss.hasPermi(production:order:add)) PostMapping(/add) public AjaxResult add(RequestBody WorkOrderDTO dto) { return AjaxResult.success(workOrderService.createWorkOrder(dto)); }ss.hasPermi是自定义的权限判断 Bean从当前登录用户里取权限集合比对。权限标识建议按「模块:资源:操作」命名和菜单表里的perms字段一一对应改权限时只改数据库不用动代码。菜单加载走「用户 → 角色 → 菜单」关联查询前端拿到树形结构后动态生成路由这样新增菜单不用重新发版。5. 避坑与排查跑不起来、数据不对时先看这几处5.1 登录成功但菜单空白现象是能进首页左侧菜单一片空白接口返回 200 但数据为空。原因多半是菜单表没数据或者用户角色没关联菜单。解决先查sys_menu有没有记录再查角色菜单关联表sys_role_menu里当前角色有没有绑定。初始化脚本导入顺序错了也会这样data.sql要在schema.sql之后执行。5.2 报工后库存没变现象是报工成功、工单进度正常但库存数量没动。原因通常是 BOM 没配deductByOrder查不到用量直接返回了或者物料 ID 对不上扣减 SQL 影响 0 行但没抛异常。解决先确认产品有没有配 BOM 明细再在扣减方法里加日志打印materialId和影响行数对不上就查数据。5.3 日期差 8 小时现象是工单计划日期存进去和查出来不一致。原因是 JDBC 连接没配时区或者实体字段用了java.util.Date而数据库是DATETIME。解决连接串加serverTimezoneAsia/Shanghai实体统一用LocalDateTime别混用。5.4 并发报工出现超扣现象是压测或多人同时报工时库存变负数。原因是扣减用了「先查后改」。解决换成 4.3 里的条件更新写法用影响行数判断同时确认扣减方法在事务内。改完用两个线程同时报工验证一次。5.5 前端请求 404 或跨域现象是页面能开但接口全红。原因通常是前端 proxy 没配或后端端口不一致。解决看vue.config.js或.env.development里的target是否指向后端实际端口后端确认server.port两边对上再重启。6. 二次开发改造从能跑到能用的几个落点源码跑通只是起点真正要用起来绕不开三处改造。第一处是工单号生成规则默认的「前缀 日期 序列」在单机够用多实例部署时 Redis 序列要加业务前缀隔离否则不同业务线会撞号。第二处是报工校验实际车间常有「超报」和「补报」建议在reportWork里加一个配置开关控制是否允许超计划报工超报部分单独记字段别直接改完工数量逻辑。第三处是库存扣减粒度现在按报工量扣原料如果工艺有损耗率要在 BOM 里加损耗字段扣减时乘系数这个改动会影响历史数据上线前先做数据迁移脚本。验证改造是否生效我习惯用「一条工单走全流程」的方式建工单 → 首次报工看状态是否转生产中→ 部分报工看完工数量累加→ 报满看状态是否转已完工→ 查库存看扣减是否正确→ 再报一次看是否被拦截。每一步都对着数据库查一遍比只看页面可靠。改造点改动位置验证方式工单号规则generateOrderNo()多实例并发建单不重号超报开关reportWork校验段开关开/关各报一次损耗率扣减BOM 表 deductByOrder对比理论用量与实际扣减有个习惯我一直保持每次改完核心业务方法先在本地用一条真实工单数据跑完整链路再提交。之前有次图省事只测了单个接口结果报工和库存的事务边界被改坏上线后对账对了一下午。从那以后我每次动Transactional相关代码都强制走一遍全链路验证。希望帮到你。本文还有配套的精品资源点击获取