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);
}
}
}
这里为了方便所有设备的管理,用了一个全局的定时任务来管理,当前不怕麻烦的话可以一个流程定义一个定时任务。
使用方法
- 定义一个流程管理类,如
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();
}
定义流程相关的步骤、执行方法等
- 通过具体的页面或者小程序的按钮将该流程动态添加到缓存中,由定时任务去管理该状态
如:我有一个小程序,上面有一个启动充电的按钮,当点击时,去执行一下代码
DeviceFlowCache.addTaskFlow(ChargerControlFlow.crateTaskManager("5112345"));
然后定时任务会自动去执行控制流程
结尾
完整项目参考请移步我的示例项目https://gitee.com/vfcm/device-flow
以上方案仅供参考,有好的方案也可以提issue私聊我