diff --git a/src/main/java/com/iflytop/gd/app/common/enums/CraftEvents.java b/src/main/java/com/iflytop/gd/app/common/enums/CraftEvents.java new file mode 100644 index 0000000..81d4683 --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/common/enums/CraftEvents.java @@ -0,0 +1,5 @@ +package com.iflytop.gd.app.common.enums; + +public enum CraftEvents { + START, STEP_COMPLETE, PAUSE, RESUME, STOP, ERROR_OCCUR +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/gd/app/common/enums/CraftStates.java b/src/main/java/com/iflytop/gd/app/common/enums/CraftStates.java new file mode 100644 index 0000000..e9a142c --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/common/enums/CraftStates.java @@ -0,0 +1,5 @@ +package com.iflytop.gd.app.common.enums; + +public enum CraftStates { + READY, RUNNING, PAUSED, STOPPED, ERROR, FINISHED +} \ No newline at end of file diff --git a/src/main/java/com/iflytop/gd/app/config/CraftsStateMachineConfig.java b/src/main/java/com/iflytop/gd/app/config/CraftsStateMachineConfig.java new file mode 100644 index 0000000..1508c00 --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/config/CraftsStateMachineConfig.java @@ -0,0 +1,45 @@ +package com.iflytop.gd.app.config; + +import com.iflytop.gd.app.common.enums.CraftEvents; +import com.iflytop.gd.app.common.enums.CraftStates; +import org.springframework.context.annotation.Configuration; +import org.springframework.statemachine.config.EnableStateMachineFactory; +import org.springframework.statemachine.config.StateMachineConfigurerAdapter; +import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; + +import java.util.EnumSet; + +@Configuration +@EnableStateMachineFactory +public class CraftsStateMachineConfig + extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(CraftStates.READY) + .states(EnumSet.allOf(CraftStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer trans) throws Exception { + trans + .withExternal().source(CraftStates.READY).target(CraftStates.RUNNING).event(CraftEvents.START) + .and() + .withInternal().source(CraftStates.RUNNING).event(CraftEvents.STEP_COMPLETE) + .and() + .withExternal().source(CraftStates.RUNNING).target(CraftStates.PAUSED).event(CraftEvents.PAUSE) + .and() + .withExternal().source(CraftStates.PAUSED).target(CraftStates.RUNNING).event(CraftEvents.RESUME) + .and() + .withExternal().source(CraftStates.RUNNING).target(CraftStates.STOPPED).event(CraftEvents.STOP) + .and() + .withExternal().source(CraftStates.PAUSED).target(CraftStates.STOPPED).event(CraftEvents.STOP) + .and() + .withExternal().source(CraftStates.RUNNING).target(CraftStates.ERROR).event(CraftEvents.ERROR_OCCUR) + .and() + .withExternal().source(CraftStates.RUNNING).target(CraftStates.FINISHED).event(CraftEvents.STOP); + } +} diff --git a/src/main/java/com/iflytop/gd/app/core/CraftsContext.java b/src/main/java/com/iflytop/gd/app/core/CraftsContext.java new file mode 100644 index 0000000..97194fc --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/core/CraftsContext.java @@ -0,0 +1,110 @@ +package com.iflytop.gd.app.core; + +import cn.hutool.json.JSONUtil; +import com.iflytop.gd.app.common.constant.WebSocketMessageType; +import com.iflytop.gd.app.common.enums.CraftEvents; +import com.iflytop.gd.app.common.enums.CraftStates; +import com.iflytop.gd.app.model.bo.CraftsStep; +import com.iflytop.gd.app.service.CraftsStepService; +import com.iflytop.gd.system.model.entity.Crafts; +import com.iflytop.gd.system.service.WebSocketService; +import lombok.Getter; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.config.StateMachineFactory; +import org.springframework.statemachine.listener.StateMachineListenerAdapter; +import org.springframework.statemachine.state.State; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Getter +public class CraftsContext implements Runnable { + private final String heatId; + private final List craftsStepList; + private final StateMachine sm; + private final WebSocketService ws; + private final CraftsStepService craftsStepService; + private int currentIndex = 0; + + public CraftsContext(String heatId, Crafts craft, StateMachineFactory factory, CraftsStepService craftsStepService, WebSocketService ws) { + this.heatId = heatId; + this.craftsStepList = JSONUtil.parseArray(craft.getSteps()).toList(CraftsStep.class); + this.craftsStepService = craftsStepService; + this.ws = ws; + + this.sm = factory.getStateMachine(heatId); + Mono.from(sm.startReactively()).block(); // READY -> RUNNING + Message startMsg = MessageBuilder.withPayload(CraftEvents.START).build(); + Mono.from(sm.sendEvent(Mono.just(startMsg))).block(); + sm.addStateListener(new StateMachineListenerAdapter<>() { + @Override + public void stateChanged(State from, State to) { + Map dataMap = new HashMap<>(); + dataMap.put("heatId", heatId); + dataMap.put("event", to.getId()); + dataMap.put("index", currentIndex); + ws.push(WebSocketMessageType.CRAFTS, dataMap); + } + }); + } + + @Override + public void run() { + try { + for (; currentIndex < craftsStepList.size(); currentIndex++) { + if (sm.getState().getId() == CraftStates.STOPPED) break; // 如果收到 STOP 事件,直接退出 + CraftsStep step = craftsStepList.get(currentIndex); + boolean ok = executeStep(step); + if (!ok) { + Message errorMsg = MessageBuilder.withPayload(CraftEvents.ERROR_OCCUR).build(); + Mono.from(sm.sendEvent(Mono.just(errorMsg))).block(); + break; + } + Message completeMsg = MessageBuilder.withPayload(CraftEvents.STEP_COMPLETE).build(); + Mono.from(sm.sendEvent(Mono.just(completeMsg))).block(); + synchronized (this) { + while (sm.getState().getId() == CraftStates.PAUSED) { + this.wait(); + } + } + } + if (sm.getState().getId() == CraftStates.RUNNING) { + Message stopMsg = MessageBuilder.withPayload(CraftEvents.STOP).build(); + Mono.from(sm.sendEvent(Mono.just(stopMsg))).block(); + } + } catch (InterruptedException e) { + Message stopMsg = MessageBuilder.withPayload(CraftEvents.STOP).build(); + Mono.from(sm.sendEvent(Mono.just(stopMsg))).block(); + } + } + + private boolean executeStep(CraftsStep step) { + // TODO: 调用 craftsStepService 或 device 服务执行具体命令 + return true; + } + + public void pause() { + Message pauseMsg = MessageBuilder.withPayload(CraftEvents.PAUSE).build(); + Mono.from(sm.sendEvent(Mono.just(pauseMsg))).block(); + } + + public void resume() { + Message resumeMsg = MessageBuilder.withPayload(CraftEvents.RESUME).build(); + Mono.from(sm.sendEvent(Mono.just(resumeMsg))).block(); + synchronized (this) { + this.notify(); + } + } + + public void stop() { + Message stopMsg = MessageBuilder.withPayload(CraftEvents.STOP).build(); + Mono.from(sm.sendEvent(Mono.just(stopMsg))).block(); + synchronized (this) { + this.notify(); + } + } +} diff --git a/src/main/java/com/iflytop/gd/app/model/bo/CraftsStep.java b/src/main/java/com/iflytop/gd/app/model/bo/CraftsStep.java new file mode 100644 index 0000000..159222b --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/model/bo/CraftsStep.java @@ -0,0 +1,8 @@ +package com.iflytop.gd.app.model.bo; + +import cn.hutool.json.JSONObject; + +public class CraftsStep { + String name; + JSONObject params; +} diff --git a/src/main/java/com/iflytop/gd/app/model/bo/TubeSol.java b/src/main/java/com/iflytop/gd/app/model/bo/TubeSol.java new file mode 100644 index 0000000..8c865c2 --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/model/bo/TubeSol.java @@ -0,0 +1,23 @@ +package com.iflytop.gd.app.model.bo; + +import lombok.Data; + +/** + * 试管添加溶液 + */ +@Data +public class TubeSol { + /** + * 需要添加溶液的试管编号 + */ + private Integer tubeNum; + /** + * 溶液id + */ + private Long solId; + /** + * 加液量 + */ + private Integer volume; + +} diff --git a/src/main/java/com/iflytop/gd/app/service/CraftsStepService.java b/src/main/java/com/iflytop/gd/app/service/CraftsStepService.java new file mode 100644 index 0000000..5f3db05 --- /dev/null +++ b/src/main/java/com/iflytop/gd/app/service/CraftsStepService.java @@ -0,0 +1,124 @@ +package com.iflytop.gd.app.service; + +import com.iflytop.gd.app.model.bo.TubeSol; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 设备步骤操作 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class CraftsStepService { + + /** + * 抬起托盘 + * + * @param heatId 加热区id + */ + public boolean upTray(String heatId) { + return true; + } + + /** + * 降下托盘 + * + * @param heatId 加热区id + */ + public boolean downTray(String heatId) { + return true; + } + + /** + * 添加溶液 + * + * @param tubeSolList 需要添加溶液的试管与溶液 + */ + public boolean addLiquid(List tubeSolList) { + return true; + } + + /** + * 将指定加热区的托盘移至加液区 + * + * @param heatId 加热区id + */ + public boolean moveToSol(String heatId) { + return true; + } + + /** + * 移至加热 + * + * @param heatId 加热区id + */ + public boolean moveToHeat(String heatId) { + return true; + } + + /** + * 摇匀 + * + * @param second 摇匀时间 + */ + public boolean shaking(int second) { + return true; + } + + /** + * 开始加热 + * + * @param heatId 加热区id + * @param temperature 目标温度 + */ + public boolean startHeating(String heatId, double temperature) { + return true; + } + + /** + * 停止加热 + * + * @param heatId 加热区id + */ + public boolean stopHeating(String heatId) { + return true; + } + + /** + * 停止加热 + */ + public boolean takePhoto() { + return true; + } + + //移至异常 + public boolean moveToExc() { + return true; + } + + //移除异常 + public boolean moveOutToExc() { + return true; + } + + /** + * 等待 + * + * @param second 秒 + */ + public boolean delay(int second) { + try { + Thread.sleep(second * 1000L); + return true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + return false; + } + + +} diff --git a/src/main/java/com/iflytop/gd/system/mapper/CraftsMapper.java b/src/main/java/com/iflytop/gd/system/mapper/CraftsMapper.java new file mode 100644 index 0000000..b707aeb --- /dev/null +++ b/src/main/java/com/iflytop/gd/system/mapper/CraftsMapper.java @@ -0,0 +1,19 @@ +package com.iflytop.gd.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.gd.system.model.entity.Crafts; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 工艺持久层接口 + */ +@Mapper +public interface CraftsMapper extends BaseMapper { + + List selectAllByOresId(Long oresId); + + Crafts findByName(String name); + +} diff --git a/src/main/java/com/iflytop/gd/system/mapper/OresMapper.java b/src/main/java/com/iflytop/gd/system/mapper/OresMapper.java new file mode 100644 index 0000000..a08fb2f --- /dev/null +++ b/src/main/java/com/iflytop/gd/system/mapper/OresMapper.java @@ -0,0 +1,15 @@ +package com.iflytop.gd.system.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.iflytop.gd.system.model.entity.Ores; +import org.apache.ibatis.annotations.Mapper; + +/** + * 矿石持久层接口 + */ +@Mapper +public interface OresMapper extends BaseMapper { + + Ores findByName(String name); + +} diff --git a/src/main/java/com/iflytop/gd/system/model/entity/Crafts.java b/src/main/java/com/iflytop/gd/system/model/entity/Crafts.java new file mode 100644 index 0000000..645740b --- /dev/null +++ b/src/main/java/com/iflytop/gd/system/model/entity/Crafts.java @@ -0,0 +1,25 @@ +package com.iflytop.gd.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.gd.system.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "工艺") +@TableName("crafts") +@Data +public class Crafts extends BaseEntity { + + @NotBlank + @Schema(description = "工艺名称") + private String name; + + @Schema(description = "工艺步骤") + private String steps; + + @Schema(description = "矿石ID") + private Long oresId; +} diff --git a/src/main/java/com/iflytop/gd/system/model/entity/Ores.java b/src/main/java/com/iflytop/gd/system/model/entity/Ores.java new file mode 100644 index 0000000..6fe0c3a --- /dev/null +++ b/src/main/java/com/iflytop/gd/system/model/entity/Ores.java @@ -0,0 +1,20 @@ +package com.iflytop.gd.system.model.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.iflytop.gd.system.common.base.BaseEntity; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Schema(description = "矿石") +@TableName("ores") +@Data +public class Ores extends BaseEntity { + + @NotBlank() + @Schema(description = "矿石名称") + private String name; + +} diff --git a/src/main/java/com/iflytop/gd/system/model/vo/OresCraftsListVO.java b/src/main/java/com/iflytop/gd/system/model/vo/OresCraftsListVO.java new file mode 100644 index 0000000..24b8bb2 --- /dev/null +++ b/src/main/java/com/iflytop/gd/system/model/vo/OresCraftsListVO.java @@ -0,0 +1,30 @@ +package com.iflytop.gd.system.model.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.iflytop.gd.system.model.entity.Crafts; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "矿石工艺视图") +@Data +public class OresCraftsListVO { + + @Schema(description = "矿石id") + private Long id; + + @Schema(description = "矿石名称") + private String oresName; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + @Schema(description = "该矿石下工艺列表") + private List craftsList; + +} diff --git a/src/main/java/com/iflytop/gd/system/service/CraftsService.java b/src/main/java/com/iflytop/gd/system/service/CraftsService.java new file mode 100644 index 0000000..37164bf --- /dev/null +++ b/src/main/java/com/iflytop/gd/system/service/CraftsService.java @@ -0,0 +1,47 @@ +package com.iflytop.gd.system.service; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.gd.system.mapper.CraftsMapper; +import com.iflytop.gd.system.model.entity.Crafts; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 工艺 + */ +@Service +@RequiredArgsConstructor +public class CraftsService extends ServiceImpl{ + + public List selectAllByOresId(Long oresId) { + return this.baseMapper.selectAllByOresId(oresId); + } + + public Crafts findByName(String name) { + return this.baseMapper.findByName(name); + } + + public boolean addCrafts(Crafts crafts) { + return this.baseMapper.insert(crafts) > 0; + } + + public boolean updateCrafts(Crafts crafts) { + return this.baseMapper.updateById(crafts) > 0; + } + + public boolean deleteCrafts(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + return this.removeByIds(ids); + } + + public Crafts findCraftsById(Long id) { + return this.baseMapper.selectById(id); + } + +} diff --git a/src/main/java/com/iflytop/gd/system/service/OresService.java b/src/main/java/com/iflytop/gd/system/service/OresService.java new file mode 100644 index 0000000..cfdba6f --- /dev/null +++ b/src/main/java/com/iflytop/gd/system/service/OresService.java @@ -0,0 +1,98 @@ +package com.iflytop.gd.system.service; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.iflytop.gd.system.common.base.BasePageQuery; +import com.iflytop.gd.system.mapper.CraftsMapper; +import com.iflytop.gd.system.mapper.OresMapper; +import com.iflytop.gd.system.model.entity.Crafts; +import com.iflytop.gd.system.model.entity.Ores; +import com.iflytop.gd.system.model.vo.OresCraftsListVO; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 矿石业务实现类 + */ +@Service +@RequiredArgsConstructor +public class OresService extends ServiceImpl { + + private final CraftsMapper craftsMapper; + + + public IPage getPage(BasePageQuery pageQuery) { + // 构建分页对象 + Page oresPage = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + + // 分页查询矿石数据 + IPage oresIPage = this.baseMapper.selectPage(oresPage, new QueryWrapper()); + + // 获取矿石ID列表 + List oresIds = oresIPage.getRecords().stream() + .map(Ores::getId) + .collect(Collectors.toList()); + + // 查询对应的工艺数据 + QueryWrapper craftsQueryWrapper = new QueryWrapper<>(); + craftsQueryWrapper.in("ores_id", oresIds); + List craftsList = craftsMapper.selectList(craftsQueryWrapper); + + // 将工艺数据按矿石ID分组 + Map> craftsMap = craftsList.stream() + .collect(Collectors.groupingBy(Crafts::getOresId)); + + // 转换成 OresCraftsListVO + List oresCraftsList = oresIPage.getRecords().stream().map(ores -> { + OresCraftsListVO oresCraftsListVO = new OresCraftsListVO(); + oresCraftsListVO.setId(ores.getId()); + oresCraftsListVO.setOresName(ores.getName()); + oresCraftsListVO.setCreateTime(ores.getCreateTime()); + oresCraftsListVO.setUpdateTime(ores.getUpdateTime()); + + // 设置该矿石的工艺列表 + List crafts = craftsMap.get(ores.getId()); + oresCraftsListVO.setCraftsList(crafts); + + return oresCraftsListVO; + }).collect(Collectors.toList()); + + // 将转换后的分页数据放入新的分页对象 + Page resultPage = new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + resultPage.setTotal(oresIPage.getTotal()); + resultPage.setRecords(oresCraftsList); + + return resultPage; + + } + + + public Ores findByName(String name) { + return this.baseMapper.findByName(name); + } + + + public boolean addOres(Ores ores) { + return this.baseMapper.insert(ores) > 0; + } + + + public boolean updateOres(Ores ores) { + return this.baseMapper.updateById(ores) > 0; + } + + + public boolean deleteOres(String idsStr) { + List ids = Arrays.stream(idsStr.split(",")) + .map(Long::parseLong) + .collect(Collectors.toList()); + return this.removeByIds(ids); + } +}