资讯动态

基于Spring Boot的汽修管理系统实战:工单状态机、计价与库存扣减

发布时间:2026/10/27 20:18:37 来源:尧图企业网站定制
简介这份资源是面向汽车修理厂及中小汽修门店的汽车修理管理系统源码适合希望了解或二次开发行业管理软件的开发者与经营者使用可解决客户档案、维修订单、配件库存与财务统计等日常运营管理问题。压缩包共53个文件约2.66MB以h头文件与cpp源文件为主体配合obj编译产物、mdb数据库文件及dsp、dsw工程配置另有rc资源脚本、ico图标与wav提示音等构成一套完整的VC工程结构。目前已有678人学习下载。源码按客户管理、预约管理、维修订单、配件库存、财务管理、员工管理、报告分析与系统安全等模块组织数据库脚本与界面资源齐备读者可据此梳理业务逻辑、理解模块划分与数据交互方式并在此基础上进行功能扩展或部署调试对掌握行业软件开发思路具有实际参考价值。1. 汽修管理系统从接车到结算一套跑得通的业务闭环上个月帮一个开修理厂的朋友看后台他还在用 Excel 记工单客户报修、配件出库、结算对账全靠三张表来回粘月底一算账工时费和配件毛利永远对不上。这类场景其实特别适合用一套轻量的汽修管理系统兜住车辆进厂登记、故障派工、配件领用、工时计价、结算开单一条链路走完数据自然沉淀。这份「汽车修理厂汽车修理管理系统」就是围绕这条主线做的适合中小修理厂做业务数字化也适合开发者拿来当管理系统类项目的练手骨架——权限、工单、库存、结算这几个模块的套路在别的行业管理系统里几乎通用。下面按「能干什么、怎么跑、怎么改」拆开讲。2. 技术选型与目录结构一套典型的前后端分离管理系统2.1 为什么是这套栈管理系统类项目最怕两件事一是权限逻辑散落在各个页面里二是业务表之间没有清晰的关联。所以这类项目我一般会选「后端 MVC 前端模板渲染或轻量 SPA」的组合而不是一上来就上微服务。汽修系统的并发量撑死就是几十个工位同时开单单体应用完全够用部署也简单一台普通服务器加一个数据库就能跑。从项目定位看这套系统属于典型的业务管理系统技术栈大概率落在下面这个组合上原文没写死版本这里按这类项目最常见的做法给层次常见选型在本项目里的作用后端框架Spring Boot / 类似 MVC 框架提供工单、车辆、配件、结算的 REST 接口数据库MySQL存车辆档案、工单、配件库存、结算单持久层MyBatis 或 JPA工单与配件的多表关联查询前端Vue / Thymeleaf 模板工单列表、开单表单、库存看板权限基于角色的菜单 接口拦截前台接待、维修工、库管、财务分角色部署Jar 包 Nginx 静态资源内网一台机器即可选这套的理由很实在汽修业务的核心是「一张工单串起车、人、件、钱」关系型数据库天然适合表达这种关联而权限按角色切分正好对应修理厂里接待、技师、库管、财务几个岗位用菜单权限加接口拦截就能覆盖不需要引入复杂的权限中台。提示如果你的环境里已经有现成的后台管理脚手架完全可以只把汽修的业务表和工单状态机搬过去没必要从零搭权限。2.2 目录结构怎么读这类项目的目录一般长这样读源码时按这个顺序看最省时间auto-repair-system/ ├── src/main/java/com/repair/ │ ├── controller/ # 接口层车辆、工单、配件、结算 │ ├── service/ # 业务层派工、领料、计价 │ ├── mapper/ # 持久层SQL 与实体映射 │ ├── entity/ # 实体Vehicle、WorkOrder、Part、Settlement │ └── config/ # 权限拦截、跨域、数据源配置 ├── src/main/resources/ │ ├── mapper/ # XML 映射文件 │ ├── static/ # 前端静态资源 │ └── application.yml # 数据库、端口、日志配置 └── sql/ └── init.sql # 建表与初始化数据看目录就能判断项目的成熟度controller按业务域拆开、service里能单独找到派工和计价的方法、sql/init.sql里有初始化数据说明这是个能直接跑起来的完整工程而不是只有页面的半成品。第一次读的时候建议从WorkOrder相关的 controller 入手因为工单是整条业务链的主键顺着它能把车辆、配件、结算全串起来。2.3 核心数据表关系工单表是整个系统的枢纽理解它和周边表的关系后面看代码就不会迷路-- 工单主表一张工单对应一次进厂维修 CREATE TABLE work_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, -- 工单号业务唯一 vehicle_id BIGINT NOT NULL, -- 关联车辆档案 customer_id BIGINT NOT NULL, -- 关联客户 status TINYINT DEFAULT 0, -- 0待派工 1维修中 2待结算 3已结算 fault_desc VARCHAR(500), -- 故障描述 total_amount DECIMAL(10,2) DEFAULT 0, -- 结算总额 created_at DATETIME DEFAULT NOW() ); -- 工单明细工时项与配件项都落在这里 CREATE TABLE work_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, item_type TINYINT NOT NULL, -- 1工时 2配件 item_id BIGINT, -- 工时项或配件ID quantity DECIMAL(10,2), unit_price DECIMAL(10,2), amount DECIMAL(10,2) );status这个字段是后面所有状态流转的锚点派工、领料、结算都要校验当前状态改代码时先看它。work_order_item用item_type区分工时和配件好处是结算时一条 SQL 就能汇总坏处是外键约束不好加得在业务层保证item_id的合法性——这是这类项目里很常见的取舍。3. 跑起来环境准备、建库与启动验证3.1 环境与依赖准备先把运行环境对齐避免后面启动报一堆找不到类的错。按这类 Spring Boot 项目的常见要求JDK 8 或 11看pom.xml里的java.versionMaven 3.6MySQL 5.7 或 8.0Node 环境仅在需要单独构建前端时用到模板渲染的项目可以跳过# 检查基础环境 java -version mvn -v mysql --version三条命令都能正常输出版本号再往下走。JDK 版本和pom.xml不一致是最常见的启动失败原因Unsupported class file major version这类报错基本都是它。3.2 建库与导入初始化脚本数据库这一步别偷懒直接跑项目自带的sql/init.sql它一般包含建库、建表和少量演示数据# 登录 MySQL 后执行 mysql -u root -p CREATE DATABASE auto_repair DEFAULT CHARACTER SET utf8mb4; USE auto_repair; SOURCE /path/to/auto-repair-system/sql/init.sql;字符集一定用utf8mb4汽修系统里客户姓名、故障描述经常有生僻字和特殊符号用utf8会在插入时报错或存成问号。导入完成后用SHOW TABLES;确认工单、车辆、配件、结算几张表都在。3.3 改配置并启动打开application.yml把数据源改成你本地的spring: datasource: url: jdbc:mysql://localhost:3306/auto_repair?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver server: port: 8080serverTimezone必须显式指定否则 MySQL 8 驱动会报时区错误工单创建时间也会差 8 小时。改完执行mvn clean package -DskipTests java -jar target/auto-repair-system.jar看到Started ... in x seconds就说明起来了浏览器访问http://localhost:8080用初始化脚本里的管理员账号登录。登录后先别急着开单去「车辆管理」确认列表能查出演示数据这一步能同时验证数据库连接、Mapper 映射和前端渲染三条链路。3.4 走一遍最小业务闭环系统跑通不等于业务跑通建议按下面顺序手动走一遍任何一步卡住都能快速定位问题模块新增客户和车辆档案确认vehicle_id能正确关联创建工单填写故障描述状态应为「待派工」派工给技师状态流转到「维修中」添加工时项和配件项观察total_amount是否实时汇总提交结算状态变为「已结算」配件库存相应扣减。这五步走完说明工单状态机、明细汇总、库存扣减三个核心逻辑都是通的。哪一步数据不对就回到对应的 service 方法里打断点比漫无目的地翻代码高效得多。4. 核心模块拆解工单状态机、计价与库存扣减4.1 工单状态流转的实现工单状态是整个系统最容易出 bug 的地方因为前端按钮、后端接口、数据库字段三处都要一致。合格的写法是把状态流转收敛到一个 service 方法里而不是在每个 controller 里各写各的public void changeStatus(Long orderId, int targetStatus) { WorkOrder order workOrderMapper.selectById(orderId); if (order null) { throw new BizException(工单不存在); } // 只允许按 0-1-2-3 顺序推进禁止跳级和回退 if (targetStatus ! order.getStatus() 1) { throw new BizException(非法的状态流转); } order.setStatus(targetStatus); workOrderMapper.updateById(order); }这段代码的关键在targetStatus ! order.getStatus() 1这个校验它把「待派工→维修中→待结算→已结算」写死成单向链路任何跳级或回退都会被拦下。实际项目里如果业务允许返修可以在这里加一个白名单比如允许2 - 1但一定要显式列出而不是放开所有流转。参数targetStatus由前端传但真正的合法性判断必须在后端做前端按钮置灰只是体验优化不能当安全边界。4.2 工时与配件的计价汇总结算金额由工时费和配件费两部分组成常见做法是在添加明细时实时重算工单总额而不是等到结算再算public void addItem(Long orderId, WorkOrderItem item) { // 配件项需要校验库存并锁定数量 if (item.getItemType() 2) { Part part partMapper.selectById(item.getItemId()); if (part.getStock() item.getQuantity()) { throw new BizException(配件库存不足); } part.setStock(part.getStock() - item.getQuantity()); partMapper.updateById(part); } item.setAmount(item.getUnitPrice().multiply(item.getQuantity())); itemMapper.insert(item); // 重算工单总额 BigDecimal total itemMapper.sumAmountByOrderId(orderId); workOrderMapper.updateTotal(orderId, total); }这里有两个细节值得注意。第一配件在添加明细时就扣库存而不是结算时扣好处是避免两个工位同时领同一个配件导致超卖代价是如果工单作废得写一个回滚逻辑把库存加回去。第二amount用BigDecimal计算金额字段千万别用double否则结算时会出现0.30000000000000004这种经典翻车现场。sumAmountByOrderId是一条聚合 SQL把该工单下所有明细的amount加起来保证总额和明细永远一致。4.3 配件库存的并发控制修理厂里多个技师同时领料是常态库存扣减如果不加控制很容易出现负库存。除了上面代码里的「先查再减」更稳妥的做法是用带条件的更新语句-- 只有库存足够时才扣减返回影响行数判断是否成功 UPDATE part SET stock stock - #{quantity} WHERE id #{partId} AND stock #{quantity};这条 SQL 把「判断库存」和「扣减」合成一个原子操作stock #{quantity}不满足时影响行数为 0业务层据此抛「库存不足」。相比先select再update它避免了两个请求同时通过库存检查的竞态。如果项目里用的是 MyBatis在 Mapper 里返回int接收影响行数即可。这个写法在管理系统里属于基本功但真正写对的人不多值得单独记一笔。4.4 权限拦截与角色划分汽修系统的角色通常分四类前台接待负责建单和派工技师只能看自己的工单库管管配件出入库财务管结算。实现上一般用拦截器加注解RequireRole({ADMIN, RECEPTION}) PostMapping(/order/create) public Result createOrder(RequestBody WorkOrder order) { workOrderService.create(order); return Result.ok(); }RequireRole是自定义注解拦截器在preHandle里读取当前登录用户的角色和注解声明的角色列表做交集为空就返回 403。这种写法比在每个方法里手写if (role ! ...)干净得多新增角色时只改注解。注意拦截器要排除登录接口和静态资源路径否则会出现「登录页也被拦截」的死循环这是新手最常踩的坑之一。5. 避坑与排查启动、数据、状态三类高频问题5.1 启动报数据库连接失败现象启动日志里出现Communications link failure或Access denied for user。原因通常是application.yml里的账号密码没改、MySQL 没启动或者url里的库名和实际建的不一致。解决顺序是先用mysql -u root -p手动登录验证账号再确认auto_repair库存在最后检查url里的serverTimezone和字符集参数是否完整。这三步能覆盖九成的连接问题。5.2 工单列表查出来是空的现象登录成功页面正常但工单列表一条数据都没有。原因多半是初始化脚本只建了表没插演示数据或者查询条件里默认带了status过滤。排查时先在数据库里SELECT COUNT(*) FROM work_order;有数据就说明是接口或前端过滤的问题没数据就回去补init.sql。另外注意 Mapper XML 里的resultMap字段名和实体属性是否对得上驼峰和下划线混用会导致字段全为 null列表看起来就像空数据。5.3 结算金额和明细对不上现象工单总额显示 500但明细加起来是 480。原因一般是某次添加明细后没有触发总额重算或者配件单价在添加后被修改过。解决方法是把总额重算逻辑统一收口到addItem和deleteItem两个方法里任何明细变动都走同一条重算路径同时给total_amount加一个定时对账任务每天扫一遍总额和明细不一致的工单。金额类字段一律用BigDecimal比较时用compareTo而不是equals后者会因为精度不同返回 false。5.4 配件库存出现负数现象库存表里某个配件数量变成 -3。原因通常是扣减逻辑用了「先查后减」且没有事务或条件更新两个请求同时通过了库存检查。解决办法是把扣减改成 4.3 节那条带stock #{quantity}条件的 UPDATE并在 service 方法上加Transactional保证扣库存和写明细在同一个事务里。已经出现的负库存需要人工盘点修正所以上线前最好加一条库存不能为负的数据库约束兜底。5.5 状态流转按钮点了没反应现象点「派工」按钮页面提示成功但状态没变。原因可能是前端传的targetStatus和后端期望的不一致或者拦截器把请求拦了但返回体被前端当成了成功。排查时打开浏览器开发者工具看接口返回码和响应体403 是权限问题500 是后端异常200 但状态没变就去看changeStatus里的校验逻辑。这类问题八成出在前后端对状态码的约定上把状态枚举抽成一个共享常量类能省很多沟通成本。6. 二次开发从单店到多门店的改造思路这套系统的骨架是单店模型车辆、工单、配件都直接挂在全局没有门店维度。如果要做成连锁版本改造的核心是给核心表加store_id并把所有查询默认带上门店过滤。具体做法是先在vehicle、work_order、part三张表加store_id字段然后在 Mapper 的查询条件里统一注入当前登录用户所属门店最后在权限模块里加一层「门店数据隔离」的拦截逻辑。这一步不要图省事在 controller 里手动拼条件否则漏一处就是数据越权。计价规则也是常见的改造点。现在工时单价是写死在工时项里的连锁场景下不同门店、不同车型的工时费不一样可以把单价抽成一张price_rule表按门店、车型、工时类型三个维度配置结算时查表取价。改造时保留原来的unit_price字段作为快照规则变了也不影响历史工单的金额这是财务类系统的基本要求。验证改造是否成功我一般会跑三个用例同一工单在不同门店账号下互相不可见同一车型在两个门店的工时费按各自规则计算历史工单在规则变更后金额不变。三个用例都过说明数据隔离和价格快照都做对了。// 门店隔离的查询示例storeId 从登录上下文取不信任前端传参 public ListWorkOrder listByStore(Long storeId, WorkOrderQuery query) { query.setStoreId(storeId); return workOrderMapper.selectByCondition(query); }这段代码的重点是storeId来自服务端登录上下文而不是请求参数。很多数据越权漏洞就是因为图方便直接用了前端传的storeId改起来只要一行但漏掉就是事故。血泪经验是这类管理系统的改造先动数据库再加业务逻辑顺序反了会反复返工。我现在的习惯是每次加字段前先把受影响的查询列一遍改完立刻用历史数据回归一遍结算金额确认没漂移再往下写。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