src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
@@ -62,8 +62,8 @@ @Schema(description = "最后执行时间") private LocalDateTime lastExecutionTime; @Schema(description = "是否激活") private boolean isActive; @Schema(description = "是否激活, 1=启用, 0=停用") private Integer isActive; @Schema(description = "备注") @Excel(name = "备注") src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskScheduler.java
@@ -25,13 +25,25 @@ private final Scheduler scheduler; /** * 添加新任务到调度器 * 添加或更新任务到调度器 */ public void scheduleMaintenanceTask(MaintenanceTask task){ try { JobDetail jobDetail = buildJobDetail(task); Trigger trigger = buildJobTrigger(task, jobDetail); scheduler.scheduleJob(jobDetail, trigger); // 检查触发器是否已存在 TriggerKey triggerKey = trigger.getKey(); Trigger existingTrigger = scheduler.getTrigger(triggerKey); if (existingTrigger != null) { // 触发器已存在,更新它 scheduler.rescheduleJob(triggerKey, trigger); } else { // 触发器不存在,先确保 Job 存在,然后调度触发器 scheduler.addJob(jobDetail, true); scheduler.scheduleJob(trigger); } }catch (SchedulerException e){ log.error("SchedulerException scheduleMaintenanceTask ERROR",e); throw new RuntimeException(e); @@ -45,26 +57,41 @@ try{ TriggerKey triggerKey = new TriggerKey("triggerMaintenanceTask_" + task.getId()); // 获取现有触发器并转换为 CronTrigger // 获取现有触发器 Trigger oldTrigger = scheduler.getTrigger(triggerKey); // 构建新的 JobDetail 和 Trigger JobDetail jobDetail = buildJobDetail(task); Trigger newTrigger = buildJobTrigger(task, jobDetail); if (oldTrigger == null) { // 触发器不存在,说明任务之前被删除过 // 先确保 Job 存在,然后调度触发器 scheduler.addJob(jobDetail, true); scheduler.scheduleJob(newTrigger); return; } // 触发器存在,直接更新 if (!(oldTrigger instanceof CronTrigger)) { throw new SchedulerException("Existing trigger is not a CronTrigger"); } // 3. 构建CronTrigger,确保持久化配置 CronTrigger newTrigger = TriggerBuilder.newTrigger() .withIdentity(triggerKey) // 唯一标识,用于持久化存储 .withDescription(task.getTaskName() + "_TRIGGER") // 触发器描述 .forJob(oldTrigger.getJobKey()) // 关联对应的Job // 构建新的 CronTrigger CronTrigger cronTrigger = TriggerBuilder.newTrigger() .withIdentity(triggerKey) .withDescription(task.getTaskName() + "_TRIGGER") .forJob(oldTrigger.getJobKey()) .withSchedule(CronScheduleBuilder .cronSchedule(convertToCronExpression(task)) // 错过执行时的策略(根据业务调整) .cronSchedule(convertToCronExpression(task)) .withMisfireHandlingInstructionDoNothing() ) // 4. 设置开始时间(若为null则立即生效) .startAt(task.getNextExecutionTime() != null ? Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant()) : new Date()) .build(); scheduler.rescheduleJob(triggerKey, newTrigger); scheduler.rescheduleJob(triggerKey, cronTrigger); }catch (SchedulerException e){ log.error("SchedulerException rescheduleMaintenanceTask ERROR",e); throw new RuntimeException(e); @@ -93,6 +120,7 @@ public void unscheduleMaintenanceTask(Long taskId){ try { JobKey jobKey = new JobKey("MaintenanceTask_" + taskId); // 删除 Job 会自动删除关联的 Trigger scheduler.deleteJob(jobKey); }catch (SchedulerException e){ log.error("SchedulerException unscheduleMaintenanceTask ERROR",e); src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
@@ -67,7 +67,6 @@ @Override public AjaxResult add(MaintenanceTask maintenanceTask) { maintenanceTask.setActive(true); // 计算首次执行时间 TimingTask task = new TimingTask(); task.setFrequencyType(maintenanceTask.getFrequencyType()); @@ -76,7 +75,10 @@ maintenanceTask.setNextExecutionTime(firstExecutionTime); int insert = maintenanceTaskMapper.insert(maintenanceTask); if (insert > 0) { // 只有当 isActive 为 1 时才添加到定时任务调度器 if (maintenanceTask.getIsActive() != null && maintenanceTask.getIsActive() == 1) { maintenanceTaskScheduler.scheduleMaintenanceTask(maintenanceTask); } } return AjaxResult.success("添加成功"); } @@ -87,22 +89,50 @@ if (maintenanceTask1 == null) { return AjaxResult.warn("没有此数据"); } // 保存旧的 isActive 状态 Integer oldIsActive = maintenanceTask1.getIsActive(); Integer newIsActive = maintenanceTask.getIsActive(); BeanUtils.copyProperties(maintenanceTask, maintenanceTask1); int update = maintenanceTaskMapper.updateById(maintenanceTask1); if (update > 0) { // 处理 isActive 状态变化 if (newIsActive != null && newIsActive == 1) { // 新状态为启用:添加到定时任务调度器 if (oldIsActive == null || oldIsActive != 1) { // 从未启用变为启用,添加到调度器 maintenanceTaskScheduler.scheduleMaintenanceTask(maintenanceTask1); } else { // 已经启用,更新调度器中的任务 maintenanceTaskScheduler.rescheduleMaintenanceTask(maintenanceTask1); } } else { // 新状态为停用:从定时任务调度器中移除 if (oldIsActive != null && oldIsActive == 1) { maintenanceTaskScheduler.unscheduleMaintenanceTask(maintenanceTask1.getId()); } } } return AjaxResult.success("更新成功"); } @Override public AjaxResult delete(List<Long> ids) { // 先从定时任务调度器中移除所有待删除的任务 ids.forEach(id -> { try { maintenanceTaskScheduler.unscheduleMaintenanceTask(id); } catch (Exception e) { log.error("删除定时任务调度失败, id: {}", id, e); } }); // 再从数据库中删除记录 int delete = maintenanceTaskMapper.deleteBatchIds(ids); if (delete > 0) { ids.forEach(id -> { maintenanceTaskScheduler.unscheduleMaintenanceTask(id); }); } return AjaxResult.success("删除成功"); } return AjaxResult.error("删除失败"); } } src/main/resources/mapper/quality/QualityInspectMapper.xml
@@ -94,11 +94,7 @@ </delete> <select id="getInspectStatistics" resultType="com.ruoyi.quality.dto.QualityInspectStatDto"> SELECT CASE pp.product_name WHEN '原材料' THEN 0 WHEN '半成品' THEN 1 WHEN '成品' THEN 2 END AS modelType, SELECT qi.inspect_type AS modelType, IFNULL(SUM(qi.quantity), 0) AS totalCount, @@ -107,22 +103,14 @@ ELSE 0 END), 0) AS completedCount FROM product p INNER JOIN product pp ON p.parent_id = pp.id LEFT JOIN product_model pm ON pm.product_id = p.id LEFT JOIN quality_inspect qi ON qi.product_model_id = pm.id FROM quality_inspect qi WHERE pp.product_name IN ('原材料', '半成品', '成品') GROUP BY pp.product_name GROUP BY qi.inspect_type </select> <select id="getPassRateStatistics" resultType="com.ruoyi.quality.dto.QualityPassRateDto"> SELECT t.modelType, SELECT qi.inspect_type AS modelType, COALESCE(SUM(qi.quantity), 0) AS totalCount, @@ -185,27 +173,10 @@ ), 0) * 100, 2) ) AS passRate FROM (SELECT 0 AS modelType UNION ALL SELECT 1 UNION ALL SELECT 2) t LEFT JOIN product p ON 1 = 1 LEFT JOIN product pp ON p.parent_id = pp.id LEFT JOIN product_model pm ON pm.product_id = p.id LEFT JOIN quality_inspect qi ON qi.product_model_id = pm.id AND ( (pp.product_name = '原材料' AND t.modelType = 0) OR (pp.product_name = '半成品' AND t.modelType = 1) OR (pp.product_name = '成品' AND t.modelType = 2) ) FROM quality_inspect qi GROUP BY t.modelType ORDER BY t.modelType; GROUP BY qi.inspect_type ORDER BY qi.inspect_type; </select> @@ -297,18 +268,10 @@ ) AS passRate FROM base b LEFT JOIN product p ON 1 = 1 LEFT JOIN product pp ON p.parent_id = pp.id LEFT JOIN product_model pm ON pm.product_id = p.id LEFT JOIN quality_inspect qi ON qi.product_model_id = pm.id ON qi.inspect_type = b.modelType AND YEAR(qi.check_time) = #{year} AND MONTH(qi.check_time) = b.month_num AND ( (pp.product_name = '原材料' AND b.modelType = 0) OR (pp.product_name = '半成品' AND b.modelType = 1) OR (pp.product_name = '成品' AND b.modelType = 2) ) GROUP BY b.month_num, b.modelType ORDER BY b.month_num, b.modelType; @@ -316,45 +279,58 @@ </select> <select id="getYearlyPassRateStatistics" resultType="com.ruoyi.quality.dto.QualityPassRateDto"> SELECT t.modelType, SELECT qi.inspect_type AS modelType, COALESCE(SUM(qi.quantity), 0) AS totalCount, COALESCE(SUM( CASE WHEN pp.product_name = '原材料' AND t.modelType = 0 THEN qi.quantity WHEN pp.product_name = '半成品' AND t.modelType = 1 THEN qi.quantity WHEN pp.product_name = '成品' AND t.modelType = 2 THEN qi.quantity WHEN qi.inspect_state = 1 THEN qi.quantity ELSE 0 END ), 0) AS totalCount, ), 0) AS completedCount, COALESCE(SUM( CASE WHEN pp.product_name = '原材料' AND t.modelType = 0 THEN qi.qualified_quantity WHEN pp.product_name = '半成品' AND t.modelType = 1 THEN qi.qualified_quantity WHEN pp.product_name = '成品' AND t.modelType = 2 THEN qi.qualified_quantity WHEN qi.inspect_state = 1 THEN qi.qualified_quantity ELSE 0 END ), 0) AS qualifiedCount, COALESCE(SUM( CASE WHEN pp.product_name = '原材料' AND t.modelType = 0 THEN qi.unqualified_quantity WHEN pp.product_name = '半成品' AND t.modelType = 1 THEN qi.unqualified_quantity WHEN pp.product_name = '成品' AND t.modelType = 2 THEN qi.unqualified_quantity WHEN qi.inspect_state = 1 THEN qi.unqualified_quantity ELSE 0 END ), 0) AS unqualifiedCount FROM (SELECT 0 AS modelType UNION ALL SELECT 1 UNION ALL SELECT 2) t LEFT JOIN product p ON 1 = 1 LEFT JOIN product pp ON p.parent_id = pp.id LEFT JOIN product_model pm ON pm.product_id = p.id LEFT JOIN quality_inspect qi ON qi.product_model_id = pm.id AND YEAR(qi.check_time) = #{year} AND qi.inspect_state = 1 GROUP BY t.modelType ORDER BY t.modelType; ), 0) AS unqualifiedCount, /* 完成率 */ IF(COALESCE(SUM(qi.quantity), 0) = 0, 0, ROUND( COALESCE(SUM( CASE WHEN qi.inspect_state = 1 THEN qi.quantity ELSE 0 END ), 0) / SUM(qi.quantity) * 100, 2) ) AS completionRate, /* 合格率 */ IF(COALESCE(SUM( CASE WHEN qi.inspect_state = 1 THEN qi.quantity ELSE 0 END ), 0) = 0, 0, ROUND( COALESCE(SUM( CASE WHEN qi.inspect_state = 1 THEN qi.qualified_quantity ELSE 0 END ), 0) / COALESCE(SUM( CASE WHEN qi.inspect_state = 1 THEN qi.quantity ELSE 0 END ), 0) * 100, 2) ) AS passRate FROM quality_inspect qi WHERE YEAR(qi.check_time) = #{year} GROUP BY qi.inspect_type ORDER BY qi.inspect_type; </select> @@ -383,7 +359,7 @@ /* 原材料 */ COALESCE(SUM( CASE WHEN pp.product_name = '原材料' WHEN qi.inspect_type = 0 THEN qi.quantity ELSE 0 END @@ -392,7 +368,7 @@ /* 半成品 */ COALESCE(SUM( CASE WHEN pp.product_name = '半成品' WHEN qi.inspect_type = 1 THEN qi.quantity ELSE 0 END @@ -401,19 +377,15 @@ /* 成品 */ COALESCE(SUM( CASE WHEN pp.product_name = '成品' WHEN qi.inspect_type = 2 THEN qi.quantity ELSE 0 END ), 0) AS outgoingCount FROM months m LEFT JOIN product p ON 1 = 1 LEFT JOIN product pp ON p.parent_id = pp.id LEFT JOIN product_model pm ON pm.product_id = p.id LEFT JOIN quality_inspect qi ON qi.product_model_id = pm.id AND qi.inspect_state = 1 ON qi.inspect_state = 1 AND YEAR(qi.check_time) = #{year} AND MONTH(qi.check_time) = m.month_num @@ -428,16 +400,8 @@ FROM quality_inspect_param qip JOIN quality_inspect qi ON qip.inspect_id = qi.id JOIN product p ON qi.product_id = p.id JOIN product pp ON p.parent_id = pp.id WHERE qi.inspect_state = 1 AND ( (#{modelType} = 1 AND pp.product_name = '原材料') OR (#{modelType} = 2 AND pp.product_name = '半成品') OR (#{modelType} = 3 AND pp.product_name = '成品') ) AND qi.inspect_type = #{modelType} - 1 GROUP BY qip.parameter_item), ranked AS (SELECT name, count,