From 1566e4c7604d85737ea67fe6757e71b8185fa48e Mon Sep 17 00:00:00 2001
From: huang <1532065656@qq.com>
Date: 星期二, 18 十一月 2025 16:52:42 +0800
Subject: [PATCH] 添加设备管理页面,添加测试设备任务监控页面
---
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceGroupController.java | 50
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionContext.java | 49
mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DevicePlcVO.java | 7
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGroupRequest.java | 17
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/README.md | 168 +
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/DeviceInteractionService.java | 14
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/MultiDeviceTaskService.java | 42
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceInteractionController.java | 46
mes-processes/mes-plcSend/src/main/java/com/mes/device/util/ConfigJsonHelper.java | 71
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceInteractionServiceImpl.java | 55
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupRelation.java | 33
mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceControlProfile.java | 33
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DevicePlcController.java | 20
mes-web/src/api/device/multiDeviceTask.js | 58
mes-processes/mes-plcSend/src/main/resources/application-dev.yml | 2
mes-web/src/views/plcTest/components/MultiDeviceTest/ExecutionMonitor.vue | 176 +
mes-web/src/api/device/deviceManagement.js | 60
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java | 124 +
mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java | 89
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteractionRegistry.java | 42
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LoadVehicleLogicHandler.java | 372 +++
mes-web/src/views/plcTest/MultiDeviceWorkbench.vue | 67
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionExecution.java | 55
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceControlProfileController.java | 10
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/GlassStorageInteraction.java | 38
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGlassFeedRequest.java | 16
mes-web/src/router/index.js | 10
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java | 160 +
mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java | 66
mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionContext.java | 54
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DevicePlcBatchRequest.java | 7
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteraction.java | 28
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java | 47
mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/TaskStepDetailMapper.java | 13
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LargeGlassLogicHandler.java | 195 +
mes-processes/mes-plcSend/src/main/java/com/mes/device/多设备联合测试扩展方案.md | 1153 ++++++++++
mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcTestWriteServiceImpl.java | 6
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupConfig.java | 37
mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java | 504 +++
mes-web/src/utils/constants.js | 2
mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcAutoTestServiceImpl.java | 8
mes-web/src/views/device/DeviceGroupList.vue | 8
mes-web/src/views/plcTest/components/DeviceGroup/GroupList.vue | 144 +
mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskRequest.java | 32
mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionResult.java | 42
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionResult.java | 60
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DEVICE_CONFIG_FIELDS.md | 152 +
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java | 77
mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/MultiDeviceTaskMapper.java | 13
mes-web/src/views/device/DeviceEditDialog.vue | 485 ++++
mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceConfigRequest.java | 25
mes-web/src/views/device/DeviceGroupEditDialog.vue | 16
mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java | 16
mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcDynamicDataService.java | 49
mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionLogic.java | 45
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandler.java | 49
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/GlassStorageLogicHandler.java | 241 ++
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LoadVehicleInteraction.java | 43
mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java | 299 ++
mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskQuery.java | 26
mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java | 50
mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DevicePlcOperationServiceImpl.java | 17
mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/MultiDeviceTask.java | 85
/dev/null | 238 --
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java | 378 +++
mes-processes/mes-plcSend/src/main/java/com/mes/task/controller/MultiDeviceTaskController.java | 62
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LargeGlassInteraction.java | 52
mes-processes/mes-plcSend/src/main/java/com/mes/config/MybatisMetaObjectHandler.java | 37
mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue | 135 +
69 files changed, 6,297 insertions(+), 583 deletions(-)
diff --git a/mes-processes/mes-plcSend/README_PLC_ADDRESS_MAPPING.md b/mes-processes/mes-plcSend/README_PLC_ADDRESS_MAPPING.md
deleted file mode 100644
index ad7b7cd..0000000
--- a/mes-processes/mes-plcSend/README_PLC_ADDRESS_MAPPING.md
+++ /dev/null
@@ -1,238 +0,0 @@
-# PLC鍦板潃鏄犲皠閰嶇疆鍔熻兘璇存槑
-
-## 姒傝堪
-
-鏈姛鑳戒紭鍖栦簡PLC鍦板潃鏄犲皠閰嶇疆绠$悊锛屾敮鎸佸姩鎬侀厤缃笉鍚岄」鐩殑PLC鍦板潃鏄犲皠锛屾棤闇�涓轰笉鍚岄」鐩垱寤轰笉鍚岀殑YAML閰嶇疆鏂囦欢銆傜郴缁熸敮鎸佷粠鏁版嵁搴撳拰閰嶇疆鏂囦欢涓ょ鏂瑰紡鍔犺浇PLC鍦板潃鏄犲皠閰嶇疆锛屼紭鍏堜娇鐢ㄦ暟鎹簱涓殑閰嶇疆銆�
-
-## 鍔熻兘鐗圭偣
-
-1. **鍔ㄦ�侀厤缃�**锛氭敮鎸侀�氳繃API鎺ュ彛鍔ㄦ�侀厤缃笉鍚岄」鐩殑PLC鍦板潃鏄犲皠
-2. **澶氭暟鎹簮**锛氭敮鎸佷粠鏁版嵁搴撳拰閰嶇疆鏂囦欢鍔犺浇閰嶇疆锛屼紭鍏堜娇鐢ㄦ暟鎹簱閰嶇疆
-3. **缂撳瓨鏈哄埗**锛氱紦瀛樹笉鍚岄」鐩殑S7Serializer瀹炰緥锛屾彁楂樻�ц兘
-4. **瀹屾暣API**锛氭彁渚涘畬鏁寸殑REST API鎺ュ彛锛屾敮鎸佸鍒犳敼鏌ユ搷浣�
-5. **杩炴帴娴嬭瘯**锛氭敮鎸佹祴璇昉LC杩炴帴鏄惁姝e父
-
-## 鏍稿績缁勪欢
-
-### 1. PlcAddressMappingConfig
-- 浣嶇疆锛歚com.mes.config.PlcAddressMappingConfig`
-- 鍔熻兘锛氶厤缃被锛屽畾涔塒LC鍦板潃鏄犲皠閰嶇疆缁撴瀯
-- 鏀寔浠庨厤缃枃浠跺姞杞介粯璁ら厤缃�
-
-### 2. PlcAddressMapping
-- 浣嶇疆锛歚com.mes.entity.PlcAddress`
-- 鍔熻兘锛氬疄浣撶被锛屽搴旀暟鎹簱琛ㄧ粨鏋�
-- 瀛樺偍椤圭洰PLC鍦板潃鏄犲皠閰嶇疆
-
-### 3. PlcAddressMappingMapper
-- 浣嶇疆锛歚com.mes.mapper.PlcAddressMapper`
-- 鍔熻兘锛歁yBatis Mapper鎺ュ彛锛屾彁渚涙暟鎹簱鎿嶄綔
-
-### 4. PlcAddressMappingService
-- 浣嶇疆锛歚com.mes.service.PlcAddressService`
-- 鍔熻兘锛氭湇鍔$被锛屾彁渚汸LC鍦板潃鏄犲皠閰嶇疆鐨勫鍒犳敼鏌ュ姛鑳�
-- 鏀寔浠庢暟鎹簱鍜岄厤缃枃浠跺姞杞介厤缃�
-
-### 5. PlcTestWriteService锛堝凡浼樺寲锛�
-- 浣嶇疆锛歚com.mes.service.PlcTestWriteService`
-- 鍔熻兘锛歅LC娴嬭瘯鍐欏叆鏈嶅姟锛屾敮鎸佸姩鎬侀厤缃甈LC鍦板潃鏄犲皠
-- 缂撳瓨涓嶅悓椤圭洰鐨凷7Serializer瀹炰緥
-
-### 6. PlcAddressMappingController
-- 浣嶇疆锛歚com.mes.controller.PlcAddressController`
-- 鍔熻兘锛氭帶鍒跺櫒锛屾彁渚汻EST API鎺ュ彛
-
-## API鎺ュ彛
-
-### 1. 鑾峰彇鎵�鏈夐厤缃�
-```
-GET /api/plc/address-mapping/list
-```
-
-### 2. 鍒嗛〉鑾峰彇閰嶇疆
-```
-GET /api/plc/address-mapping/page?page=1&size=10&projectId=shuttle&plcIp=192.168.10.21
-```
-
-### 3. 鏍规嵁ID鑾峰彇閰嶇疆
-```
-GET /api/plc/address-mapping/{id}
-```
-
-### 4. 鏍规嵁椤圭洰ID鑾峰彇閰嶇疆
-```
-GET /api/plc/address-mapping/project/{projectId}
-```
-
-### 5. 鏍规嵁椤圭洰ID鑾峰彇椤圭洰閰嶇疆锛堝寘鍚湴鍧�鏄犲皠锛�
-```
-GET /api/plc/address-mapping/project/{projectId}/config
-```
-
-### 6. 鍒涘缓鏂伴厤缃�
-```
-POST /api/plc/address-mapping
-Content-Type: application/json
-
-{
- "projectId": "new_project",
- "projectName": "鏂伴」鐩�",
- "dbArea": "DB1",
- "beginIndex": 0,
- "plcIp": "192.168.10.22",
- "plcType": "S1200",
- "description": "鏂伴」鐩厤缃�"
-}
-```
-
-### 7. 鏇存柊閰嶇疆
-```
-PUT /api/plc/address-mapping/{id}
-Content-Type: application/json
-
-{
- "projectId": "updated_project",
- "projectName": "鏇存柊椤圭洰",
- "dbArea": "DB2",
- "beginIndex": 10,
- "plcIp": "192.168.10.23",
- "plcType": "S1500",
- "description": "鏇存柊椤圭洰閰嶇疆"
-}
-```
-
-### 8. 鍒犻櫎閰嶇疆
-```
-DELETE /api/plc/address-mapping/{id}
-```
-
-### 9. 鎵归噺鍒犻櫎閰嶇疆
-```
-DELETE /api/plc/address-mapping/batch
-Content-Type: application/json
-
-[1, 2, 3]
-```
-
-### 10. 娴嬭瘯PLC杩炴帴
-```
-POST /api/plc/address-mapping/{id}/test-connection
-```
-
-### 11. 閲嶆柊鍔犺浇閰嶇疆鏂囦欢
-```
-POST /api/plc/address-mapping/reload-config
-```
-
-## 閰嶇疆鏂囦欢绀轰緥
-
-鍦╜application-dev.yml`涓坊鍔犱互涓嬮厤缃細
-
-```yaml
-plc:
- address:
- mapping:
- # 榛樿DB鍧楀湴鍧�鍜岃捣濮嬬储寮�
- defaultDbArea: "DB1"
- defaultBeginIndex: 0
-
- # 椤圭洰鐗瑰畾閰嶇疆
- projects:
- shuttle:
- dbArea: "DB38"
- beginIndex: 0
- plcIp: "192.168.10.21"
- plcType: "S1200"
- addressMapping:
- onlineState: "0"
- plcRequest: "2"
- plcReport: "4"
- mesSend: "6"
- mesConfirm: "8"
- mesGlassCount: "10"
- alarmInfo: "12"
-```
-
-## 浣跨敤绀轰緥
-
-### 1. 鍦ㄤ唬鐮佷腑浣跨敤鍔ㄦ�侀厤缃�
-
-```java
-@Service
-public class SomeService {
-
- @Autowired
- private PlcTestWriteService plcTestWriteService;
-
- public void doSomething() {
- // 璁剧疆褰撳墠椤圭洰
- plcTestWriteService.setCurrentProjectId("shuttle");
-
- // 浣跨敤褰撳墠椤圭洰閰嶇疆鎵ц鎿嶄綔
- plcTestWriteService.simulatePlcRequest();
-
- // 鎴栬�呯洿鎺ユ寚瀹氶」鐩�
- plcTestWriteService.simulatePlcRequest("another_project");
- }
-}
-```
-
-### 2. 閫氳繃API绠$悊閰嶇疆
-
-```bash
-# 鍒涘缓鏂伴」鐩厤缃�
-curl -X POST http://localhost:8080/api/plc/address-mapping \
- -H "Content-Type: application/json" \
- -d '{
- "projectId": "new_project",
- "projectName": "鏂伴」鐩�",
- "dbArea": "DB1",
- "beginIndex": 0,
- "plcIp": "192.168.10.22",
- "plcType": "S1200"
- }'
-
-# 鑾峰彇椤圭洰閰嶇疆
-curl http://localhost:8080/api/plc/address-mapping/project/new_project/config
-
-# 娴嬭瘯杩炴帴
-curl -X POST http://localhost:8080/api/plc/address-mapping/1/test-connection
-```
-
-## 鏁版嵁搴撹〃缁撴瀯
-
-```sql
-CREATE TABLE `plc_address_mapping` (
- `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
- `project_id` varchar(50) NOT NULL COMMENT '椤圭洰鏍囪瘑',
- `project_name` varchar(100) DEFAULT NULL COMMENT '椤圭洰鍚嶇О',
- `db_area` varchar(20) NOT NULL COMMENT 'DB鍧楀湴鍧�锛屽DB1',
- `begin_index` int(11) NOT NULL DEFAULT '0' COMMENT '璧峰绱㈠紩',
- `plc_ip` varchar(50) DEFAULT NULL COMMENT 'PLC IP鍦板潃',
- `plc_type` varchar(20) DEFAULT 'S1200' COMMENT 'PLC绫诲瀷',
- `address_mapping_json` text COMMENT '鍦板潃鏄犲皠JSON閰嶇疆',
- `description` varchar(255) DEFAULT NULL COMMENT '鎻忚堪',
- `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '鏄惁鍚敤锛�1-鍚敤锛�0-绂佺敤',
- `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
- `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
- `create_by` varchar(50) DEFAULT NULL COMMENT '鍒涘缓浜�',
- `update_by` varchar(50) DEFAULT NULL COMMENT '鏇存柊浜�',
- PRIMARY KEY (`id`),
- UNIQUE KEY `uk_project_id` (`project_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='PLC鍦板潃鏄犲皠閰嶇疆琛�';
-```
-
-## 娉ㄦ剰浜嬮」
-
-1. **閰嶇疆浼樺厛绾�**锛氭暟鎹簱涓殑閰嶇疆浼樺厛绾ч珮浜庨厤缃枃浠朵腑鐨勯厤缃�
-2. **缂撳瓨绠$悊**锛氬綋閰嶇疆鏇存柊鍚庯紝闇�瑕佹竻闄ゅ搴旂殑S7Serializer缂撳瓨
-3. **杩炴帴娴嬭瘯**锛氭祴璇曡繛鎺ュ姛鑳戒粎楠岃瘉鍙傛暟鏄惁姝g‘锛屼笉瀹為檯杩炴帴PLC
-4. **瀹夊叏鎬�**锛氬湪鐢熶骇鐜涓紝搴旀坊鍔犻�傚綋鐨勬潈闄愭帶鍒跺拰鍙傛暟楠岃瘉
-
-## 鎵╁睍寤鸿
-
-1. **娣诲姞閰嶇疆鐗堟湰鎺у埗**锛氭敮鎸侀厤缃殑鐗堟湰绠$悊鍜屽洖婊�
-2. **娣诲姞閰嶇疆鍙樻洿閫氱煡**锛氬綋閰嶇疆鍙樻洿鏃讹紝閫氱煡鐩稿叧鏈嶅姟
-3. **娣诲姞閰嶇疆瀵煎叆瀵煎嚭**锛氭敮鎸侀厤缃殑鎵归噺瀵煎叆瀵煎嚭
-4. **娣诲姞閰嶇疆妯℃澘**锛氭敮鎸佸垱寤洪厤缃ā鏉匡紝绠�鍖栨柊椤圭洰閰嶇疆
-5. **娣诲姞閰嶇疆楠岃瘉**锛氬寮洪厤缃弬鏁扮殑楠岃瘉瑙勫垯
\ No newline at end of file
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/config/MybatisMetaObjectHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/config/MybatisMetaObjectHandler.java
new file mode 100644
index 0000000..1376009
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/config/MybatisMetaObjectHandler.java
@@ -0,0 +1,37 @@
+package com.mes.config;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * 缁熶竴澶勭悊鍏叡瀛楁锛坈reated_time銆乽pdated_time銆乧reated_by銆乽pdated_by锛夌殑鑷姩濉厖
+ */
+@Component
+public class MybatisMetaObjectHandler implements MetaObjectHandler {
+
+ @Override
+ public void insertFill(MetaObject metaObject) {
+ LocalDateTime now = LocalDateTime.now();
+ String operator = resolveOperator();
+
+ strictInsertFill(metaObject, "createdTime", LocalDateTime.class, now);
+ strictInsertFill(metaObject, "updatedTime", LocalDateTime.class, now);
+ strictInsertFill(metaObject, "createdBy", String.class, operator);
+ strictInsertFill(metaObject, "updatedBy", String.class, operator);
+ }
+
+ @Override
+ public void updateFill(MetaObject metaObject) {
+ strictUpdateFill(metaObject, "updatedTime", LocalDateTime.class, LocalDateTime.now());
+ strictUpdateFill(metaObject, "updatedBy", String.class, resolveOperator());
+ }
+
+ private String resolveOperator() {
+ // TODO: 涔嬪悗鍙帴鍏ョ櫥褰曚笂涓嬫枃锛岃繖閲屼复鏃跺洖閫�涓� system
+ return "system";
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java
index 7a57917..c1f5da1 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceConfigController.java
@@ -1,15 +1,16 @@
package com.mes.device.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.mes.device.entity.DeviceConfig;
import com.mes.device.request.DeviceConfigRequest;
import com.mes.device.service.DeviceConfigService;
import com.mes.device.vo.DeviceConfigVO;
import com.mes.device.vo.StatisticsVO;
import com.mes.vo.Result;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -27,17 +28,20 @@
@Slf4j
@RestController
@RequestMapping("device/config")
-@Tag(name = "璁惧閰嶇疆绠$悊", description = "璁惧閰嶇疆绠$悊鐩稿叧鎺ュ彛")
+@Api(tags = "璁惧閰嶇疆绠$悊")
public class DeviceConfigController {
@Autowired
private DeviceConfigService deviceConfigService;
+ @Autowired
+ private ObjectMapper objectMapper;
+
/**
* 鍒涘缓璁惧閰嶇疆
*/
@PostMapping("/devices")
- @Operation(summary = "鍒涘缓璁惧閰嶇疆", description = "鍒涘缓鏂扮殑璁惧閰嶇疆")
+ @ApiOperation("鍒涘缓璁惧閰嶇疆")
public Result<DeviceConfig> createDevice(
@Valid @RequestBody DeviceConfig deviceConfig) {
try {
@@ -59,11 +63,23 @@
* 鏇存柊璁惧閰嶇疆
*/
@PostMapping("/devices/update")
- @Operation(summary = "鏇存柊璁惧閰嶇疆", description = "鏇存柊鎸囧畾ID鐨勮澶囬厤缃�")
+ @ApiOperation("鏇存柊璁惧閰嶇疆")
public Result<DeviceConfig> updateDevice(
@Valid @RequestBody DeviceConfigRequest request) {
try {
- DeviceConfig deviceConfig = (DeviceConfig) request.getDeviceConfig();
+ DeviceConfig deviceConfig;
+ Object deviceConfigObj = request.getDeviceConfig();
+
+ // 濡傛灉 deviceConfig 鏄� Map 绫诲瀷锛圝SON 鍙嶅簭鍒楀寲鍚庣殑 LinkedHashMap锛夛紝闇�瑕佽浆鎹负 DeviceConfig
+ if (deviceConfigObj instanceof Map) {
+ deviceConfig = objectMapper.convertValue(deviceConfigObj, DeviceConfig.class);
+ } else if (deviceConfigObj instanceof DeviceConfig) {
+ deviceConfig = (DeviceConfig) deviceConfigObj;
+ } else {
+ log.error("涓嶆敮鎸佺殑 deviceConfig 绫诲瀷: {}", deviceConfigObj != null ? deviceConfigObj.getClass() : "null");
+ return Result.error("璁惧閰嶇疆鏁版嵁鏍煎紡閿欒");
+ }
+
deviceConfig.setId(request.getDeviceId());
boolean success = deviceConfigService.updateDevice(deviceConfig);
if (success) {
@@ -75,7 +91,7 @@
}
} catch (Exception e) {
log.error("鏇存柊璁惧閰嶇疆澶辫触", e);
- return Result.error("鏇存柊璁惧閰嶇疆澶辫触");
+ return Result.error("鏇存柊璁惧閰嶇疆澶辫触: " + e.getMessage());
}
}
@@ -83,7 +99,7 @@
* 鍒犻櫎璁惧閰嶇疆
*/
@PostMapping("/devices/delete")
- @Operation(summary = "鍒犻櫎璁惧閰嶇疆", description = "鍒犻櫎鎸囧畾ID鐨勮澶囬厤缃�")
+ @ApiOperation("鍒犻櫎璁惧閰嶇疆")
public Result<Void> deleteDevice(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -99,7 +115,7 @@
* 鏍规嵁ID鑾峰彇璁惧閰嶇疆
*/
@PostMapping("/devices/detail")
- @Operation(summary = "鑾峰彇璁惧閰嶇疆璇︽儏", description = "鏍规嵁ID鑾峰彇璁惧閰嶇疆鐨勮缁嗕俊鎭�")
+ @ApiOperation("鑾峰彇璁惧閰嶇疆璇︽儏")
public Result<DeviceConfig> getDeviceById(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -115,7 +131,7 @@
* 鍒嗛〉鏌ヨ璁惧閰嶇疆鍒楄〃
*/
@PostMapping("/devices/list")
- @Operation(summary = "鍒嗛〉鏌ヨ璁惧閰嶇疆", description = "鍒嗛〉鏌ヨ璁惧閰嶇疆鍒楄〃")
+ @ApiOperation("鍒嗛〉鏌ヨ璁惧閰嶇疆")
public Result<Page<DeviceConfigVO.DeviceInfo>> getDeviceList(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -137,7 +153,7 @@
* 鍚敤璁惧
*/
@PostMapping("/devices/enable")
- @Operation(summary = "鍚敤璁惧", description = "鍚敤鎸囧畾ID鐨勮澶�")
+ @ApiOperation("鍚敤璁惧")
public Result<Void> enableDevice(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -153,7 +169,7 @@
* 绂佺敤璁惧
*/
@PostMapping("/devices/disable")
- @Operation(summary = "绂佺敤璁惧", description = "绂佺敤鎸囧畾ID鐨勮澶�")
+ @ApiOperation("绂佺敤璁惧")
public Result<Void> disableDevice(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -169,7 +185,7 @@
* 鎵归噺鍚敤璁惧
*/
@PostMapping("/devices/batch-enable")
- @Operation(summary = "鎵归噺鍚敤璁惧", description = "鎵归噺鍚敤鎸囧畾ID鍒楄〃鐨勮澶�")
+ @ApiOperation("鎵归噺鍚敤璁惧")
public Result<Void> batchEnableDevices(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -185,7 +201,7 @@
* 鎵归噺绂佺敤璁惧
*/
@PostMapping("/devices/batch-disable")
- @Operation(summary = "鎵归噺绂佺敤璁惧", description = "鎵归噺绂佺敤鎸囧畾ID鍒楄〃鐨勮澶�")
+ @ApiOperation("鎵归噺绂佺敤璁惧")
public Result<Void> batchDisableDevices(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -201,9 +217,9 @@
* 鑾峰彇璁惧缁熻淇℃伅
*/
@PostMapping("/statistics/devices")
- @Operation(summary = "鑾峰彇璁惧缁熻淇℃伅", description = "鑾峰彇璁惧鐩稿叧鐨勭粺璁′俊鎭�")
+ @ApiOperation("鑾峰彇璁惧缁熻淇℃伅")
public Result<StatisticsVO.DeviceStatistics> getDeviceStatistics(
- @Parameter(description = "璁惧閰嶇疆璇锋眰") @RequestBody(required = false) DeviceConfigRequest request) {
+ @ApiParam("璁惧閰嶇疆璇锋眰") @RequestBody(required = false) DeviceConfigRequest request) {
try {
StatisticsVO.DeviceStatistics statistics = deviceConfigService.getDeviceStatistics(request != null ? request.getProjectId() : null);
return Result.success(statistics);
@@ -217,9 +233,9 @@
* 妫�鏌ヨ澶囩紪鐮佹槸鍚﹀凡瀛樺湪
*/
@PostMapping("/devices/check-code")
- @Operation(summary = "妫�鏌ヨ澶囩紪鐮�", description = "妫�鏌ヨ澶囩紪鐮佹槸鍚﹀凡瀛樺湪")
+ @ApiOperation("妫�鏌ヨ澶囩紪鐮�")
public Result<Boolean> checkDeviceCodeExists(
- @Parameter(description = "璁惧閰嶇疆璇锋眰") @RequestBody DeviceConfigRequest request) {
+ @ApiParam("璁惧閰嶇疆璇锋眰") @RequestBody DeviceConfigRequest request) {
try {
boolean exists = deviceConfigService.isDeviceCodeExists(request.getDeviceCode(), request.getDeviceId());
return Result.success(exists);
@@ -233,7 +249,7 @@
* 鑾峰彇璁惧绫诲瀷鍒楄〃
*/
@PostMapping("/devices/types")
- @Operation(summary = "鑾峰彇璁惧绫诲瀷鍒楄〃", description = "鑾峰彇鎵�鏈夊彲鐢ㄧ殑璁惧绫诲瀷")
+ @ApiOperation("鑾峰彇璁惧绫诲瀷鍒楄〃")
public Result<List<String>> getDeviceTypes(@RequestBody(required = false) Map<String, Object> request) {
try {
List<String> deviceTypes = deviceConfigService.getAllDeviceTypes();
@@ -248,7 +264,7 @@
* 鑾峰彇璁惧鐘舵�佸垪琛�
*/
@PostMapping("/devices/statuses")
- @Operation(summary = "鑾峰彇璁惧鐘舵�佸垪琛�", description = "鑾峰彇鎵�鏈夊彲鐢ㄧ殑璁惧鐘舵��")
+ @ApiOperation("鑾峰彇璁惧鐘舵�佸垪琛�")
public Result<List<String>> getDeviceStatuses(@RequestBody(required = false) Map<String, Object> request) {
try {
List<String> deviceStatuses = deviceConfigService.getAllDeviceStatuses();
@@ -263,9 +279,9 @@
* 鑾峰彇璁惧閰嶇疆鏍戠粨鏋�
*/
@PostMapping("/devices/tree")
- @Operation(summary = "鑾峰彇璁惧閰嶇疆鏍戠粨鏋�", description = "鑾峰彇璁惧鍜岃澶囩粍鐨勬爲褰㈢粨鏋勬暟鎹�")
+ @ApiOperation("鑾峰彇璁惧閰嶇疆鏍戠粨鏋�")
public Result<List<DeviceConfigVO.DeviceTreeNode>> getDeviceTree(
- @Parameter(description = "璁惧閰嶇疆璇锋眰") @RequestBody(required = false) DeviceConfigRequest request) {
+ @ApiParam("璁惧閰嶇疆璇锋眰") @RequestBody(required = false) DeviceConfigRequest request) {
try {
List<DeviceConfigVO.DeviceTreeNode> treeData = deviceConfigService.getDeviceTree(request != null ? request.getProjectId() : null);
return Result.success(treeData);
@@ -279,7 +295,7 @@
* 璁惧鍋ュ悍妫�鏌�
*/
@PostMapping("/devices/health-check")
- @Operation(summary = "璁惧鍋ュ悍妫�鏌�", description = "瀵规寚瀹氳澶囪繘琛屽仴搴锋鏌�")
+ @ApiOperation("璁惧鍋ュ悍妫�鏌�")
public Result<DeviceConfigVO.HealthCheckResult> performHealthCheck(
@Valid @RequestBody DeviceConfigRequest request) {
try {
@@ -298,7 +314,7 @@
* 2. 鐩存帴浼犲叆 plcIp / plcPort / timeout 杩涜涓�娆℃�ф祴璇�
*/
@PostMapping("/devices/test-connection")
- @Operation(summary = "娴嬭瘯璁惧PLC杩炴帴", description = "鏍规嵁璁惧閰嶇疆娴嬭瘯PLC杩炴帴鏄惁鍙揪")
+ @ApiOperation("娴嬭瘯璁惧PLC杩炴帴")
public Result<String> testDeviceConnection(@RequestBody Map<String, Object> body) {
try {
String plcIp = null;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceControlProfileController.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceControlProfileController.java
index 1ebc89b..6dcb2f7 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceControlProfileController.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceControlProfileController.java
@@ -3,15 +3,15 @@
import com.mes.device.service.DeviceControlProfileService;
import com.mes.device.vo.DeviceControlProfile;
import com.mes.vo.Result;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("device/control")
-@Tag(name = "璁惧鎺у埗鍙傛暟", description = "璁惧鎺у埗鍙傛暟閰嶇疆鎺ュ彛")
+@Api(tags = "璁惧鎺у埗鍙傛暟")
@RequiredArgsConstructor
@Validated
public class DeviceControlProfileController {
@@ -19,13 +19,13 @@
private final DeviceControlProfileService controlProfileService;
@GetMapping("/{deviceId}")
- @Operation(summary = "鑾峰彇璁惧鎺у埗鍙傛暟")
+ @ApiOperation("鑾峰彇璁惧鎺у埗鍙傛暟")
public Result<DeviceControlProfile> getProfile(@PathVariable Long deviceId) {
return Result.success(controlProfileService.getProfile(deviceId));
}
@PostMapping("/{deviceId}")
- @Operation(summary = "鏇存柊璁惧鎺у埗鍙傛暟")
+ @ApiOperation("鏇存柊璁惧鎺у埗鍙傛暟")
public Result<Void> saveProfile(@PathVariable Long deviceId,
@RequestBody DeviceControlProfile profile) {
controlProfileService.updateProfile(deviceId, profile);
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceGroupController.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceGroupController.java
index 88e657c..37ea229 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceGroupController.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceGroupController.java
@@ -8,8 +8,8 @@
import com.mes.device.vo.DeviceGroupVO;
import com.mes.device.vo.StatisticsVO;
import com.mes.vo.Result;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -29,7 +29,7 @@
@Slf4j
@RestController
@RequestMapping("device/group")
-@Tag(name = "璁惧缁勭鐞�", description = "璁惧缁勭鐞嗙浉鍏虫帴鍙�")
+@Api(tags = "璁惧缁勭鐞�")
public class DeviceGroupController {
@Resource
@@ -42,7 +42,7 @@
* 鍒涘缓璁惧缁�
*/
@PostMapping("/create")
- @Operation(summary = "鍒涘缓璁惧缁�", description = "鍒涘缓璁惧缁勪俊鎭�")
+ @ApiOperation("鍒涘缓璁惧缁�")
public Result<DeviceGroupConfig> createGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -65,7 +65,7 @@
* 鏇存柊璁惧缁勯厤缃�
*/
@PostMapping("/update")
- @Operation(summary = "鏇存柊璁惧缁勯厤缃�", description = "鏇存柊鎸囧畾ID鐨勮澶囩粍閰嶇疆")
+ @ApiOperation("鏇存柊璁惧缁勯厤缃�")
public Result<DeviceGroupConfig> updateGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -89,7 +89,7 @@
* 鍒犻櫎璁惧缁勯厤缃�
*/
@PostMapping("/delete")
- @Operation(summary = "鍒犻櫎璁惧缁勯厤缃�", description = "鍒犻櫎鎸囧畾ID鐨勮澶囩粍閰嶇疆")
+ @ApiOperation("鍒犻櫎璁惧缁勯厤缃�")
public Result<Void> deleteGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -105,7 +105,7 @@
* 鏍规嵁ID鑾峰彇璁惧缁勯厤缃�
*/
@PostMapping("/detail")
- @Operation(summary = "鑾峰彇璁惧缁勯厤缃鎯�", description = "鏍规嵁ID鑾峰彇璁惧缁勯厤缃殑璇︾粏淇℃伅")
+ @ApiOperation("鑾峰彇璁惧缁勯厤缃鎯�")
public Result<DeviceGroupConfig> getGroupById(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -121,7 +121,7 @@
* 鍒嗛〉鏌ヨ璁惧缁勫垪琛�
*/
@PostMapping("/list")
- @Operation(summary = "鍒嗛〉鏌ヨ璁惧缁勫垪琛�", description = "鍒嗛〉鏌ヨ璁惧缁勫垪琛�")
+ @ApiOperation("鍒嗛〉鏌ヨ璁惧缁勫垪琛�")
public Result<Page<DeviceGroupVO.GroupInfo>> getGroupList(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -146,7 +146,7 @@
* 鍚敤璁惧缁�
*/
@PostMapping("/enable")
- @Operation(summary = "鍚敤璁惧缁�", description = "鍚敤鎸囧畾璁惧缁�")
+ @ApiOperation("鍚敤璁惧缁�")
public Result<Void> enableGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -162,7 +162,7 @@
* 绂佺敤璁惧缁�
*/
@PostMapping("/disable")
- @Operation(summary = "绂佺敤璁惧缁�", description = "绂佺敤鎸囧畾璁惧缁�")
+ @ApiOperation("绂佺敤璁惧缁�")
public Result<Void> disableGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -178,7 +178,7 @@
* 鎵归噺鍚敤璁惧缁�
*/
@PostMapping("/batch-enable")
- @Operation(summary = "鎵归噺鍚敤璁惧缁�", description = "鎵归噺鍚敤鎸囧畾ID鍒楄〃鐨勮澶囩粍")
+ @ApiOperation("鎵归噺鍚敤璁惧缁�")
public Result<Void> batchEnableGroups(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -194,7 +194,7 @@
* 鎵归噺绂佺敤璁惧缁�
*/
@PostMapping("/batch-disable")
- @Operation(summary = "鎵归噺绂佺敤璁惧缁�", description = "鎵归噺绂佺敤鎸囧畾ID鍒楄〃鐨勮澶囩粍")
+ @ApiOperation("鎵归噺绂佺敤璁惧缁�")
public Result<Void> batchDisableGroups(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -210,7 +210,7 @@
* 鑾峰彇璁惧缁勭粺璁′俊鎭�
*/
@PostMapping("/statistics/groups")
- @Operation(summary = "鑾峰彇璁惧缁勭粺璁′俊鎭�", description = "鑾峰彇璁惧缁勭浉鍏崇殑缁熻淇℃伅")
+ @ApiOperation("鑾峰彇璁惧缁勭粺璁′俊鎭�")
public Result<StatisticsVO.GroupStatistics> getGroupStatistics(
@RequestBody(required = false) Map<String, Object> request) {
try {
@@ -227,7 +227,7 @@
* 妫�鏌ヨ澶囩粍缂栫爜鏄惁宸插瓨鍦�
*/
@PostMapping("/check-code")
- @Operation(summary = "妫�鏌ヨ澶囩粍缂栫爜", description = "妫�鏌ヨ澶囩粍缂栫爜鏄惁宸插瓨鍦�")
+ @ApiOperation("妫�鏌ヨ澶囩粍缂栫爜")
public Result<Boolean> checkGroupCodeExists(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -247,7 +247,7 @@
* 鑾峰彇璁惧缁勭被鍨嬪垪琛�
*/
@PostMapping("/types")
- @Operation(summary = "鑾峰彇璁惧缁勭被鍨嬪垪琛�", description = "鑾峰彇鎵�鏈夊彲鐢ㄧ殑璁惧缁勭被鍨�")
+ @ApiOperation("鑾峰彇璁惧缁勭被鍨嬪垪琛�")
public Result<List<String>> getGroupTypes() {
try {
List<String> groupTypes = deviceGroupConfigService.getAllGroupTypes();
@@ -262,7 +262,7 @@
* 鑾峰彇璁惧缁勭姸鎬佸垪琛�
*/
@PostMapping("/statuses")
- @Operation(summary = "鑾峰彇璁惧缁勭姸鎬佸垪琛�", description = "鑾峰彇鎵�鏈夊彲鐢ㄧ殑璁惧缁勭姸鎬�")
+ @ApiOperation("鑾峰彇璁惧缁勭姸鎬佸垪琛�")
public Result<List<String>> getGroupStatuses() {
try {
List<String> groupStatuses = deviceGroupConfigService.getAllGroupStatuses();
@@ -277,7 +277,7 @@
* 娣诲姞璁惧鍒拌澶囩粍
*/
@PostMapping("/devices")
- @Operation(summary = "娣诲姞璁惧鍒拌澶囩粍", description = "灏嗘寚瀹氳澶囨坊鍔犲埌璁惧缁勪腑")
+ @ApiOperation("娣诲姞璁惧鍒拌澶囩粍")
public Result<Void> addDeviceToGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -294,7 +294,7 @@
* 浠庤澶囩粍绉婚櫎璁惧
*/
@PostMapping("/devices/remove")
- @Operation(summary = "浠庤澶囩粍绉婚櫎璁惧", description = "浠庤澶囩粍涓Щ闄ゆ寚瀹氳澶�")
+ @ApiOperation("浠庤澶囩粍绉婚櫎璁惧")
public Result<Void> removeDeviceFromGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -310,7 +310,7 @@
* 鏇存柊璁惧瑙掕壊
*/
@PostMapping("/devices/role")
- @Operation(summary = "鏇存柊璁惧瑙掕壊", description = "鏇存柊璁惧鍦ㄨ澶囩粍涓殑瑙掕壊")
+ @ApiOperation("鏇存柊璁惧瑙掕壊")
public Result<Void> updateDeviceRole(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -327,7 +327,7 @@
* 鑾峰彇璁惧缁勮澶囧垪琛�
*/
@PostMapping("/devices/list")
- @Operation(summary = "鑾峰彇璁惧缁勮澶囧垪琛�", description = "鑾峰彇鎸囧畾璁惧缁勪笅鐨勬墍鏈夎澶�")
+ @ApiOperation("鑾峰彇璁惧缁勮澶囧垪琛�")
public Result<List<DeviceGroupVO.DeviceInfo>> getGroupDevices(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -343,7 +343,7 @@
* 鑾峰彇璁惧璁惧缁勫垪琛�
*/
@PostMapping("/devices/groups")
- @Operation(summary = "鑾峰彇璁惧璁惧缁勫垪琛�", description = "鑾峰彇鎸囧畾璁惧鎵�灞炵殑鎵�鏈夎澶囩粍")
+ @ApiOperation("鑾峰彇璁惧璁惧缁勫垪琛�")
public Result<List<DeviceGroupVO.GroupInfo>> getDeviceGroups(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -359,7 +359,7 @@
* 鎵归噺娣诲姞璁惧鍒拌澶囩粍
*/
@PostMapping("/batch-add-devices")
- @Operation(summary = "鎵归噺娣诲姞璁惧鍒拌澶囩粍", description = "鎵归噺灏嗘寚瀹氳澶囧垪琛ㄦ坊鍔犲埌璁惧缁勪腑")
+ @ApiOperation("鎵归噺娣诲姞璁惧鍒拌澶囩粍")
public Result<Void> batchAddDevicesToGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -375,7 +375,7 @@
* 鎵归噺浠庤澶囩粍绉婚櫎璁惧
*/
@PostMapping("/devices/batch-remove")
- @Operation(summary = "鎵归噺浠庤澶囩粍绉婚櫎璁惧", description = "鎵归噺浠庤澶囩粍涓Щ闄ゆ寚瀹氳澶囧垪琛�")
+ @ApiOperation("鎵归噺浠庤澶囩粍绉婚櫎璁惧")
public Result<Void> batchRemoveDevicesFromGroup(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -391,7 +391,7 @@
* 璁惧缁勫仴搴锋鏌�
*/
@PostMapping("/health-check")
- @Operation(summary = "璁惧缁勫仴搴锋鏌�", description = "瀵规寚瀹氳澶囩粍杩涜鍋ュ悍妫�鏌�")
+ @ApiOperation("璁惧缁勫仴搴锋鏌�")
public Result<DeviceGroupVO.HealthCheckResult> performGroupHealthCheck(
@Valid @RequestBody DeviceGroupRequest request) {
try {
@@ -407,7 +407,7 @@
* 鑾峰彇璁惧缁勬�ц兘缁熻
*/
@PostMapping("/performance")
- @Operation(summary = "鑾峰彇璁惧缁勬�ц兘缁熻", description = "鑾峰彇鎸囧畾璁惧缁勭殑鎬ц兘缁熻淇℃伅")
+ @ApiOperation("鑾峰彇璁惧缁勬�ц兘缁熻")
public Result<DeviceGroupVO.PerformanceStats> getGroupPerformance(
@Valid @RequestBody DeviceGroupRequest request) {
try {
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceInteractionController.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceInteractionController.java
index b5655ae..f38af62 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceInteractionController.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DeviceInteractionController.java
@@ -4,20 +4,22 @@
import com.mes.device.service.DeviceInteractionService;
import com.mes.device.vo.DevicePlcVO;
import com.mes.vo.Result;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+import java.util.Map;
@RestController
@RequestMapping("device/interaction")
-@Tag(name = "璁惧浜や簰", description = "璁惧浜や簰閫昏緫鎵ц鎺ュ彛")
+@Api(tags = "璁惧浜や簰")
@Validated
@RequiredArgsConstructor
public class DeviceInteractionController {
@@ -25,9 +27,37 @@
private final DeviceInteractionService deviceInteractionService;
@PostMapping("/glass-feed")
- @Operation(summary = "鐜荤拑涓婃枡鍐欏叆")
+ @ApiOperation("鐜荤拑涓婃枡鍐欏叆")
public Result<DevicePlcVO.OperationResult> feedGlass(@Valid @RequestBody DeviceGlassFeedRequest request) {
return Result.success(deviceInteractionService.feedGlass(request));
}
+
+ @PostMapping("/execute")
+ @ApiOperation("鎵ц璁惧閫昏緫鎿嶄綔")
+ public Result<DevicePlcVO.OperationResult> executeOperation(
+ @Valid @RequestBody DeviceOperationRequest request) {
+ return Result.success(deviceInteractionService.executeOperation(
+ request.getDeviceId(),
+ request.getOperation(),
+ request.getParams()
+ ));
+ }
+
+ /**
+ * 璁惧鎿嶄綔璇锋眰
+ */
+ @Data
+ public static class DeviceOperationRequest {
+ @NotNull(message = "璁惧ID涓嶈兘涓虹┖")
+ @ApiParam(value = "璁惧ID", required = true)
+ private Long deviceId;
+
+ @NotNull(message = "鎿嶄綔绫诲瀷涓嶈兘涓虹┖")
+ @ApiParam(value = "鎿嶄綔绫诲瀷锛堝锛歠eedGlass, triggerRequest, triggerReport绛夛級", required = true)
+ private String operation;
+
+ @ApiParam(value = "鎿嶄綔鍙傛暟")
+ private Map<String, Object> params;
+ }
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DevicePlcController.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DevicePlcController.java
index d048db9..27aae25 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DevicePlcController.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/controller/DevicePlcController.java
@@ -4,8 +4,8 @@
import com.mes.device.service.DevicePlcOperationService;
import com.mes.device.vo.DevicePlcVO;
import com.mes.vo.Result;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
@@ -25,54 +25,54 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("device/plc")
-@Tag(name = "璁惧PLC鎿嶄綔", description = "澶氳澶嘝LC鍐欏叆涓庣姸鎬佹煡璇㈡帴鍙�")
+@Api(tags = "璁惧PLC鎿嶄綔")
public class DevicePlcController {
private final DevicePlcOperationService devicePlcOperationService;
@PostMapping("/requests")
- @Operation(summary = "鎵归噺瑙﹀彂PLC璇锋眰", description = "瀵规寚瀹氳澶囧彂閫丳LC璇锋眰瀛�")
+ @ApiOperation("鎵归噺瑙﹀彂PLC璇锋眰")
public Result<List<DevicePlcVO.OperationResult>> triggerRequests(
@Valid @RequestBody DevicePlcBatchRequest request) {
return Result.success(devicePlcOperationService.triggerRequest(request.getDeviceIds()));
}
@PostMapping("/reports")
- @Operation(summary = "鎵归噺妯℃嫙PLC姹囨姤", description = "瀵规寚瀹氳澶囨ā鎷烶LC浠诲姟瀹屾垚姹囨姤")
+ @ApiOperation("鎵归噺妯℃嫙PLC姹囨姤")
public Result<List<DevicePlcVO.OperationResult>> triggerReports(
@Valid @RequestBody DevicePlcBatchRequest request) {
return Result.success(devicePlcOperationService.triggerReport(request.getDeviceIds()));
}
@PostMapping("/resets")
- @Operation(summary = "鎵归噺閲嶇疆PLC鐘舵��", description = "閲嶇疆鎸囧畾璁惧鍏宠仈PLC鐨勫叧閿瓧娈�")
+ @ApiOperation("鎵归噺閲嶇疆PLC鐘舵��")
public Result<List<DevicePlcVO.OperationResult>> resetPlc(
@Valid @RequestBody DevicePlcBatchRequest request) {
return Result.success(devicePlcOperationService.resetDevices(request.getDeviceIds()));
}
@PostMapping("/groups/{groupId}/request")
- @Operation(summary = "璁惧缁勮Е鍙慞LC璇锋眰", description = "瀵硅澶囩粍鍐呮墍鏈夎澶囧彂閫丳LC璇锋眰瀛�")
+ @ApiOperation("璁惧缁勮Е鍙慞LC璇锋眰")
public Result<List<DevicePlcVO.OperationResult>> triggerGroupRequest(
@PathVariable Long groupId) {
return Result.success(devicePlcOperationService.triggerRequestByGroup(groupId));
}
@PostMapping("/groups/{groupId}/report")
- @Operation(summary = "璁惧缁勬ā鎷烶LC姹囨姤", description = "瀵硅澶囩粍鍐呮墍鏈夎澶囨ā鎷熶换鍔″畬鎴愭眹鎶�")
+ @ApiOperation("璁惧缁勬ā鎷烶LC姹囨姤")
public Result<List<DevicePlcVO.OperationResult>> triggerGroupReport(
@PathVariable Long groupId) {
return Result.success(devicePlcOperationService.triggerReportByGroup(groupId));
}
@GetMapping("/status/{deviceId}")
- @Operation(summary = "鏌ヨ璁惧PLC鐘舵��", description = "璇诲彇鍗曞彴璁惧鐨凱LC鏁版嵁")
+ @ApiOperation("鏌ヨ璁惧PLC鐘舵��")
public Result<DevicePlcVO.StatusInfo> readStatus(@PathVariable Long deviceId) {
return Result.success(devicePlcOperationService.readStatus(deviceId));
}
@GetMapping("/groups/{groupId}/status")
- @Operation(summary = "鏌ヨ璁惧缁凱LC鐘舵��", description = "璇诲彇璁惧缁勫唴鎵�鏈夎澶囩殑PLC鏁版嵁")
+ @ApiOperation("鏌ヨ璁惧缁凱LC鐘舵��")
public Result<List<DevicePlcVO.StatusInfo>> readGroupStatus(@PathVariable Long groupId) {
return Result.success(devicePlcOperationService.readStatusByGroup(groupId));
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DEVICE_CONFIG_FIELDS.md b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DEVICE_CONFIG_FIELDS.md
new file mode 100644
index 0000000..70df7c5
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DEVICE_CONFIG_FIELDS.md
@@ -0,0 +1,152 @@
+# DeviceConfig 琛ㄥ瓧娈佃鏄�
+
+## 鏁版嵁搴撹〃锛歞evice_config
+
+### 瀛楁缁撴瀯璇存槑
+
+#### 1. `config_json` 鍒�
+**鐢ㄩ��**锛氬瓨鍌�**閫氱敤閰嶇疆鍙傛暟**锛堝墠绔�"閰嶇疆鍙傛暟"鍗$墖涓殑閿�煎锛�
+
+**鏁版嵁缁撴瀯**锛�
+```json
+[
+ {
+ "paramKey": "鍙傛暟鍚�1",
+ "paramValue": "鍙傛暟鍊�1",
+ "description": "鎻忚堪1"
+ },
+ {
+ "paramKey": "鍙傛暟鍚�2",
+ "paramValue": "鍙傛暟鍊�2",
+ "description": "鎻忚堪2"
+ }
+]
+```
+
+**鍓嶇浣嶇疆**锛氳澶囩紪杈戝脊绐� 鈫� "閰嶇疆鍙傛暟"鍗$墖
+
+**鐗圭偣**锛�
+- 閫氱敤鐨勯敭鍊煎閰嶇疆
+- 鍙互娣诲姞浠绘剰鍙傛暟
+- 鐢ㄤ簬瀛樺偍璁惧鐗瑰畾鐨勮嚜瀹氫箟鍙傛暟
+
+---
+
+#### 2. `extra_params` 鍒�
+**鐢ㄩ��**锛氬瓨鍌�**鎵╁睍鍙傛暟**锛堢粨鏋勫寲鐨凧SON瀵硅薄锛�
+
+**鏁版嵁缁撴瀯**锛�
+```json
+{
+ "connectionConfig": {
+ "moduleCode": "妯″潡缂栧彿",
+ "protocolType": "閫氳鍗忚",
+ "timeout": 30,
+ "retryCount": 3,
+ "heartbeatInterval": 30
+ },
+ "plcConfig": {
+ "dbArea": "DB1",
+ "beginIndex": 0,
+ "autoModeInterval": 5000,
+ "plcType": "S7-1200"
+ },
+ "plcProjectId": "椤圭洰ID",
+ "deviceLogic": {
+ // 鏍规嵁璁惧绫诲瀷涓嶅悓鑰屼笉鍚�
+ // 涓婂ぇ杞﹁澶囷細
+ "vehicleCapacity": 6000,
+ "glassIntervalMs": 1000,
+ "autoFeed": true,
+ "maxRetryCount": 5,
+ "positionMapping": {
+ "POS1": 1,
+ "POS2": 2
+ }
+ // 澶х悊鐗囪澶囷細
+ // "glassSize": 2000,
+ // "processingTime": 5000,
+ // "autoProcess": true
+ // 鐜荤拑瀛樺偍璁惧锛�
+ // "storageCapacity": 100,
+ // "retrievalMode": "FIFO",
+ // "autoStore": true,
+ // "autoRetrieve": true
+ }
+}
+```
+
+**鍓嶇浣嶇疆**锛氳澶囩紪杈戝脊绐椾腑鐨勫涓厤缃尯鍩�
+
+---
+
+### extra_params 璇︾粏璇存槑
+
+#### 2.1 `connectionConfig` - 杩炴帴閰嶇疆
+**鍓嶇浣嶇疆**锛�"杩炴帴閰嶇疆"鍗$墖
+
+| 瀛楁 | 璇存槑 | 鍓嶇鏄剧ず浣嶇疆 |
+|------|------|------------|
+| `moduleCode` | 妯″潡缂栧彿 | 杩炴帴閰嶇疆 鈫� 妯″潡缂栧彿 |
+| `protocolType` | 閫氳鍗忚 | 杩炴帴閰嶇疆 鈫� 閫氳鍗忚 |
+| `timeout` | 瓒呮椂鏃堕棿(绉�) | 杩炴帴閰嶇疆 鈫� 瓒呮椂鏃堕棿 |
+| `retryCount` | 閲嶈瘯娆℃暟 | 杩炴帴閰嶇疆 鈫� 閲嶈瘯娆℃暟 |
+| `heartbeatInterval` | 蹇冭烦闂撮殧(绉�) | 杩炴帴閰嶇疆 鈫� 蹇冭烦闂撮殧 |
+
+#### 2.2 `plcConfig` - PLC鍦板潃閰嶇疆
+**鍓嶇浣嶇疆**锛�"PLC 鍦板潃閰嶇疆"鍗$墖
+
+| 瀛楁 | 璇存槑 | 鍓嶇鏄剧ず浣嶇疆 |
+|------|------|------------|
+| `dbArea` | DB鍧� | PLC鍦板潃閰嶇疆 鈫� DB鍧� |
+| `beginIndex` | 璧峰绱㈠紩 | PLC鍦板潃閰嶇疆 鈫� 璧峰绱㈠紩 |
+| `autoModeInterval` | 鑷姩闂撮殧(ms) | PLC鍦板潃閰嶇疆 鈫� 鑷姩闂撮殧 |
+| `plcType` | PLC绫诲瀷 | 鍩烘湰淇℃伅 鈫� PLC绫诲瀷锛堜絾淇濆瓨鍦ㄨ繖閲岋級 |
+
+#### 2.3 `plcProjectId` - PLC椤圭洰ID
+**璇存槑**锛氱敤浜庢爣璇哖LC椤圭洰锛屽湪PLC鎿嶄綔鏃朵娇鐢�
+
+#### 2.4 `deviceLogic` - 璁惧閫昏緫鍙傛暟
+**鍓嶇浣嶇疆**锛�"璁惧閫昏緫閰嶇疆"鍗$墖锛堟牴鎹澶囩被鍨嬪姩鎬佹樉绀猴級
+
+**涓婂ぇ杞﹁澶�**锛�
+- `vehicleCapacity`: 杞﹁締瀹归噺
+- `glassIntervalMs`: 鐜荤拑闂撮殧(ms)
+- `autoFeed`: 鑷姩涓婃枡
+- `maxRetryCount`: 鏈�澶ч噸璇曟鏁�
+- `positionMapping`: 浣嶇疆鏄犲皠瀵硅薄
+
+**澶х悊鐗囪澶�**锛�
+- `glassSize`: 鐜荤拑灏哄
+- `processingTime`: 澶勭悊鏃堕棿(ms)
+- `autoProcess`: 鑷姩澶勭悊
+- `maxRetryCount`: 鏈�澶ч噸璇曟鏁�
+
+**鐜荤拑瀛樺偍璁惧**锛�
+- `storageCapacity`: 瀛樺偍瀹归噺
+- `retrievalMode`: 鍙栬揣妯″紡 (FIFO/LIFO/RANDOM)
+- `autoStore`: 鑷姩瀛樺偍
+- `autoRetrieve`: 鑷姩鍙栬揣
+- `maxRetryCount`: 鏈�澶ч噸璇曟鏁�
+
+---
+
+## 瀛楁瀵规瘮鎬荤粨
+
+| 瀛楁 | 瀛樺偍鍐呭 | 鍓嶇浣嶇疆 | 鐢ㄩ�� |
+|------|---------|---------|------|
+| `config_json` | 閫氱敤閿�煎鍙傛暟 | "閰嶇疆鍙傛暟"鍗$墖 | 鑷畾涔夊弬鏁帮紝鐏垫椿閰嶇疆 |
+| `extra_params.connectionConfig` | 杩炴帴鐩稿叧閰嶇疆 | "杩炴帴閰嶇疆"鍗$墖 | 閫氳杩炴帴鍙傛暟 |
+| `extra_params.plcConfig` | PLC鍦板潃閰嶇疆 | "PLC鍦板潃閰嶇疆"鍗$墖 | PLC璇诲啓鍦板潃鍙傛暟 |
+| `extra_params.deviceLogic` | 璁惧閫昏緫鍙傛暟 | "璁惧閫昏緫閰嶇疆"鍗$墖 | 璁惧涓氬姟閫昏緫鍙傛暟 |
+| `extra_params.plcProjectId` | 椤圭洰ID | 闅愯棌瀛楁 | 鍐呴儴浣跨敤 |
+
+---
+
+## 浣跨敤寤鸿
+
+1. **config_json**锛氱敤浜庡瓨鍌ㄤ笉甯哥敤鐨勩�佽嚜瀹氫箟鐨勯厤缃弬鏁�
+2. **extra_params.connectionConfig**锛氱敤浜庡瓨鍌ㄨ繛鎺ョ浉鍏崇殑鏍囧噯閰嶇疆
+3. **extra_params.plcConfig**锛氱敤浜庡瓨鍌≒LC鍦板潃鐩稿叧鐨勯厤缃�
+4. **extra_params.deviceLogic**锛氱敤浜庡瓨鍌ㄨ澶囦笟鍔¢�昏緫鐩稿叧鐨勯厤缃紙鏍规嵁璁惧绫诲瀷涓嶅悓锛�
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java
index 3a8900e..86584a9 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceConfig.java
@@ -2,7 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -15,93 +16,93 @@
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("device_config")
-@Schema(name = "DeviceConfig", description = "璁惧閰嶇疆淇℃伅")
+@ApiModel(value = "璁惧閰嶇疆淇℃伅")
public class DeviceConfig {
- @Schema(description = "璁惧ID", example = "1")
+ @ApiModelProperty(value = "璁惧ID", example = "1")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
- @Schema(description = "璁惧鍞竴鏍囪瘑", example = "DEVICE_001")
+ @ApiModelProperty(value = "璁惧鍞竴鏍囪瘑", example = "DEVICE_001")
@TableField("device_id")
private String deviceId;
- @Schema(description = "璁惧鍚嶇О", example = "涓婂ぇ杞﹁澶�1")
+ @ApiModelProperty(value = "璁惧鍚嶇О", example = "涓婂ぇ杞﹁澶�1")
@TableField("device_name")
private String deviceName;
- @Schema(description = "璁惧缂栧彿", example = "DEV_001")
+ @ApiModelProperty(value = "璁惧缂栧彿", example = "DEV_001")
@TableField("device_code")
private String deviceCode;
- @Schema(description = "璁惧绫诲瀷", example = "涓婂ぇ杞�/澶х悊鐗�/鐜荤拑瀛樺偍")
+ @ApiModelProperty(value = "璁惧绫诲瀷", example = "涓婂ぇ杞�/澶х悊鐗�/鐜荤拑瀛樺偍")
@TableField("device_type")
private String deviceType;
- @Schema(description = "鎵�灞為」鐩甀D", example = "1")
+ @ApiModelProperty(value = "鎵�灞為」鐩甀D", example = "1")
@TableField("project_id")
private Long projectId;
- @Schema(description = "PLC IP鍦板潃", example = "192.168.1.100")
+ @ApiModelProperty(value = "PLC IP鍦板潃", example = "192.168.1.100")
@TableField("plc_ip")
private String plcIp;
- @Schema(description = "PLC绔彛", example = "102")
+ @ApiModelProperty(value = "PLC绔彛", example = "102")
@TableField("plc_port")
private Integer plcPort;
- @Schema(description = "璁惧鐘舵��", example = "鍦ㄧ嚎/绂荤嚎/缁存姢涓�/鏁呴殰")
+ @ApiModelProperty(value = "璁惧鐘舵��", example = "鍦ㄧ嚎/绂荤嚎/缁存姢涓�/鏁呴殰")
@TableField("status")
private String status;
- @Schema(description = "PLC绫诲瀷", example = "S7-1200/S7-1500")
+ @ApiModelProperty(value = "PLC绫诲瀷", example = "S7-1200/S7-1500")
@TableField("plc_type")
private String plcType;
- @Schema(description = "妯″潡鍚嶇О", example = "涓婂ぇ杞︽ā鍧�")
+ @ApiModelProperty(value = "妯″潡鍚嶇О", example = "涓婂ぇ杞︽ā鍧�")
@TableField("module_name")
private String moduleName;
- @Schema(description = "鏄惁涓绘帶璁惧", example = "true")
+ @ApiModelProperty(value = "鏄惁涓绘帶璁惧", example = "true")
@TableField("is_primary")
private Boolean isPrimary;
- @Schema(description = "鏄惁鍚敤", example = "true")
+ @ApiModelProperty(value = "鏄惁鍚敤", example = "true")
@TableField("enabled")
private Boolean enabled;
- @Schema(description = "璁惧鐗瑰畾閰嶇疆JSON", example = "{\"vehicleCapacity\": 6000, \"glassIntervalMs\": 1000}")
+ @ApiModelProperty(value = "璁惧鐗瑰畾閰嶇疆JSON", example = "{\"vehicleCapacity\": 6000, \"glassIntervalMs\": 1000}")
@TableField("config_json")
private String configJson;
- @Schema(description = "璁惧鎻忚堪", example = "涓婂ぇ杞﹁澶�1")
+ @ApiModelProperty(value = "璁惧鎻忚堪", example = "涓婂ぇ杞﹁澶�1")
@TableField("description")
private String description;
- @Schema(description = "鎵╁睍鍙傛暟JSON", example = "{\"timeout\": 5000, \"retries\": 3}")
+ @ApiModelProperty(value = "鎵╁睍鍙傛暟JSON", example = "{\"timeout\": 5000, \"retries\": 3}")
@TableField("extra_params")
private String extraParams;
- @Schema(description = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
+ @ApiModelProperty(value = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
- @Schema(description = "鍒涘缓鏃堕棿")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
@TableField(value = "created_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
- @Schema(description = "鏇存柊鏃堕棿")
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updatedTime;
- @Schema(description = "鍒涘缓浜�", example = "system")
+ @ApiModelProperty(value = "鍒涘缓浜�", example = "system")
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
- @Schema(description = "鏇存柊浜�", example = "system")
+ @ApiModelProperty(value = "鏇存柊浜�", example = "system")
@TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupConfig.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupConfig.java
index a7641bc..3427fd9 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupConfig.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupConfig.java
@@ -2,7 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -15,72 +16,72 @@
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("device_group_config")
-@Schema(name = "DeviceGroupConfig", description = "璁惧缁勯厤缃俊鎭�")
+@ApiModel(value = "DeviceGroupConfig", description = "璁惧缁勯厤缃俊鎭�")
public class DeviceGroupConfig {
- @Schema(description = "璁惧缁処D", example = "1")
+ @ApiModelProperty(value = "璁惧缁処D", example = "1")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
- @Schema(description = "璁惧缁勫悕绉�", example = "鐢熶骇绾緼")
+ @ApiModelProperty(value = "璁惧缁勫悕绉�", example = "鐢熶骇绾緼")
@TableField("group_name")
private String groupName;
- @Schema(description = "璁惧缁勭紪鍙�", example = "GROUP_001")
+ @ApiModelProperty(value = "璁惧缁勭紪鍙�", example = "GROUP_001")
@TableField("group_code")
private String groupCode;
- @Schema(description = "璁惧缁勭被鍨嬶細1-鐢熶骇绾匡紝2-娴嬭瘯绾匡紝3-杈呭姪璁惧缁�", example = "1")
+ @ApiModelProperty(value = "璁惧缁勭被鍨嬶細1-鐢熶骇绾匡紝2-娴嬭瘯绾匡紝3-杈呭姪璁惧缁�", example = "1")
@TableField("group_type")
private Integer groupType;
- @Schema(description = "鎵�灞為」鐩甀D", example = "1")
+ @ApiModelProperty(value = "鎵�灞為」鐩甀D", example = "1")
@TableField("project_id")
private Long projectId;
- @Schema(description = "璁惧缁勭姸鎬侊細0-鍋滅敤锛�1-鍚敤锛�3-缁存姢涓�", example = "1")
+ @ApiModelProperty(value = "璁惧缁勭姸鎬侊細0-鍋滅敤锛�1-鍚敤锛�3-缁存姢涓�", example = "1")
@TableField("status")
private Integer status;
- @Schema(description = "鏈�澶у苟鍙戣澶囨暟", example = "3")
+ @ApiModelProperty(value = "鏈�澶у苟鍙戣澶囨暟", example = "3")
@TableField("max_concurrent_devices")
private Integer maxConcurrentDevices;
- @Schema(description = "蹇冭烦妫�娴嬮棿闅�(绉�)", example = "30")
+ @ApiModelProperty(value = "蹇冭烦妫�娴嬮棿闅�(绉�)", example = "30")
@TableField("heartbeat_interval")
private Integer heartbeatInterval;
- @Schema(description = "閫氫俊瓒呮椂鏃堕棿(姣)", example = "5000")
+ @ApiModelProperty(value = "閫氫俊瓒呮椂鏃堕棿(姣)", example = "5000")
@TableField("communication_timeout")
private Integer communicationTimeout;
- @Schema(description = "璁惧缁勬弿杩�", example = "鐢熶骇绾緼璁惧缁�")
+ @ApiModelProperty(value = "璁惧缁勬弿杩�", example = "鐢熶骇绾緼璁惧缁�")
@TableField("description")
private String description;
- @Schema(description = "鎵╁睍閰嶇疆JSON", example = "{\"retryTimes\": 3, \"batchSize\": 100}")
+ @ApiModelProperty(value = "鎵╁睍閰嶇疆JSON", example = "{\"retryTimes\": 3, \"batchSize\": 100}")
@TableField("extra_config")
private String extraConfig;
- @Schema(description = "鍒涘缓鏃堕棿")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
@TableField(value = "created_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
- @Schema(description = "鏇存柊鏃堕棿")
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updatedTime;
- @Schema(description = "鍒涘缓浜�", example = "system")
+ @ApiModelProperty(value = "鍒涘缓浜�", example = "system")
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
- @Schema(description = "鏇存柊浜�", example = "system")
+ @ApiModelProperty(value = "鏇存柊浜�", example = "system")
@TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
- @Schema(description = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
+ @ApiModelProperty(value = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupRelation.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupRelation.java
index b7dedd3..4b9cb7f 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupRelation.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceGroupRelation.java
@@ -2,7 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -15,64 +16,64 @@
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("device_group_relation")
-@Schema(name = "DeviceGroupRelation", description = "璁惧缁勪笌璁惧鍏宠仈鍏崇郴")
+@ApiModel(value = "DeviceGroupRelation", description = "璁惧缁勪笌璁惧鍏宠仈鍏崇郴")
public class DeviceGroupRelation {
- @Schema(description = "鍏宠仈ID", example = "1")
+ @ApiModelProperty(value = "鍏宠仈ID", example = "1")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
- @Schema(description = "璁惧缁処D", example = "1")
+ @ApiModelProperty(value = "璁惧缁処D", example = "1")
@TableField("group_id")
private Long groupId;
- @Schema(description = "璁惧ID", example = "1")
+ @ApiModelProperty(value = "璁惧ID", example = "1")
@TableField("device_id")
private Long deviceId;
- @Schema(description = "璁惧鍦ㄧ粍鍐呯殑浼樺厛绾э細1-鏈�楂橈紝10-鏈�浣�", example = "1")
+ @ApiModelProperty(value = "璁惧鍦ㄧ粍鍐呯殑浼樺厛绾э細1-鏈�楂橈紝10-鏈�浣�", example = "1")
@TableField("priority")
private Integer priority;
- @Schema(description = "璁惧鍦ㄧ粍鍐呯殑瑙掕壊锛�1-涓绘帶锛�2-鍗忎綔锛�3-鐩戞帶", example = "1")
+ @ApiModelProperty(value = "璁惧鍦ㄧ粍鍐呯殑瑙掕壊锛�1-涓绘帶锛�2-鍗忎綔锛�3-鐩戞帶", example = "1")
@TableField("role")
private Integer role;
- @Schema(description = "璁惧鍦ㄨ缁勪腑鐨勭姸鎬侊細0-鏈厤缃紝1-姝e父锛�2-鏁呴殰锛�3-缁存姢", example = "1")
+ @ApiModelProperty(value = "璁惧鍦ㄨ缁勪腑鐨勭姸鎬侊細0-鏈厤缃紝1-姝e父锛�2-鏁呴殰锛�3-缁存姢", example = "1")
@TableField("status")
private Integer status;
- @Schema(description = "杩炴帴椤哄簭锛氭暟鍊艰秺灏忚秺鍏堣繛鎺�", example = "1")
+ @ApiModelProperty(value = "杩炴帴椤哄簭锛氭暟鍊艰秺灏忚秺鍏堣繛鎺�", example = "1")
@TableField("connection_order")
private Integer connectionOrder;
- @Schema(description = "鍏宠仈鎻忚堪", example = "涓绘帶璁惧锛岃礋璐f暣浣撳崗璋�")
+ @ApiModelProperty(value = "鍏宠仈鎻忚堪", example = "涓绘帶璁惧锛岃礋璐f暣浣撳崗璋�")
@TableField("relation_desc")
private String relationDesc;
- @Schema(description = "鎵╁睍鍙傛暟JSON", example = "{\"timeout\": 5000, \"retryPolicy\": \"exponential\"}")
+ @ApiModelProperty(value = "鎵╁睍鍙傛暟JSON", example = "{\"timeout\": 5000, \"retryPolicy\": \"exponential\"}")
@TableField("extra_params")
private String extraParams;
- @Schema(description = "鍒涘缓鏃堕棿")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
@TableField(value = "created_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
- @Schema(description = "鏇存柊鏃堕棿")
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updatedTime;
- @Schema(description = "鍒涘缓浜�", example = "system")
+ @ApiModelProperty(value = "鍒涘缓浜�", example = "system")
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
- @Schema(description = "鏇存柊浜�", example = "system")
+ @ApiModelProperty(value = "鏇存柊浜�", example = "system")
@TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
- @Schema(description = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
+ @ApiModelProperty(value = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionExecution.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionExecution.java
index 4c61d26..b6265ac 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionExecution.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionExecution.java
@@ -2,7 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -15,110 +16,110 @@
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("device_interaction_execution")
-@Schema(name = "DeviceInteractionExecution", description = "璁惧浜や簰鎵ц璁板綍")
+@ApiModel(value = "DeviceInteractionExecution", description = "璁惧浜や簰鎵ц璁板綍")
public class DeviceInteractionExecution {
- @Schema(description = "鎵ц璁板綍ID", example = "1")
+ @ApiModelProperty(value = "鎵ц璁板綍ID", example = "1")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
- @Schema(description = "鍏宠仈鐨勪氦浜掗�昏緫ID", example = "1")
+ @ApiModelProperty(value = "鍏宠仈鐨勪氦浜掗�昏緫ID", example = "1")
@TableField("logic_id")
private Long logicId;
- @Schema(description = "璁惧缁処D", example = "1")
+ @ApiModelProperty(value = "璁惧缁処D", example = "1")
@TableField("group_id")
private Long groupId;
- @Schema(description = "鎵�灞為」鐩甀D", example = "1")
+ @ApiModelProperty(value = "鎵�灞為」鐩甀D", example = "1")
@TableField("project_id")
private Long projectId;
- @Schema(description = "鎵ц鎵规鍙�", example = "EXEC_20241030_001")
+ @ApiModelProperty(value = "鎵ц鎵规鍙�", example = "EXEC_20241030_001")
@TableField("batch_no")
private String batchNo;
- @Schema(description = "鎵ц鐘舵�侊細0-绛夊緟锛�1-鎵ц涓紝2-鎴愬姛锛�3-澶辫触锛�4-瓒呮椂锛�5-鍙栨秷", example = "0")
+ @ApiModelProperty(value = "鎵ц鐘舵�侊細0-绛夊緟锛�1-鎵ц涓紝2-鎴愬姛锛�3-澶辫触锛�4-瓒呮椂锛�5-鍙栨秷", example = "0")
@TableField("status")
private Integer status;
- @Schema(description = "鎵ц妯″紡锛�1-鎵嬪姩锛�2-鑷姩锛�3-瀹氭椂", example = "2")
+ @ApiModelProperty(value = "鎵ц妯″紡锛�1-鎵嬪姩锛�2-鑷姩锛�3-瀹氭椂", example = "2")
@TableField("execution_mode")
private Integer executionMode;
- @Schema(description = "寮�濮嬫墽琛屾椂闂�")
+ @ApiModelProperty(value = "寮�濮嬫墽琛屾椂闂�")
@TableField("start_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
- @Schema(description = "缁撴潫鎵ц鏃堕棿")
+ @ApiModelProperty(value = "缁撴潫鎵ц鏃堕棿")
@TableField("end_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
- @Schema(description = "鎵ц鑰楁椂(姣)", example = "25000")
+ @ApiModelProperty(value = "鎵ц鑰楁椂(姣)", example = "25000")
@TableField("execution_duration")
private Long executionDuration;
- @Schema(description = "鎵ц杩涘害锛�0-100", example = "80")
+ @ApiModelProperty(value = "鎵ц杩涘害锛�0-100", example = "80")
@TableField("progress")
private Integer progress;
- @Schema(description = "褰撳墠鎵ц鐨勬楠ゅ簭鍙�", example = "3")
+ @ApiModelProperty(value = "褰撳墠鎵ц鐨勬楠ゅ簭鍙�", example = "3")
@TableField("current_step")
private Integer currentStep;
- @Schema(description = "鎬绘楠ゆ暟", example = "10")
+ @ApiModelProperty(value = "鎬绘楠ゆ暟", example = "10")
@TableField("total_steps")
private Integer totalSteps;
- @Schema(description = "鎴愬姛鎵ц鐨勮澶囨暟閲�", example = "3")
+ @ApiModelProperty(value = "鎴愬姛鎵ц鐨勮澶囨暟閲�", example = "3")
@TableField("success_devices")
private Integer successDevices;
- @Schema(description = "澶辫触鐨勮澶囨暟閲�", example = "0")
+ @ApiModelProperty(value = "澶辫触鐨勮澶囨暟閲�", example = "0")
@TableField("failed_devices")
private Integer failedDevices;
- @Schema(description = "瑙﹀彂鎵ц鐨勬搷浣滀汉", example = "admin")
+ @ApiModelProperty(value = "瑙﹀彂鎵ц鐨勬搷浣滀汉", example = "admin")
@TableField("triggered_by")
private String triggeredBy;
- @Schema(description = "鎵ц缁撴灉鎻忚堪", example = "鎵�鏈夎澶囨垚鍔熷畬鎴愯嚜鍔ㄥ寲娴嬭瘯")
+ @ApiModelProperty(value = "鎵ц缁撴灉鎻忚堪", example = "鎵�鏈夎澶囨垚鍔熷畬鎴愯嚜鍔ㄥ寲娴嬭瘯")
@TableField("result_message")
private String resultMessage;
- @Schema(description = "閿欒淇℃伅JSON", example = "{\"deviceId\": 2, \"error\": \"Connection timeout\"}")
+ @ApiModelProperty(value = "閿欒淇℃伅JSON", example = "{\"deviceId\": 2, \"error\": \"Connection timeout\"}")
@TableField("error_details")
private String errorDetails;
- @Schema(description = "鎵ц鏁版嵁缁熻JSON", example = "{\"totalTime\": 25000, \"avgResponseTime\": 120}")
+ @ApiModelProperty(value = "鎵ц鏁版嵁缁熻JSON", example = "{\"totalTime\": 25000, \"avgResponseTime\": 120}")
@TableField("execution_stats")
private String executionStats;
- @Schema(description = "鎵╁睍鍙傛暟JSON", example = "{\"testDataId\": \"TD_001\", \"environment\": \"prod\"}")
+ @ApiModelProperty(value = "鎵╁睍鍙傛暟JSON", example = "{\"testDataId\": \"TD_001\", \"environment\": \"prod\"}")
@TableField("extra_params")
private String extraParams;
- @Schema(description = "鍒涘缓鏃堕棿")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
@TableField(value = "created_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
- @Schema(description = "鏇存柊鏃堕棿")
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updatedTime;
- @Schema(description = "鍒涘缓浜�", example = "system")
+ @ApiModelProperty(value = "鍒涘缓浜�", example = "system")
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
- @Schema(description = "鏇存柊浜�", example = "system")
+ @ApiModelProperty(value = "鏇存柊浜�", example = "system")
@TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
- @Schema(description = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
+ @ApiModelProperty(value = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionLogic.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionLogic.java
index a16a4fa..f029282 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionLogic.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/entity/DeviceInteractionLogic.java
@@ -2,7 +2,8 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -15,88 +16,88 @@
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("device_interaction_logic")
-@Schema(name = "DeviceInteractionLogic", description = "璁惧浜や簰閫昏緫閰嶇疆")
+@ApiModel(value = "DeviceInteractionLogic", description = "璁惧浜や簰閫昏緫閰嶇疆")
public class DeviceInteractionLogic {
- @Schema(description = "閫昏緫ID", example = "1")
+ @ApiModelProperty(value = "閫昏緫ID", example = "1")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
- @Schema(description = "閫昏緫鍚嶇О", example = "澶ц溅鑷姩鍖栨祴璇曢�昏緫")
+ @ApiModelProperty(value = "閫昏緫鍚嶇О", example = "澶ц溅鑷姩鍖栨祴璇曢�昏緫")
@TableField("logic_name")
private String logicName;
- @Schema(description = "閫昏緫缂栧彿", example = "TRUCK_AUTO_TEST_001")
+ @ApiModelProperty(value = "閫昏緫缂栧彿", example = "TRUCK_AUTO_TEST_001")
@TableField("logic_code")
private String logicCode;
- @Schema(description = "鎵�灞炴ā鍧楋細1-涓婂ぇ杞︼紝2-涓嬪ぇ杞︼紝3-杞繍锛�4-娴嬭瘯", example = "1")
+ @ApiModelProperty(value = "鎵�灞炴ā鍧楋細1-涓婂ぇ杞︼紝2-涓嬪ぇ杞︼紝3-杞繍锛�4-娴嬭瘯", example = "1")
@TableField("module_type")
private Integer moduleType;
- @Schema(description = "鎵�灞炶澶囩粍ID", example = "1")
+ @ApiModelProperty(value = "鎵�灞炶澶囩粍ID", example = "1")
@TableField("group_id")
private Long groupId;
- @Schema(description = "閫昏緫绫诲瀷锛�1-椤哄簭鎵ц锛�2-骞惰鎵ц锛�3-鏉′欢鎵ц锛�4-寰幆鎵ц", example = "1")
+ @ApiModelProperty(value = "閫昏緫绫诲瀷锛�1-椤哄簭鎵ц锛�2-骞惰鎵ц锛�3-鏉′欢鎵ц锛�4-寰幆鎵ц", example = "1")
@TableField("logic_type")
private Integer logicType;
- @Schema(description = "閫昏緫鐘舵�侊細0-绂佺敤锛�1-鍚敤锛�3-璋冭瘯涓�", example = "1")
+ @ApiModelProperty(value = "閫昏緫鐘舵�侊細0-绂佺敤锛�1-鍚敤锛�3-璋冭瘯涓�", example = "1")
@TableField("status")
private Integer status;
- @Schema(description = "璁惧缁勫唴璇ラ�昏緫鐨勪紭鍏堢骇锛�1-鏈�楂橈紝10-鏈�浣�", example = "1")
+ @ApiModelProperty(value = "璁惧缁勫唴璇ラ�昏緫鐨勪紭鍏堢骇锛�1-鏈�楂橈紝10-鏈�浣�", example = "1")
@TableField("priority")
private Integer priority;
- @Schema(description = "鎵ц瓒呮椂鏃堕棿(姣)", example = "30000")
+ @ApiModelProperty(value = "鎵ц瓒呮椂鏃堕棿(姣)", example = "30000")
@TableField("execution_timeout")
private Integer executionTimeout;
- @Schema(description = "閲嶈瘯娆℃暟", example = "3")
+ @ApiModelProperty(value = "閲嶈瘯娆℃暟", example = "3")
@TableField("retry_times")
private Integer retryTimes;
- @Schema(description = "閫昏緫鎻忚堪", example = "澶ц溅鑷姩鍖栨祴璇曠殑瀹屾暣娴佺▼鎺у埗")
+ @ApiModelProperty(value = "閫昏緫鎻忚堪", example = "澶ц溅鑷姩鍖栨祴璇曠殑瀹屾暣娴佺▼鎺у埗")
@TableField("description")
private String description;
- @Schema(description = "浜や簰姝ラJSON鏁扮粍", example = "[{\"step\": 1, \"deviceId\": 1, \"action\": \"START\", \"params\": {}}]")
+ @ApiModelProperty(value = "浜や簰姝ラJSON鏁扮粍", example = "[{\"step\": 1, \"deviceId\": 1, \"action\": \"START\", \"params\": {}}]")
@TableField("interaction_steps")
private String interactionSteps;
- @Schema(description = "鏉′欢鍒ゆ柇閫昏緫JSON", example = "{\"conditions\": [{\"field\": \"status\", \"operator\": \"eq\", \"value\": 1}]}")
+ @ApiModelProperty(value = "鏉′欢鍒ゆ柇閫昏緫JSON", example = "{\"conditions\": [{\"field\": \"status\", \"operator\": \"eq\", \"value\": 1}]}")
@TableField("condition_logic")
private String conditionLogic;
- @Schema(description = "鎵╁睍鍙傛暟JSON", example = "{\"parallelLimit\": 5, \"errorHandling\": \"retry\"}")
+ @ApiModelProperty(value = "鎵╁睍鍙傛暟JSON", example = "{\"parallelLimit\": 5, \"errorHandling\": \"retry\"}")
@TableField("extra_params")
private String extraParams;
- @Schema(description = "鐗堟湰鍙�", example = "1.0.0")
+ @ApiModelProperty(value = "鐗堟湰鍙�", example = "1.0.0")
@TableField("version")
private String version;
- @Schema(description = "鍒涘缓鏃堕棿")
+ @ApiModelProperty(value = "鍒涘缓鏃堕棿")
@TableField(value = "created_time", fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createdTime;
- @Schema(description = "鏇存柊鏃堕棿")
+ @ApiModelProperty(value = "鏇存柊鏃堕棿")
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updatedTime;
- @Schema(description = "鍒涘缓浜�", example = "system")
+ @ApiModelProperty(value = "鍒涘缓浜�", example = "system")
@TableField(value = "created_by", fill = FieldFill.INSERT)
private String createdBy;
- @Schema(description = "鏇存柊浜�", example = "system")
+ @ApiModelProperty(value = "鏇存柊浜�", example = "system")
@TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
- @Schema(description = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
+ @ApiModelProperty(value = "鏄惁鍒犻櫎锛�0-鍚︼紝1-鏄�", example = "0")
@TableField("is_deleted")
@TableLogic
private Integer isDeleted;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java
index 6ae3625..f9eb11f 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/mapper/DeviceGroupRelationMapper.java
@@ -1,6 +1,7 @@
package com.mes.device.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.mes.device.entity.DeviceConfig;
import com.mes.device.entity.DeviceGroupRelation;
import com.mes.device.vo.DeviceGroupVO;
import org.apache.ibatis.annotations.Mapper;
@@ -77,4 +78,19 @@
"INNER JOIN device_group_relation dgr ON dgc.id = dgr.group_id " +
"WHERE dgr.device_id = #{deviceId} AND dgr.is_deleted = 0 AND dgc.is_deleted = 0")
List<DeviceGroupVO.GroupInfo> getDeviceGroups(@Param("deviceId") Long deviceId);
+
+ /**
+ * 鑾峰彇鎸夎繛鎺ラ『搴忔帓搴忕殑璁惧閰嶇疆鍒楄〃
+ *
+ * @param groupId 璁惧缁処D
+ * @return 璁惧閰嶇疆闆嗗悎
+ */
+ @Select("SELECT d.* " +
+ "FROM device_group_relation dgr " +
+ "INNER JOIN device_config d ON dgr.device_id = d.id " +
+ "WHERE dgr.group_id = #{groupId} " +
+ " AND dgr.is_deleted = 0 " +
+ " AND d.is_deleted = 0 " +
+ "ORDER BY IFNULL(dgr.connection_order, 0) ASC, dgr.id ASC")
+ List<DeviceConfig> getOrderedDeviceConfigs(@Param("groupId") Long groupId);
}
\ No newline at end of file
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceConfigRequest.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceConfigRequest.java
index ca837d1..717a216 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceConfigRequest.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceConfigRequest.java
@@ -1,6 +1,7 @@
package com.mes.device.request;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@@ -12,37 +13,37 @@
* @since 2025-07-12
*/
@Data
-@Schema(description = "璁惧閰嶇疆鎿嶄綔璇锋眰浣�")
+@ApiModel(description = "璁惧閰嶇疆鎿嶄綔璇锋眰浣�")
public class DeviceConfigRequest {
- @Schema(description = "璁惧ID", example = "1")
+ @ApiModelProperty(value = "璁惧ID", example = "1")
private Long deviceId;
- @Schema(description = "璁惧閰嶇疆淇℃伅")
+ @ApiModelProperty(value = "璁惧閰嶇疆淇℃伅")
private Object deviceConfig;
- @Schema(description = "璁惧ID鍒楄〃")
+ @ApiModelProperty(value = "璁惧ID鍒楄〃")
private List<Long> deviceIds;
- @Schema(description = "椤圭洰ID", example = "1")
+ @ApiModelProperty(value = "椤圭洰ID", example = "1")
private Long projectId;
- @Schema(description = "璁惧绫诲瀷", example = "1")
+ @ApiModelProperty(value = "璁惧绫诲瀷", example = "1")
private String deviceType;
- @Schema(description = "璁惧鐘舵��", example = "1")
+ @ApiModelProperty(value = "璁惧鐘舵��", example = "1")
private String deviceStatus;
- @Schema(description = "鎼滅储鍏抽敭璇�", example = "璁惧1")
+ @ApiModelProperty(value = "鎼滅储鍏抽敭璇�", example = "璁惧1")
private String keyword;
- @Schema(description = "璁惧缂栫爜", example = "DEVICE001")
+ @ApiModelProperty(value = "璁惧缂栫爜", example = "DEVICE001")
private String deviceCode;
- @Schema(description = "椤电爜", example = "1")
+ @ApiModelProperty(value = "椤电爜", example = "1")
private Integer page;
- @Schema(description = "姣忛〉澶у皬", example = "10")
+ @ApiModelProperty(value = "姣忛〉澶у皬", example = "10")
private Integer size;
// 鏋勯�犲嚱鏁�
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGlassFeedRequest.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGlassFeedRequest.java
index b35348d..47a12b4 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGlassFeedRequest.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGlassFeedRequest.java
@@ -1,7 +1,7 @@
package com.mes.device.request;
-import io.swagger.v3.oas.annotations.media.ArraySchema;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@@ -12,23 +12,23 @@
* 鐜荤拑鍐欏叆璇锋眰
*/
@Data
-@Schema(name = "DeviceGlassFeedRequest", description = "璁惧鐜荤拑鍐欏叆璇锋眰")
+@ApiModel(value = "DeviceGlassFeedRequest", description = "璁惧鐜荤拑鍐欏叆璇锋眰")
public class DeviceGlassFeedRequest {
@NotNull
- @Schema(description = "璁惧ID", required = true)
+ @ApiModelProperty(value = "璁惧ID", required = true)
private Long deviceId;
- @ArraySchema(schema = @Schema(description = "鐜荤拑ID鍒楄〃", example = "GLS001"), minItems = 1)
+ @ApiModelProperty(value = "鐜荤拑ID鍒楄〃", example = "GLS001")
private List<String> glassIds;
- @Schema(description = "杩涚墖浣嶇疆鏍囪瘑锛堜笌鎺у埗鍙傛暟涓殑 positionMappings 瀵瑰簲锛�")
+ @ApiModelProperty(value = "杩涚墖浣嶇疆鏍囪瘑锛堜笌鎺у埗鍙傛暟涓殑 positionMappings 瀵瑰簲锛�")
private String positionCode;
- @Schema(description = "鐩存帴鎸囧畾鐨勪綅缃�硷紙浼樺厛绾ч珮浜� positionCode锛�")
+ @ApiModelProperty(value = "鐩存帴鎸囧畾鐨勪綅缃�硷紙浼樺厛绾ч珮浜� positionCode锛�")
private Integer positionValue;
- @Schema(description = "鏄惁鑷姩鍐欏叆璇锋眰瀛�", defaultValue = "true")
+ @ApiModelProperty(value = "鏄惁鑷姩鍐欏叆璇锋眰瀛�", example = "true")
private Boolean triggerRequest = true;
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGroupRequest.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGroupRequest.java
index 187aa9e..2c3acbd 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGroupRequest.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DeviceGroupRequest.java
@@ -1,6 +1,7 @@
package com.mes.device.request;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@@ -12,25 +13,25 @@
* @since 2025-07-12
*/
@Data
-@Schema(description = "璁惧缁勬搷浣滆姹備綋")
+@ApiModel(description = "璁惧缁勬搷浣滆姹備綋")
public class DeviceGroupRequest {
- @Schema(description = "璁惧缁処D", example = "1")
+ @ApiModelProperty(value = "璁惧缁処D", example = "1")
private Long groupId;
- @Schema(description = "璁惧ID", example = "1")
+ @ApiModelProperty(value = "璁惧ID", example = "1")
private Long deviceId;
- @Schema(description = "璁惧ID鍒楄〃")
+ @ApiModelProperty(value = "璁惧ID鍒楄〃")
private List<Long> deviceIds;
- @Schema(description = "璁惧缁処D鍒楄〃")
+ @ApiModelProperty(value = "璁惧缁処D鍒楄〃")
private List<Long> groupIds;
- @Schema(description = "璁惧缁勯厤缃俊鎭�")
+ @ApiModelProperty(value = "璁惧缁勯厤缃俊鎭�")
private Object groupConfig;
- @Schema(description = "璁惧瑙掕壊", example = "MEMBER")
+ @ApiModelProperty(value = "璁惧瑙掕壊", example = "MEMBER")
private String deviceRole;
// 鏋勯�犲嚱鏁�
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DevicePlcBatchRequest.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DevicePlcBatchRequest.java
index 445ccad..12b82fd 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DevicePlcBatchRequest.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/request/DevicePlcBatchRequest.java
@@ -1,6 +1,7 @@
package com.mes.device.request;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@@ -14,11 +15,11 @@
* @since 2025-11-17
*/
@Data
-@Schema(name = "DevicePlcBatchRequest", description = "璁惧 PLC 鎵归噺鎿嶄綔璇锋眰")
+@ApiModel(value = "DevicePlcBatchRequest", description = "璁惧 PLC 鎵归噺鎿嶄綔璇锋眰")
public class DevicePlcBatchRequest implements Serializable {
@NotEmpty(message = "璁惧ID鍒楄〃涓嶈兘涓虹┖")
- @Schema(description = "璁惧ID鍒楄〃", required = true)
+ @ApiModelProperty(value = "璁惧ID鍒楄〃", required = true)
private List<Long> deviceIds;
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/DeviceInteractionService.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/DeviceInteractionService.java
index 6f0b4bb..6075183 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/DeviceInteractionService.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/DeviceInteractionService.java
@@ -3,14 +3,26 @@
import com.mes.device.request.DeviceGlassFeedRequest;
import com.mes.device.vo.DevicePlcVO;
+import java.util.Map;
+
/**
* 璁惧浜や簰閫昏緫鏈嶅姟
*/
public interface DeviceInteractionService {
/**
- * 鎵ц鐜荤拑涓婃枡鍐欏叆
+ * 鎵ц鐜荤拑涓婃枡鍐欏叆锛堝吋瀹规棫鎺ュ彛锛�
*/
DevicePlcVO.OperationResult feedGlass(DeviceGlassFeedRequest request);
+
+ /**
+ * 鎵ц璁惧閫昏緫鎿嶄綔锛堟柊鎺ュ彛锛屼娇鐢ㄥ鐞嗗櫒鏋舵瀯锛�
+ *
+ * @param deviceId 璁惧ID
+ * @param operation 鎿嶄綔绫诲瀷锛堝锛歠eedGlass, triggerRequest, triggerReport绛夛級
+ * @param params 鎿嶄綔鍙傛暟
+ * @return 鎿嶄綔缁撴灉
+ */
+ DevicePlcVO.OperationResult executeOperation(Long deviceId, String operation, Map<String, Object> params);
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceInteractionServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceInteractionServiceImpl.java
index 9c6fb3f..73cb33a 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceInteractionServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DeviceInteractionServiceImpl.java
@@ -1,6 +1,10 @@
package com.mes.device.service.impl;
+import com.mes.device.entity.DeviceConfig;
+import com.mes.interaction.DeviceLogicHandler;
+import com.mes.interaction.DeviceLogicHandlerFactory;
import com.mes.device.request.DeviceGlassFeedRequest;
+import com.mes.device.service.DeviceConfigService;
import com.mes.device.service.DeviceControlProfileService;
import com.mes.device.service.DeviceInteractionService;
import com.mes.device.service.DevicePlcOperationService;
@@ -25,9 +29,30 @@
private final DeviceControlProfileService controlProfileService;
private final DevicePlcOperationService devicePlcOperationService;
+ private final DeviceConfigService deviceConfigService;
+ private final DeviceLogicHandlerFactory handlerFactory;
+ /**
+ * 鎵ц鐜荤拑涓婃枡鍐欏叆锛堝吋瀹规棫鎺ュ彛锛屼繚鐣欏師鏈夐�昏緫锛�
+ */
@Override
public DevicePlcVO.OperationResult feedGlass(DeviceGlassFeedRequest request) {
+ // 浼樺厛浣跨敤鏂扮殑澶勭悊鍣ㄦ灦鏋�
+ DeviceConfig deviceConfig = deviceConfigService.getDeviceById(request.getDeviceId());
+ if (deviceConfig != null) {
+ DeviceLogicHandler handler = handlerFactory.getHandler(deviceConfig.getDeviceType());
+ if (handler != null) {
+ // 浣跨敤鏂版灦鏋勬墽琛�
+ Map<String, Object> params = new HashMap<>();
+ params.put("glassIds", request.getGlassIds());
+ params.put("positionCode", request.getPositionCode());
+ params.put("positionValue", request.getPositionValue());
+ params.put("triggerRequest", request.getTriggerRequest());
+ return handler.execute(deviceConfig, "feedGlass", params);
+ }
+ }
+
+ // 闄嶇骇鍒板師鏈夐�昏緫锛堝吋瀹规棫浠g爜锛�
DeviceControlProfile profile = controlProfileService.getProfile(request.getDeviceId());
Map<String, Object> payload = buildGlassPayload(profile, request);
String opName = "鐜荤拑涓婃枡";
@@ -37,6 +62,36 @@
return devicePlcOperationService.writeFields(request.getDeviceId(), payload, opName);
}
+ /**
+ * 鎵ц璁惧閫昏緫鎿嶄綔锛堟柊鎺ュ彛锛屼娇鐢ㄥ鐞嗗櫒鏋舵瀯锛�
+ */
+ @Override
+ public DevicePlcVO.OperationResult executeOperation(Long deviceId, String operation, Map<String, Object> params) {
+ // 鑾峰彇璁惧閰嶇疆
+ DeviceConfig deviceConfig = deviceConfigService.getDeviceById(deviceId);
+ if (deviceConfig == null) {
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("璁惧涓嶅瓨鍦�: " + deviceId)
+ .build();
+ }
+
+ // 鑾峰彇瀵瑰簲鐨勫鐞嗗櫒
+ DeviceLogicHandler handler = handlerFactory.getHandler(deviceConfig.getDeviceType());
+ if (handler == null) {
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("涓嶆敮鎸佺殑璁惧绫诲瀷: " + deviceConfig.getDeviceType())
+ .build();
+ }
+
+ // 鎵ц鎿嶄綔
+ return handler.execute(deviceConfig, operation, params != null ? params : new HashMap<>());
+ }
+
+ /**
+ * 鏋勫缓鐜荤拑涓婃枡鏁版嵁锛堝吋瀹规棫閫昏緫锛�
+ */
private Map<String, Object> buildGlassPayload(DeviceControlProfile profile, DeviceGlassFeedRequest request) {
if (CollectionUtils.isEmpty(profile.getGlassSlots())) {
throw new IllegalStateException("璁惧鏈厤缃幓鐠冩Ы浣嶄俊鎭�");
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DevicePlcOperationServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DevicePlcOperationServiceImpl.java
index c3c50d1..f034b9e 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DevicePlcOperationServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/service/impl/DevicePlcOperationServiceImpl.java
@@ -6,6 +6,7 @@
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;
@@ -247,13 +248,21 @@
throw new IllegalArgumentException("璁惧淇℃伅涓虹┖");
}
+ // 浼樺厛浠巆onfigJson涓幏鍙�
+ Map<String, Object> 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<String, Object> extraParams = objectMapper.readValue(extra, new TypeReference<Map<String, Object>>() {});
- Object plcProjectId = extraParams.get(PLC_PROJECT_ID_KEY);
- if (plcProjectId != null) {
- return String.valueOf(plcProjectId);
+ Object plcProjectIdFromExtra = extraParams.get(PLC_PROJECT_ID_KEY);
+ if (plcProjectIdFromExtra != null) {
+ return String.valueOf(plcProjectIdFromExtra);
}
} catch (Exception e) {
log.warn("瑙f瀽璁惧鎵╁睍鍙傛暟澶辫触, deviceId={}", device.getId(), e);
@@ -271,7 +280,7 @@
throw new IllegalStateException("鏃犳硶瑙f瀽璁惧鐨� PLC 椤圭洰鏍囪瘑, deviceId=" + device.getId());
}
- private enum PlcOperationType {
+ public enum PlcOperationType {
REQUEST("PLC璇锋眰", "PLC 璇锋眰鍙戦�佹垚鍔�", "PLC 璇锋眰鍙戦�佸け璐�"),
REPORT("PLC姹囨姤", "PLC 姹囨姤妯℃嫙鎴愬姛", "PLC 姹囨姤妯℃嫙澶辫触"),
RESET("PLC閲嶇疆", "PLC 鐘舵�佸凡閲嶇疆", "PLC 鐘舵�侀噸缃け璐�");
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/util/ConfigJsonHelper.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/util/ConfigJsonHelper.java
new file mode 100644
index 0000000..c132d55
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/util/ConfigJsonHelper.java
@@ -0,0 +1,71 @@
+package com.mes.device.util;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 宸ュ叿绫伙細瑙f瀽 DeviceConfig.configJson 鐨勫绉嶆牸寮�
+ * 鍏煎瀵硅薄缁撴瀯鍜� [{paramKey,paramValue}] 鏁扮粍缁撴瀯
+ */
+@Slf4j
+public final class ConfigJsonHelper {
+
+ private static final TypeReference<Map<String, Object>> MAP_TYPE =
+ new TypeReference<Map<String, Object>>() {};
+ private static final TypeReference<List<Map<String, Object>>> LIST_TYPE =
+ new TypeReference<List<Map<String, Object>>>() {};
+
+ private ConfigJsonHelper() {
+ }
+
+ /**
+ * 灏� configJson 瑙f瀽涓� key-value 鐨� Map
+ *
+ * @param configJson 鍘熷 JSON 瀛楃涓�
+ * @param objectMapper 鍏叡 ObjectMapper
+ * @return 鍙傛暟鏄犲皠锛屼笉鍙慨鏀�
+ */
+ public static Map<String, Object> parseToMap(String configJson, ObjectMapper objectMapper) {
+ if (configJson == null || configJson.trim().isEmpty()) {
+ return Collections.emptyMap();
+ }
+ String trimmed = configJson.trim();
+ try {
+ if (trimmed.startsWith("[")) {
+ List<Map<String, Object>> items = objectMapper.readValue(trimmed, LIST_TYPE);
+ Map<String, Object> result = new LinkedHashMap<>();
+ for (Map<String, Object> item : items) {
+ if (item == null) {
+ continue;
+ }
+ Object keyObj = firstNonNull(item.get("paramKey"), item.get("key"));
+ if (keyObj == null) {
+ continue;
+ }
+ Object valueObj = firstNonNull(item.get("paramValue"), item.get("value"));
+ result.put(String.valueOf(keyObj), valueObj);
+ }
+ return result;
+ } else if (trimmed.startsWith("{")) {
+ return objectMapper.readValue(trimmed, MAP_TYPE);
+ } else {
+ log.warn("鏈煡鐨� configJson 鏍煎紡: {}", trimmed);
+ }
+ } catch (Exception e) {
+ log.warn("瑙f瀽 configJson 澶辫触: {}", trimmed, e);
+ }
+ return Collections.emptyMap();
+ }
+
+ private static Object firstNonNull(Object first, Object second) {
+ return Objects.nonNull(first) ? first : second;
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceControlProfile.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceControlProfile.java
index 60ee815..e3e9fd5 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceControlProfile.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DeviceControlProfile.java
@@ -1,6 +1,7 @@
package com.mes.device.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -17,52 +18,52 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
-@Schema(name = "DeviceControlProfile", description = "璁惧鎺у埗鍙傛暟閰嶇疆")
+@ApiModel(value = "DeviceControlProfile", description = "璁惧鎺у埗鍙傛暟閰嶇疆")
public class DeviceControlProfile implements Serializable {
- @Schema(description = "鑺傛媿/绾块�熷害锛坢m/s锛�")
+ @ApiModelProperty(value = "鑺傛媿/绾块�熷害锛坢m/s锛�")
private Integer lineSpeed;
- @Schema(description = "鐜荤拑闀垮害锛坢m锛�")
+ @ApiModelProperty(value = "鐜荤拑闀垮害锛坢m锛�")
private Integer glassLength;
- @Schema(description = "缂撳瓨鏁伴噺/妲戒綅鏁伴噺")
+ @ApiModelProperty(value = "缂撳瓨鏁伴噺/妲戒綅鏁伴噺")
private Integer bufferCount;
- @Schema(description = "鏄惁鑷姩瑙﹀彂PLC璇锋眰")
+ @ApiModelProperty(value = "鏄惁鑷姩瑙﹀彂PLC璇锋眰")
private Boolean autoRequest;
- @Schema(description = "PLC璇锋眰瀛楁鍚�", defaultValue = "plcRequest")
+ @ApiModelProperty(value = "PLC璇锋眰瀛楁鍚�")
private String requestField = "plcRequest";
- @Schema(description = "杩涚墖浣嶇疆瀛楁鍚�", defaultValue = "inPosition")
+ @ApiModelProperty(value = "杩涚墖浣嶇疆瀛楁鍚�")
private String positionField = "inPosition";
- @Schema(description = "鐜荤拑鏁伴噺瀛楁鍚�", defaultValue = "plcGlassCount")
+ @ApiModelProperty(value = "鐜荤拑鏁伴噺瀛楁鍚�")
private String glassCountField = "plcGlassCount";
- @Schema(description = "鐜荤拑ID妲戒綅瀛楁瀹氫箟")
+ @ApiModelProperty(value = "鐜荤拑ID妲戒綅瀛楁瀹氫箟")
private List<GlassSlot> glassSlots;
- @Schema(description = "浣嶇疆鏄犲皠锛屽锛歿 \"station1\":1 }")
+ @ApiModelProperty(value = "浣嶇疆鏄犲皠锛屽锛歿 \"station1\":1 }")
private Map<String, Integer> positionMappings;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
- @Schema(name = "GlassSlot", description = "鐜荤拑ID妲戒綅")
+ @ApiModel(value = "GlassSlot", description = "鐜荤拑ID妲戒綅")
public static class GlassSlot implements Serializable {
- @Schema(description = "妲戒綅搴忓彿锛屼粠1寮�濮�")
+ @ApiModelProperty(value = "妲戒綅搴忓彿锛屼粠1寮�濮�")
private Integer order;
- @Schema(description = "PLC瀛楁鍚嶏紝渚嬪 plcGlassId1")
+ @ApiModelProperty(value = "PLC瀛楁鍚嶏紝渚嬪 plcGlassId1")
private String field;
- @Schema(description = "瀛楁闀垮害锛屽瓧绗︿覆闀垮害绛�")
+ @ApiModelProperty(value = "瀛楁闀垮害锛屽瓧绗︿覆闀垮害绛�")
private Integer length;
- @Schema(description = "妲戒綅鎻忚堪")
+ @ApiModelProperty(value = "妲戒綅鎻忚堪")
private String description;
}
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DevicePlcVO.java b/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DevicePlcVO.java
index a4099ae..873c2e0 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DevicePlcVO.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/device/vo/DevicePlcVO.java
@@ -1,6 +1,7 @@
package com.mes.device.vo;
-import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@@ -25,7 +26,7 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
- @Schema(name = "DevicePlcOperationResult", description = "PLC 鎿嶄綔缁撴灉")
+ @ApiModel(value = "DevicePlcOperationResult", description = "PLC 鎿嶄綔缁撴灉")
public static class OperationResult implements Serializable {
private Long deviceId;
private String deviceName;
@@ -44,7 +45,7 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
- @Schema(name = "DevicePlcStatus", description = "PLC 鐘舵�佹暟鎹�")
+ @ApiModel(value = "DevicePlcStatus", description = "PLC 鐘舵�佹暟鎹�")
public static class StatusInfo implements Serializable {
private Long deviceId;
private String deviceName;
diff --git "a/mes-processes/mes-plcSend/src/main/java/com/mes/device/\345\244\232\350\256\276\345\244\207\350\201\224\345\220\210\346\265\213\350\257\225\346\211\251\345\261\225\346\226\271\346\241\210.md" "b/mes-processes/mes-plcSend/src/main/java/com/mes/device/\345\244\232\350\256\276\345\244\207\350\201\224\345\220\210\346\265\213\350\257\225\346\211\251\345\261\225\346\226\271\346\241\210.md"
new file mode 100644
index 0000000..eb15099
--- /dev/null
+++ "b/mes-processes/mes-plcSend/src/main/java/com/mes/device/\345\244\232\350\256\276\345\244\207\350\201\224\345\220\210\346\265\213\350\257\225\346\211\251\345\261\225\346\226\271\346\241\210.md"
@@ -0,0 +1,1153 @@
+# MES Test Project 澶氳澶囪仈鍚堟祴璇曟墿灞曟柟妗�
+
+## 馃搵 椤圭洰姒傝堪
+
+鍩轰簬鐜版湁鐨凪ES Test Project锛坢es-web + mes-plcSend锛夛紝鎵╁睍鏀寔澶氳澶囪仈鍚堟祴璇曞姛鑳斤紝瀹炵幇"涓婂ぇ杞﹁澶� 鈫� 澶х悊鐗囪澶� 鈫� 鐜荤拑瀛樺偍璁惧"鐨勫畬鏁寸敓浜ф祦绋嬭嚜鍔ㄥ寲娴嬭瘯銆�
+
+## 馃幆 鏍稿績闇�姹�
+
+### 涓氬姟鍦烘櫙
+1. **涓婂ぇ杞﹀墠璇锋眰**锛氭娴嬭溅杈嗗閲忥紙6000mm鍙厤缃級銆佺幓鐠冭鏍煎尮閰嶃�佽妭鎷嶆帶鍒�
+2. **澶х悊鐗囦氦浜�**锛氫笌MES澶х悊鐗囦俊鎭瘮瀵归獙璇併�佹壒閲忓鐞嗛�昏緫
+3. **澶氳澶囧崗璋�**锛氳澶囬棿鏁版嵁浼犻�掋�佺姸鎬佸悓姝ャ�佷緷璧栫鐞�
+
+### 鎶�鏈渶姹�
+- 鏀寔澶歅LC璁惧鍦板潃鏄犲皠
+- 璁惧缁勯厤缃拰绠$悊
+- 涓茶/骞惰鎵ц妯″紡
+- 璁惧闂存暟鎹叡浜�
+- 瀹炴椂鐘舵�佺洃鎺�
+
+## 馃搧 鎵╁睍鏋舵瀯璁捐
+
+### 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 # 璁惧缁勭鐞咥PI
+鈹溾攢鈹� interaction/ # 浜や簰閫昏緫妯″潡
+鈹� 鈹溾攢鈹� base/
+鈹� 鈹� 鈹溾攢鈹� BaseInteraction.java # 鍩虹浜や簰鎶借薄
+鈹� 鈹� 鈹溾攢鈹� InteractionContext.java # 浜や簰涓婁笅鏂�
+鈹� 鈹� 鈹斺攢鈹� InteractionResult.java # 浜や簰缁撴灉
+鈹� 鈹溾攢鈹� 涓婂ぇ杞�/
+鈹� 鈹� 鈹溾攢鈹� 涓婂ぇ杞nteraction.java # 涓婂ぇ杞︿氦浜掗�昏緫
+鈹� 鈹� 鈹斺攢鈹� 涓婂ぇ杞onfig.java # 涓婂ぇ杞﹂厤缃�
+鈹� 鈹溾攢鈹� 澶х悊鐗�/
+鈹� 鈹� 鈹溾攢鈹� 澶х悊鐗嘔nteraction.java # 澶х悊鐗囦氦浜掗�昏緫
+鈹� 鈹� 鈹斺攢鈹� 澶х悊鐗嘋onfig.java # 澶х悊鐗囬厤缃�
+鈹� 鈹斺攢鈹� 鐜荤拑瀛樺偍/
+鈹� 鈹溾攢鈹� 鐜荤拑瀛樺偍Interaction.java # 鐜荤拑瀛樺偍浜や簰閫昏緫
+鈹� 鈹斺攢鈹� 鐜荤拑瀛樺偍Config.java # 鐜荤拑瀛樺偍閰嶇疆
+鈹斺攢鈹� task/ # 浠诲姟绠$悊灞�
+ 鈹溾攢鈹� entity/
+ 鈹� 鈹溾攢鈹� MultiDeviceTask.java # 澶氳澶囦换鍔″疄浣�
+ 鈹� 鈹斺攢鈹� TaskStep.java # 浠诲姟姝ラ瀹炰綋
+ 鈹溾攢鈹� service/
+ 鈹� 鈹溾攢鈹� MultiDeviceTaskService.java # 澶氳澶囦换鍔℃湇鍔�
+ 鈹� 鈹斺攢鈹� TaskExecutionEngine.java # 浠诲姟鎵ц寮曟搸
+ 鈹斺攢鈹� controller/
+ 鈹斺攢鈹� MultiDeviceTaskController.java # 澶氳澶囦换鍔PI
+```
+
+#### 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 '璁惧缁処D',
+ 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 '璁惧缁処D',
+ 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/ # 浜や簰閫昏緫缁勪欢
+鈹� 鈹溾攢鈹� 涓婂ぇ杞onfig.vue # 涓婂ぇ杞﹂厤缃�
+鈹� 鈹溾攢鈹� 澶х悊鐗嘋onfig.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; // 璁惧鐗瑰畾閰嶇疆
+
+ // 閰嶇疆瑙f瀽鏂规硶
+ public <T> T getConfig(Class<T> clazz) {
+ if (StringUtils.isBlank(configJson)) {
+ return null;
+ }
+ try {
+ return JsonUtils.fromJson(configJson, clazz);
+ } catch (Exception e) {
+ log.error("瑙f瀽璁惧閰嶇疆澶辫触: {}", 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());
+ }
+
+ /**
+ * 鑾峰彇璁惧鐨凱LC鍦板潃鏄犲皠
+ */
+ public Map<String, Integer> 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<DeviceConfig> getDevices() {
+ return deviceGroupService.getDevicesByGroupId(groupId);
+ }
+
+ // 鑾峰彇鎵ц椤哄簭
+ public List<DeviceConfig> 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("涓婂ぇ杞nteraction")
+public class 涓婂ぇ杞nteraction implements DeviceInteraction {
+
+ @Override
+ public InteractionResult execute(InteractionContext context) {
+ 涓婂ぇ杞onfig config = context.getConfig(涓婂ぇ杞onfig.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) {
+ // 楠岃瘉杞﹁締淇℃伅銆佺幓鐠冧俊鎭�丳LC杩炴帴绛�
+ return context.getVehicleSpec() != null &&
+ context.getCurrentGlass() != null &&
+ validatePlcConnection(context.getDeviceId());
+ }
+
+ @Override
+ public String getDeviceType() {
+ return "涓婂ぇ杞�";
+ }
+}
+```
+
+#### 澶х悊鐗囦氦浜掑疄鐜�
+```java
+@Component("澶х悊鐗嘔nteraction")
+public class 澶х悊鐗嘔nteraction implements DeviceInteraction {
+
+ @Override
+ public InteractionResult execute(InteractionContext context) {
+ 澶х悊鐗嘋onfig config = context.getConfig(澶х悊鐗嘋onfig.class);
+
+ try {
+ // 1. 鑾峰彇涓婂ぇ杞﹂樁娈典紶閫掔殑鏁版嵁
+ List<GlassSpec> glassesFromVehicle = context.getSharedData("glassesFromVehicle", List.class);
+ VehicleSpec vehicleInfo = context.getSharedData("vehicleInfo", VehicleSpec.class);
+
+ if (glassesFromVehicle == null || glassesFromVehicle.isEmpty()) {
+ return InteractionResult.wait("绛夊緟涓婂ぇ杞︽暟鎹�");
+ }
+
+ // 2. 涓嶮ES澶х悊鐗囦俊鎭瘮瀵归獙璇�
+ List<GlassSpec> 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<GlassSpec> glasses, InteractionContext context, 澶х悊鐗嘋onfig 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<DeviceConfig> 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
+<template>
+ <div class="device-management">
+ <!-- 璁惧鍒楄〃 -->
+ <div class="device-list">
+ <el-table :data="devices" style="width: 100%">
+ <el-table-column prop="deviceName" label="璁惧鍚嶇О" />
+ <el-table-column prop="deviceType" label="璁惧绫诲瀷" />
+ <el-table-column prop="plcIp" label="PLC IP" />
+ <el-table-column prop="status" label="鐘舵��">
+ <template #default="{ row }">
+ <el-tag :type="getStatusType(row.status)">{{ row.status }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column label="鎿嶄綔">
+ <template #default="{ row }">
+ <el-button @click="editDevice(row)">缂栬緫</el-button>
+ <el-button @click="testConnection(row)">娴嬭瘯杩炴帴</el-button>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
+ <!-- 璁惧閰嶇疆琛ㄥ崟 -->
+ <el-dialog v-model="showConfigDialog" title="璁惧閰嶇疆">
+ <el-form :model="deviceForm" label-width="120px">
+ <el-form-item label="璁惧ID">
+ <el-input v-model="deviceForm.deviceId" />
+ </el-form-item>
+ <el-form-item label="璁惧鍚嶇О">
+ <el-input v-model="deviceForm.deviceName" />
+ </el-form-item>
+ <el-form-item label="璁惧绫诲瀷">
+ <el-select v-model="deviceForm.deviceType">
+ <el-option label="涓婂ぇ杞﹁澶�" value="涓婂ぇ杞�" />
+ <el-option label="澶х悊鐗囪澶�" value="澶х悊鐗�" />
+ <el-option label="鐜荤拑瀛樺偍璁惧" value="鐜荤拑瀛樺偍" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="PLC閰嶇疆">
+ <div class="plc-config">
+ <el-input v-model="deviceForm.plcIp" placeholder="PLC IP鍦板潃" />
+ <el-select v-model="deviceForm.plcType" placeholder="PLC绫诲瀷">
+ <el-option label="S7-1200" value="S7-1200" />
+ <el-option label="S7-1500" value="S7-1500" />
+ </el-select>
+ </div>
+ </el-form-item>
+
+ <!-- 璁惧鐗瑰畾閰嶇疆 -->
+ <div v-if="deviceForm.deviceType === '涓婂ぇ杞�'">
+ <el-form-item label="杞﹁締瀹归噺(mm)">
+ <el-input-number v-model="deviceForm.config.vehicleCapacity" :min="1000" :max="12000" />
+ </el-form-item>
+ <el-form-item label="鐜荤拑闂撮殧(ms)">
+ <el-input-number v-model="deviceForm.config.glassIntervalMs" :min="100" :max="10000" />
+ </el-form-item>
+ </div>
+
+ <div v-if="deviceForm.deviceType === '澶х悊鐗�'">
+ <el-form-item label="鍚敤鐜荤拑姣斿">
+ <el-switch v-model="deviceForm.config.glassMatchingEnabled" />
+ </el-form-item>
+ <el-form-item label="鎵归噺澶勭悊">
+ <el-switch v-model="deviceForm.config.batchProcessing" />
+ </el-form-item>
+ </div>
+ </el-form>
+
+ <template #footer>
+ <el-button @click="showConfigDialog = false">鍙栨秷</el-button>
+ <el-button type="primary" @click="saveDevice">淇濆瓨</el-button>
+ </template>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+export default {
+ data() {
+ return {
+ devices: [],
+ showConfigDialog: false,
+ deviceForm: {
+ deviceId: '',
+ deviceName: '',
+ deviceType: '',
+ plcIp: '',
+ plcType: '',
+ config: {
+ vehicleCapacity: 6000, // 榛樿6绫�
+ glassIntervalMs: 1000, // 榛樿1绉�
+ glassMatchingEnabled: true,
+ batchProcessing: true
+ }
+ }
+ }
+ },
+ methods: {
+ async loadDevices() {
+ // 鍔犺浇璁惧鍒楄〃
+ const response = await this.$api.device.getDeviceList();
+ this.devices = response.data;
+ },
+
+ async saveDevice() {
+ try {
+ if (this.deviceForm.deviceId) {
+ await this.$api.device.updateDevice(this.deviceForm);
+ } else {
+ await this.$api.device.createDevice(this.deviceForm);
+ }
+ this.$message.success('淇濆瓨鎴愬姛');
+ this.showConfigDialog = false;
+ this.loadDevices();
+ } catch (error) {
+ this.$message.error('淇濆瓨澶辫触: ' + error.message);
+ }
+ }
+ }
+}
+</script>
+```
+
+### 4.2 璁惧缁勯厤缃晫闈�
+
+```vue
+<template>
+ <div class="device-group-management">
+ <!-- 璁惧缁勫垪琛� -->
+ <div class="group-list">
+ <div class="header">
+ <h3>璁惧缁勯厤缃�</h3>
+ <el-button type="primary" @click="createGroup">鏂板缓璁惧缁�</el-button>
+ </div>
+
+ <div class="groups">
+ <el-card v-for="group in deviceGroups" :key="group.groupId" class="group-card">
+ <template #header>
+ <div class="card-header">
+ <span>{{ group.groupName }}</span>
+ <el-tag :type="group.executionMode === 'SERIAL' ? 'primary' : 'success'">
+ {{ group.executionMode === 'SERIAL' ? '涓茶鎵ц' : '骞惰鎵ц' }}
+ </el-tag>
+ </div>
+ </template>
+
+ <div class="group-content">
+ <!-- 璁惧鎷撴墤鍥� -->
+ <div class="topology">
+ <div v-for="(device, index) in group.devices" :key="device.deviceId" class="device-node">
+ <div class="device-box" :class="device.deviceType">
+ <div class="device-name">{{ device.deviceName }}</div>
+ <div class="device-type">{{ device.deviceType }}</div>
+ </div>
+ <div v-if="index < group.devices.length - 1" class="arrow">鈫�</div>
+ </div>
+ </div>
+
+ <!-- 渚濊禆鍏崇郴 -->
+ <div class="dependencies" v-if="group.dependencies">
+ <h4>璁惧渚濊禆鍏崇郴锛�</h4>
+ <div v-for="(deps, device) in group.dependencies" :key="device" class="dependency-item">
+ {{ device }} 渚濊禆浜�: {{ deps.join(', ') }}
+ </div>
+ </div>
+ </div>
+
+ <template #footer>
+ <div class="card-footer">
+ <el-button @click="editGroup(group)">缂栬緫</el-button>
+ <el-button @click="startTest(group)">寮�濮嬫祴璇�</el-button>
+ <el-button @click="deleteGroup(group)">鍒犻櫎</el-button>
+ </div>
+ </template>
+ </el-card>
+ </div>
+ </div>
+
+ <!-- 璁惧缁勯厤缃璇濇 -->
+ <el-dialog v-model="showGroupDialog" title="璁惧缁勯厤缃�" width="800px">
+ <el-form :model="groupForm" label-width="120px">
+ <el-form-item label="璁惧缁勫悕绉�">
+ <el-input v-model="groupForm.groupName" />
+ </el-form-item>
+ <el-form-item label="鍏宠仈椤圭洰">
+ <el-select v-model="groupForm.projectId">
+ <el-option v-for="project in projects" :key="project.id"
+ :label="project.projectName" :value="project.id" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="鎵ц妯″紡">
+ <el-radio-group v-model="groupForm.executionMode">
+ <el-radio label="SERIAL">涓茶鎵ц</el-radio>
+ <el-radio label="PARALLEL">骞惰鎵ц</el-radio>
+ </el-radio-group>
+ </el-form-item>
+
+ <!-- 璁惧閫夋嫨 -->
+ <el-form-item label="鍖呭惈璁惧">
+ <el-transfer
+ v-model="selectedDevices"
+ :data="availableDevices"
+ :titles="['鍙敤璁惧', '宸查�夎澶�']"
+ @change="updateDeviceOrder" />
+ </el-form-item>
+
+ <!-- 鎵ц椤哄簭璋冩暣 -->
+ <el-form-item label="鎵ц椤哄簭" v-if="groupForm.executionMode === 'SERIAL'">
+ <div class="execution-order">
+ <div v-for="(deviceId, index) in deviceOrder" :key="deviceId" class="order-item">
+ <span>{{ index + 1 }}. {{ getDeviceName(deviceId) }}</span>
+ <el-button-group>
+ <el-button @click="moveUp(index)" :disabled="index === 0">鈫�</el-button>
+ <el-button @click="moveDown(index)" :disabled="index === deviceOrder.length - 1">鈫�</el-button>
+ </el-button-group>
+ </div>
+ </div>
+ </el-form-item>
+ </el-form>
+ </el-dialog>
+ </div>
+</template>
+
+<script>
+export default {
+ data() {
+ return {
+ deviceGroups: [],
+ availableDevices: [],
+ selectedDevices: [],
+ deviceOrder: [],
+ showGroupDialog: false,
+ groupForm: {
+ groupId: '',
+ groupName: '',
+ projectId: '',
+ executionMode: 'SERIAL'
+ }
+ }
+ },
+ methods: {
+ async loadDeviceGroups() {
+ const response = await this.$api.deviceGroup.getGroupList();
+ this.deviceGroups = response.data;
+ },
+
+ async startTest(group) {
+ try {
+ // 璺宠浆鍒版祴璇曟墽琛岄〉闈�
+ this.$router.push({
+ name: 'MultiDeviceTest',
+ query: { groupId: group.groupId }
+ });
+ } catch (error) {
+ this.$message.error('鍚姩娴嬭瘯澶辫触: ' + error.message);
+ }
+ }
+ }
+}
+</script>
+```
+
+### 4.3 澶氳澶囨祴璇曟墽琛岀晫闈�
+
+```vue
+<template>
+ <div class="multi-device-test">
+ <!-- 娴嬭瘯閰嶇疆 -->
+ <div class="test-config">
+ <el-card>
+ <template #header>
+ <h3>娴嬭瘯閰嶇疆 - {{ groupInfo.groupName }}</h3>
+ </template>
+
+ <div class="config-content">
+ <div class="execution-mode">
+ <el-tag :type="groupInfo.executionMode === 'SERIAL' ? 'primary' : 'success'">
+ {{ groupInfo.executionMode === 'SERIAL' ? '涓茶鎵ц' : '骞惰鎵ц' }}
+ </el-tag>
+ </div>
+
+ <div class="parameters">
+ <h4>娴嬭瘯鍙傛暟锛�</h4>
+ <el-form label-width="150px">
+ <el-form-item label="杞﹁締瑙勬牸">
+ <el-input-number v-model="testParams.vehicleLength" :min="1000" :max="12000" /> mm
+ </el-form-item>
+ <el-form-item label="娴嬭瘯鐜荤拑鏁伴噺">
+ <el-input-number v-model="testParams.glassCount" :min="1" :max="50" />
+ </el-form-item>
+ <el-form-item label="鎵ц闂撮殧">
+ <el-input-number v-model="testParams.executionInterval" :min="500" :max="10000" /> ms
+ </el-form-item>
+ </el-form>
+ </div>
+
+ <div class="test-controls">
+ <el-button type="primary" size="large" @click="startTest" :loading="isRunning">
+ {{ isRunning ? '娴嬭瘯鎵ц涓�...' : '寮�濮嬫祴璇�' }}
+ </el-button>
+ <el-button @click="stopTest" :disabled="!isRunning">鍋滄娴嬭瘯</el-button>
+ <el-button @click="pauseTest" :disabled="!isRunning">鏆傚仠娴嬭瘯</el-button>
+ </div>
+ </div>
+ </el-card>
+ </div>
+
+ <!-- 鎵ц鐩戞帶 -->
+ <div class="execution-monitor" v-if="isRunning || currentTask">
+ <el-card>
+ <template #header>
+ <h3>鎵ц鐩戞帶</h3>
+ </template>
+
+ <div class="monitor-content">
+ <!-- 浠诲姟杩涘害 -->
+ <div class="task-progress">
+ <el-progress
+ :percentage="getOverallProgress()"
+ :status="getTaskStatus()" />
+ <div class="progress-info">
+ 姝ラ {{ currentStep }} / {{ totalSteps }} - {{ getCurrentStepName() }}
+ </div>
+ </div>
+
+ <!-- 璁惧鐘舵�� -->
+ <div class="device-status-grid">
+ <div v-for="device in groupInfo.devices" :key="device.deviceId"
+ class="device-status-card">
+ <div class="device-header">
+ <span class="device-name">{{ device.deviceName }}</span>
+ <el-tag :type="getDeviceStatusType(device.deviceId)">
+ {{ getDeviceStatus(device.deviceId) }}
+ </el-tag>
+ </div>
+
+ <div class="device-details" v-if="getDeviceDetails(device.deviceId)">
+ <div class="detail-item">
+ <span>褰撳墠鐜荤拑:</span> {{ getDeviceDetails(device.deviceId).currentGlass || '-' }}
+ </div>
+ <div class="detail-item">
+ <span>澶勭悊鏁伴噺:</span> {{ getDeviceDetails(device.deviceId).processedCount || 0 }}
+ </div>
+ <div class="detail-item">
+ <span>鍓╀綑瀹归噺:</span> {{ getDeviceDetails(device.deviceId).remainingCapacity || '-' }} mm
+ </div>
+ </div>
+
+ <!-- 瀹炴椂鏃ュ織 -->
+ <div class="device-logs">
+ <div v-for="log in getDeviceLogs(device.deviceId)" :key="log.timestamp"
+ class="log-item" :class="log.level">
+ <span class="log-time">{{ formatTime(log.timestamp) }}</span>
+ <span class="log-message">{{ log.message }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- 鏁版嵁娴佸彲瑙嗗寲 -->
+ <div class="data-flow">
+ <h4>鏁版嵁娴佺姸鎬侊細</h4>
+ <div class="flow-diagram">
+ <div v-for="(device, index) in groupInfo.devices" :key="device.deviceId" class="flow-node">
+ <div class="node" :class="['device-' + device.deviceType.toLowerCase(), getNodeStatus(device.deviceId)]">
+ {{ device.deviceName }}
+ </div>
+ <div class="data-indicator" v-if="index < groupInfo.devices.length - 1">
+ <div class="data-flow-arrow" :class="getFlowStatus(device.deviceId, index + 1)">
+ {{ getFlowData(device.deviceId) }}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-card>
+ </div>
+
+ <!-- 缁撴灉鍒嗘瀽 -->
+ <div class="test-results" v-if="testResults">
+ <el-card>
+ <template #header>
+ <h3>娴嬭瘯缁撴灉</h3>
+ </template>
+
+ <div class="results-content">
+ <!-- 鎬讳綋缁撴灉 -->
+ <div class="overall-result">
+ <el-result
+ :icon="testResults.success ? 'success' : 'error'"
+ :title="testResults.success ? '娴嬭瘯鎴愬姛' : '娴嬭瘯澶辫触'"
+ :sub-title="testResults.message">
+ <template #extra>
+ <div class="result-stats">
+ <div class="stat-item">
+ <span class="label">鎵ц鏃堕棿:</span>
+ <span class="value">{{ formatDuration(testResults.duration) }}</span>
+ </div>
+ <div class="stat-item">
+ <span class="label">澶勭悊鐜荤拑:</span>
+ <span class="value">{{ testResults.processedGlassCount }} 鐗�</span>
+ </div>
+ <div class="stat-item">
+ <span class="label">鎴愬姛鐜�:</span>
+ <span class="value">{{ testResults.successRate }}%</span>
+ </div>
+ </div>
+ </template>
+ </el-result>
+ </div>
+
+ <!-- 璇︾粏缁撴灉 -->
+ <div class="detailed-results">
+ <h4>鍚勮澶囨墽琛岃鎯咃細</h4>
+ <el-collapse>
+ <el-collapse-item v-for="(result, deviceName) in testResults.deviceResults"
+ :key="deviceName" :title="deviceName">
+ <div class="device-result-detail">
+ <div class="result-summary">
+ <el-tag :type="result.success ? 'success' : 'error'">
+ {{ result.success ? '鎵ц鎴愬姛' : '鎵ц澶辫触' }}
+ </el-tag>
+ <span class="duration">鑰楁椂: {{ formatDuration(result.duration) }}</span>
+ </div>
+
+ <div class="result-data">
+ <h5>杈撳嚭鏁版嵁锛�</h5>
+ <pre>{{ JSON.stringify(result.outputData, null, 2) }}</pre>
+ </div>
+
+ <div class="result-logs">
+ <h5>鎵ц鏃ュ織锛�</h5>
+ <div class="log-list">
+ <div v-for="log in result.logs" :key="log.timestamp" class="log-line">
+ <span class="log-time">{{ formatTime(log.timestamp) }}</span>
+ <span class="log-level">{{ log.level }}</span>
+ <span class="log-message">{{ log.message }}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </el-collapse-item>
+ </el-collapse>
+ </div>
+
+ <!-- 瀵煎嚭缁撴灉 -->
+ <div class="export-results">
+ <el-button @click="exportResults('json')">瀵煎嚭JSON</el-button>
+ <el-button @click="exportResults('excel')">瀵煎嚭Excel</el-button>
+ <el-button @click="exportResults('pdf')">瀵煎嚭PDF鎶ュ憡</el-button>
+ </div>
+ </div>
+ </el-card>
+ </div>
+ </div>
+</template>
+```
+
+## 馃殌 瀹炴柦璁″垝
+
+### 绗竴闃舵锛氬熀纭�鏋舵瀯鎼缓锛�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. **鍥炴粴鏈哄埗**锛氬嚭鐜伴棶棰樻椂鐨勫揩閫熷洖婊氭柟妗�
+
+---
+
+## 馃摑 缁撹
+
+閫氳繃杩欎釜鎵╁睍鏂规锛孧ES Test Project灏嗗叿澶囧畬鏁寸殑澶氳澶囪仈鍚堟祴璇曡兘鍔涳紝鏀寔澶嶆潅鐨勭敓浜ф祦绋嬭嚜鍔ㄥ寲娴嬭瘯銆傛柟妗堝熀浜庣幇鏈夋灦鏋勮璁★紝椋庨櫓鍙帶锛屽疄鏂介毦搴﹂�備腑锛岃兘澶熷緢濂藉湴婊¤冻涓氬姟闇�姹傘��
+
+寤鸿浼樺厛瀹炵幇绗竴闃舵鐨勫熀纭�鏋舵瀯锛岀劧鍚庨�愭瀹屽杽浜や簰閫昏緫鍜屽墠绔晫闈紝纭繚姣忎釜闃舵閮借兘浜や粯鍙敤鐨勫姛鑳姐��
\ No newline at end of file
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java
new file mode 100644
index 0000000..92b9093
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/BaseDeviceLogicHandler.java
@@ -0,0 +1,124 @@
+package com.mes.interaction;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mes.device.entity.DeviceConfig;
+import com.mes.device.service.DevicePlcOperationService;
+import com.mes.device.vo.DevicePlcVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 璁惧閫昏緫澶勭悊鍣ㄥ熀绫�
+ * 鎻愪緵閫氱敤鐨勫姛鑳藉疄鐜�
+ *
+ * @author mes
+ * @since 2025-01-XX
+ */
+@Slf4j
+@RequiredArgsConstructor
+public abstract class BaseDeviceLogicHandler implements DeviceLogicHandler {
+
+ protected final DevicePlcOperationService devicePlcOperationService;
+ protected final ObjectMapper objectMapper = new ObjectMapper();
+
+ @Override
+ public DevicePlcVO.OperationResult execute(DeviceConfig deviceConfig, String operation, Map<String, Object> params) {
+ try {
+ // 楠岃瘉璁惧閰嶇疆
+ String validationError = validateLogicParams(deviceConfig);
+ if (validationError != null) {
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("璁惧閫昏緫鍙傛暟楠岃瘉澶辫触: " + validationError)
+ .build();
+ }
+
+ // 瑙f瀽璁惧閫昏緫鍙傛暟锛堜粠 extraParams.deviceLogic 涓鍙栵級
+ Map<String, Object> logicParams = parseLogicParams(deviceConfig);
+
+ // 鎵ц鍏蜂綋鎿嶄綔锛堢敱瀛愮被瀹炵幇锛�
+ return doExecute(deviceConfig, operation, params, logicParams);
+ } catch (Exception e) {
+ log.error("鎵ц璁惧閫昏緫鎿嶄綔澶辫触, deviceId={}, operation={}", deviceConfig.getId(), operation, e);
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("鎵ц澶辫触: " + e.getMessage())
+ .build();
+ }
+ }
+
+ /**
+ * 瀛愮被瀹炵幇鍏蜂綋鐨勬搷浣滈�昏緫
+ *
+ * @param deviceConfig 璁惧閰嶇疆
+ * @param operation 鎿嶄綔绫诲瀷
+ * @param params 杩愯鏃跺弬鏁帮紙鍔ㄦ�佷紶鍏ワ級
+ * @param logicParams 閫昏緫閰嶇疆鍙傛暟锛堜粠 extraParams.deviceLogic 瑙f瀽锛�
+ * @return 鎿嶄綔缁撴灉
+ */
+ protected abstract DevicePlcVO.OperationResult doExecute(
+ DeviceConfig deviceConfig,
+ String operation,
+ Map<String, Object> params,
+ Map<String, Object> logicParams
+ );
+
+ /**
+ * 瑙f瀽璁惧閫昏緫鍙傛暟锛堜粠 extraParams 涓彁鍙� deviceLogic锛�
+ */
+ protected Map<String, Object> parseLogicParams(DeviceConfig deviceConfig) {
+ String extraParams = deviceConfig.getExtraParams();
+ if (extraParams == null || extraParams.trim().isEmpty()) {
+ return new HashMap<>();
+ }
+
+ try {
+ Map<String, Object> extra = objectMapper.readValue(extraParams, new TypeReference<Map<String, Object>>() {});
+ @SuppressWarnings("unchecked")
+ Map<String, Object> deviceLogic = (Map<String, Object>) extra.get("deviceLogic");
+ return deviceLogic != null ? deviceLogic : new HashMap<>();
+ } catch (Exception e) {
+ log.warn("瑙f瀽璁惧閫昏緫鍙傛暟澶辫触, deviceId={}", deviceConfig.getId(), e);
+ return new HashMap<>();
+ }
+ }
+
+ /**
+ * 鑾峰彇閫昏緫鍙傛暟涓殑鍊硷紙甯﹂粯璁ゅ�硷級
+ */
+ @SuppressWarnings("unchecked")
+ protected <T> T getLogicParam(Map<String, Object> logicParams, String key, T defaultValue) {
+ Object value = logicParams.get(key);
+ if (value == null) {
+ return defaultValue;
+ }
+ try {
+ return (T) value;
+ } catch (ClassCastException e) {
+ log.warn("閫昏緫鍙傛暟绫诲瀷杞崲澶辫触, key={}, value={}", key, value, e);
+ return defaultValue;
+ }
+ }
+
+ /**
+ * 鑾峰彇閫昏緫鍙傛暟涓殑鍊硷紙涓嶅甫榛樿鍊硷級
+ */
+ @SuppressWarnings("unchecked")
+ protected <T> T getLogicParam(Map<String, Object> logicParams, String key) {
+ Object value = logicParams.get(key);
+ if (value == null) {
+ return null;
+ }
+ try {
+ return (T) value;
+ } catch (ClassCastException e) {
+ log.warn("閫昏緫鍙傛暟绫诲瀷杞崲澶辫触, key={}, value={}", key, value, e);
+ return null;
+ }
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteraction.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteraction.java
new file mode 100644
index 0000000..1dd6dc0
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteraction.java
@@ -0,0 +1,28 @@
+package com.mes.interaction;
+
+import com.mes.interaction.base.InteractionContext;
+import com.mes.interaction.base.InteractionResult;
+
+/**
+ * 澶氳澶囦氦浜掓帴鍙�
+ */
+public interface DeviceInteraction {
+
+ /**
+ * 浜や簰瀵瑰簲鐨勮澶囩被鍨�
+ */
+ String getDeviceType();
+
+ /**
+ * 鎵ц浜や簰閫昏緫
+ */
+ InteractionResult execute(InteractionContext context);
+
+ /**
+ * 鏄惁鏀寔鎸囧畾鎿嶄綔
+ */
+ default boolean supportsOperation(String operation) {
+ return true;
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteractionRegistry.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteractionRegistry.java
new file mode 100644
index 0000000..fc4cc8c
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceInteractionRegistry.java
@@ -0,0 +1,42 @@
+package com.mes.interaction;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 浜や簰娉ㄥ唽涓績
+ */
+@Slf4j
+@Component
+public class DeviceInteractionRegistry {
+
+ private final Map<String, DeviceInteraction> interactionMap = new HashMap<>();
+
+ public DeviceInteractionRegistry(List<DeviceInteraction> interactions) {
+ if (interactions != null) {
+ for (DeviceInteraction interaction : interactions) {
+ if (interaction.getDeviceType() != null) {
+ interactionMap.put(interaction.getDeviceType(), interaction);
+ log.info("娉ㄥ唽璁惧浜や簰: {}", interaction.getDeviceType());
+ }
+ }
+ }
+ }
+
+ public DeviceInteraction getInteraction(String deviceType) {
+ if (deviceType == null) {
+ return null;
+ }
+ return interactionMap.get(deviceType);
+ }
+
+ public Map<String, DeviceInteraction> getInteractions() {
+ return Collections.unmodifiableMap(interactionMap);
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandler.java
new file mode 100644
index 0000000..9566222
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandler.java
@@ -0,0 +1,49 @@
+package com.mes.interaction;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.device.vo.DevicePlcVO;
+
+import java.util.Map;
+
+/**
+ * 璁惧閫昏緫澶勭悊鍣ㄦ帴鍙�
+ * 涓嶅悓璁惧绫诲瀷瀹炵幇姝ゆ帴鍙f潵澶勭悊鍚勮嚜鐨勪笟鍔¢�昏緫
+ *
+ * @author mes
+ * @since 2025-01-XX
+ */
+public interface DeviceLogicHandler {
+
+ /**
+ * 鑾峰彇璁惧绫诲瀷锛堢敤浜庡尮閰嶅鐞嗗櫒锛�
+ *
+ * @return 璁惧绫诲瀷锛屽锛�"涓婂ぇ杞�"銆�"澶х悊鐗�"銆�"鐜荤拑瀛樺偍"
+ */
+ String getDeviceType();
+
+ /**
+ * 鎵ц璁惧閫昏緫鎿嶄綔
+ *
+ * @param deviceConfig 璁惧閰嶇疆淇℃伅
+ * @param operation 鎿嶄綔绫诲瀷锛堝锛歠eedGlass, triggerRequest, triggerReport绛夛級
+ * @param params 鎿嶄綔鍙傛暟锛堣繍琛屾椂浼犲叆鐨勫姩鎬佸弬鏁帮級
+ * @return 鎿嶄綔缁撴灉
+ */
+ DevicePlcVO.OperationResult execute(DeviceConfig deviceConfig, String operation, Map<String, Object> params);
+
+ /**
+ * 楠岃瘉璁惧閫昏緫鍙傛暟閰嶇疆鏄惁鏈夋晥
+ *
+ * @param deviceConfig 璁惧閰嶇疆
+ * @return 楠岃瘉缁撴灉锛宯ull琛ㄧず楠岃瘉閫氳繃锛屽惁鍒欒繑鍥為敊璇俊鎭�
+ */
+ String validateLogicParams(DeviceConfig deviceConfig);
+
+ /**
+ * 鑾峰彇璁惧閫昏緫鍙傛暟鐨勯粯璁ら厤缃�
+ *
+ * @return 榛樿閰嶇疆鐨凧SON瀛楃涓�
+ */
+ String getDefaultLogicParams();
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java
new file mode 100644
index 0000000..f5eb175
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/DeviceLogicHandlerFactory.java
@@ -0,0 +1,77 @@
+package com.mes.interaction;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 璁惧閫昏緫澶勭悊鍣ㄥ伐鍘傜被
+ * 鏍规嵁璁惧绫诲瀷鑾峰彇瀵瑰簲鐨勫鐞嗗櫒
+ *
+ * @author mes
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+public class DeviceLogicHandlerFactory {
+
+ @Autowired
+ private List<DeviceLogicHandler> handlers;
+
+ private final Map<String, DeviceLogicHandler> handlerMap = new HashMap<>();
+
+ /**
+ * 鍒濆鍖栧鐞嗗櫒鏄犲皠
+ */
+ @PostConstruct
+ public void init() {
+ if (handlers != null) {
+ for (DeviceLogicHandler handler : handlers) {
+ String deviceType = handler.getDeviceType();
+ if (deviceType != null && !deviceType.isEmpty()) {
+ handlerMap.put(deviceType, handler);
+ log.info("娉ㄥ唽璁惧閫昏緫澶勭悊鍣�: {} -> {}", deviceType, handler.getClass().getSimpleName());
+ }
+ }
+ }
+ log.info("璁惧閫昏緫澶勭悊鍣ㄥ垵濮嬪寲瀹屾垚锛屽叡娉ㄥ唽 {} 涓鐞嗗櫒", handlerMap.size());
+ }
+
+ /**
+ * 鏍规嵁璁惧绫诲瀷鑾峰彇瀵瑰簲鐨勫鐞嗗櫒
+ *
+ * @param deviceType 璁惧绫诲瀷
+ * @return 璁惧閫昏緫澶勭悊鍣紝濡傛灉鏈壘鍒拌繑鍥瀗ull
+ */
+ public DeviceLogicHandler getHandler(String deviceType) {
+ if (deviceType == null || deviceType.isEmpty()) {
+ return null;
+ }
+ return handlerMap.get(deviceType);
+ }
+
+ /**
+ * 妫�鏌ユ槸鍚︽敮鎸佹寚瀹氱殑璁惧绫诲瀷
+ *
+ * @param deviceType 璁惧绫诲瀷
+ * @return true琛ㄧず鏀寔锛宖alse琛ㄧず涓嶆敮鎸�
+ */
+ public boolean supports(String deviceType) {
+ return deviceType != null && handlerMap.containsKey(deviceType);
+ }
+
+ /**
+ * 鑾峰彇鎵�鏈夊凡娉ㄥ唽鐨勮澶囩被鍨�
+ *
+ * @return 璁惧绫诲瀷闆嗗悎
+ */
+ public java.util.Set<String> getSupportedDeviceTypes() {
+ return handlerMap.keySet();
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/README.md b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/README.md
new file mode 100644
index 0000000..ede551b
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/README.md
@@ -0,0 +1,168 @@
+# 璁惧閫昏緫澶勭悊鍣ㄦ灦鏋勮鏄�
+
+## 鏋舵瀯姒傝堪
+
+閲囩敤**绛栫暐妯″紡 + 宸ュ巶妯″紡**瀹炵幇璁惧閫昏緫澶勭悊锛屾敮鎸佷笉鍚岃澶囩被鍨嬬殑宸紓鍖栭�昏緫澶勭悊銆�
+
+## 鐩綍缁撴瀯
+
+```
+com.mes.interaction/
+鈹溾攢鈹� DeviceLogicHandler.java # 澶勭悊鍣ㄦ帴鍙�
+鈹溾攢鈹� BaseDeviceLogicHandler.java # 鍩虹鎶借薄绫�
+鈹溾攢鈹� DeviceLogicHandlerFactory.java # 宸ュ巶绫�
+鈹斺攢鈹� impl/
+ 鈹溾攢鈹� LoadVehicleLogicHandler.java # 涓婂ぇ杞﹂�昏緫澶勭悊鍣�
+ 鈹溾攢鈹� LargeGlassLogicHandler.java # 澶х悊鐗囬�昏緫澶勭悊鍣�
+ 鈹斺攢鈹� GlassStorageLogicHandler.java # 鐜荤拑瀛樺偍閫昏緫澶勭悊鍣�
+```
+
+## 鏍稿績姒傚康
+
+### 1. 鍙傛暟閰嶇疆锛坋xtraParams.deviceLogic锛�
+
+璁惧閫昏緫鍙傛暟瀛樺偍鍦� `DeviceConfig.extraParams` 鐨� `deviceLogic` 鑺傜偣涓細
+
+```json
+{
+ "connectionConfig": { ... },
+ "plcConfig": { ... },
+ "deviceLogic": {
+ "vehicleCapacity": 6000,
+ "glassIntervalMs": 1000,
+ "autoFeed": true,
+ "positionMapping": {
+ "POS1": 1,
+ "POS2": 2
+ }
+ }
+}
+```
+
+### 2. 鎵ц娴佺▼
+
+```
+鐢ㄦ埛璋冪敤鎺ュ彛
+ 鈫�
+Service 灞傝幏鍙栬澶囬厤缃紙鍖呭惈 extraParams锛�
+ 鈫�
+Factory 鏍规嵁 deviceType 閫夋嫨瀵瑰簲鐨� Handler
+ 鈫�
+Handler 浠� extraParams.deviceLogic 璇诲彇閰嶇疆鍙傛暟
+ 鈫�
+Handler 鏍规嵁閰嶇疆鍙傛暟鍜岃繍琛屾椂鍙傛暟鎵ц閫昏緫
+ 鈫�
+璋冪敤 DevicePlcOperationService.writeFields() 鍐欏叆PLC
+```
+
+## 浣跨敤鏂瑰紡
+
+### 鏂瑰紡1锛氶�氳繃 DeviceInteractionService锛堟帹鑽愶級
+
+```java
+@Autowired
+private DeviceInteractionService deviceInteractionService;
+
+// 鎵ц鎿嶄綔
+Map<String, Object> params = new HashMap<>();
+params.put("glassIds", Arrays.asList("GLS001", "GLS002"));
+params.put("positionCode", "POS1");
+
+OperationResult result = deviceInteractionService.executeOperation(
+ deviceId,
+ "feedGlass",
+ params
+);
+```
+
+### 鏂瑰紡2锛氱洿鎺ヤ娇鐢� Handler
+
+```java
+@Autowired
+private DeviceLogicHandlerFactory handlerFactory;
+@Autowired
+private DeviceConfigService deviceConfigService;
+
+// 鑾峰彇璁惧鍜屽鐞嗗櫒
+DeviceConfig device = deviceConfigService.getDeviceById(deviceId);
+DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+
+// 鎵ц鎿嶄綔
+Map<String, Object> params = new HashMap<>();
+params.put("glassIds", Arrays.asList("GLS001"));
+OperationResult result = handler.execute(device, "feedGlass", params);
+```
+
+## 鏀寔鐨勬搷浣滅被鍨�
+
+### 涓婂ぇ杞︼紙LoadVehicleLogicHandler锛�
+- `feedGlass` - 鐜荤拑涓婃枡
+- `triggerRequest` - 瑙﹀彂璇锋眰
+- `triggerReport` - 瑙﹀彂姹囨姤
+- `reset` - 閲嶇疆
+
+### 澶х悊鐗囷紙LargeGlassLogicHandler锛�
+- `processGlass` - 鐜荤拑鍔犲伐
+- `triggerRequest` - 瑙﹀彂璇锋眰
+- `triggerReport` - 瑙﹀彂姹囨姤
+- `reset` - 閲嶇疆
+
+### 鐜荤拑瀛樺偍锛圙lassStorageLogicHandler锛�
+- `storeGlass` - 瀛樺偍鐜荤拑
+- `retrieveGlass` - 鍙栬揣
+- `triggerRequest` - 瑙﹀彂璇锋眰
+- `triggerReport` - 瑙﹀彂姹囨姤
+- `reset` - 閲嶇疆
+
+## 鎵╁睍鏂拌澶囩被鍨�
+
+### 姝ラ1锛氬垱寤哄鐞嗗櫒绫�
+
+```java
+@Component
+public class NewDeviceLogicHandler extends BaseDeviceLogicHandler {
+
+ public NewDeviceLogicHandler(DevicePlcOperationService devicePlcOperationService) {
+ super(devicePlcOperationService);
+ }
+
+ @Override
+ public String getDeviceType() {
+ return "鏂拌澶囩被鍨�";
+ }
+
+ @Override
+ protected OperationResult doExecute(...) {
+ // 瀹炵幇鍏蜂綋閫昏緫
+ }
+}
+```
+
+### 姝ラ2锛氬湪 DeviceConfig.DeviceType 涓坊鍔犲父閲�
+
+```java
+public static final String NEW_DEVICE = "鏂拌澶囩被鍨�";
+```
+
+### 姝ラ3锛氶厤缃粯璁ゅ弬鏁�
+
+瀹炵幇 `getDefaultLogicParams()` 鏂规硶锛岃繑鍥為粯璁ょ殑JSON閰嶇疆銆�
+
+## 鍙傛暟璇存槑
+
+### 閰嶇疆鍙傛暟锛堜粠 extraParams.deviceLogic 璇诲彇锛�
+- 闈欐�侀厤缃紝鍦ㄨ澶囬厤缃椂璁剧疆
+- 瀛樺偍鍦ㄦ暟鎹簱涓�
+- 绀轰緥锛歷ehicleCapacity, glassIntervalMs
+
+### 杩愯鏃跺弬鏁帮紙浠庢柟娉曞弬鏁颁紶鍏ワ級
+- 鍔ㄦ�佸弬鏁帮紝姣忔璋冪敤鏃朵紶鍏�
+- 绀轰緥锛歡lassIds, positionCode
+
+## 娉ㄦ剰浜嬮」
+
+1. **鍙傛暟楠岃瘉**锛氭瘡涓� Handler 瀹炵幇 `validateLogicParams()` 鏂规硶杩涜鍙傛暟楠岃瘉
+2. **榛樿鍊煎鐞�**锛氫娇鐢� `getLogicParam()` 鏂规硶鑾峰彇鍙傛暟锛屾敮鎸侀粯璁ゅ��
+3. **閿欒澶勭悊**锛氭墍鏈夊紓甯搁兘浼氳鎹曡幏骞惰繑鍥� `OperationResult`
+4. **鍚戝悗鍏煎**锛氫繚鐣欎簡鍘熸湁鐨� `feedGlass()` 鏂规硶锛屼紭鍏堜娇鐢ㄦ柊鏋舵瀯锛屽け璐ユ椂闄嶇骇鍒版棫閫昏緫
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionContext.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionContext.java
new file mode 100644
index 0000000..44374df
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionContext.java
@@ -0,0 +1,49 @@
+package com.mes.interaction.base;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.task.model.TaskExecutionContext;
+import com.mes.task.dto.TaskParameters;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 浜や簰涓婁笅鏂�
+ */
+@Getter
+public class InteractionContext {
+
+ private final DeviceConfig currentDevice;
+ private final TaskExecutionContext taskContext;
+
+ public InteractionContext(DeviceConfig currentDevice, TaskExecutionContext taskContext) {
+ this.currentDevice = currentDevice;
+ this.taskContext = taskContext;
+ }
+
+ public TaskParameters getParameters() {
+ return taskContext.getParameters();
+ }
+
+ public Map<String, Object> getSharedData() {
+ return taskContext.getSharedData();
+ }
+
+ public void setLoadedGlassIds(List<String> glassIds) {
+ taskContext.setLoadedGlassIds(glassIds);
+ }
+
+ public void setProcessedGlassIds(List<String> glassIds) {
+ taskContext.setProcessedGlassIds(glassIds);
+ }
+
+ public List<String> getLoadedGlassIds() {
+ return taskContext.getSafeLoadedGlassIds();
+ }
+
+ public List<String> getProcessedGlassIds() {
+ return taskContext.getSafeProcessedGlassIds();
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionResult.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionResult.java
new file mode 100644
index 0000000..a3353d0
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/base/InteractionResult.java
@@ -0,0 +1,60 @@
+package com.mes.interaction.base;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 浜や簰鎵ц缁撴灉
+ */
+@Getter
+@Builder
+@AllArgsConstructor
+public class InteractionResult {
+
+ public enum Status {
+ SUCCESS, WAITING, FAILED
+ }
+
+ private final Status status;
+ private final String message;
+ @Builder.Default
+ private final Map<String, Object> data = new HashMap<>();
+
+ public static InteractionResult success(Map<String, Object> payload) {
+ return InteractionResult.builder()
+ .status(Status.SUCCESS)
+ .message("success")
+ .data(payload != null ? payload : Collections.emptyMap())
+ .build();
+ }
+
+ public static InteractionResult waitResult(String message, Map<String, Object> payload) {
+ return InteractionResult.builder()
+ .status(Status.WAITING)
+ .message(message)
+ .data(payload != null ? payload : Collections.emptyMap())
+ .build();
+ }
+
+ public static InteractionResult fail(String message) {
+ return InteractionResult.builder()
+ .status(Status.FAILED)
+ .message(message)
+ .data(Collections.emptyMap())
+ .build();
+ }
+
+ public boolean isSuccess() {
+ return status == Status.SUCCESS;
+ }
+
+ public boolean isWaiting() {
+ return status == Status.WAITING;
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/GlassStorageInteraction.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/GlassStorageInteraction.java
new file mode 100644
index 0000000..e4751e7
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/GlassStorageInteraction.java
@@ -0,0 +1,38 @@
+package com.mes.interaction.flow;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.interaction.DeviceInteraction;
+import com.mes.interaction.base.InteractionContext;
+import com.mes.interaction.base.InteractionResult;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 鐜荤拑瀛樺偍浜や簰瀹炵幇
+ */
+@Component
+public class GlassStorageInteraction implements DeviceInteraction {
+
+ @Override
+ public String getDeviceType() {
+ return DeviceConfig.DeviceType.GLASS_STORAGE;
+ }
+
+ @Override
+ public InteractionResult execute(InteractionContext context) {
+ List<String> processed = context.getProcessedGlassIds();
+ if (CollectionUtils.isEmpty(processed)) {
+ return InteractionResult.waitResult("娌℃湁鍙瓨鍌ㄧ殑鐜荤拑", null);
+ }
+
+ Map<String, Object> data = new HashMap<>();
+ data.put("storedCount", processed.size());
+ data.put("storedGlasses", processed);
+ return InteractionResult.success(data);
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LargeGlassInteraction.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LargeGlassInteraction.java
new file mode 100644
index 0000000..7a24ee6
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LargeGlassInteraction.java
@@ -0,0 +1,52 @@
+package com.mes.interaction.flow;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.interaction.DeviceInteraction;
+import com.mes.interaction.base.InteractionContext;
+import com.mes.interaction.base.InteractionResult;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 澶х悊鐗囦氦浜掑疄鐜�
+ */
+@Component
+public class LargeGlassInteraction implements DeviceInteraction {
+
+ @Override
+ public String getDeviceType() {
+ return DeviceConfig.DeviceType.LARGE_GLASS;
+ }
+
+ @Override
+ public InteractionResult execute(InteractionContext context) {
+ Object source = context.getSharedData().get("glassesFromVehicle");
+ List<String> glassQueue = castList(source);
+ if (CollectionUtils.isEmpty(glassQueue)) {
+ return InteractionResult.waitResult("绛夊緟涓婂ぇ杞﹁緭鍑�", null);
+ }
+
+ List<String> processed = new ArrayList<>(glassQueue);
+ context.setProcessedGlassIds(processed);
+ context.getSharedData().put("processedGlasses", processed);
+
+ Map<String, Object> data = new HashMap<>();
+ data.put("processedCount", processed.size());
+ data.put("processedGlasses", processed);
+ return InteractionResult.success(data);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<String> castList(Object value) {
+ if (value instanceof List) {
+ return (List<String>) value;
+ }
+ return null;
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LoadVehicleInteraction.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LoadVehicleInteraction.java
new file mode 100644
index 0000000..675b6ab
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/flow/LoadVehicleInteraction.java
@@ -0,0 +1,43 @@
+package com.mes.interaction.flow;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.interaction.DeviceInteraction;
+import com.mes.interaction.base.InteractionContext;
+import com.mes.interaction.base.InteractionResult;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 涓婂ぇ杞︿氦浜掑疄鐜�
+ */
+@Component
+public class LoadVehicleInteraction implements DeviceInteraction {
+
+ @Override
+ public String getDeviceType() {
+ return DeviceConfig.DeviceType.LOAD_VEHICLE;
+ }
+
+ @Override
+ public InteractionResult execute(InteractionContext context) {
+ List<String> glassIds = context.getParameters().getGlassIds();
+ if (CollectionUtils.isEmpty(glassIds)) {
+ return InteractionResult.waitResult("鏈彁渚涚幓鐠僆D锛岀瓑寰呰緭鍏�", null);
+ }
+
+ List<String> copied = new ArrayList<>(glassIds);
+ context.setLoadedGlassIds(copied);
+ context.getSharedData().put("glassesFromVehicle", copied);
+
+ Map<String, Object> data = new HashMap<>();
+ data.put("loaded", copied);
+ data.put("glassCount", copied.size());
+ return InteractionResult.success(data);
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/GlassStorageLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/GlassStorageLogicHandler.java
new file mode 100644
index 0000000..6776cf5
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/GlassStorageLogicHandler.java
@@ -0,0 +1,241 @@
+package com.mes.interaction.impl;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.interaction.BaseDeviceLogicHandler;
+import com.mes.device.service.DevicePlcOperationService;
+import com.mes.device.vo.DevicePlcVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 鐜荤拑瀛樺偍璁惧閫昏緫澶勭悊鍣�
+ *
+ * @author mes
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+public class GlassStorageLogicHandler extends BaseDeviceLogicHandler {
+
+ public GlassStorageLogicHandler(DevicePlcOperationService devicePlcOperationService) {
+ super(devicePlcOperationService);
+ }
+
+ @Override
+ public String getDeviceType() {
+ return DeviceConfig.DeviceType.GLASS_STORAGE;
+ }
+
+ @Override
+ protected DevicePlcVO.OperationResult doExecute(
+ DeviceConfig deviceConfig,
+ String operation,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ log.info("鎵ц鐜荤拑瀛樺偍璁惧鎿嶄綔: deviceId={}, operation={}", deviceConfig.getId(), operation);
+
+ switch (operation) {
+ case "storeGlass":
+ return handleStoreGlass(deviceConfig, params, logicParams);
+ case "retrieveGlass":
+ return handleRetrieveGlass(deviceConfig, params, logicParams);
+ case "triggerRequest":
+ return handleTriggerRequest(deviceConfig, params, logicParams);
+ case "triggerReport":
+ return handleTriggerReport(deviceConfig, params, logicParams);
+ case "reset":
+ return handleReset(deviceConfig, params, logicParams);
+ default:
+ log.warn("涓嶆敮鎸佺殑鎿嶄綔绫诲瀷: {}", operation);
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("涓嶆敮鎸佺殑鎿嶄綔: " + operation)
+ .build();
+ }
+ }
+
+ /**
+ * 澶勭悊瀛樺偍鐜荤拑鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleStoreGlass(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ // 浠庨�昏緫鍙傛暟涓幏鍙栭厤缃�
+ Integer storageCapacity = getLogicParam(logicParams, "storageCapacity", 100);
+ String retrievalMode = getLogicParam(logicParams, "retrievalMode", "FIFO");
+ Boolean autoStore = getLogicParam(logicParams, "autoStore", true);
+
+ // 浠庤繍琛屾椂鍙傛暟涓幏鍙栨暟鎹�
+ String glassId = (String) params.get("glassId");
+ Integer storagePosition = (Integer) params.get("storagePosition");
+ Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoStore);
+
+ // 鏋勫缓鍐欏叆鏁版嵁
+ Map<String, Object> payload = new HashMap<>();
+
+ if (glassId != null) {
+ payload.put("plcGlassId", glassId);
+ }
+
+ if (storagePosition != null) {
+ payload.put("storagePosition", storagePosition);
+ }
+
+ // 鑷姩瑙﹀彂璇锋眰
+ if (triggerRequest != null && triggerRequest) {
+ payload.put("plcRequest", 1);
+ }
+
+ log.info("鐜荤拑瀛樺偍: deviceId={}, glassId={}, position={}",
+ deviceConfig.getId(), glassId, storagePosition);
+
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "鐜荤拑瀛樺偍-瀛樺偍鐜荤拑"
+ );
+ }
+
+ /**
+ * 澶勭悊鍙栬揣鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleRetrieveGlass(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ // 浠庨�昏緫鍙傛暟涓幏鍙栭厤缃�
+ String retrievalMode = getLogicParam(logicParams, "retrievalMode", "FIFO");
+ Boolean autoRetrieve = getLogicParam(logicParams, "autoRetrieve", true);
+
+ // 浠庤繍琛屾椂鍙傛暟涓幏鍙栨暟鎹�
+ Integer storagePosition = (Integer) params.get("storagePosition");
+ String glassId = (String) params.get("glassId");
+ Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoRetrieve);
+
+ // 鏋勫缓鍐欏叆鏁版嵁
+ Map<String, Object> payload = new HashMap<>();
+
+ if (storagePosition != null) {
+ payload.put("retrievePosition", storagePosition);
+ }
+
+ if (glassId != null) {
+ payload.put("retrieveGlassId", glassId);
+ }
+
+ // 鑷姩瑙﹀彂璇锋眰
+ if (triggerRequest != null && triggerRequest) {
+ payload.put("plcRequest", 1);
+ }
+
+ log.info("鐜荤拑鍙栬揣: deviceId={}, position={}, glassId={}",
+ deviceConfig.getId(), storagePosition, glassId);
+
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "鐜荤拑瀛樺偍-鍙栬揣"
+ );
+ }
+
+ /**
+ * 澶勭悊瑙﹀彂璇锋眰鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleTriggerRequest(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcRequest", 1);
+
+ log.info("鐜荤拑瀛樺偍瑙﹀彂璇锋眰: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "鐜荤拑瀛樺偍-瑙﹀彂璇锋眰"
+ );
+ }
+
+ /**
+ * 澶勭悊瑙﹀彂姹囨姤鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleTriggerReport(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcReport", 1);
+
+ log.info("鐜荤拑瀛樺偍瑙﹀彂姹囨姤: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "鐜荤拑瀛樺偍-瑙﹀彂姹囨姤"
+ );
+ }
+
+ /**
+ * 澶勭悊閲嶇疆鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleReset(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcRequest", 0);
+ payload.put("plcReport", 0);
+
+ log.info("鐜荤拑瀛樺偍閲嶇疆: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "鐜荤拑瀛樺偍-閲嶇疆"
+ );
+ }
+
+ @Override
+ public String validateLogicParams(DeviceConfig deviceConfig) {
+ Map<String, Object> logicParams = parseLogicParams(deviceConfig);
+
+ // 楠岃瘉蹇呭~鍙傛暟
+ Integer storageCapacity = getLogicParam(logicParams, "storageCapacity", null);
+ if (storageCapacity != null && storageCapacity <= 0) {
+ return "瀛樺偍瀹归噺(storageCapacity)蹇呴』澶т簬0";
+ }
+
+ String retrievalMode = getLogicParam(logicParams, "retrievalMode", null);
+ if (retrievalMode != null && !retrievalMode.matches("FIFO|LIFO|RANDOM")) {
+ return "鍙栬揣妯″紡(retrievalMode)蹇呴』鏄疐IFO銆丩IFO鎴朢ANDOM";
+ }
+
+ return null; // 楠岃瘉閫氳繃
+ }
+
+ @Override
+ public String getDefaultLogicParams() {
+ Map<String, Object> defaultParams = new HashMap<>();
+ defaultParams.put("storageCapacity", 100);
+ defaultParams.put("retrievalMode", "FIFO");
+ defaultParams.put("autoStore", true);
+ defaultParams.put("autoRetrieve", true);
+ defaultParams.put("maxRetryCount", 3);
+
+ try {
+ return objectMapper.writeValueAsString(defaultParams);
+ } catch (Exception e) {
+ log.error("鐢熸垚榛樿閫昏緫鍙傛暟澶辫触", e);
+ return "{}";
+ }
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LargeGlassLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LargeGlassLogicHandler.java
new file mode 100644
index 0000000..284fefa
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LargeGlassLogicHandler.java
@@ -0,0 +1,195 @@
+package com.mes.interaction.impl;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.interaction.BaseDeviceLogicHandler;
+import com.mes.device.service.DevicePlcOperationService;
+import com.mes.device.vo.DevicePlcVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 澶х悊鐗囪澶囬�昏緫澶勭悊鍣�
+ *
+ * @author mes
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+public class LargeGlassLogicHandler extends BaseDeviceLogicHandler {
+
+ public LargeGlassLogicHandler(DevicePlcOperationService devicePlcOperationService) {
+ super(devicePlcOperationService);
+ }
+
+ @Override
+ public String getDeviceType() {
+ return DeviceConfig.DeviceType.LARGE_GLASS;
+ }
+
+ @Override
+ protected DevicePlcVO.OperationResult doExecute(
+ DeviceConfig deviceConfig,
+ String operation,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ log.info("鎵ц澶х悊鐗囪澶囨搷浣�: deviceId={}, operation={}", deviceConfig.getId(), operation);
+
+ switch (operation) {
+ case "processGlass":
+ return handleProcessGlass(deviceConfig, params, logicParams);
+ case "triggerRequest":
+ return handleTriggerRequest(deviceConfig, params, logicParams);
+ case "triggerReport":
+ return handleTriggerReport(deviceConfig, params, logicParams);
+ case "reset":
+ return handleReset(deviceConfig, params, logicParams);
+ default:
+ log.warn("涓嶆敮鎸佺殑鎿嶄綔绫诲瀷: {}", operation);
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("涓嶆敮鎸佺殑鎿嶄綔: " + operation)
+ .build();
+ }
+ }
+
+ /**
+ * 澶勭悊鐜荤拑鍔犲伐鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleProcessGlass(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ // 浠庨�昏緫鍙傛暟涓幏鍙栭厤缃�
+ Integer glassSize = getLogicParam(logicParams, "glassSize", 2000);
+ Integer processingTime = getLogicParam(logicParams, "processingTime", 5000);
+ Boolean autoProcess = getLogicParam(logicParams, "autoProcess", true);
+
+ // 浠庤繍琛屾椂鍙傛暟涓幏鍙栨暟鎹�
+ String glassId = (String) params.get("glassId");
+ Integer processType = (Integer) params.get("processType");
+ Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoProcess);
+
+ // 鏋勫缓鍐欏叆鏁版嵁
+ Map<String, Object> payload = new HashMap<>();
+
+ if (glassId != null) {
+ payload.put("plcGlassId", glassId);
+ }
+
+ if (processType != null) {
+ payload.put("processType", processType);
+ }
+
+ // 鑷姩瑙﹀彂璇锋眰
+ if (triggerRequest != null && triggerRequest) {
+ payload.put("plcRequest", 1);
+ }
+
+ log.info("澶х悊鐗囩幓鐠冨姞宸�: deviceId={}, glassId={}, processType={}",
+ deviceConfig.getId(), glassId, processType);
+
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "澶х悊鐗�-鐜荤拑鍔犲伐"
+ );
+ }
+
+ /**
+ * 澶勭悊瑙﹀彂璇锋眰鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleTriggerRequest(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcRequest", 1);
+
+ log.info("澶х悊鐗囪Е鍙戣姹�: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "澶х悊鐗�-瑙﹀彂璇锋眰"
+ );
+ }
+
+ /**
+ * 澶勭悊瑙﹀彂姹囨姤鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleTriggerReport(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcReport", 1);
+
+ log.info("澶х悊鐗囪Е鍙戞眹鎶�: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "澶х悊鐗�-瑙﹀彂姹囨姤"
+ );
+ }
+
+ /**
+ * 澶勭悊閲嶇疆鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleReset(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcRequest", 0);
+ payload.put("plcReport", 0);
+
+ log.info("澶х悊鐗囬噸缃�: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "澶х悊鐗�-閲嶇疆"
+ );
+ }
+
+ @Override
+ public String validateLogicParams(DeviceConfig deviceConfig) {
+ Map<String, Object> logicParams = parseLogicParams(deviceConfig);
+
+ // 楠岃瘉蹇呭~鍙傛暟
+ Integer glassSize = getLogicParam(logicParams, "glassSize", null);
+ if (glassSize != null && glassSize <= 0) {
+ return "鐜荤拑灏哄(glassSize)蹇呴』澶т簬0";
+ }
+
+ Integer processingTime = getLogicParam(logicParams, "processingTime", null);
+ if (processingTime != null && processingTime < 0) {
+ return "澶勭悊鏃堕棿(processingTime)涓嶈兘涓鸿礋鏁�";
+ }
+
+ return null; // 楠岃瘉閫氳繃
+ }
+
+ @Override
+ public String getDefaultLogicParams() {
+ Map<String, Object> defaultParams = new HashMap<>();
+ defaultParams.put("glassSize", 2000);
+ defaultParams.put("processingTime", 5000);
+ defaultParams.put("autoProcess", true);
+ defaultParams.put("maxRetryCount", 3);
+
+ try {
+ return objectMapper.writeValueAsString(defaultParams);
+ } catch (Exception e) {
+ log.error("鐢熸垚榛樿閫昏緫鍙傛暟澶辫触", e);
+ return "{}";
+ }
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LoadVehicleLogicHandler.java b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LoadVehicleLogicHandler.java
new file mode 100644
index 0000000..7d3f18a
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/interaction/impl/LoadVehicleLogicHandler.java
@@ -0,0 +1,372 @@
+package com.mes.interaction.impl;
+
+import com.mes.device.entity.DeviceConfig;
+import com.mes.interaction.BaseDeviceLogicHandler;
+import com.mes.device.service.DevicePlcOperationService;
+import com.mes.device.vo.DevicePlcVO;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * 涓婂ぇ杞﹁澶囬�昏緫澶勭悊鍣�
+ *
+ * @author mes
+ * @since 2025-01-XX
+ */
+@Slf4j
+@Component
+public class LoadVehicleLogicHandler extends BaseDeviceLogicHandler {
+
+ public LoadVehicleLogicHandler(DevicePlcOperationService devicePlcOperationService) {
+ super(devicePlcOperationService);
+ }
+
+ @Override
+ public String getDeviceType() {
+ return DeviceConfig.DeviceType.LOAD_VEHICLE;
+ }
+
+ @Override
+ protected DevicePlcVO.OperationResult doExecute(
+ DeviceConfig deviceConfig,
+ String operation,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ log.info("鎵ц涓婂ぇ杞﹁澶囨搷浣�: deviceId={}, operation={}", deviceConfig.getId(), operation);
+
+ switch (operation) {
+ case "feedGlass":
+ return handleFeedGlass(deviceConfig, params, logicParams);
+ case "triggerRequest":
+ return handleTriggerRequest(deviceConfig, params, logicParams);
+ case "triggerReport":
+ return handleTriggerReport(deviceConfig, params, logicParams);
+ case "reset":
+ return handleReset(deviceConfig, params, logicParams);
+ default:
+ log.warn("涓嶆敮鎸佺殑鎿嶄綔绫诲瀷: {}", operation);
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("涓嶆敮鎸佺殑鎿嶄綔: " + operation)
+ .build();
+ }
+ }
+
+ /**
+ * 澶勭悊鐜荤拑涓婃枡鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleFeedGlass(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ // 浠庨�昏緫鍙傛暟涓幏鍙栭厤缃紙浠� extraParams.deviceLogic 璇诲彇锛�
+ Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000);
+ Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000);
+ Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true);
+ Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5);
+
+ // 浠庤繍琛屾椂鍙傛暟涓幏鍙栨暟鎹紙浠庢帴鍙h皟鐢ㄦ椂浼犲叆锛�
+ List<GlassInfo> glassInfos = extractGlassInfos(params);
+ if (glassInfos.isEmpty()) {
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("鏈彁渚涙湁鏁堢殑鐜荤拑淇℃伅")
+ .build();
+ }
+
+ String positionCode = (String) params.get("positionCode");
+ Integer positionValue = (Integer) params.get("positionValue");
+ Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed);
+
+ List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity,
+ getLogicParam(logicParams, "defaultGlassLength", 2000));
+ if (plannedGlasses.isEmpty()) {
+ return DevicePlcVO.OperationResult.builder()
+ .success(false)
+ .message("褰撳墠鐜荤拑灏哄瓒呭嚭杞﹁締瀹归噺锛屾棤娉曡杞�")
+ .build();
+ }
+
+ // 鏋勫缓鍐欏叆鏁版嵁
+ Map<String, Object> payload = new HashMap<>();
+
+ // 鍐欏叆鐜荤拑ID
+ int plcSlots = Math.min(plannedGlasses.size(), 6);
+ for (int i = 0; i < plcSlots; i++) {
+ String fieldName = "plcGlassId" + (i + 1);
+ payload.put(fieldName, plannedGlasses.get(i).getGlassId());
+ }
+ payload.put("plcGlassCount", plcSlots);
+
+ // 鍐欏叆浣嶇疆淇℃伅
+ if (positionValue != null) {
+ payload.put("inPosition", positionValue);
+ } else if (positionCode != null) {
+ // 浠庝綅缃槧灏勪腑鑾峰彇浣嶇疆鍊�
+ @SuppressWarnings("unchecked")
+ Map<String, Integer> positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>());
+ Integer mappedValue = positionMapping.get(positionCode);
+ if (mappedValue != null) {
+ payload.put("inPosition", mappedValue);
+ }
+ }
+
+ // 鑷姩瑙﹀彂璇锋眰瀛�
+ if (triggerRequest != null && triggerRequest) {
+ payload.put("plcRequest", 1);
+ }
+
+ String operationName = "涓婂ぇ杞�-鐜荤拑涓婃枡";
+ if (positionCode != null) {
+ operationName += "(" + positionCode + ")";
+ }
+
+ log.info("涓婂ぇ杞︾幓鐠冧笂鏂�: deviceId={}, glassCount={}, position={}, plannedGlassIds={}",
+ deviceConfig.getId(), plcSlots, positionCode, plannedGlasses);
+
+ if (glassIntervalMs != null && glassIntervalMs > 0) {
+ try {
+ Thread.sleep(glassIntervalMs);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return devicePlcOperationService.writeFields(deviceConfig.getId(), payload, operationName);
+ }
+
+ /**
+ * 澶勭悊瑙﹀彂璇锋眰鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleTriggerRequest(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcRequest", 1);
+
+ log.info("涓婂ぇ杞﹁Е鍙戣姹�: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "涓婂ぇ杞�-瑙﹀彂璇锋眰"
+ );
+ }
+
+ /**
+ * 澶勭悊瑙﹀彂姹囨姤鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleTriggerReport(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcReport", 1);
+
+ log.info("涓婂ぇ杞﹁Е鍙戞眹鎶�: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "涓婂ぇ杞�-瑙﹀彂姹囨姤"
+ );
+ }
+
+ /**
+ * 澶勭悊閲嶇疆鎿嶄綔
+ */
+ private DevicePlcVO.OperationResult handleReset(
+ DeviceConfig deviceConfig,
+ Map<String, Object> params,
+ Map<String, Object> logicParams) {
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("plcRequest", 0);
+ payload.put("plcReport", 0);
+
+ log.info("涓婂ぇ杞﹂噸缃�: deviceId={}", deviceConfig.getId());
+ return devicePlcOperationService.writeFields(
+ deviceConfig.getId(),
+ payload,
+ "涓婂ぇ杞�-閲嶇疆"
+ );
+ }
+
+ @Override
+ public String validateLogicParams(DeviceConfig deviceConfig) {
+ Map<String, Object> logicParams = parseLogicParams(deviceConfig);
+
+ // 楠岃瘉蹇呭~鍙傛暟
+ Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", null);
+ if (vehicleCapacity == null || vehicleCapacity <= 0) {
+ return "杞﹁締瀹归噺(vehicleCapacity)蹇呴』澶т簬0";
+ }
+
+ Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", null);
+ if (glassIntervalMs != null && glassIntervalMs < 0) {
+ return "鐜荤拑闂撮殧鏃堕棿(glassIntervalMs)涓嶈兘涓鸿礋鏁�";
+ }
+
+ return null; // 楠岃瘉閫氳繃
+ }
+
+ @Override
+ public String getDefaultLogicParams() {
+ Map<String, Object> defaultParams = new HashMap<>();
+ defaultParams.put("vehicleCapacity", 6000);
+ defaultParams.put("glassIntervalMs", 1000);
+ defaultParams.put("autoFeed", true);
+ defaultParams.put("maxRetryCount", 5);
+ defaultParams.put("defaultGlassLength", 2000);
+
+ Map<String, Integer> positionMapping = new HashMap<>();
+ positionMapping.put("POS1", 1);
+ positionMapping.put("POS2", 2);
+ defaultParams.put("positionMapping", positionMapping);
+
+ try {
+ return objectMapper.writeValueAsString(defaultParams);
+ } catch (Exception e) {
+ log.error("鐢熸垚榛樿閫昏緫鍙傛暟澶辫触", e);
+ return "{}";
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<GlassInfo> extractGlassInfos(Map<String, Object> params) {
+ List<GlassInfo> result = new ArrayList<>();
+ Object rawGlassInfos = params.get("glassInfos");
+ if (rawGlassInfos instanceof List) {
+ List<?> list = (List<?>) rawGlassInfos;
+ for (Object item : list) {
+ GlassInfo info = convertToGlassInfo(item);
+ if (info != null) {
+ result.add(info);
+ }
+ }
+ }
+
+ if (result.isEmpty()) {
+ List<String> glassIds = (List<String>) params.get("glassIds");
+ if (glassIds != null) {
+ for (String glassId : glassIds) {
+ result.add(new GlassInfo(glassId, null));
+ }
+ }
+ }
+ return result;
+ }
+
+ private GlassInfo convertToGlassInfo(Object source) {
+ if (source instanceof GlassInfo) {
+ return (GlassInfo) source;
+ }
+ if (source instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) source;
+ Object id = map.get("glassId");
+ if (id == null) {
+ id = map.get("id");
+ }
+ if (id == null) {
+ return null;
+ }
+ Integer length = parseLength(map.get("length"));
+ if (length == null) {
+ length = parseLength(map.get("size"));
+ }
+ return new GlassInfo(String.valueOf(id), length);
+ }
+ if (source instanceof String) {
+ return new GlassInfo((String) source, null);
+ }
+ return null;
+ }
+
+ private Integer parseLength(Object value) {
+ if (value instanceof Number) {
+ return ((Number) value).intValue();
+ }
+ if (value instanceof String) {
+ try {
+ return Integer.parseInt((String) value);
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ return null;
+ }
+
+ private List<GlassInfo> planGlassLoading(List<GlassInfo> source,
+ int vehicleCapacity,
+ Integer defaultGlassLength) {
+ List<GlassInfo> planned = new ArrayList<>();
+ int usedLength = 0;
+ int capacity = Math.max(vehicleCapacity, 1);
+ int fallbackLength = defaultGlassLength != null && defaultGlassLength > 0 ? defaultGlassLength : 2000;
+
+ for (GlassInfo info : source) {
+ int length = info.getLength() != null && info.getLength() > 0 ? info.getLength() : fallbackLength;
+ if (planned.isEmpty()) {
+ planned.add(info.withLength(length));
+ usedLength = length;
+ continue;
+ }
+ if (usedLength + length <= capacity) {
+ planned.add(info.withLength(length));
+ usedLength += length;
+ } else {
+ break;
+ }
+ }
+ return planned;
+ }
+
+ private static class GlassInfo {
+ private final String glassId;
+ private final Integer length;
+
+ GlassInfo(String glassId, Integer length) {
+ this.glassId = glassId;
+ this.length = length;
+ }
+
+ public String getGlassId() {
+ return glassId;
+ }
+
+ public Integer getLength() {
+ return length;
+ }
+
+ public GlassInfo withLength(Integer newLength) {
+ return new GlassInfo(this.glassId, newLength);
+ }
+
+ @Override
+ public String toString() {
+ return glassId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(glassId, length);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (obj == null || getClass() != obj.getClass()) return false;
+ GlassInfo other = (GlassInfo) obj;
+ return Objects.equals(glassId, other.glassId) && Objects.equals(length, other.length);
+ }
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcDynamicDataService.java b/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcDynamicDataService.java
index 84b20e7..12eeb8e 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcDynamicDataService.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcDynamicDataService.java
@@ -3,6 +3,7 @@
import com.alibaba.fastjson.JSONObject;
import com.github.xingshuangs.iot.common.enums.EDataType;
import com.github.xingshuangs.iot.protocol.s7.serializer.S7Parameter;
+import com.mes.device.entity.DeviceConfig;
import com.mes.entity.PlcAddress;
import com.mes.s7.enhanced.EnhancedS7Serializer;
@@ -66,4 +67,52 @@
* @param s7Serializer S7搴忓垪鍖栧櫒
*/
void writePlcField(PlcAddress config, String fieldName, Object value, EnhancedS7Serializer s7Serializer);
+
+ /**
+ * 鏍规嵁DeviceConfig閰嶇疆鍜屽瓧娈靛悕绉拌鍙朠LC鏁版嵁
+ *
+ * @param device 璁惧閰嶇疆
+ * @param fieldNames 瑕佽鍙栫殑瀛楁鍚嶇О鍒楄〃
+ * @param s7Serializer S7搴忓垪鍖栧櫒
+ * @return 瀛楁鍚�->鍊� 鐨凪ap
+ */
+ Map<String, Object> readPlcData(DeviceConfig device, List<String> fieldNames, EnhancedS7Serializer s7Serializer);
+
+ /**
+ * 鏍规嵁DeviceConfig閰嶇疆鍜屾暟鎹甅ap鍐欏叆PLC
+ *
+ * @param device 璁惧閰嶇疆
+ * @param dataMap 瀛楁鍚�->鍊� 鐨凪ap
+ * @param s7Serializer S7搴忓垪鍖栧櫒
+ */
+ void writePlcData(DeviceConfig device, Map<String, Object> dataMap, EnhancedS7Serializer s7Serializer);
+
+ /**
+ * 璇诲彇PLC鎵�鏈夊瓧娈碉紙鍩轰簬DeviceConfig锛�
+ *
+ * @param device 璁惧閰嶇疆
+ * @param s7Serializer S7搴忓垪鍖栧櫒
+ * @return 鎵�鏈夊瓧娈电殑鍊�
+ */
+ Map<String, Object> readAllPlcData(DeviceConfig device, EnhancedS7Serializer s7Serializer);
+
+ /**
+ * 璇诲彇鍗曚釜瀛楁锛堝熀浜嶥eviceConfig锛�
+ *
+ * @param device 璁惧閰嶇疆
+ * @param fieldName 瀛楁鍚�
+ * @param s7Serializer S7搴忓垪鍖栧櫒
+ * @return 瀛楁鍊�
+ */
+ Object readPlcField(DeviceConfig device, String fieldName, EnhancedS7Serializer s7Serializer);
+
+ /**
+ * 鍐欏叆鍗曚釜瀛楁锛堝熀浜嶥eviceConfig锛�
+ *
+ * @param device 璁惧閰嶇疆
+ * @param fieldName 瀛楁鍚�
+ * @param value 瀛楁鍊�
+ * @param s7Serializer S7搴忓垪鍖栧櫒
+ */
+ void writePlcField(DeviceConfig device, String fieldName, Object value, EnhancedS7Serializer s7Serializer);
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java b/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java
index 9e53e9f..ba3905b 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/service/PlcTestWriteService.java
@@ -1,14 +1,22 @@
package com.mes.service;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
+import com.mes.device.entity.DeviceConfig;
+import com.mes.device.service.DeviceConfigService;
+import com.mes.device.util.ConfigJsonHelper;
import com.mes.entity.PlcBaseData;
import com.mes.entity.PlcAddress;
+import com.mes.service.PlcDynamicDataService;
import com.mes.s7.enhanced.EnhancedS7Serializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -25,6 +33,15 @@
@Resource
private PlcAddressService plcAddressService;
+
+ @Resource
+ private DeviceConfigService deviceConfigService;
+
+ @Resource
+ private PlcDynamicDataService plcDynamicDataService;
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {};
private static final int ON = 1;
private static final int OFF = 0;
@@ -47,33 +64,43 @@
*/
public boolean simulatePlcRequest(String projectId) {
try {
- // 鑾峰彇椤圭洰閰嶇疆锛堟暟鎹簱瀹炰綋锛�
PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId);
-
- // 鑾峰彇瀵瑰簲鐨凷7Serializer
- EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config);
-
- // 璇诲彇褰撳墠PLC鐘舵��
- PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
-
- if (currentData.getOnlineState() == OFF) {
- log.info("褰撳墠PLC鑱旀満妯″紡涓�0锛屽仠姝㈣仈鏈�");
+ if (config == null) {
+ log.error("椤圭洰閰嶇疆涓嶅瓨鍦�: projectId={}", projectId);
return false;
- }else if (currentData.getPlcReport() == ON){
- log.info("褰撳墠涓婄墖PLC姹囨姤瀛椾负1锛岄噸缃负0");
- currentData.setPlcReport(OFF);
}
- // 璁剧疆PLC璇锋眰瀛椾负1锛堣Е鍙慚ES浠诲姟澶勭悊锛�
- currentData.setPlcRequest(ON);
- s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex());
- log.info("妯℃嫙PLC鍙戦�佽姹傚瓧鎴愬姛锛歱lcRequest=1, projectId={}, dbArea={}, beginIndex={}",
- projectId, config.getDbArea(), config.getBeginIndex());
- return true;
-
+ EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config);
+ if (s7Serializer == null) {
+ log.error("鏃犳硶鍒涘缓S7Serializer: projectId={}", projectId);
+ return false;
+ }
+ return simulatePlcRequestInternal(projectId, config, s7Serializer);
} catch (Exception e) {
log.error("妯℃嫙PLC璇锋眰瀛楀け璐�", e);
return false;
}
+ }
+
+ private boolean simulatePlcRequestInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception {
+ PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
+ if (currentData == null) {
+ log.error("璇诲彇PLC鏁版嵁澶辫触锛岃繑鍥瀗ull: projectId={}, dbArea={}, beginIndex={}",
+ projectId, config.getDbArea(), config.getBeginIndex());
+ return false;
+ }
+
+ if (currentData.getOnlineState() == OFF) {
+ log.info("褰撳墠PLC鑱旀満妯″紡涓�0锛屽仠姝㈣仈鏈�");
+ return false;
+ } else if (currentData.getPlcReport() == ON) {
+ log.info("褰撳墠涓婄墖PLC姹囨姤瀛椾负1锛岄噸缃负0");
+ currentData.setPlcReport(OFF);
+ }
+ currentData.setPlcRequest(ON);
+ s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex());
+ log.info("妯℃嫙PLC鍙戦�佽姹傚瓧鎴愬姛锛歱lcRequest=1, projectId={}, dbArea={}, beginIndex={}",
+ projectId, config.getDbArea(), config.getBeginIndex());
+ return true;
}
/**
@@ -90,29 +117,41 @@
try {
// 鑾峰彇椤圭洰閰嶇疆锛堟暟鎹簱瀹炰綋锛�
PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId);
+ if (config == null) {
+ log.error("椤圭洰閰嶇疆涓嶅瓨鍦�: projectId={}", projectId);
+ return false;
+ }
// 鑾峰彇瀵瑰簲鐨凷7Serializer
EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config);
+ if (s7Serializer == null) {
+ log.error("鏃犳硶鍒涘缓S7Serializer: projectId={}", projectId);
+ return false;
+ }
- PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
-
- // 璁剧疆PLC姹囨姤瀛椾负1锛堜换鍔″畬鎴愶級
- currentData.setPlcReport(ON);
- // 璇锋眰瀛楁竻0
- currentData.setPlcRequest(OFF);
-
- // 璁剧疆瀹屾垚鏁伴噺绛夋暟鎹�
- currentData.setMesGlassCount(10);
-
- s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex());
- log.info("妯℃嫙PLC浠诲姟瀹屾垚姹囨姤锛歱lcReport=1, mesGlassCount=10, projectId={}, dbArea={}, beginIndex={}",
- projectId, config.getDbArea(), config.getBeginIndex());
- return true;
-
+ return simulatePlcReportInternal(projectId, config, s7Serializer);
} catch (Exception e) {
log.error("妯℃嫙PLC浠诲姟瀹屾垚姹囨姤澶辫触", e);
return false;
}
+ }
+
+ private boolean simulatePlcReportInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception {
+ PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
+ if (currentData == null) {
+ log.error("璇诲彇PLC鏁版嵁澶辫触锛岃繑鍥瀗ull: projectId={}, dbArea={}, beginIndex={}",
+ projectId, config.getDbArea(), config.getBeginIndex());
+ return false;
+ }
+
+ currentData.setPlcReport(ON);
+ currentData.setPlcRequest(OFF);
+ currentData.setMesGlassCount(10);
+
+ s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex());
+ log.info("妯℃嫙PLC浠诲姟瀹屾垚姹囨姤锛歱lcReport=1, mesGlassCount=10, projectId={}, dbArea={}, beginIndex={}",
+ projectId, config.getDbArea(), config.getBeginIndex());
+ return true;
}
/**
@@ -129,24 +168,38 @@
try {
// 鑾峰彇椤圭洰閰嶇疆锛堟暟鎹簱瀹炰綋锛�
PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId);
+ if (config == null) {
+ log.error("椤圭洰閰嶇疆涓嶅瓨鍦�: projectId={}", projectId);
+ return false;
+ }
// 鑾峰彇瀵瑰簲鐨凷7Serializer
EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config);
+ if (s7Serializer == null) {
+ log.error("鏃犳硶鍒涘缓S7Serializer: projectId={}", projectId);
+ return false;
+ }
- PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
-
- // 1:鑱旀満 0:鑴辨満
- currentData.setOnlineState(onlineState);
-
- s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex());
- log.info("妯℃嫙PLC鑱旀満鐘舵�侊細onlineState={}, projectId={}, dbArea={}, beginIndex={}",
- onlineState, projectId, config.getDbArea(), config.getBeginIndex());
- return true;
-
+ return simulateOnlineStatusInternal(onlineState, projectId, config, s7Serializer);
} catch (Exception e) {
log.error("妯℃嫙PLC鑱旀満鐘舵�佸け璐�", e);
return false;
}
+ }
+
+ private boolean simulateOnlineStatusInternal(int onlineState, String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception {
+ PlcBaseData currentData = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
+ if (currentData == null) {
+ log.error("璇诲彇PLC鏁版嵁澶辫触锛岃繑鍥瀗ull: projectId={}, dbArea={}, beginIndex={}",
+ projectId, config.getDbArea(), config.getBeginIndex());
+ return false;
+ }
+
+ currentData.setOnlineState(onlineState);
+ s7Serializer.write(currentData, config.getDbArea(), config.getBeginIndex());
+ log.info("妯℃嫙PLC鑱旀満鐘舵�侊細onlineState={}, projectId={}, dbArea={}, beginIndex={}",
+ onlineState, projectId, config.getDbArea(), config.getBeginIndex());
+ return true;
}
/**
@@ -163,32 +216,37 @@
try {
// 鑾峰彇椤圭洰閰嶇疆锛堟暟鎹簱瀹炰綋锛�
PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId);
+ if (config == null) {
+ log.error("椤圭洰閰嶇疆涓嶅瓨鍦�: projectId={}", projectId);
+ return false;
+ }
- // 鑾峰彇瀵瑰簲鐨凷7Serializer
EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config);
-
- PlcBaseData resetData = new PlcBaseData();
-
- // 閲嶇疆鎵�鏈夊叧閿瓧娈�
- resetData.setPlcRequest(OFF);
- resetData.setPlcReport(OFF);
- resetData.setMesSend(OFF);
- resetData.setMesConfirm(OFF);
- // 榛樿鑱旀満
- resetData.setOnlineState(ON);
- resetData.setMesGlassCount(0);
- // 娓呴櫎鎶ヨ
- resetData.setAlarmInfo(OFF);
-
- s7Serializer.write(resetData, config.getDbArea(), config.getBeginIndex());
- log.info("PLC鐘舵�佸凡閲嶇疆, projectId={}, dbArea={}, beginIndex={}",
- projectId, config.getDbArea(), config.getBeginIndex());
- return true;
-
+ if (s7Serializer == null) {
+ log.error("鏃犳硶鍒涘缓S7Serializer: projectId={}", projectId);
+ return false;
+ }
+ return resetPlcInternal(projectId, config, s7Serializer);
} catch (Exception e) {
log.error("閲嶇疆PLC鐘舵�佸け璐�", e);
return false;
}
+ }
+
+ private boolean resetPlcInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception {
+ PlcBaseData resetData = new PlcBaseData();
+ resetData.setPlcRequest(OFF);
+ resetData.setPlcReport(OFF);
+ resetData.setMesSend(OFF);
+ resetData.setMesConfirm(OFF);
+ resetData.setOnlineState(ON);
+ resetData.setMesGlassCount(0);
+ resetData.setAlarmInfo(OFF);
+
+ s7Serializer.write(resetData, config.getDbArea(), config.getBeginIndex());
+ log.info("PLC鐘舵�佸凡閲嶇疆, projectId={}, dbArea={}, beginIndex={}",
+ projectId, config.getDbArea(), config.getBeginIndex());
+ return true;
}
/**
@@ -205,15 +263,31 @@
try {
// 鑾峰彇椤圭洰閰嶇疆锛堟暟鎹簱瀹炰綋锛�
PlcAddress config = plcAddressService.getProjectConfigWithMapping(projectId);
+ if (config == null) {
+ log.error("椤圭洰閰嶇疆涓嶅瓨鍦�: projectId={}", projectId);
+ return null;
+ }
- // 鑾峰彇瀵瑰簲鐨凷7Serializer
EnhancedS7Serializer s7Serializer = getSerializerForProject(projectId, config);
+ if (s7Serializer == null) {
+ log.error("鏃犳硶鍒涘缓S7Serializer: projectId={}", projectId);
+ return null;
+ }
- return s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
+ return readPlcStatusInternal(projectId, config, s7Serializer);
} catch (Exception e) {
log.error("璇诲彇PLC鐘舵�佸け璐�", e);
return null;
}
+ }
+
+ private PlcBaseData readPlcStatusInternal(String projectId, PlcAddress config, EnhancedS7Serializer s7Serializer) throws Exception {
+ PlcBaseData data = s7Serializer.read(PlcBaseData.class, config.getDbArea(), config.getBeginIndex());
+ if (data == null) {
+ log.error("璇诲彇PLC鐘舵�佽繑鍥瀗ull: projectId={}, dbArea={}, beginIndex={}",
+ projectId, config.getDbArea(), config.getBeginIndex());
+ }
+ return data;
}
/**
@@ -276,4 +350,300 @@
serializerCache.clear();
log.info("宸叉竻闄ゆ墍鏈塖7Serializer缂撳瓨");
}
+
+ /**
+ * 鏍规嵁璁惧ID妯℃嫙PLC鍙戦�佽姹傚瓧
+ *
+ * @param deviceId 璁惧ID
+ * @return 鏄惁鎴愬姛
+ */
+ public boolean simulatePlcRequestByDevice(Long deviceId) {
+ DeviceConfig device = deviceConfigService.getDeviceById(deviceId);
+ if (device == null) {
+ log.error("璁惧涓嶅瓨鍦�: deviceId={}", deviceId);
+ return false;
+ }
+ try {
+ String projectId = resolveProjectId(device);
+ PlcAddress config = buildPlcAddressFromDevice(device);
+ EnhancedS7Serializer s7Serializer = getSerializerForDevice(device);
+ return simulatePlcRequestInternal(projectId, config, s7Serializer);
+ } catch (Exception e) {
+ log.error("鏍规嵁璁惧妯℃嫙PLC璇锋眰瀛楀け璐�: deviceId={}", deviceId, e);
+ return false;
+ }
+ }
+
+ /**
+ * 鏍规嵁璁惧ID妯℃嫙PLC浠诲姟瀹屾垚姹囨姤
+ *
+ * @param deviceId 璁惧ID
+ * @return 鏄惁鎴愬姛
+ */
+ public boolean simulatePlcReportByDevice(Long deviceId) {
+ DeviceConfig device = deviceConfigService.getDeviceById(deviceId);
+ if (device == null) {
+ log.error("璁惧涓嶅瓨鍦�: deviceId={}", deviceId);
+ return false;
+ }
+ try {
+ String projectId = resolveProjectId(device);
+ PlcAddress config = buildPlcAddressFromDevice(device);
+ EnhancedS7Serializer s7Serializer = getSerializerForDevice(device);
+ return simulatePlcReportInternal(projectId, config, s7Serializer);
+ } catch (Exception e) {
+ log.error("鏍规嵁璁惧妯℃嫙PLC姹囨姤澶辫触: deviceId={}", deviceId, e);
+ return false;
+ }
+ }
+
+ /**
+ * 鏍规嵁璁惧ID閲嶇疆PLC鎵�鏈夌姸鎬�
+ *
+ * @param deviceId 璁惧ID
+ * @return 鏄惁鎴愬姛
+ */
+ public boolean resetPlcByDevice(Long deviceId) {
+ DeviceConfig device = deviceConfigService.getDeviceById(deviceId);
+ if (device == null) {
+ log.error("璁惧涓嶅瓨鍦�: deviceId={}", deviceId);
+ return false;
+ }
+ try {
+ String projectId = resolveProjectId(device);
+ PlcAddress config = buildPlcAddressFromDevice(device);
+ EnhancedS7Serializer s7Serializer = getSerializerForDevice(device);
+ return resetPlcInternal(projectId, config, s7Serializer);
+ } catch (Exception e) {
+ log.error("鏍规嵁璁惧閲嶇疆PLC鐘舵�佸け璐�: deviceId={}", deviceId, e);
+ return false;
+ }
+ }
+
+ /**
+ * 鏍规嵁璁惧ID璇诲彇PLC褰撳墠鐘舵��
+ *
+ * @param deviceId 璁惧ID
+ * @return PLC鐘舵�佹暟鎹�
+ */
+ public Map<String, Object> readPlcStatusByDevice(Long deviceId) {
+ DeviceConfig device = deviceConfigService.getDeviceById(deviceId);
+ if (device == null) {
+ log.error("璁惧涓嶅瓨鍦�: deviceId={}", deviceId);
+ return null;
+ }
+ try {
+ String projectId = resolveProjectId(device);
+ PlcAddress config = buildPlcAddressFromDevice(device);
+ EnhancedS7Serializer s7Serializer = getSerializerForDevice(device);
+ PlcBaseData data = readPlcStatusInternal(projectId, config, s7Serializer);
+ if (data == null) {
+ return null;
+ }
+ String json = objectMapper.writeValueAsString(data);
+ return objectMapper.readValue(json, MAP_TYPE);
+ } catch (Exception e) {
+ log.error("璇诲彇璁惧PLC鐘舵�佸け璐�: deviceId={}", deviceId, e);
+ return null;
+ }
+ }
+
+ /**
+ * 鏍规嵁璁惧ID鍐欏叆PLC瀛楁
+ *
+ * @param deviceId 璁惧ID
+ * @param fieldValues 瀛楁鍚�->鍊� 鐨凪ap
+ * @return 鏄惁鎴愬姛
+ */
+ public boolean writeFieldsByDevice(Long deviceId, Map<String, Object> fieldValues) {
+ DeviceConfig device = deviceConfigService.getDeviceById(deviceId);
+ if (device == null) {
+ log.error("璁惧涓嶅瓨鍦�: deviceId={}", deviceId);
+ return false;
+ }
+
+ try {
+ // 浠庤澶囬厤缃腑鑾峰彇椤圭洰鏍囪瘑
+ String projectId = resolveProjectId(device);
+
+ // 鑾峰彇瀵瑰簲鐨凷7Serializer锛堜娇鐢ㄨ澶囬厤缃級
+ EnhancedS7Serializer s7Serializer = getSerializerForDevice(device);
+
+ // 浣跨敤鍔ㄦ�佹暟鎹湇鍔″啓鍏ュ瓧娈碉紙鍩轰簬DeviceConfig锛�
+ plcDynamicDataService.writePlcData(device, fieldValues, s7Serializer);
+
+ log.info("鍐欏叆PLC瀛楁鎴愬姛: deviceId={}, projectId={}, fields={}", deviceId, projectId, fieldValues.keySet());
+ return true;
+ } catch (Exception e) {
+ log.error("鍐欏叆PLC瀛楁澶辫触: deviceId={}", deviceId, e);
+ return false;
+ }
+ }
+
+ /**
+ * 鑾峰彇璁惧瀵瑰簲鐨凷7Serializer瀹炰緥
+ *
+ * @param device 璁惧閰嶇疆
+ * @return S7Serializer瀹炰緥
+ */
+ private EnhancedS7Serializer getSerializerForDevice(DeviceConfig device) {
+ String cacheKey = "device:" + (device.getId() != null ? device.getId() : resolveProjectId(device));
+ return serializerCache.computeIfAbsent(cacheKey, id -> {
+ // 瑙f瀽PLC绫诲瀷锛堜粎鍙栧疄浣撳瓧娈碉級
+ EPlcType plcType = EPlcType.S1200;
+ String plcTypeValue = device.getPlcType();
+ if (plcTypeValue == null || plcTypeValue.isEmpty()) {
+ log.warn("璁惧鏈厤缃甈LC绫诲瀷锛屼娇鐢ㄩ粯璁ょ被鍨婼1200, deviceId={}", device.getId());
+ } else {
+ try {
+ plcType = EPlcType.valueOf(plcTypeValue);
+ } catch (IllegalArgumentException e) {
+ log.warn("鏈煡鐨凱LC绫诲瀷: {}, 浣跨敤榛樿绫诲瀷 S1200", plcTypeValue);
+ }
+ }
+
+ // 鍒涘缓S7PLC瀹炰緥锛堜粎鍙栧疄浣撳瓧娈碉級
+ String plcIp = device.getPlcIp();
+ if (plcIp == null || plcIp.isEmpty()) {
+ log.warn("璁惧鏈厤缃甈LC IP锛屼娇鐢ㄩ粯璁� 192.168.10.21, deviceId={}", device.getId());
+ plcIp = "192.168.10.21";
+ }
+ S7PLC s7Plc = new S7PLC(plcType, plcIp);
+
+ // 鍒涘缓骞惰繑鍥濫nhancedS7Serializer瀹炰緥
+ return EnhancedS7Serializer.newInstance(s7Plc);
+ });
+ }
+
+ private PlcAddress buildPlcAddressFromDevice(DeviceConfig device) {
+ Map<String, Object> plcConfig = getPlcConfigParams(device);
+ String dbArea = plcConfig.get("dbArea") != null ? String.valueOf(plcConfig.get("dbArea")) : "DB12";
+ int beginIndex = plcConfig.get("beginIndex") != null ? parseInteger(plcConfig.get("beginIndex")) : 0;
+
+ String plcIp = device.getPlcIp();
+ if (plcIp == null || plcIp.isEmpty()) {
+ log.warn("璁惧鏈厤缃甈LC IP锛屼娇鐢ㄩ粯璁� 192.168.10.21, deviceId={}", device.getId());
+ plcIp = "192.168.10.21";
+ }
+
+ String plcType = device.getPlcType();
+ if (plcType == null || plcType.isEmpty()) {
+ log.warn("璁惧鏈厤缃甈LC绫诲瀷锛屼娇鐢ㄩ粯璁1200, deviceId={}", device.getId());
+ plcType = EPlcType.S1200.name();
+ }
+
+ String addressMapping = resolveAddressMapping(device);
+
+ PlcAddress config = new PlcAddress();
+ config.setProjectId(resolveProjectId(device));
+ config.setDbArea(dbArea);
+ config.setBeginIndex(beginIndex);
+ config.setPlcIp(plcIp);
+ config.setPlcType(plcType);
+ config.setAddressMapping(addressMapping);
+ return config;
+ }
+
+ private String resolveAddressMapping(DeviceConfig device) {
+ Map<String, Object> mapping = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper);
+ if (!mapping.isEmpty()) {
+ try {
+ return objectMapper.writeValueAsString(mapping);
+ } catch (Exception e) {
+ log.warn("搴忓垪鍖朿onfigJson瀛楁鏄犲皠澶辫触, deviceId={}", device.getId(), e);
+ }
+ }
+ Map<String, Object> extraParams = parseExtraParams(device);
+ Object addressMapping = extraParams.get("addressMapping");
+ if (addressMapping instanceof String) {
+ return (String) addressMapping;
+ }
+ if (addressMapping != null) {
+ try {
+ return objectMapper.writeValueAsString(addressMapping);
+ } catch (Exception e) {
+ log.warn("搴忓垪鍖杄xtraParams.addressMapping澶辫触, deviceId={}", device.getId(), e);
+ }
+ }
+ throw new IllegalStateException("璁惧鏈厤缃甈LC瀛楁鏄犲皠, deviceId=" + device.getId());
+ }
+
+ private Map<String, Object> parseExtraParams(DeviceConfig device) {
+ if (device.getExtraParams() == null || device.getExtraParams().trim().isEmpty()) {
+ return Collections.emptyMap();
+ }
+ try {
+ return objectMapper.readValue(device.getExtraParams(), MAP_TYPE);
+ } catch (Exception e) {
+ log.warn("瑙f瀽璁惧extraParams澶辫触, deviceId={}", device.getId(), e);
+ return Collections.emptyMap();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, Object> getPlcConfigParams(DeviceConfig device) {
+ Map<String, Object> extraParams = parseExtraParams(device);
+ Object plcConfig = extraParams.get("plcConfig");
+ if (plcConfig instanceof Map) {
+ return (Map<String, Object>) plcConfig;
+ }
+ if (plcConfig instanceof String) {
+ try {
+ return objectMapper.readValue((String) plcConfig, MAP_TYPE);
+ } catch (Exception e) {
+ log.warn("瑙f瀽extraParams.plcConfig澶辫触, deviceId={}", device.getId(), e);
+ }
+ }
+ return Collections.emptyMap();
+ }
+
+ private int parseInteger(Object value) {
+ if (value instanceof Number) {
+ return ((Number) value).intValue();
+ }
+ try {
+ return Integer.parseInt(String.valueOf(value));
+ } catch (NumberFormatException ex) {
+ log.warn("鏃犳硶瑙f瀽鏁村瀷鍊�: {}", value);
+ return 0;
+ }
+ }
+
+ /**
+ * 浠庤澶囬厤缃腑瑙f瀽椤圭洰鏍囪瘑
+ *
+ * @param device 璁惧閰嶇疆
+ * @return 椤圭洰鏍囪瘑
+ */
+ private String resolveProjectId(DeviceConfig device) {
+ if (device == null) {
+ throw new IllegalArgumentException("璁惧淇℃伅涓虹┖");
+ }
+
+ // 1. 浼樺厛浣跨敤瀹炰綋涓婄殑projectId
+ if (device.getProjectId() != null) {
+ return String.valueOf(device.getProjectId());
+ }
+
+ // 2. 浠巈xtraParams涓鍙�
+ Map<String, Object> extraParams = parseExtraParams(device);
+ Object plcProjectId = extraParams.get("plcProjectId");
+ if (plcProjectId != null) {
+ return String.valueOf(plcProjectId);
+ }
+
+ // 3. 鍏煎鏃х粨鏋勶細configJson鎴杄xtraParams鍐呭祵
+ Map<String, Object> configParams = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper);
+ Object legacyProjectId = configParams.get("plcProjectId");
+ if (legacyProjectId != null) {
+ return String.valueOf(legacyProjectId);
+ }
+
+ // 鏈�鍚庝娇鐢ㄨ澶囩紪鍙�
+ if (device.getDeviceCode() != null && !device.getDeviceCode().isEmpty()) {
+ return device.getDeviceCode();
+ }
+
+ throw new IllegalStateException("鏃犳硶瑙f瀽璁惧鐨凱LC椤圭洰鏍囪瘑, deviceId=" + device.getId());
+ }
}
\ No newline at end of file
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcAutoTestServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcAutoTestServiceImpl.java
index 5a2cfe5..74fd0a2 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcAutoTestServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcAutoTestServiceImpl.java
@@ -1,7 +1,7 @@
package com.mes.service.impl;
-import com.mes.service.IPlcAutoTestService;
-import com.mes.service.IPlcTestWriteService;
+import com.mes.service.PlcAutoTestService;
+import com.mes.service.PlcTestWriteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
@@ -18,10 +18,10 @@
*/
@Slf4j
@Service
-public class PlcAutoTestServiceImpl implements PlcAutoTestService {
+public class PlcAutoTestServiceImpl extends PlcAutoTestService {
@Resource
- private IPlcTestWriteService plcTestWriteService;
+ private PlcTestWriteService plcTestWriteService;
// 鑷姩娴嬭瘯寮�鍏�
@Value("${plc.auto.test.enabled:false}")
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java
index 2a1b439..15a52af 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcDynamicDataServiceImpl.java
@@ -1,8 +1,12 @@
package com.mes.service.impl;
import com.alibaba.fastjson.JSONObject;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.xingshuangs.iot.common.enums.EDataType;
import com.github.xingshuangs.iot.protocol.s7.serializer.S7Parameter;
+import com.mes.device.entity.DeviceConfig;
+import com.mes.device.util.ConfigJsonHelper;
import com.mes.entity.PlcAddress;
import com.mes.s7.enhanced.EnhancedS7Serializer;
import com.mes.service.PlcDynamicDataService;
@@ -10,6 +14,7 @@
import org.springframework.stereotype.Service;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -24,6 +29,9 @@
@Slf4j
@Service
public class PlcDynamicDataServiceImpl implements PlcDynamicDataService {
+
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<Map<String, Object>>() {};
/**
* 鏍规嵁PlcAddress閰嶇疆鍜屽瓧娈靛悕绉拌鍙朠LC鏁版嵁
@@ -59,8 +67,8 @@
return resultMap;
} catch (Exception e) {
- log.error("璇诲彇PLC鏁版嵁澶辫触锛岃妫�鏌ワ細1.PLC IP鍦板潃鏄惁姝g‘[{}] 2.PLC璁惧鏄惁鍦ㄧ嚎 3.缃戠粶杩炴帴鏄惁姝e父锛宮odule: {}, 璇︾粏閿欒: {}",
- config.getPlcIp(), config.getModule(), e.getMessage(), e);
+ log.error("璇诲彇PLC鏁版嵁澶辫触锛岃妫�鏌ワ細1.PLC IP鍦板潃鏄惁姝g‘[{}] 2.PLC璁惧鏄惁鍦ㄧ嚎 3.缃戠粶杩炴帴鏄惁姝e父锛岃缁嗛敊璇�: {}",
+ config.getPlcIp(), e.getMessage(), e);
return new HashMap<>();
}
}
@@ -88,8 +96,8 @@
// 鍐欏叆PLC
s7Serializer.write(parameters);
} catch (Exception e) {
- log.error("鍐欏叆PLC鏁版嵁澶辫触锛岃妫�鏌ワ細1.PLC IP鍦板潃鏄惁姝g‘[{}] 2.PLC璁惧鏄惁鍦ㄧ嚎 3.缃戠粶杩炴帴鏄惁姝e父锛宮odule: {}, 璇︾粏閿欒: {}",
- config.getPlcIp(), config.getModule(), e.getMessage(), e);
+ log.error("鍐欏叆PLC鏁版嵁澶辫触锛岃妫�鏌ワ細1.PLC IP鍦板潃鏄惁姝g‘[{}] 2.PLC璁惧鏄惁鍦ㄧ嚎 3.缃戠粶杩炴帴鏄惁姝e父锛岃缁嗛敊璇�: {}",
+ config.getPlcIp(), e.getMessage(), e);
}
}
@@ -212,4 +220,287 @@
return parameters;
}
+
+ /**
+ * 浠嶥eviceConfig涓彁鍙栧湴鍧�鏄犲皠閰嶇疆
+ *
+ * @param device 璁惧閰嶇疆
+ * @return 鍦板潃鏄犲皠JSON瀛楃涓�
+ */
+ private String extractAddressMapping(DeviceConfig device) {
+ // configJson 鐜板湪浠呭瓨鏀惧瓧娈靛湴鍧�鏄犲皠锛堟暟缁勫舰寮忥級
+ Map<String, Object> configParams = ConfigJsonHelper.parseToMap(device.getConfigJson(), objectMapper);
+ if (!configParams.isEmpty()) {
+ try {
+ return objectMapper.writeValueAsString(configParams);
+ } catch (Exception e) {
+ log.warn("搴忓垪鍖朿onfigJson鍦板潃鏄犲皠澶辫触, deviceId={}", device.getId(), e);
+ }
+ }
+
+ // 鍏舵浠巈xtraParams涓幏鍙栵紙鍏煎鏃х粨鏋勶級
+ Map<String, Object> extraParams = parseExtraParams(device);
+ Object addressMapping = extraParams.get("addressMapping");
+ if (addressMapping != null) {
+ if (addressMapping instanceof String) {
+ return (String) addressMapping;
+ } else {
+ try {
+ return objectMapper.writeValueAsString(addressMapping);
+ } catch (Exception e) {
+ log.warn("搴忓垪鍖杄xtraParams.addressMapping澶辫触, deviceId={}", device.getId(), e);
+ }
+ }
+ }
+
+ throw new IllegalArgumentException("璁惧閰嶇疆涓湭鎵惧埌addressMapping, deviceId=" + device.getId());
+ }
+
+ /**
+ * 浠嶥eviceConfig涓彁鍙杁bArea
+ *
+ * @param device 璁惧閰嶇疆
+ * @return dbArea
+ */
+ private String extractDbArea(DeviceConfig device) {
+ // 浠巈xtraParams.plcConfig涓幏鍙栵紙鏂扮粨鏋勶級
+ Map<String, Object> plcConfig = getPlcConfig(device);
+ Object dbArea = plcConfig.get("dbArea");
+ if (dbArea != null) {
+ return String.valueOf(dbArea);
+ }
+
+ // 鍏煎鏃х粨鏋勶細extraParams鏍硅妭鐐�
+ Map<String, Object> extraParams = parseExtraParams(device);
+ Object legacyDbArea = extraParams.get("dbArea");
+ if (legacyDbArea != null) {
+ return String.valueOf(legacyDbArea);
+ }
+
+ // 榛樿鍊�
+ return "DB12";
+ }
+
+ /**
+ * 浠嶥eviceConfig涓彁鍙朾eginIndex
+ *
+ * @param device 璁惧閰嶇疆
+ * @return beginIndex
+ */
+ private int extractBeginIndex(DeviceConfig device) {
+ // 浠巈xtraParams.plcConfig涓幏鍙�
+ Map<String, Object> plcConfig = getPlcConfig(device);
+ Object beginIndex = plcConfig.get("beginIndex");
+ if (beginIndex != null) {
+ return parseInteger(beginIndex);
+ }
+
+ // 鍏煎鏃х粨鏋勶細extraParams鏍硅妭鐐�
+ Map<String, Object> extraParams = parseExtraParams(device);
+ Object legacyBeginIndex = extraParams.get("beginIndex");
+ if (legacyBeginIndex != null) {
+ return parseInteger(legacyBeginIndex);
+ }
+
+ // 榛樿鍊�
+ return 0;
+ }
+
+ private Map<String, Object> parseExtraParams(DeviceConfig device) {
+ if (device.getExtraParams() == null || device.getExtraParams().trim().isEmpty()) {
+ return Collections.emptyMap();
+ }
+ try {
+ return objectMapper.readValue(device.getExtraParams(), MAP_TYPE);
+ } catch (Exception e) {
+ log.warn("瑙f瀽璁惧extraParams澶辫触, deviceId={}", device.getId(), e);
+ return Collections.emptyMap();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, Object> getPlcConfig(DeviceConfig device) {
+ Map<String, Object> extraParams = parseExtraParams(device);
+ Object plcConfig = extraParams.get("plcConfig");
+ if (plcConfig instanceof Map) {
+ return (Map<String, Object>) plcConfig;
+ }
+ if (plcConfig instanceof String) {
+ try {
+ return objectMapper.readValue((String) plcConfig, MAP_TYPE);
+ } catch (Exception e) {
+ log.warn("瑙f瀽extraParams.plcConfig澶辫触, deviceId={}", device.getId(), e);
+ }
+ }
+ return Collections.emptyMap();
+ }
+
+ private int parseInteger(Object value) {
+ if (value instanceof Number) {
+ return ((Number) value).intValue();
+ }
+ try {
+ return Integer.parseInt(String.valueOf(value));
+ } catch (NumberFormatException ex) {
+ log.warn("鏃犳硶瑙f瀽鏁存暟鍊�: {}", value);
+ return 0;
+ }
+ }
+
+ @Override
+ public Map<String, Object> readPlcData(DeviceConfig device, List<String> fieldNames, EnhancedS7Serializer s7Serializer) {
+ if (device == null) {
+ throw new IllegalArgumentException("璁惧閰嶇疆涓嶈兘涓虹┖");
+ }
+
+ String addressMapping = extractAddressMapping(device);
+ if (addressMapping == null || addressMapping.isEmpty()) {
+ throw new IllegalArgumentException("璁惧閰嶇疆涓璦ddressMapping涓嶈兘涓虹┖");
+ }
+
+ try {
+ // 瑙f瀽addressMapping JSON閰嶇疆
+ JSONObject addressMappingObj = JSONObject.parseObject(addressMapping);
+
+ // 鏋勫缓S7Parameter鍒楄〃
+ String dbArea = extractDbArea(device);
+ List<S7Parameter> parameters = buildS7ParametersForDevice(device, dbArea, addressMappingObj, fieldNames);
+
+ // 浠嶱LC璇诲彇鏁版嵁
+ List<S7Parameter> results = s7Serializer.read(parameters);
+
+ // 灏嗙粨鏋滆浆鎹负Map
+ Map<String, Object> resultMap = new HashMap<>();
+ for (int i = 0; i < fieldNames.size() && i < results.size(); i++) {
+ String fieldName = fieldNames.get(i);
+ Object value = results.get(i).getValue();
+ resultMap.put(fieldName, value);
+ }
+
+ return resultMap;
+ } catch (Exception e) {
+ log.error("璇诲彇PLC鏁版嵁澶辫触锛岃妫�鏌ワ細1.PLC IP鍦板潃鏄惁姝g‘[{}] 2.PLC璁惧鏄惁鍦ㄧ嚎 3.缃戠粶杩炴帴鏄惁姝e父锛宒eviceId: {}, 璇︾粏閿欒: {}",
+ device.getPlcIp(), device.getId(), e.getMessage(), e);
+ return new HashMap<>();
+ }
+ }
+
+ @Override
+ public void writePlcData(DeviceConfig device, Map<String, Object> dataMap, EnhancedS7Serializer s7Serializer) {
+ if (device == null) {
+ throw new IllegalArgumentException("璁惧閰嶇疆涓嶈兘涓虹┖");
+ }
+
+ String addressMapping = extractAddressMapping(device);
+ if (addressMapping == null || addressMapping.isEmpty()) {
+ throw new IllegalArgumentException("璁惧閰嶇疆涓璦ddressMapping涓嶈兘涓虹┖");
+ }
+
+ try {
+ // 瑙f瀽addressMapping JSON閰嶇疆
+ JSONObject addressMappingObj = JSONObject.parseObject(addressMapping);
+
+ // 鏋勫缓S7Parameter鍒楄〃锛屽苟濉厖鍊�
+ String dbArea = extractDbArea(device);
+ List<S7Parameter> parameters = buildS7ParametersWithValuesForDevice(device, dbArea, addressMappingObj, dataMap);
+
+ // 鍐欏叆PLC
+ s7Serializer.write(parameters);
+ } catch (Exception e) {
+ log.error("鍐欏叆PLC鏁版嵁澶辫触锛岃妫�鏌ワ細1.PLC IP鍦板潃鏄惁姝g‘[{}] 2.PLC璁惧鏄惁鍦ㄧ嚎 3.缃戠粶杩炴帴鏄惁姝e父锛宒eviceId: {}, 璇︾粏閿欒: {}",
+ device.getPlcIp(), device.getId(), e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public Map<String, Object> readAllPlcData(DeviceConfig device, EnhancedS7Serializer s7Serializer) {
+ if (device == null) {
+ throw new IllegalArgumentException("璁惧閰嶇疆涓嶈兘涓虹┖");
+ }
+
+ String addressMapping = extractAddressMapping(device);
+ if (addressMapping == null || addressMapping.isEmpty()) {
+ throw new IllegalArgumentException("璁惧閰嶇疆涓璦ddressMapping涓嶈兘涓虹┖");
+ }
+
+ // 鑾峰彇鎵�鏈夊瓧娈靛悕
+ JSONObject addressMappingObj = JSONObject.parseObject(addressMapping);
+ List<String> allFields = new ArrayList<>(addressMappingObj.keySet());
+
+ // 璇诲彇鎵�鏈夊瓧娈�
+ return readPlcData(device, allFields, s7Serializer);
+ }
+
+ @Override
+ public Object readPlcField(DeviceConfig device, String fieldName, EnhancedS7Serializer s7Serializer) {
+ List<String> fields = new ArrayList<>();
+ fields.add(fieldName);
+
+ Map<String, Object> result = readPlcData(device, fields, s7Serializer);
+ return result.get(fieldName);
+ }
+
+ @Override
+ public void writePlcField(DeviceConfig device, String fieldName, Object value, EnhancedS7Serializer s7Serializer) {
+ Map<String, Object> dataMap = new HashMap<>();
+ dataMap.put(fieldName, value);
+
+ writePlcData(device, dataMap, s7Serializer);
+ }
+
+ /**
+ * 鏋勫缓S7Parameter鍒楄〃锛堜笉鍖呭惈鍊硷級- 鍩轰簬DeviceConfig
+ */
+ private List<S7Parameter> buildS7ParametersForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, List<String> fieldNames) {
+ List<S7Parameter> parameters = new ArrayList<>();
+
+ for (String fieldName : fieldNames) {
+ if (!addressMapping.containsKey(fieldName)) {
+ log.warn("瀛楁 {} 鍦╝ddressMapping涓笉瀛樺湪锛岃烦杩�", fieldName);
+ continue;
+ }
+
+ // 鑾峰彇瀛楁鐨勫亸绉诲湴鍧�
+ int offset = addressMapping.getInteger(fieldName);
+
+ // 鏋勫缓瀹屾暣鍦板潃锛歞bArea + offset锛堝锛欴B12.2锛�
+ String fullAddress = dbArea + "." + offset;
+
+ // 鍒涘缓S7Parameter锛岄粯璁や娇鐢║INT16绫诲瀷锛�16浣嶆棤绗﹀彿鏁存暟锛�
+ S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1);
+ parameters.add(parameter);
+ }
+
+ return parameters;
+ }
+
+ /**
+ * 鏋勫缓S7Parameter鍒楄〃锛堝寘鍚�硷級- 鍩轰簬DeviceConfig
+ */
+ private List<S7Parameter> buildS7ParametersWithValuesForDevice(DeviceConfig device, String dbArea, JSONObject addressMapping, Map<String, Object> dataMap) {
+ List<S7Parameter> parameters = new ArrayList<>();
+
+ for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
+ String fieldName = entry.getKey();
+ Object value = entry.getValue();
+
+ if (!addressMapping.containsKey(fieldName)) {
+ log.warn("瀛楁 {} 鍦╝ddressMapping涓笉瀛樺湪锛岃烦杩�", fieldName);
+ continue;
+ }
+
+ // 鑾峰彇瀛楁鐨勫亸绉诲湴鍧�
+ int offset = addressMapping.getInteger(fieldName);
+
+ // 鏋勫缓瀹屾暣鍦板潃
+ String fullAddress = dbArea + "." + offset;
+
+ // 鍒涘缓S7Parameter锛岃缃��
+ S7Parameter parameter = new S7Parameter(fullAddress, EDataType.UINT16, 1);
+ parameter.setValue(value);
+ parameters.add(parameter);
+ }
+
+ return parameters;
+ }
}
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcTestWriteServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcTestWriteServiceImpl.java
index b0fb233..b2ebbc1 100644
--- a/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcTestWriteServiceImpl.java
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/service/impl/PlcTestWriteServiceImpl.java
@@ -2,11 +2,11 @@
import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
-import com.mes.entity.PlcBaseData;
import com.mes.entity.PlcAddress;
+import com.mes.entity.PlcBaseData;
import com.mes.s7.enhanced.EnhancedS7Serializer;
import com.mes.service.PlcAddressService;
-import com.mes.service.IPlcTestWriteService;
+import com.mes.service.PlcTestWriteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -23,7 +23,7 @@
*/
@Slf4j
@Service
-public class PlcTestWriteServiceImpl implements PlcTestWriteService {
+public class PlcTestWriteServiceImpl extends PlcTestWriteService {
@Resource
private PlcAddressService plcAddressService;
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/controller/MultiDeviceTaskController.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/controller/MultiDeviceTaskController.java
new file mode 100644
index 0000000..948ee05
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/controller/MultiDeviceTaskController.java
@@ -0,0 +1,62 @@
+package com.mes.task.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.mes.task.dto.MultiDeviceTaskQuery;
+import com.mes.task.dto.MultiDeviceTaskRequest;
+import com.mes.task.entity.MultiDeviceTask;
+import com.mes.task.entity.TaskStepDetail;
+import com.mes.task.service.MultiDeviceTaskService;
+import com.mes.vo.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import java.util.List;
+
+/**
+ * 澶氳澶囦换鍔℃帶鍒跺櫒
+ */
+@RestController
+@RequestMapping("device/task")
+@Api(tags = "澶氳澶囦换鍔$鐞�")
+@Validated
+@RequiredArgsConstructor
+public class MultiDeviceTaskController {
+
+ private final MultiDeviceTaskService multiDeviceTaskService;
+
+ @PostMapping("/start")
+ @ApiOperation("鍚姩澶氳澶囪仈鍚堟祴璇曚换鍔�")
+ public Result<MultiDeviceTask> startTask(@Valid @RequestBody MultiDeviceTaskRequest request) {
+ return Result.success(multiDeviceTaskService.startTask(request));
+ }
+
+ @PostMapping("/list")
+ @ApiOperation("鍒嗛〉鏌ヨ浠诲姟鍒楄〃")
+ public Result<Page<MultiDeviceTask>> listTasks(@RequestBody(required = false) MultiDeviceTaskQuery query) {
+ MultiDeviceTaskQuery finalQuery = query != null ? query : new MultiDeviceTaskQuery();
+ return Result.success(multiDeviceTaskService.queryTasks(finalQuery));
+ }
+
+ @GetMapping("/{taskId}")
+ @ApiOperation("鏌ヨ浠诲姟璇︽儏")
+ public Result<MultiDeviceTask> getTask(@PathVariable String taskId) {
+ return Result.success(multiDeviceTaskService.getTaskByTaskId(taskId));
+ }
+
+ @GetMapping("/{taskId}/steps")
+ @ApiOperation("鏌ヨ浠诲姟姝ラ璇︽儏")
+ public Result<List<TaskStepDetail>> getTaskSteps(@PathVariable String taskId) {
+ return Result.success(multiDeviceTaskService.getTaskSteps(taskId));
+ }
+
+ @PostMapping("/{taskId}/cancel")
+ @ApiOperation("鍙栨秷姝e湪杩愯鐨勪换鍔�")
+ public Result<Boolean> cancelTask(@PathVariable String taskId) {
+ return Result.success(multiDeviceTaskService.cancelTask(taskId));
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskQuery.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskQuery.java
new file mode 100644
index 0000000..e4dad09
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskQuery.java
@@ -0,0 +1,26 @@
+package com.mes.task.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * 澶氳澶囦换鍔℃煡璇㈡潯浠�
+ */
+@Data
+@ApiModel(value = "MultiDeviceTaskQuery", description = "澶氳澶囦换鍔″垎椤垫煡璇㈡潯浠�")
+public class MultiDeviceTaskQuery {
+
+ @ApiModelProperty(value = "璁惧缁処D", example = "1")
+ private Long groupId;
+
+ @ApiModelProperty(value = "浠诲姟鐘舵�侊紙PENDING/RUNNING/COMPLETED/FAILED/CANCELLED锛�")
+ private String status;
+
+ @ApiModelProperty(value = "椤电爜锛屼粠1寮�濮�", example = "1")
+ private Integer page = 1;
+
+ @ApiModelProperty(value = "姣忛〉鏁伴噺", example = "10")
+ private Integer size = 10;
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskRequest.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskRequest.java
new file mode 100644
index 0000000..123da8e
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/MultiDeviceTaskRequest.java
@@ -0,0 +1,32 @@
+package com.mes.task.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 澶氳澶囦换鍔″惎鍔ㄨ姹�
+ */
+@Data
+@ApiModel(value = "MultiDeviceTaskRequest", description = "澶氳澶囪仈鍚堟祴璇曚换鍔″惎鍔ㄨ姹�")
+public class MultiDeviceTaskRequest {
+
+ @ApiModelProperty(value = "璁惧缁処D", example = "1", required = true)
+ @NotNull(message = "璁惧缁処D涓嶈兘涓虹┖")
+ private Long groupId;
+
+ @ApiModelProperty(value = "浠诲姟鏄剧ず鍚嶇О")
+ private String taskName;
+
+ @ApiModelProperty(value = "瑙﹀彂浜�")
+ private String triggeredBy;
+
+ @ApiModelProperty(value = "浠诲姟鍙傛暟", required = true)
+ @Valid
+ @NotNull(message = "浠诲姟鍙傛暟涓嶈兘涓虹┖")
+ private TaskParameters parameters;
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java
new file mode 100644
index 0000000..aaf2c7c
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/dto/TaskParameters.java
@@ -0,0 +1,50 @@
+package com.mes.task.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.validation.constraints.NotEmpty;
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 澶氳澶囦换鍔″弬鏁�
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ApiModel(value = "TaskParameters", description = "澶氳澶囦换鍔℃墽琛屽弬鏁�")
+public class TaskParameters implements Serializable {
+
+ @ApiModelProperty(value = "鐜荤拑ID鍒楄〃锛堜繚鎸佹墽琛岄『搴忥級", required = true)
+ @NotEmpty(message = "鐜荤拑ID鍒楄〃涓嶈兘涓虹┖")
+ private List<String> glassIds;
+
+ @ApiModelProperty(value = "涓婂ぇ杞︿綅缃紪鐮�")
+ private String positionCode;
+
+ @ApiModelProperty(value = "涓婂ぇ杞︿綅缃��")
+ private Integer positionValue;
+
+ @ApiModelProperty(value = "澶х悊鐗囧姞宸ョ被鍨�")
+ private Integer processType;
+
+ @ApiModelProperty(value = "鐜荤拑瀛樺偍浣嶇疆")
+ private Integer storagePosition;
+
+ @ApiModelProperty(value = "鎵ц闂撮殧(姣)")
+ private Integer executionInterval;
+
+ @ApiModelProperty(value = "璁惧绾у埆鍙傛暟瑕嗙洊锛宬ey鍙互鏄澶囩被鍨嬫垨璁惧缂栫爜")
+ private Map<String, Map<String, Object>> deviceOverrides;
+
+ @ApiModelProperty(value = "棰濆閫忎紶鍙傛暟")
+ private Map<String, Object> extra;
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/MultiDeviceTask.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/MultiDeviceTask.java
new file mode 100644
index 0000000..55c4fc0
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/MultiDeviceTask.java
@@ -0,0 +1,85 @@
+package com.mes.task.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 澶氳澶囪仈鍚堟祴璇曚换鍔″疄浣�
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("multi_device_task")
+@ApiModel(value = "MultiDeviceTask", description = "澶氳澶囪仈鍚堟祴璇曚换鍔¤褰�")
+public class MultiDeviceTask implements Serializable {
+
+ @TableId(value = "id", type = IdType.AUTO)
+ @ApiModelProperty("鑷涓婚敭")
+ private Long id;
+
+ @TableField("task_id")
+ @ApiModelProperty("浠诲姟鍞竴缂栧彿")
+ private String taskId;
+
+ @TableField("group_id")
+ @ApiModelProperty("璁惧缁処D锛堝瓧绗︿覆锛�")
+ private String groupId;
+
+ @TableField("project_id")
+ @ApiModelProperty("鎵�灞為」鐩甀D锛堝瓧绗︿覆锛�")
+ private String projectId;
+
+ @TableField("status")
+ @ApiModelProperty("浠诲姟鐘舵��")
+ private String status;
+
+ @TableField("current_step")
+ @ApiModelProperty("褰撳墠鎵ц姝ラ")
+ private Integer currentStep;
+
+ @TableField("total_steps")
+ @ApiModelProperty("鎬绘楠ゆ暟")
+ private Integer totalSteps;
+
+ @TableField("start_time")
+ @ApiModelProperty("寮�濮嬫椂闂�")
+ private Date startTime;
+
+ @TableField("end_time")
+ @ApiModelProperty("缁撴潫鏃堕棿")
+ private Date endTime;
+
+ @TableField("error_message")
+ @ApiModelProperty("閿欒淇℃伅")
+ private String errorMessage;
+
+ @TableField("result_data")
+ @ApiModelProperty("鎵ц缁撴灉鏁版嵁(JSON)")
+ private String resultData;
+
+ @TableField(value = "created_time", fill = FieldFill.INSERT)
+ @ApiModelProperty("鍒涘缓鏃堕棿")
+ private Date createdTime;
+
+ @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+ @ApiModelProperty("鏇存柊鏃堕棿")
+ private Date updatedTime;
+
+ public enum Status {
+ PENDING,
+ RUNNING,
+ COMPLETED,
+ FAILED,
+ CANCELLED
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java
new file mode 100644
index 0000000..73deefa
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/entity/TaskStepDetail.java
@@ -0,0 +1,89 @@
+package com.mes.task.entity;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 澶氳澶囦换鍔℃楠ゅ疄浣�
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("task_step_detail")
+@ApiModel(value = "TaskStepDetail", description = "澶氳澶囦换鍔℃楠よ鎯�")
+public class TaskStepDetail implements Serializable {
+
+ @TableId(value = "id", type = IdType.AUTO)
+ @ApiModelProperty("涓婚敭ID")
+ private Long id;
+
+ @TableField("task_id")
+ @ApiModelProperty("鍏宠仈浠诲姟ID")
+ private String taskId;
+
+ @TableField("step_order")
+ @ApiModelProperty("姝ラ椤哄簭")
+ private Integer stepOrder;
+
+ @TableField("device_id")
+ @ApiModelProperty("璁惧ID锛堝瓧绗︿覆锛�")
+ private String deviceId;
+
+ @TableField("step_name")
+ @ApiModelProperty("姝ラ鍚嶇О")
+ private String stepName;
+
+ @TableField("status")
+ @ApiModelProperty("姝ラ鐘舵��")
+ private String status;
+
+ @TableField("start_time")
+ @ApiModelProperty("寮�濮嬫椂闂�")
+ private Date startTime;
+
+ @TableField("end_time")
+ @ApiModelProperty("缁撴潫鏃堕棿")
+ private Date endTime;
+
+ @TableField("duration_ms")
+ @ApiModelProperty("鎵ц鑰楁椂锛堟绉掞級")
+ private Long durationMs;
+
+ @TableField("input_data")
+ @ApiModelProperty("杈撳叆鏁版嵁(JSON)")
+ private String inputData;
+
+ @TableField("output_data")
+ @ApiModelProperty("杈撳嚭鏁版嵁(JSON)")
+ private String outputData;
+
+ @TableField("error_message")
+ @ApiModelProperty("閿欒淇℃伅")
+ private String errorMessage;
+
+ @TableField("retry_count")
+ @ApiModelProperty("閲嶈瘯娆℃暟")
+ private Integer retryCount;
+
+ @TableField(value = "created_time", fill = FieldFill.INSERT)
+ @ApiModelProperty("璁板綍鍒涘缓鏃堕棿")
+ private Date createdTime;
+
+ public enum Status {
+ PENDING,
+ RUNNING,
+ COMPLETED,
+ FAILED,
+ SKIPPED
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/MultiDeviceTaskMapper.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/MultiDeviceTaskMapper.java
new file mode 100644
index 0000000..140f2c9
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/MultiDeviceTaskMapper.java
@@ -0,0 +1,13 @@
+package com.mes.task.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.mes.task.entity.MultiDeviceTask;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 澶氳澶囦换鍔� Mapper
+ */
+@Mapper
+public interface MultiDeviceTaskMapper extends BaseMapper<MultiDeviceTask> {
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/TaskStepDetailMapper.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/TaskStepDetailMapper.java
new file mode 100644
index 0000000..5131362
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/mapper/TaskStepDetailMapper.java
@@ -0,0 +1,13 @@
+package com.mes.task.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.mes.task.entity.TaskStepDetail;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 浠诲姟姝ラ Mapper
+ */
+@Mapper
+public interface TaskStepDetailMapper extends BaseMapper<TaskStepDetail> {
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionContext.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionContext.java
new file mode 100644
index 0000000..364db3f
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionContext.java
@@ -0,0 +1,54 @@
+package com.mes.task.model;
+
+import com.mes.task.dto.TaskParameters;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 澶氳澶囦换鍔℃墽琛屼笂涓嬫枃
+ */
+@Getter
+public class TaskExecutionContext {
+
+ private final TaskParameters parameters;
+
+ @Setter
+ private List<String> loadedGlassIds;
+
+ @Setter
+ private List<String> processedGlassIds;
+
+ private final Map<String, Object> sharedData = new ConcurrentHashMap<>();
+
+ public TaskExecutionContext(TaskParameters parameters) {
+ if (parameters == null) {
+ this.parameters = new TaskParameters();
+ } else {
+ this.parameters = parameters;
+ }
+ if (CollectionUtils.isEmpty(this.parameters.getGlassIds())) {
+ this.parameters.setGlassIds(new ArrayList<>());
+ }
+ this.sharedData.put("initialGlassIds", new ArrayList<>(this.parameters.getGlassIds()));
+ }
+
+ public Map<String, Object> getSharedData() {
+ return sharedData;
+ }
+
+ public List<String> getSafeLoadedGlassIds() {
+ return loadedGlassIds == null ? Collections.emptyList() : loadedGlassIds;
+ }
+
+ public List<String> getSafeProcessedGlassIds() {
+ return processedGlassIds == null ? Collections.emptyList() : processedGlassIds;
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionResult.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionResult.java
new file mode 100644
index 0000000..11dcc1a
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/model/TaskExecutionResult.java
@@ -0,0 +1,42 @@
+package com.mes.task.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 浠诲姟鎵ц缁撴灉
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class TaskExecutionResult {
+
+ private boolean success;
+
+ private String message;
+
+ private Map<String, Object> data;
+
+ public static TaskExecutionResult success(Map<String, Object> payload) {
+ return TaskExecutionResult.builder()
+ .success(true)
+ .message("鎵ц瀹屾垚")
+ .data(payload != null ? payload : new HashMap<>())
+ .build();
+ }
+
+ public static TaskExecutionResult failure(String message, Map<String, Object> payload) {
+ return TaskExecutionResult.builder()
+ .success(false)
+ .message(message)
+ .data(payload != null ? payload : new HashMap<>())
+ .build();
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/MultiDeviceTaskService.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/MultiDeviceTaskService.java
new file mode 100644
index 0000000..774b654
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/MultiDeviceTaskService.java
@@ -0,0 +1,42 @@
+package com.mes.task.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.mes.task.dto.MultiDeviceTaskQuery;
+import com.mes.task.dto.MultiDeviceTaskRequest;
+import com.mes.task.entity.MultiDeviceTask;
+import com.mes.task.entity.TaskStepDetail;
+
+import java.util.List;
+
+/**
+ * 澶氳澶囦换鍔℃湇鍔�
+ */
+public interface MultiDeviceTaskService extends IService<MultiDeviceTask> {
+
+ /**
+ * 鍚姩澶氳澶囨祴璇曚换鍔�
+ */
+ MultiDeviceTask startTask(MultiDeviceTaskRequest request);
+
+ /**
+ * 鏍规嵁浠诲姟缂栧彿鑾峰彇浠诲姟
+ */
+ MultiDeviceTask getTaskByTaskId(String taskId);
+
+ /**
+ * 鏌ヨ浠诲姟姝ラ
+ */
+ List<TaskStepDetail> getTaskSteps(String taskId);
+
+ /**
+ * 鍙栨秷浠诲姟
+ */
+ boolean cancelTask(String taskId);
+
+ /**
+ * 鍒嗛〉鏌ヨ浠诲姟
+ */
+ Page<MultiDeviceTask> queryTasks(MultiDeviceTaskQuery query);
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
new file mode 100644
index 0000000..6e6bcbd
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -0,0 +1,378 @@
+package com.mes.task.service;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mes.device.entity.DeviceConfig;
+import com.mes.device.entity.DeviceGroupConfig;
+import com.mes.interaction.DeviceInteraction;
+import com.mes.interaction.DeviceInteractionRegistry;
+import com.mes.interaction.DeviceLogicHandler;
+import com.mes.interaction.DeviceLogicHandlerFactory;
+import com.mes.interaction.base.InteractionContext;
+import com.mes.interaction.base.InteractionResult;
+import com.mes.device.service.DeviceInteractionService;
+import com.mes.task.dto.TaskParameters;
+import com.mes.task.entity.MultiDeviceTask;
+import com.mes.task.entity.TaskStepDetail;
+import com.mes.task.mapper.MultiDeviceTaskMapper;
+import com.mes.task.mapper.TaskStepDetailMapper;
+import com.mes.task.model.TaskExecutionContext;
+import com.mes.task.model.TaskExecutionResult;
+import com.mes.device.vo.DevicePlcVO;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.util.*;
+
+/**
+ * 澶氳澶囦换鍔℃墽琛屽紩鎿�
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class TaskExecutionEngine {
+
+ private static final Map<String, String> DEFAULT_OPERATIONS = new HashMap<>();
+
+ static {
+ DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.LOAD_VEHICLE, "feedGlass");
+ DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.LARGE_GLASS, "processGlass");
+ DEFAULT_OPERATIONS.put(DeviceConfig.DeviceType.GLASS_STORAGE, "storeGlass");
+ }
+
+ private final TaskStepDetailMapper taskStepDetailMapper;
+ private final MultiDeviceTaskMapper multiDeviceTaskMapper;
+ private final DeviceInteractionService deviceInteractionService;
+ private final DeviceInteractionRegistry interactionRegistry;
+ private final DeviceLogicHandlerFactory handlerFactory;
+ private final ObjectMapper objectMapper;
+
+ public TaskExecutionResult execute(MultiDeviceTask task,
+ DeviceGroupConfig groupConfig,
+ List<DeviceConfig> devices,
+ TaskParameters parameters) {
+
+ if (CollectionUtils.isEmpty(devices)) {
+ return TaskExecutionResult.failure("璁惧缁勬湭閰嶇疆璁惧锛屾棤娉曟墽琛屼换鍔�", Collections.emptyMap());
+ }
+
+ TaskExecutionContext context = new TaskExecutionContext(parameters);
+ task.setTotalSteps(devices.size());
+ task.setStatus(MultiDeviceTask.Status.RUNNING.name());
+ multiDeviceTaskMapper.updateById(task);
+
+ List<Map<String, Object>> stepSummaries = new ArrayList<>();
+ boolean success = true;
+ String failureMessage = null;
+
+ for (int i = 0; i < devices.size(); i++) {
+ DeviceConfig device = devices.get(i);
+ int order = i + 1;
+ TaskStepDetail step = createStepRecord(task, device, order);
+ StepResult stepResult = executeStep(task, step, device, context);
+ stepSummaries.add(stepResult.toSummary());
+ if (!stepResult.isSuccess()) {
+ success = false;
+ failureMessage = stepResult.getMessage();
+ break;
+ }
+ }
+
+ Map<String, Object> payload = new HashMap<>();
+ payload.put("steps", stepSummaries);
+ payload.put("groupId", groupConfig.getId());
+ payload.put("deviceCount", devices.size());
+
+ if (success) {
+ return TaskExecutionResult.success(payload);
+ }
+ return TaskExecutionResult.failure(failureMessage != null ? failureMessage : "浠诲姟鎵ц澶辫触", payload);
+ }
+
+ private TaskStepDetail createStepRecord(MultiDeviceTask task, DeviceConfig device, int order) {
+ TaskStepDetail step = new TaskStepDetail();
+ step.setTaskId(task.getTaskId());
+ step.setStepOrder(order);
+ step.setDeviceId(String.valueOf(device.getId()));
+ step.setStepName(device.getDeviceName());
+ step.setStatus(TaskStepDetail.Status.PENDING.name());
+ step.setRetryCount(0);
+ taskStepDetailMapper.insert(step);
+ return step;
+ }
+
+ private StepResult executeStep(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context) {
+ Date startTime = new Date();
+ step.setStartTime(startTime);
+ step.setStatus(TaskStepDetail.Status.RUNNING.name());
+
+ DeviceInteraction deviceInteraction = interactionRegistry.getInteraction(device.getDeviceType());
+ if (deviceInteraction != null) {
+ return executeInteractionStep(task, step, device, context, deviceInteraction);
+ }
+
+ Map<String, Object> params = buildOperationParams(device, context);
+ step.setInputData(toJson(params));
+ taskStepDetailMapper.updateById(step);
+
+ String operation = determineOperation(device, params);
+ DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
+ DevicePlcVO.OperationResult result;
+
+ try {
+ if (handler == null) {
+ result = deviceInteractionService.executeOperation(device.getId(), operation, params);
+ } else {
+ result = handler.execute(device, operation, params);
+ }
+
+ boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
+ updateStepAfterOperation(step, result, opSuccess);
+ updateTaskProgress(task, step.getStepOrder(), opSuccess);
+
+ if (opSuccess) {
+ updateContextAfterSuccess(device, context, params);
+ return StepResult.success(device.getDeviceName(), result.getMessage());
+ }
+ return StepResult.failure(device.getDeviceName(), result.getMessage());
+ } catch (Exception e) {
+ log.error("璁惧鎿嶄綔寮傚父, deviceId={}, operation={}", device.getId(), operation, e);
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(e.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), e.getMessage());
+ }
+ }
+
+ private StepResult executeInteractionStep(MultiDeviceTask task,
+ TaskStepDetail step,
+ DeviceConfig device,
+ TaskExecutionContext context,
+ DeviceInteraction deviceInteraction) {
+ try {
+ InteractionContext interactionContext = new InteractionContext(device, context);
+ step.setInputData(toJson(context.getParameters()));
+ InteractionResult interactionResult = deviceInteraction.execute(interactionContext);
+ boolean success = interactionResult != null && interactionResult.isSuccess();
+ updateStepAfterInteraction(step, interactionResult);
+ updateTaskProgress(task, step.getStepOrder(), success);
+
+ if (success) {
+ return StepResult.success(device.getDeviceName(), interactionResult.getMessage());
+ }
+ String message = interactionResult != null ? interactionResult.getMessage() : "浜や簰鎵ц澶辫触";
+ return StepResult.failure(device.getDeviceName(), message);
+ } catch (Exception e) {
+ log.error("浜や簰鎵ц寮傚父, deviceId={}", device.getId(), e);
+ step.setStatus(TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(e.getMessage());
+ step.setEndTime(new Date());
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ taskStepDetailMapper.updateById(step);
+ updateTaskProgress(task, step.getStepOrder(), false);
+ return StepResult.failure(device.getDeviceName(), e.getMessage());
+ }
+ }
+
+ private void updateStepAfterOperation(TaskStepDetail step,
+ DevicePlcVO.OperationResult result,
+ boolean success) {
+ step.setEndTime(new Date());
+ if (step.getStartTime() != null) {
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ }
+ step.setStatus(success ? TaskStepDetail.Status.COMPLETED.name() : TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(success ? null : result.getMessage());
+ step.setOutputData(toJson(result));
+ taskStepDetailMapper.updateById(step);
+ }
+
+ private void updateStepAfterInteraction(TaskStepDetail step,
+ InteractionResult result) {
+ step.setEndTime(new Date());
+ if (step.getStartTime() != null) {
+ step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
+ }
+ boolean success = result != null && result.isSuccess();
+ step.setStatus(success ? TaskStepDetail.Status.COMPLETED.name() : TaskStepDetail.Status.FAILED.name());
+ step.setErrorMessage(success ? null : (result != null ? result.getMessage() : "浜や簰鎵ц澶辫触"));
+ step.setOutputData(result != null ? toJson(result.getData()) : "{}");
+ taskStepDetailMapper.updateById(step);
+ }
+
+ private void updateTaskProgress(MultiDeviceTask task, int currentStep, boolean success) {
+ task.setCurrentStep(currentStep);
+ if (!success) {
+ task.setStatus(MultiDeviceTask.Status.FAILED.name());
+ }
+ LambdaUpdateWrapper<MultiDeviceTask> update = Wrappers.<MultiDeviceTask>lambdaUpdate()
+ .eq(MultiDeviceTask::getId, task.getId())
+ .set(MultiDeviceTask::getCurrentStep, currentStep);
+ if (!success) {
+ update.set(MultiDeviceTask::getStatus, MultiDeviceTask.Status.FAILED.name());
+ }
+ multiDeviceTaskMapper.update(null, update);
+ }
+
+ private String determineOperation(DeviceConfig device, Map<String, Object> params) {
+ if (params != null && params.containsKey("operation")) {
+ Object op = params.get("operation");
+ if (op != null) {
+ return String.valueOf(op);
+ }
+ }
+ return DEFAULT_OPERATIONS.getOrDefault(device.getDeviceType(), "feedGlass");
+ }
+
+ private Map<String, Object> buildOperationParams(DeviceConfig device, TaskExecutionContext context) {
+ Map<String, Object> params = new HashMap<>();
+ TaskParameters taskParams = context.getParameters();
+
+ switch (device.getDeviceType()) {
+ case DeviceConfig.DeviceType.LOAD_VEHICLE:
+ params.put("glassIds", new ArrayList<>(taskParams.getGlassIds()));
+ if (StringUtils.hasText(taskParams.getPositionCode())) {
+ params.put("positionCode", taskParams.getPositionCode());
+ }
+ if (taskParams.getPositionValue() != null) {
+ params.put("positionValue", taskParams.getPositionValue());
+ }
+ params.put("triggerRequest", true);
+ break;
+ case DeviceConfig.DeviceType.LARGE_GLASS:
+ List<String> source = context.getSafeLoadedGlassIds();
+ if (CollectionUtils.isEmpty(source)) {
+ source = taskParams.getGlassIds();
+ }
+ if (!CollectionUtils.isEmpty(source)) {
+ params.put("glassId", source.get(0));
+ params.put("glassIds", new ArrayList<>(source));
+ }
+ params.put("processType", taskParams.getProcessType() != null ? taskParams.getProcessType() : 1);
+ params.put("triggerRequest", true);
+ break;
+ case DeviceConfig.DeviceType.GLASS_STORAGE:
+ List<String> processed = context.getSafeProcessedGlassIds();
+ if (CollectionUtils.isEmpty(processed)) {
+ processed = context.getSafeLoadedGlassIds();
+ }
+ if (!CollectionUtils.isEmpty(processed)) {
+ params.put("glassId", processed.get(0));
+ params.put("glassIds", new ArrayList<>(processed));
+ }
+ if (taskParams.getStoragePosition() != null) {
+ params.put("storagePosition", taskParams.getStoragePosition());
+ }
+ params.put("triggerRequest", true);
+ break;
+ default:
+ if (!CollectionUtils.isEmpty(taskParams.getExtra())) {
+ params.putAll(taskParams.getExtra());
+ }
+ }
+
+ mergeOverrides(device, taskParams, params);
+ return params;
+ }
+
+ private void mergeOverrides(DeviceConfig device, TaskParameters taskParameters, Map<String, Object> params) {
+ if (CollectionUtils.isEmpty(taskParameters.getDeviceOverrides())) {
+ return;
+ }
+ Map<String, Object> override = taskParameters.getDeviceOverrides().get(device.getDeviceType());
+ if (override == null && StringUtils.hasText(device.getDeviceCode())) {
+ override = taskParameters.getDeviceOverrides().get(device.getDeviceCode());
+ }
+ if (override != null) {
+ params.putAll(override);
+ }
+ }
+
+ private void updateContextAfterSuccess(DeviceConfig device,
+ TaskExecutionContext context,
+ Map<String, Object> params) {
+ switch (device.getDeviceType()) {
+ case DeviceConfig.DeviceType.LOAD_VEHICLE:
+ context.setLoadedGlassIds(extractGlassIds(params));
+ break;
+ case DeviceConfig.DeviceType.LARGE_GLASS:
+ context.setProcessedGlassIds(extractGlassIds(params));
+ break;
+ default:
+ break;
+ }
+ }
+
+ private List<String> extractGlassIds(Map<String, Object> params) {
+ if (params == null) {
+ return Collections.emptyList();
+ }
+ Object glassIds = params.get("glassIds");
+ if (glassIds instanceof List) {
+ @SuppressWarnings("unchecked")
+ List<String> cast = (List<String>) glassIds;
+ return new ArrayList<>(cast);
+ }
+ Object glassId = params.get("glassId");
+ if (glassId != null) {
+ return Collections.singletonList(String.valueOf(glassId));
+ }
+ return Collections.emptyList();
+ }
+
+ private String toJson(Object value) {
+ try {
+ return objectMapper.writeValueAsString(value);
+ } catch (JsonProcessingException e) {
+ return "{}";
+ }
+ }
+
+ private static class StepResult {
+ private final boolean success;
+ private final String message;
+ private final String deviceName;
+
+ private StepResult(boolean success, String message, String deviceName) {
+ this.success = success;
+ this.message = message;
+ this.deviceName = deviceName;
+ }
+
+ public static StepResult success(String deviceName, String message) {
+ return new StepResult(true, message, deviceName);
+ }
+
+ public static StepResult failure(String deviceName, String message) {
+ return new StepResult(false, message, deviceName);
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public Map<String, Object> toSummary() {
+ Map<String, Object> summary = new HashMap<>();
+ summary.put("deviceName", deviceName);
+ summary.put("success", success);
+ summary.put("message", message);
+ return summary;
+ }
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java
new file mode 100644
index 0000000..f879887
--- /dev/null
+++ b/mes-processes/mes-plcSend/src/main/java/com/mes/task/service/impl/MultiDeviceTaskServiceImpl.java
@@ -0,0 +1,160 @@
+package com.mes.task.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mes.device.entity.DeviceConfig;
+import com.mes.device.entity.DeviceGroupConfig;
+import com.mes.device.mapper.DeviceGroupRelationMapper;
+import com.mes.device.service.DeviceGroupConfigService;
+import com.mes.task.dto.MultiDeviceTaskQuery;
+import com.mes.task.dto.MultiDeviceTaskRequest;
+import com.mes.task.dto.TaskParameters;
+import com.mes.task.entity.MultiDeviceTask;
+import com.mes.task.entity.TaskStepDetail;
+import com.mes.task.mapper.MultiDeviceTaskMapper;
+import com.mes.task.mapper.TaskStepDetailMapper;
+import com.mes.task.model.TaskExecutionResult;
+import com.mes.task.service.MultiDeviceTaskService;
+import com.mes.task.service.TaskExecutionEngine;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * 澶氳澶囦换鍔℃湇鍔″疄鐜�
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class MultiDeviceTaskServiceImpl extends ServiceImpl<MultiDeviceTaskMapper, MultiDeviceTask>
+ implements MultiDeviceTaskService {
+
+ private final DeviceGroupConfigService deviceGroupConfigService;
+ private final DeviceGroupRelationMapper deviceGroupRelationMapper;
+ private final TaskStepDetailMapper taskStepDetailMapper;
+ private final TaskExecutionEngine taskExecutionEngine;
+ private final ObjectMapper objectMapper;
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public MultiDeviceTask startTask(MultiDeviceTaskRequest request) {
+ DeviceGroupConfig groupConfig = deviceGroupConfigService.getDeviceGroupById(request.getGroupId());
+ if (groupConfig == null) {
+ throw new IllegalArgumentException("璁惧缁勪笉瀛樺湪: " + request.getGroupId());
+ }
+ if (groupConfig.getStatus() != DeviceGroupConfig.Status.ENABLED) {
+ throw new IllegalStateException("璁惧缁勬湭鍚敤锛屾棤娉曟墽琛屼换鍔�");
+ }
+
+ List<DeviceConfig> devices = deviceGroupRelationMapper.getOrderedDeviceConfigs(groupConfig.getId());
+ if (CollectionUtils.isEmpty(devices)) {
+ throw new IllegalStateException("璁惧缁勬湭閰嶇疆浠讳綍璁惧锛屾棤娉曟墽琛屼换鍔�");
+ }
+
+ TaskParameters parameters = request.getParameters();
+ if (parameters == null || CollectionUtils.isEmpty(parameters.getGlassIds())) {
+ throw new IllegalArgumentException("鑷冲皯闇�瑕侀厤缃竴鏉$幓鐠僆D");
+ }
+
+ MultiDeviceTask task = new MultiDeviceTask();
+ task.setTaskId(generateTaskId(groupConfig));
+ task.setGroupId(String.valueOf(groupConfig.getId()));
+ task.setProjectId(String.valueOf(groupConfig.getProjectId()));
+ task.setStatus(MultiDeviceTask.Status.PENDING.name());
+ task.setCurrentStep(0);
+ task.setTotalSteps(devices.size());
+ task.setStartTime(new Date());
+ save(task);
+
+ try {
+ TaskExecutionResult result = taskExecutionEngine.execute(task, groupConfig, devices, parameters);
+ task.setStatus(result.isSuccess() ? MultiDeviceTask.Status.COMPLETED.name() : MultiDeviceTask.Status.FAILED.name());
+ task.setErrorMessage(result.isSuccess() ? null : result.getMessage());
+ task.setEndTime(new Date());
+ task.setResultData(writeJson(result.getData()));
+ updateById(task);
+ return task;
+ } catch (Exception ex) {
+ log.error("澶氳澶囦换鍔℃墽琛屽紓甯�, taskId={}", task.getTaskId(), ex);
+ task.setStatus(MultiDeviceTask.Status.FAILED.name());
+ task.setErrorMessage(ex.getMessage());
+ task.setEndTime(new Date());
+ updateById(task);
+ throw new RuntimeException("澶氳澶囦换鍔℃墽琛屽け璐�: " + ex.getMessage(), ex);
+ }
+ }
+
+ @Override
+ public MultiDeviceTask getTaskByTaskId(String taskId) {
+ LambdaQueryWrapper<MultiDeviceTask> wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(MultiDeviceTask::getTaskId, taskId);
+ return getOne(wrapper);
+ }
+
+ @Override
+ public List<TaskStepDetail> getTaskSteps(String taskId) {
+ LambdaQueryWrapper<TaskStepDetail> wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(TaskStepDetail::getTaskId, taskId);
+ wrapper.orderByAsc(TaskStepDetail::getStepOrder);
+ return taskStepDetailMapper.selectList(wrapper);
+ }
+
+ @Override
+ public boolean cancelTask(String taskId) {
+ MultiDeviceTask task = getTaskByTaskId(taskId);
+ if (task == null) {
+ return false;
+ }
+ if (!MultiDeviceTask.Status.RUNNING.name().equals(task.getStatus())) {
+ return false;
+ }
+ task.setStatus(MultiDeviceTask.Status.CANCELLED.name());
+ task.setEndTime(new Date());
+ return updateById(task);
+ }
+
+ @Override
+ public Page<MultiDeviceTask> queryTasks(MultiDeviceTaskQuery query) {
+ int page = query.getPage() != null && query.getPage() > 0 ? query.getPage() : 1;
+ int size = query.getSize() != null && query.getSize() > 0 ? query.getSize() : 10;
+ Page<MultiDeviceTask> pageParam = new Page<>(page, size);
+
+ LambdaQueryWrapper<MultiDeviceTask> wrapper = new LambdaQueryWrapper<>();
+ if (query.getGroupId() != null) {
+ wrapper.eq(MultiDeviceTask::getGroupId, String.valueOf(query.getGroupId()));
+ }
+ if (StringUtils.hasText(query.getStatus())) {
+ wrapper.eq(MultiDeviceTask::getStatus, query.getStatus().toUpperCase(Locale.ROOT));
+ }
+ wrapper.orderByDesc(MultiDeviceTask::getCreatedTime);
+ return page(pageParam, wrapper);
+ }
+
+ private String generateTaskId(DeviceGroupConfig groupConfig) {
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA);
+ return "TASK_" + groupConfig.getId() + "_" + sdf.format(new Date());
+ }
+
+ private String writeJson(Object data) {
+ if (data == null) {
+ return "{}";
+ }
+ try {
+ return objectMapper.writeValueAsString(data);
+ } catch (JsonProcessingException e) {
+ return "{}";
+ }
+ }
+}
+
diff --git a/mes-processes/mes-plcSend/src/main/resources/application-dev.yml b/mes-processes/mes-plcSend/src/main/resources/application-dev.yml
index 51d11e7..bb9e3bf 100644
--- a/mes-processes/mes-plcSend/src/main/resources/application-dev.yml
+++ b/mes-processes/mes-plcSend/src/main/resources/application-dev.yml
@@ -8,7 +8,7 @@
strict: false #璁剧疆涓ユ牸妯″紡,榛樿false涓嶅惎鍔�. 鍚姩鍚庡湪鏈尮閰嶅埌鎸囧畾鏁版嵁婧愭椂鍊欏洖鎶涘嚭寮傚父,涓嶅惎鍔ㄤ細浣跨敤榛樿鏁版嵁婧�.
datasource:
northGlassMes:
- url: jdbc:mysql://${ip}:${port}/mes_modular?serverTimezone=GMT%2b8
+ url: jdbc:mysql://${ip}:${port}/mes_test?serverTimezone=GMT%2b8
username: root
password: beibo.123/
driver-class-name: com.mysql.cj.jdbc.Driver
diff --git a/mes-web/src/api/device/deviceManagement.js b/mes-web/src/api/device/deviceManagement.js
index 13dd443..48c27ee 100644
--- a/mes-web/src/api/device/deviceManagement.js
+++ b/mes-web/src/api/device/deviceManagement.js
@@ -200,33 +200,38 @@
/**
* 鍒涘缓璁惧缁勯厤缃�
*/
- create(data) {
+ create(config) {
return request({
url: '/api/plcSend/device/group/create',
method: 'post',
- data
+ data: {
+ groupConfig: config
+ }
})
},
/**
* 鏇存柊璁惧缁勯厤缃�
*/
- update(data) {
+ update(id, config) {
return request({
url: '/api/plcSend/device/group/update',
method: 'post',
- data
+ data: {
+ groupId: id,
+ groupConfig: config
+ }
})
},
/**
* 鍒犻櫎璁惧缁勯厤缃�
*/
- delete(data) {
+ delete(id) {
return request({
url: '/api/plcSend/device/group/delete',
method: 'post',
- data
+ data: { groupId: id }
})
},
@@ -263,44 +268,44 @@
/**
* 鍚敤璁惧缁�
*/
- enable(data) {
+ enable(id) {
return request({
url: '/api/plcSend/device/group/enable',
method: 'post',
- data
+ data: { groupId: id }
})
},
/**
* 绂佺敤璁惧缁�
*/
- disable(data) {
+ disable(id) {
return request({
url: '/api/plcSend/device/group/disable',
method: 'post',
- data
+ data: { groupId: id }
})
},
/**
* 鎵归噺鍚敤璁惧缁�
*/
- batchEnable(data) {
+ batchEnable(groupIds) {
return request({
url: '/api/plcSend/device/group/batch-enable',
method: 'post',
- data
+ data: { groupIds }
})
},
/**
* 鎵归噺绂佺敤璁惧缁�
*/
- batchDisable(data) {
+ batchDisable(groupIds) {
return request({
url: '/api/plcSend/device/group/batch-disable',
method: 'post',
- data
+ data: { groupIds }
})
},
@@ -526,6 +531,32 @@
}
}
+// 璁惧浜や簰鎿嶄綔API
+export const deviceInteractionApi = {
+ /**
+ * 鎵ц璁惧閫昏緫鎿嶄綔
+ * @param {Object} data - { deviceId, operation, params }
+ */
+ executeOperation(data) {
+ return request({
+ url: '/api/plcSend/device/interaction/execute',
+ method: 'post',
+ data
+ })
+ },
+
+ /**
+ * 鐜荤拑涓婃枡鍐欏叆
+ */
+ feedGlass(data) {
+ return request({
+ url: '/api/plcSend/device/interaction/glass-feed',
+ method: 'post',
+ data
+ })
+ }
+}
+
// 缁熻API
export const getDeviceStatistics = (data) => {
return request({
@@ -547,6 +578,7 @@
deviceConfigApi,
deviceGroupApi,
devicePlcApi,
+ deviceInteractionApi,
getDeviceStatistics,
getDeviceGroupStatistics
}
\ No newline at end of file
diff --git a/mes-web/src/api/device/multiDeviceTask.js b/mes-web/src/api/device/multiDeviceTask.js
new file mode 100644
index 0000000..6d3f05d
--- /dev/null
+++ b/mes-web/src/api/device/multiDeviceTask.js
@@ -0,0 +1,58 @@
+import request from '@/utils/request'
+
+const BASE_URL = '/api/plcSend/device/task'
+
+export const multiDeviceTaskApi = {
+ /**
+ * 鍚姩澶氳澶囦换鍔�
+ */
+ startTask(data) {
+ return request({
+ url: `${BASE_URL}/start`,
+ method: 'post',
+ data
+ })
+ },
+
+ /**
+ * 鏌ヨ浠诲姟鍒楄〃
+ */
+ getTaskList(params) {
+ return request({
+ url: `${BASE_URL}/list`,
+ method: 'post',
+ data: params
+ })
+ },
+
+ /**
+ * 鏌ヨ浠诲姟璇︽儏
+ */
+ getTaskById(taskId) {
+ return request({
+ url: `${BASE_URL}/${taskId}`,
+ method: 'get'
+ })
+ },
+
+ /**
+ * 鏌ヨ浠诲姟姝ラ
+ */
+ getTaskSteps(taskId) {
+ return request({
+ url: `${BASE_URL}/${taskId}/steps`,
+ method: 'get'
+ })
+ },
+
+ /**
+ * 鍙栨秷浠诲姟
+ */
+ cancelTask(taskId) {
+ return request({
+ url: `${BASE_URL}/${taskId}/cancel`,
+ method: 'post'
+ })
+ }
+}
+
diff --git a/mes-web/src/router/index.js b/mes-web/src/router/index.js
index 867ce2c..382db8f 100644
--- a/mes-web/src/router/index.js
+++ b/mes-web/src/router/index.js
@@ -34,6 +34,16 @@
name: 'plcTest',
component: () => import('../views/plcTest/Test.vue')
},
+ {
+ path: '/plcTest/MultiDeviceWorkbench',
+ name: 'MultiDeviceWorkbench',
+ component: () => import('../views/plcTest/MultiDeviceWorkbench.vue')
+ },
+ {
+ path: '/device/DeviceManagement',
+ name: 'DeviceManagement',
+ component: () => import('../views/device/DeviceManagement.vue')
+ }
]
},
diff --git a/mes-web/src/utils/constants.js b/mes-web/src/utils/constants.js
index 41682c9..ad93a89 100644
--- a/mes-web/src/utils/constants.js
+++ b/mes-web/src/utils/constants.js
@@ -1,6 +1,6 @@
// export const WebSocketHost = "10.153.19.150";
// export const WebSocketHost = "172.17.2.7";
-export const WebSocketHost = "10.153.19.213";//hxl
+export const WebSocketHost = "10.153.19.49";//hxl
// export const WebSocketHost = "10.153.19.2";//zt
//export const WebSocketHost = "10.153.19.20";//wsx
// export const WebSocketHost = "127.0.0.1";
diff --git a/mes-web/src/views/device/DeviceEditDialog.vue b/mes-web/src/views/device/DeviceEditDialog.vue
index 773dd73..881ab73 100644
--- a/mes-web/src/views/device/DeviceEditDialog.vue
+++ b/mes-web/src/views/device/DeviceEditDialog.vue
@@ -106,13 +106,20 @@
</el-form-item>
<el-form-item label="閫氳鍗忚" prop="protocolType">
- <el-select v-model="deviceForm.protocolType" placeholder="閫夋嫨閫氳鍗忚" style="width: 100%;">
+ <el-select
+ v-model="deviceForm.protocolType"
+ placeholder="閫夋嫨閫氳鍗忚"
+ style="width: 100%;"
+ @change="handleProtocolTypeChange"
+ >
+ <el-option label="S7 Communication" value="S7 Communication" />
<el-option label="Modbus TCP" value="Modbus TCP" />
<el-option label="OPC UA" value="OPC UA" />
<el-option label="EtherNet/IP" value="EtherNet/IP" />
<el-option label="Profinet" value="Profinet" />
<el-option label="鍏朵粬" value="鍏朵粬" />
</el-select>
+ <span class="form-tip">S7绯诲垪PLC閫氬父浣跨敤S7 Communication鍗忚</span>
</el-form-item>
<el-form-item label="瓒呮椂鏃堕棿(绉�)" prop="timeout">
@@ -240,6 +247,200 @@
</div>
</el-card>
+ <!-- 璁惧閫昏緫閰嶇疆 -->
+ <el-card class="form-section" shadow="never" style="margin-top: 20px;" v-if="deviceForm.deviceType">
+ <template #header>
+ <span class="section-title">璁惧閫昏緫閰嶇疆</span>
+ <span class="form-tip">鏍规嵁璁惧绫诲瀷閰嶇疆鐗瑰畾鐨勪笟鍔¢�昏緫鍙傛暟</span>
+ </template>
+
+ <!-- 涓婂ぇ杞﹁澶囬�昏緫閰嶇疆 -->
+ <div v-if="deviceForm.deviceType === '涓婂ぇ杞�'">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="杞﹁締瀹归噺">
+ <el-input-number
+ v-model="deviceLogicParams.vehicleCapacity"
+ :min="1"
+ :max="10000"
+ :step="100"
+ style="width: 100%;"
+ />
+ <span class="form-tip">杞﹁締鏈�澶у閲�</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鐜荤拑闂撮殧(ms)">
+ <el-input-number
+ v-model="deviceLogicParams.glassIntervalMs"
+ :min="100"
+ :max="10000"
+ :step="100"
+ style="width: 100%;"
+ />
+ <span class="form-tip">鐜荤拑涓婃枡闂撮殧鏃堕棿锛堟绉掞級</span>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鑷姩涓婃枡">
+ <el-switch v-model="deviceLogicParams.autoFeed" />
+ <span class="form-tip">鏄惁鑷姩瑙﹀彂涓婃枡璇锋眰</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈�澶ч噸璇曟鏁�">
+ <el-input-number
+ v-model="deviceLogicParams.maxRetryCount"
+ :min="0"
+ :max="10"
+ :step="1"
+ style="width: 100%;"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-form-item label="浣嶇疆鏄犲皠">
+ <div class="position-mapping">
+ <div
+ v-for="(value, key, index) in deviceLogicParams.positionMapping"
+ :key="index"
+ class="mapping-item"
+ >
+ <el-input
+ v-model="mappingKeys[index]"
+ placeholder="浣嶇疆浠g爜"
+ size="small"
+ style="width: 150px; margin-right: 10px;"
+ @input="updatePositionMapping(index, $event, value)"
+ />
+ <el-input-number
+ v-model="deviceLogicParams.positionMapping[mappingKeys[index] || key]"
+ :min="0"
+ :max="100"
+ size="small"
+ style="width: 120px; margin-right: 10px;"
+ />
+ <el-button
+ type="danger"
+ size="small"
+ @click="removePositionMapping(key)"
+ >
+ 鍒犻櫎
+ </el-button>
+ </div>
+ <el-button type="primary" size="small" @click="addPositionMapping">
+ 娣诲姞浣嶇疆鏄犲皠
+ </el-button>
+ </div>
+ </el-form-item>
+ </div>
+
+ <!-- 澶х悊鐗囪澶囬�昏緫閰嶇疆 -->
+ <div v-if="deviceForm.deviceType === '澶х悊鐗�'">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鐜荤拑灏哄">
+ <el-input-number
+ v-model="deviceLogicParams.glassSize"
+ :min="100"
+ :max="5000"
+ :step="100"
+ style="width: 100%;"
+ />
+ <span class="form-tip">鐜荤拑灏哄锛坢m锛�</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="澶勭悊鏃堕棿(ms)">
+ <el-input-number
+ v-model="deviceLogicParams.processingTime"
+ :min="1000"
+ :max="60000"
+ :step="1000"
+ style="width: 100%;"
+ />
+ <span class="form-tip">鐜荤拑澶勭悊鏃堕棿锛堟绉掞級</span>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鑷姩澶勭悊">
+ <el-switch v-model="deviceLogicParams.autoProcess" />
+ <span class="form-tip">鏄惁鑷姩瑙﹀彂澶勭悊璇锋眰</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鏈�澶ч噸璇曟鏁�">
+ <el-input-number
+ v-model="deviceLogicParams.maxRetryCount"
+ :min="0"
+ :max="10"
+ :step="1"
+ style="width: 100%;"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </div>
+
+ <!-- 鐜荤拑瀛樺偍璁惧閫昏緫閰嶇疆 -->
+ <div v-if="deviceForm.deviceType === '鐜荤拑瀛樺偍'">
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="瀛樺偍瀹归噺">
+ <el-input-number
+ v-model="deviceLogicParams.storageCapacity"
+ :min="1"
+ :max="1000"
+ :step="1"
+ style="width: 100%;"
+ />
+ <span class="form-tip">鏈�澶у瓨鍌ㄦ暟閲�</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鍙栬揣妯″紡">
+ <el-select v-model="deviceLogicParams.retrievalMode" style="width: 100%;">
+ <el-option label="鍏堣繘鍏堝嚭 (FIFO)" value="FIFO" />
+ <el-option label="鍚庤繘鍏堝嚭 (LIFO)" value="LIFO" />
+ <el-option label="闅忔満 (RANDOM)" value="RANDOM" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鑷姩瀛樺偍">
+ <el-switch v-model="deviceLogicParams.autoStore" />
+ <span class="form-tip">鏄惁鑷姩瑙﹀彂瀛樺偍璇锋眰</span>
+ </el-form-item>
+ </el-col>
+ <el-col :span="12">
+ <el-form-item label="鑷姩鍙栬揣">
+ <el-switch v-model="deviceLogicParams.autoRetrieve" />
+ <span class="form-tip">鏄惁鑷姩瑙﹀彂鍙栬揣璇锋眰</span>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ <el-row :gutter="20">
+ <el-col :span="12">
+ <el-form-item label="鏈�澶ч噸璇曟鏁�">
+ <el-input-number
+ v-model="deviceLogicParams.maxRetryCount"
+ :min="0"
+ :max="10"
+ :step="1"
+ style="width: 100%;"
+ />
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </div>
+ </el-card>
+
<!-- 鎻忚堪淇℃伅 -->
<el-card class="form-section" shadow="never" style="margin-top: 20px;">
<template #header>
@@ -317,6 +518,28 @@
const saving = ref(false)
const testing = ref(false)
const testResult = ref(null)
+
+// 璁惧閫昏緫鍙傛暟锛堟牴鎹澶囩被鍨嬪姩鎬佹樉绀猴級
+const deviceLogicParams = reactive({
+ // 涓婂ぇ杞﹀弬鏁�
+ vehicleCapacity: 6000,
+ glassIntervalMs: 1000,
+ autoFeed: true,
+ maxRetryCount: 5,
+ positionMapping: {},
+ // 澶х悊鐗囧弬鏁�
+ glassSize: 2000,
+ processingTime: 5000,
+ autoProcess: true,
+ // 鐜荤拑瀛樺偍鍙傛暟
+ storageCapacity: 100,
+ retrievalMode: 'FIFO',
+ autoStore: true,
+ autoRetrieve: true
+})
+
+// 浣嶇疆鏄犲皠鐨勯敭鏁扮粍锛堢敤浜巚-for锛�
+const mappingKeys = ref([])
// 璁惧琛ㄥ崟鏁版嵁
const getDefaultForm = () => ({
@@ -420,6 +643,27 @@
emit('update:modelValue', newVal)
})
+// 鐩戝惉PLC绫诲瀷鍙樺寲锛岃嚜鍔ㄨ缃�氳鍗忚
+watch(() => deviceForm.plcType, (newPlcType) => {
+ // 濡傛灉閫夋嫨鐨勬槸S7绯诲垪PLC锛岃嚜鍔ㄨ缃�氳鍗忚涓篠7 Communication
+ if (newPlcType && (newPlcType.startsWith('S') || newPlcType.includes('S7'))) {
+ if (!deviceForm.protocolType || deviceForm.protocolType === '鍏朵粬') {
+ deviceForm.protocolType = 'S7 Communication'
+ }
+ }
+})
+
+// 澶勭悊閫氳鍗忚鍙樺寲
+const handleProtocolTypeChange = (value) => {
+ // 濡傛灉閫夋嫨浜嗛潪S7鍗忚锛屼絾PLC绫诲瀷鏄疭7绯诲垪锛岀粰鍑烘彁绀�
+ if (value && value !== 'S7 Communication' && deviceForm.plcType) {
+ const s7Types = ['S1200', 'S1500', 'S400', 'S300', 'S200', 'S200_SMART']
+ if (s7Types.includes(deviceForm.plcType)) {
+ ElMessage.warning('S7绯诲垪PLC閫氬父浣跨敤S7 Communication鍗忚锛岃纭鍗忚閫夋嫨鏄惁姝g‘')
+ }
+ }
+}
+
// 鏂规硶瀹氫箟
const parseJsonSafe = (str, defaultValue = null) => {
if (!str) return defaultValue
@@ -453,11 +697,182 @@
deviceForm.dbArea = plcConfig.dbArea || 'DB1'
deviceForm.beginIndex = plcConfig.beginIndex ?? 0
deviceForm.autoModeInterval = plcConfig.autoModeInterval ?? 5000
+
+ // 鍔犺浇閰嶇疆鍙傛暟锛堜粠 configJson锛�
+ // 鍏煎涓ょ鏍煎紡锛�
+ // 1. 鏁扮粍鏍煎紡锛歔{ paramKey, paramValue, description }]
+ // 2. 瀵硅薄鏍煎紡锛堟棫鏍煎紡锛夛細{ fieldName: offset } - 鑷姩杞崲涓烘暟缁勬牸寮�
+ loadConfigParams(data?.configJson)
+
+ // 鍔犺浇璁惧閫昏緫鍙傛暟
+ const deviceLogic = extraObj.deviceLogic || {}
+ loadDeviceLogicParams(deviceLogic, data?.deviceType)
+}
+
+// 鍔犺浇閰嶇疆鍙傛暟锛堝吋瀹规棫鐨勫璞℃牸寮忥級
+const loadConfigParams = (configJson) => {
+ if (!configJson) {
+ deviceForm.configParams = []
+ return
+ }
+
+ try {
+ const parsed = typeof configJson === 'string' ? JSON.parse(configJson) : configJson
+
+ // 濡傛灉鏄暟缁勬牸寮忥紝鐩存帴浣跨敤
+ if (Array.isArray(parsed)) {
+ deviceForm.configParams = parsed
+ }
+ // 濡傛灉鏄璞℃牸寮忥紙瀛楁鍚� 鈫� 鍋忕Щ閲忥級锛岃浆鎹负鏁扮粍鏍煎紡
+ else if (typeof parsed === 'object' && parsed !== null) {
+ // 瀛楁鍚嶅埌涓枃鎻忚堪鐨勬槧灏�
+ const fieldDescriptionMap = {
+ 'plcRequest': 'PLC璇锋眰瀛�',
+ 'inPosition': '杩涚墖浣嶇疆',
+ 'plcGlassId1': '鐜荤拑id1',
+ 'plcGlassId2': '鐜荤拑id2',
+ 'plcGlassId3': '鐜荤拑id3',
+ 'plcGlassId4': '鐜荤拑id4',
+ 'plcGlassId5': '鐜荤拑id5',
+ 'plcGlassId6': '鐜荤拑id6',
+ 'plcGlassCount': '鐜荤拑鏁伴噺',
+ 'onlineState': '鑱旀満鐘舵��',
+ 'plcReport': 'PLC姹囨姤',
+ 'state1': '鐘舵��1',
+ 'state2': '鐘舵��2',
+ 'state3': '鐘舵��3',
+ 'state4': '鐘舵��4',
+ 'state5': '鐘舵��5',
+ 'state6': '鐘舵��6',
+ 'mesSend': 'MES鍙戦��',
+ 'mesConfirm': 'MES纭',
+ 'trainInfo': '鍒楄溅淇℃伅',
+ 'start1': '璧峰1',
+ 'start2': '璧峰2',
+ 'start3': '璧峰3',
+ 'start4': '璧峰4',
+ 'start5': '璧峰5',
+ 'start6': '璧峰6',
+ 'target1': '鐩爣1',
+ 'target2': '鐩爣2',
+ 'target3': '鐩爣3',
+ 'target4': '鐩爣4',
+ 'target5': '鐩爣5',
+ 'target6': '鐩爣6',
+ 'mesWidth1': 'MES瀹藉害1',
+ 'mesWidth2': 'MES瀹藉害2',
+ 'mesWidth3': 'MES瀹藉害3',
+ 'mesWidth4': 'MES瀹藉害4',
+ 'mesWidth5': 'MES瀹藉害5',
+ 'mesWidth6': 'MES瀹藉害6',
+ 'mesHeight1': 'MES楂樺害1',
+ 'mesHeight2': 'MES楂樺害2',
+ 'mesHeight3': 'MES楂樺害3',
+ 'mesHeight4': 'MES楂樺害4',
+ 'mesHeight5': 'MES楂樺害5',
+ 'mesHeight6': 'MES楂樺害6',
+ 'mesThickness1': 'MES鍘氬害1',
+ 'mesThickness2': 'MES鍘氬害2',
+ 'mesThickness3': 'MES鍘氬害3',
+ 'mesThickness4': 'MES鍘氬害4',
+ 'mesThickness5': 'MES鍘氬害5',
+ 'mesThickness6': 'MES鍘氬害6',
+ 'edgeDistance1': '杈圭紭璺濈1',
+ 'edgeDistance2': '杈圭紭璺濈2',
+ 'edgeDistance3': '杈圭紭璺濈3',
+ 'edgeDistance4': '杈圭紭璺濈4',
+ 'edgeDistance5': '杈圭紭璺濈5',
+ 'edgeDistance6': '杈圭紭璺濈6',
+ 'targetEdgeDistance1': '鐩爣杈圭紭璺濈1',
+ 'targetEdgeDistance2': '鐩爣杈圭紭璺濈2',
+ 'targetEdgeDistance3': '鐩爣杈圭紭璺濈3',
+ 'targetEdgeDistance4': '鐩爣杈圭紭璺濈4',
+ 'targetEdgeDistance5': '鐩爣杈圭紭璺濈5',
+ 'targetEdgeDistance6': '鐩爣杈圭紭璺濈6',
+ 'alarmInfo': '鎶ヨ淇℃伅'
+ }
+
+ // 杞崲涓烘暟缁勬牸寮�
+ deviceForm.configParams = Object.keys(parsed).map(fieldName => ({
+ paramKey: fieldName,
+ paramValue: String(parsed[fieldName]),
+ description: fieldDescriptionMap[fieldName] || fieldName
+ }))
+ } else {
+ deviceForm.configParams = []
+ }
+ } catch (error) {
+ console.warn('瑙f瀽configJson澶辫触', error)
+ deviceForm.configParams = []
+ }
+}
+
+// 鍔犺浇璁惧閫昏緫鍙傛暟
+const loadDeviceLogicParams = (deviceLogic, deviceType) => {
+ if (deviceType === '涓婂ぇ杞�') {
+ deviceLogicParams.vehicleCapacity = deviceLogic.vehicleCapacity ?? 6000
+ deviceLogicParams.glassIntervalMs = deviceLogic.glassIntervalMs ?? 1000
+ deviceLogicParams.autoFeed = deviceLogic.autoFeed ?? true
+ deviceLogicParams.maxRetryCount = deviceLogic.maxRetryCount ?? 5
+ deviceLogicParams.positionMapping = deviceLogic.positionMapping || {}
+ mappingKeys.value = Object.keys(deviceLogicParams.positionMapping)
+ } else if (deviceType === '澶х悊鐗�') {
+ deviceLogicParams.glassSize = deviceLogic.glassSize ?? 2000
+ deviceLogicParams.processingTime = deviceLogic.processingTime ?? 5000
+ deviceLogicParams.autoProcess = deviceLogic.autoProcess ?? true
+ deviceLogicParams.maxRetryCount = deviceLogic.maxRetryCount ?? 3
+ } else if (deviceType === '鐜荤拑瀛樺偍') {
+ deviceLogicParams.storageCapacity = deviceLogic.storageCapacity ?? 100
+ deviceLogicParams.retrievalMode = deviceLogic.retrievalMode || 'FIFO'
+ deviceLogicParams.autoStore = deviceLogic.autoStore ?? true
+ deviceLogicParams.autoRetrieve = deviceLogic.autoRetrieve ?? true
+ deviceLogicParams.maxRetryCount = deviceLogic.maxRetryCount ?? 3
+ }
+}
+
+// 浣嶇疆鏄犲皠鐩稿叧鏂规硶
+const addPositionMapping = () => {
+ const newKey = `POS${Object.keys(deviceLogicParams.positionMapping).length + 1}`
+ deviceLogicParams.positionMapping[newKey] = 1
+ mappingKeys.value.push(newKey)
+}
+
+const removePositionMapping = (key) => {
+ delete deviceLogicParams.positionMapping[key]
+ mappingKeys.value = mappingKeys.value.filter(k => k !== key)
+}
+
+const updatePositionMapping = (index, newKey, oldValue) => {
+ const oldKey = mappingKeys.value[index]
+ if (oldKey && oldKey !== newKey) {
+ delete deviceLogicParams.positionMapping[oldKey]
+ }
+ mappingKeys.value[index] = newKey
+ if (newKey) {
+ deviceLogicParams.positionMapping[newKey] = oldValue || 1
+ }
}
const resetForm = () => {
Object.assign(deviceForm, getDefaultForm())
deviceFormRef.value?.clearValidate()
+
+ // 閲嶇疆璁惧閫昏緫鍙傛暟
+ deviceLogicParams.vehicleCapacity = 6000
+ deviceLogicParams.glassIntervalMs = 1000
+ deviceLogicParams.autoFeed = true
+ deviceLogicParams.maxRetryCount = 5
+ deviceLogicParams.positionMapping = {}
+ mappingKeys.value = []
+
+ deviceLogicParams.glassSize = 2000
+ deviceLogicParams.processingTime = 5000
+ deviceLogicParams.autoProcess = true
+
+ deviceLogicParams.storageCapacity = 100
+ deviceLogicParams.retrievalMode = 'FIFO'
+ deviceLogicParams.autoStore = true
+ deviceLogicParams.autoRetrieve = true
}
const addConfigParam = () => {
@@ -531,6 +946,44 @@
plcType: deviceForm.plcType
}
+ // 淇濆瓨璁惧閫昏緫鍙傛暟
+ const deviceLogic = {}
+ if (deviceForm.deviceType === '涓婂ぇ杞�') {
+ deviceLogic.vehicleCapacity = deviceLogicParams.vehicleCapacity
+ deviceLogic.glassIntervalMs = deviceLogicParams.glassIntervalMs
+ deviceLogic.autoFeed = deviceLogicParams.autoFeed
+ deviceLogic.maxRetryCount = deviceLogicParams.maxRetryCount
+ deviceLogic.positionMapping = deviceLogicParams.positionMapping
+ } else if (deviceForm.deviceType === '澶х悊鐗�') {
+ deviceLogic.glassSize = deviceLogicParams.glassSize
+ deviceLogic.processingTime = deviceLogicParams.processingTime
+ deviceLogic.autoProcess = deviceLogicParams.autoProcess
+ deviceLogic.maxRetryCount = deviceLogicParams.maxRetryCount
+ } else if (deviceForm.deviceType === '鐜荤拑瀛樺偍') {
+ deviceLogic.storageCapacity = deviceLogicParams.storageCapacity
+ deviceLogic.retrievalMode = deviceLogicParams.retrievalMode
+ deviceLogic.autoStore = deviceLogicParams.autoStore
+ deviceLogic.autoRetrieve = deviceLogicParams.autoRetrieve
+ deviceLogic.maxRetryCount = deviceLogicParams.maxRetryCount
+ }
+
+ if (Object.keys(deviceLogic).length > 0) {
+ extraObj.deviceLogic = deviceLogic
+ }
+
+ // 鏋勫缓 configJson锛氬皢 configParams 鏁扮粍杞崲涓� JSON 瀛楃涓�
+ // configParams 缁撴瀯: [{ paramKey: '', paramValue: '', description: '' }]
+ let configJsonValue = null
+ if (deviceForm.configParams && deviceForm.configParams.length > 0) {
+ // 杩囨护鎺夌┖鍙傛暟
+ const validParams = deviceForm.configParams.filter(
+ param => param.paramKey && param.paramKey.trim() !== ''
+ )
+ if (validParams.length > 0) {
+ configJsonValue = JSON.stringify(validParams)
+ }
+ }
+
const saveData = {
deviceName: deviceForm.deviceName,
deviceCode: deviceForm.deviceCode,
@@ -542,9 +995,7 @@
isPrimary: deviceForm.isPrimary,
enabled: deviceForm.enabled,
description: deviceForm.description,
- configJson: deviceForm.configParams.length > 0
- ? JSON.stringify(deviceForm.configParams)
- : null,
+ configJson: configJsonValue, // 淇濆瓨閰嶇疆鍙傛暟JSON
extraParams: JSON.stringify(extraObj)
}
@@ -562,6 +1013,18 @@
handleClose()
} catch (error) {
console.error('淇濆瓨璁惧閰嶇疆澶辫触:', error)
+ // 濡傛灉鏄〃鍗曢獙璇侀敊璇紝鏄剧ず鏇磋缁嗙殑閿欒淇℃伅
+ if (error && typeof error === 'object' && !error.response) {
+ const errorFields = Object.keys(error)
+ if (errorFields.length > 0) {
+ const firstError = error[errorFields[0]]
+ const errorMessage = Array.isArray(firstError)
+ ? firstError[0]?.message || firstError[0]
+ : firstError?.message || firstError
+ ElMessage.error(`琛ㄥ崟楠岃瘉澶辫触: ${errorMessage}`)
+ return
+ }
+ }
ElMessage.error(isEdit.value ? '鏇存柊璁惧閰嶇疆澶辫触' : '鍒涘缓璁惧閰嶇疆澶辫触')
} finally {
saving.value = false
@@ -647,4 +1110,18 @@
:deep(.el-card__body) {
padding: 20px;
}
+
+.position-mapping {
+ width: 100%;
+}
+
+.mapping-item {
+ display: flex;
+ align-items: center;
+ margin-bottom: 12px;
+ padding: 12px;
+ border: 1px solid #ebeef5;
+ border-radius: 6px;
+ background-color: #fafafa;
+}
</style>
\ No newline at end of file
diff --git a/mes-web/src/views/device/DeviceGroupEditDialog.vue b/mes-web/src/views/device/DeviceGroupEditDialog.vue
index 57dc0c9..6f360bc 100644
--- a/mes-web/src/views/device/DeviceGroupEditDialog.vue
+++ b/mes-web/src/views/device/DeviceGroupEditDialog.vue
@@ -600,19 +600,17 @@
customParams: form.customParams
}
- let response
- if (isEdit.value) {
- response = await deviceGroupApi.update(props.data.id, config)
- } else {
- response = await deviceGroupApi.create(config)
- }
-
- if (response.success) {
+ const response = isEdit.value
+ ? await deviceGroupApi.update(props.data.id, config)
+ : await deviceGroupApi.create(config)
+
+ const ok = response && (response.success || response.code === 200 || response.isSuccess)
+ if (ok) {
ElMessage.success(isEdit.value ? '璁惧缁勬洿鏂版垚鍔�' : '璁惧缁勫垱寤烘垚鍔�')
emit('success', isEdit.value ? 'update' : 'create')
handleClose()
} else {
- ElMessage.error(response.message || (isEdit.value ? '鏇存柊澶辫触' : '鍒涘缓澶辫触'))
+ ElMessage.error(response?.message || (isEdit.value ? '鏇存柊澶辫触' : '鍒涘缓澶辫触'))
}
} catch (error) {
console.error('淇濆瓨閰嶇疆澶辫触:', error)
diff --git a/mes-web/src/views/device/DeviceGroupList.vue b/mes-web/src/views/device/DeviceGroupList.vue
index 9d4163c..ae5c1ce 100644
--- a/mes-web/src/views/device/DeviceGroupList.vue
+++ b/mes-web/src/views/device/DeviceGroupList.vue
@@ -398,10 +398,10 @@
try {
const groupId = row.id || row.groupId
if (row.enabled) {
- await deviceGroupApi.enable({ groupId })
+ await deviceGroupApi.enable(groupId)
ElMessage.success('璁惧缁勫惎鐢ㄦ垚鍔�')
} else {
- await deviceGroupApi.disable({ groupId })
+ await deviceGroupApi.disable(groupId)
ElMessage.success('璁惧缁勭鐢ㄦ垚鍔�')
}
emit('refresh-statistics')
@@ -416,7 +416,7 @@
const batchEnable = async () => {
try {
const groupIds = selectedGroups.value.map(item => item.id || item.groupId)
- await deviceGroupApi.batchEnable({ groupIds })
+ await deviceGroupApi.batchEnable(groupIds)
ElMessage.success(`鎴愬姛鍚敤 ${groupIds.length} 涓澶囩粍`)
clearSelection()
loadGroupList()
@@ -430,7 +430,7 @@
const batchDisable = async () => {
try {
const groupIds = selectedGroups.value.map(item => item.id || item.groupId)
- await deviceGroupApi.batchDisable({ groupIds })
+ await deviceGroupApi.batchDisable(groupIds)
ElMessage.success(`鎴愬姛绂佺敤 ${groupIds.length} 涓澶囩粍`)
clearSelection()
loadGroupList()
diff --git a/mes-web/src/views/plcTest/MultiDeviceWorkbench.vue b/mes-web/src/views/plcTest/MultiDeviceWorkbench.vue
new file mode 100644
index 0000000..5c5c465
--- /dev/null
+++ b/mes-web/src/views/plcTest/MultiDeviceWorkbench.vue
@@ -0,0 +1,67 @@
+<template>
+ <div class="multi-device-workbench">
+ <div class="main-grid">
+ <div class="left-panel">
+ <GroupList @select="handleGroupSelect" />
+ </div>
+ <div class="right-panel">
+ <TaskOrchestration :group="selectedGroup" @task-started="refreshMonitor" />
+ <ExecutionMonitor ref="monitorRef" :group-id="selectedGroupId" class="monitor-panel" />
+ </div>
+ </div>
+ </div>
+</template>
+
+<script setup>
+import { computed, ref } from 'vue'
+import GroupList from './components/DeviceGroup/GroupList.vue'
+import TaskOrchestration from './components/MultiDeviceTest/TaskOrchestration.vue'
+import ExecutionMonitor from './components/MultiDeviceTest/ExecutionMonitor.vue'
+
+const selectedGroup = ref(null)
+const monitorRef = ref(null)
+
+const selectedGroupId = computed(() => {
+ if (!selectedGroup.value) return null
+ return selectedGroup.value.id || selectedGroup.value.groupId
+})
+
+const handleGroupSelect = (group) => {
+ selectedGroup.value = group
+}
+
+const refreshMonitor = () => {
+ monitorRef.value?.fetchTasks?.()
+}
+</script>
+
+<style scoped>
+.multi-device-workbench {
+ padding: 24px;
+ min-height: 100%;
+ background: linear-gradient(180deg, #f6f9ff 0%, #f4f6fb 100%);
+}
+
+.main-grid {
+ display: grid;
+ grid-template-columns: 360px 1fr;
+ gap: 24px;
+}
+
+.right-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
+
+.monitor-panel {
+ flex: 1;
+}
+
+@media (max-width: 1200px) {
+ .main-grid {
+ grid-template-columns: 1fr;
+ }
+}
+</style>
+
diff --git a/mes-web/src/views/plcTest/components/DeviceGroup/GroupList.vue b/mes-web/src/views/plcTest/components/DeviceGroup/GroupList.vue
new file mode 100644
index 0000000..bcccb89
--- /dev/null
+++ b/mes-web/src/views/plcTest/components/DeviceGroup/GroupList.vue
@@ -0,0 +1,144 @@
+<template>
+ <div class="group-list-panel">
+ <div class="panel-header">
+ <div>
+ <h3>璁惧缁勫垪琛�</h3>
+ <p>閫夋嫨涓�涓澶囩粍杩涜缂栨帓娴嬭瘯</p>
+ </div>
+ <div class="actions">
+ <el-input
+ v-model="filters.keyword"
+ placeholder="鎼滅储璁惧缁勫悕绉�/缂栫爜"
+ clearable
+ @clear="fetchGroups"
+ @keyup.enter="fetchGroups"
+ class="search-input"
+ >
+ <template #prefix>
+ <el-icon><Search /></el-icon>
+ </template>
+ </el-input>
+ <el-button :loading="loading" @click="fetchGroups">
+ <el-icon><Refresh /></el-icon>
+ </el-button>
+ </div>
+ </div>
+
+ <el-table
+ v-loading="loading"
+ :data="groups"
+ height="320"
+ stripe
+ class="group-table"
+ @row-click="handleRowClick"
+ >
+ <el-table-column prop="groupName" label="璁惧缁�" min-width="160" />
+ <el-table-column prop="groupCode" label="缂栫爜" min-width="120" />
+ <el-table-column prop="status" label="鐘舵��" width="100">
+ <template #default="{ row }">
+ <el-tag :type="formatStatus(row.status).type">{{ formatStatus(row.status).label }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="deviceCount" label="璁惧鏁伴噺" width="100" />
+ <el-table-column label="鏈�鍚庢洿鏂版椂闂�" min-width="160">
+ <template #default="{ row }">
+ {{ row.updatedTime || row.createTime || '-' }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+</template>
+
+<script setup>
+import { onMounted, reactive, ref } from 'vue'
+import { Refresh, Search } from '@element-plus/icons-vue'
+import { deviceGroupApi } from '@/api/device/deviceManagement'
+
+const emit = defineEmits(['select'])
+
+const loading = ref(false)
+const groups = ref([])
+const filters = reactive({
+ keyword: ''
+})
+
+const fetchGroups = async () => {
+ try {
+ loading.value = true
+ const payload = {
+ page: 1,
+ size: 20,
+ keyword: filters.keyword
+ }
+ const { data } = await deviceGroupApi.getList(payload)
+ const records = data?.records || data?.data || data || []
+ groups.value = Array.isArray(records) ? records : []
+ } finally {
+ loading.value = false
+ }
+}
+
+const handleRowClick = (row) => {
+ emit('select', row)
+}
+
+const formatStatus = (status) => {
+ const value = typeof status === 'number' ? status : String(status || '').toUpperCase()
+ if (value === 1 || value === '鍚敤' || value === 'ENABLED') {
+ return { label: '鍚敤', type: 'success' }
+ }
+ if (value === 0 || value === '鍋滅敤' || value === 'DISABLED') {
+ return { label: '鍋滅敤', type: 'info' }
+ }
+ if (value === 2 || value === '缁存姢涓�' || value === 'MAINTENANCE') {
+ return { label: '缁存姢', type: 'warning' }
+ }
+ return { label: status || '鏈煡', type: 'default' }
+}
+
+onMounted(fetchGroups)
+</script>
+
+<style scoped>
+.group-list-panel {
+ background: #fff;
+ border-radius: 12px;
+ padding: 20px;
+ box-shadow: 0 8px 32px rgba(15, 18, 63, 0.08);
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.panel-header h3 {
+ margin: 0;
+ font-size: 18px;
+}
+
+.panel-header p {
+ margin: 2px 0 0;
+ color: #909399;
+ font-size: 13px;
+}
+
+.actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.search-input {
+ width: 240px;
+}
+
+.group-table {
+ flex: 1;
+}
+</style>
+
diff --git a/mes-web/src/views/plcTest/components/MultiDeviceTest/ExecutionMonitor.vue b/mes-web/src/views/plcTest/components/MultiDeviceTest/ExecutionMonitor.vue
new file mode 100644
index 0000000..65133b4
--- /dev/null
+++ b/mes-web/src/views/plcTest/components/MultiDeviceTest/ExecutionMonitor.vue
@@ -0,0 +1,176 @@
+<template>
+ <div class="execution-monitor">
+ <div class="panel-header">
+ <div>
+ <h3>浠诲姟鎵ц鐩戞帶</h3>
+ <p>瀹炴椂鏌ョ湅鏈�鏂扮殑澶氳澶囦换鍔�</p>
+ </div>
+ <el-button :loading="loading" @click="fetchTasks">
+ <el-icon><Refresh /></el-icon>
+ 鍒锋柊
+ </el-button>
+ </div>
+
+ <el-table
+ v-loading="loading"
+ :data="tasks"
+ height="300"
+ stripe
+ @row-click="handleRowClick"
+ >
+ <el-table-column prop="taskId" label="浠诲姟缂栧彿" min-width="160" />
+ <el-table-column prop="groupId" label="璁惧缁処D" width="120" />
+ <el-table-column prop="status" label="鐘舵��" width="120">
+ <template #default="{ row }">
+ <el-tag :type="statusType(row.status)">{{ row.status }}</el-tag>
+ </template>
+ </el-table-column>
+ <el-table-column prop="currentStep" label="杩涘害" width="120">
+ <template #default="{ row }">
+ {{ row.currentStep || 0 }} / {{ row.totalSteps || 0 }}
+ </template>
+ </el-table-column>
+ <el-table-column label="寮�濮嬫椂闂�" min-width="160" prop="startTime" />
+ <el-table-column label="缁撴潫鏃堕棿" min-width="160" prop="endTime" />
+ </el-table>
+
+ <el-drawer v-model="drawerVisible" size="40%" title="浠诲姟姝ラ璇︽儏">
+ <el-timeline v-loading="stepsLoading" :reverse="false">
+ <el-timeline-item
+ v-for="step in steps"
+ :key="step.id"
+ :timestamp="step.startTime || '-'"
+ :type="step.status === 'COMPLETED' ? 'success' : step.status === 'FAILED' ? 'danger' : 'primary'"
+ >
+ <div class="step-title">{{ step.stepName }}</div>
+ <div class="step-desc">鐘舵�侊細{{ step.status }}</div>
+ <div class="step-desc">鑰楁椂锛歿{ formatDuration(step.durationMs) }}</div>
+ <div class="step-desc" v-if="step.errorMessage">
+ 閿欒锛歿{ step.errorMessage }}
+ </div>
+ </el-timeline-item>
+ </el-timeline>
+ </el-drawer>
+ </div>
+</template>
+
+<script setup>
+import { onMounted, ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { Refresh } from '@element-plus/icons-vue'
+import { multiDeviceTaskApi } from '@/api/device/multiDeviceTask'
+
+const props = defineProps({
+ groupId: {
+ type: [String, Number],
+ default: null
+ }
+})
+
+const loading = ref(false)
+const tasks = ref([])
+const drawerVisible = ref(false)
+const stepsLoading = ref(false)
+const steps = ref([])
+const currentTaskId = ref(null)
+
+const fetchTasks = async () => {
+ try {
+ loading.value = true
+ const { data } = await multiDeviceTaskApi.getTaskList({
+ groupId: props.groupId,
+ page: 1,
+ size: 10
+ })
+ tasks.value = data?.records || data?.data || data || []
+ } catch (error) {
+ ElMessage.error(error?.message || '鍔犺浇浠诲姟鍒楄〃澶辫触')
+ } finally {
+ loading.value = false
+ }
+}
+
+const handleRowClick = async (row) => {
+ currentTaskId.value = row.taskId
+ drawerVisible.value = true
+ stepsLoading.value = true
+ try {
+ const { data } = await multiDeviceTaskApi.getTaskSteps(row.taskId)
+ steps.value = Array.isArray(data) ? data : (data?.data || [])
+ } catch (error) {
+ ElMessage.error(error?.message || '鍔犺浇浠诲姟姝ラ澶辫触')
+ } finally {
+ stepsLoading.value = false
+ }
+}
+
+const statusType = (status) => {
+ switch ((status || '').toUpperCase()) {
+ case 'COMPLETED':
+ return 'success'
+ case 'FAILED':
+ return 'danger'
+ case 'RUNNING':
+ return 'warning'
+ default:
+ return 'info'
+ }
+}
+
+const formatDuration = (ms) => {
+ if (!ms) return '-'
+ if (ms < 1000) return `${ms} ms`
+ return `${(ms / 1000).toFixed(1)} s`
+}
+
+watch(
+ () => props.groupId,
+ () => {
+ fetchTasks()
+ },
+ { immediate: true }
+)
+
+onMounted(fetchTasks)
+
+defineExpose({
+ fetchTasks
+})
+</script>
+
+<style scoped>
+.execution-monitor {
+ background: #fff;
+ border-radius: 12px;
+ padding: 20px;
+ box-shadow: 0 8px 32px rgba(15, 18, 63, 0.08);
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.panel-header h3 {
+ margin: 0;
+}
+
+.panel-header p {
+ margin: 4px 0 0;
+ color: #909399;
+ font-size: 13px;
+}
+
+.step-title {
+ font-weight: 600;
+ margin-bottom: 4px;
+}
+
+.step-desc {
+ font-size: 13px;
+ color: #606266;
+}
+</style>
+
diff --git a/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue b/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
new file mode 100644
index 0000000..606f293
--- /dev/null
+++ b/mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
@@ -0,0 +1,135 @@
+<template>
+ <div class="task-orchestration">
+ <div class="panel-header">
+ <div>
+ <h3>澶氳澶囨祴璇曠紪鎺�</h3>
+ <p v-if="group">褰撳墠璁惧缁勶細{{ group.groupName }}锛坽{ group.deviceCount || '-' }} 鍙拌澶囷級</p>
+ <p v-else class="warning">璇峰厛鍦ㄥ乏渚ч�夋嫨涓�涓澶囩粍</p>
+ </div>
+ <el-button type="primary" :disabled="!group" :loading="loading" @click="handleSubmit">
+ <el-icon><Promotion /></el-icon>
+ 鍚姩娴嬭瘯
+ </el-button>
+ </div>
+
+ <el-form :model="form" label-width="120px">
+ <el-form-item label="鐜荤拑ID鍒楄〃">
+ <el-input
+ v-model="glassIdsInput"
+ type="textarea"
+ :rows="4"
+ placeholder="璇疯緭鍏ョ幓鐠冩潯鐮侊紝鏀寔澶氳鎴栭�楀彿鍒嗛殧"
+ />
+ </el-form-item>
+ <el-form-item label="浣嶇疆缂栫爜">
+ <el-input v-model="form.positionCode" placeholder="渚嬪锛歅OS1" />
+ </el-form-item>
+ <el-form-item label="瀛樺偍浣嶇疆">
+ <el-input-number v-model="form.storagePosition" :min="1" :max="200" />
+ </el-form-item>
+ <el-form-item label="鎵ц闂撮殧 (ms)">
+ <el-input-number v-model="form.executionInterval" :min="100" :max="10000" />
+ </el-form-item>
+ </el-form>
+ </div>
+</template>
+
+<script setup>
+import { computed, reactive, ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { Promotion } from '@element-plus/icons-vue'
+import { multiDeviceTaskApi } from '@/api/device/multiDeviceTask'
+
+const props = defineProps({
+ group: {
+ type: Object,
+ default: null
+ }
+})
+
+const emit = defineEmits(['task-started'])
+
+const form = reactive({
+ positionCode: '',
+ storagePosition: null,
+ executionInterval: 1000
+})
+
+const glassIdsInput = ref('')
+const loading = ref(false)
+
+watch(
+ () => props.group,
+ () => {
+ glassIdsInput.value = ''
+ }
+)
+
+const glassIds = computed(() => {
+ if (!glassIdsInput.value) return []
+ return glassIdsInput.value
+ .split(/[\n,锛宂/)
+ .map((item) => item.trim())
+ .filter((item) => item.length > 0)
+})
+
+const handleSubmit = async () => {
+ if (!props.group) {
+ ElMessage.warning('璇峰厛閫夋嫨璁惧缁�')
+ return
+ }
+ if (glassIds.value.length === 0) {
+ ElMessage.warning('璇疯嚦灏戣緭鍏ヤ竴涓幓鐠僆D')
+ return
+ }
+ try {
+ loading.value = true
+ await multiDeviceTaskApi.startTask({
+ groupId: props.group.id || props.group.groupId,
+ parameters: {
+ glassIds: glassIds.value,
+ positionCode: form.positionCode || null,
+ storagePosition: form.storagePosition,
+ executionInterval: form.executionInterval
+ }
+ })
+ ElMessage.success('浠诲姟宸插惎鍔�')
+ emit('task-started')
+ } catch (error) {
+ ElMessage.error(error?.message || '浠诲姟鍚姩澶辫触')
+ } finally {
+ loading.value = false
+ }
+}
+</script>
+
+<style scoped>
+.task-orchestration {
+ background: #fff;
+ border-radius: 12px;
+ padding: 20px;
+ box-shadow: 0 8px 32px rgba(15, 18, 63, 0.08);
+}
+
+.panel-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 16px;
+}
+
+.panel-header h3 {
+ margin: 0;
+}
+
+.panel-header p {
+ margin: 4px 0 0;
+ color: #909399;
+ font-size: 13px;
+}
+
+.panel-header .warning {
+ color: #f56c6c;
+}
+</style>
+
--
Gitblit v1.8.0