package com.zy.core.utils.station;
|
|
import com.alibaba.fastjson.JSONObject;
|
import com.zy.asrs.domain.path.StationPathResolvedPolicy;
|
import com.zy.asrs.domain.vo.StationCycleCapacityVo;
|
import com.zy.asrs.domain.vo.StationCycleLoopVo;
|
import com.zy.asrs.entity.BasDevp;
|
import com.zy.asrs.entity.WrkMast;
|
import com.zy.asrs.service.BasDevpService;
|
import com.zy.asrs.service.StationCycleCapacityService;
|
import com.zy.asrs.service.StationPathPolicyService;
|
import com.zy.common.model.NavigateNode;
|
import com.zy.common.utils.NavigateUtils;
|
import com.zy.common.utils.RedisUtil;
|
import com.zy.core.News;
|
import com.zy.core.cache.SlaveConnection;
|
import com.zy.core.enums.RedisKeyType;
|
import com.zy.core.enums.SlaveType;
|
import com.zy.core.model.protocol.StationProtocol;
|
import com.zy.core.thread.StationThread;
|
import com.zy.core.utils.station.model.DispatchLimitConfig;
|
import com.zy.core.utils.station.model.LoadGuardState;
|
import com.zy.core.utils.station.model.LoopHitResult;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Component;
|
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
|
@Component
|
public class StationDispatchLoadSupport {
|
|
private static final int LOOP_LOAD_RESERVE_EXPIRE_SECONDS = 120;
|
|
@Autowired
|
private RedisUtil redisUtil;
|
@Autowired
|
private BasDevpService basDevpService;
|
@Autowired
|
private StationCycleCapacityService stationCycleCapacityService;
|
@Autowired
|
private StationPathPolicyService stationPathPolicyService;
|
@Autowired
|
private NavigateUtils navigateUtils;
|
|
public int countCurrentStationTask() {
|
int currentStationTaskCount = 0;
|
List<BasDevp> basDevps = basDevpService.list();
|
for (BasDevp basDevp : basDevps) {
|
StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basDevp.getDevpNo());
|
if (stationThread == null) {
|
continue;
|
}
|
|
for (StationProtocol stationProtocol : stationThread.getStatus()) {
|
if (stationProtocol.getTaskNo() > 0) {
|
currentStationTaskCount++;
|
}
|
}
|
}
|
return currentStationTaskCount;
|
}
|
|
public boolean isDispatchBlocked(DispatchLimitConfig config,
|
int currentStationTaskCount,
|
LoadGuardState loadGuardState,
|
boolean needReserveLoopLoad) {
|
if (config != null && config.isLoopModeEnable()) {
|
double currentLoad = loadGuardState.currentLoad();
|
if (currentLoad >= config.getCircleMaxLoadLimit()) {
|
News.warn("当前承载量达到上限,已停止站点任务下发。当前承载量={},上限={}", formatPercent(currentLoad), formatPercent(config.getCircleMaxLoadLimit()));
|
return true;
|
}
|
|
if (needReserveLoopLoad) {
|
double reserveLoad = loadGuardState.loadAfterReserve();
|
if (reserveLoad >= config.getCircleMaxLoadLimit()) {
|
News.warn("预占后承载量达到上限,已停止站点任务下发。预占后承载量={},上限={}", formatPercent(reserveLoad), formatPercent(config.getCircleMaxLoadLimit()));
|
return true;
|
}
|
}
|
}
|
return false;
|
}
|
|
public LoadGuardState buildLoadGuardState(DispatchLimitConfig config) {
|
LoadGuardState state = new LoadGuardState();
|
if (config == null || !config.isLoopModeEnable()) {
|
return state;
|
}
|
|
StationCycleCapacityVo capacityVo = stationCycleCapacityService.getLatestSnapshot();
|
if (capacityVo == null) {
|
return state;
|
}
|
|
Integer occupiedStationCount = capacityVo.getOccupiedStationCount();
|
state.setTotalStationCount(toNonNegative(capacityVo.getTotalStationCount()));
|
state.setProjectedTaskStationCount(toNonNegative(occupiedStationCount != null ? occupiedStationCount : capacityVo.getTaskStationCount()));
|
|
List<StationCycleLoopVo> loopList = capacityVo.getLoopList();
|
if (loopList != null) {
|
for (StationCycleLoopVo loopVo : loopList) {
|
if (loopVo == null || loopVo.getStationIdList() == null) {
|
continue;
|
}
|
Integer loopNo = loopVo.getLoopNo();
|
for (Integer stationId : loopVo.getStationIdList()) {
|
if (stationId != null && loopNo != null) {
|
state.putStationLoopNo(stationId, loopNo);
|
}
|
}
|
}
|
}
|
return state;
|
}
|
|
public LoopHitResult findPathLoopHit(DispatchLimitConfig config,
|
Integer sourceStationId,
|
Integer targetStationId,
|
LoadGuardState loadGuardState) {
|
return findPathLoopHit(config, sourceStationId, targetStationId, loadGuardState, null, null);
|
}
|
|
public LoopHitResult findPathLoopHit(DispatchLimitConfig config,
|
Integer sourceStationId,
|
Integer targetStationId,
|
LoadGuardState loadGuardState,
|
WrkMast wrkMast,
|
Double pathLenFactor) {
|
if (config == null || !config.isLoopModeEnable()) {
|
return LoopHitResult.noHit();
|
}
|
if (sourceStationId == null || targetStationId == null) {
|
return LoopHitResult.noHit();
|
}
|
if (loadGuardState == null || loadGuardState.getStationLoopNoMap().isEmpty()) {
|
return LoopHitResult.noHit();
|
}
|
|
try {
|
List<NavigateNode> nodes = wrkMast == null
|
? navigateUtils.calcByStationId(sourceStationId, targetStationId)
|
: calcOutboundNavigatePath(wrkMast, sourceStationId, targetStationId, pathLenFactor);
|
if (nodes == null || nodes.isEmpty()) {
|
return LoopHitResult.noHit();
|
}
|
|
for (NavigateNode node : nodes) {
|
Integer stationId = getStationIdFromNode(node);
|
if (stationId == null) {
|
continue;
|
}
|
Integer loopNo = loadGuardState.getStationLoopNoMap().get(stationId);
|
if (loopNo != null) {
|
return new LoopHitResult(true, loopNo, stationId);
|
}
|
}
|
} catch (Exception ignore) {
|
return LoopHitResult.noHit();
|
}
|
|
return LoopHitResult.noHit();
|
}
|
|
public void saveLoopLoadReserve(Integer wrkNo, LoopHitResult loopHitResult) {
|
if (wrkNo == null || wrkNo <= 0 || loopHitResult == null || !loopHitResult.isThroughLoop()) {
|
return;
|
}
|
JSONObject reserveJson = new JSONObject();
|
reserveJson.put("wrkNo", wrkNo);
|
reserveJson.put("loopNo", loopHitResult.getLoopNo());
|
reserveJson.put("hitStationId", loopHitResult.getHitStationId());
|
reserveJson.put("createTime", System.currentTimeMillis());
|
redisUtil.hset(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, String.valueOf(wrkNo), reserveJson.toJSONString());
|
redisUtil.expire(RedisKeyType.STATION_CYCLE_LOAD_RESERVE.key, LOOP_LOAD_RESERVE_EXPIRE_SECONDS);
|
}
|
|
public DispatchLimitConfig getDispatchLimitConfig(Integer startStationId, Integer endStationId) {
|
DispatchLimitConfig config = new DispatchLimitConfig();
|
Object systemConfigMapObj = redisUtil.get(RedisKeyType.SYSTEM_CONFIG_MAP.key);
|
if (systemConfigMapObj instanceof Map) {
|
Map<?, ?> systemConfigMap = (Map<?, ?>) systemConfigMapObj;
|
config.setCircleMaxLoadLimit(parseLoadLimit(getConfigValue(systemConfigMap, "circleMaxLoadLimit"), config.getCircleMaxLoadLimit()));
|
String loopModeValue = getConfigValue(systemConfigMap, "circleLoopModeEnable");
|
if (isBlank(loopModeValue)) {
|
loopModeValue = getConfigValue(systemConfigMap, "circleModeEnable");
|
}
|
if (isBlank(loopModeValue)) {
|
loopModeValue = getConfigValue(systemConfigMap, "isCircleMode");
|
}
|
config.setLoopModeEnable(parseBoolean(loopModeValue, config.isLoopModeEnable()));
|
}
|
|
if (stationPathPolicyService != null && startStationId != null && endStationId != null) {
|
try {
|
StationPathResolvedPolicy resolvedPolicy = stationPathPolicyService.resolvePolicy(startStationId, endStationId);
|
if (resolvedPolicy != null && resolvedPolicy.getProfileConfig() != null) {
|
config.setCircleMaxLoadLimit(parseLoadLimit(String.valueOf(resolvedPolicy.getProfileConfig().getCircleMaxLoadLimit()), config.getCircleMaxLoadLimit()));
|
}
|
} catch (Exception ignore) {
|
}
|
}
|
|
return config;
|
}
|
|
private List<NavigateNode> calcOutboundNavigatePath(WrkMast wrkMast,
|
Integer sourceStationId,
|
Integer targetStationId,
|
Double pathLenFactor) {
|
Double normalizedFactor = normalizePathLenFactor(pathLenFactor);
|
Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo();
|
if (currentTaskNo == null) {
|
return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor);
|
}
|
return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor);
|
}
|
|
private Integer getStationIdFromNode(NavigateNode node) {
|
if (node == null || isBlank(node.getNodeValue())) {
|
return null;
|
}
|
try {
|
JSONObject value = JSONObject.parseObject(node.getNodeValue());
|
return value == null ? null : value.getInteger("stationId");
|
} catch (Exception ignore) {
|
return null;
|
}
|
}
|
|
private int toNonNegative(Integer value) {
|
if (value == null || value < 0) {
|
return 0;
|
}
|
return value;
|
}
|
|
private Double normalizePathLenFactor(Double pathLenFactor) {
|
if (pathLenFactor == null || pathLenFactor < 0.0d) {
|
return 0.0d;
|
}
|
if (pathLenFactor > 1.0d) {
|
return 1.0d;
|
}
|
return pathLenFactor;
|
}
|
|
private String getConfigValue(Map<?, ?> configMap, String key) {
|
Object value = configMap.get(key);
|
return value == null ? null : String.valueOf(value).trim();
|
}
|
|
private boolean parseBoolean(String value, boolean defaultValue) {
|
if (isBlank(value)) {
|
return defaultValue;
|
}
|
String lowValue = value.toLowerCase(Locale.ROOT);
|
if ("y".equals(lowValue) || "yes".equals(lowValue) || "true".equals(lowValue)
|
|| "1".equals(lowValue) || "on".equals(lowValue)) {
|
return true;
|
}
|
if ("n".equals(lowValue) || "no".equals(lowValue) || "false".equals(lowValue)
|
|| "0".equals(lowValue) || "off".equals(lowValue)) {
|
return false;
|
}
|
return defaultValue;
|
}
|
|
private double parseLoadLimit(String value, double defaultValue) {
|
if (isBlank(value)) {
|
return defaultValue;
|
}
|
try {
|
String normalized = value.replace("%", "").trim();
|
double parsed = Double.parseDouble(normalized);
|
if (parsed > 1.0) {
|
parsed = parsed / 100.0;
|
}
|
if (parsed < 0.0) {
|
return 0.0;
|
}
|
if (parsed > 1.0) {
|
return 1.0;
|
}
|
return parsed;
|
} catch (Exception ignore) {
|
return defaultValue;
|
}
|
}
|
|
private String formatPercent(double value) {
|
return String.format(Locale.ROOT, "%.1f%%", value * 100.0);
|
}
|
|
private boolean isBlank(String value) {
|
return value == null || value.trim().isEmpty();
|
}
|
}
|