Browse Source

fix:增加配置加热区工艺接口

tags/freeze
白凤吉 3 months ago
parent
commit
04416a9ca9
  1. 29
      src/main/java/com/iflytop/gd/app/controller/CraftsController.java
  2. 59
      src/main/java/com/iflytop/gd/app/core/CraftsContext.java
  3. 19
      src/main/java/com/iflytop/gd/app/model/dto/SetCraftsDTO.java
  4. 7
      src/main/java/com/iflytop/gd/app/model/dto/StartCraftsDTO.java
  5. 3
      src/main/java/com/iflytop/gd/app/model/vo/CraftStatusVO.java
  6. 109
      src/main/java/com/iflytop/gd/app/service/CraftsService.java
  7. 52
      src/main/java/com/iflytop/gd/app/service/CraftsStepService.java
  8. 2
      src/main/java/com/iflytop/gd/common/result/ResultCode.java

29
src/main/java/com/iflytop/gd/app/controller/CraftsController.java

@ -1,13 +1,9 @@
package com.iflytop.gd.app.controller;
import com.iflytop.gd.app.model.dto.PauseCraftsDto;
import com.iflytop.gd.app.model.dto.ResumeCraftsDto;
import com.iflytop.gd.app.model.dto.StartCraftsDTO;
import com.iflytop.gd.app.model.dto.StopCraftsDto;
import com.iflytop.gd.app.model.dto.*;
import com.iflytop.gd.app.model.entity.Crafts;
import com.iflytop.gd.app.model.vo.CraftStatusVO;
import com.iflytop.gd.app.service.CraftsService;
import com.iflytop.gd.app.model.bo.CraftsStep;
import com.iflytop.gd.common.result.Result;
import com.iflytop.gd.common.result.ResultCode;
import io.swagger.v3.oas.annotations.Operation;
@ -22,7 +18,6 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@Tag(name = "工艺管理")
@RestController
@ -79,14 +74,19 @@ public class CraftsController {
return Result.failed();
}
@Operation(summary = "配置加热区工艺")
@PostMapping("/set")
public Result<String> setCrafts(@Valid @RequestBody SetCraftsDTO setCraftsDTO) {
craftsService.setCraft(setCraftsDTO.getCraftId(), setCraftsDTO.getHeatId());
return Result.success();
}
@Operation(summary = "开始执行工艺")
@PostMapping("/start")
public Result<String> startCrafts(@Valid @RequestBody StartCraftsDTO startCraftsDTO) {
boolean isSuccess = craftsService.startCrafts(startCraftsDTO.getCraftId(), startCraftsDTO.getHeatId());
if (isSuccess) {
return Result.success();
}
return Result.failed();
craftsService.startCrafts(startCraftsDTO.getHeatId());
return Result.success();
}
@Operation(summary = "暂停执行工艺")
@ -106,11 +106,8 @@ public class CraftsController {
@Operation(summary = "停止执行工艺")
@PostMapping("/stop")
public Result<String> stopCrafts(@Valid @RequestBody StopCraftsDto stopCraftsDto) {
boolean isSuccess = craftsService.stopCrafts(stopCraftsDto.getHeatId());
if (isSuccess) {
return Result.success();
}
return Result.failed();
craftsService.stopCrafts(stopCraftsDto.getHeatId());
return Result.success();
}
@Operation(summary = "获取某个加热区工艺状态")

59
src/main/java/com/iflytop/gd/app/core/CraftsContext.java

@ -24,41 +24,55 @@ import java.util.Map;
@Getter
public class CraftsContext implements Runnable {
private final String heatId;
private final Crafts craft;
private final List<CraftsStep> craftsStepList;
private final StateMachine<CraftStates, CraftEvents> sm;
private final WebSocketService ws;
private final CraftsStepService craftsStepService;
private int currentIndex = 0;
public CraftsContext(String heatId, Crafts craft, StateMachineFactory<CraftStates, CraftEvents> factory, WebSocketService ws, CraftsStepService craftsStepService) {
/**
* 构造方法初始化上下文并启动状态机至 READY
*/
public CraftsContext(String heatId,
Crafts craft,
StateMachineFactory<CraftStates, CraftEvents> factory,
WebSocketService ws,
CraftsStepService craftsStepService) {
this.heatId = heatId;
this.craft = craft;
this.craftsStepList = JSONUtil.parseArray(craft.getSteps()).toList(CraftsStep.class);
this.ws = ws;
this.craftsStepService = craftsStepService;
this.sm = factory.getStateMachine(heatId);
Mono.from(sm.startReactively()).block();
Message<CraftEvents> startMsg = MessageBuilder.withPayload(CraftEvents.START).build();
Mono.from(sm.sendEvent(Mono.just(startMsg))).block();
sm.addStateListener(new StateMachineListenerAdapter<>() {
@Override
public void stateChanged(State<CraftStates, CraftEvents> from, State<CraftStates, CraftEvents> to) {
public void stateEntered(State<CraftStates, CraftEvents> state) {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("heatId", heatId);
dataMap.put("event", to.getId());
dataMap.put("state", state.getId());
dataMap.put("index", currentIndex);
ws.push(WebSocketMessageType.CRAFTS_STATE, dataMap);
}
});
Mono.from(sm.startReactively()).block();
}
/**
* 执行工艺脚本遍历所有步骤并根据状态机控制流程
*/
@Override
public void run() {
try {
Message<CraftEvents> startMsg = MessageBuilder.withPayload(CraftEvents.START).build();
Mono.from(sm.sendEvent(Mono.just(startMsg))).block();
for (; currentIndex < craftsStepList.size(); currentIndex++) {
if (sm.getState().getId() == CraftStates.STOPPED) break;
CraftsStep step = craftsStepList.get(currentIndex);
boolean ok = executeStep(step);
if (!ok) {
Message<CraftEvents> errMsg = MessageBuilder.withPayload(CraftEvents.ERROR_OCCUR).build();
Mono.from(sm.sendEvent(Mono.just(errMsg))).block();
@ -67,7 +81,6 @@ public class CraftsContext implements Runnable {
Message<CraftEvents> compMsg = MessageBuilder.withPayload(CraftEvents.STEP_COMPLETE).build();
Mono.from(sm.sendEvent(Mono.just(compMsg))).block();
// 如果被 PAUSE阻塞等待
synchronized (this) {
while (sm.getState().getId() == CraftStates.PAUSED) {
this.wait();
@ -75,8 +88,7 @@ public class CraftsContext implements Runnable {
}
}
if (sm.getState().getId() == CraftStates.RUNNING) {
Message<CraftEvents> finishMsg = MessageBuilder
.withPayload(CraftEvents.FINISH).build();
Message<CraftEvents> finishMsg = MessageBuilder.withPayload(CraftEvents.FINISH).build();
Mono.from(sm.sendEvent(Mono.just(finishMsg))).block();
}
} catch (InterruptedException e) {
@ -85,23 +97,32 @@ public class CraftsContext implements Runnable {
}
}
/**
* 执行单个步骤并推送开始及结果
*
* @param step 当前工艺步骤
* @return 是否执行成功
* @throws InterruptedException 执行中被中断
*/
private boolean executeStep(CraftsStep step) throws InterruptedException {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("heatId", heatId);
dataMap.put("currentStep", step.getMethod());
ws.push(WebSocketMessageType.CRAFTS_STEP, dataMap);
Thread.sleep(3000);
Map<String, Object> startData = new HashMap<>();
startData.put("heatId", heatId);
startData.put("currentStep", step.getMethod());
ws.push(WebSocketMessageType.CRAFTS_STEP, startData);
return craftsStepService.executeStep(heatId, step);
}
// 暂停
/**
* 暂停执行触发 PAUSE 事件
*/
public void pause() {
Message<CraftEvents> msg = MessageBuilder.withPayload(CraftEvents.PAUSE).build();
Mono.from(sm.sendEvent(Mono.just(msg))).block();
}
// 恢复
/**
* 恢复执行触发 RESUME 事件并唤醒线程
*/
public void resume() {
Message<CraftEvents> msg = MessageBuilder.withPayload(CraftEvents.RESUME).build();
Mono.from(sm.sendEvent(Mono.just(msg))).block();
@ -110,7 +131,9 @@ public class CraftsContext implements Runnable {
}
}
// 用户手动停止
/**
* 停止执行触发 STOP 事件并唤醒线程
*/
public void stop() {
Message<CraftEvents> msg = MessageBuilder.withPayload(CraftEvents.STOP).build();
Mono.from(sm.sendEvent(Mono.just(msg))).block();

19
src/main/java/com/iflytop/gd/app/model/dto/SetCraftsDTO.java

@ -0,0 +1,19 @@
package com.iflytop.gd.app.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
@Schema(description = "配置工艺")
@Data
public class SetCraftsDTO {
@Positive(message = "工艺ID 必须是正数")
@NotNull
@Schema(description = "工艺id")
private Long craftId;
@NotNull
@Schema(description = "加热区id")
private String heatId;
}

7
src/main/java/com/iflytop/gd/app/model/dto/StartCraftsDTO.java

@ -1,19 +1,12 @@
package com.iflytop.gd.app.model.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.Data;
@Schema(description = "开始工艺")
@Data
public class StartCraftsDTO {
@Positive(message = "工艺ID 必须是正数")
@NotNull
@Schema(description = "工艺id")
private Long craftId;
@NotNull
@Schema(description = "加热区id")
private String heatId;

3
src/main/java/com/iflytop/gd/app/model/vo/CraftStatusVO.java

@ -16,6 +16,9 @@ public class CraftStatusVO {
@Schema(description = "加热区 ID")
private String heatId;
@Schema(description = "工艺 ID")
private Long craftsId;
@Schema(description = "当前状态")
private CraftStates state;

109
src/main/java/com/iflytop/gd/app/service/CraftsService.java

@ -8,6 +8,8 @@ import com.iflytop.gd.app.core.CraftsContext;
import com.iflytop.gd.app.mapper.CraftsMapper;
import com.iflytop.gd.app.model.entity.Crafts;
import com.iflytop.gd.app.model.vo.CraftStatusVO;
import com.iflytop.gd.common.exception.AppException;
import com.iflytop.gd.common.result.ResultCode;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.statemachine.config.StateMachineFactory;
@ -15,11 +17,14 @@ import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
/**
* 工艺执行管理服务每个 heatId 对应一个独立线程执行任务
* 工艺执行管理服务
*/
@Service
@RequiredArgsConstructor
@ -38,28 +43,42 @@ public class CraftsService extends ServiceImpl<CraftsMapper, Crafts> {
}
/**
* 开始执行工艺
* 配置工艺
*/
public synchronized boolean startCrafts(Long craftId, String heatId) {
if (futureMap.containsKey(heatId)) {
return false;
public synchronized void setCraft(Long craftId, String heatId) {
// 校验已有上下文状态仅允许在 READYSTOPPED FINISHED 状态下重置
CraftsContext existing = contextMap.get(heatId);
if (existing != null) {
CraftStates state = existing.getSm().getState().getId();
if (state == CraftStates.RUNNING || state == CraftStates.PAUSED) {
throw new AppException(ResultCode.CRAFT_RUNNING);
}
clearCraftContext(heatId);
}
Crafts craft = this.getById(craftId);
if (craft == null) {
return false;
}
CraftsContext ctx = new CraftsContext(heatId, craft, stateMachineFactory, webSocketService, craftsStepService);
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
try {
ctx.run();
} finally {
contextMap.remove(heatId);
futureMap.remove(heatId);
}
}, executor);
CraftsContext ctx = new CraftsContext(
heatId,
craft,
stateMachineFactory,
webSocketService,
craftsStepService
);
contextMap.put(heatId, ctx);
futureMap.put(heatId, cf);
return true;
}
/**
* 启动执行工艺需先调用 setCraft
*/
public synchronized void startCrafts(String heatId) {
CraftsContext ctx = contextMap.get(heatId);
if (ctx == null) {
throw new AppException(ResultCode.CRAFT_CONTEXT_NULL);
}
if (futureMap.containsKey(heatId)) {
throw new AppException(ResultCode.CRAFT_RUNNING);
}
Future<?> future = executor.submit(ctx);
futureMap.put(heatId, future);
}
/**
@ -67,9 +86,10 @@ public class CraftsService extends ServiceImpl<CraftsMapper, Crafts> {
*/
public synchronized void pauseCrafts(String heatId) {
CraftsContext ctx = contextMap.get(heatId);
if (ctx != null) {
ctx.pause();
if (ctx == null) {
throw new AppException(ResultCode.CRAFT_CONTEXT_NULL);
}
ctx.pause();
}
/**
@ -77,37 +97,45 @@ public class CraftsService extends ServiceImpl<CraftsMapper, Crafts> {
*/
public synchronized void resumeCrafts(String heatId) {
CraftsContext ctx = contextMap.get(heatId);
if (ctx != null) {
ctx.resume();
if (ctx == null) {
throw new AppException(ResultCode.CRAFT_CONTEXT_NULL);
}
ctx.resume();
}
/**
* 停止执行工艺
* 停止执行工艺不清除上下文
*/
public synchronized boolean stopCrafts(String heatId) {
public synchronized void stopCrafts(String heatId) {
CraftsContext ctx = contextMap.get(heatId);
Future<?> future = futureMap.get(heatId);
if (ctx != null && future != null) {
ctx.stop();
future.cancel(true);
contextMap.remove(heatId);
futureMap.remove(heatId);
return true;
Future<?> future = futureMap.remove(heatId);
if (ctx == null || future == null) {
throw new AppException(ResultCode.CRAFT_CONTEXT_NULL);
}
return false;
ctx.stop();
future.cancel(true);
}
/**
* 清理指定 heatId 的执行上下文和 Future
*/
public synchronized void clearCraftContext(String heatId) {
contextMap.remove(heatId);
Future<?> future = futureMap.remove(heatId);
if (future != null) future.cancel(true);
}
/**
* 查询指定 heatId 的执行状态
*/
public synchronized CraftStatusVO getStatus(String heatId) {
public CraftStatusVO getStatus(String heatId) {
CraftsContext ctx = contextMap.get(heatId);
if (ctx == null) {
return null;
}
CraftStatusVO vo = new CraftStatusVO();
vo.setHeatId(heatId);
vo.setCraftsId(ctx.getCraft().getId());
vo.setState(ctx.getSm().getState().getId());
vo.setCurrentIndex(ctx.getCurrentIndex());
vo.setSteps(ctx.getCraftsStepList());
@ -117,12 +145,13 @@ public class CraftsService extends ServiceImpl<CraftsMapper, Crafts> {
/**
* 查询所有正在执行的工艺状态
*/
public synchronized List<CraftStatusVO> getAllStatuses() {
public List<CraftStatusVO> getAllStatuses() {
return contextMap.entrySet().stream().map(entry -> {
String heatId = entry.getKey();
String heatIdKey = entry.getKey();
CraftsContext ctx = entry.getValue();
CraftStatusVO vo = new CraftStatusVO();
vo.setHeatId(heatId);
vo.setHeatId(heatIdKey);
vo.setCraftsId(ctx.getCraft().getId());
vo.setState(ctx.getSm().getState().getId());
vo.setCurrentIndex(ctx.getCurrentIndex());
vo.setSteps(ctx.getCraftsStepList());
@ -147,9 +176,7 @@ public class CraftsService extends ServiceImpl<CraftsMapper, Crafts> {
}
public boolean deleteCrafts(String idsStr) {
List<Long> ids = Arrays.stream(idsStr.split(","))
.map(Long::parseLong)
.collect(Collectors.toList());
List<Long> ids = Arrays.stream(idsStr.split(",")).map(Long::parseLong).collect(Collectors.toList());
return this.removeByIds(ids);
}
}

52
src/main/java/com/iflytop/gd/app/service/CraftsStepService.java

@ -6,7 +6,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 工艺步骤
* 工艺步骤执行服务
*/
@Service
@RequiredArgsConstructor
@ -14,45 +14,65 @@ public class CraftsStepService {
/**
* 执行单个工艺步骤
*
* @param heatId 加热区 ID
* @param step 工艺步骤包括 method params
* @return true 表示执行成功false 表示失败
* @throws InterruptedException 如果延时被中断
*/
public boolean executeStep(String heatId, CraftsStep step) throws InterruptedException {
public boolean executeStep(String heatId, CraftsStep step){
String method = step.getMethod();
JSONObject params = step.getParams();
delay(3);
return switch (method) {
case "UP_TRAY" -> upTray(heatId);
case "DOWN_TRAY" -> downTray(heatId);
case "ADD_LIQUID" -> addLiquid(heatId, params);
case "DELAY" -> delay(params);
default -> false;
case "ADD_LIQUID" -> addLiquid(heatId, params);
case "SHAKING" -> shaking(heatId, params);
case "TAKE_PHOTO" -> takePhoto(heatId);
default -> true;
};
}
/**
* 将托盘升起
* 添加溶液
*/
private boolean upTray(String heatId) {
private boolean addLiquid(String heatId, JSONObject params) {
// TODO: params 中获取溶液类型体积等参数并调用设备服务
return true;
}
/**
* 将托盘降下
* 摇匀操作
*/
private boolean downTray(String heatId) {
private boolean shaking(String heatId, JSONObject params) {
// TODO: params 中获取速度时长等参数并调用设备服务
return true;
}
/**
* 液操作
*
*/
private boolean addLiquid(String heatId, JSONObject params) {
private boolean heating(String heatId, JSONObject params) {
// TODO: params 中获取温度时长等参数并调用设备服务
return true;
}
/**
* 拍照操作·
*/
private boolean takePhoto(String heatId) {
// TODO: 调用相机或设备服务执行拍照
return true;
}
/**
* 等待指定秒数
* 延时等待
*/
private boolean delay(JSONObject params) throws InterruptedException {
Thread.sleep(params.getInt("second") * 1000L);
private boolean delay(int seconds){
try {
Thread.sleep(seconds * 1000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return true;
}
}

2
src/main/java/com/iflytop/gd/common/result/ResultCode.java

@ -36,6 +36,8 @@ public enum ResultCode implements IResultCode, Serializable {
INVALID_CREDENTIALS("4002", "用户名或密码错误"),
OPERATION_NOT_ALLOWED("4003", "业务操作不允许"),
DATA_ALREADY_EXISTS("4004", "数据已存在"),
CRAFT_RUNNING("4004", "工艺正在执行"),
CRAFT_CONTEXT_NULL("4004", "请先配置该加热区工艺"),
//============================ 5xxx系统 & 第三方 ============================
SYSTEM_ERROR("5000", "系统内部错误"),
SERVICE_UNAVAILABLE("5001", "服务暂不可用"),

Loading…
Cancel
Save