# MES Test Project 多设备联合测试扩展方案 ## 📋 项目概述 基于现有的MES Test Project(mes-web + mes-plcSend),扩展支持多设备联合测试功能,实现"上大车设备 → 大理片设备 → 玻璃存储设备"的完整生产流程自动化测试。 ## 🎯 核心需求 ### 业务场景 1. **上大车前请求**:检测车辆容量(6000mm可配置)、玻璃规格匹配、节拍控制 2. **大理片交互**:与MES大理片信息比对验证、批量处理逻辑 3. **多设备协调**:设备间数据传递、状态同步、依赖管理 ### 技术需求 - 支持多PLC设备地址映射 - 设备组配置和管理 - 串行/并行执行模式 - 设备间数据共享 - 实时状态监控 ## 📁 扩展架构设计 ### 1. 后端扩展结构 #### 1.1 新增目录结构 ``` mes-plcSend/src/main/java/com/mes/ ├── device/ # 设备管理层 │ ├── entity/ │ │ ├── DeviceConfig.java # 设备配置实体 │ │ ├── DeviceGroup.java # 设备组实体 │ │ └── DeviceStatus.java # 设备状态实体 │ ├── service/ │ │ ├── DeviceService.java # 设备管理服务 │ │ ├── DeviceGroupService.java # 设备组服务 │ │ └── DeviceCoordinationService.java # 设备协调服务 │ └── controller/ │ ├── DeviceController.java # 设备管理API │ └── DeviceGroupController.java # 设备组管理API ├── interaction/ # 交互逻辑模块 │ ├── base/ │ │ ├── BaseInteraction.java # 基础交互抽象 │ │ ├── InteractionContext.java # 交互上下文 │ │ └── InteractionResult.java # 交互结果 │ ├── 上大车/ │ │ ├── 上大车Interaction.java # 上大车交互逻辑 │ │ └── 上大车Config.java # 上大车配置 │ ├── 大理片/ │ │ ├── 大理片Interaction.java # 大理片交互逻辑 │ │ └── 大理片Config.java # 大理片配置 │ └── 玻璃存储/ │ ├── 玻璃存储Interaction.java # 玻璃存储交互逻辑 │ └── 玻璃存储Config.java # 玻璃存储配置 └── task/ # 任务管理层 ├── entity/ │ ├── MultiDeviceTask.java # 多设备任务实体 │ └── TaskStep.java # 任务步骤实体 ├── service/ │ ├── MultiDeviceTaskService.java # 多设备任务服务 │ └── TaskExecutionEngine.java # 任务执行引擎 └── controller/ └── MultiDeviceTaskController.java # 多设备任务API ``` #### 1.2 数据库表扩展 ```sql -- 设备配置表 CREATE TABLE device_config ( id BIGINT PRIMARY KEY AUTO_INCREMENT, device_id VARCHAR(50) UNIQUE NOT NULL COMMENT '设备ID', device_name VARCHAR(100) NOT NULL COMMENT '设备名称', device_type VARCHAR(50) NOT NULL COMMENT '设备类型(上大车/大理片/玻璃存储)', plc_ip VARCHAR(15) NOT NULL COMMENT 'PLC IP地址', plc_type VARCHAR(20) NOT NULL COMMENT 'PLC类型', module_name VARCHAR(50) NOT NULL COMMENT '模块名称', is_primary BOOLEAN DEFAULT FALSE COMMENT '是否主控设备', enabled BOOLEAN DEFAULT TRUE COMMENT '是否启用', config_json TEXT COMMENT '设备特定配置(JSON)', created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 设备组配置表 CREATE TABLE device_group ( id BIGINT PRIMARY KEY AUTO_INCREMENT, group_id VARCHAR(50) UNIQUE NOT NULL COMMENT '设备组ID', group_name VARCHAR(100) NOT NULL COMMENT '设备组名称', project_id VARCHAR(50) NOT NULL COMMENT '关联项目ID', execution_mode ENUM('SERIAL', 'PARALLEL') DEFAULT 'SERIAL' COMMENT '执行模式', execution_config JSON COMMENT '执行配置', dependencies JSON COMMENT '设备依赖关系', created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 设备组与设备关系表 CREATE TABLE device_group_mapping ( id BIGINT PRIMARY KEY AUTO_INCREMENT, group_id VARCHAR(50) NOT NULL, device_id VARCHAR(50) NOT NULL, execution_order INT NOT NULL COMMENT '执行顺序', FOREIGN KEY (group_id) REFERENCES device_group(group_id) ON DELETE CASCADE, FOREIGN KEY (device_id) REFERENCES device_config(device_id) ON DELETE CASCADE, UNIQUE KEY uk_group_device (group_id, device_id) ); -- 多设备任务表 CREATE TABLE multi_device_task ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_id VARCHAR(50) UNIQUE NOT NULL COMMENT '任务ID', group_id VARCHAR(50) NOT NULL COMMENT '设备组ID', project_id VARCHAR(50) NOT NULL COMMENT '项目ID', status ENUM('PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'CANCELLED') DEFAULT 'PENDING', current_step INT DEFAULT 0 COMMENT '当前步骤', total_steps INT DEFAULT 0 COMMENT '总步骤数', start_time DATETIME COMMENT '开始时间', end_time DATETIME COMMENT '结束时间', error_message TEXT COMMENT '错误信息', result_data JSON COMMENT '结果数据', created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- 任务步骤详情表 CREATE TABLE task_step_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, task_id VARCHAR(50) NOT NULL COMMENT '任务ID', step_order INT NOT NULL COMMENT '步骤顺序', device_id VARCHAR(50) NOT NULL COMMENT '设备ID', step_name VARCHAR(100) NOT NULL COMMENT '步骤名称', status ENUM('PENDING', 'RUNNING', 'COMPLETED', 'FAILED', 'SKIPPED') DEFAULT 'PENDING', start_time DATETIME COMMENT '步骤开始时间', end_time DATETIME COMMENT '步骤结束时间', duration_ms BIGINT COMMENT '执行耗时(毫秒)', input_data JSON COMMENT '输入数据', output_data JSON COMMENT '输出数据', error_message TEXT COMMENT '错误信息', retry_count INT DEFAULT 0 COMMENT '重试次数', created_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (task_id) REFERENCES multi_device_task(task_id) ON DELETE CASCADE ); ``` ### 2. 前端扩展结构 #### 2.1 新增前端目录 ``` mes-web/src/views/plcTest/ ├── components/ │ ├── DeviceManagement/ # 设备管理组件 │ │ ├── DeviceList.vue # 设备列表 │ │ ├── DeviceConfig.vue # 设备配置 │ │ └── DeviceStatus.vue # 设备状态 │ ├── DeviceGroup/ # 设备组组件 │ │ ├── GroupList.vue # 设备组列表 │ │ ├── GroupConfig.vue # 设备组配置 │ │ └── GroupTopology.vue # 设备组拓扑图 │ ├── MultiDeviceTest/ # 多设备测试组件 │ │ ├── TestOrchestration.vue # 测试编排 │ │ ├── ExecutionMonitor.vue # 执行监控 │ │ └── ResultAnalysis.vue # 结果分析 │ └── InteractionLogic/ # 交互逻辑组件 │ ├── 上大车Config.vue # 上大车配置 │ ├── 大理片Config.vue # 大理片配置 │ └── 玻璃存储Config.vue # 玻璃存储配置 ``` ## 🔧 核心实现设计 ### 3.1 设备管理实现 #### 设备配置实体 ```java @Data @Entity @TableName("device_config") public class DeviceConfig { @TableId(type = IdType.AUTO) private Long id; @TableField("device_id") private String deviceId; @TableField("device_name") private String deviceName; @TableField("device_type") private String deviceType; // "上大车" / "大理片" / "玻璃存储" @TableField("plc_ip") private String plcIp; @TableField("plc_type") private String plcType; @TableField("module_name") private String moduleName; @TableField("is_primary") private Boolean isPrimary; @TableField("config_json") private String configJson; // 设备特定配置 // 配置解析方法 public T getConfig(Class clazz) { if (StringUtils.isBlank(configJson)) { return null; } try { return JsonUtils.fromJson(configJson, clazz); } catch (Exception e) { log.error("解析设备配置失败: {}", deviceId, e); return null; } } } ``` #### 设备管理服务 ```java @Service public class DeviceService { @Resource private DeviceConfigMapper deviceConfigMapper; /** * 注册设备 */ @Transactional public void registerDevice(DeviceConfig device) { // 验证PLC连接 validatePlcConnection(device.getPlcIp(), device.getPlcType()); // 保存设备配置 deviceConfigMapper.insert(device); // 更新地址映射 updatePlcAddressMapping(device); log.info("设备注册成功: {}", device.getDeviceId()); } /** * 获取设备的PLC地址映射 */ public Map getDeviceAddressMapping(String deviceId) { DeviceConfig device = getDeviceById(deviceId); if (device == null) { throw new RuntimeException("设备不存在: " + deviceId); } // 根据设备类型获取对应的地址映射配置 String mappingKey = device.getDeviceType() + "_" + device.getModuleName(); return plcAddressService.getMappingByKey(mappingKey); } } ``` ### 3.2 设备组管理实现 #### 设备组实体 ```java @Data @TableName("device_group") public class DeviceGroup { @TableId(type = IdType.AUTO) private Long id; @TableField("group_id") private String groupId; @TableField("group_name") private String groupName; @TableField("project_id") private String projectId; @TableField("execution_mode") private ExecutionMode executionMode; // SERIAL / PARALLEL @TableField("execution_config") private String executionConfig; @TableField("dependencies") private String dependencies; // JSON格式的依赖关系 // 获取设备组中的所有设备 public List getDevices() { return deviceGroupService.getDevicesByGroupId(groupId); } // 获取执行顺序 public List getExecutionSequence() { return deviceGroupService.getDevicesByOrder(groupId); } } ``` ### 3.3 交互逻辑实现 #### 基础交互接口 ```java /** * 设备交互逻辑接口 */ public interface DeviceInteraction { /** * 执行交互逻辑 */ InteractionResult execute(InteractionContext context); /** * 验证前置条件 */ boolean validatePreCondition(InteractionContext context); /** * 获取设备类型 */ String getDeviceType(); /** * 获取默认配置 */ DeviceInteractionConfig getDefaultConfig(); } ``` #### 上大车交互实现 ```java @Component("上大车Interaction") public class 上大车Interaction implements DeviceInteraction { @Override public InteractionResult execute(InteractionContext context) { 上大车Config config = context.getConfig(上大车Config.class); try { // 1. 验证前置条件 if (!validatePreCondition(context)) { return InteractionResult.fail("前置条件验证失败"); } // 2. 获取车辆规格(可配置) VehicleSpec vehicle = context.getVehicleSpec(); double vehicleCapacity = config.getVehicleCapacity(); // 默认6000mm // 3. 检查车辆容量 if (vehicle.getMaxCapacity() > vehicleCapacity) { return InteractionResult.fail("车辆容量超出限制: " + vehicle.getMaxCapacity()); } // 4. 获取当前玻璃信息 GlassSpec currentGlass = context.getCurrentGlass(); // 5. 计算装载空间 double usedCapacity = calculateUsedCapacity(context); double remainingCapacity = vehicleCapacity - usedCapacity; if (remainingCapacity >= currentGlass.getLength()) { // 6. 分配车辆空间 allocateVehicleSpace(context, currentGlass); // 7. 节拍控制(可配置间隔时间) sleep(config.getGlassIntervalMs()); // 默认1000ms // 8. 触发PLC写入 triggerPlcWrite(context, "车辆装载", currentGlass); return InteractionResult.success("上大车成功", Map.of("remainingCapacity", remainingCapacity, "allocatedGlass", currentGlass)); } return InteractionResult.wait("等待下一辆车上大车", Map.of("remainingCapacity", remainingCapacity)); } catch (Exception e) { log.error("上大车交互执行失败", e); return InteractionResult.fail("执行异常: " + e.getMessage()); } } @Override public boolean validatePreCondition(InteractionContext context) { // 验证车辆信息、玻璃信息、PLC连接等 return context.getVehicleSpec() != null && context.getCurrentGlass() != null && validatePlcConnection(context.getDeviceId()); } @Override public String getDeviceType() { return "上大车"; } } ``` #### 大理片交互实现 ```java @Component("大理片Interaction") public class 大理片Interaction implements DeviceInteraction { @Override public InteractionResult execute(InteractionContext context) { 大理片Config config = context.getConfig(大理片Config.class); try { // 1. 获取上大车阶段传递的数据 List glassesFromVehicle = context.getSharedData("glassesFromVehicle", List.class); VehicleSpec vehicleInfo = context.getSharedData("vehicleInfo", VehicleSpec.class); if (glassesFromVehicle == null || glassesFromVehicle.isEmpty()) { return InteractionResult.wait("等待上大车数据"); } // 2. 与MES大理片信息比对验证 List mesGlasses = fetchMesGlassesInfo(vehicleInfo.getVehicleId()); for (GlassSpec vehicleGlass : glassesFromVehicle) { boolean matched = false; for (GlassSpec mesGlass : mesGlasses) { if (isGlassMatched(vehicleGlass, mesGlass, config)) { matched = true; break; } } if (!matched && config.isGlassMatchingEnabled()) { return InteractionResult.fail("玻璃信息不匹配: " + vehicleGlass.getGlassId()); } } // 3. 批量处理(可配置) if (config.isBatchProcessing()) { return processBatch(glassesFromVehicle, context, config); } else { return processIndividual(glassesFromVehicle, context, config); } } catch (Exception e) { log.error("大理片交互执行失败", e); return InteractionResult.fail("执行异常: " + e.getMessage()); } } private InteractionResult processBatch(List glasses, InteractionContext context, 大理片Config config) { // 批量处理逻辑 for (GlassSpec glass : glasses) { // 每片玻璃处理间隔 sleep(config.getProcessingInterval()); // 默认2000ms triggerPlcWrite(context, "大理片处理", glass); } // 传递处理结果到下一个设备 context.setSharedData("processedGlasses", glasses); return InteractionResult.success("大理片批量处理完成", Map.of("processedCount", glasses.size())); } } ``` ### 3.4 多设备任务执行引擎 #### 任务执行引擎 ```java @Service public class TaskExecutionEngine { @Resource private DeviceGroupService deviceGroupService; /** * 执行多设备联合测试 */ @Transactional public MultiDeviceTaskResult executeMultiDeviceTask(String groupId, TaskParameters parameters) { DeviceGroup group = deviceGroupService.getDeviceGroupById(groupId); if (group == null) { throw new RuntimeException("设备组不存在: " + groupId); } // 1. 创建任务记录 MultiDeviceTask task = createTaskRecord(group, parameters); try { task.setStatus("RUNNING"); task.setStartTime(new Date()); // 2. 构建交互上下文 InteractionContext context = buildInteractionContext(group, parameters); // 3. 按执行模式执行 MultiDeviceTaskResult result; if (group.getExecutionMode() == ExecutionMode.SERIAL) { result = executeSerialDevices(group, context, task); } else { result = executeParallelDevices(group, context, task); } // 4. 更新任务结果 task.setStatus(result.isSuccess() ? "COMPLETED" : "FAILED"); task.setEndTime(new Date()); task.setResultData(JsonUtils.toJson(result)); return result; } catch (Exception e) { log.error("多设备任务执行失败: {}", groupId, e); task.setStatus("FAILED"); task.setEndTime(new Date()); task.setErrorMessage(e.getMessage()); return MultiDeviceTaskResult.fail("任务执行失败: " + e.getMessage()); } } /** * 串行设备执行:上大车 → 大理片 → 玻璃存储 */ private MultiDeviceTaskResult executeSerialDevices(DeviceGroup group, InteractionContext context, MultiDeviceTask task) { MultiDeviceTaskResult finalResult = new MultiDeviceTaskResult(); List executionSequence = group.getExecutionSequence(); for (int i = 0; i < executionSequence.size(); i++) { DeviceConfig device = executionSequence.get(i); TaskStep step = createTaskStep(task.getTaskId(), i + 1, device); try { step.setStatus("RUNNING"); step.setStartTime(new Date()); // 设置当前设备上下文 context.setCurrentDevice(device); context.setDeviceId(device.getDeviceId()); // 执行设备交互逻辑 DeviceInteraction interaction = getInteraction(device.getDeviceType()); InteractionResult stepResult = interaction.execute(context); step.setEndTime(new Date()); step.setDurationMs(System.currentTimeMillis() - step.getStartTime().getTime()); if (stepResult.isSuccess()) { step.setStatus("COMPLETED"); step.setOutputData(JsonUtils.toJson(stepResult.getData())); // 传递数据到下一个设备 passDataToNextDevice(context, device, stepResult); finalResult.addStepResult(device.getDeviceName(), stepResult); } else { step.setStatus("FAILED"); step.setErrorMessage(stepResult.getMessage()); finalResult.addStepResult(device.getDeviceName(), stepResult); finalResult.fail("设备 " + device.getDeviceName() + " 执行失败: " + stepResult.getMessage()); break; } } catch (Exception e) { step.setStatus("FAILED"); step.setErrorMessage(e.getMessage()); step.setEndTime(new Date()); log.error("设备执行异常: {}", device.getDeviceName(), e); finalResult.fail("设备 " + device.getDeviceName() + " 执行异常: " + e.getMessage()); break; } } return finalResult; } /** * 数据传递到下一个设备 */ private void passDataToNextDevice(InteractionContext context, DeviceConfig currentDevice, InteractionResult result) { String deviceType = currentDevice.getDeviceType(); if ("上大车".equals(deviceType)) { // 上大车 → 大理片:传递玻璃列表和车辆信息 context.setSharedData("glassesFromVehicle", result.getData("glasses")); context.setSharedData("vehicleInfo", result.getData("vehicle")); } else if ("大理片".equals(deviceType)) { // 大理片 → 玻璃存储:传递处理完成的玻璃 context.setSharedData("processedGlasses", result.getData("processedGlasses")); } } } ``` ## 🎨 前端界面设计 ### 4.1 设备管理界面 #### 设备配置页面 ```vue ``` ### 4.2 设备组配置界面 ```vue ``` ### 4.3 多设备测试执行界面 ```vue ``` ## 🚀 实施计划 ### 第一阶段:基础架构搭建(1-2周) 1. **数据库表创建** - 创建设备管理相关表 - 建立表关系和索引 - 迁移现有数据(如需要) 2. **后端基础组件** - 设备配置实体和管理服务 - 设备组管理组件 - 基础交互接口定义 3. **前端基础界面** - 设备管理页面 - 设备组配置页面 - 基础组件开发 ### 第二阶段:核心交互逻辑(2-3周) 1. **设备交互实现** - 上大车交互逻辑 - 大理片交互逻辑 - 玻璃存储交互逻辑 2. **任务执行引擎** - 串行执行引擎 - 多设备协调机制 - 错误处理和重试逻辑 3. **PLC地址映射扩展** - 多设备地址映射支持 - 地址配置管理 - PLC通信适配 ### 第三阶段:前端完善(1-2周) 1. **测试执行界面** - 多设备测试编排 - 实时监控面板 - 结果分析展示 2. **用户交互优化** - 配置向导 - 可视化设备拓扑 - 实时数据流展示 ### 第四阶段:集成测试(1周) 1. **功能测试** - 多设备联合测试流程 - 异常情况处理 - 性能测试 2. **系统集成** - 与现有系统集成 - 数据迁移验证 - 用户验收测试 ## 📊 技术要点总结 ### 核心优势 1. **模块化设计**:每个设备类型独立实现,便于扩展 2. **配置驱动**:所有参数可配置,支持不同业务场景 3. **数据流管理**:设备间数据传递和状态同步 4. **可视化监控**:实时显示设备状态和数据流 5. **灵活扩展**:支持新增设备类型和交互逻辑 ### 技术难点 1. **设备协调**:确保多设备执行的正确顺序 2. **数据同步**:设备间数据的准确传递 3. **异常处理**:单个设备失败对整个流程的影响 4. **性能优化**:大批量数据的处理性能 ### 风险控制 1. **向后兼容**:不破坏现有单设备功能 2. **渐进式升级**:分阶段实施,降低风险 3. **充分测试**:每个阶段的全面测试验证 4. **回滚机制**:出现问题时的快速回滚方案 --- ## 📝 结论 通过这个扩展方案,MES Test Project将具备完整的多设备联合测试能力,支持复杂的生产流程自动化测试。方案基于现有架构设计,风险可控,实施难度适中,能够很好地满足业务需求。 建议优先实现第一阶段的基础架构,然后逐步完善交互逻辑和前端界面,确保每个阶段都能交付可用的功能。