From 67e5b0f91ac9e5284abe87f7573db424828bd728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=87=A4=E5=90=89?= Date: Fri, 25 Jul 2025 20:18:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=B7=A5=E8=89=BA=E4=B8=BB=E6=A1=86?= =?UTF-8?q?=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/CraftsController.java | 29 ++-- .../app/core/crafts/CraftsDispatcher.java | 148 +++++++++++++++++++++ .../colortitration/app/core/crafts/CraftsJob.java | 32 +++++ .../colortitration/app/core/state/DeviceState.java | 15 ++- .../app/service/DeviceCommandService.java | 2 + .../common/service/CraftsService.java | 60 +++++++++ 6 files changed, 265 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsDispatcher.java create mode 100644 src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsJob.java diff --git a/src/main/java/com/iflytop/colortitration/app/controller/CraftsController.java b/src/main/java/com/iflytop/colortitration/app/controller/CraftsController.java index 09107f6..d521ba5 100644 --- a/src/main/java/com/iflytop/colortitration/app/controller/CraftsController.java +++ b/src/main/java/com/iflytop/colortitration/app/controller/CraftsController.java @@ -65,21 +65,20 @@ public class CraftsController { return Result.success(); } -// -// @Operation(summary = "暂停执行工艺") -// @PostMapping("/pause") -// public Result pauseCrafts(@Valid @RequestBody CraftsPauseDto pauseCraftsDto) { -// craftsService.pauseCrafts(pauseCraftsDto.getHeatId()); -// return Result.success(); -// } -// -// @Operation(summary = "恢复执行工艺") -// @PostMapping("/resume") -// public Result resumeCrafts(@Valid @RequestBody CraftsResumeDTO resumeCraftsDto) { -// craftsService.resumeCrafts(resumeCraftsDto.getHeatId()); -// return Result.success(); -// } -// + @Operation(summary = "暂停执行工艺") + @PostMapping("/pause") + public Result pauseCrafts() { + craftsService.pause(); + return Result.success(); + } + + @Operation(summary = "恢复执行工艺") + @PostMapping("/resume") + public Result resumeCrafts() { + craftsService.resume(); + return Result.success(); + } + // @Operation(summary = "停止执行工艺") // @PostMapping("/stop") // public Result stopCrafts(@Valid @RequestBody CraftsStopDTO stopCraftsDto) { diff --git a/src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsDispatcher.java b/src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsDispatcher.java new file mode 100644 index 0000000..a48ac32 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsDispatcher.java @@ -0,0 +1,148 @@ +package com.iflytop.colortitration.app.core.crafts; + +import com.iflytop.colortitration.app.common.enums.MultipleModuleCode; +import com.iflytop.colortitration.app.core.state.DeviceState; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 动态为每个滴定位模块创建独立队列和工作线程 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class CraftsDispatcher { + private final BlockingQueue jobQueue = new LinkedBlockingQueue<>(); + private ExecutorService executor; + private final AtomicInteger threadCounter = new AtomicInteger(0); + private final ReentrantLock pauseLock = new ReentrantLock(); + private final Condition pauseCondition = pauseLock.newCondition(); + private final DeviceState deviceState; + + @PostConstruct + public void init() { + MultipleModuleCode[] modules = MultipleModuleCode.values(); + int totalThreads = modules.length; + executor = Executors.newFixedThreadPool(totalThreads, r -> { + int id = threadCounter.incrementAndGet(); + return new Thread(r, "CraftsWorker-" + id); + }); + for (int i = 0; i < totalThreads; i++) { + executor.submit(createWorker()); + } + log.info("已初始化{}个工作线程来处理滴定任务。", totalThreads); + } + + /** + * 创建消费线程,每个线程从队列中取任务并执行 + */ + private Runnable createWorker() { + return () -> { + while (true) { + try { + CraftsJob job = jobQueue.take(); + job.executeSteps(this); + log.info("试管编号 {} 的任务执行成功。", job.getTubeNum()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("工作线程被中断,停止执行。"); + return; + } catch (Exception e) { + log.error("处理任务时发生错误: {}", e.getMessage(), e); + } + } + }; + } + + /** + * 将新的待执行工艺添加至队列 + */ + public void addCraftsJob(CraftsJob craftsJob) { + try { + jobQueue.put(craftsJob); + log.info("试管编号 {} 的任务已添加到队列中。", craftsJob.getTubeNum()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("添加试管编号 {} 的任务时发生中断。", craftsJob.getTubeNum()); + } + } + + /** + * 获取所有待执行的任务 + */ + public BlockingQueue getAllJobs() { + return jobQueue; + } + + /** + * 根据试管编号删除任务 + */ + public void removeCraftsJob(int tubeNum) { + boolean removed = false; + for (CraftsJob job : jobQueue) { + if (job.getTubeNum() == tubeNum) { + jobQueue.remove(job); // 删除任务 + removed = true; + log.info("试管编号 {} 的任务已从队列中删除。", tubeNum); + } + } + if (!removed) { + log.warn("未找到试管编号 {} 的任务进行删除。", tubeNum); + } + } + + /** + * 如果暂停,线程将进入等待状态 + * + * @throws InterruptedException 如果线程被中断 + */ + public void waitIfPaused() throws InterruptedException { + pauseLock.lock(); + try { + while (deviceState.isPaused()) { + // 如果已暂停,当前线程将被挂起,直到调用 notifyAll() 唤醒 + pauseCondition.await(); + } + } finally { + pauseLock.unlock(); + } + } + + /** + * 暂停所有任务 + */ + public void pause() { + pauseLock.lock(); + try { + deviceState.setPaused(true); + log.info("所有任务已暂停。"); + } finally { + pauseLock.unlock(); + } + } + + /** + * 恢复所有任务 + */ + public void resume() { + pauseLock.lock(); + try { + deviceState.setPaused(false); + pauseCondition.signalAll(); // 唤醒所有等待的线程 + log.info("所有任务已恢复。"); + } finally { + pauseLock.unlock(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsJob.java b/src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsJob.java new file mode 100644 index 0000000..ded29b9 --- /dev/null +++ b/src/main/java/com/iflytop/colortitration/app/core/crafts/CraftsJob.java @@ -0,0 +1,32 @@ +package com.iflytop.colortitration.app.core.crafts; + +import com.iflytop.colortitration.app.common.enums.MultipleModuleCode; +import com.iflytop.colortitration.common.model.entity.Crafts; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Slf4j +@Component +@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) +@Data +public class CraftsJob { + private int tubeNum; + private List selectedModules; + private Crafts crafts; + + public void executeSteps(CraftsDispatcher dispatcher) { + log.info("执行工艺开始,试管{}", tubeNum); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + log.info("执行工艺结束,试管{}", tubeNum); + } + +} diff --git a/src/main/java/com/iflytop/colortitration/app/core/state/DeviceState.java b/src/main/java/com/iflytop/colortitration/app/core/state/DeviceState.java index df132ce..d465a27 100644 --- a/src/main/java/com/iflytop/colortitration/app/core/state/DeviceState.java +++ b/src/main/java/com/iflytop/colortitration/app/core/state/DeviceState.java @@ -17,6 +17,9 @@ import java.util.Map; @Component @JsonIgnoreProperties(value = {"advisors", "frozen", "preFiltered", "proxyTargetClass", "targetSource", "exposeProxy", "advisorCount", "proxiedInterfaces", "targetClass"}) public class DeviceState { + @Schema(description = "当前设备是否暂停") + private volatile boolean paused = false; + @Schema(description = "滴定模块") private final Map titrationModuleStateMap = new HashMap<>(); @@ -27,22 +30,22 @@ public class DeviceState { private final Map trayTubeStateMap = new HashMap<>(); @Schema(description = "托盘1是否存在,true 存在 false不存在") - private boolean trayExist1 = false; + private volatile boolean trayExist1 = false; @Schema(description = "托盘2是否存在,true 存在 false不存在") - private boolean trayExist2 = false; + private volatile boolean trayExist2 = false; @Schema(description = "虚拟模式,true为虚拟") - private boolean virtual = false; + private volatile boolean virtual = false; @Schema(description = "初始化状态,true初始化完毕") - private boolean initComplete = false; + private volatile boolean initComplete = false; @Schema(description = "自检状态,true自检完毕") - private boolean selfTest = false; + private volatile boolean selfTest = false; @Schema(description = "是否是急停状态,true为急停") - private boolean emergencyStop = false; + private volatile boolean emergencyStop = false; @Schema(description = "当前登录用户") private User currentUser; diff --git a/src/main/java/com/iflytop/colortitration/app/service/DeviceCommandService.java b/src/main/java/com/iflytop/colortitration/app/service/DeviceCommandService.java index 52ce38b..1cf94c8 100644 --- a/src/main/java/com/iflytop/colortitration/app/service/DeviceCommandService.java +++ b/src/main/java/com/iflytop/colortitration/app/service/DeviceCommandService.java @@ -6,6 +6,7 @@ import com.iflytop.colortitration.app.common.constant.CommandStatus; import com.iflytop.colortitration.app.core.command.CommandFuture; import com.iflytop.colortitration.app.core.command.CyclicNumberGenerator; import com.iflytop.colortitration.app.core.command.DeviceCommand; +import com.iflytop.colortitration.app.core.state.DeviceState; import com.iflytop.colortitration.app.websocket.server.DebugGenerator; import com.iflytop.colortitration.app.websocket.server.WebSocketSender; import com.iflytop.colortitration.hardware.HardwareService; @@ -27,6 +28,7 @@ import java.util.concurrent.LinkedBlockingQueue; public class DeviceCommandService { private final HardwareService hardwareService; private final WebSocketSender webSocketService; + private final DeviceState deviceState; /** * 需要等待加液区空闲的龙门架机械臂指令 */ diff --git a/src/main/java/com/iflytop/colortitration/common/service/CraftsService.java b/src/main/java/com/iflytop/colortitration/common/service/CraftsService.java index e59b3af..80462ac 100644 --- a/src/main/java/com/iflytop/colortitration/common/service/CraftsService.java +++ b/src/main/java/com/iflytop/colortitration/common/service/CraftsService.java @@ -3,15 +3,21 @@ package com.iflytop.colortitration.common.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.iflytop.colortitration.app.common.enums.TitrationStatus; +import com.iflytop.colortitration.app.core.crafts.CraftsDispatcher; +import com.iflytop.colortitration.app.core.crafts.CraftsJob; import com.iflytop.colortitration.app.core.state.DeviceState; import com.iflytop.colortitration.app.core.state.TubeState; import com.iflytop.colortitration.app.model.dto.SetCraftsDTO; import com.iflytop.colortitration.common.mapper.CraftsMapper; import com.iflytop.colortitration.common.model.entity.Crafts; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; + /** * 工艺接口服务 */ @@ -19,7 +25,12 @@ import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class CraftsService extends ServiceImpl { + private final CraftsDispatcher craftsDispatcher; private final DeviceState deviceState; + private final ReentrantLock lock = new ReentrantLock(true); + private final Condition condition = lock.newCondition(); + @Getter + private volatile boolean paused = false; public Crafts findByName(String name) { return this.getOne(new LambdaQueryWrapper<>(new Crafts()).eq(Crafts::getName, name)); @@ -36,9 +47,58 @@ public class CraftsService extends ServiceImpl { tubeState.setCraftsId(crafts.getId()); tubeState.setCraftsName(crafts.getName()); tubeState.setTitrationModuleCodes(startCraftsDTO.getModuleCodes()); + + CraftsJob job = new CraftsJob(); + job.setTubeNum(tubeNum); + job.setCrafts(crafts); + job.setSelectedModules(startCraftsDTO.getModuleCodes()); + craftsDispatcher.addCraftsJob(job); } } + } + /** + * 暂停所有自动滴定任务 + */ + public void pause() { + lock.lock(); + try { + paused = true; + log.info("Titration paused"); + } finally { + lock.unlock(); + } + } + + /** + * 恢复所有自动滴定任务 + */ + public void resume() { + lock.lock(); + try { + paused = false; + condition.signalAll(); + log.info("Titration resumed"); + } finally { + lock.unlock(); + } + } + + /** + * 在各处理步骤前调用,以挂起当前线程直到恢复 + * + * @throws InterruptedException 如果线程被中断 + */ + public void pauseIfNeeded() throws InterruptedException { + lock.lock(); + try { + while (paused) { + log.debug("Titration is paused, waiting..."); + condition.await(); + } + } finally { + lock.unlock(); + } } }