java自动控制设备流程方案(单设备或者多设备协同)

java自动控制设备流程方案(单设备或者多设备协同)

前言

本小白最近在用java做设备的控制流程,由于设备的控制的都是异步的,需要从设备的状态报文中去轮询设备是否已经启动成功,然而用传统的流程方案已经不能满足这个,所以写了一个轻量级的用一个定时任务去执行流程,从而完整的实现一组或者多个设备协同进行的方案,包括设备的控制方法的重试,因为设备是一个无状态的,无法让指令绝对正常下发到设备上。下面是实现该方案的主要逻辑

定义流程相关类


import com.zhouhx.flow.tasklist.TaskManager;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.function.Function;

/**
 * @author : zhouhx
 * 流程配置
 * @date : 2024/5/3 14:50
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class FlowConf {
    /**
     * 是否重试
     */
    private boolean retry;
    /**
     * 重试次数
     */
    private int retryCount;
    /**
     * <p>步骤的状态的超时时间(获取设备状态的超时时间)(单位:s),默认5s</p>
     * <p>根据流程的定时任务时间综合考虑,如果超时,则认为步骤失败</p>
     */
    private int timeout = 5;

    /**
     * 设备编号(可能为仓位号,也可能为充电桩编号+枪号)
     */
    private String equipmentNo;

    /**
     * 流程结束通知方法
     */
    public Function<TaskManager,?> posteriorFn;
}

import cn.hutool.core.lang.Assert;
import lombok.Data;

/**
 * @author : zhouhx
 * 流程实例
 * @date : 2024/5/3 14:50
 */
@Data
public class FlowInstance {
    /**
     * 流程实例id(必填)
     */
    private String flowInstanceId;
    /**
     * 当前流程步骤名称
     */
    private String currentFlowStepName;
    /**
     * 当前流程状态
     */
    private int currentFlowStatus;
    /**
     * 流程创建时间(格式:yyyy-MM-dd HH:mm:ss.SSSS)
     */
    private String flowCreateTime;
    /**
     * 流程结束时间(格式:yyyy-MM-dd HH:mm:ss.SSSS)
     */
    private String flowEndTime;
    /**
     * 流程id(必填)
     */
    private String flowId;
    /**
     * 流程名称(必填)
     */
    private String flowName;
    /**
     * 流程备注
     */
    private String remark;

    /**
     * 检查参数
     */
    public void check(){
        Assert.notNull(flowInstanceId,"流程实例id不能为空");
        Assert.notNull(flowName,"流程名称不能为空");
        Assert.notNull(flowId,"流程id不能为空");
    }
}

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import com.zhouhx.flow.tasklist.TaskState;
import com.zhouhx.flow.tasklist.enums.FlowStepStatusEnum;
import com.zhouhx.flow.tasklist.enums.FlowStepTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.function.Function;

/**
 * @author : zhouhx
 * 流程步骤
 * @date : 2024/5/3 14:50
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public  class FlowStep implements TaskState {
    private static final Logger log = LoggerFactory.getLogger(FlowStep.class);
    /**
     * 流程步骤id(必填)
     */
    private String flowStepId;
    /**
     * 获取状态的方法(必填),返回值是定义的流程状态
     */
    private Function<String,?> stateFunction;
    /**
     * 执行动作的方法(必填),返回值是定义的流程状态
     */
    private Function<String,?> excuteFunction;
    /**
     * 下一个流程步骤id(必填)
     */
    private String nextFlowStepId;
    /**
     * 步骤类型(必填)
     */
    private String stepType;
    /**
     * 执行次数
     */
    private int count;
    /**
     * 步骤名称
     */
    private String flowStepName;
    /**
     * 创建时间(格式:yyyy-MM-dd HH:mm:ss.SSSS)
     */
    private String createdTime;
    /**
     * 结束时间(格式:yyyy-MM-dd HH:mm:ss.SSSS)
     */
    private String endTime;
    /**
     * 步骤状态
     */
    private Integer flowStepStatus;

    /**
     * 执行动作的方法的返回状态
     */
    private String excuteFunctionStatus;
    /**
     * 执行动作的方法的错误原因
     */
    private String errorMsg;

    @Override
    public String getState(String equipmentNo) {
        Object apply = stateFunction.apply(equipmentNo);

        return apply.toString();
    }

    @Override
    public void execute(String equipmentNo) {
        this.createdTime = DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN);
        this.flowStepStatus = FlowStepStatusEnum.RUNNING.getStatus();
        // 执行方法可能报错,放在最后
        try {
            Object apply = excuteFunction.apply(equipmentNo);
            this.excuteFunctionStatus = apply.toString();
        } catch (Exception e) {
            log.error("流程任务>>>>>>执行流程步骤【{}】异常",flowStepName,e);
            this.errorMsg = e.getMessage();
        }
    }

    public void check(){
        Assert.notNull(this.flowStepId,"流程步骤id不能为null");
        Assert.notNull(this.stepType," 步骤类型不能为null");
        // 结束步骤不判断
        if(!this.stepType.equals(FlowStepTypeEnum.END.getType())){
            Assert.notNull(this.stateFunction,"获取状态的方法不能为null");
            Assert.notNull(this.excuteFunction,"获取执行动作的方法不能为null");
            Assert.notNull(this.nextFlowStepId,"下一个流程步骤id不能为null");
        }
    }
}

