调用mes导入工程参数修改,Excel表数据流程卡号一致;增加读取层号/工程号方法;
工程号一致覆盖更新
1个文件已删除
29个文件已修改
1201 ■■■■■ 已修改文件
mes-common/serverBase/src/main/java/com/mes/s7/enhanced/EnhancedS7Serializer.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/ARCHITECTURE.md 536 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceGroupController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceInteractionController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceStatusController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceStatus.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceConfigRequest.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGlassFeedRequest.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGroupRequest.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceConfigServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceInteractionServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceStatusServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/coordination/VehicleCoordinationService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/flow/LoadVehicleInteraction.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java 80 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/transfer/handler/HorizontalTransferLogicHandler.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/s7/provider/S7SerializerProvider.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/api/device/deviceManagement.js 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/device/DeviceConfigForm.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/device/DeviceConfigList.vue 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/device/DeviceGroupList.vue 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-common/serverBase/src/main/java/com/mes/s7/enhanced/EnhancedS7Serializer.java
@@ -487,7 +487,25 @@
    private void extractField(S7ParseData item, Object data) {
        switch (item.getDataType()) {
            case BOOL:
                item.setDataItem(DataItem.createReqByBoolean((Boolean) data));
                // 类型转换容错:支持 Integer/Number 转 Boolean
                Boolean boolValue;
                if (data instanceof Boolean) {
                    boolValue = (Boolean) data;
                } else if (data instanceof Number) {
                    boolValue = ((Number) data).intValue() != 0;
                } else if (data instanceof String) {
                    String str = ((String) data).trim().toLowerCase();
                    boolValue = "true".equals(str) || "1".equals(str) || "on".equals(str);
                } else {
                    // 尝试转换为数字再转Boolean
                    try {
                        int intValue = Integer.parseInt(String.valueOf(data));
                        boolValue = intValue != 0;
                    } catch (NumberFormatException e) {
                        throw new S7CommException("无法将值转换为Boolean: " + data + " (类型: " + (data != null ? data.getClass().getName() : "null") + ")");
                    }
                }
                item.setDataItem(DataItem.createReqByBoolean(boolValue));
                break;
            case BYTE:
                item.setDataItem(DataItem.createReqByByte(ByteReadBuff.newInstance((byte[]) data)
mes-processes/mes-plcSend/ARCHITECTURE.md
File was deleted
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java
@@ -82,11 +82,11 @@
                return Result.error("设备配置数据格式错误");
            }
            
            deviceConfig.setId(request.getDeviceId());
            deviceConfig.setId(request.getId());
            boolean success = deviceConfigService.updateDevice(deviceConfig);
            if (success) {
                // 更新成功后,重新获取设备对象
                DeviceConfig updated = deviceConfigService.getDeviceById(request.getDeviceId());
                DeviceConfig updated = deviceConfigService.getDeviceById(request.getId());
                return Result.success(updated);
            } else {
                return Result.error("设备配置不存在");
@@ -105,7 +105,7 @@
    public Result<Void> deleteDevice(
            @Valid @RequestBody DeviceConfigRequest request) {
        try {
            deviceConfigService.deleteDevice(request.getDeviceId());
            deviceConfigService.deleteDevice(request.getId());
            return Result.success(null);
        } catch (Exception e) {
            log.error("删除设备配置失败", e);
@@ -121,7 +121,7 @@
    public Result<DeviceConfig> getDeviceById(
            @Valid @RequestBody DeviceConfigRequest request) {
        try {
            DeviceConfig device = deviceConfigService.getDeviceById(request.getDeviceId());
            DeviceConfig device = deviceConfigService.getDeviceById(request.getId());
            return Result.success(device);
        } catch (Exception e) {
            log.error("获取设备配置失败", e);
@@ -159,7 +159,7 @@
    public Result<Void> enableDevice(
            @Valid @RequestBody DeviceConfigRequest request) {
        try {
            deviceConfigService.enableDevice(request.getDeviceId());
            deviceConfigService.enableDevice(request.getId());
            return Result.success(null);
        } catch (Exception e) {
            log.error("启用设备失败", e);
@@ -175,7 +175,7 @@
    public Result<Void> disableDevice(
            @Valid @RequestBody DeviceConfigRequest request) {
        try {
            deviceConfigService.disableDevice(request.getDeviceId());
            deviceConfigService.disableDevice(request.getId());
            return Result.success(null);
        } catch (Exception e) {
            log.error("禁用设备失败", e);
@@ -239,7 +239,7 @@
    public Result<Boolean> checkDeviceCodeExists(
            @ApiParam("设备配置请求") @RequestBody DeviceConfigRequest request) {
        try {
            boolean exists = deviceConfigService.isDeviceCodeExists(request.getDeviceCode(), request.getDeviceId());
            boolean exists = deviceConfigService.isDeviceCodeExists(request.getDeviceCode(), request.getId());
            return Result.success(exists);
        } catch (Exception e) {
            log.error("检查设备编码失败", e);
@@ -301,7 +301,7 @@
    public Result<DeviceConfigVO.HealthCheckResult> performHealthCheck(
            @Valid @RequestBody DeviceConfigRequest request) {
        try {
            DeviceConfigVO.HealthCheckResult result = deviceConfigService.performHealthCheck(request.getDeviceId());
            DeviceConfigVO.HealthCheckResult result = deviceConfigService.performHealthCheck(request.getId());
            return Result.success(result);
        } catch (Exception e) {
            log.error("设备健康检查失败", e);
@@ -312,7 +312,7 @@
    /**
     * 测试设备PLC连接
     * 支持两种方式:
     * 1. 传入 deviceId,根据已保存的设备配置测试
     * 1. 传入 id,根据已保存的设备配置测试
     * 2. 直接传入 plcIp / plcPort / timeout 进行一次性测试
     */
    @PostMapping("/devices/test-connection")
@@ -323,15 +323,15 @@
            Integer plcPort = null;
            Integer timeoutMs = null;
            // 优先根据 deviceId 读取已保存配置
            Object deviceIdObj = body.get("deviceId");
            if (deviceIdObj != null) {
                Long deviceId = deviceIdObj instanceof Number
                        ? ((Number) deviceIdObj).longValue()
                        : Long.parseLong(deviceIdObj.toString());
                DeviceConfig device = deviceConfigService.getDeviceById(deviceId);
            // 优先根据 id 读取已保存配置
            Object idObj = body.get("id");
            if (idObj != null) {
                Long id = idObj instanceof Number
                        ? ((Number) idObj).longValue()
                        : Long.parseLong(idObj.toString());
                DeviceConfig device = deviceConfigService.getDeviceById(id);
                if (device == null) {
                    return Result.error("设备不存在: " + deviceId);
                    return Result.error("设备不存在: " + id);
                }
                plcIp = device.getPlcIp();
                plcPort = device.getPlcPort();
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceGroupController.java
@@ -294,7 +294,7 @@
    public Result<Void> addDeviceToGroup(
            @Valid @RequestBody DeviceGroupRequest request) {
        try {
            deviceGroupRelationService.addDeviceToGroup(request.getGroupId(), request.getDeviceId(),
            deviceGroupRelationService.addDeviceToGroup(request.getGroupId(), request.getId(),
                request.getDeviceRole() != null ? request.getDeviceRole() : "MEMBER");
            return Result.success(null);
        } catch (Exception e) {
@@ -311,7 +311,7 @@
    public Result<Void> removeDeviceFromGroup(
            @Valid @RequestBody DeviceGroupRequest request) {
        try {
            deviceGroupRelationService.removeDeviceFromGroup(request.getGroupId(), request.getDeviceId());
            deviceGroupRelationService.removeDeviceFromGroup(request.getGroupId(), request.getId());
            return Result.success(null);
        } catch (Exception e) {
            log.error("从设备组移除设备失败", e);
@@ -327,7 +327,7 @@
    public Result<Void> updateDeviceRole(
            @Valid @RequestBody DeviceGroupRequest request) {
        try {
            deviceGroupRelationService.updateDeviceRole(request.getGroupId(), request.getDeviceId(),
            deviceGroupRelationService.updateDeviceRole(request.getGroupId(), request.getId(),
                request.getDeviceRole());
            return Result.success(null);
        } catch (Exception e) {
@@ -360,7 +360,7 @@
    public Result<List<DeviceGroupVO.GroupInfo>> getDeviceGroups(
            @Valid @RequestBody DeviceGroupRequest request) {
        try {
            List<DeviceGroupVO.GroupInfo> groups = deviceGroupRelationService.getDeviceGroups(request.getDeviceId());
            List<DeviceGroupVO.GroupInfo> groups = deviceGroupRelationService.getDeviceGroups(request.getId());
            return Result.success(groups);
        } catch (Exception e) {
            log.error("获取设备设备组列表失败", e);
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceInteractionController.java
@@ -37,7 +37,7 @@
    public Result<DevicePlcVO.OperationResult> executeOperation(
            @Valid @RequestBody DeviceOperationRequest request) {
        return Result.success(deviceInteractionService.executeOperation(
                request.getDeviceId(),
                request.getId(),
                request.getOperation(),
                request.getParams()
        ));
@@ -50,7 +50,7 @@
    public static class DeviceOperationRequest {
        @NotNull(message = "设备ID不能为空")
        @ApiParam(value = "设备ID", required = true)
        private Long deviceId;
        private Long id;
        @NotNull(message = "操作类型不能为空")
        @ApiParam(value = "操作类型(如:feedGlass, triggerRequest, triggerReport等)", required = true)
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceStatusController.java
@@ -34,7 +34,7 @@
            @Valid @RequestBody DeviceStatusUpdateRequest request) {
        try {
            boolean success = deviceStatusService.updateDeviceOnlineStatus(
                    request.getDeviceId(),
                    request.getId(),
                    request.getStatus()
            );
            if (success) {
@@ -104,7 +104,7 @@
    public static class DeviceStatusUpdateRequest {
        @NotNull(message = "设备ID不能为空")
        @ApiParam(value = "设备配置ID", required = true)
        private Long deviceId;
        private Long id;
        @NotEmpty(message = "设备状态不能为空")
        @ApiParam(value = "设备状态(ONLINE/OFFLINE/BUSY/ERROR/MAINTENANCE)", required = true)
@@ -131,7 +131,7 @@
    @Data
    public static class DeviceHeartbeatRequest {
        @NotEmpty(message = "设备ID不能为空")
        @ApiParam(value = "设备ID(device_config.device_id)", required = true)
        @ApiParam(value = "设备ID(DeviceConfig.id的字符串形式,对应device_status.device_id)", required = true)
        private String deviceId;
        @ApiParam(value = "设备状态(可选,默认为ONLINE)")
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java
@@ -23,10 +23,6 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "设备唯一标识", example = "DEVICE_001")
    @TableField("device_id")
    private String deviceId;
    @ApiModelProperty(value = "设备名称", example = "大车设备1")
    @TableField("device_name")
    private String deviceName;
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceStatus.java
@@ -25,7 +25,7 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "设备ID(device_config.device_id)", example = "DEVICE_001")
    @ApiModelProperty(value = "设备ID(DeviceConfig.id的字符串形式)", example = "1")
    @TableField("device_id")
    private String deviceId;
mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java
@@ -62,8 +62,8 @@
            "CASE WHEN ds.status = 'ONLINE' THEN TRUE ELSE FALSE END as isOnline " +
            "FROM device_config d " +
            "INNER JOIN device_group_relation dgr ON d.id = dgr.device_id " +
            "LEFT JOIN device_status ds ON d.device_id = ds.device_id " +
            "  AND ds.id = (SELECT MAX(id) FROM device_status WHERE device_id = d.device_id) " +
            "LEFT JOIN device_status ds ON CAST(d.id AS CHAR) = ds.device_id " +
            "  AND ds.id = (SELECT MAX(id) FROM device_status WHERE device_id = CAST(d.id AS CHAR)) " +
            "WHERE dgr.group_id = #{groupId} AND dgr.is_deleted = 0 AND d.is_deleted = 0 " +
            "ORDER BY dgr.connection_order ASC")
    List<DeviceGroupVO.DeviceInfo> getGroupDevices(@Param("groupId") Long groupId);
@@ -107,8 +107,8 @@
    @Select("SELECT COUNT(DISTINCT d.id) " +
            "FROM device_config d " +
            "INNER JOIN device_group_relation dgr ON d.id = dgr.device_id " +
            "LEFT JOIN device_status ds ON d.device_id = ds.device_id " +
            "  AND ds.id = (SELECT MAX(id) FROM device_status WHERE device_id = d.device_id) " +
            "LEFT JOIN device_status ds ON CAST(d.id AS CHAR) = ds.device_id " +
            "  AND ds.id = (SELECT MAX(id) FROM device_status WHERE device_id = CAST(d.id AS CHAR)) " +
            "WHERE dgr.group_id = #{groupId} " +
            "  AND dgr.is_deleted = 0 " +
            "  AND d.is_deleted = 0 " +
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceConfigRequest.java
@@ -17,7 +17,7 @@
public class DeviceConfigRequest {
    @ApiModelProperty(value = "设备ID", example = "1")
    private Long deviceId;
    private Long id;
    @ApiModelProperty(value = "设备配置信息")
    private Object deviceConfig;
@@ -50,12 +50,12 @@
    public DeviceConfigRequest() {
    }
    public DeviceConfigRequest(Long deviceId) {
        this.deviceId = deviceId;
    public DeviceConfigRequest(Long id) {
        this.id = id;
    }
    public DeviceConfigRequest(Long deviceId, Object deviceConfig) {
        this.deviceId = deviceId;
    public DeviceConfigRequest(Long id, Object deviceConfig) {
        this.id = id;
        this.deviceConfig = deviceConfig;
    }
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGlassFeedRequest.java
@@ -17,7 +17,7 @@
    @NotNull
    @ApiModelProperty(value = "设备ID", required = true)
    private Long deviceId;
    private Long id;
    @ApiModelProperty(value = "玻璃ID列表", example = "GLS001")
    private List<String> glassIds;
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGroupRequest.java
@@ -20,7 +20,7 @@
    private Long groupId;
    @ApiModelProperty(value = "设备ID", example = "1")
    private Long deviceId;
    private Long id;
    @ApiModelProperty(value = "设备ID列表")
    private List<Long> deviceIds;
@@ -42,14 +42,14 @@
        this.groupId = groupId;
    }
    public DeviceGroupRequest(Long groupId, Long deviceId) {
    public DeviceGroupRequest(Long groupId, Long id) {
        this.groupId = groupId;
        this.deviceId = deviceId;
        this.id = id;
    }
    public DeviceGroupRequest(Long groupId, Long deviceId, String deviceRole) {
    public DeviceGroupRequest(Long groupId, Long id, String deviceRole) {
        this.groupId = groupId;
        this.deviceId = deviceId;
        this.id = id;
        this.deviceRole = deviceRole;
    }
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceConfigServiceImpl.java
@@ -48,9 +48,6 @@
                throw new IllegalArgumentException("设备编码已存在");
            }
            // 兼容旧字段:统一将 device_id 填为 deviceCode,避免非空/唯一约束问题
            deviceConfig.setDeviceId(code);
            // 项目ID未传则使用默认项目(单项目场景可用),避免非空约束
            if (deviceConfig.getProjectId() == null) {
                deviceConfig.setProjectId(1L);
@@ -79,11 +76,6 @@
            if (isDeviceCodeExists(deviceConfig.getDeviceCode(), deviceConfig.getId())) {
                log.warn("设备编号已存在: {}", deviceConfig.getDeviceCode());
                return false;
            }
            // 同步 device_id 与 deviceCode,保持一致
            if (StringUtils.isNotBlank(deviceConfig.getDeviceCode())) {
                deviceConfig.setDeviceId(deviceConfig.getDeviceCode().trim());
            }
            // 若项目ID缺失,使用默认项目
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceInteractionServiceImpl.java
@@ -38,7 +38,7 @@
    @Override
    public DevicePlcVO.OperationResult feedGlass(DeviceGlassFeedRequest request) {
        // 优先使用新的处理器架构
        DeviceConfig deviceConfig = deviceConfigService.getDeviceById(request.getDeviceId());
        DeviceConfig deviceConfig = deviceConfigService.getDeviceById(request.getId());
        if (deviceConfig != null) {
            DeviceLogicHandler handler = handlerFactory.getHandler(deviceConfig.getDeviceType());
            if (handler != null) {
@@ -53,13 +53,13 @@
        }
        // 降级到原有逻辑(兼容旧代码)
        DeviceControlProfile profile = controlProfileService.getProfile(request.getDeviceId());
        DeviceControlProfile profile = controlProfileService.getProfile(request.getId());
        Map<String, Object> payload = buildGlassPayload(profile, request);
        String opName = "玻璃上料";
        if (request.getPositionCode() != null) {
            opName = opName + "(" + request.getPositionCode() + ")";
        }
        return devicePlcOperationService.writeFields(request.getDeviceId(), payload, opName);
        return devicePlcOperationService.writeFields(request.getId(), payload, opName);
    }
    /**
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceStatusServiceImpl.java
@@ -83,9 +83,9 @@
                return false;
            }
            String deviceIdStr = device.getDeviceId();
            String deviceIdStr = String.valueOf(device.getId());
            if (deviceIdStr == null || deviceIdStr.trim().isEmpty()) {
                log.warn("设备配置中device_id字段为空: id={}", deviceId);
                log.warn("设备配置中id字段为空: id={}", deviceId);
                return false;
            }
@@ -188,10 +188,10 @@
        }
        try {
            DeviceConfig device = deviceConfigService.getDeviceById(deviceConfigId);
            if (device == null || device.getDeviceId() == null) {
            if (device == null || device.getId() == null) {
                return null;
            }
            return getLatestByDeviceId(device.getDeviceId());
            return getLatestByDeviceId(String.valueOf(device.getId()));
        } catch (Exception e) {
            log.error("根据设备配置ID获取设备状态失败: deviceConfigId={}", deviceConfigId, e);
            return null;
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/GlassInfoServiceImpl.java
@@ -233,8 +233,18 @@
            return result;
        }
        // 工程号生成:每次导入都生成新的工程号(先只生成,不保存到数据库,等到MES调用成功后再保存)
        final String engineerId = engineeringSequenceService.generateEngineeringId(new Date());
        // 工程号:代表整个Excel表,优先使用Excel中的工程号(从第一行或任意一行获取),如果所有行都没有则自动生成
        String engineerIdFromExcel = null;
        for (Map<String, Object> row : excelRows) {
            String engineeringId = str(row.get("engineeringId"));
            if (engineeringId != null && !engineeringId.trim().isEmpty()) {
                engineerIdFromExcel = engineeringId.trim();
                break; // 找到第一个非空的工程号就使用
            }
        }
        final String engineerId = engineerIdFromExcel != null
                ? engineerIdFromExcel
                : engineeringSequenceService.generateEngineeringId(new Date());
        final String filmsIdDefault = firstValue(excelRows, "filmsId", "白玻");
        final double thicknessDefault = parseDouble(firstValue(excelRows, "thickness"), 0d);
@@ -246,6 +256,15 @@
        // 生成日期字符串(yyMMdd格式),用于流程卡ID生成
        LocalDate localDate = new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        String dateStr = localDate.format(DateTimeFormatter.ofPattern("yyMMdd"));
        // 检查是否有流程卡ID:如果所有行的流程卡ID都为空,则所有记录共享同一个流程卡ID
        boolean allFlowCardIdEmpty = excelRows.stream()
                .allMatch(row -> {
                    String flowCardId = str(row.get("flowCardId"));
                    return flowCardId == null || flowCardId.trim().isEmpty();
                });
        // 如果所有流程卡ID都为空,生成一个共享的流程卡ID
        String sharedFlowCardId = allFlowCardIdEmpty ? "NG" + dateStr + "01A001" : null;
        
        // 用于存储每个玻璃ID对应的流程卡ID(同一玻璃ID的多个玻璃共享同一个流程卡ID)
        Map<String, String> glassIdFlowCardIdMap = new HashMap<>();
@@ -276,32 +295,25 @@
            String glassId = str(row.get("glassId"));
            String filmsId = strOrDefault(row.get("filmsId"), filmsIdDefaultFinal);
            String flowCardId = str(row.get("flowCardId"));
            // 如果流程卡ID为空,按新规则生成:NG + yyMMdd + 序号(两位,使用玻璃ID) + A001
            if (flowCardId.isEmpty()) {
                // 检查是否已为该玻璃ID生成过流程卡ID(同一玻璃ID的多个玻璃共享同一个流程卡ID)
                String generatedFlowCardId = glassIdFlowCardIdMap.get(glassId);
                if (generatedFlowCardId == null) {
                    // 使用玻璃ID作为序号(解析为整数,如果解析失败则使用1)
                    int sequence;
                    try {
                        sequence = Integer.parseInt(glassId.trim());
                        if (sequence <= 0) {
                            sequence = 1;
                        }
                    } catch (NumberFormatException e) {
                        log.warn("玻璃ID无法解析为整数,使用默认值1: glassId={}", glassId);
                        sequence = 1;
                    }
                    generatedFlowCardId = "NG" + dateStr + String.format("%02d", sequence) + "A001";
                    glassIdFlowCardIdMap.put(glassId, generatedFlowCardId);
                    log.info("为玻璃ID {} 生成流程卡ID: flowCardId={}", glassId, generatedFlowCardId);
                }
                flowCardId = generatedFlowCardId;
            // 流程卡ID:如果Excel中有,使用Excel的;如果为空,使用共享的流程卡ID
            String flowCardIdFromExcel = str(row.get("flowCardId"));
            String flowCardId;
            if (flowCardIdFromExcel != null && !flowCardIdFromExcel.trim().isEmpty()) {
                // Excel中有流程卡ID,使用Excel的
                flowCardId = flowCardIdFromExcel.trim();
            } else {
                // Excel中流程卡ID为空,使用共享的流程卡ID
                flowCardId = sharedFlowCardId != null ? sharedFlowCardId : getOrGenerateFlowCardId(glassId, dateStr, glassIdFlowCardIdMap);
            }
            // 去掉尾部 "/数字"(如果有)
            String baseFlowCardId = flowCardId.replaceFirst("/\\d+$", "");
            // 层号:如果Excel中有,使用Excel的;如果没有,默认1
            Object layerObj = row.get("layer");
            int layer = layerObj != null ? (int) parseDouble(layerObj, 1) : 1;
            if (layer <= 0) {
                layer = 1;
            }
            
            // orderNumber 是整型(玻璃类型),从 Excel 读取或使用默认值 1
            Object orderNumberObj = row.get("orderNumber");
@@ -351,7 +363,7 @@
                m.put("height", height);
                m.put("thickness", thickness);
                m.put("filmsId", filmsId);
                m.put("layer", 1);
                m.put("layer", layer);
                m.put("totalLayer", 1);
                m.put("edgWidth", width);
                m.put("edgHeight", height);
@@ -403,26 +415,15 @@
        Map<String, Map<String, Object>> flowCardMap = new HashMap<>();
        for (Map<String, Object> row : excelRows) {
            String glassId = str(row.get("glassId"));
            String flowCardId = str(row.get("flowCardId"));
            if (flowCardId.isEmpty()) {
                // 使用已生成的流程卡ID(与glassInfolList中的逻辑保持一致)
                flowCardId = glassIdFlowCardIdMap.get(glassId);
                if (flowCardId == null) {
                    // 如果未生成,则按规则生成(理论上不应该走到这里,因为glassInfolList已经生成过)
                    int sequence;
                    try {
                        sequence = Integer.parseInt(glassId.trim());
                        if (sequence <= 0) {
                            sequence = 1;
                        }
                    } catch (NumberFormatException e) {
                        log.warn("玻璃ID无法解析为整数,使用默认值1: glassId={}", glassId);
                        sequence = 1;
                    }
                    flowCardId = "NG" + dateStr + String.format("%02d", sequence) + "A001";
                    glassIdFlowCardIdMap.put(glassId, flowCardId);
                    log.warn("流程卡ID未在glassInfolList中生成,此处补充生成: flowCardId={}, glassId={}", flowCardId, glassId);
                }
            // 流程卡ID:如果Excel中有,使用Excel的;如果为空,使用共享的流程卡ID(与glassInfolList逻辑一致)
            String flowCardIdFromExcel = str(row.get("flowCardId"));
            String flowCardId;
            if (flowCardIdFromExcel != null && !flowCardIdFromExcel.trim().isEmpty()) {
                // Excel中有流程卡ID,使用Excel的
                flowCardId = flowCardIdFromExcel.trim();
            } else {
                // Excel中流程卡ID为空,使用共享的流程卡ID
                flowCardId = sharedFlowCardId != null ? sharedFlowCardId : getOrGenerateFlowCardId(glassId, dateStr, glassIdFlowCardIdMap);
            }
            // 去掉尾部 "/数字"(如果有)
            flowCardId = flowCardId.replaceFirst("/\\d+$", "");
@@ -594,6 +595,40 @@
    }
    /**
     * 获取或生成流程卡ID
     * 如果已存在则返回,否则生成新的流程卡ID并缓存
     * 前端格式:原始glassId + 两位序号(如"101"、"102"、"201"),通过除以100去掉最后两位提取原始glassId
     *
     * @param glassId 玻璃ID(如"101"、"102"、"201")
     * @param dateStr 日期字符串(yyMMdd格式)
     * @param glassIdFlowCardIdMap 玻璃ID到流程卡ID的映射缓存
     * @return 流程卡ID(格式:NG + yyMMdd + 序号(两位) + A001)
     */
    private String getOrGenerateFlowCardId(String glassId, String dateStr, Map<String, String> glassIdFlowCardIdMap) {
        String flowCardId = glassIdFlowCardIdMap.get(glassId);
        if (flowCardId == null) {
            // 从glassId中提取原始数字:101 -> 1, 102 -> 1, 201 -> 2
            int sequence = 1;
            if (glassId != null && !glassId.trim().isEmpty()) {
                try {
                    String cleaned = glassId.trim().split("[\\r\\n\\t\\s]+")[0];
                    if (cleaned.matches("\\d+")) {
                        int num = Integer.parseInt(cleaned);
                        // 如果长度>=3,除以100去掉最后两位(前端追加的序号)
                        sequence = (cleaned.length() >= 3 && num >= 100) ? num / 100 : (num > 0 ? num : 1);
                    }
                } catch (Exception e) {
                    log.error("从glassId中提取原始数字失败: glassId={}", glassId, e);
                }
            }
            flowCardId = "NG" + dateStr + String.format("%02d", sequence) + "A001";
            glassIdFlowCardIdMap.put(glassId, flowCardId);
            log.info("为玻璃ID {} 生成流程卡ID: flowCardId={}", glassId, flowCardId);
        }
        return flowCardId;
    }
    /**
     * 保留两位小数(四舍五入)
     *
     * @param v 原始数值
@@ -622,6 +657,13 @@
            return;
        }
        // 如果工程号已存在,先删除该工程号下的旧数据,实现覆盖更新
        List<GlassInfo> existingGlassInfos = getGlassInfosByEngineeringId(engineeringId.trim());
        if (!existingGlassInfos.isEmpty()) {
            log.info("检测到工程号 {} 已存在 {} 条记录,将删除旧数据并更新", engineeringId, existingGlassInfos.size());
            deleteGlassInfosByEngineeringId(engineeringId.trim());
        }
        List<GlassInfo> glassInfos = new ArrayList<>();
        Date now = new Date();
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/coordination/VehicleCoordinationService.java
@@ -93,7 +93,7 @@
        
        if (deviceConfig != null) {
            log.info("选择可用车辆: deviceId={}, deviceName={}", 
                deviceConfig.getDeviceId(), deviceConfig.getDeviceName());
                String.valueOf(deviceConfig.getId()), deviceConfig.getDeviceName());
        }
        
        return deviceConfig;
@@ -172,7 +172,7 @@
     */
    public List<DeviceConfig> getAvailableVehiclesInGroup(Long groupId) {
        return getVehiclesInGroup(groupId).stream()
            .filter(v -> statusManager.isVehicleAvailable(v.getDeviceId()))
            .filter(v -> statusManager.isVehicleAvailable(String.valueOf(v.getId())))
            .collect(Collectors.toList());
    }
    
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/flow/LoadVehicleInteraction.java
@@ -55,7 +55,7 @@
            }
            DeviceConfig currentDevice = context.getCurrentDevice();
            String deviceId = currentDevice.getDeviceId();
            String deviceId = String.valueOf(currentDevice.getId());
            // 1. 检查车辆状态(如果设备已指定)
            if (deviceId != null) {
@@ -98,7 +98,7 @@
            }
            // 4. 标记车辆为执行中
            String selectedDeviceId = selectedDevice.getDeviceId();
            String selectedDeviceId = String.valueOf(selectedDevice.getId());
            statusManager.updateVehicleStatus(
                selectedDeviceId, 
                selectedDevice.getDeviceName(), 
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
@@ -138,7 +138,7 @@
            Map<String, Object> params,
            Map<String, Object> logicParams) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        log.info("执行大车设备操作: deviceId={}, deviceName={}, operation={}", 
            deviceId, deviceConfig.getDeviceName(), operation);
@@ -289,7 +289,7 @@
            Map<String, Object> logicParams) {
        
        VehicleTask task = new VehicleTask();
        task.setTaskId(generateTaskId(deviceConfig.getDeviceId()));
        task.setTaskId(generateTaskId(String.valueOf(deviceConfig.getId())));
        task.setTaskName("大车设备-" + operation);
        task.setOperation(operation);
        
@@ -365,7 +365,7 @@
        Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed);
        List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, glassGap,
                deviceConfig.getDeviceId());
                String.valueOf(deviceConfig.getId()));
        if (plannedGlasses == null) {
            // 玻璃没有长度时返回null表示错误
            return DevicePlcVO.OperationResult.builder()
@@ -437,7 +437,7 @@
        // 如果执行成功,更新位置信息到状态
        if (Boolean.TRUE.equals(result.getSuccess())) {
            VehicleStatus status = statusManager.getOrCreateVehicleStatus(
                deviceConfig.getDeviceId(), deviceConfig.getDeviceName());
                String.valueOf(deviceConfig.getId()), deviceConfig.getDeviceName());
            if (positionCode != null || positionValue != null) {
                VehiclePosition position = new VehiclePosition(positionCode, positionValue);
                status.setCurrentPosition(position);
@@ -522,15 +522,15 @@
        
        // 重置时,清除任务并恢复为空闲状态,停止监控
        if (Boolean.TRUE.equals(result.getSuccess())) {
            statusManager.clearVehicleTask(deviceConfig.getDeviceId());
            statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
            stopStateMonitoring(deviceConfig.getDeviceId());
            statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId()));
            statusManager.updateVehicleStatus(String.valueOf(deviceConfig.getId()), VehicleState.IDLE);
            stopStateMonitoring(String.valueOf(deviceConfig.getId()));
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
            updateDeviceOnlineStatus(deviceConfig, true);
        } else {
            // 即便重置失败,也尝试停止内部监控,避免任务取消后仍然反复写PLC
            stopStateMonitoring(deviceConfig.getDeviceId());
            stopStateMonitoring(String.valueOf(deviceConfig.getId()));
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
        }
@@ -637,15 +637,15 @@
        
        // 清空后,恢复为空闲状态,停止监控
        if (Boolean.TRUE.equals(result.getSuccess())) {
            statusManager.clearVehicleTask(deviceConfig.getDeviceId());
            statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
            stopStateMonitoring(deviceConfig.getDeviceId());
            statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId()));
            statusManager.updateVehicleStatus(String.valueOf(deviceConfig.getId()), VehicleState.IDLE);
            stopStateMonitoring(String.valueOf(deviceConfig.getId()));
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
            updateDeviceOnlineStatus(deviceConfig, true);
        } else {
            // 写入失败也尝试停止监控,避免任务取消后仍旧运行
            stopStateMonitoring(deviceConfig.getDeviceId());
            stopStateMonitoring(String.valueOf(deviceConfig.getId()));
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
        }
@@ -662,7 +662,7 @@
            deviceStatusService.updateDeviceOnlineStatus(deviceConfig.getId(), status);
        } catch (Exception e) {
            log.warn("同步设备在线状态到数据库失败: deviceId={}, online={}, error={}",
                    deviceConfig.getDeviceId(), online, e.getMessage());
                    String.valueOf(deviceConfig.getId()), online, e.getMessage());
        }
    }
@@ -870,7 +870,7 @@
     * 定期检查大车的 state1~6,当检测到 state=1 时自动协调卧转立设备
     */
    private void startStateMonitoring(DeviceConfig deviceConfig, Map<String, Object> logicParams) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        
        // 如果已经在监控,先停止旧的监控任务
        stopStateMonitoring(deviceId);
@@ -941,7 +941,7 @@
     * 检查大车状态并协调卧转立设备(内部方法,由监控线程调用)
     */
    private void checkAndCoordinateState(DeviceConfig deviceConfig) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        List<String> alreadyCoordinated = coordinatedStates.get(deviceId);
        if (alreadyCoordinated == null) {
            alreadyCoordinated = new CopyOnWriteArrayList<>();
@@ -1229,7 +1229,7 @@
            Map<String, Object> params,
            Map<String, Object> logicParams) {
        
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        
        // 停止旧的监控任务
        handleStopIdleMonitor(deviceConfig);
@@ -1287,7 +1287,7 @@
     * 停止空闲监控
     */
    private DevicePlcVO.OperationResult handleStopIdleMonitor(DeviceConfig deviceConfig) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        ScheduledFuture<?> future = idleMonitoringTasks.remove(deviceId);
        if (future != null && !future.isCancelled()) {
            future.cancel(false);
@@ -1308,7 +1308,7 @@
            Map<String, Object> params,
            Map<String, Object> logicParams) {
        
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        PlcClient plcClient = getPlcClient(deviceConfig);
        if (plcClient == null) {
            return DevicePlcVO.OperationResult.builder()
@@ -1701,7 +1701,7 @@
     */
    private Integer getCurrentPosition(DeviceConfig deviceConfig, Map<String, Object> logicParams) {
        // 从状态管理器获取
        VehicleStatus status = statusManager.getVehicleStatus(deviceConfig.getDeviceId());
        VehicleStatus status = statusManager.getVehicleStatus(String.valueOf(deviceConfig.getId()));
        if (status != null && status.getCurrentPosition() != null) {
            return status.getCurrentPosition().getPositionValue();
        }
@@ -1792,7 +1792,7 @@
            Map<String, Object> params,
            Map<String, Object> logicParams) {
        
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        
        // 停止旧的监控任务
        handleStopTaskMonitor(deviceConfig);
@@ -1833,7 +1833,7 @@
                                      MesTaskInfo taskInfo,
                                      Map<String, Object> logicParams) {
        
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        PlcClient plcClient = getPlcClient(deviceConfig);
        if (plcClient == null) {
            return;
@@ -1887,7 +1887,7 @@
                if (taskInfo.brokenGlassIndices != null && taskInfo.brokenGlassIndices.contains(i)) {
                    updateStateIfNeeded(deviceConfig, plcClient, stateValues, stateField, 8, taskInfo);
                    log.info("玻璃标记为破损: deviceId={}, stateField={}, glassIndex={}", 
                            deviceConfig.getDeviceId(), stateField, i);
                            String.valueOf(deviceConfig.getId()), stateField, i);
                    continue;
                }
                
@@ -1895,7 +1895,7 @@
                if (elapsed >= state3TimeoutTime && (currentState == null || currentState < 2)) {
                    updateStateIfNeeded(deviceConfig, plcClient, stateValues, stateField, 3, taskInfo);
                    log.warn("任务超时未完成: deviceId={}, stateField={}, elapsed={}ms, expectedTime={}ms", 
                            deviceConfig.getDeviceId(), stateField, elapsed, state2Time);
                            String.valueOf(deviceConfig.getId()), stateField, elapsed, state2Time);
                    continue;
                }
                
@@ -1972,7 +1972,7 @@
        // 注意:如果当前state已经是3(未完成)或8(破损),不再更新
        if (currentState != null && (currentState == 3 || currentState == 8)) {
            log.debug("任务状态已为异常状态,不再更新: deviceId={}, stateField={}, currentState={}, targetState={}", 
                    deviceConfig.getDeviceId(), stateField, currentState, targetState);
                    String.valueOf(deviceConfig.getId()), stateField, currentState, targetState);
            return false;
        }
        
@@ -1984,12 +1984,12 @@
                plcClient.writeData(payload);
                
                log.info("任务状态已更新到PLC: deviceId={}, stateField={}, currentState={}, targetState={}", 
                        deviceConfig.getDeviceId(), stateField, currentState, targetState);
                        String.valueOf(deviceConfig.getId()), stateField, currentState, targetState);
                // 返回true表示状态发生了变化
                return true;
            } catch (Exception e) {
                log.error("写入PLC state字段失败: deviceId={}, stateField={}, targetState={}, error={}", 
                        deviceConfig.getDeviceId(), stateField, targetState, e.getMessage());
                        String.valueOf(deviceConfig.getId()), stateField, targetState, e.getMessage());
                return false;
            }
        }
@@ -2067,11 +2067,11 @@
                    .map(g -> g.glassId)
                    .collect(java.util.stream.Collectors.joining(","));
            log.info("已给MES汇报({}任务): deviceId={}, glassCount={}, glassIds=[{}]", 
                    taskType, deviceConfig.getDeviceId(), taskInfo.glasses.size(), glassIds);
                    taskType, String.valueOf(deviceConfig.getId()), taskInfo.glasses.size(), glassIds);
            
            // 多设备任务场景下,不在这里阻塞等待MES确认,由任务引擎定时调用checkMesConfirm
        } catch (Exception e) {
            log.error("给MES汇报异常: deviceId={}", deviceConfig.getDeviceId(), e);
            log.error("给MES汇报异常: deviceId={}", String.valueOf(deviceConfig.getId()), e);
        }
    }
@@ -2082,7 +2082,7 @@
    public DevicePlcVO.OperationResult checkMesConfirm(DeviceConfig deviceConfig,
                                                       Map<String, Object> params,
                                                       Map<String, Object> logicParams) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        PlcClient plcClient = getPlcClient(deviceConfig);
        if (plcClient == null) {
            return DevicePlcVO.OperationResult.builder()
@@ -2167,9 +2167,9 @@
                } catch (Exception e) {
                    log.warn("MES确认超时时清空任务状态失败: deviceId={}, error={}", deviceId, e.getMessage());
                }
                statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.ERROR);
                statusManager.clearVehicleTask(deviceConfig.getDeviceId());
                currentTasks.remove(deviceConfig.getDeviceId());
                statusManager.updateVehicleStatus(String.valueOf(deviceConfig.getId()), VehicleState.ERROR);
                statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId()));
                currentTasks.remove(String.valueOf(deviceConfig.getId()));
                handleStopTaskMonitor(deviceConfig);
                return DevicePlcVO.OperationResult.builder()
@@ -2196,11 +2196,11 @@
                // 任务完成,恢复为空闲状态
                statusManager.updateVehicleStatus(
                        deviceConfig.getDeviceId(), VehicleState.IDLE);
                statusManager.clearVehicleTask(deviceConfig.getDeviceId());
                        String.valueOf(deviceConfig.getId()), VehicleState.IDLE);
                statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId()));
                // 移除任务记录(如果有)
                currentTasks.remove(deviceConfig.getDeviceId());
                currentTasks.remove(String.valueOf(deviceConfig.getId()));
                // 停止任务监控
                handleStopTaskMonitor(deviceConfig);
@@ -2210,7 +2210,7 @@
                payload.put("plcRequest", 1);
                plcClient.writeData(payload);
                log.info("MES任务已确认完成: deviceId={}", deviceConfig.getDeviceId());
                log.info("MES任务已确认完成: deviceId={}", String.valueOf(deviceConfig.getId()));
                String taskType = (taskInfo != null && taskInfo.isOutbound) ? "出片" : "进片";
                return DevicePlcVO.OperationResult.builder()
                        .success(true)
@@ -2234,7 +2234,7 @@
                    .data(data)
                    .build();
        } catch (Exception e) {
            log.error("检查MES确认状态异常: deviceId={}", deviceConfig.getDeviceId(), e);
            log.error("检查MES确认状态异常: deviceId={}", String.valueOf(deviceConfig.getId()), e);
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message("检查MES确认状态异常: " + e.getMessage())
@@ -2250,7 +2250,7 @@
    private DevicePlcVO.OperationResult handleMarkBroken(DeviceConfig deviceConfig,
                                                         Map<String, Object> params,
                                                         Map<String, Object> logicParams) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        MesTaskInfo taskInfo = currentTasks.get(deviceId);
        if (taskInfo == null) {
            return DevicePlcVO.OperationResult.builder()
@@ -2342,7 +2342,7 @@
            payload.put("plcReport", 0);
            plcClient.writeData(payload);
        } catch (Exception e) {
            log.error("清空任务状态异常: deviceId={}", deviceConfig.getDeviceId(), e);
            log.error("清空任务状态异常: deviceId={}", String.valueOf(deviceConfig.getId()), e);
        }
    }
@@ -2350,7 +2350,7 @@
     * 停止任务监控
     */
    private DevicePlcVO.OperationResult handleStopTaskMonitor(DeviceConfig deviceConfig) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        ScheduledFuture<?> future = taskMonitoringTasks.remove(deviceId);
        if (future != null && !future.isCancelled()) {
            future.cancel(false);
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/workstation/transfer/handler/HorizontalTransferLogicHandler.java
@@ -114,7 +114,7 @@
            Map<String, Object> logicParams,
            Map<String, Object> params) {
        
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        PlcClient plcClient = getPlcClient(deviceConfig);
        if (plcClient == null) {
            return buildResult(deviceConfig, "checkAndProcess", false, 
@@ -482,7 +482,7 @@
            WorkstationLogicConfig config,
            Map<String, Object> logicParams) {
        
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        
        // 停止旧的监控任务
        handleStopMonitor(deviceConfig);
@@ -513,7 +513,7 @@
     * 停止监控任务
     */
    private DevicePlcVO.OperationResult handleStopMonitor(DeviceConfig deviceConfig) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        ScheduledFuture<?> future = monitorTasks.remove(deviceId);
        if (future != null && !future.isCancelled()) {
            future.cancel(false);
@@ -526,7 +526,7 @@
     * 清空缓冲队列
     */
    private DevicePlcVO.OperationResult handleClearBuffer(DeviceConfig deviceConfig) {
        String deviceId = deviceConfig.getDeviceId();
        String deviceId = String.valueOf(deviceConfig.getId());
        glassBuffer.remove(deviceId);
        lastScanTime.remove(deviceId);
        log.info("已清空缓冲队列: deviceId={}", deviceId);
mes-processes/mes-plcSend/src/main/java/com/mes/s7/provider/S7SerializerProvider.java
@@ -45,12 +45,15 @@
    }
    private String buildCacheKey(DeviceConfig deviceConfig) {
        // 优先使用数据库主键ID
        if (deviceConfig.getId() != null) {
            return "device:" + deviceConfig.getId();
        }
        // 备用方案:使用设备编码
        if (deviceConfig.getDeviceCode() != null) {
            return "device:" + deviceConfig.getDeviceCode();
        }
        // 最后方案:使用对象哈希
        return "device:" + Objects.hash(deviceConfig);
    }
mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java
@@ -70,11 +70,16 @@
                // 使用新的PLC客户端读取数据
                Map<String, Object> currentData = plcClient.readAllData();
                if (currentData != null && !currentData.isEmpty()) {
                    // 检查联机状态
                    Integer onlineState = parseInteger(currentData.get("onlineState"));
                    if (onlineState != null && onlineState == OFF) {
                    // 检查联机状态(仅当配置中存在该字段时)
                    if (hasFieldInConfig(device, "onlineState")) {
                        Object onlineStateObj = currentData.get("onlineState");
                        if (onlineStateObj != null) {
                            Integer onlineState = parseInteger(onlineStateObj);
                            if (onlineState == OFF) {
                        log.info("当前PLC联机模式为0,停止联机: deviceId={}", deviceId);
                        return false;
                            }
                        }
                    }
                    // 检查汇报字,如果为1则重置为0
@@ -126,7 +131,8 @@
                return false;
            }
            // 检查联机状态
            // 检查联机状态(仅当配置中存在该字段时)
            if (hasFieldInConfig(device, "onlineState")) {
            Object onlineStateObj = currentData.get("onlineState");
            Integer onlineState = null;
            if (onlineStateObj != null) {
@@ -147,6 +153,7 @@
            if (onlineState != null && onlineState == OFF) {
                log.info("当前PLC联机模式为0,停止联机: deviceId={}", device.getId());
                return false;
                }
            }
            // 检查汇报字,如果为1则重置为0
@@ -281,14 +288,32 @@
            // 尝试使用新的PLC客户端工厂
            PlcClient plcClient = plcClientFactory.getClient(device);
            if (plcClient != null) {
                // 构建重置数据
                // 构建重置数据(只添加配置中存在的字段)
                Map<String, Object> resetData = new HashMap<>();
                if (hasFieldInConfig(device, "plcRequest")) {
                resetData.put("plcRequest", OFF);
                }
                if (hasFieldInConfig(device, "plcReport")) {
                resetData.put("plcReport", OFF);
                }
                if (hasFieldInConfig(device, "mesSend")) {
                resetData.put("mesSend", OFF);
                }
                if (hasFieldInConfig(device, "mesConfirm")) {
                resetData.put("mesConfirm", OFF);
                }
                if (hasFieldInConfig(device, "onlineState")) {
                resetData.put("onlineState", ON);
                }
                if (hasFieldInConfig(device, "alarmInfo")) {
                resetData.put("alarmInfo", OFF);
                }
                // 检查是否有字段需要重置
                if (resetData.isEmpty()) {
                    log.warn("设备配置中未找到任何可重置的字段: deviceId={}", deviceId);
                    return false;
                }
                // 使用新的PLC客户端写入数据
                boolean success = plcClient.writeData(resetData);
@@ -321,14 +346,32 @@
                return false;
            }
            // 构建重置数据
            // 构建重置数据(只添加配置中存在的字段)
            Map<String, Object> resetData = new HashMap<>();
            if (hasFieldInConfig(device, "plcRequest")) {
            resetData.put("plcRequest", OFF);
            }
            if (hasFieldInConfig(device, "plcReport")) {
            resetData.put("plcReport", OFF);
            }
            if (hasFieldInConfig(device, "mesSend")) {
            resetData.put("mesSend", OFF);
            }
            if (hasFieldInConfig(device, "mesConfirm")) {
            resetData.put("mesConfirm", OFF);
            }
            if (hasFieldInConfig(device, "onlineState")) {
            resetData.put("onlineState", ON);
            }
            if (hasFieldInConfig(device, "alarmInfo")) {
            resetData.put("alarmInfo", OFF);
            }
            // 检查是否有字段需要重置
            if (resetData.isEmpty()) {
                log.warn("设备配置中未找到任何可重置的字段: deviceId={}", device.getId());
                return false;
            }
            // 使用PlcDynamicDataService写入数据
            plcDynamicDataService.writePlcData(device, resetData, s7Serializer);
@@ -572,4 +615,44 @@
        
        throw new IllegalStateException("无法解析设备的PLC项目标识, deviceId=" + device.getId());
    }
    /**
     * 检查设备配置中是否存在指定字段
     *
     * @param device 设备配置
     * @param fieldName 字段名
     * @return 是否存在
     */
    private boolean hasFieldInConfig(DeviceConfig device, String fieldName) {
        if (device == null || fieldName == null || fieldName.isEmpty()) {
            return false;
        }
        try {
            // 从 configJson 中检查(新结构)
            Map<String, Object> configParams = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper);
            if (configParams.containsKey(fieldName)) {
                return true;
            }
            // 从 extraParams.addressMapping 中检查(兼容旧结构)
            Map<String, Object> extraParams = parseExtraParams(device);
            Object addressMapping = extraParams.get("addressMapping");
            if (addressMapping != null) {
                Map<String, Object> addressMappingMap;
                if (addressMapping instanceof Map) {
                    addressMappingMap = (Map<String, Object>) addressMapping;
                } else if (addressMapping instanceof String) {
                    addressMappingMap = objectMapper.readValue((String) addressMapping, MAP_TYPE);
                } else {
                    return false;
                }
                return addressMappingMap.containsKey(fieldName);
            }
        } catch (Exception e) {
            log.warn("检查字段是否存在时出错: deviceId={}, fieldName={}", device.getId(), fieldName, e);
        }
        return false;
    }
}
mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java
@@ -213,8 +213,9 @@
        try {
            String addressMapping = extractAddressMapping(device);
            if (addressMapping == null || addressMapping.isEmpty()) {
                log.error("设备配置中addressMapping为空: deviceId={}", device.getId());
                return;
                String errorMsg = "设备配置中addressMapping为空: deviceId=" + device.getId();
                log.error(errorMsg);
                throw new IllegalArgumentException(errorMsg);
            }
            
            // 解析addressMapping JSON配置
@@ -224,11 +225,18 @@
            String dbArea = extractDbArea(device);
            List<S7Parameter> parameters = buildS7ParametersWithValuesForDevice(device, dbArea, addressMappingObj, dataMap);
            
            if (parameters.isEmpty()) {
                log.warn("没有有效的字段需要写入PLC: deviceId={}", device.getId());
                return;
            }
            // 写入PLC
            s7Serializer.write(parameters);
        } catch (Exception e) {
            log.error("写入PLC数据失败,请检查:1.PLC IP地址是否正确[{}] 2.PLC设备是否在线 3.网络连接是否正常,deviceId: {}, 详细错误: {}",
                device.getPlcIp(), device.getId(), e.getMessage(), e);
            String errorMsg = String.format("写入PLC数据失败,请检查:1.PLC IP地址是否正确[%s] 2.PLC设备是否在线 3.网络连接是否正常,deviceId: %s, 详细错误: %s",
                device.getPlcIp(), device.getId(), e.getMessage());
            log.error(errorMsg, e);
            throw new RuntimeException(errorMsg, e);
        }
    }
    
@@ -463,9 +471,12 @@
            EDataType dataType = fieldConfig.dataType != null ? fieldConfig.dataType : determineFieldTypeByName(fieldName);
            int count = fieldConfig.count > 0 ? fieldConfig.count : determineFieldCountByName(fieldName);
            
            // 根据字段类型转换值
            Object convertedValue = convertValueByType(value, dataType);
            // 创建S7Parameter,设置值
            S7Parameter parameter = new S7Parameter(fullAddress, dataType, count);
            parameter.setValue(value);
            parameter.setValue(convertedValue);
            parameters.add(parameter);
        }
        
@@ -579,6 +590,65 @@
    }
    
    /**
     * 根据字段类型转换值
     * 主要处理:Integer -> Boolean (对于BOOL类型)
     *
     * @param value 原始值
     * @param dataType 目标数据类型
     * @return 转换后的值
     */
    private Object convertValueByType(Object value, EDataType dataType) {
        if (value == null) {
            return null;
        }
        // 如果已经是目标类型,直接返回
        if (dataType == EDataType.BOOL) {
            if (value instanceof Boolean) {
                return value;
            }
            // 将 Integer/Number 转换为 Boolean
            if (value instanceof Number) {
                int intValue = ((Number) value).intValue();
                return intValue != 0;
            }
            // 尝试从字符串转换
            if (value instanceof String) {
                String str = ((String) value).trim().toLowerCase();
                return "true".equals(str) || "1".equals(str) || "on".equals(str);
            }
            // 其他类型,尝试转换为数字再转Boolean
            try {
                int intValue = Integer.parseInt(String.valueOf(value));
                return intValue != 0;
            } catch (NumberFormatException e) {
                log.warn("无法将值转换为Boolean: {}", value);
                return false;
            }
        }
        // 对于其他类型,如果已经是目标类型或兼容类型,直接返回
        // 例如:UINT16 可以接受 Integer, Short, Byte 等
        if (dataType == EDataType.UINT16 || dataType == EDataType.INT16) {
            if (value instanceof Number) {
                return value;
            }
            // 尝试从字符串转换
            if (value instanceof String) {
                try {
                    return Integer.parseInt((String) value);
                } catch (NumberFormatException e) {
                    log.warn("无法将值转换为Integer: {}", value);
                    return 0;
                }
            }
        }
        // 对于其他类型,直接返回原值
        return value;
    }
    /**
     * 根据字段名推断字段长度/数量
     */
    private int determineFieldCountByName(String fieldName) {
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -2833,7 +2833,7 @@
            List<TaskStepDetail> largeGlassSteps = taskStepDetailMapper.selectList(
                    Wrappers.<TaskStepDetail>lambdaQuery()
                            .eq(TaskStepDetail::getTaskId, task.getTaskId())
                            .eq(TaskStepDetail::getDeviceId, largeGlassDevice.getId())
                            .eq(TaskStepDetail::getDeviceId, String.valueOf(largeGlassDevice.getId()))
                            .orderByDesc(TaskStepDetail::getStepOrder)
                            .last("LIMIT 1")
            );
@@ -2904,7 +2904,7 @@
            List<TaskStepDetail> transferSteps = taskStepDetailMapper.selectList(
                Wrappers.<TaskStepDetail>lambdaQuery()
                    .eq(TaskStepDetail::getTaskId, taskId)
                    .eq(TaskStepDetail::getDeviceId, transferDevice.getId())
                    .eq(TaskStepDetail::getDeviceId, String.valueOf(transferDevice.getId()))
                    .orderByDesc(TaskStepDetail::getStepOrder)
                    .last("LIMIT 1")
            );
mes-web/src/api/device/deviceManagement.js
@@ -25,7 +25,7 @@
      url: `/api/plcSend/device/config/devices/update`,
      method: 'post',
      data: {
        deviceId: id,
        id: id,
        deviceConfig: data
      }
    })
@@ -38,7 +38,7 @@
    return request({
      url: `/api/plcSend/device/config/devices/delete`,
      method: 'post',
      data: { deviceId: id }
      data: { id: id }
    })
  },
@@ -49,7 +49,7 @@
    return request({
      url: `/api/plcSend/device/config/devices/detail`,
      method: 'post',
      data: { deviceId: id }
      data: { id: id }
    })
  },
@@ -78,7 +78,7 @@
    return request({
      url: '/api/plcSend/device/config/devices/enable',
      method: 'post',
      data: { deviceId: id }
      data: { id: id }
    })
  },
@@ -89,7 +89,7 @@
    return request({
      url: '/api/plcSend/device/config/devices/disable',
      method: 'post',
      data: { deviceId: id }
      data: { id: id }
    })
  },
@@ -135,7 +135,7 @@
      method: 'post',
      data: {
        deviceCode,
        excludeId
        id: excludeId
      }
    })
  },
@@ -162,7 +162,7 @@
  /**
   * 测试设备PLC连接
   * data 可以是 { deviceId } 或 { plcIp, plcPort, timeout }
   * data 可以是 { id } 或 { plcIp, plcPort, timeout }
   */
  testConnection(data) {
    return request({
@@ -190,7 +190,7 @@
    return request({
      url: `/api/plcSend/device/config/devices/health-check`,
      method: 'post',
      data: { deviceId: id }
      data: { id: id }
    })
  }
}
@@ -535,7 +535,7 @@
export const deviceInteractionApi = {
  /**
   * 执行设备逻辑操作
   * @param {Object} data - { deviceId, operation, params }
   * @param {Object} data - { id, operation, params }
   */
  executeOperation(data) {
    return request({
@@ -561,7 +561,7 @@
export const deviceStatusApi = {
  /**
   * 更新设备在线状态
   * @param {Object} data - { deviceId, status }
   * @param {Object} data - { id, status }
   */
  updateDeviceOnlineStatus(data) {
    return request({
mes-web/src/views/device/DeviceConfigForm.vue
@@ -432,7 +432,7 @@
      showConfirmButton: false
    })
    
    const response = await deviceConfigApi.testConnection({ deviceId: row.id })
    const response = await deviceConfigApi.testConnection({ id: row.id })
    
    if (response.success) {
      ElMessage.success(response.data || `设备 ${row.deviceName} 连接测试成功`)
mes-web/src/views/device/DeviceConfigList.vue
@@ -79,7 +79,7 @@
        </el-table-column>
        <el-table-column prop="plcIp" label="PLC IP" width="130" />
        <el-table-column prop="plcType" label="PLC类型" width="100" />
        <el-table-column prop="moduleName" label="模块名称" min-width="120" />
        <el-table-column prop="moduleName" label="模块名称" min-width="60" />
        <el-table-column prop="isPrimary" label="主控设备" width="100" align="center">
          <template #default="scope">
            <el-tag v-if="scope.row.isPrimary" type="success" size="small">主控</el-tag>
@@ -107,12 +107,12 @@
            {{ formatDateTime(scope.row.lastHeartbeat) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="200" fixed="right">
        <el-table-column label="操作" width="300" fixed="right">
          <template #default="scope">
            <el-button type="primary" size="small" @click="editDevice(scope.row)">
              编辑
            </el-button>
            <el-button type="warning" size="small" :loading="plcOperationLoading" @click="handleSinglePlcRequest(scope.row)">
            <el-button type="warning" size="small" :loading="plcOperationLoading" @click.stop="handleSinglePlcRequest(scope.row, $event)">
              PLC请求
            </el-button>
            <el-button type="success" size="small" @click="healthCheck(scope.row)">
@@ -322,9 +322,24 @@
  }
}
const handleSinglePlcRequest = (row) => executePlcOperation([row.id || row.deviceId], 'request')
const handleSinglePlcReport = (row) => executePlcOperation([row.id || row.deviceId], 'report')
const handleSinglePlcReset = (row) => executePlcOperation([row.id || row.deviceId], 'reset')
const handleSinglePlcRequest = (row, event) => {
  if (event) {
    event.stopPropagation()
  }
  executePlcOperation([row.id || row.deviceId], 'request')
}
const handleSinglePlcReport = (row, event) => {
  if (event) {
    event.stopPropagation()
  }
  executePlcOperation([row.id || row.deviceId], 'report')
}
const handleSinglePlcReset = (row, event) => {
  if (event) {
    event.stopPropagation()
  }
  executePlcOperation([row.id || row.deviceId], 'reset')
}
const batchPlcRequest = () => executePlcOperation(getSelectedDeviceIds(), 'request')
const batchPlcReport = () => executePlcOperation(getSelectedDeviceIds(), 'report')
mes-web/src/views/device/DeviceGroupList.vue
@@ -208,7 +208,7 @@
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column label="操作" width="200" fixed="right">
          <el-table-column label="操作" width="280" fixed="right">
            <template #default="scope">
              <el-button
                v-if="scope.row.isOnline"
@@ -227,6 +227,13 @@
                :loading="scope.row.statusUpdating"
              >
                设为在线
              </el-button>
              <el-button
                type="danger"
                size="small"
                @click="removeSingleDevice(scope.row)"
              >
                移除设备
              </el-button>
            </template>
          </el-table-column>
@@ -761,6 +768,37 @@
  }
}
// 移除单个设备
const removeSingleDevice = async (device) => {
  try {
    await ElMessageBox.confirm(
      `确定要从设备组中移除设备"${device.deviceName || device.deviceCode}"吗?`,
      '移除设备确认',
      {
        confirmButtonText: '确定移除',
        cancelButtonText: '取消',
        type: 'warning'
      }
    )
    const deviceId = device.id || device.deviceId
    await deviceGroupApi.batchRemoveDevicesFromGroup({
      groupId: currentGroup.value.id || currentGroup.value.groupId,
      deviceIds: [deviceId]
    })
    ElMessage.success('设备移除成功')
    const groupId = currentGroup.value.id || currentGroup.value.groupId
    await loadGroupDevices(groupId)
    await loadAvailableDevices()
    emit('refresh-statistics')
  } catch (error) {
    if (error !== 'cancel') {
      console.error('移除设备失败:', error)
      ElMessage.error('移除设备失败: ' + (error.response?.data?.message || error.message))
    }
  }
}
// 更新设备在线状态
const updateDeviceOnlineStatus = async (device, status) => {
  try {
@@ -774,7 +812,7 @@
    }
    await deviceStatusApi.updateDeviceOnlineStatus({
      deviceId: deviceId,
      id: deviceId,
      status: status
    })
mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
@@ -385,7 +385,7 @@
  try {
    togglingDeviceId.value = deviceId
    await deviceInteractionApi.executeOperation({
      deviceId,
      id: deviceId,
      operation: 'setOnlineState',
      params: {
        onlineState: value
@@ -415,7 +415,7 @@
  try {
    clearingDeviceId.value = deviceId
    await deviceInteractionApi.executeOperation({
      deviceId,
      id: deviceId,
      operation: 'clearPlc'
    })
    ElMessage.success(`已清空 ${device.deviceName || device.deviceCode} 的PLC数据`)
mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
@@ -398,7 +398,7 @@
  try {
    clearLoading.value = true
    const response = await deviceInteractionApi.executeOperation({
      deviceId: loadDeviceId.value,
      id: loadDeviceId.value,
      operation: 'clearPlc',
      params: {}
    })
@@ -566,6 +566,16 @@
      headerStr === '客户名称') {
      headerMap.customerName = index
    }
    // 层号
    else if (headerStr.includes('层号') || headerStr.includes('layer') ||
      headerStr === '层') {
      headerMap.layer = index
    }
    // 工程号
    else if (headerStr.includes('工程号') || headerStr.includes('engineeringid') ||
      headerStr.includes('engineering') || headerStr === '工程id') {
      headerMap.engineeringId = index
    }
  })
  // 如果没有找到表头,尝试使用第一行作为表头(索引方式)
@@ -594,6 +604,8 @@
    const flowCardId = row[headerMap.flowCardId] ? String(row[headerMap.flowCardId]).trim() : ''
    const productName = row[headerMap.productName] ? String(row[headerMap.productName]).trim() : ''
    const customerName = row[headerMap.customerName] ? String(row[headerMap.customerName]).trim() : ''
    const layer = row[headerMap.layer] ? String(row[headerMap.layer]).trim() : ''
    const engineeringId = row[headerMap.engineeringId] ? String(row[headerMap.engineeringId]).trim() : ''
    // 跳过空行
    if (!glassId && !width && !length && !thickness && !quantity) {
@@ -613,11 +625,13 @@
      return isNaN(num) ? '0' : String(num)
    }
    // 处理数量:如果数量大于1,需要生成多条记录
    // 处理数量:根据数量生成多条记录,每条记录都要补齐序号
    const qty = parseInt(quantity) || 1
    for (let j = 0; j < qty; j++) {
      // 如果数量大于1,为每条记录生成唯一的玻璃ID(追加序号)
      const finalGlassId = qty > 1 ? `${glassId}${padTwoZero(j + 1)}` : glassId
      // 为每条记录生成唯一的玻璃ID(追加序号,即使数量为1也要补齐)
      // 例如:glassId="1", quantity=2 -> "101", "102"
      //       glassId="2", quantity=1 -> "201"
      const finalGlassId = `${glassId}${padTwoZero(j + 1)}`
      result.push({
        glassId: finalGlassId,
@@ -626,9 +640,11 @@
        thickness: parseNumber(thickness),
        quantity: '1', // 每条记录数量为1
        filmsId: filmsId,
        flowCardId: flowCardId || finalGlassId,
        flowCardId: flowCardId || '', // 如果Excel中没有流程卡ID,传空字符串让后端生成
        productName: productName,
        customerName: customerName
        customerName: customerName,
        layer: layer || '', // 层号,如果Excel中没有则为空
        engineeringId: engineeringId || '' // 工程号,如果Excel中没有则为空
      })
    }
  }