From 46302c0de184287f88313d4dadbb84a3d3d4a370 Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Tue, 11 Aug 2026 11:58:53 +0800 Subject: [PATCH] refactor(ft546): register shuttle runtimes from configuration --- .../device/Ft546DeviceRuntimeRegistry.java | 53 +++++++++ .../shuttle/Ft546SrgvRuntimeFactory.java | 53 +++++++++ .../com/ruoyi/schedule/ScheduleThread.java | 53 ++++----- .../schedule/plcRead/srgv/PlcSrgvRead.java | 14 ++- .../scheduleTask/srgv/BaseSRgvThread.java | 39 +++---- .../Ft546DeviceRuntimeRegistryTest.java | 101 ++++++++++++++++++ 6 files changed, 263 insertions(+), 50 deletions(-) create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistry.java create mode 100644 ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/shuttle/Ft546SrgvRuntimeFactory.java create mode 100644 ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistryTest.java diff --git a/ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistry.java b/ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistry.java new file mode 100644 index 0000000..cbc86ce --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistry.java @@ -0,0 +1,53 @@ +package com.ruoyi.project.ft546.device; + +import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +@Component +public class Ft546DeviceRuntimeRegistry { + private final ConcurrentMap shuttleRuntimes = + new ConcurrentHashMap(); + + public void register(String deviceId, PlcSrgvRead runtime) { + String id = requireText(deviceId, "deviceId"); + PlcSrgvRead value = Objects.requireNonNull(runtime, "runtime"); + PlcSrgvRead existing = shuttleRuntimes.putIfAbsent(id, value); + if (existing != null && existing != value) { + throw new IllegalStateException( + "runtime already registered for device " + id); + } + } + + public PlcSrgvRead requireSrgv(String deviceId) { + String id = requireText(deviceId, "deviceId"); + PlcSrgvRead runtime = shuttleRuntimes.get(id); + if (runtime == null) { + throw new IllegalArgumentException( + "shuttle runtime not found: " + id); + } + return runtime; + } + + public Map allSrgv() { + return Collections.unmodifiableMap( + new LinkedHashMap(shuttleRuntimes)); + } + + public void clear() { + shuttleRuntimes.clear(); + } + + private static String requireText(String value, String field) { + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException(field + " must not be blank"); + } + return value.trim(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/shuttle/Ft546SrgvRuntimeFactory.java b/ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/shuttle/Ft546SrgvRuntimeFactory.java new file mode 100644 index 0000000..8ba036f --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/project/ft546/device/shuttle/Ft546SrgvRuntimeFactory.java @@ -0,0 +1,53 @@ +package com.ruoyi.project.ft546.device.shuttle; + +import com.ruoyi.common.domain.PlcConnectInfo; +import com.ruoyi.device.srgv.domain.DeviceSrgv; +import com.ruoyi.project.ft546.config.Ft546DeviceProfile; +import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead; +import org.springframework.stereotype.Component; + +@Component +public class Ft546SrgvRuntimeFactory { + + public PlcSrgvRead create(DeviceSrgv device, + Ft546DeviceProfile profile) { + if (device == null || device.getDeviceId() == null) { + throw new IllegalArgumentException("device identity is required"); + } + if (profile == null + || !String.valueOf(device.getDeviceId()).equals( + profile.getDeviceId())) { + throw new IllegalArgumentException( + "profile belongs to another device"); + } + if (!"SHUTTLE".equals(profile.getDeviceType()) + || !"FT546_LEGACY_SRGV".equals(profile.getAdapterType())) { + throw new IllegalArgumentException( + "profile is not a legacy shuttle profile"); + } + String ip = hasText(profile.getIp()) ? profile.getIp().trim() + : device.getIp(); + Integer port = profile.getPort() != null ? profile.getPort() + : device.getPort(); + if (!hasText(ip) || port == null || port <= 0 || port > 65535) { + throw new IllegalArgumentException( + "device endpoint is not configured"); + } + int station = profile.getStation() == null ? 1 : profile.getStation(); + return new PlcSrgvRead(device, new PlcConnectInfo(ip, port), station); + } + + public PlcSrgvRead create(DeviceSrgv device) { + Ft546DeviceProfile profile = new Ft546DeviceProfile(); + profile.setDeviceId(String.valueOf(device.getDeviceId())); + profile.setDeviceType("SHUTTLE"); + profile.setAdapterType("FT546_LEGACY_SRGV"); + profile.setIp(device.getIp()); + profile.setPort(device.getPort()); + return create(device, profile); + } + + private static boolean hasText(String value) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/ScheduleThread.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/ScheduleThread.java index b01e2fb..6bd22aa 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/schedule/ScheduleThread.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/ScheduleThread.java @@ -12,6 +12,8 @@ import com.ruoyi.device.ssj.domain.DeviceSsj; import com.ruoyi.device.ssj.service.IDeviceSsjService; import com.ruoyi.device.tsj.domain.DeviceTsj; import com.ruoyi.device.tsj.service.IDeviceTsjService; +import com.ruoyi.project.ft546.device.Ft546DeviceRuntimeRegistry; +import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory; import com.ruoyi.schedule.plcRead.cdz.PlcCdzRead; import com.ruoyi.schedule.plcRead.ddj.PlcDdjRead; import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead; @@ -30,6 +32,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @Component @@ -39,9 +42,9 @@ public class ScheduleThread { public CustomThread plcSsjReadCustomThread; public CustomThread plcTsjReadCustomThread; public CustomThread plcCdzReadCustomThread; - public CustomThread plcSrgvReadCustomThread1; - public CustomThread plcSrgvReadCustomThread2; - public CustomThread plcSrgvReadCustomThread3; + public final Map> + plcSrgvReadCustomThreads = + new ConcurrentHashMap>(); public CustomThread monitoringCustomThread; @@ -58,6 +61,10 @@ public class ScheduleThread { private IDeviceTsjService deviceTsjService; @Autowired private IDeviceCdzService deviceCdzService; + @Autowired + private Ft546DeviceRuntimeRegistry deviceRuntimeRegistry; + @Autowired + private Ft546SrgvRuntimeFactory srgvRuntimeFactory; private boolean isRun = false; private boolean isRun2 = false; @@ -128,25 +135,14 @@ public class ScheduleThread { ConcurrentHashMap srgvList = new ConcurrentHashMap<>(); List srgvs=deviceSrgvService.selectDeviceSrgvList(new DeviceSrgv()); for(DeviceSrgv srgv:srgvs){ - srgvList.put(srgv.getDeviceId().toString(),srgv); - switch (srgv.getDeviceId()){ - case 1: - PlcSrgvRead plcSrgvRead1=new PlcSrgvRead(srgv,new PlcConnectInfo(srgv.getIp(),srgv.getPort())); - Thread plcSrgvReadThread1 = new Thread((plcSrgvRead1)); - plcSrgvReadCustomThread1=new CustomThread(plcSrgvReadThread1,plcSrgvRead1); - break; - case 2: - PlcSrgvRead plcSrgvRead2=new PlcSrgvRead(srgv,new PlcConnectInfo(srgv.getIp(),srgv.getPort())); - Thread plcSrgvReadThread2 = new Thread((plcSrgvRead2)); - plcSrgvReadCustomThread2=new CustomThread(plcSrgvReadThread2,plcSrgvRead2); - break; - case 3: - PlcSrgvRead plcSrgvRead3=new PlcSrgvRead(srgv,new PlcConnectInfo(srgv.getIp(),srgv.getPort())); - Thread plcSrgvReadThread3 = new Thread((plcSrgvRead3)); - plcSrgvReadCustomThread3=new CustomThread(plcSrgvReadThread3,plcSrgvRead3); - break; - } - + String deviceId = srgv.getDeviceId().toString(); + srgvList.put(deviceId,srgv); + PlcSrgvRead plcSrgvRead = srgvRuntimeFactory.create(srgv); + deviceRuntimeRegistry.register(deviceId, plcSrgvRead); + Thread plcSrgvReadThread = new Thread(plcSrgvRead); + plcSrgvReadCustomThreads.put(deviceId, + new CustomThread(plcSrgvReadThread, + plcSrgvRead)); } PlcSrgvRead.deviceSrgvMap=srgvList; @@ -177,9 +173,10 @@ public class ScheduleThread { */ public void startAllThread() { plcSsjReadCustomThread.getThread().start(); - plcSrgvReadCustomThread1.getThread().start(); - plcSrgvReadCustomThread2.getThread().start(); - plcSrgvReadCustomThread3.getThread().start(); + for (CustomThread reader + : plcSrgvReadCustomThreads.values()) { + reader.getThread().start(); + } plcTsjReadCustomThread.getThread().start(); plcCdzReadCustomThread.getThread().start(); @@ -205,6 +202,12 @@ public class ScheduleThread { */ public void stopAllThread() { plcSsjReadCustomThread.runnable.isRun = false; + for (CustomThread reader + : plcSrgvReadCustomThreads.values()) { + reader.runnable.isRun = false; + } + plcSrgvReadCustomThreads.clear(); + deviceRuntimeRegistry.clear(); // plcDdj1ReadCustomThread.runnable.isRun = false; /* plcDdj2ReadCustomThread.runnable.isRun = false; plcDdj3ReadCustomThread.runnable.isRun = false; diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java index 8c2e7ca..7d3d4f2 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/plcRead/srgv/PlcSrgvRead.java @@ -29,10 +29,22 @@ public class PlcSrgvRead implements Runnable { private IByteTransform byteTransform = null; public PlcSrgvRead(DeviceSrgv deviceSrgv, PlcConnectInfo plcConnectInfo) { + this(deviceSrgv, plcConnectInfo, 1); + } + + public PlcSrgvRead(DeviceSrgv deviceSrgv, PlcConnectInfo plcConnectInfo, + int station) { + if (station < 0 || station > 255) { + throw new IllegalArgumentException("station is invalid"); + } this.deviceSrgv = deviceSrgv; this.ip = plcConnectInfo.getIp(); this.port = plcConnectInfo.getPort(); - this.station = (byte) 1; + this.station = (byte) station; + } + + public DeviceSrgv getDeviceSrgv() { + return deviceSrgv; } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java index bdd0709..c12dec1 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java @@ -13,7 +13,6 @@ import com.ruoyi.bas.srgvmap.domain.WcsSrgvMap; import com.ruoyi.bas.srgvmap.service.IWcsSrgvMapService; import com.ruoyi.bas.srgvmap.service.RoadCacheService; import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.domain.CustomThread; import com.ruoyi.common.domain.SendData2; import com.ruoyi.common.utils.spring.SpringUtils; import com.ruoyi.device.srgv.domain.DeviceSrgv; @@ -28,7 +27,7 @@ import com.ruoyi.job.service.impl.JobCallBackServiceImpl; import com.ruoyi.queue.srgv.domain.WcsSrgvQueue; import com.ruoyi.queue.srgv.service.IWcsSrgvQueueFinishService; import com.ruoyi.queue.srgv.service.IWcsSrgvQueueService; -import com.ruoyi.schedule.ScheduleThread; +import com.ruoyi.project.ft546.device.Ft546DeviceRuntimeRegistry; import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead; import com.ruoyi.schedule.plcRead.srgv.roadUtil.PathFinder; import com.ruoyi.schedule.plcRead.ssj.PlcSsjRead; @@ -73,10 +72,10 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { public void init() { sRgvCodeList = new HashMap<>(); - ArrayList sRgvCodes = new ArrayList<>(); - sRgvCodes.add("1"); - sRgvCodes.add("2"); - sRgvCodes.add("3"); + Ft546DeviceRuntimeRegistry runtimeRegistry = + SpringUtils.getBean(Ft546DeviceRuntimeRegistry.class); + ArrayList sRgvCodes = new ArrayList<>( + runtimeRegistry.allSrgv().keySet()); sRgvCodeList.put("srgv", sRgvCodes); wcsSagvQueueMapper = SpringUtils.getBean(IWcsSrgvQueueService.class); @@ -1380,26 +1379,18 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { private boolean sendCarTask(List sendDataList, DeviceSrgv deviceSrgv) { boolean b = true; - ScheduleThread scheduleThread = SpringUtils.getBean(ScheduleThread.class); - CustomThread ct = null; Integer deviceId = deviceSrgv.getDeviceId(); - if (deviceId != null) { - switch (deviceId) { - case 1: - ct = scheduleThread.plcSrgvReadCustomThread1; - break; - case 2: - ct = scheduleThread.plcSrgvReadCustomThread2; - break; - case 3: - ct = scheduleThread.plcSrgvReadCustomThread3; - break; - default: - break; - } + if (deviceId == null) { + return false; } - PlcSrgvRead plcReader = ct != null ? (PlcSrgvRead) ct.getRunnable() : null; - if (plcReader == null) { + Ft546DeviceRuntimeRegistry runtimeRegistry = + SpringUtils.getBean(Ft546DeviceRuntimeRegistry.class); + PlcSrgvRead plcReader; + try { + plcReader = runtimeRegistry.requireSrgv( + String.valueOf(deviceId)); + } catch (IllegalArgumentException missingRuntime) { + log.warn("小车 {} 没有已注册的 PLC 运行时,取消本次下发", deviceId); return false; } for (SendData2 sendData : sendDataList) { diff --git a/ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistryTest.java b/ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistryTest.java new file mode 100644 index 0000000..603e041 --- /dev/null +++ b/ruoyi-admin/src/test/java/com/ruoyi/project/ft546/device/Ft546DeviceRuntimeRegistryTest.java @@ -0,0 +1,101 @@ +package com.ruoyi.project.ft546.device; + +import com.ruoyi.common.domain.PlcConnectInfo; +import com.ruoyi.device.srgv.domain.DeviceSrgv; +import com.ruoyi.project.ft546.config.Ft546DeviceProfile; +import com.ruoyi.project.ft546.device.shuttle.Ft546SrgvRuntimeFactory; +import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class Ft546DeviceRuntimeRegistryTest { + + @Test + void registersAnyConfiguredNumberOfShuttleRuntimes() { + Ft546DeviceRuntimeRegistry registry = new Ft546DeviceRuntimeRegistry(); + PlcSrgvRead first = runtime("1"); + PlcSrgvRead second = runtime("2"); + PlcSrgvRead ninth = runtime("9"); + + registry.register("1", first); + registry.register("2", second); + registry.register("9", ninth); + + assertEquals(3, registry.allSrgv().size()); + assertSame(ninth, registry.requireSrgv("9")); + } + + @Test + void duplicateDeviceCannotSilentlyReplaceAnActiveRuntime() { + Ft546DeviceRuntimeRegistry registry = new Ft546DeviceRuntimeRegistry(); + registry.register("1", runtime("1")); + + assertThrows(IllegalStateException.class, + () -> registry.register("1", runtime("1"))); + } + + @Test + void factoryRejectsAProfileForAnotherDevice() { + DeviceSrgv device = device("1"); + Ft546DeviceProfile profile = new Ft546DeviceProfile(); + profile.setDeviceId("2"); + profile.setDeviceType("SHUTTLE"); + profile.setAdapterType("FT546_LEGACY_SRGV"); + + assertThrows(IllegalArgumentException.class, + () -> new Ft546SrgvRuntimeFactory().create(device, profile)); + } + + @Test + void schedulingAndLegacySendPathsDoNotSelectThreeFixedReaders() + throws Exception { + String schedule = readSource( + "com/ruoyi/schedule/ScheduleThread.java"); + String legacy = readSource("com/ruoyi/schedule/scheduleTask/srgv/" + + "BaseSRgvThread.java"); + + assertFalse(schedule.contains("plcSrgvReadCustomThread1")); + assertFalse(schedule.contains("case 1:")); + assertFalse(legacy.contains("plcSrgvReadCustomThread1")); + assertFalse(legacy.contains("switch (deviceId)")); + } + + private static PlcSrgvRead runtime(String id) { + DeviceSrgv device = device(id); + return new PlcSrgvRead(device, + new PlcConnectInfo(device.getIp(), device.getPort())); + } + + private static DeviceSrgv device(String id) { + DeviceSrgv device = new DeviceSrgv(); + device.setDeviceId(Integer.valueOf(id)); + device.setIp("127.0.0." + id); + device.setPort(502); + return device; + } + + private static String readSource(String relative) throws Exception { + Path current = Paths.get(System.getProperty( + "maven.multiModuleProjectDirectory", ".")) + .toAbsolutePath().normalize(); + while (current != null) { + Path source = current.resolve(Paths.get("ruoyi-admin", "src", + "main", "java", relative)); + if (Files.isRegularFile(source)) { + return new String(Files.readAllBytes(source), + StandardCharsets.UTF_8); + } + current = current.getParent(); + } + throw new java.nio.file.NoSuchFileException(relative); + } +}