定义流程执行逻辑类

**
 * @author : zhouhx
 * 任务管理类
 * @date : 2024/5/3 14:50
 */
@Data
@Slf4j
public class TaskManager {
    /**
     * 流程配置
     */
    private FlowConf flowConf;
    /**
     * 流程实例id
     */
    private FlowInstance flowInstance;
    /**
     * 任务清单
     * key 为步骤id
     */
    private final ConcurrentMap<String, FlowStep> taskMap;
    /**
     * 当前流程步骤
     */
    private FlowStep currentStep;

    public TaskManager(FlowConf flowConf, FlowInstance flowInstance, ConcurrentMap<String, FlowStep> taskMap){
        this.flowConf = flowConf;
        this.flowInstance = flowInstance;
        this.taskMap = taskMap;

        Assert.notNull(flowConf, "流程配置不能为空");
        Assert.notNull(flowInstance, "流程实例不能为空");
        Assert.notNull(taskMap, "流程清单不能为空");
        Assert.notNull(flowConf.getEquipmentNo(), "设备编号不能为空");
        checkFlowStep(taskMap);
        flowInstance.check();
        Assert.notNull(getStartFlowStep(), "必须存在开始步骤");
        Assert.notNull(getEndStep(), "必须存在结束步骤");


    }

    /**
     * 检查流程步骤参数
     * @param taskMap 流程步骤
     */
    private static void checkFlowStep(ConcurrentMap<String, FlowStep> taskMap) {
        taskMap.forEach((k, v)->{
            v.check();
        });
    }

    /**
     * 自动执行流程
     */
    public void startTaskExecution() {
        try {
            int currentFlowStatus = flowInstance.getCurrentFlowStatus();
            if(FlowInstanceStatusEnum.FINISH.getStatus() == currentFlowStatus){
                // 流程结束,不在执行
                return;
            }
            // 当前步骤为空,则执行开始流程
            if(ObjectUtil.isEmpty(currentStep)){
                FlowStep flowStep = getStartFlowStep();
                createStaretFlowStep(flowStep);
            }else{
                // 步骤执行次数
                int count = currentStep.getCount();
                // 重试
                boolean retry = flowConf.isRetry();
                // 重试次数
                int retryCount = flowConf.getRetryCount();
                Date createdTime = DateUtil.parse(currentStep.getCreatedTime(), DatePattern.NORM_DATETIME_MS_PATTERN);
                // 现在流程步骤执行时间
                long time = DateUtil.between(createdTime, new Date(), DateUnit.SECOND);
                if(time > flowConf.getTimeout()){
                    if(retry){
                        if(count < retryCount){
                            count++;
                            // 重试
                            log.debug("流程任务-重试>>>>>>流程名称:{},流程实例id:{},第{}次重试,开始执行流程步骤【{}】",this.flowInstance.getFlowName(),flowInstance.getFlowInstanceId(),count,currentStep.getFlowStepName());
                            currentStep.execute(flowConf.getEquipmentNo());
                            currentStep.setCount(count);
                        }else{
                            // 超过重试次数,自动办结流程
                            finishStep(currentStep);
                            finishFlow(String.format("超出最大重试次数,重试次数:%s,原因:%s",count,currentStep.getErrorMsg()));
                        }
                    }else {
                        // 流程步骤超时,自动办结流程
                        finishStep(currentStep);
                        finishFlow(String.format("步骤时间超时,超时时间:%s秒",time));
                    }
                }else{
                    // 已经有任务执行了,先判断状态,状态对了,则执行下一个
                    extractedNextStep();
                }
            }
        } catch (Exception e) {
            log.error("自动执行流程任务异常",e);
        }
    }

