From 8f3a85044b6e4b56a8dd0b104ca023933f1f129c Mon Sep 17 00:00:00 2001
From: huang <1532065656@qq.com>
Date: 星期三, 03 十二月 2025 16:58:36 +0800
Subject: [PATCH] 统一卧转立扫码、卧转立、大车、大理片笼的定时器逻辑和步骤状态;添加设备拓扑图清除数据、联机状态切换按钮,
---
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java | 2114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 2,046 insertions(+), 68 deletions(-)
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
index 6e6bcbd..627c88e 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -3,34 +3,42 @@
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mes.device.entity.DeviceConfig;
import com.mes.device.entity.DeviceGroupConfig;
+import com.mes.device.service.DeviceCoordinationService;
+import com.mes.device.service.DeviceInteractionService;
+import com.mes.device.service.GlassInfoService;
+import com.mes.device.vo.DevicePlcVO;
import com.mes.interaction.DeviceInteraction;
import com.mes.interaction.DeviceInteractionRegistry;
import com.mes.interaction.DeviceLogicHandler;
import com.mes.interaction.DeviceLogicHandlerFactory;
import com.mes.interaction.base.InteractionContext;
import com.mes.interaction.base.InteractionResult;
-import com.mes.device.service.DeviceInteractionService;
import com.mes.task.dto.TaskParameters;
import com.mes.task.entity.MultiDeviceTask;
import com.mes.task.entity.TaskStepDetail;
import com.mes.task.mapper.MultiDeviceTaskMapper;
import com.mes.task.mapper.TaskStepDetailMapper;
+import com.mes.task.model.RetryPolicy;
import com.mes.task.model.TaskExecutionContext;
import com.mes.task.model.TaskExecutionResult;
-import com.mes.device.vo.DevicePlcVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* 澶氳澶囦换鍔℃墽琛屽紩鎿�
+ * 鏀寔涓茶鍜屽苟琛屼袱绉嶆墽琛屾ā寮�
*/
@Slf4j
@Component
@@ -38,11 +46,19 @@
public class TaskExecutionEngine {
private static final Map<String, String> DEFAULT_OPERATIONS = new HashMap<>();
+ private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {};
+ private static final int SCANNER_LOOKBACK_MINUTES = 2;
+ private static final int SCANNER_LOOKBACK_LIMIT = 20;
+
+ // 鎵ц妯″紡甯搁噺
+ private static final String EXECUTION_MODE_SERIAL = "SERIAL";
+ private static final String EXECUTION_MODE_PARALLEL = "PARALLEL";
static {
DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.LOAD_VEHICLE, "feedGlass");
DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.LARGE_GLASS, "processGlass");
- DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.GLASS_STORAGE, "storeGlass");
+ DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.WORKSTATION_SCANNER, "scanOnce");
+ DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.WORKSTATION_TRANSFER, "checkAndProcess");
}
private final TaskStepDetailMapper taskStepDetailMapper;
@@ -50,7 +66,30 @@
private final DeviceInteractionService deviceInteractionService;
private final DeviceInteractionRegistry interactionRegistry;
private final DeviceLogicHandlerFactory handlerFactory;
+ private final DeviceCoordinationService deviceCoordinationService;
+ private final TaskStatusNotificationService notificationService;
private final ObjectMapper objectMapper;
+ @Qualifier("deviceGlassInfoService")
+ private final GlassInfoService glassInfoService;
+
+ // 绾跨▼姹犵敤浜庡苟琛屾墽琛�
+ private final ExecutorService executorService = Executors.newCachedThreadPool(r -> {
+ Thread t = new Thread(r, "TaskExecutionEngine-Parallel");
+ t.setDaemon(true);
+ return t;
+ });
+
+ // 瀹氭椂鍣ㄧ嚎绋嬫睜锛氱敤浜庤澶囧畾鏃舵壂鎻�
+ private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(10, r -> {
+ Thread t = new Thread(r, "TaskExecutionEngine-Scheduled");
+ t.setDaemon(true);
+ return t;
+ });
+
+ // 瀛樺偍姣忎釜浠诲姟鐨勫畾鏃跺櫒浠诲姟锛歵askId -> List<ScheduledFuture>
+ private final Map<String, List<ScheduledFuture<?>>> taskScheduledTasks = new ConcurrentHashMap<>();
+ // 璁板綍姝e湪杩愯浠诲姟鐨勪笂涓嬫枃锛屼究浜庡彇娑堜换鍔℃椂璁块棶
+ private final Map<String, TaskExecutionContext> runningTaskContexts = new ConcurrentHashMap<>();
public TaskExecutionResult execute(MultiDeviceTask task,
DeviceGroupConfig groupConfig,
@@ -62,36 +101,1572 @@
}
TaskExecutionContext context = new TaskExecutionContext(parameters);
+ runningTaskContexts.put(task.getTaskId(), context);
+
task.setTotalSteps(devices.size());
task.setStatus(MultiDeviceTask.Status.RUNNING.name());
multiDeviceTaskMapper.updateById(task);
+
+ // 閫氱煡浠诲姟寮�濮嬫墽琛�
+ notificationService.notifyTaskStatus(task);
+
+ // 纭畾鎵ц妯″紡
+ String executionMode = determineExecutionMode(groupConfig);
+ Integer maxConcurrent = getMaxConcurrentDevices(groupConfig);
- List<Map<String, Object>> stepSummaries = new ArrayList<>();
- boolean success = true;
- String failureMessage = null;
+ log.info("浠诲姟鎵ц妯″紡: {}, 鏈�澶у苟鍙戞暟: {}, 璁惧鏁�: {}", executionMode, maxConcurrent, devices.size());
- for (int i = 0; i < devices.size(); i++) {
- DeviceConfig device = devices.get(i);
- int order = i + 1;
- TaskStepDetail step = createStepRecord(task, device, order);
- StepResult stepResult = executeStep(task, step, device, context);
- stepSummaries.add(stepResult.toSummary());
- if (!stepResult.isSuccess()) {
- success = false;
- failureMessage = stepResult.getMessage();
- break;
+ List<Map<String, Object>> stepSummaries;
+ boolean success;
+ String failureMessage;
+
+ if (EXECUTION_MODE_PARALLEL.equals(executionMode)) {
+ // 骞惰鎵ц妯″紡
+ stepSummaries = new ArrayList<>(Collections.nCopies(devices.size(), null));
+ Pair<Boolean, String> result = executeParallel(task, devices, context, stepSummaries, maxConcurrent);
+ success = result.getFirst();
+ failureMessage = result.getSecond();
+ } else {
+ // 涓茶鎵ц妯″紡锛堥粯璁わ級
+ stepSummaries = new ArrayList<>();
+ success = true;
+ failureMessage = null;
+
+ TaskParameters params = context.getParameters();
+ boolean hasGlassIds = !CollectionUtils.isEmpty(params.getGlassIds());
+ boolean triggerFirst = Boolean.TRUE.equals(params.getTriggerRequestFirst());
+
+ int currentOrder = 1;
+ // 缁熻澶ц溅璁惧鏁伴噺锛岀敤浜庡尯鍒嗚繘鐗囧ぇ杞﹀拰鍑虹墖澶ц溅
+ int loadVehicleCount = 0;
+ for (DeviceConfig device : devices) {
+ if (DeviceConfig.DeviceType.LOAD_VEHICLE.equals(device.getDeviceType())) {
+ loadVehicleCount++;
+ }
}
+ int currentLoadVehicleIndex = 0;
+
+ for (DeviceConfig device : devices) {
+ String deviceType = device.getDeviceType();
+ log.debug("澶勭悊璁惧: deviceId={}, deviceType={}, deviceName={}, WORKSTATION_SCANNER甯搁噺={}, equals={}",
+ device.getId(), deviceType, device.getDeviceName(),
+ DeviceConfig.DeviceType.WORKSTATION_SCANNER,
+ DeviceConfig.DeviceType.WORKSTATION_SCANNER.equals(deviceType));
+ boolean isLoadVehicle = DeviceConfig.DeviceType.LOAD_VEHICLE.equals(deviceType);
+ boolean isScanner = DeviceConfig.DeviceType.WORKSTATION_SCANNER.equals(deviceType)
+ || (deviceType != null && (deviceType.contains("鎵爜") || deviceType.contains("SCANNER")));
+ boolean isLargeGlass = DeviceConfig.DeviceType.LARGE_GLASS.equals(deviceType);
+ boolean isTransfer = DeviceConfig.DeviceType.WORKSTATION_TRANSFER.equals(deviceType);
+ log.debug("璁惧绫诲瀷鍒ゆ柇: deviceId={}, isLoadVehicle={}, isScanner={}, isLargeGlass={}, isTransfer={}",
+ device.getId(), isLoadVehicle, isScanner, isLargeGlass, isTransfer);
+
+ // 1. 鍗ц浆绔嬫壂鐮佽澶囷細鍚姩瀹氭椂鍣ㄦ壂鎻忥紙姣�10绉掑鐞嗕竴涓幓鐠僆D锛�
+ if (isScanner) {
+ log.debug("妫�娴嬪埌鎵爜璁惧锛屽噯澶囧惎鍔ㄥ畾鏃跺櫒: deviceId={}, deviceType={}, deviceName={}",
+ device.getId(), device.getDeviceType(), device.getDeviceName());
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+
+ ScheduledFuture<?> scannerTask = startScannerTimer(task, step, device, context);
+ if (scannerTask != null) {
+ registerScheduledTask(task.getTaskId(), scannerTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "瀹氭椂鍣ㄥ凡鍚姩锛屾瘡10绉掓壂鎻忎竴娆�"));
+ log.debug("鎵爜璁惧瀹氭椂鍣ㄥ惎鍔ㄦ垚鍔�: deviceId={}, taskId={}", device.getId(), task.getTaskId());
+ } else {
+ log.warn("鎵爜璁惧瀹氭椂鍣ㄥ惎鍔ㄥけ璐ワ紝glassIds鍙兘涓虹┖: deviceId={}, taskId={}, contextParams={}",
+ device.getId(), task.getTaskId(), context.getParameters());
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "鍗ц浆绔嬫壂鐮佽澶囧惎鍔ㄥ畾鏃跺櫒澶辫触";
+ break;
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 2. 鍗ц浆绔嬭澶囷細鍚姩瀹氭椂鍣ㄥ畾鏈熸鏌ュ苟澶勭悊锛堜腑杞澶囷級
+ if (isTransfer) {
+ log.debug("妫�娴嬪埌鍗ц浆绔嬭澶囷紝鍑嗗鍚姩瀹氭椂鍣�: deviceId={}, deviceType={}, deviceName={}",
+ device.getId(), device.getDeviceType(), device.getDeviceName());
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+
+ ScheduledFuture<?> transferTask = startTransferTimer(task, step, device, context);
+ if (transferTask != null) {
+ registerScheduledTask(task.getTaskId(), transferTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "瀹氭椂鍣ㄥ凡鍚姩锛屽畾鏈熸鏌ュ苟澶勭悊鐜荤拑鎵规"));
+ log.debug("鍗ц浆绔嬭澶囧畾鏃跺櫒鍚姩鎴愬姛: deviceId={}, taskId={}", device.getId(), task.getTaskId());
+ } else {
+ log.warn("鍗ц浆绔嬭澶囧畾鏃跺櫒鍚姩澶辫触: deviceId={}, taskId={}", device.getId(), task.getTaskId());
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "鍗ц浆绔嬭澶囧惎鍔ㄥ畾鏃跺櫒澶辫触";
+ break;
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 3. 杩涚墖澶ц溅璁惧锛氬惎鍔ㄥ畾鏃跺櫒鎸佺画鐩戞帶瀹归噺锛堢涓�涓ぇ杞﹁澶囷級
+ if (isLoadVehicle) {
+ currentLoadVehicleIndex++;
+ boolean isInboundVehicle = currentLoadVehicleIndex == 1; // 绗竴涓ぇ杞︽槸杩涚墖澶ц溅
+
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+
+ ScheduledFuture<?> vehicleTask;
+ if (isInboundVehicle) {
+ // 杩涚墖澶ц溅锛氱洃鎺у閲忥紝鍔ㄦ�佸垽鏂�
+ vehicleTask = startInboundVehicleTimer(task, step, device, context);
+ if (vehicleTask != null) {
+ registerScheduledTask(task.getTaskId(), vehicleTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "杩涚墖澶ц溅瀹氭椂鍣ㄥ凡鍚姩锛屾寔缁洃鎺у閲�"));
+ } else {
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "杩涚墖澶ц溅璁惧鍚姩瀹氭椂鍣ㄥけ璐�";
+ break;
+ }
+ } else {
+ // 鍑虹墖澶ц溅锛氬惎鍔ㄥ畾鏃跺櫒鐩戞帶鍑虹墖浠诲姟
+ vehicleTask = startOutboundVehicleTimer(task, step, device, context);
+ if (vehicleTask != null) {
+ registerScheduledTask(task.getTaskId(), vehicleTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "鍑虹墖澶ц溅瀹氭椂鍣ㄥ凡鍚姩锛屾寔缁洃鎺у嚭鐗囦换鍔�"));
+ } else {
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "鍑虹墖澶ц溅璁惧鍚姩瀹氭椂鍣ㄥけ璐�";
+ break;
+ }
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 4. 澶х悊鐗囩璁惧锛氬惎鍔ㄥ畾鏃跺櫒閫昏緫澶勭悊锛堜笉娑夊強PLC浜や簰锛屽彧璐熻矗閫昏緫澶勭悊锛�
+ if (isLargeGlass) {
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+
+ ScheduledFuture<?> largeGlassTask = startLargeGlassTimer(task, step, device, context);
+ if (largeGlassTask != null) {
+ registerScheduledTask(task.getTaskId(), largeGlassTask);
+ stepSummaries.add(createStepSummary(device.getDeviceName(), true, "澶х悊鐗囩瀹氭椂鍣ㄥ凡鍚姩锛岄�昏緫澶勭悊涓�"));
+ } else {
+ stepSummaries.add(createStepSummary(device.getDeviceName(), false, "鍚姩瀹氭椂鍣ㄥけ璐�"));
+ success = false;
+ failureMessage = "澶х悊鐗囩璁惧鍚姩瀹氭椂鍣ㄥけ璐�";
+ break;
+ }
+ currentOrder++;
+ continue;
+ }
+
+ // 鍏朵粬璁惧锛氭甯告墽琛�
+ TaskStepDetail step = createStepRecord(task, device, currentOrder);
+ StepResult stepResult = executeStep(task, step, device, context);
+ stepSummaries.add(stepResult.toSummary());
+ if (!stepResult.isSuccess()) {
+ success = false;
+ failureMessage = stepResult.getMessage();
+ break;
+ }
+ currentOrder++;
+ }
+
+ // 濡傛灉鎵�鏈夎澶囬兘鏄畾鏃跺櫒妯″紡锛屼换鍔′繚鎸佽繍琛岀姸鎬侊紝涓嶇瓑寰呭畬鎴�
+ // 瀹氭椂鍣ㄤ細鍦ㄥ悗鍙版寔缁繍琛岋紝鐩村埌鎵嬪姩鍋滄鎴栬秴鏃�
+ boolean hasScheduledTasks = !CollectionUtils.isEmpty(taskScheduledTasks.get(task.getTaskId()));
+ if (hasScheduledTasks) {
+ log.debug("浠诲姟宸插惎鍔ㄦ墍鏈夊畾鏃跺櫒锛屼繚鎸佽繍琛岀姸鎬�: taskId={}, scheduledTasksCount={}",
+ task.getTaskId(), taskScheduledTasks.get(task.getTaskId()).size());
+ // 浠诲姟淇濇寔 RUNNING 鐘舵�侊紝瀹氭椂鍣ㄥ湪鍚庡彴杩愯
+ // 涓嶆洿鏂颁换鍔$姸鎬佷负 COMPLETED锛岃浠诲姟鎸佺画杩愯
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("steps", stepSummaries);
+ payload.put("groupId", groupConfig.getId());
+ payload.put("deviceCount", devices.size());
+ payload.put("executionMode", executionMode);
+ payload.put("message", "浠诲姟宸插惎鍔紝瀹氭椂鍣ㄥ湪鍚庡彴杩愯涓�");
+
+ // 閫氱煡浠诲姟鐘舵�侊紙淇濇寔 RUNNING锛�
+ notificationService.notifyTaskStatus(task);
+
+ if (success) {
+ return TaskExecutionResult.success(payload);
+ }
+ return TaskExecutionResult.failure(failureMessage != null ? failureMessage : "浠诲姟鎵ц澶辫触", payload);
+ }
+
+ // 濡傛灉娌℃湁瀹氭椂鍣ㄤ换鍔★紝绛夊緟鎵�鏈夋楠ゅ畬鎴�
+ // 杩欑鎯呭喌閫氬父涓嶄細鍙戠敓锛屽洜涓烘墍鏈夎澶囬兘鏄畾鏃跺櫒妯″紡
}
Map<String, Object> payload = new HashMap<>();
payload.put("steps", stepSummaries);
payload.put("groupId", groupConfig.getId());
payload.put("deviceCount", devices.size());
+ payload.put("executionMode", executionMode);
+ // 鍋滄鎵�鏈夊畾鏃跺櫒浠诲姟
+ stopScheduledTasks(task.getTaskId());
+
+ boolean cancelled = isTaskCancelled(context);
+ // 鏇存柊浠诲姟鏈�缁堢姸鎬�
+ if (cancelled) {
+ task.setStatus(MultiDeviceTask.Status.CANCELLED.name());
+ task.setErrorMessage("浠诲姟宸插彇娑�");
+ } else if (success) {
+ task.setStatus(MultiDeviceTask.Status.COMPLETED.name());
+ } else {
+ task.setStatus(MultiDeviceTask.Status.FAILED.name());
+ task.setErrorMessage(failureMessage);
+ }
+ task.setEndTime(new Date());
+ multiDeviceTaskMapper.updateById(task);
+
+ // 閫氱煡浠诲姟瀹屾垚
+ notificationService.notifyTaskStatus(task);
+
if (success) {
return TaskExecutionResult.success(payload);
}
return TaskExecutionResult.failure(failureMessage != null ? failureMessage : "浠诲姟鎵ц澶辫触", payload);
+ }
+
+ /**
+ * 璇锋眰鍙栨秷浠诲姟锛氬仠姝㈡墍鏈夊畾鏃跺櫒骞舵爣璁颁笂涓嬫枃
+ */
+ public void requestTaskCancellation(String taskId) {
+ TaskExecutionContext context = runningTaskContexts.get(taskId);
+ if (context != null) {
+ context.getSharedData().put("taskCancelled", true);
+ log.warn("宸叉爣璁颁换鍔″彇娑�: taskId={}", taskId);
+ } else {
+ log.warn("璇锋眰鍙栨秷浠诲姟浣嗘湭鎵惧埌涓婁笅鏂�: taskId={}", taskId);
+ }
+ stopScheduledTasks(taskId);
+ }
+
+ /**
+ * 鍚姩鍗ц浆绔嬫壂鐮佽澶囧畾鏃跺櫒锛氭瘡10绉掑鐞嗕竴涓幓鐠僆D
+ */
+ private ScheduledFuture<?> startScannerTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ TaskParameters params = context.getParameters();
+ List<String> glassIds = params.getGlassIds();
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鍒濆鍖�: taskId={}, deviceId={}, glassIds={}, glassIdsSize={}, isEmpty={}",
+ task.getTaskId(), device.getId(), glassIds,
+ glassIds != null ? glassIds.size() : 0,
+ CollectionUtils.isEmpty(glassIds));
+ if (CollectionUtils.isEmpty(glassIds)) {
+ log.warn("鍗ц浆绔嬫壂鐮佽澶囨病鏈夌幓鐠僆D锛屽畾鏃跺櫒涓嶅惎鍔�: deviceId={}", device.getId());
+ return null;
+ }
+
+ // 鍒涘缓寰呭鐞嗙幓鐠僆D闃熷垪
+ Queue<String> glassIdQueue = new ConcurrentLinkedQueue<>(glassIds);
+ AtomicInteger processedCount = new AtomicInteger(0);
+ AtomicInteger successCount = new AtomicInteger(0);
+ AtomicInteger failCount = new AtomicInteger(0);
+
+ // 浠庤澶囬厤缃腑鑾峰彇鎵爜闂撮殧锛岄粯璁�10绉�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ Integer scanIntervalMs = getLogicParam(logicParams, "scanIntervalMs", 10_000);
+
+ log.debug("鍚姩鍗ц浆绔嬫壂鐮佸畾鏃跺櫒: taskId={}, deviceId={}, glassCount={}, interval={}ms, glassIds={}",
+ task.getTaskId(), device.getId(), glassIds.size(), scanIntervalMs, glassIds);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.debug("浠诲姟宸插彇娑堬紝鍋滄鍗ц浆绔嬫壂鐮佸畾鏃跺櫒: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ ensureStepRunning(step, task.getTaskId());
+ // 妫�鏌ユ槸鍚﹂渶瑕佹殏鍋�
+ if (shouldPauseScanner(context)) {
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鏆傚仠: taskId={}, deviceId={}", task.getTaskId(), device.getId());
+ return;
+ }
+
+ // 妫�鏌ユ槸鍚﹁繕鏈夊緟澶勭悊鐨勭幓鐠僆D
+ String glassId = glassIdQueue.poll();
+ if (glassId == null) {
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒瀹屾垚: taskId={}, deviceId={}, processed={}/{}, success={}, fail={}",
+ task.getTaskId(), device.getId(), processedCount.get(), glassIds.size(),
+ successCount.get(), failCount.get());
+
+ // 娓呯┖plcRequest鍜宲lcGlassId锛堢‘淇漃LC鐘舵�佹竻鐞嗭級
+ try {
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ Map<String, Object> clearParams = new HashMap<>();
+ clearParams.put("_taskContext", context);
+ handler.execute(device, "clearPlc", clearParams);
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒瀹屾垚锛屽凡娓呯┖PLC璇锋眰瀛楁: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ }
+ } catch (Exception e) {
+ log.warn("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒瀹屾垚鏃舵竻绌篜LC澶辫触: taskId={}, deviceId={}, error={}",
+ task.getTaskId(), device.getId(), e.getMessage());
+ }
+
+ // 鑻ヤ箣鍓嶆湭鍑虹幇澶辫触锛屽啀灏嗙姸鎬佺疆涓哄畬鎴�
+ boolean alreadyFailed = TaskStepDetail.Status.FAILED.name().equals(step.getStatus());
+ if (!alreadyFailed) {
+ step.setStatus(TaskStepDetail.Status.COMPLETED.name());
+ step.setSuccessMessage(String.format("宸插畬鎴愭壂鎻�: 鎴愬姛=%d, 澶辫触=%d", successCount.get(), failCount.get()));
+ if (step.getEndTime() == null) {
+ step.setEndTime(new Date());
+ }
+ taskStepDetailMapper.updateById(step);
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+ // 鎵爜璁惧瀹屾垚鍚庡皾璇曡嚜鍔ㄦ敹灏炬暣涓换鍔�
+ checkAndCompleteTaskIfDone(step.getTaskId());
+ }
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.COMPLETED, context);
+ return;
+ }
+
+ int currentIndex = processedCount.incrementAndGet();
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶勭悊绗瑊}/{}涓幓鐠�: taskId={}, deviceId={}, glassId={}",
+ currentIndex, glassIds.size(), task.getTaskId(), device.getId(), glassId);
+
+ // 鎵ц鍗曟鎵弿
+ Map<String, Object> scanParams = new HashMap<>();
+ scanParams.put("glassId", glassId);
+ scanParams.put("_taskContext", context);
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鍑嗗鎵ц: taskId={}, deviceId={}, glassId={}, scanParams={}",
+ task.getTaskId(), device.getId(), glassId, scanParams);
+
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ // 灏唋ogicParams鍚堝苟鍒皊canParams涓紙浣跨敤宸插畾涔夌殑logicParams鍙橀噺锛�
+ if (logicParams != null && !logicParams.isEmpty()) {
+ scanParams.put("_logicParams", logicParams);
+ }
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒璋冪敤handler.execute: taskId={}, deviceId={}, glassId={}, operation=scanOnce, scanParamsKeys={}, scanParams={}",
+ task.getTaskId(), device.getId(), glassId, scanParams.keySet(), scanParams);
+ DevicePlcVO.OperationResult result = handler.execute(device, "scanOnce", scanParams);
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒handler.execute杩斿洖: taskId={}, deviceId={}, glassId={}, success={}",
+ task.getTaskId(), device.getId(), glassId, result.getSuccess());
+
+ if (Boolean.TRUE.equals(result.getSuccess())) {
+ successCount.incrementAndGet();
+ log.debug("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶勭悊鎴愬姛: taskId={}, deviceId={}, glassId={}",
+ task.getTaskId(), device.getId(), glassId);
+ } else {
+ failCount.incrementAndGet();
+ log.warn("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶勭悊澶辫触: taskId={}, deviceId={}, glassId={}, error={}",
+ task.getTaskId(), device.getId(), glassId, result.getMessage());
+ }
+
+ // 鏇存柊姝ラ鐘舵�侊紙鏄剧ず杩涘害锛屼繚鎸丷UNNING鐘舵�佺洿鍒版墍鏈夌幓鐠冨鐞嗗畬鎴愶級
+ updateStepStatusForScanner(step, result, currentIndex, glassIds.size(),
+ successCount.get(), failCount.get());
+ // 閫氱煡姝ラ鏇存柊锛堣鍓嶇瀹炴椂鐪嬪埌姝ラ鐘舵�佸拰杩涘害锛�
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ } catch (Exception e) {
+ log.error("鍗ц浆绔嬫壂鐮佸畾鏃跺櫒鎵ц寮傚父: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ failCount.incrementAndGet();
+ }
+ }, 0, scanIntervalMs, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩鍗ц浆绔嬫壂鐮佸畾鏃跺櫒澶辫触: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩鍗ц浆绔嬭澶囧畾鏃跺櫒锛氬畾鏈熸鏌ュ苟澶勭悊鐜荤拑鎵规
+ */
+ private ScheduledFuture<?> startTransferTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ // 浠庤澶囬厤缃腑鑾峰彇鐩戞帶闂撮殧锛岄粯璁�5绉�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ Integer monitorIntervalMs = getLogicParam(logicParams, "monitorIntervalMs", 5_000);
+
+ log.debug("鍚姩鍗ц浆绔嬭澶囧畾鏃跺櫒: taskId={}, deviceId={}, interval={}ms",
+ task.getTaskId(), device.getId(), monitorIntervalMs);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.debug("浠诲姟宸插彇娑堬紝鍋滄鍗ц浆绔嬭澶囧畾鏃跺櫒: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ ensureStepRunning(step, task.getTaskId());
+ // 鏋勫缓鍙傛暟
+ Map<String, Object> params = new HashMap<>();
+ params.put("_taskContext", context);
+ if (logicParams != null && !logicParams.isEmpty()) {
+ params.put("_logicParams", logicParams);
+ }
+
+ // 璋冪敤handler鎵цcheckAndProcess
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ DevicePlcVO.OperationResult result = handler.execute(device, "checkAndProcess", params);
+
+ // 鏇存柊姝ラ鐘舵�侊紙鍖哄垎绛夊緟涓拰鐪熸瀹屾垚锛�
+ updateStepStatusForTransfer(step, result);
+ // 閫氱煡姝ラ鏇存柊锛堣鍓嶇瀹炴椂鐪嬪埌姝ラ鐘舵�侊級
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (opSuccess) {
+ String message = result.getMessage();
+ if (message != null && message.contains("鎵规宸插啓鍏LC")) {
+ log.debug("鍗ц浆绔嬭澶囧畾鏃跺櫒鎵ц鎴愬姛锛堝凡鍐欏叆PLC锛�: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), message);
+ } else {
+ log.debug("鍗ц浆绔嬭澶囧畾鏃跺櫒绛夊緟涓�: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), message);
+ }
+ } else {
+ log.warn("鍗ц浆绔嬭澶囧畾鏃跺櫒鎵ц澶辫触: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), result.getMessage());
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ } catch (Exception e) {
+ log.error("鍗ц浆绔嬭澶囧畾鏃跺櫒鎵ц寮傚父: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, monitorIntervalMs, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩鍗ц浆绔嬭澶囧畾鏃跺櫒澶辫触: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩杩涚墖澶ц溅璁惧瀹氭椂鍣細鎸佺画鐩戞帶瀹归噺锛屽姩鎬佸垽鏂�
+ */
+ private ScheduledFuture<?> startInboundVehicleTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ final long MONITOR_INTERVAL_MS = 2_000; // 2绉掔洃鎺т竴娆�
+ final AtomicInteger lastProcessedCount = new AtomicInteger(0);
+
+ log.debug("鍚姩杩涚墖澶ц溅璁惧瀹氭椂鍣�: taskId={}, deviceId={}, interval={}s",
+ task.getTaskId(), device.getId(), MONITOR_INTERVAL_MS / 1000);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.debug("浠诲姟宸插彇娑堬紝鍋滄杩涚墖澶ц溅瀹氭椂鍣�: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ ensureStepRunning(step, task.getTaskId());
+ // 妫�鏌ユ槸鍚︽湁鍗ц浆绔嬩富浣撳凡杈撳嚭銆佸噯澶囦笂澶ц溅鐨勭幓鐠冧俊鎭�
+ List<String> readyGlassIds = getTransferReadyGlassIds(context);
+ if (CollectionUtils.isEmpty(readyGlassIds)) {
+ // 娌℃湁鍗ц浆绔嬭緭鍑虹殑鐜荤拑锛岀户缁瓑寰�
+ return;
+ }
+
+ // 濡傛灉鐜荤拑ID鏁伴噺娌℃湁鍙樺寲锛岃鏄庢病鏈夋柊鐨勭幓鐠冿紝缁х画绛夊緟
+ int currentCount = readyGlassIds.size();
+ if (currentCount == lastProcessedCount.get()) {
+ log.debug("澶ц溅璁惧瀹氭椂鍣細鐜荤拑ID鏁伴噺鏈彉鍖栵紝缁х画绛夊緟: taskId={}, deviceId={}, count={}",
+ task.getTaskId(), device.getId(), currentCount);
+ return;
+ }
+
+ log.debug("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄦ娴嬪埌鍗ц浆绔嬭緭鍑虹殑鐜荤拑淇℃伅: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), currentCount);
+
+ // 妫�鏌ュ閲�
+ Map<String, Object> checkParams = new HashMap<>();
+ checkParams.put("glassIds", new ArrayList<>(readyGlassIds));
+ checkParams.put("_taskContext", context);
+
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ // 灏唋ogicParams鍚堝苟鍒癱heckParams涓�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ if (logicParams != null && !logicParams.isEmpty()) {
+ checkParams.put("_logicParams", logicParams);
+ }
+ // 绗竴姝ワ細鍐欏叆澶ц溅涓婃枡璇锋眰
+ DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", checkParams);
+
+ if (Boolean.TRUE.equals(feedResult.getSuccess())) {
+ log.debug("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屾垚鍔�: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), readyGlassIds.size());
+ // 灏嗗凡瑁呰浇鐨勭幓鐠僆D淇濆瓨鍒板叡浜暟鎹腑锛堜緵澶х悊鐗囩浣跨敤锛�
+ setLoadedGlassIds(context, new ArrayList<>(readyGlassIds));
+ // 娓呯┖鍗ц浆绔嬭緭鍑虹殑鐜荤拑ID鍒楄〃锛堝凡澶勭悊锛�
+ clearTransferReadyGlassIds(context);
+ lastProcessedCount.set(0);
+ // 纭繚鍗ц浆绔嬫壂鐮佺户缁繍琛�
+ setScannerPause(context, false);
+ } else {
+ // 瑁呬笉涓嬶紝璁板綍瀹归噺涓嶈冻锛堟槸鍚﹂渶瑕佸奖鍝嶆壂鐮佺敱宸ヨ壓鍐嶅喅瀹氾級
+ log.warn("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄥ閲忎笉瓒�: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), feedResult.getMessage());
+ lastProcessedCount.set(currentCount); // 璁板綍褰撳墠鏁伴噺锛岄伩鍏嶉噸澶嶆鏌�
+ }
+
+ // 绗簩姝ワ細妫�鏌ES纭鐘舵�侊紙濡傛灉澶ц溅澶勭悊鍣ㄦ敮鎸佺殑璇濓級
+ DevicePlcVO.OperationResult mesResult = null;
+ try {
+ mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap());
+ } catch (Exception e) {
+ log.warn("杩涚墖澶ц溅璁惧妫�鏌ES纭鐘舵�佸紓甯�: taskId={}, deviceId={}, error={}",
+ task.getTaskId(), device.getId(), e.getMessage());
+ }
+
+ // 鏇存柊姝ラ鐘舵�侊紙澶ц溅璁惧淇濇寔RUNNING锛岀洿鍒癕ES纭瀹屾垚鎴栦换鍔″彇娑堬級
+ if (mesResult != null) {
+ updateStepStatusForVehicle(step, mesResult);
+ boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ } else {
+ updateStepStatusForVehicle(step, feedResult);
+ boolean opSuccess = Boolean.TRUE.equals(feedResult.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("杩涚墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屽紓甯�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, MONITOR_INTERVAL_MS, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩杩涚墖澶ц溅璁惧瀹氭椂鍣ㄥけ璐�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩鍑虹墖澶ц溅璁惧瀹氭椂鍣細鎸佺画鐩戞帶鍑虹墖浠诲姟
+ */
+ private ScheduledFuture<?> startOutboundVehicleTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ final long MONITOR_INTERVAL_MS = 2_000; // 2绉掔洃鎺т竴娆�
+
+ log.debug("鍚姩鍑虹墖澶ц溅璁惧瀹氭椂鍣�: taskId={}, deviceId={}, interval={}s",
+ task.getTaskId(), device.getId(), MONITOR_INTERVAL_MS / 1000);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.debug("浠诲姟宸插彇娑堬紝鍋滄鍑虹墖澶ц溅瀹氭椂鍣�: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ ensureStepRunning(step, task.getTaskId());
+ // 妫�鏌ユ槸鍚︽湁宸插鐞嗙殑鐜荤拑淇℃伅锛堜粠澶х悊鐗囩鏉ョ殑锛�
+ List<String> processedGlassIds = getProcessedGlassIds(context);
+ if (CollectionUtils.isEmpty(processedGlassIds)) {
+ log.debug("鍑虹墖澶ц溅璁惧瀹氭椂鍣細鏆傛棤宸插鐞嗙殑鐜荤拑淇℃伅: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+
+ log.debug("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ娴嬪埌宸插鐞嗙殑鐜荤拑淇℃伅: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), processedGlassIds.size());
+
+ // 鎵ц鍑虹墖鎿嶄綔
+ Map<String, Object> checkParams = new HashMap<>();
+ checkParams.put("glassIds", new ArrayList<>(processedGlassIds));
+ checkParams.put("_taskContext", context);
+
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ if (handler != null) {
+ // 灏唋ogicParams鍚堝苟鍒癱heckParams涓�
+ Map<String, Object> logicParams = parseLogicParams(device);
+ if (logicParams != null && !logicParams.isEmpty()) {
+ checkParams.put("_logicParams", logicParams);
+ }
+ // 绗竴姝ワ細鍐欏叆澶ц溅鍑虹墖璇锋眰
+ DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", checkParams);
+
+ if (Boolean.TRUE.equals(feedResult.getSuccess())) {
+ log.debug("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屾垚鍔�: taskId={}, deviceId={}, glassCount={}",
+ task.getTaskId(), device.getId(), processedGlassIds.size());
+ // 娓呯┖宸插鐞嗙殑鐜荤拑ID鍒楄〃锛堝凡澶勭悊锛�
+ clearProcessedGlassIds(context);
+ } else {
+ log.debug("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屽け璐�: taskId={}, deviceId={}, message={}",
+ task.getTaskId(), device.getId(), feedResult.getMessage());
+ }
+
+ // 绗簩姝ワ細妫�鏌ES纭鐘舵�侊紙濡傛灉澶ц溅澶勭悊鍣ㄦ敮鎸佺殑璇濓級
+ DevicePlcVO.OperationResult mesResult = null;
+ try {
+ mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap());
+ } catch (Exception e) {
+ log.warn("鍑虹墖澶ц溅璁惧妫�鏌ES纭鐘舵�佸紓甯�: taskId={}, deviceId={}, error={}",
+ task.getTaskId(), device.getId(), e.getMessage());
+ }
+
+ // 鏇存柊姝ラ鐘舵�侊紙澶ц溅璁惧淇濇寔RUNNING锛岀洿鍒癕ES纭瀹屾垚鎴栦换鍔″彇娑堬級
+ if (mesResult != null) {
+ updateStepStatusForVehicle(step, mesResult);
+ boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ } else {
+ updateStepStatusForVehicle(step, feedResult);
+ boolean opSuccess = Boolean.TRUE.equals(feedResult.getSuccess());
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+ if (!opSuccess) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄦ墽琛屽紓甯�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, MONITOR_INTERVAL_MS, TimeUnit.MILLISECONDS);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.RUNNING, context);
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩鍑虹墖澶ц溅璁惧瀹氭椂鍣ㄥけ璐�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鍚姩澶х悊鐗囩璁惧瀹氭椂鍣細閫昏緫澶勭悊锛堜笉娑夊強PLC浜や簰锛屽彧璐熻矗閫昏緫澶勭悊锛屾瘮濡傚涔呯粰浠诲姟姹囨姤锛�
+ */
+ private ScheduledFuture<?> startLargeGlassTimer(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ try {
+ // 浠庤澶囬厤缃腑鑾峰彇澶勭悊鏃堕棿锛堥粯璁�30绉掞級
+ Map<String, Object> logicParams = parseLogicParams(device);
+ Integer processTimeSeconds = getLogicParam(logicParams, "processTimeSeconds", 30);
+ final long PROCESS_TIME_MS = processTimeSeconds * 1000;
+
+ log.debug("鍚姩澶х悊鐗囩璁惧瀹氭椂鍣�: taskId={}, deviceId={}, processTime={}s",
+ task.getTaskId(), device.getId(), processTimeSeconds);
+
+ // 鍚姩瀹氭椂浠诲姟
+ ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
+ try {
+ if (isTaskCancelled(context)) {
+ log.debug("浠诲姟宸插彇娑堬紝鍋滄澶х悊鐗囩瀹氭椂鍣�: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+ ensureStepRunning(step, task.getTaskId());
+ // 妫�鏌ユ槸鍚︽湁宸茶杞界殑鐜荤拑淇℃伅锛堜粠杩涚墖澶ц溅鏉ョ殑锛�
+ List<String> loadedGlassIds = getLoadedGlassIds(context);
+ if (CollectionUtils.isEmpty(loadedGlassIds)) {
+ log.debug("澶х悊鐗囩璁惧瀹氭椂鍣細鏆傛棤宸茶杞界殑鐜荤拑淇℃伅: taskId={}, deviceId={}",
+ task.getTaskId(), device.getId());
+ return;
+ }
+
+ // 妫�鏌ョ幓鐠冩槸鍚﹀凡缁忓鐞嗗畬鎴愶紙閫氳繃澶勭悊鏃堕棿鍒ゆ柇锛�
+ Long processStartTime = getProcessStartTime(context);
+ if (processStartTime == null) {
+ // 绗竴娆℃娴嬪埌鐜荤拑锛岃褰曞紑濮嬪鐞嗘椂闂�
+ setProcessStartTime(context, System.currentTimeMillis());
+ log.debug("澶х悊鐗囩璁惧寮�濮嬪鐞�: taskId={}, deviceId={}, glassCount={}, processTime={}s",
+ task.getTaskId(), device.getId(), loadedGlassIds.size(), processTimeSeconds);
+ return;
+ }
+
+ long elapsed = System.currentTimeMillis() - processStartTime;
+ if (elapsed < PROCESS_TIME_MS) {
+ // 澶勭悊鏃堕棿鏈埌锛岀户缁瓑寰�
+ log.debug("澶х悊鐗囩璁惧澶勭悊涓�: taskId={}, deviceId={}, elapsed={}s, remaining={}s",
+ task.getTaskId(), device.getId(), elapsed / 1000, (PROCESS_TIME_MS - elapsed) / 1000);
+ return;
+ }
+
+ // 澶勭悊鏃堕棿宸插埌锛屽畬鎴愪换鍔℃眹鎶�
+ log.debug("澶х悊鐗囩璁惧澶勭悊瀹屾垚: taskId={}, deviceId={}, glassCount={}, processTime={}s",
+ task.getTaskId(), device.getId(), loadedGlassIds.size(), processTimeSeconds);
+
+ // 灏嗗凡澶勭悊鐨勭幓鐠僆D杞Щ鍒板凡澶勭悊鍒楄〃锛堜緵鍑虹墖澶ц溅浣跨敤锛�
+ setProcessedGlassIds(context, new ArrayList<>(loadedGlassIds));
+ clearLoadedGlassIds(context);
+ clearProcessStartTime(context);
+
+ // 鏇存柊姝ラ鐘舵��
+ step.setStatus(TaskStepDetail.Status.COMPLETED.name());
+ step.setErrorMessage(null);
+ step.setOutputData(toJson(Collections.singletonMap("glassIds", loadedGlassIds)));
+ taskStepDetailMapper.updateById(step);
+ // 澶х悊鐗囩瀹屾垚鍚庡皾璇曡嚜鍔ㄦ敹灏炬暣涓换鍔�
+ checkAndCompleteTaskIfDone(step.getTaskId());
+
+ } catch (Exception e) {
+ log.error("澶х悊鐗囩璁惧瀹氭椂鍣ㄦ墽琛屽紓甯�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ }
+ }, 0, 1_000, TimeUnit.MILLISECONDS); // 姣忕妫�鏌ヤ竴娆�
+
+ return future;
+ } catch (Exception e) {
+ log.error("鍚姩澶х悊鐗囩璁惧瀹氭椂鍣ㄥけ璐�: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 鑾峰彇閫昏緫鍙傛暟
+ */
+ @SuppressWarnings("unchecked")
+ private <T> T getLogicParam(Map<String, Object> logicParams, String key, T defaultValue) {
+ if (logicParams == null) {
+ return defaultValue;
+ }
+ Object value = logicParams.get(key);
+ if (value == null) {
+ return defaultValue;
+ }
+ try {
+ return (T) value;
+ } catch (ClassCastException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 鑾峰彇宸茶杞界殑鐜荤拑ID鍒楄〃
+ */
+ @SuppressWarnings("unchecked")
+ private List<String> getLoadedGlassIds(TaskExecutionContext context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = context.getSharedData().get("loadedGlassIds");
+ if (glassIds instanceof List) {
+ return new ArrayList<>((List<String>) glassIds);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 璁剧疆宸茶杞界殑鐜荤拑ID鍒楄〃
+ */
+ private void setLoadedGlassIds(TaskExecutionContext context, List<String> glassIds) {
+ if (context != null) {
+ context.getSharedData().put("loadedGlassIds", new ArrayList<>(glassIds));
+ }
+ }
+
+ /**
+ * 娓呯┖宸茶杞界殑鐜荤拑ID鍒楄〃
+ */
+ private void clearLoadedGlassIds(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().put("loadedGlassIds", new ArrayList<>());
+ }
+ }
+
+ /**
+ * 鑾峰彇宸插鐞嗙殑鐜荤拑ID鍒楄〃
+ */
+ @SuppressWarnings("unchecked")
+ private List<String> getProcessedGlassIds(TaskExecutionContext context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = context.getSharedData().get("processedGlassIds");
+ if (glassIds instanceof List) {
+ return new ArrayList<>((List<String>) glassIds);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 璁剧疆宸插鐞嗙殑鐜荤拑ID鍒楄〃
+ */
+ private void setProcessedGlassIds(TaskExecutionContext context, List<String> glassIds) {
+ if (context != null) {
+ context.getSharedData().put("processedGlassIds", new ArrayList<>(glassIds));
+ }
+ }
+
+ /**
+ * 娓呯┖宸插鐞嗙殑鐜荤拑ID鍒楄〃
+ */
+ private void clearProcessedGlassIds(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().put("processedGlassIds", new ArrayList<>());
+ }
+ }
+
+ /**
+ * 鑾峰彇澶勭悊寮�濮嬫椂闂�
+ */
+ private Long getProcessStartTime(TaskExecutionContext context) {
+ if (context == null) {
+ return null;
+ }
+ Object time = context.getSharedData().get("processStartTime");
+ if (time instanceof Number) {
+ return ((Number) time).longValue();
+ }
+ return null;
+ }
+
+ /**
+ * 璁剧疆澶勭悊寮�濮嬫椂闂�
+ */
+ private void setProcessStartTime(TaskExecutionContext context, long time) {
+ if (context != null) {
+ context.getSharedData().put("processStartTime", time);
+ }
+ }
+
+ /**
+ * 娓呯┖澶勭悊寮�濮嬫椂闂�
+ */
+ private void clearProcessStartTime(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().remove("processStartTime");
+ }
+ }
+
+ /**
+ * 璁剧疆鍗ц浆绔嬫壂鐮佹殏鍋滄爣蹇�
+ */
+ private void setScannerPause(TaskExecutionContext context, boolean pause) {
+ if (context != null) {
+ context.getSharedData().put("scannerPause", pause);
+ }
+ }
+
+ private boolean isTaskCancelled(TaskExecutionContext context) {
+ if (context == null) {
+ return false;
+ }
+ Object cancelled = context.getSharedData().get("taskCancelled");
+ return cancelled instanceof Boolean && (Boolean) cancelled;
+ }
+
+ /**
+ * 妫�鏌ユ槸鍚﹂渶瑕佹殏鍋滃崸杞珛鎵爜
+ */
+ private boolean shouldPauseScanner(TaskExecutionContext context) {
+ if (context == null) {
+ return false;
+ }
+ Object pauseFlag = context.getSharedData().get("scannerPause");
+ return pauseFlag instanceof Boolean && (Boolean) pauseFlag;
+ }
+
+ /**
+ * 鑾峰彇宸叉壂鎻忕殑鐜荤拑ID鍒楄〃
+ */
+ @SuppressWarnings("unchecked")
+ private List<String> getScannedGlassIds(TaskExecutionContext context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = context.getSharedData().get("scannedGlassIds");
+ if (glassIds instanceof List) {
+ return new ArrayList<>((List<String>) glassIds);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 娓呯┖宸叉壂鎻忕殑鐜荤拑ID鍒楄〃
+ */
+ private void clearScannedGlassIds(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().put("scannedGlassIds", new ArrayList<>());
+ }
+ }
+
+ /**
+ * 鑾峰彇鍗ц浆绔嬩富浣撳凡杈撳嚭銆佸噯澶囦笂澶ц溅鐨勭幓鐠僆D鍒楄〃
+ */
+ @SuppressWarnings("unchecked")
+ private List<String> getTransferReadyGlassIds(TaskExecutionContext context) {
+ if (context == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = context.getSharedData().get("transferReadyGlassIds");
+ if (glassIds instanceof List) {
+ return new ArrayList<>((List<String>) glassIds);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * 娓呯┖鍗ц浆绔嬩富浣撳凡杈撳嚭鐨勭幓鐠僆D鍒楄〃
+ */
+ private void clearTransferReadyGlassIds(TaskExecutionContext context) {
+ if (context != null) {
+ context.getSharedData().put("transferReadyGlassIds", new ArrayList<>());
+ }
+ }
+
+ /**
+ * 娉ㄥ唽瀹氭椂鍣ㄤ换鍔�
+ */
+ private void registerScheduledTask(String taskId, ScheduledFuture<?> future) {
+ taskScheduledTasks.computeIfAbsent(taskId, k -> new ArrayList<>()).add(future);
+ }
+
+ /**
+ * 鍋滄鎵�鏈夊畾鏃跺櫒浠诲姟
+ */
+ private void stopScheduledTasks(String taskId) {
+ List<ScheduledFuture<?>> futures = taskScheduledTasks.remove(taskId);
+ if (futures != null) {
+ for (ScheduledFuture<?> future : futures) {
+ if (future != null && !future.isCancelled()) {
+ future.cancel(false);
+ }
+ }
+ log.debug("宸插仠姝换鍔$殑鎵�鏈夊畾鏃跺櫒: taskId={}, count={}", taskId, futures.size());
+ }
+ runningTaskContexts.remove(taskId);
+ }
+
+ /**
+ * 绛夊緟瀹氭椂鍣ㄤ换鍔″畬鎴愶紙甯﹁秴鏃讹級
+ */
+ private void waitForScheduledTasks(String taskId, TaskExecutionContext context) {
+ // 鑾峰彇浠诲姟瓒呮椂鏃堕棿锛堥粯璁�30鍒嗛挓锛�
+ TaskParameters params = context.getParameters();
+ long timeoutMinutes = params != null && params.getTimeoutMinutes() != null
+ ? params.getTimeoutMinutes() : 30;
+ long timeoutMs = timeoutMinutes * 60 * 1000;
+ long deadline = System.currentTimeMillis() + timeoutMs;
+
+ log.debug("绛夊緟瀹氭椂鍣ㄤ换鍔″畬鎴�: taskId={}, timeout={}鍒嗛挓", taskId, timeoutMinutes);
+
+ while (System.currentTimeMillis() < deadline) {
+ List<ScheduledFuture<?>> futures = taskScheduledTasks.get(taskId);
+ if (futures == null || futures.isEmpty()) {
+ break;
+ }
+
+ // 妫�鏌ユ槸鍚︽墍鏈変换鍔¢兘宸插畬鎴�
+ boolean allDone = true;
+ for (ScheduledFuture<?> future : futures) {
+ if (future != null && !future.isDone()) {
+ allDone = false;
+ break;
+ }
+ }
+
+ if (allDone) {
+ break;
+ }
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+ }
+
+ log.debug("瀹氭椂鍣ㄤ换鍔$瓑寰呭畬鎴�: taskId={}", taskId);
+ }
+
+ /**
+ * 褰撴煇涓楠ゅ彲鑳藉畬鎴愭椂锛屾鏌ヤ换鍔℃槸鍚︽墍鏈夋楠ら兘宸插畬鎴愶紝濡傛灉鏄垯鑷姩灏嗕换鍔℃爣璁颁负宸插畬鎴�
+ */
+ private void checkAndCompleteTaskIfDone(String taskId) {
+ if (taskId == null) {
+ return;
+ }
+ try {
+ MultiDeviceTask task = multiDeviceTaskMapper.selectOne(
+ Wrappers.<MultiDeviceTask>lambdaQuery()
+ .eq(MultiDeviceTask::getTaskId, taskId)
+ );
+ if (task == null) {
+ return;
+ }
+ // 浠呭湪浠诲姟浠嶄负RUNNING鏃舵墠灏濊瘯鑷姩鏀跺熬
+ if (!MultiDeviceTask.Status.RUNNING.name().equals(task.getStatus())) {
+ return;
+ }
+
+ int totalSteps = task.getTotalSteps() != null ? task.getTotalSteps() : 0;
+ if (totalSteps <= 0) {
+ return;
+ }
+
+ int completedSteps = countCompletedSteps(taskId);
+ if (completedSteps < totalSteps) {
+ return;
+ }
+
+ // 鎵�鏈夋楠ら兘宸插畬鎴愶紝鏀跺熬浠诲姟
+ task.setStatus(MultiDeviceTask.Status.COMPLETED.name());
+ task.setEndTime(new Date());
+ multiDeviceTaskMapper.updateById(task);
+
+ // 鍋滄鎵�鏈夊畾鏃跺櫒
+ stopScheduledTasks(taskId);
+
+ // 閫氱煡浠诲姟瀹屾垚
+ notificationService.notifyTaskStatus(task);
+
+ log.info("鎵�鏈夋楠ゅ凡瀹屾垚锛岃嚜鍔ㄥ皢浠诲姟鏍囪涓哄凡瀹屾垚: taskId={}, totalSteps={}", taskId, totalSteps);
+ } catch (Exception e) {
+ log.warn("妫�鏌ュ苟鑷姩瀹屾垚浠诲姟澶辫触: taskId={}", taskId, e);
+ }
+ }
+
+ /**
+ * 鏇存柊姝ラ鐘舵��
+ */
+ private void updateStepStatus(TaskStepDetail step, DevicePlcVO.OperationResult result) {
+ if (step == null || result == null) {
+ return;
+ }
+ boolean success = Boolean.TRUE.equals(result.getSuccess());
+ step.setStatus(success
+ ? TaskStepDetail.Status.COMPLETED.name()
+ : TaskStepDetail.Status.FAILED.name());
+ // 璁剧疆娑堟伅锛氭垚鍔熸椂濡傛灉鏈夋秷鎭篃淇濆瓨锛屽け璐ユ椂淇濆瓨閿欒娑堟伅
+ String message = result.getMessage();
+ if (success) {
+ // 鎴愬姛鏃讹紝濡傛灉鏈夋秷鎭垯淇濆瓨锛堢敤浜庢彁绀轰俊鎭級锛屽惁鍒欐竻绌�
+ step.setSuccessMessage(StringUtils.hasText(message) ? message : null);
+ // 濡傛灉鐘舵�佸彉涓哄畬鎴愶紝璁剧疆缁撴潫鏃堕棿
+ if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus()) && step.getEndTime() == null) {
+ step.setEndTime(new Date());
+ }
+ } else {
+ // 澶辫触鏃朵繚瀛橀敊璇秷鎭�
+ step.setErrorMessage(message);
+ // 濡傛灉鐘舵�佸彉涓哄け璐ワ紝璁剧疆缁撴潫鏃堕棿
+ if (TaskStepDetail.Status.FAILED.name().equals(step.getStatus()) && step.getEndTime() == null) {
+ step.setEndTime(new Date());
+ }
+ }
+ step.setOutputData(toJson(result));
+ taskStepDetailMapper.updateById(step);
+ }
+
+ /**
+ * 纭繚姝ラ杩涘叆RUNNING鐘舵�侊紙浠呭湪绗竴娆$湡姝f墽琛屽墠璋冪敤锛�
+ */
+ private void ensureStepRunning(TaskStepDetail step, String taskId) {
+ if (step == null) {
+ return;
+ }
+ if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ if (step.getStartTime() == null) {
+ step.setStartTime(new Date());
+ }
+ taskStepDetailMapper.updateById(step);
+ notificationService.notifyStepUpdate(taskId, step);
+ }
+ }
+
+ /**
+ * 鏇存柊鎵爜璁惧姝ラ鐘舵�侊紙鏄剧ず杩涘害锛屼繚鎸丷UNNING鐘舵�佺洿鍒版墍鏈夌幓鐠冨鐞嗗畬鎴愶級
+ */
+ private void updateStepStatusForScanner(TaskStepDetail step, DevicePlcVO.OperationResult result,
+ int currentIndex, int totalCount,
+ int successCount, int failCount) {
+ if (step == null || result == null) {
+ return;
+ }
+
+ boolean success = Boolean.TRUE.equals(result.getSuccess());
+
+ // 淇濇寔RUNNING鐘舵�侊紝鐩村埌鎵�鏈夌幓鐠冨鐞嗗畬鎴愶紙鍦ㄥ畾鏃跺櫒瀹屾垚鏃跺啀璁剧疆涓篊OMPLETED锛�
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+
+ // 鏇存柊鏃堕棿鍜岃�楁椂锛屽墠绔彲浠ュ疄鏃剁湅鍒版墽琛岃�楁椂
+ Date now = new Date();
+ if (step.getStartTime() == null) {
+ step.setStartTime(now);
+ }
+ if (step.getStartTime() != null) {
+ step.setDurationMs(now.getTime() - step.getStartTime().getTime());
+ }
+
+ // 鏇存柊杩涘害淇℃伅
+ String progressMessage = String.format("姝e湪澶勭悊 %d/%d (鎴愬姛:%d, 澶辫触:%d)",
+ currentIndex, totalCount, successCount, failCount);
+
+ if (success) {
+ // 鎴愬姛鏃舵樉绀鸿繘搴﹀拰鎴愬姛娑堟伅
+ String resultMessage = result.getMessage();
+ if (StringUtils.hasText(resultMessage)) {
+ step.setSuccessMessage(progressMessage + " - " + resultMessage);
+ } else {
+ step.setSuccessMessage(progressMessage);
+ }
+ step.setErrorMessage(null);
+ } else {
+ // 澶辫触鏃舵樉绀鸿繘搴﹀拰閿欒娑堟伅
+ String errorMessage = result.getMessage();
+ step.setErrorMessage(progressMessage + " - " + (StringUtils.hasText(errorMessage) ? errorMessage : "澶勭悊澶辫触"));
+ step.setSuccessMessage(null);
+ }
+
+ step.setOutputData(toJson(result));
+ taskStepDetailMapper.updateById(step);
+ }
+
+ /**
+ * 鏇存柊澶ц溅璁惧姝ラ鐘舵�侊紙淇濇寔RUNNING锛岀洿鍒版墜鍔ㄥ仠姝㈡垨浠诲姟鍙栨秷锛涘け璐ユ椂鏍囪涓篎AILED锛�
+ */
+ private void updateStepStatusForVehicle(TaskStepDetail step, DevicePlcVO.OperationResult result) {
+ if (step == null || result == null) {
+ return;
+ }
+ boolean success = Boolean.TRUE.equals(result.getSuccess());
+ boolean completed = false;
+ if (result.getData() != null && result.getData().get("completed") != null) {
+ Object flag = result.getData().get("completed");
+ if (flag instanceof Boolean) {
+ completed = (Boolean) flag;
+ } else {
+ completed = "true".equalsIgnoreCase(String.valueOf(flag));
+ }
+ }
+ Date now = new Date();
+
+ // 鍒濆鍖栧紑濮嬫椂闂�
+ if (step.getStartTime() == null) {
+ step.setStartTime(now);
+ }
+
+ if (success && !completed) {
+ // 鎴愬姛浣嗘湭瀹屾垚锛氫繚鎸丷UNNING鐘舵�侊紝浠呮洿鏂版彁绀轰俊鎭拰鑰楁椂
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ String message = result.getMessage();
+ step.setSuccessMessage(StringUtils.hasText(message) ? message : "澶ц溅璁惧杩愯涓�");
+ step.setErrorMessage(null);
+ if (step.getStartTime() != null) {
+ step.setDurationMs(now.getTime() - step.getStartTime().getTime());
+ }
+ } else if (success && completed) {
+ // 鎴愬姛涓擬ES宸茬‘璁ゅ畬鎴愶細鏍囪涓篊OMPLETED骞惰褰曠粨鏉熸椂闂�
+ step.setStatus(TaskStepDetail.Status.COMPLETED.name());
+ String message = result.getMessage();
+ step.setSuccessMessage(StringUtils.hasText(message) ? message : "澶ц溅璁惧浠诲姟宸插畬鎴�");
+ step.setErrorMessage(null);
+ if (step.getEndTime() == null) {
+ step.setEndTime(now);
+ }
+ if (step.getStartTime() != null && step.getEndTime() != null) {
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ }
+ // 灏濊瘯鑷姩鏀跺熬鏁翠釜浠诲姟
+ checkAndCompleteTaskIfDone(step.getTaskId());
+ } else {
+ // 澶辫触锛氭爣璁颁负FAILED骞惰褰曠粨鏉熸椂闂�
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ String message = result.getMessage();
+ step.setErrorMessage(message);
+ if (step.getEndTime() == null) {
+ step.setEndTime(now);
+ }
+ if (step.getStartTime() != null && step.getEndTime() != null) {
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ }
+ }
+
+ step.setOutputData(toJson(result));
+ taskStepDetailMapper.updateById(step);
+ }
+
+ /**
+ * 鏇存柊鍗ц浆绔嬭澶囨楠ょ姸鎬侊紙鍖哄垎绛夊緟涓拰鐪熸瀹屾垚锛�
+ */
+ private void updateStepStatusForTransfer(TaskStepDetail step, DevicePlcVO.OperationResult result) {
+ if (step == null || result == null) {
+ return;
+ }
+ boolean success = Boolean.TRUE.equals(result.getSuccess());
+ String message = result.getMessage();
+
+ // 鍒ゆ柇鏄惁鐪熸瀹屾垚锛�
+ // 1. 鍐欏叆PLC鎴愬姛
+ // 2. 涓旂紦鍐插凡娓呯┖锛堣〃绀烘墍鏈夌幓鐠冨凡澶勭悊瀹岋紝鏃犳柊鐜荤拑锛�
+ boolean isRealCompleted = success && message != null
+ && message.contains("鎵规宸插啓鍏LC")
+ && message.contains("缂撳啿宸叉竻绌猴紝浠诲姟瀹屾垚");
+
+ if (isRealCompleted) {
+ // 鐪熸瀹屾垚锛氳缃负瀹屾垚鐘舵�侊紝骞惰缃粨鏉熸椂闂�
+ step.setStatus(TaskStepDetail.Status.COMPLETED.name());
+ step.setSuccessMessage(message);
+ if (step.getEndTime() == null) {
+ step.setEndTime(new Date());
+ }
+ // 璁$畻鑰楁椂
+ if (step.getStartTime() != null && step.getEndTime() != null) {
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ }
+ log.debug("鍗ц浆绔嬭澶囨楠ゅ凡瀹屾垚: stepId={}, durationMs={}", step.getId(), step.getDurationMs());
+ // 鍗ц浆绔嬩富浣撳畬鎴愬悗灏濊瘯鑷姩鏀跺熬鏁翠釜浠诲姟
+ checkAndCompleteTaskIfDone(step.getTaskId());
+ } else if (success && message != null && message.contains("鎵规宸插啓鍏LC")) {
+ // 鍐欏叆PLC鎴愬姛浣嗙紦鍐茶繕鏈夌幓鐠冿紙杞︽弧鎯呭喌锛夛紝缁х画杩愯
+ if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ }
+ step.setSuccessMessage(message);
+ // 纭繚寮�濮嬫椂闂村凡璁剧疆
+ if (step.getStartTime() == null) {
+ step.setStartTime(new Date());
+ }
+ } else if (success) {
+ // 绛夊緟涓細淇濇寔杩愯鐘舵�侊紝鍙洿鏂版秷鎭�
+ if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ }
+ step.setSuccessMessage(message);
+ // 纭繚寮�濮嬫椂闂村凡璁剧疆
+ if (step.getStartTime() == null) {
+ step.setStartTime(new Date());
+ }
+ } else {
+ // 澶辫触锛氳缃负澶辫触鐘舵�侊紝骞惰缃粨鏉熸椂闂�
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(message);
+ if (step.getEndTime() == null) {
+ step.setEndTime(new Date());
+ }
+ // 璁$畻鑰楁椂
+ if (step.getStartTime() != null && step.getEndTime() != null) {
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ }
+ }
+
+ step.setOutputData(toJson(result));
+ taskStepDetailMapper.updateById(step);
+ }
+
+ /**
+ * 鍒涘缓姝ラ鎽樿
+ */
+ private Map<String, Object> createStepSummary(String deviceName, boolean success, String message) {
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("deviceName", deviceName);
+ summary.put("success", success);
+ summary.put("message", message);
+ return summary;
+ }
+
+ /**
+ * 瑙f瀽璁惧閫昏緫鍙傛暟
+ */
+ @SuppressWarnings("unchecked")
+ private Map<String, Object> parseLogicParams(DeviceConfig device) {
+ String extraParams = device.getExtraParams();
+ if (!StringUtils.hasText(extraParams)) {
+ return Collections.emptyMap();
+ }
+ try {
+ Map<String, Object> extraParamsMap = objectMapper.readValue(extraParams, MAP_TYPE);
+ Object deviceLogic = extraParamsMap.get("deviceLogic");
+ if (deviceLogic instanceof Map) {
+ return (Map<String, Object>) deviceLogic;
+ }
+ return Collections.emptyMap();
+ } catch (Exception e) {
+ log.warn("瑙f瀽璁惧閫昏緫鍙傛暟澶辫触: deviceId={}", device.getId(), e);
+ return Collections.emptyMap();
+ }
+ }
+
+ /**
+ * 骞惰鎵ц澶氫釜璁惧鎿嶄綔
+ */
+ private Pair<Boolean, String> executeParallel(MultiDeviceTask task,
+ List<DeviceConfig> devices,
+ TaskExecutionContext context,
+ List<Map<String, Object>> stepSummaries,
+ Integer maxConcurrent) {
+ int concurrency = maxConcurrent != null && maxConcurrent > 0
+ ? Math.min(maxConcurrent, devices.size())
+ : devices.size();
+
+ // 鍒涘缓鎵�鏈夋楠よ褰�
+ List<TaskStepDetail> steps = new ArrayList<>();
+ for (int i = 0; i < devices.size(); i++) {
+ DeviceConfig device = devices.get(i);
+ int order = i + 1;
+ TaskStepDetail step = createStepRecord(task, device, order);
+ steps.add(step);
+ }
+
+ // 浣跨敤淇″彿閲忔帶鍒跺苟鍙戞暟
+ Semaphore semaphore = new Semaphore(concurrency);
+ List<CompletableFuture<StepResult>> futures = new ArrayList<>();
+
+ for (int i = 0; i < devices.size(); i++) {
+ final int index = i;
+ final DeviceConfig device = devices.get(index);
+ final TaskStepDetail step = steps.get(index);
+
+ CompletableFuture<StepResult> future = CompletableFuture.supplyAsync(() -> {
+ try {
+ semaphore.acquire();
+ try {
+ return executeStep(task, step, device, context);
+ } finally {
+ semaphore.release();
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("骞惰鎵ц琚腑鏂�, deviceId={}", device.getId(), e);
+ return StepResult.failure(device.getDeviceName(), "鎵ц琚腑鏂�");
+ } catch (Exception e) {
+ log.error("骞惰鎵ц寮傚父, deviceId={}", device.getId(), e);
+ return StepResult.failure(device.getDeviceName(), e.getMessage());
+ }
+ }, executorService);
+
+ final int finalIndex = index;
+ future.whenComplete((result, throwable) -> {
+ if (throwable != null) {
+ log.error("骞惰鎵ц瀹屾垚鏃跺紓甯�, deviceId={}", device.getId(), throwable);
+ stepSummaries.set(finalIndex, StepResult.failure(device.getDeviceName(),
+ throwable.getMessage()).toSummary());
+ } else if (result != null) {
+ stepSummaries.set(finalIndex, result.toSummary());
+ }
+ });
+
+ futures.add(future);
+ }
+
+ // 绛夊緟鎵�鏈変换鍔″畬鎴�
+ CompletableFuture<Void> allFutures = CompletableFuture.allOf(
+ futures.toArray(new CompletableFuture[0])
+ );
+
+ try {
+ allFutures.get(30, TimeUnit.MINUTES); // 鏈�澶氱瓑寰�30鍒嗛挓
+ } catch (TimeoutException e) {
+ log.error("骞惰鎵ц瓒呮椂, taskId={}", task.getTaskId(), e);
+ return Pair.of(false, "浠诲姟鎵ц瓒呮椂");
+ } catch (Exception e) {
+ log.error("绛夊緟骞惰鎵ц瀹屾垚鏃跺紓甯�, taskId={}", task.getTaskId(), e);
+ return Pair.of(false, "绛夊緟鎵ц瀹屾垚鏃跺彂鐢熷紓甯�: " + e.getMessage());
+ }
+
+ // 妫�鏌ユ墍鏈夋楠ょ殑鎵ц缁撴灉
+ boolean allSuccess = true;
+ String firstFailureMessage = null;
+ for (int i = 0; i < futures.size(); i++) {
+ try {
+ StepResult result = futures.get(i).get();
+ if (result != null && !result.isSuccess()) {
+ allSuccess = false;
+ if (firstFailureMessage == null) {
+ firstFailureMessage = result.getMessage();
+ }
+ }
+ } catch (Exception e) {
+ log.error("鑾峰彇姝ラ鎵ц缁撴灉寮傚父, stepIndex={}", i, e);
+ allSuccess = false;
+ if (firstFailureMessage == null) {
+ firstFailureMessage = "鑾峰彇鎵ц缁撴灉寮傚父: " + e.getMessage();
+ }
+ }
+ }
+
+ return Pair.of(allSuccess, firstFailureMessage);
+ }
+
+ /**
+ * 纭畾鎵ц妯″紡
+ */
+ private String determineExecutionMode(DeviceGroupConfig groupConfig) {
+ if (groupConfig == null) {
+ return EXECUTION_MODE_SERIAL; // 榛樿涓茶
+ }
+
+ // 浠巈xtraConfig涓鍙杄xecutionMode
+ String extraConfig = groupConfig.getExtraConfig();
+ if (StringUtils.hasText(extraConfig)) {
+ try {
+ Map<String, Object> config = objectMapper.readValue(extraConfig, MAP_TYPE);
+ Object mode = config.get("executionMode");
+ if (mode != null) {
+ String modeStr = String.valueOf(mode).toUpperCase();
+ if (EXECUTION_MODE_PARALLEL.equals(modeStr) || EXECUTION_MODE_SERIAL.equals(modeStr)) {
+ return modeStr;
+ }
+ }
+ } catch (Exception e) {
+ log.warn("瑙f瀽璁惧缁勬墽琛屾ā寮忓け璐�, groupId={}", groupConfig.getId(), e);
+ }
+ }
+
+ // 濡傛灉鏈塵axConcurrentDevices涓斿ぇ浜�1锛岄粯璁や娇鐢ㄥ苟琛屾ā寮�
+ if (groupConfig.getMaxConcurrentDevices() != null && groupConfig.getMaxConcurrentDevices() > 1) {
+ return EXECUTION_MODE_PARALLEL;
+ }
+
+ return EXECUTION_MODE_SERIAL; // 榛樿涓茶
+ }
+
+ /**
+ * 鑾峰彇鏈�澶у苟鍙戣澶囨暟
+ */
+ private Integer getMaxConcurrentDevices(DeviceGroupConfig groupConfig) {
+ if (groupConfig == null) {
+ return 1;
+ }
+
+ // 浠巈xtraConfig涓鍙杕axConcurrent
+ String extraConfig = groupConfig.getExtraConfig();
+ if (StringUtils.hasText(extraConfig)) {
+ try {
+ Map<String, Object> config = objectMapper.readValue(extraConfig, MAP_TYPE);
+ Object maxConcurrent = config.get("maxConcurrent");
+ if (maxConcurrent instanceof Number) {
+ return ((Number) maxConcurrent).intValue();
+ }
+ } catch (Exception e) {
+ log.warn("瑙f瀽璁惧缁勬渶澶у苟鍙戞暟澶辫触, groupId={}", groupConfig.getId(), e);
+ }
+ }
+
+ // 浣跨敤瀹炰綋瀛楁
+ return groupConfig.getMaxConcurrentDevices() != null && groupConfig.getMaxConcurrentDevices() > 0
+ ? groupConfig.getMaxConcurrentDevices()
+ : 1;
+ }
+
+ /**
+ * 绠�鍗曠殑Pair绫荤敤浜庤繑鍥炰袱涓��
+ */
+ private static class Pair<T, U> {
+ private final T first;
+ private final U second;
+
+ private Pair(T first, U second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ public static <T, U> Pair<T, U> of(T first, U second) {
+ return new Pair<>(first, second);
+ }
+
+ public T getFirst() {
+ return first;
+ }
+
+ public U getSecond() {
+ return second;
+ }
+ }
+
+ /**
+ * 鍒嗘壒鎵ц澶ц溅璁惧鐜荤拑涓婃枡锛堝綋鐜荤拑ID鏁伴噺瓒呰繃6涓椂锛�
+ */
+ private StepResult executeLoadVehicleWithBatches(MultiDeviceTask task,
+ DeviceConfig device,
+ int order,
+ TaskExecutionContext context,
+ List<Map<String, Object>> stepSummaries) {
+ List<String> allGlassIds = context.getParameters().getGlassIds();
+ int batchSize = 6; // 姣忔壒鏈�澶�6涓幓鐠僆D
+
+ // 鍒嗘壒澶勭悊
+ int totalBatches = (allGlassIds.size() + batchSize - 1) / batchSize;
+ log.debug("澶ц溅璁惧鍒嗘壒涓婃枡: deviceId={}, totalGlassIds={}, batchSize={}, totalBatches={}",
+ device.getId(), allGlassIds.size(), batchSize, totalBatches);
+
+ for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
+ int startIndex = batchIndex * batchSize;
+ int endIndex = Math.min(startIndex + batchSize, allGlassIds.size());
+ List<String> batchGlassIds = allGlassIds.subList(startIndex, endIndex);
+
+ // 鍒涘缓涓存椂鍙傛暟锛屽彧鍖呭惈褰撳墠鎵规鐨勭幓鐠僆D
+ TaskParameters batchParams = new TaskParameters();
+ batchParams.setGlassIds(new ArrayList<>(batchGlassIds));
+ batchParams.setPositionCode(context.getParameters().getPositionCode());
+ batchParams.setPositionValue(context.getParameters().getPositionValue());
+
+ // 鍒涘缓涓存椂涓婁笅鏂�
+ TaskExecutionContext batchContext = new TaskExecutionContext(batchParams);
+
+ // 鍒涘缓姝ラ璁板綍
+ TaskStepDetail step = createStepRecord(task, device, order);
+ step.setStepName(step.getStepName() + String.format(" (鎵规 %d/%d)", batchIndex + 1, totalBatches));
+
+ // 鎵ц褰撳墠鎵规
+ StepResult stepResult = executeStep(task, step, device, batchContext);
+ stepSummaries.add(stepResult.toSummary());
+
+ if (!stepResult.isSuccess()) {
+ log.error("澶ц溅璁惧鍒嗘壒涓婃枡澶辫触: deviceId={}, batchIndex={}/{}, error={}",
+ device.getId(), batchIndex + 1, totalBatches, stepResult.getMessage());
+ return stepResult;
+ }
+
+ log.debug("澶ц溅璁惧鍒嗘壒涓婃枡鎴愬姛: deviceId={}, batchIndex={}/{}, glassIds={}",
+ device.getId(), batchIndex + 1, totalBatches, batchGlassIds);
+ }
+
+ // 鏇存柊涓婁笅鏂囦腑鐨勫凡鍔犺浇鐜荤拑ID
+ context.setLoadedGlassIds(new ArrayList<>(allGlassIds));
+
+ return StepResult.success(device.getDeviceName(), "鍒嗘壒涓婃枡瀹屾垚锛屽叡" + totalBatches + "鎵�");
}
private TaskStepDetail createStepRecord(MultiDeviceTask task, DeviceConfig device, int order) {
@@ -110,80 +1685,366 @@
TaskStepDetail step,
DeviceConfig device,
TaskExecutionContext context) {
+ DeviceCoordinationService.DependencyCheckResult dependencyResult =
+ deviceCoordinationService.checkDependencies(device, context);
+ if (!dependencyResult.isSatisfied()) {
+ log.warn("璁惧渚濊禆鏈弧瓒�: deviceId={}, message={}", device.getId(), dependencyResult.getMessage());
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(dependencyResult.getMessage());
+ step.setStartTime(new Date());
+ step.setEndTime(new Date());
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), dependencyResult.getMessage());
+ }
+ return executeStepWithRetry(task, step, device, context, getRetryPolicy(device));
+ }
+
+ /**
+ * 甯﹂噸璇曠殑姝ラ鎵ц
+ */
+ private StepResult executeStepWithRetry(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context,
+ RetryPolicy retryPolicy) {
Date startTime = new Date();
step.setStartTime(startTime);
step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ step.setRetryCount(0);
DeviceInteraction deviceInteraction = interactionRegistry.getInteraction(device.getDeviceType());
if (deviceInteraction != null) {
- return executeInteractionStep(task, step, device, context, deviceInteraction);
+ return executeInteractionStepWithRetry(task, step, device, context, deviceInteraction, retryPolicy);
}
Map<String, Object> params = buildOperationParams(device, context);
+ // 灏哻ontext寮曠敤鏀惧叆params锛屼緵璁惧澶勭悊鍣ㄤ娇鐢紙鐢ㄤ簬璁惧鍗忚皟锛�
+ params.put("_taskContext", context);
+ log.debug("executeStepWithRetry鏋勫缓鍙傛暟: deviceId={}, deviceType={}, operation={}, paramsKeys={}, params={}",
+ device.getId(), device.getDeviceType(), determineOperation(device, params), params.keySet(), params);
step.setInputData(toJson(params));
taskStepDetailMapper.updateById(step);
String operation = determineOperation(device, params);
DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
- DevicePlcVO.OperationResult result;
+
+ int retryAttempt = 0;
+ Exception lastException = null;
+
+ while (retryAttempt <= retryPolicy.getMaxRetryCount()) {
+ try {
+ if (retryAttempt > 0) {
+ // 閲嶈瘯鍓嶇瓑寰�
+ long waitTime = retryPolicy.calculateRetryInterval(retryAttempt);
+ log.debug("姝ラ鎵ц閲嶈瘯: deviceId={}, operation={}, retryAttempt={}/{}, waitTime={}ms",
+ device.getId(), operation, retryAttempt, retryPolicy.getMaxRetryCount(), waitTime);
+ Thread.sleep(waitTime);
+
+ // 鏇存柊姝ラ鐘舵��
+ step.setRetryCount(retryAttempt);
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ step.setStartTime(new Date());
+ taskStepDetailMapper.updateById(step);
+ }
+
+ DevicePlcVO.OperationResult result;
+ if (handler == null) {
+ result = deviceInteractionService.executeOperation(device.getId(), operation, params);
+ } else {
+ result = handler.execute(device, operation, params);
+ }
+
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateStepAfterOperation(step, result, opSuccess);
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+
+ // 閫氱煡姝ラ鏇存柊
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+
+ if (opSuccess) {
+ updateContextAfterSuccess(device, context, params, result);
+
+ // 鍚屾璁惧鐘舵��
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.COMPLETED, context);
+
+ return StepResult.success(device.getDeviceName(), result.getMessage());
+ } else {
+ // 涓氬姟澶辫触锛屽垽鏂槸鍚﹀彲閲嶈瘯
+ if (retryAttempt < retryPolicy.getMaxRetryCount() && isRetryableFailure(result)) {
+ retryAttempt++;
+ lastException = new RuntimeException(result.getMessage());
+ log.warn("姝ラ鎵ц澶辫触锛屽噯澶囬噸璇�: deviceId={}, operation={}, retryAttempt={}, message={}",
+ device.getId(), operation, retryAttempt, result.getMessage());
+ continue;
+ }
+
+ // 鍚屾澶辫触鐘舵��
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+
+ return StepResult.failure(device.getDeviceName(), result.getMessage());
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("姝ラ鎵ц琚腑鏂�, deviceId={}, operation={}", device.getId(), operation, e);
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage("鎵ц琚腑鏂�: " + e.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ step.setRetryCount(retryAttempt);
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), "鎵ц琚腑鏂�");
+ } catch (Exception e) {
+ lastException = e;
+ log.error("璁惧鎿嶄綔寮傚父, deviceId={}, operation={}, retryAttempt={}",
+ device.getId(), operation, retryAttempt, e);
+
+ // 鍒ゆ柇鏄惁鍙噸璇�
+ if (retryAttempt < retryPolicy.getMaxRetryCount() && retryPolicy.isRetryable(e)) {
+ retryAttempt++;
+ log.warn("姝ラ鎵ц寮傚父锛屽噯澶囬噸璇�: deviceId={}, operation={}, retryAttempt={}, exception={}",
+ device.getId(), operation, retryAttempt, e.getClass().getSimpleName());
+ continue;
+ }
+
+ // 涓嶅彲閲嶈瘯鎴栬揪鍒版渶澶ч噸璇曟鏁�
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(e.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ step.setRetryCount(retryAttempt);
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+
+ // 閫氱煡姝ラ鏇存柊
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+
+ // 鍚屾澶辫触鐘舵��
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+
+ String errorMsg = retryAttempt > 0
+ ? String.format("鎵ц澶辫触锛堝凡閲嶈瘯%d娆★級: %s", retryAttempt, e.getMessage())
+ : e.getMessage();
+ return StepResult.failure(device.getDeviceName(), errorMsg);
+ }
+ }
+
+ // 杈惧埌鏈�澶ч噸璇曟鏁�
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(lastException != null ? lastException.getMessage() : "鎵ц澶辫触");
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ step.setRetryCount(retryAttempt);
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+
+ // 閫氱煡姝ラ鏇存柊
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+
+ return StepResult.failure(device.getDeviceName(),
+ String.format("鎵ц澶辫触锛堝凡閲嶈瘯%d娆★級", retryAttempt));
+ }
+
+ /**
+ * 鎵ц涓�娆$畝鍗曠殑璁惧鎿嶄綔姝ラ锛堜笉璧颁氦浜掑紩鎿庯級锛岀敤浜庤Е鍙戣姹傜瓑鍦烘櫙
+ */
+ private StepResult executeDirectOperationStep(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context,
+ String operation,
+ Map<String, Object> params) {
+ Date startTime = new Date();
+ step.setStartTime(startTime);
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ step.setRetryCount(0);
+ step.setInputData(toJson(params));
+ taskStepDetailMapper.updateById(step);
try {
- if (handler == null) {
- result = deviceInteractionService.executeOperation(device.getId(), operation, params);
- } else {
- result = handler.execute(device, operation, params);
+ DeviceCoordinationService.DependencyCheckResult dependencyResult =
+ deviceCoordinationService.checkDependencies(device, context);
+ if (!dependencyResult.isSatisfied()) {
+ log.warn("鐩存帴鎿嶄綔渚濊禆鏈弧瓒�: deviceId={}, message={}", device.getId(), dependencyResult.getMessage());
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(dependencyResult.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), dependencyResult.getMessage());
}
+
+ DevicePlcVO.OperationResult result = deviceInteractionService.executeOperation(
+ device.getId(), operation, params);
boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
updateStepAfterOperation(step, result, opSuccess);
updateTaskProgress(task, step.getStepOrder(), opSuccess);
if (opSuccess) {
- updateContextAfterSuccess(device, context, params);
+ updateContextAfterSuccess(device, context, params, result);
+ // 绠�鍗曞悓姝ヨ澶囩姸鎬佷负宸插畬鎴�
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.COMPLETED, context);
return StepResult.success(device.getDeviceName(), result.getMessage());
+ } else {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ return StepResult.failure(device.getDeviceName(), result.getMessage());
}
- return StepResult.failure(device.getDeviceName(), result.getMessage());
} catch (Exception e) {
- log.error("璁惧鎿嶄綔寮傚父, deviceId={}, operation={}", device.getId(), operation, e);
+ log.error("鐩存帴璁惧鎿嶄綔寮傚父, deviceId={}, operation={}", device.getId(), operation, e);
step.setStatus(TaskStepDetail.Status.FAILED.name());
step.setErrorMessage(e.getMessage());
step.setEndTime(new Date());
step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
taskStepDetailMapper.updateById(step);
updateTaskProgress(task, step.getStepOrder(), false);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
return StepResult.failure(device.getDeviceName(), e.getMessage());
}
}
- private StepResult executeInteractionStep(MultiDeviceTask task,
- TaskStepDetail step,
- DeviceConfig device,
- TaskExecutionContext context,
- DeviceInteraction deviceInteraction) {
- try {
- InteractionContext interactionContext = new InteractionContext(device, context);
- step.setInputData(toJson(context.getParameters()));
- InteractionResult interactionResult = deviceInteraction.execute(interactionContext);
- boolean success = interactionResult != null && interactionResult.isSuccess();
- updateStepAfterInteraction(step, interactionResult);
- updateTaskProgress(task, step.getStepOrder(), success);
+ /**
+ * 甯﹂噸璇曠殑浜や簰姝ラ鎵ц
+ */
+ private StepResult executeInteractionStepWithRetry(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context,
+ DeviceInteraction deviceInteraction,
+ RetryPolicy retryPolicy) {
+ int retryAttempt = 0;
+ Exception lastException = null;
+
+ while (retryAttempt <= retryPolicy.getMaxRetryCount()) {
+ try {
+ if (retryAttempt > 0) {
+ long waitTime = retryPolicy.calculateRetryInterval(retryAttempt);
+ log.debug("浜や簰姝ラ鎵ц閲嶈瘯: deviceId={}, retryAttempt={}/{}, waitTime={}ms",
+ device.getId(), retryAttempt, retryPolicy.getMaxRetryCount(), waitTime);
+ Thread.sleep(waitTime);
+
+ step.setRetryCount(retryAttempt);
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+ step.setStartTime(new Date());
+ taskStepDetailMapper.updateById(step);
+ }
- if (success) {
- return StepResult.success(device.getDeviceName(), interactionResult.getMessage());
+ InteractionContext interactionContext = new InteractionContext(device, context);
+ step.setInputData(toJson(context.getParameters()));
+ InteractionResult interactionResult = deviceInteraction.execute(interactionContext);
+ boolean success = interactionResult != null && interactionResult.isSuccess();
+ updateStepAfterInteraction(step, interactionResult);
+ updateTaskProgress(task, step.getStepOrder(), success);
+
+ if (success) {
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.COMPLETED, context);
+ return StepResult.success(device.getDeviceName(), interactionResult.getMessage());
+ } else {
+ if (retryAttempt < retryPolicy.getMaxRetryCount()) {
+ retryAttempt++;
+ continue;
+ }
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+ String message = interactionResult != null ? interactionResult.getMessage() : "浜や簰鎵ц澶辫触";
+ return StepResult.failure(device.getDeviceName(), message);
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ log.error("浜や簰姝ラ鎵ц琚腑鏂�, deviceId={}", device.getId(), e);
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage("鎵ц琚腑鏂�: " + e.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ step.setRetryCount(retryAttempt);
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), "鎵ц琚腑鏂�");
+ } catch (Exception e) {
+ lastException = e;
+ log.error("浜や簰鎵ц寮傚父, deviceId={}, retryAttempt={}", device.getId(), retryAttempt, e);
+
+ if (retryAttempt < retryPolicy.getMaxRetryCount() && retryPolicy.isRetryable(e)) {
+ retryAttempt++;
+ continue;
+ }
+
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(e.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ step.setRetryCount(retryAttempt);
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+
+ // 閫氱煡姝ラ鏇存柊
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+
+ String errorMsg = retryAttempt > 0
+ ? String.format("鎵ц澶辫触锛堝凡閲嶈瘯%d娆★級: %s", retryAttempt, e.getMessage())
+ : e.getMessage();
+ return StepResult.failure(device.getDeviceName(), errorMsg);
}
- String message = interactionResult != null ? interactionResult.getMessage() : "浜や簰鎵ц澶辫触";
- return StepResult.failure(device.getDeviceName(), message);
- } catch (Exception e) {
- log.error("浜や簰鎵ц寮傚父, deviceId={}", device.getId(), e);
- step.setStatus(TaskStepDetail.Status.FAILED.name());
- step.setErrorMessage(e.getMessage());
- step.setEndTime(new Date());
- step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
- taskStepDetailMapper.updateById(step);
- updateTaskProgress(task, step.getStepOrder(), false);
- return StepResult.failure(device.getDeviceName(), e.getMessage());
}
+
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(lastException != null ? lastException.getMessage() : "浜や簰鎵ц澶辫触");
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ step.setRetryCount(retryAttempt);
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+
+ // 閫氱煡姝ラ鏇存柊
+ notificationService.notifyStepUpdate(task.getTaskId(), step);
+
+ deviceCoordinationService.syncDeviceStatus(device,
+ DeviceCoordinationService.DeviceStatus.FAILED, context);
+
+ return StepResult.failure(device.getDeviceName(),
+ String.format("鎵ц澶辫触锛堝凡閲嶈瘯%d娆★級", retryAttempt));
}
+
+ /**
+ * 鑾峰彇閲嶈瘯绛栫暐
+ */
+ private RetryPolicy getRetryPolicy(DeviceConfig device) {
+ // 鍙互浠庤澶囬厤缃腑璇诲彇閲嶈瘯绛栫暐
+ // 鏆傛椂浣跨敤榛樿绛栫暐
+ return RetryPolicy.defaultPolicy();
+ }
+
+ /**
+ * 鍒ゆ柇涓氬姟澶辫触鏄惁鍙噸璇�
+ */
+ private boolean isRetryableFailure(DevicePlcVO.OperationResult result) {
+ if (result == null || result.getMessage() == null) {
+ return false;
+ }
+ String message = result.getMessage().toLowerCase();
+ // 缃戠粶閿欒銆佽秴鏃堕敊璇彲閲嶈瘯
+ return message.contains("timeout") ||
+ message.contains("connection") ||
+ message.contains("缃戠粶") ||
+ message.contains("瓒呮椂");
+ }
+
private void updateStepAfterOperation(TaskStepDetail step,
DevicePlcVO.OperationResult result,
@@ -193,7 +2054,15 @@
step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
}
step.setStatus(success ? TaskStepDetail.Status.COMPLETED.name() : TaskStepDetail.Status.FAILED.name());
- step.setErrorMessage(success ? null : result.getMessage());
+ // 璁剧疆娑堟伅锛氭垚鍔熸椂濡傛灉鏈夋秷鎭篃淇濆瓨锛屽け璐ユ椂淇濆瓨閿欒娑堟伅
+ String message = result != null ? result.getMessage() : null;
+ if (success) {
+ // 鎴愬姛鏃讹紝濡傛灉鏈夋秷鎭垯淇濆瓨锛堢敤浜庢彁绀轰俊鎭級锛屽惁鍒欐竻绌�
+ step.setErrorMessage(StringUtils.hasText(message) ? message : null);
+ } else {
+ // 澶辫触鏃朵繚瀛橀敊璇秷鎭�
+ step.setErrorMessage(message);
+ }
step.setOutputData(toJson(result));
taskStepDetailMapper.updateById(step);
}
@@ -212,17 +2081,48 @@
}
private void updateTaskProgress(MultiDeviceTask task, int currentStep, boolean success) {
- task.setCurrentStep(currentStep);
if (!success) {
task.setStatus(MultiDeviceTask.Status.FAILED.name());
}
+
+ // 璁$畻宸插畬鎴愮殑姝ラ鏁帮紙鐢ㄤ簬杩涘害鏄剧ず锛�
+ int completedSteps = countCompletedSteps(task.getTaskId());
+ int progressStep = success
+ ? completedSteps
+ : Math.max(completedSteps, currentStep); // 澶辫触鏃惰嚦灏戞樉绀哄綋鍓嶆楠�
+
LambdaUpdateWrapper<MultiDeviceTask> update = Wrappers.<MultiDeviceTask>lambdaUpdate()
.eq(MultiDeviceTask::getId, task.getId())
- .set(MultiDeviceTask::getCurrentStep, currentStep);
+ .set(MultiDeviceTask::getCurrentStep, progressStep);
if (!success) {
update.set(MultiDeviceTask::getStatus, MultiDeviceTask.Status.FAILED.name());
}
multiDeviceTaskMapper.update(null, update);
+
+ // 鏇存柊浠诲姟瀵硅薄鐨勮繘搴︼紝鐢ㄤ簬閫氱煡
+ task.setCurrentStep(progressStep);
+
+ // 閫氱煡浠诲姟鐘舵�佹洿鏂帮紙鍖呭惈杩涘害淇℃伅锛�
+ notificationService.notifyTaskStatus(task);
+ }
+
+ /**
+ * 缁熻宸插畬鎴愮殑姝ラ鏁�
+ */
+ private int countCompletedSteps(String taskId) {
+ if (taskId == null) {
+ return 0;
+ }
+ try {
+ return taskStepDetailMapper.selectCount(
+ Wrappers.<TaskStepDetail>lambdaQuery()
+ .eq(TaskStepDetail::getTaskId, taskId)
+ .eq(TaskStepDetail::getStatus, TaskStepDetail.Status.COMPLETED.name())
+ ).intValue();
+ } catch (Exception e) {
+ log.warn("缁熻宸插畬鎴愭楠ゆ暟澶辫触: taskId={}", taskId, e);
+ return 0;
+ }
}
private String determineOperation(DeviceConfig device, Map<String, Object> params) {
@@ -262,19 +2162,21 @@
params.put("processType", taskParams.getProcessType() != null ? taskParams.getProcessType() : 1);
params.put("triggerRequest", true);
break;
- case DeviceConfig.DeviceType.GLASS_STORAGE:
- List<String> processed = context.getSafeProcessedGlassIds();
- if (CollectionUtils.isEmpty(processed)) {
- processed = context.getSafeLoadedGlassIds();
+ case DeviceConfig.DeviceType.WORKSTATION_SCANNER:
+ // 鍗ц浆绔嬫壂鐮佽澶囷細浠庝换鍔″弬鏁颁腑鑾峰彇鐜荤拑ID鍒楄〃锛屽彇绗竴涓綔涓哄綋鍓嶈娴嬭瘯鐨勭幓鐠僆D
+ // 娉ㄦ剰锛氭壂鐮佽澶囬�氬父閫氳繃瀹氭椂鍣ㄦ墽琛岋紝浣嗗鏋滈�氳繃executeStep鎵ц锛屼篃闇�瑕佷紶閫抔lassId
+ log.debug("buildOperationParams澶勭悊鎵爜璁惧: deviceId={}, taskParams.glassIds={}, isEmpty={}",
+ device.getId(), taskParams.getGlassIds(),
+ CollectionUtils.isEmpty(taskParams.getGlassIds()));
+ if (!CollectionUtils.isEmpty(taskParams.getGlassIds())) {
+ params.put("glassId", taskParams.getGlassIds().get(0));
+ params.put("glassIds", new ArrayList<>(taskParams.getGlassIds()));
+ log.debug("buildOperationParams涓烘壂鐮佽澶囨坊鍔爂lassId: deviceId={}, glassId={}, glassIdsSize={}",
+ device.getId(), taskParams.getGlassIds().get(0), taskParams.getGlassIds().size());
+ } else {
+ log.warn("buildOperationParams鎵爜璁惧glassIds涓虹┖: deviceId={}, taskParams.glassIds={}, taskParams={}",
+ device.getId(), taskParams.getGlassIds(), taskParams);
}
- if (!CollectionUtils.isEmpty(processed)) {
- params.put("glassId", processed.get(0));
- params.put("glassIds", new ArrayList<>(processed));
- }
- if (taskParams.getStoragePosition() != null) {
- params.put("storagePosition", taskParams.getStoragePosition());
- }
- params.put("triggerRequest", true);
break;
default:
if (!CollectionUtils.isEmpty(taskParams.getExtra())) {
@@ -301,16 +2203,53 @@
private void updateContextAfterSuccess(DeviceConfig device,
TaskExecutionContext context,
- Map<String, Object> params) {
+ Map<String, Object> params,
+ DevicePlcVO.OperationResult result) {
+ List<String> glassIds = extractGlassIds(params);
+
switch (device.getDeviceType()) {
+ case DeviceConfig.DeviceType.WORKSTATION_SCANNER:
+ handleScannerSuccess(context, result);
+ break;
case DeviceConfig.DeviceType.LOAD_VEHICLE:
- context.setLoadedGlassIds(extractGlassIds(params));
+ context.setLoadedGlassIds(glassIds);
+ // 鏁版嵁浼犻�掞細澶ц溅璁惧 -> 涓嬩竴涓澶�
+ if (!CollectionUtils.isEmpty(glassIds)) {
+ Map<String, Object> transferData = new HashMap<>();
+ transferData.put("glassIds", glassIds);
+ transferData.put("sourceDevice", device.getDeviceCode());
+ // 杩欓噷绠�鍖栧鐞嗭紝瀹為檯搴旇鎵惧埌涓嬩竴涓澶�
+ // 鍦ㄤ覆琛屾ā寮忎笅锛屼笅涓�涓澶囦細鍦ㄥ惊鐜腑鑷姩鑾峰彇
+ }
break;
case DeviceConfig.DeviceType.LARGE_GLASS:
- context.setProcessedGlassIds(extractGlassIds(params));
+ context.setProcessedGlassIds(glassIds);
+ // 鏁版嵁浼犻�掞細澶х悊鐗� -> 涓嬩竴涓澶�
+ if (!CollectionUtils.isEmpty(glassIds)) {
+ Map<String, Object> transferData = new HashMap<>();
+ transferData.put("glassIds", glassIds);
+ transferData.put("sourceDevice", device.getDeviceCode());
+ }
break;
default:
break;
+ }
+ }
+
+ private void handleScannerSuccess(TaskExecutionContext context,
+ DevicePlcVO.OperationResult result) {
+ List<String> scannerGlassIds = extractGlassIdsFromResult(result);
+ if (CollectionUtils.isEmpty(scannerGlassIds)) {
+ String workLine = resolveWorkLineFromResult(result, context.getParameters());
+ scannerGlassIds = glassInfoService.getRecentScannedGlassIds(
+ SCANNER_LOOKBACK_MINUTES, SCANNER_LOOKBACK_LIMIT, workLine);
+ }
+ if (!CollectionUtils.isEmpty(scannerGlassIds)) {
+ context.getParameters().setGlassIds(new ArrayList<>(scannerGlassIds));
+ context.setLoadedGlassIds(new ArrayList<>(scannerGlassIds));
+ log.debug("鍗ц浆绔嬫壂鐮佽幏鍙栧埌鐜荤拑ID: {}", scannerGlassIds);
+ } else {
+ log.warn("鍗ц浆绔嬫壂鐮佹湭鑾峰彇鍒扮幓鐠僆D锛屽悗缁澶囧彲鑳芥棤娉曟墽琛�");
}
}
@@ -331,6 +2270,45 @@
return Collections.emptyList();
}
+ @SuppressWarnings("unchecked")
+ private List<String> extractGlassIdsFromResult(DevicePlcVO.OperationResult result) {
+ if (result == null || result.getData() == null) {
+ return Collections.emptyList();
+ }
+ Object data = result.getData().get("glassIds");
+ if (data instanceof List) {
+ List<Object> raw = (List<Object>) data;
+ List<String> converted = new ArrayList<>();
+ for (Object item : raw) {
+ if (item != null) {
+ converted.add(String.valueOf(item));
+ }
+ }
+ return converted;
+ }
+ if (data instanceof String && StringUtils.hasText((String) data)) {
+ return Collections.singletonList((String) data);
+ }
+ return Collections.emptyList();
+ }
+
+ private String resolveWorkLineFromResult(DevicePlcVO.OperationResult result,
+ TaskParameters parameters) {
+ if (result != null && result.getData() != null) {
+ Object workLine = result.getData().get("workLine");
+ if (workLine != null && StringUtils.hasText(String.valueOf(workLine))) {
+ return String.valueOf(workLine);
+ }
+ }
+ if (parameters != null && !CollectionUtils.isEmpty(parameters.getExtra())) {
+ Object extraWorkLine = parameters.getExtra().get("workLine");
+ if (extraWorkLine != null) {
+ return String.valueOf(extraWorkLine);
+ }
+ }
+ return null;
+ }
+
private String toJson(Object value) {
try {
return objectMapper.writeValueAsString(value);
--
Gitblit v1.8.0