基于Spring Boot构建工业生产计划管理系统:从核心流程到技术实现
这类系统最值得先看的不是功能列表,而是能不能把生产计划、物料、订单、排程这些核心流程串起来,并且在实际部署时稳定运行。很多团队在选型或自研时,容易陷入两个误区:要么过度追求大而全,引入了大量用不上的复杂模块,导致维护成本飙升;要么只做了简单的增删改查,业务逻辑散落在各个角落,一旦计划调整或插单,整个系统就乱了套。
一个真正能用的工业生产计划管理系统,核心价值在于流程闭环和数据驱动。它需要把销售订单、物料清单(BOM)、库存、设备产能、工时、交货期这些原本分散在Excel、邮件甚至纸质单据里的信息,通过一个统一的逻辑串联起来,让计划员能基于实时数据做决策,而不是靠经验“拍脑袋”。对于正在评估或准备动手开发的团队来说,最该关注的是:系统如何定义“计划”这个核心对象?排程算法是简单的先到先得,还是能考虑优先级、换线时间、设备约束?当紧急订单插入时,系统是推倒重来,还是能局部调整并给出影响评估?
下面,我会以一个从零开始构建的视角,拆解基于Spring Boot实现这样一个系统的关键环节、技术选型考量以及那些只有踩过坑才知道的细节。文章会避开空泛的概念,直接聚焦于环境搭建、核心表设计、排程逻辑实现、前后端协作以及生产环境部署中的具体问题。
1. 先明确系统边界:它到底管什么,不管什么
在动手写第一行代码之前,必须把系统的边界画清楚。一个“工业生产计划管理系统”听起来包罗万象,但如果试图一次性解决所有问题,项目大概率会失败。
1.1 核心管理对象:从销售订单到生产工单的转化链
系统管理的起点通常是销售订单或预测订单。终点是下达给车间执行的生产工单。中间的核心转化逻辑,是这套系统的灵魂。
你需要明确以下几个实体及其关系:
- 销售订单:客户要什么、要多少、何时要。
- 产品:最终要交付的成品。
- 物料清单:一个成品由哪些子部件或原材料构成(BOM),以及构成的数量关系。这是计划分解的基石。
- 物料:具体的原材料、半成品、零部件,需要有库存信息。
- 工作中心:可以是一台设备、一条生产线或一个班组,是执行生产任务的基本单位,需要有产能(如小时/天)和日历(工作日、休息日)信息。
- 工艺路线:一个产品在哪些工作中心上,经过哪些工序,每个工序的标准工时是多少。
- 生产工单:最终下达的指令,包含产品、数量、计划开始/结束时间、所需物料、工序步骤等。
很多新手系统只做到了“工单管理”,即手工创建工单并分配,这只是一个高级版的记事本。真正的计划系统,应该能根据销售订单,结合BOM和库存,自动计算出需要生产或采购的物料需求(即MRP核心思想),再根据工艺路线和工作中心产能,将生产任务在时间轴上排列开来(即排程)。
1.2 功能边界划分:第一期做什么,后续迭代什么
对于第一期(MVP),我建议聚焦以下核心闭环:
- 基础数据管理:产品、BOM、物料、工作中心、工艺路线的维护。
- 销售订单录入。
- MRP运算:根据订单和现有库存,计算物料净需求。这一步可以暂时简化,例如只考虑单层BOM,不考虑在途、安全库存等复杂因素。
- 简单排程:根据物料齐套情况和工作中心产能,为生产任务分配一个建议的开始时间。初期可以采用无限产能排程(即假设产能无限,只按顺序排),先跑通流程。
- 工单下达与状态跟踪:工单创建后,可以下发,并更新状态(待生产、生产中、已完成)。
暂时不要做:
- 复杂的有限产能优化算法。
- 完整的供应链管理(供应商、采购订单)。
- 车间现场数据采集。
- 复杂的成本核算。
先让核心流程跑起来,数据能流转,再基于实际使用反馈进行迭代。这个边界意识能帮你节省大量初期开发时间,避免陷入技术细节而忘了业务目标。
2. 技术栈选型与项目初始化:不只是Spring Boot
虽然标题是“基于Spring Boot”,但一个可用的系统远不止一个框架。技术选型需要平衡开发效率、团队技能和后期维护成本。
2.1 后端技术栈:稳健为主,预留扩展
- Spring Boot:毫无疑问是基石。版本选择上,不必追求最新。Spring Boot 2.7.x 或 3.x 的LTS版本都是稳妥的选择。新项目可以从3.x开始,注意其最低要求JDK 17。
- 持久层:
- ORM框架:MyBatis-Plus。它比纯JPA更贴近国内开发者的SQL习惯,代码生成器能极大提升基础CRUD的开发速度,对于管理类系统非常友好。
- 数据库:MySQL 8.0 或 PostgreSQL。生产环境务必做好主从分离和备份策略的规划。
- 缓存:Redis。用于存储会话、热点数据(如产品信息)、以及简单的排程队列状态。Spring Boot缓存技术可以很方便地通过注解集成,但要注意缓存一致性问题。
- 消息队列:可选,但建议预留。当工单状态变更、计划更新需要通知其他系统(如MES、WMS)时,消息队列是解耦利器。RabbitMQ或RocketMQ都是常见选择。SpringBoot整合ActiveMQ也是一种方案,但ActiveMQ在吞吐量和社区活跃度上相对弱一些。
- 任务调度:计划系统本身可能就有定时任务需求,如每天凌晨自动运行MRP计算。Spring自带的
@Scheduled注解适合简单场景,更复杂的分布式调度可以考虑XXL-JOB或Quartz集成。 - API文档:Swagger。用SpringBoot增加Swagger依赖并简单配置,可以自动生成API文档,前后端协作效率倍增。
- 工具包:Hutool、Lombok等,能减少很多样板代码。
2.2 前端技术栈:分离部署,快速迭代
推荐前后端分离架构。后端提供RESTful API,前端独立部署。
- Vue 3 + Element Plus:是目前管理后台类项目非常主流和高效的选择。组件丰富,生态成熟。
- 构建工具:Vite,开发热更新速度极快。
2.3 项目初始化与关键配置
使用IDEA的Spring Initializr或阿里云的 start.aliyun.com 快速生成项目骨架。
pom.xml文件是依赖管理的核心,初期不必引入所有功能。按需添加,例如:
<dependencies> <!-- Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 数据库 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>最新版本</version> </dependency> <!-- 缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 文档 --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.x</version> </dependency> <!-- 工具 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>application.yml配置要点:
server: port: 8080 servlet: context-path: /api # 建议统一API前缀 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/prod_plan?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: your_password redis: host: localhost port: 6379 database: 0 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发阶段开启SQL日志 global-config: db-config: logic-delete-field: deleted # 逻辑删除字段 logic-delete-value: 1 logic-not-delete-value: 0 # Swagger/OpenAPI 配置 springdoc: api-docs: path: /v3/api-docs swagger-ui: path: /swagger-ui.html注意:生产环境务必移除SQL日志打印,并妥善保管数据库密码。
3. 核心数据库设计与领域建模
数据库设计直接决定了系统的扩展性和性能。不要一上来就对着界面画表,要先理解业务实体和它们之间的关系。
3.1 核心表结构设计(简化版)
以下是最核心的几张表,用于支撑第一节描述的闭环:
产品表 (product)
CREATE TABLE `product` ( `id` bigint NOT NULL COMMENT '主键', `code` varchar(64) NOT NULL COMMENT '产品编码', `name` varchar(255) NOT NULL COMMENT '产品名称', `spec` varchar(500) DEFAULT NULL COMMENT '规格型号', `unit` varchar(20) DEFAULT NULL COMMENT '单位', `type` tinyint DEFAULT NULL COMMENT '类型:1-成品,2-半成品,3-原材料', PRIMARY KEY (`id`), UNIQUE KEY `uk_code` (`code`) ) COMMENT='产品表';物料清单表 (bom)
CREATE TABLE `bom` ( `id` bigint NOT NULL, `parent_product_id` bigint NOT NULL COMMENT '父产品ID', `component_product_id` bigint NOT NULL COMMENT '子件/原材料产品ID', `quantity` decimal(12,4) NOT NULL COMMENT '单台用量', `loss_rate` decimal(5,4) DEFAULT '0.0000' COMMENT '损耗率', PRIMARY KEY (`id`), KEY `idx_parent` (`parent_product_id`) ) COMMENT='物料清单表';- 一个成品(parent)对应多条BOM记录。
type字段可以区分替代料、虚拟件等,初期可省略。
销售订单表 (sales_order)
CREATE TABLE `sales_order` ( `id` bigint NOT NULL, `order_no` varchar(64) NOT NULL COMMENT '订单号', `customer_name` varchar(255) DEFAULT NULL COMMENT '客户', `product_id` bigint NOT NULL COMMENT '产品ID', `quantity` decimal(12,4) NOT NULL COMMENT '订单数量', `delivery_date` date NOT NULL COMMENT '要求交货日期', `status` tinyint DEFAULT '1' COMMENT '状态:1-待处理,2-已计划,3-已关闭', PRIMARY KEY (`id`) ) COMMENT='销售订单表';物料库存表 (material_stock)
CREATE TABLE `material_stock` ( `id` bigint NOT NULL, `product_id` bigint NOT NULL COMMENT '物料产品ID', `warehouse` varchar(100) DEFAULT NULL COMMENT '仓库', `location` varchar(100) DEFAULT NULL COMMENT '库位', `quantity` decimal(12,4) NOT NULL DEFAULT '0.0000' COMMENT '当前库存数量', `locked_quantity` decimal(12,4) DEFAULT '0.0000' COMMENT '已锁定数量(已分配未出库)', PRIMARY KEY (`id`), UNIQUE KEY `uk_product_warehouse` (`product_id`,`warehouse`) ) COMMENT='物料库存表';locked_quantity用于实现简单的库存预留,防止超卖。
工作中心表 (work_center)
CREATE TABLE `work_center` ( `id` bigint NOT NULL, `code` varchar(64) NOT NULL COMMENT '工作中心编码', `name` varchar(255) NOT NULL COMMENT '名称', `daily_capacity` decimal(10,2) DEFAULT NULL COMMENT '日产能(小时)', `efficiency` decimal(5,4) DEFAULT '1.0000' COMMENT '效率系数', `calendar_id` bigint DEFAULT NULL COMMENT '关联日历ID', PRIMARY KEY (`id`) ) COMMENT='工作中心表';工艺路线表 (process_route) 与 工序表 (process_step)
-- 工艺路线头 CREATE TABLE `process_route` ( `id` bigint NOT NULL, `product_id` bigint NOT NULL COMMENT '产品ID', `version` varchar(20) DEFAULT 'A' COMMENT '版本', PRIMARY KEY (`id`), UNIQUE KEY `uk_product_version` (`product_id`,`version`) ) COMMENT='工艺路线表'; -- 工艺路线明细(工序) CREATE TABLE `process_step` ( `id` bigint NOT NULL, `route_id` bigint NOT NULL COMMENT '工艺路线ID', `step_no` int NOT NULL COMMENT '工序序号', `work_center_id` bigint NOT NULL COMMENT '工作中心ID', `setup_time` decimal(8,2) DEFAULT '0.00' COMMENT '准备时间(小时)', `process_time` decimal(8,4) NOT NULL COMMENT '单件加工时间(小时/件)', `description` varchar(500) DEFAULT NULL COMMENT '工序描述', PRIMARY KEY (`id`), KEY `idx_route` (`route_id`) ) COMMENT='工序表';生产工单表 (work_order)
CREATE TABLE `work_order` ( `id` bigint NOT NULL, `order_no` varchar(64) NOT NULL COMMENT '工单号', `product_id` bigint NOT NULL COMMENT '产品ID', `plan_quantity` decimal(12,4) NOT NULL COMMENT '计划数量', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态:1-已创建,2-已排程,3-已下达,4-生产中,5-已完成,6-已关闭', `plan_start_time` datetime DEFAULT NULL COMMENT '计划开始时间', `plan_end_time` datetime DEFAULT NULL COMMENT '计划结束时间', `actual_start_time` datetime DEFAULT NULL, `actual_end_time` datetime DEFAULT NULL, `priority` int DEFAULT '10' COMMENT '优先级,数字越小优先级越高', `source_order_id` bigint DEFAULT NULL COMMENT '来源销售订单ID', PRIMARY KEY (`id`) ) COMMENT='生产工单表';
3.2 领域模型与Service层设计
有了表结构,在Java中需要建立对应的实体类。使用MyBatis-Plus,可以配合Lombok简化代码。
@Data @TableName("work_order") public class WorkOrder { @TableId(type = IdType.ASSIGN_ID) // 使用雪花算法ID private Long id; private String orderNo; private Long productId; private BigDecimal planQuantity; private Integer status; private LocalDateTime planStartTime; private LocalDateTime planEndTime; private Integer priority; private Long sourceOrderId; // 其他字段... }业务逻辑集中在Service层。例如,WorkOrderService中会包含创建工单、排程、状态变更等方法。这里的关键是事务管理。例如,创建工单并扣减物料库存,必须在一个事务内完成,否则会导致数据不一致。
@Service @Transactional(rollbackFor = Exception.class) public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder> implements WorkOrderService { @Autowired private MaterialStockService stockService; @Override public boolean createAndAllocate(WorkOrder workOrder, List<MaterialRequirement> requirements) { // 1. 保存工单 this.save(workOrder); // 2. 为工单分配物料,锁定库存 for (MaterialRequirement req : requirements) { stockService.lockStock(req.getProductId(), req.getWarehouse(), req.getQuantity()); } // 如果任何一步失败,整个事务回滚 return true; } }4. 核心业务逻辑实现:MRP与排程
这是系统的“大脑”。初期可以简化,但逻辑必须清晰。
4.1 简化的MRP计算服务
MRP的核心是需求分解和净需求计算。我们实现一个最简单的版本:只考虑一层BOM,不考虑在途、安全库存和批量政策。
@Service public class MrpService { @Autowired private SalesOrderService salesOrderService; @Autowired private BomService bomService; @Autowired private MaterialStockService stockService; /** * 为指定销售订单计算物料需求 * @param orderId 销售订单ID * @return 物料需求清单 */ public List<MaterialRequirement> calculateRequirement(Long orderId) { SalesOrder order = salesOrderService.getById(orderId); List<MaterialRequirement> result = new ArrayList<>(); // 1. 获取订单产品 Product finishedProduct = productService.getById(order.getProductId()); // 2. 获取该产品的BOM List<Bom> bomList = bomService.listByParentProductId(finishedProduct.getId()); for (Bom bom : bomList) { MaterialRequirement req = new MaterialRequirement(); req.setProductId(bom.getComponentProductId()); // 3. 计算毛需求 = 订单数量 * BOM用量 * (1 + 损耗率) BigDecimal grossReq = order.getQuantity() .multiply(bom.getQuantity()) .multiply(BigDecimal.ONE.add(bom.getLossRate())); // 4. 查询当前可用库存 (当前库存 - 已锁定库存) BigDecimal availableStock = stockService.getAvailableStock(bom.getComponentProductId()); // 5. 计算净需求 = 毛需求 - 可用库存 (如果为负则取0) BigDecimal netReq = grossReq.subtract(availableStock); if (netReq.compareTo(BigDecimal.ZERO) < 0) { netReq = BigDecimal.ZERO; } req.setQuantity(netReq); if (netReq.compareTo(BigDecimal.ZERO) > 0) { result.add(req); } } return result; } }这个计算结果是“需要多少物料”。实际项目中,净需求还会触发采购建议或生产建议(对于半成品)。
4.2 基于有限产能的后向排程
排程算法极其复杂,工业级APS(高级计划排程)系统是另一个专业领域。我们实现一个最基本的后向排程:从交货日期倒推,考虑每个工序的标准工时和工作中心产能。
假设条件:
- 工作中心每天工作8小时。
- 工序之间没有等待时间。
- 排程时暂时忽略其他已排产任务(即无限产能假设)。
@Service public class SchedulingService { @Autowired private ProcessRouteService routeService; @Autowired private WorkCenterService workCenterService; /** * 为工单计算计划开始和结束时间(后向排程) * @param workOrder 工单 * @param deliveryDate 要求完工日期 * @return 排程结果 */ public ScheduleResult backwardSchedule(WorkOrder workOrder, LocalDate deliveryDate) { ScheduleResult result = new ScheduleResult(); // 1. 获取产品的工艺路线 ProcessRoute route = routeService.getByProductId(workOrder.getProductId()); List<ProcessStep> steps = routeService.getStepsByRouteId(route.getId()); // 假设最后一道工序在交货日下班前完成 LocalDateTime currentEndTime = deliveryDate.atTime(17, 0); // 下午5点下班 List<StepSchedule> stepSchedules = new ArrayList<>(); // 2. 倒序计算每道工序 for (int i = steps.size() - 1; i >= 0; i--) { ProcessStep step = steps.get(i); StepSchedule ss = new StepSchedule(); ss.setStep(step); // 计算该工序总工时 = 准备时间 + (单件时间 * 工单数量) BigDecimal totalHours = step.getSetupTime() .add(step.getProcessTime().multiply(workOrder.getPlanQuantity())); // 将工时转换为工作日(简化:按8小时/天) double daysNeeded = totalHours.doubleValue() / 8.0; // 计算计划开始时间(从结束时间往前推) LocalDateTime planStart = calculateStartTime(currentEndTime, daysNeeded); ss.setPlanStartTime(planStart); ss.setPlanEndTime(currentEndTime); stepSchedules.add(0, ss); // 按正序插入列表 // 当前工序的开始时间,是上一道工序(在循环中)的结束时间 currentEndTime = planStart; } result.setStepSchedules(stepSchedules); if (!stepSchedules.isEmpty()) { result.setPlanStartTime(stepSchedules.get(0).getPlanStartTime()); result.setPlanEndTime(stepSchedules.get(stepSchedules.size() - 1).getPlanEndTime()); } // 3. 将最终的计划时间更新到工单 workOrder.setPlanStartTime(result.getPlanStartTime()); workOrder.setPlanEndTime(result.getPlanEndTime()); workOrder.setStatus(2); // 状态更新为“已排程” // ... 保存工单 return result; } private LocalDateTime calculateStartTime(LocalDateTime endTime, double daysNeeded) { // 简化计算:不考虑节假日,直接减去所需天数 long hoursToSubtract = (long) (daysNeeded * 8); return endTime.minusHours(hoursToSubtract); } }这个算法非常初级,但足以演示排程的核心思想:时间推算和产能占用。真实的排程还需要考虑:
- 有限产能:检查工作中心在计划时间段内是否已被占用。
- 工序重叠:某些工序可以并行。
- 换模时间:产品切换带来的设备准备时间。
- 优先级规则:紧急订单插单处理。
对于初期系统,可以先使用这个简化算法,让计划员能看到一个初步的时间线,然后再手动调整。这比完全没有排程要前进一大步。
5. 前端界面与API交互:让数据流动起来
后端逻辑完成后,需要通过清晰的界面和稳定的API暴露给用户。
5.1 RESTful API设计要点
遵循RESTful风格,保持接口语义清晰。
GET /api/work-orders:获取工单列表(可分页、过滤、排序)。GET /api/work-orders/{id}:获取单个工单详情。POST /api/work-orders:创建新工单。PUT /api/work-orders/{id}:更新工单信息。POST /api/work-orders/{id}/schedule:为指定工单执行排程。POST /api/mrp/calculate?orderId=xxx:触发MRP计算。
使用SpringBoot常用注解简化开发:
@RestController @RequestMapping("/api/work-orders") @Api(tags = "生产工单管理") public class WorkOrderController { @Autowired private WorkOrderService workOrderService; @GetMapping @ApiOperation("分页查询工单列表") public R<Page<WorkOrderVO>> list(@RequestParam(required = false) Integer status, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { Page<WorkOrder> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<WorkOrder> queryWrapper = new LambdaQueryWrapper<>(); if (status != null) { queryWrapper.eq(WorkOrder::getStatus, status); } queryWrapper.orderByDesc(WorkOrder::getId); Page<WorkOrder> resultPage = workOrderService.page(page, queryWrapper); // 转换为VO对象... return R.ok(voPage); } @PostMapping("/{id}/schedule") @ApiOperation("为工单排程") public R<ScheduleResult> schedule(@PathVariable Long id, @RequestBody ScheduleRequest request) { WorkOrder order = workOrderService.getById(id); if (order == null) { return R.failed("工单不存在"); } ScheduleResult result = schedulingService.backwardSchedule(order, request.getDeliveryDate()); return R.ok(result); } }注意:R是一个自定义的通用响应体,包含code、msg、data。VO是视图对象,用于过滤掉不必要的实体字段。
5.2 前端页面关键功能
使用Vue 3 + Element Plus,可以快速搭建以下核心页面:
- 基础数据维护:产品、BOM、工作中心、工艺路线的增删改查列表页。BOM维护界面最好支持树形展示和拖拽。
- 销售订单管理:列表页和表单页。在表单页,选择产品后,可以联动显示产品信息。
- MRP运算界面:提供一个操作按钮,选择销售订单后,触发后端计算,并以表格形式展示计算出的物料净需求清单。
- 工单管理:
- 工单列表:显示所有工单,支持按状态、产品、时间筛选。状态可以用标签(Tag)直观显示。
- 工单详情/编辑:查看和修改工单信息。
- 排程操作:在工单行或详情页,有一个“排程”按钮,点击后弹出对话框,让用户确认或修改要求完工日期,然后调用排程API,并将返回的计划时间展示出来。
- 甘特图视图:这是计划系统的“仪表盘”。可以使用如
dhtmlx-gantt或frappe-gantt等开源库,将工单和工序以时间条的形式展示在时间轴上。拖拽时间条可以调整计划,并同步回后端。
- 库存查询:查看物料的当前库存、锁定库存和可用库存。
前后端联调时,重点关注数据一致性和用户体验。例如,创建工单时,前端应实时验证产品ID是否存在;排程计算可能较耗时,需要添加加载状态提示。
6. 部署、监控与生产环境考量
系统开发完成,如何在生产环境稳定运行是另一个挑战。
6.1 部署方式选择
- 传统部署:将Spring Boot打成的可执行JAR包,放在服务器上,用
nohup java -jar app.jar &或systemd服务来运行。SpringBoot 直接打jar包运行是最简单的方式。 - 容器化部署:使用Docker。编写Dockerfile,将应用、JDK打包成镜像。Docker部署SpringBoot项目能保证环境一致性。
FROM openjdk:17-jdk-slim VOLUME /tmp COPY target/*.jar app.jar ENTRYPOINT ["java","-jar","/app.jar"] - 容器编排部署:在微服务架构或需要高可用时,使用Kubernetes。k8s部署SpringBoot项目涉及Deployment、Service、Ingress等资源配置。
6.2 生产环境关键配置
- 数据库连接池:使用HikariCP,在
application-prod.yml中调整参数。spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 10 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 - 日志:配置Logback或Log4j2,将日志按级别输出到文件,并设置滚动策略。生产环境关闭DEBUG日志。
- 监控:集成Spring Boot Actuator,暴露健康检查、指标等信息。可以搭配Spring Boot Admin搭建一个简单的监控面板。
- 异常处理:使用
@ControllerAdvice实现全局异常处理器,将系统异常转换为友好的错误信息返回给前端,同时记录详细日志用于排查。 - 安全:
- 使用Spring Security实现认证和授权。
- API接口做好权限控制。
- 对用户上传的文件(如果有)进行病毒扫描和大小限制,防止恶意上传。SpringBoot解决PDF XSS攻击这类问题,本质是对所有外部输入进行严格的校验和过滤。
6.3 信创环境适配
如果项目需要适配信创环境(国产化),这是一个系统工程,不仅仅是更换应用服务器。
- 服务器:可能从CentOS转向麒麟、统信UOS。
- 中间件:数据库可能换为达梦、金仓、OceanBase。缓存可能换为腾讯云Tendis等兼容Redis协议的国产产品。
- 应用服务器:Tomcat是Spring Boot内嵌的,通常没问题。如果需要外置,东方通的TongWeb、金蝶的Apusic等是常见选择。是否需要更换,取决于部署环境的具体要求。通常,Spring Boot内嵌Tomcat打成的JAR包,只要JDK是兼容的(如龙芯的Loongson JDK、华为的毕昇JDK),在国产操作系统上可以直接运行。只有在客户明确要求必须使用特定国产应用服务器时,才需要将应用打包成WAR包并部署到TongWeb中。
- 芯片:可能运行在鲲鹏、飞腾、龙芯等架构上。关键是要使用对应架构的JDK。
适配信创的首要步骤是:在目标国产化环境上,从编译到运行,完整地走通一遍CI/CD流程,尽早发现兼容性问题。
6.4 常见问题排查清单
系统上线后,遇到问题可按此顺序排查:
应用启动失败:
- 检查
java -version,确认JDK版本符合Spring Boot要求。 - 检查
application.yml配置,特别是数据库、Redis连接信息。 - 检查端口是否被占用。
- 查看启动日志,寻找
Error或Exception关键字。
- 检查
数据库连接问题:
- 检查数据库服务是否启动。
- 检查连接IP、端口、用户名、密码是否正确。
- 检查数据库驱动版本是否匹配。
- 检查防火墙设置。
业务逻辑错误:
- MRP计算不准:检查BOM数据是否正确,库存数据是否及时更新,计算逻辑的循环和累加是否有误。
- 排程时间不合理:检查工艺路线中的工时单位(是小时还是分钟),检查工作中心日历数据,检查排程算法的日期计算逻辑(是否考虑了非工作日)。
- 库存数据不同步:检查工单创建、领料、入库等操作是否都在事务内,是否有并发操作导致的数据覆盖。考虑使用数据库悲观锁或乐观锁。
性能问题:
- 列表查询慢:检查是否缺少索引,特别是
where和order by涉及的字段。使用MyBatis-Plus的@TableField注解优化查询字段。 - 排程计算慢:当工单和工序数量很大时,后向排程的循环计算可能变慢。考虑对算法进行优化,或引入缓存,将已排程结果缓存起来,除非基础数据变更。
- 前端甘特图加载卡顿:一次不要加载太多时间范围的数据,采用分页或滚动加载。
- 列表查询慢:检查是否缺少索引,特别是
前端显示问题:
- 控制台乱码:确认后端接口返回的
Content-Type头包含charset=UTF-8,前端Axios请求也统一使用UTF-8编码。IDEA中SpringBoot应用运行控制台乱码通常是因为IDE或终端编码设置问题,与项目本身关系不大。 - 时间显示不对:前后端传递时间时,统一使用时间戳或ISO 8601格式(如
yyyy-MM-dd'T'HH:mm:ss),并明确时区。
- 控制台乱码:确认后端接口返回的
开发这样一个系统,最大的挑战往往不是技术实现,而是对生产管理业务的理解和抽象。最好的做法是,与未来的使用人员(计划员、生产主管)保持紧密沟通,用最短的周期交付一个可用的最小版本,然后根据他们的实际反馈快速迭代。把系统当作一个不断进化的“活工具”,而不是一个一次性交付的“死项目”。
