添加状态初始化能力
This commit is contained in:
parent
89e49dd17d
commit
51fe7cf816
|
|
@ -70,8 +70,9 @@ public class AirportMachineConfig {
|
|||
private void configureStates(StateMachineBuilder.Builder<AirportState, AirportEvent> builder) throws Exception {
|
||||
builder.configureStates()
|
||||
.withStates()
|
||||
.initial(AirportState.OFFLINE)
|
||||
.initial(AirportState.UNKNOWN)
|
||||
.states(EnumSet.of(
|
||||
AirportState.UNKNOWN,
|
||||
AirportState.OFFLINE,
|
||||
AirportState.ONLINE,
|
||||
AirportState.REBOOTING
|
||||
|
|
@ -90,6 +91,43 @@ public class AirportMachineConfig {
|
|||
StateMachineBuilder.Builder<AirportState, AirportEvent> builder,
|
||||
AirportPlatformStrategy strategy) throws Exception {
|
||||
builder.configureTransitions()
|
||||
// ========== 从 UNKNOWN 到所有状态的转换(服务器重启后状态同步) ==========
|
||||
// UNKNOWN -> OFFLINE
|
||||
.withExternal()
|
||||
.source(AirportState.UNKNOWN)
|
||||
.target(AirportState.OFFLINE)
|
||||
.event(AirportEvent.AIRPORT_OFFLINE)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> ONLINE(STANDBY)
|
||||
.withExternal()
|
||||
.source(AirportState.UNKNOWN)
|
||||
.target(AirportState.ONLINE)
|
||||
.event(AirportEvent.AIRPORT_ONLINE)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> STANDBY
|
||||
.withExternal()
|
||||
.source(AirportState.UNKNOWN)
|
||||
.target(AirportState.STANDBY)
|
||||
.event(AirportEvent.AIRPORT_ONLINE)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> DEBUG_MODE
|
||||
.withExternal()
|
||||
.source(AirportState.UNKNOWN)
|
||||
.target(AirportState.DEBUG_MODE)
|
||||
.event(AirportEvent.DEBUG_MODE_OPEN)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> REBOOTING
|
||||
.withExternal()
|
||||
.source(AirportState.UNKNOWN)
|
||||
.target(AirportState.REBOOTING)
|
||||
.event(AirportEvent.AIRPORT_REBOOT)
|
||||
.and()
|
||||
|
||||
// ========== 正常状态转换(带 Guard 和 Action) ==========
|
||||
// OFFLINE -> ONLINE(STANDBY)
|
||||
.withExternal()
|
||||
.source(AirportState.OFFLINE)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class CoverMachineConfig {
|
|||
private void configureCoverStates(StateMachineBuilder.Builder<CoverState, CoverEvent> builder) throws Exception {
|
||||
builder.configureStates()
|
||||
.withStates()
|
||||
.initial(CoverState.CLOSED)
|
||||
.initial(CoverState.UNKNOWN)
|
||||
.states(EnumSet.allOf(CoverState.class));
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +78,50 @@ public class CoverMachineConfig {
|
|||
StateMachineBuilder.Builder<CoverState, CoverEvent> builder,
|
||||
CoverPlatformStrategy strategy) throws Exception {
|
||||
builder.configureTransitions()
|
||||
// ========== 从 UNKNOWN 到所有状态的转换(服务器重启后状态同步) ==========
|
||||
// UNKNOWN -> CLOSED
|
||||
.withExternal()
|
||||
.source(CoverState.UNKNOWN)
|
||||
.target(CoverState.CLOSED)
|
||||
.event(CoverEvent.CLOSED)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> OPENING
|
||||
.withExternal()
|
||||
.source(CoverState.UNKNOWN)
|
||||
.target(CoverState.OPENING)
|
||||
.event(CoverEvent.OPEN)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> OPENED
|
||||
.withExternal()
|
||||
.source(CoverState.UNKNOWN)
|
||||
.target(CoverState.OPENED)
|
||||
.event(CoverEvent.OPENED)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> CLOSING
|
||||
.withExternal()
|
||||
.source(CoverState.UNKNOWN)
|
||||
.target(CoverState.CLOSING)
|
||||
.event(CoverEvent.CLOSE)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> HALF_OPEN
|
||||
.withExternal()
|
||||
.source(CoverState.UNKNOWN)
|
||||
.target(CoverState.HALF_OPEN)
|
||||
.event(CoverEvent.OPENED)
|
||||
.and()
|
||||
|
||||
// UNKNOWN -> ERROR
|
||||
.withExternal()
|
||||
.source(CoverState.UNKNOWN)
|
||||
.target(CoverState.ERROR)
|
||||
.event(CoverEvent.ERROR)
|
||||
.and()
|
||||
|
||||
// ========== 正常状态转换(带 Guard 和 Action) ==========
|
||||
// CLOSED -> OPENING
|
||||
.withExternal()
|
||||
.source(CoverState.CLOSED)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public class MultiPlatformDemo {
|
|||
|
||||
// 获取必要的Bean
|
||||
PlatformStrategyFactory strategyFactory = context.getBean(PlatformStrategyFactory.class);
|
||||
AirportPlatformRepository repository = context.getBean(AirportPlatformRepository.class);
|
||||
|
||||
|
||||
System.out.println("\n========== DJI 机巢系统演示开始 ==========\n");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
package com.tuoheng.status.machine.manager;
|
||||
|
||||
import com.tuoheng.status.machine.events.AirportEvent;
|
||||
import com.tuoheng.status.machine.events.CoverEvent;
|
||||
import com.tuoheng.status.machine.platform.PlatformType;
|
||||
import com.tuoheng.status.machine.service.AirportMachineService;
|
||||
import com.tuoheng.status.machine.service.CoverMachineService;
|
||||
import com.tuoheng.status.machine.status.AirportState;
|
||||
import com.tuoheng.status.machine.status.CoverState;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
/**
|
||||
|
|
@ -22,5 +26,137 @@ public abstract class AbstractAirportSystemManager implements AirportSystemManag
|
|||
|
||||
@Autowired
|
||||
protected CoverMachineService coverService;
|
||||
|
||||
|
||||
public boolean sendEvent(String airportSn, AirportEvent event) {
|
||||
return airportService.sendEvent(airportSn, event);
|
||||
}
|
||||
|
||||
public boolean sendEvent(String airportSn, CoverEvent event) {
|
||||
return coverService.sendEvent(airportSn, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步机巢状态
|
||||
* 仅在当前状态为 UNKNOWN 时才同步到目标状态
|
||||
*
|
||||
* @param airportSn 机巢序列号
|
||||
* @param targetState 目标状态
|
||||
* @return 是否同步成功
|
||||
*/
|
||||
public boolean syncAirportState(String airportSn, AirportState targetState) {
|
||||
AirportState currentState = airportService.getCurrentState(airportSn);
|
||||
|
||||
if (currentState == null) {
|
||||
System.out.println(String.format("同步机巢状态失败 - 机巢: %s, 状态机不存在", airportSn));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentState != AirportState.UNKNOWN) {
|
||||
System.out.println(String.format("同步机巢状态跳过 - 机巢: %s, 当前状态: %s (非UNKNOWN状态,无需同步)",
|
||||
airportSn, currentState));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据目标状态发送相应的事件
|
||||
AirportEvent event = getAirportEventForState(targetState);
|
||||
if (event == null) {
|
||||
System.out.println(String.format("同步机巢状态失败 - 机巢: %s, 无法为目标状态 %s 找到对应事件",
|
||||
airportSn, targetState));
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean result = airportService.sendEvent(airportSn, event);
|
||||
if (result) {
|
||||
System.out.println(String.format("同步机巢状态成功 - 机巢: %s, 从 UNKNOWN 同步到 %s",
|
||||
airportSn, targetState));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步舱门状态
|
||||
* 仅在当前状态为 UNKNOWN 时才同步到目标状态
|
||||
*
|
||||
* @param airportSn 机巢序列号
|
||||
* @param targetState 目标状态
|
||||
* @return 是否同步成功
|
||||
*/
|
||||
public boolean syncCoverState(String airportSn, CoverState targetState) {
|
||||
CoverState currentState = coverService.getCurrentState(airportSn);
|
||||
|
||||
if (currentState == null) {
|
||||
System.out.println(String.format("同步舱门状态失败 - 机巢: %s, 状态机不存在", airportSn));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentState != CoverState.UNKNOWN) {
|
||||
System.out.println(String.format("同步舱门状态跳过 - 机巢: %s, 当前状态: %s (非UNKNOWN状态,无需同步)",
|
||||
airportSn, currentState));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据目标状态发送相应的事件
|
||||
CoverEvent event = getCoverEventForState(targetState);
|
||||
if (event == null) {
|
||||
System.out.println(String.format("同步舱门状态失败 - 机巢: %s, 无法为目标状态 %s 找到对应事件",
|
||||
airportSn, targetState));
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean result = coverService.sendEvent(airportSn, event);
|
||||
if (result) {
|
||||
System.out.println(String.format("同步舱门状态成功 - 机巢: %s, 从 UNKNOWN 同步到 %s",
|
||||
airportSn, targetState));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据目标状态获取对应的机巢事件
|
||||
* 子类可以重写此方法以提供自定义的状态到事件的映射
|
||||
*
|
||||
* @param targetState 目标状态
|
||||
* @return 对应的事件,如果没有对应事件则返回null
|
||||
*/
|
||||
protected AirportEvent getAirportEventForState(AirportState targetState) {
|
||||
switch (targetState) {
|
||||
case ONLINE:
|
||||
case STANDBY:
|
||||
return AirportEvent.AIRPORT_ONLINE;
|
||||
case OFFLINE:
|
||||
return AirportEvent.AIRPORT_OFFLINE;
|
||||
case DEBUG_MODE:
|
||||
return AirportEvent.DEBUG_MODE_OPEN;
|
||||
case REBOOTING:
|
||||
return AirportEvent.AIRPORT_REBOOT;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据目标状态获取对应的舱门事件
|
||||
* 子类可以重写此方法以提供自定义的状态到事件的映射
|
||||
*
|
||||
* @param targetState 目标状态
|
||||
* @return 对应的事件,如果没有对应事件则返回null
|
||||
*/
|
||||
protected CoverEvent getCoverEventForState(CoverState targetState) {
|
||||
switch (targetState) {
|
||||
case CLOSED:
|
||||
return CoverEvent.CLOSED;
|
||||
case OPENED:
|
||||
return CoverEvent.OPENED;
|
||||
case OPENING:
|
||||
return CoverEvent.OPEN;
|
||||
case CLOSING:
|
||||
return CoverEvent.CLOSE;
|
||||
case ERROR:
|
||||
return CoverEvent.ERROR;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tuoheng.status.machine.manager;
|
||||
|
||||
import com.tuoheng.status.machine.events.AirportEvent;
|
||||
import com.tuoheng.status.machine.events.CoverEvent;
|
||||
import com.tuoheng.status.machine.platform.PlatformType;
|
||||
import com.tuoheng.status.machine.status.AirportState;
|
||||
import com.tuoheng.status.machine.status.CoverState;
|
||||
|
|
@ -15,6 +17,18 @@ public interface AirportSystemManager {
|
|||
*/
|
||||
PlatformType getPlatformType();
|
||||
|
||||
public boolean sendEvent(String airportSn, AirportEvent event);
|
||||
|
||||
public boolean sendEvent(String airportSn, CoverEvent event);
|
||||
|
||||
public boolean syncAirportState(String airportSn, AirportState targetState);
|
||||
|
||||
public boolean syncCoverState(String airportSn, CoverState targetState);
|
||||
/**
|
||||
* 有心跳的时候调用
|
||||
* @param airportSn
|
||||
* @return
|
||||
*/
|
||||
boolean airportOnline(String airportSn);
|
||||
|
||||
boolean airportOffline(String airportSn);
|
||||
|
|
|
|||
|
|
@ -20,11 +20,19 @@ public class DjiAirportSystemManager extends AbstractAirportSystemManager {
|
|||
return PlatformType.DJI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param airportSn
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean airportOnline(String airportSn) {
|
||||
return airportService.sendEvent(airportSn, AirportEvent.AIRPORT_ONLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param airportSn
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean airportOffline(String airportSn) {
|
||||
return airportService.sendEvent(airportSn, AirportEvent.AIRPORT_OFFLINE);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tuoheng.status.machine.redis;
|
||||
|
||||
import com.tuoheng.status.machine.status.AirportState;
|
||||
import com.tuoheng.status.machine.status.CoverState;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 使用 Redis 记录和恢复机巢/舱门状态的存储组件。
|
||||
*
|
||||
* 当前实现采用内存 Map 占位,便于无 Redis 环境下直接运行。
|
||||
* 如果接入真正的 Redis,只需将存取逻辑替换为 RedisTemplate 等实现。
|
||||
*/
|
||||
@Component
|
||||
public class RedisStateStore {
|
||||
|
||||
private final Map<String, AirportState> airportStateMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, CoverState> coverStateMap = new ConcurrentHashMap<>();
|
||||
|
||||
public void saveAirportState(String airportSn, AirportState state) {
|
||||
if (airportSn != null && state != null) {
|
||||
airportStateMap.put(airportSn, state);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveCoverState(String airportSn, CoverState state) {
|
||||
if (airportSn != null && state != null) {
|
||||
coverStateMap.put(airportSn, state);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<AirportState> loadAirportState(String airportSn) {
|
||||
return Optional.ofNullable(airportStateMap.get(airportSn));
|
||||
}
|
||||
|
||||
public Optional<CoverState> loadCoverState(String airportSn) {
|
||||
return Optional.ofNullable(coverStateMap.get(airportSn));
|
||||
}
|
||||
|
||||
public Set<String> allAirportIds() {
|
||||
// 合并两张表的 key,防止有只存机场或只存舱门的情况
|
||||
Set<String> ids = ConcurrentHashMap.newKeySet();
|
||||
ids.addAll(airportStateMap.keySet());
|
||||
ids.addAll(coverStateMap.keySet());
|
||||
return ids;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tuoheng.status.machine.service;
|
||||
|
||||
import com.tuoheng.status.machine.events.AirportEvent;
|
||||
import com.tuoheng.status.machine.redis.RedisStateStore;
|
||||
import com.tuoheng.status.machine.status.AirportState;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.support.DefaultStateMachineContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -20,6 +22,9 @@ public class AirportMachineService {
|
|||
@Autowired
|
||||
StateMachineFactory<AirportState, AirportEvent> stateMachineFactory;
|
||||
|
||||
@Autowired
|
||||
private RedisStateStore redisStateStore;
|
||||
|
||||
/**
|
||||
* 存储所有机巢的状态机实例
|
||||
* Key: 机巢ID (airportSn)
|
||||
|
|
@ -37,8 +42,11 @@ public class AirportMachineService {
|
|||
public StateMachine<AirportState, AirportEvent> getOrCreateStateMachine(String airportSn) {
|
||||
return stateMachineMap.computeIfAbsent(airportSn, id -> {
|
||||
StateMachine<AirportState, AirportEvent> stateMachine = stateMachineFactory.getStateMachine(id);
|
||||
// 服务器重启后,状态机初始化为 UNKNOWN 状态
|
||||
// 不从 Redis 恢复旧状态,等待第一次心跳同步真实状态
|
||||
// 这样可以避免服务器重启期间丢失心跳导致的状态不一致问题
|
||||
stateMachine.start();
|
||||
System.out.println("创建并启动状态机: " + id);
|
||||
System.out.println(String.format("创建并启动状态机: %s, 初始状态: UNKNOWN (等待心跳同步)", id));
|
||||
return stateMachine;
|
||||
});
|
||||
}
|
||||
|
|
@ -103,6 +111,8 @@ public class AirportMachineService {
|
|||
boolean result = stateMachine.sendEvent(event);
|
||||
|
||||
if (result) {
|
||||
// 持久化最新状态
|
||||
redisStateStore.saveAirportState(airportSn, stateMachine.getState().getId());
|
||||
System.out.println(String.format("事件发送成功 - 机巢: %s, 事件: %s, 当前状态: %s",
|
||||
airportSn, event, getCurrentStates(airportSn)));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tuoheng.status.machine.service;
|
||||
|
||||
import com.tuoheng.status.machine.events.CoverEvent;
|
||||
import com.tuoheng.status.machine.redis.RedisStateStore;
|
||||
import com.tuoheng.status.machine.status.CoverState;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.support.DefaultStateMachineContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
|
@ -19,13 +21,19 @@ public class CoverMachineService {
|
|||
@Autowired
|
||||
StateMachineFactory<CoverState, CoverEvent> coverStateMachineFactory;
|
||||
|
||||
@Autowired
|
||||
private RedisStateStore redisStateStore;
|
||||
|
||||
private final Map<String, StateMachine<CoverState, CoverEvent>> stateMachineMap = new ConcurrentHashMap<>();
|
||||
|
||||
public StateMachine<CoverState, CoverEvent> getOrCreateStateMachine(String airportSn) {
|
||||
return stateMachineMap.computeIfAbsent(airportSn, id -> {
|
||||
StateMachine<CoverState, CoverEvent> stateMachine = coverStateMachineFactory.getStateMachine(id);
|
||||
// 服务器重启后,状态机初始化为 UNKNOWN 状态
|
||||
// 不从 Redis 恢复旧状态,等待第一次心跳同步真实状态
|
||||
// 这样可以避免服务器重启期间丢失心跳导致的状态不一致问题
|
||||
stateMachine.start();
|
||||
System.out.println("创建并启动舱门状态机: " + id);
|
||||
System.out.println(String.format("创建并启动舱门状态机: %s, 初始状态: UNKNOWN (等待心跳同步)", id));
|
||||
return stateMachine;
|
||||
});
|
||||
}
|
||||
|
|
@ -43,6 +51,8 @@ public class CoverMachineService {
|
|||
boolean result = stateMachine.sendEvent(event);
|
||||
|
||||
if (result) {
|
||||
// 持久化最新状态
|
||||
redisStateStore.saveCoverState(airportSn, stateMachine.getState().getId());
|
||||
System.out.println(String.format("舱门事件发送成功 - 机巢: %s, 事件: %s, 当前状态: %s",
|
||||
airportSn, event, getCurrentState(airportSn)));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ package com.tuoheng.status.machine.status;
|
|||
* 机巢状态枚举(简化版 - 舱门状态已分离)
|
||||
*/
|
||||
public enum AirportState {
|
||||
/**
|
||||
* 未知状态(服务器重启后的初始状态,等待第一次心跳同步)
|
||||
*/
|
||||
UNKNOWN,
|
||||
|
||||
/**
|
||||
* 离线
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ package com.tuoheng.status.machine.status;
|
|||
* 舱门状态枚举
|
||||
*/
|
||||
public enum CoverState {
|
||||
/**
|
||||
* 未知状态(服务器重启后的初始状态,等待第一次心跳同步)
|
||||
*/
|
||||
UNKNOWN,
|
||||
|
||||
/**
|
||||
* 舱门已关闭
|
||||
*/
|
||||
|
|
@ -24,11 +29,6 @@ public enum CoverState {
|
|||
*/
|
||||
CLOSING,
|
||||
|
||||
/**
|
||||
* 舱门半开
|
||||
*/
|
||||
HALF_OPEN,
|
||||
|
||||
/**
|
||||
* 舱门状态异常
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in New Issue