package com.mes.device.service.impl; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.mes.device.entity.DeviceConfig; import com.mes.device.service.DeviceConfigService; import com.mes.device.service.DeviceGroupRelationService; import com.mes.device.service.DevicePlcOperationService; import com.mes.device.util.ConfigJsonHelper; import com.mes.device.vo.DeviceGroupVO; import com.mes.device.vo.DevicePlcVO; import com.mes.service.PlcTestWriteService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; /** * 设备 PLC 操作服务实现 * * @author mes * @since 2025-11-17 */ @Slf4j @Service @RequiredArgsConstructor public class DevicePlcOperationServiceImpl implements DevicePlcOperationService { private static final String PLC_PROJECT_ID_KEY = "plcProjectId"; private final DeviceConfigService deviceConfigService; private final DeviceGroupRelationService deviceGroupRelationService; private final PlcTestWriteService plcTestWriteService; private final ObjectMapper objectMapper; @Override public DevicePlcVO.OperationResult triggerRequest(Long deviceId) { return executeOperation(deviceId, PlcOperationType.REQUEST); } @Override public List triggerRequest(List deviceIds) { return executeBatch(deviceIds, PlcOperationType.REQUEST); } @Override public List triggerRequestByGroup(Long groupId) { return executeBatch(getDeviceIdsByGroup(groupId), PlcOperationType.REQUEST); } @Override public DevicePlcVO.OperationResult triggerReport(Long deviceId) { return executeOperation(deviceId, PlcOperationType.REPORT); } @Override public List triggerReport(List deviceIds) { return executeBatch(deviceIds, PlcOperationType.REPORT); } @Override public List triggerReportByGroup(Long groupId) { return executeBatch(getDeviceIdsByGroup(groupId), PlcOperationType.REPORT); } @Override public DevicePlcVO.OperationResult resetDevice(Long deviceId) { return executeOperation(deviceId, PlcOperationType.RESET); } @Override public List resetDevices(List deviceIds) { return executeBatch(deviceIds, PlcOperationType.RESET); } @Override public DevicePlcVO.StatusInfo readStatus(Long deviceId) { DeviceConfig device = deviceConfigService.getDeviceById(deviceId); if (device == null) { return DevicePlcVO.StatusInfo.builder() .deviceId(deviceId) .deviceName("未知设备") .data(Collections.emptyMap()) .timestamp(LocalDateTime.now()) .build(); } try { Map data = plcTestWriteService.readPlcStatusByDevice(deviceId); return DevicePlcVO.StatusInfo.builder() .deviceId(device.getId()) .deviceName(device.getDeviceName()) .deviceCode(device.getDeviceCode()) .projectId(String.valueOf(device.getProjectId())) .data(data) .timestamp(LocalDateTime.now()) .build(); } catch (Exception e) { log.error("读取设备 PLC 状态失败, deviceId={}", deviceId, e); return DevicePlcVO.StatusInfo.builder() .deviceId(device.getId()) .deviceName(device.getDeviceName()) .deviceCode(device.getDeviceCode()) .projectId(null) .data(Collections.emptyMap()) .timestamp(LocalDateTime.now()) .build(); } } @Override public List readStatusByGroup(Long groupId) { List deviceIds = getDeviceIdsByGroup(groupId); if (CollectionUtils.isEmpty(deviceIds)) { return Collections.emptyList(); } return deviceIds.stream() .map(this::readStatus) .collect(Collectors.toList()); } private List executeBatch(List deviceIds, PlcOperationType type) { if (CollectionUtils.isEmpty(deviceIds)) { return Collections.emptyList(); } return deviceIds.stream() .filter(Objects::nonNull) .distinct() .map(id -> executeOperation(id, type)) .collect(Collectors.toList()); } private DevicePlcVO.OperationResult executeOperation(Long deviceId, PlcOperationType type) { DeviceConfig device = deviceConfigService.getDeviceById(deviceId); if (device == null) { return buildResult(deviceId, null, null, type, false, "设备不存在"); } try { boolean success = invokeOperation(type, deviceId); String message = success ? type.successMsg : type.failedMsg; return buildResult(device.getId(), device, String.valueOf(device.getProjectId()), type, success, message); } catch (Exception e) { log.error("执行 PLC 操作失败, deviceId={}, operation={}", deviceId, type, e); return buildResult(device.getId(), device, null, type, false, e.getMessage()); } } private boolean invokeOperation(PlcOperationType type, Long deviceId) { switch (type) { case REQUEST: return plcTestWriteService.simulatePlcRequestByDevice(deviceId); case REPORT: return plcTestWriteService.simulatePlcReportByDevice(deviceId); case RESET: return plcTestWriteService.resetPlcByDevice(deviceId); default: return false; } } private DevicePlcVO.OperationResult buildResult(Long deviceId, DeviceConfig device, String projectId, PlcOperationType type, boolean success, String message) { return DevicePlcVO.OperationResult.builder() .deviceId(deviceId) .deviceName(device != null ? device.getDeviceName() : "未知设备") .deviceCode(device != null ? device.getDeviceCode() : null) .projectId(projectId) .operation(type.display) .success(success) .message(message) .timestamp(LocalDateTime.now()) .build(); } private List getDeviceIdsByGroup(Long groupId) { if (groupId == null) { return Collections.emptyList(); } try { List devices = deviceGroupRelationService.getGroupDevices(groupId); if (CollectionUtils.isEmpty(devices)) { return Collections.emptyList(); } return devices.stream() .map(DeviceGroupVO.DeviceInfo::getId) .filter(Objects::nonNull) .collect(Collectors.toList()); } catch (Exception e) { log.error("获取设备组设备失败, groupId={}", groupId, e); return Collections.emptyList(); } } @Override public DevicePlcVO.OperationResult writeFields(Long deviceId, Map fieldValues, String operationName) { DeviceConfig device = deviceConfigService.getDeviceById(deviceId); if (device == null) { return buildResult(deviceId, null, null, PlcOperationType.REQUEST, false, "设备不存在"); } try { boolean success = plcTestWriteService.writeFieldsByDevice(deviceId, fieldValues); String opName = operationName != null ? operationName : "PLC写入"; return DevicePlcVO.OperationResult.builder() .deviceId(device.getId()) .deviceName(device.getDeviceName()) .deviceCode(device.getDeviceCode()) .projectId(String.valueOf(device.getProjectId())) .operation(opName) .success(success) .message(success ? opName + "成功" : opName + "失败") .timestamp(LocalDateTime.now()) .build(); } catch (Exception e) { log.error("写入PLC字段失败, deviceId={}", deviceId, e); return DevicePlcVO.OperationResult.builder() .deviceId(device.getId()) .deviceName(device.getDeviceName()) .deviceCode(device.getDeviceCode()) .projectId(null) .operation(operationName) .success(false) .message(e.getMessage()) .timestamp(LocalDateTime.now()) .build(); } } @Override public String resolveProjectId(Long deviceId) { DeviceConfig device = deviceConfigService.getDeviceById(deviceId); if (device == null) { throw new IllegalArgumentException("设备不存在: " + deviceId); } return resolveProjectId(device); } private String resolveProjectId(DeviceConfig device) { if (device == null) { throw new IllegalArgumentException("设备信息为空"); } // 优先从configJson中获取 Map configParams = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper); Object plcProjectId = configParams.get(PLC_PROJECT_ID_KEY); if (plcProjectId != null) { return String.valueOf(plcProjectId); } // 其次从扩展参数中获取(兼容旧配置) String extra = device.getExtraParams(); if (extra != null && !extra.isEmpty()) { try { Map extraParams = objectMapper.readValue(extra, new TypeReference>() {}); Object plcProjectIdFromExtra = extraParams.get(PLC_PROJECT_ID_KEY); if (plcProjectIdFromExtra != null) { return String.valueOf(plcProjectIdFromExtra); } } catch (Exception e) { log.warn("解析设备扩展参数失败, deviceId={}", device.getId(), e); } } if (device.getProjectId() != null) { return String.valueOf(device.getProjectId()); } if (device.getDeviceCode() != null && !device.getDeviceCode().isEmpty()) { return device.getDeviceCode(); } throw new IllegalStateException("无法解析设备的 PLC 项目标识, deviceId=" + device.getId()); } public enum PlcOperationType { REQUEST("PLC请求", "PLC 请求发送成功", "PLC 请求发送失败"), REPORT("PLC汇报", "PLC 汇报模拟成功", "PLC 汇报模拟失败"), RESET("PLC重置", "PLC 状态已重置", "PLC 状态重置失败"); private final String display; private final String successMsg; private final String failedMsg; PlcOperationType(String display, String successMsg, String failedMsg) { this.display = display; this.successMsg = successMsg; this.failedMsg = failedMsg; } } }