    /**
     * 执行下一步流程
     */
    private void extractedNextStep() {
        String state = currentStep.getState(flowConf.getEquipmentNo());
        String excuteFunctionStatus = currentStep.getExcuteFunctionStatus();
        if(ObjectUtil.isNotEmpty(excuteFunctionStatus) && excuteFunctionStatus.equals(state)){
            // 完结步骤
            finishStep(currentStep);
            // 执行下一个步骤
            FlowStep flowStep = taskMap.get(currentStep.getNextFlowStepId());
            FlowStep endStep = getEndStep();
            if(currentStep.getFlowStepId().equals(endStep.getFlowStepId())){
                // 没有下一个步骤,说明流程已经在最后一个环节
                // 办结流程
                finishFlow("流程办结");
            }else{
                // 执行下一步骤
                createFlowStep(flowStep);
            }

        }
    }

    /**
     * 办结流程
     */
    private void finishFlow(String remark) {
        log.debug("流程任务>>>>>>流程名称:{},流程实例id:{},流程结束原因:{}",this.flowInstance.getFlowName(),flowInstance.getFlowInstanceId(),remark);
        flowInstance.setCurrentFlowStatus(FlowInstanceStatusEnum.FINISH.getStatus());
        flowInstance.setFlowEndTime(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
        flowInstance.setRemark(remark);
        TaskLog.generateFlowLog(this.flowInstance.getFlowName(),this.flowInstance.getFlowInstanceId(),flowInstance);
        Function<TaskManager,?> fn = flowConf.getPosteriorFn();
        if(ObjectUtil.isNotEmpty(fn)){
            fn.apply(this);
        }
    }

    private void finishStep(FlowStep step) {
        log.debug("流程任务>>>>>>流程名称:{},流程实例id:{},流程步骤【{}】已结束,原因:{}",this.flowInstance.getFlowName(),flowInstance.getFlowInstanceId(),step.getFlowStepName(),step.getErrorMsg());
        step.setFlowStepStatus(FlowStepStatusEnum.FINISH.getStatus());
        step.setEndTime(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
        TaskLog.generateStepLog(this.flowInstance.getFlowName(),this.flowInstance.getFlowInstanceId(),step);
    }

    /**
     * 创建开始流程步骤
     * @param flowStep 流程步骤
     */
    private void createStaretFlowStep(FlowStep flowStep) {
        createFlowStep(flowStep);
        // 更新流程实例
        flowInstance.setCurrentFlowStatus(FlowInstanceStatusEnum.RUNNING.getStatus());
        flowInstance.setFlowCreateTime(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
        flowInstance.setCurrentFlowStepName(flowStep.getFlowStepName());
    }
    /**
     * 创建流程步骤
     * @param flowStep 流程步骤
     */
    private void createFlowStep(FlowStep flowStep) {
        log.debug("流程任务>>>>>>流程名称:{},流程实例id:{},开始执行流程步骤【{}】",this.flowInstance.getFlowName(),flowInstance.getFlowInstanceId(),flowStep.getFlowStepName());
        flowStep.execute(flowConf.getEquipmentNo());
        flowStep.setCount(1);
        this.currentStep = flowStep;
        // 更新流程实例
        flowInstance.setCurrentFlowStepName(flowStep.getFlowStepName());
    }
    /**
     * 获取开始流程步骤
     * @return
     */
    private FlowStep getStartFlowStep() {
        AtomicReference<FlowStep> step = new AtomicReference<>();
        taskMap.forEach((key,value)->{
            if(value.getStepType().equals(FlowStepTypeEnum.START.getType())){
                step.set(value);
            }
        });

        return step.get();
    }

    /**
     * 获取结束流程步骤
     * @return
     */
    private FlowStep getEndStep() {
        AtomicReference<FlowStep> step = new AtomicReference<>();
        taskMap.forEach((key,value)->{
            if(value.getStepType().equals(FlowStepTypeEnum.END.getType())){
                step.set(value);
            }
        });

        return step.get();
    }
}

这里主要逻辑是每次定时任务运行,通过currentStep当前步骤判断,然后通过java8的Function类的之执行定义的执行流程方法和获取状态的方法,当状态满足时,再执行流程步骤定义的下一个步骤的stepId,然后继续执行。

定义流程缓存类

由于设备都是一个异步的,启动相关控制需要时间,所以需要一个缓存类去缓存相关状态,这里为了方便我直接用了内存来缓存。

import com.alibaba.fastjson2.JSONObject;
import com.zhouhx.flow.tasklist.TaskManager;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 *
 * @author zhuohx
 * @description 流程缓存
 * @date 2024/5/3 14:50
 */
@Slf4j
public class DeviceFlowCache {
    /**
     * 自动流程任务缓存
     */
    private static Map<String, TaskManager> taskFlowMap = new ConcurrentHashMap<>();

    /**
     * 添加任务
     * @param taskManager 任务
     */
    public static void addTaskFlow(TaskManager taskManager) {
        log.debug(">>>>>>添加流程自动任务,参数:{}", JSONObject.toJSONString(taskManager));
        // 判断流程任务中是否已将存在该任务,存在则不添加
        boolean b = hasExist(taskManager);
        if(!b){
            taskFlowMap.put(taskManager.getFlowInstance().getFlowId(),taskManager);
        }
    }

    private static boolean hasExist(TaskManager taskManager) {
        AtomicBoolean flag = new AtomicBoolean(false);
        String equipmentNo = taskManager.getFlowConf().getEquipmentNo();
        String flowId = taskManager.getFlowInstance().getFlowId();
        taskFlowMap.forEach((k,v)->{
            if (v.getFlowConf().getEquipmentNo().equals(equipmentNo) && v.getFlowInstance().getFlowId().equals(flowId)) {
                log.warn(">>>>>>流程任务已存在,参数:{}",JSONObject.toJSONString(taskManager));
                flag.set(true);
            }
        });

        return flag.get();
    }

    /**
     * 获取所有任务流程
     */
    public static Map<String, TaskManager> getAllTaskFlow() {
        return taskFlowMap;
    }

定义流程自动执行的定时任务

这里为了方便管理,我定义了一个线程池,保证流程的执行效率

import com.zhouhx.flow.cache.DeviceFlowCache;
import com.zhouhx.flow.tasklist.TaskManager;
import com.zhouhx.flow.tasklist.entity.FlowInstance;
import com.zhouhx.flow.tasklist.enums.FlowInstanceStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.*;

/**
 * @author : zhouhx
 * 流程任务
 * @date : 2024/5/3 14:47
 */
@Slf4j
@Component
public class DeviceFlowTask {
    /**
     * 线程池数量
     */
    private int poolSize = 4;
    /**
     *
     *有界队列
     */
    private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);
    /**
     *
     *   拒绝策略(什么也不做,直接忽略)
     */
    private RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();
    /**
     * 线程池
     */
    private  ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(poolSize, poolSize,
            60, TimeUnit.SECONDS,queue, new CustomizableThreadFactory("auto-flow-task-scheduler"),policy);

    private volatile static DeviceFlowTask instance;

    public static DeviceFlowTask getInstance() {
        if (instance == null) {
            // 第一次检查,如果为空才进入同步代码块
            synchronized (DeviceFlowTask.class) {
                if (instance == null) {
                    // 第二次检查,如果为空才创建新的实例
                    instance = new DeviceFlowTask();
                }
            }
        }
        return instance;
    }

    /**
     *
     * 获取线程池
     * @author zhuohx
     * @param
     * @return java.util.concurrent.ThreadPoolExecutor
     * @throws
     * @version 1.0
     * @since  2024/5/3 14:47
     */
    public  ThreadPoolExecutor getThreadPool() {

        return this.threadPoolExecutor;
    }

    public void shutdown() {
        this.threadPoolExecutor.shutdown();
        log.info("释放线程池成功");
    }

    // 每个5s执行一次
    @Scheduled(cron = "0/1 * * * * ?")
    void execute() {
        Map<String, TaskManager> allTask = DeviceFlowCache.getAllTaskFlow();
        if (allTask.isEmpty()) {
            return;
        }
        try {
            allTask.forEach((k, v) -> {
                threadPoolExecutor.execute(() ->{
                    FlowInstance instance = v.getFlowInstance();
                    if(FlowInstanceStatusEnum.FINISH.getStatus() == instance.getCurrentFlowStatus()){
                        // 流程结束,则移除
                        allTask.remove(k);
                    }else{
                        v.startTaskExecution();
                    }
                });
            });
        } catch (Exception e) {
            log.error("自动执行流程任务异常", e);
        }
    }
}

这里为了方便所有设备的管理,用了一个全局的定时任务来管理,当前不怕麻烦的话可以一个流程定义一个定时任务。

使用方法

  1. 定义一个流程管理类,如
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.zhouhx.flow.cache.DeviceFlowCache;
import com.zhouhx.flow.demo.service.ChargerStartChargingService;
import com.zhouhx.flow.tasklist.TaskManager;
import com.zhouhx.flow.tasklist.entity.FlowConf;
import com.zhouhx.flow.tasklist.entity.FlowInstance;
import com.zhouhx.flow.tasklist.entity.FlowStep;
import com.zhouhx.flow.tasklist.enums.FlowStepTypeEnum;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 1. @author : zhouhx
 2. 普通充电流程
 3. @date : 2024/5/3 15:05
 */
public class ChargerControlFlow {
    // 流程id
    private final static String FLOW_ID = "666666";
    private final static String FLOW_NAME = "普通启动充电流程";
    // 开始充电步骤id
    private final static String START_CHARGING_STEPID1 = String.format("%s%s", FLOW_ID, "01");
    private final static String START_CHARGING_STEPID2 = String.format("%s%s", FLOW_ID, "02");

    private final static ChargerStartChargingService startChargingService = SpringUtil.getBean(ChargerStartChargingService.class);

    /**
     * 创建开始充电流程任务
     */
    public static TaskManager crateTaskManager(String equipmentNo){
        FlowConf flowConf = getFlowConf(equipmentNo);
        ConcurrentMap<String, FlowStep> taskMap = getTaskMap();
        FlowInstance flowInstance = getFlowInstance();

        return new TaskManager(flowConf,flowInstance,taskMap);
    }

    /**
     * 获取流程配置
     */
    private  static FlowConf getFlowConf(String equipmentNo) {
        return FlowConf.builder()
                .retry(true)
                .retryCount(3)
                .timeout(10)
                .equipmentNo(equipmentNo)
                .build();
    }

    /**
     * 获取流程实例
     */
    private static FlowInstance getFlowInstance() {
        FlowInstance instance = new FlowInstance();
        instance.setFlowInstanceId(IdUtil.getSnowflakeNextIdStr());
        instance.setFlowId(FLOW_ID);
        instance.setFlowName(FLOW_NAME);

        return instance;
    }
    /**
     * 获取任务清单
     */
    private static ConcurrentMap<String, FlowStep> getTaskMap(){
        ConcurrentMap<String, FlowStep> map = new ConcurrentHashMap<>();
        map.put(START_CHARGING_STEPID1,getStartChargingStep1());
        map.put(START_CHARGING_STEPID2,getStartChargingStep2());

        return map;
    }

    /**
     * 步骤一
     */
    private static FlowStep getStartChargingStep1(){
        return FlowStep.builder()
                .flowStepId(START_CHARGING_STEPID2)
                .flowStepName(CommonChargingStatusEnum.STATUS_2.getText())
                .stepType(FlowStepTypeEnum.START.getType())
                // 执行方法
                .excuteFunction((houseNo) -> {
                    ChargerStartChargingVo startChargingVo = new ChargerStartChargingVo();
                    startChargingVo.setHouseNo(houseNo);
                    startChargingService.startCharging(startChargingVo);
                    return CommonChargingStatusEnum.STATUS_2.getStatus();
                })
                // 获取状态的方法
                .stateFunction((houseNo) -> {
                    ChargerStartChargingVo startChargingVo = new ChargerStartChargingVo();
                    startChargingVo.setHouseNo(houseNo);
                    boolean result  = startChargingService.getStartChargingResult(startChargingVo);
                    if(result){
                        return CommonChargingStatusEnum.STATUS_2.getStatus();
                    }
                    return "0";
                })
                .nextFlowStepId(START_CHARGING_STEPID2)
                .build();
    }

    /**
     * 步骤二
     */
    private static FlowStep getStartChargingStep2(){
        return FlowStep.builder()
                .flowStepId(START_CHARGING_STEPID2)
                .flowStepName(CommonChargingStatusEnum.STATUS_2.getText())
                .stepType(FlowStepTypeEnum.END.getType())
                .build();
    }

定义流程相关的步骤、执行方法等

  1. 通过具体的页面或者小程序的按钮将该流程动态添加到缓存中,由定时任务去管理该状态
    如:我有一个小程序,上面有一个启动充电的按钮,当点击时,去执行一下代码
DeviceFlowCache.addTaskFlow(ChargerControlFlow.crateTaskManager("5112345"));

然后定时任务会自动去执行控制流程

结尾

完整项目参考请移步我的示例项目https://gitee.com/vfcm/device-flow
以上方案仅供参考,有好的方案也可以提issue私聊我

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值