feat():跟踪逻辑调整
This commit is contained in:
@@ -1,33 +1,36 @@
|
|||||||
package com.fizz.business.constants.enums;
|
package com.fizz.business.constants.enums;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
/**
|
/**
|
||||||
* 细粒度设备枚举(按你给出的17台设备)
|
* 细粒度设备枚举
|
||||||
* position 为基准位置(单位:m),用于和 stripLocation 比较。
|
* - position: 基准位置(单位:m)
|
||||||
* paramFields 列出该设备需要采样/缓存的字段名(与 AppMeasure*Message 中字段名对应)。
|
* - sectionType: 所属段(入口 / 工艺 / 出口)
|
||||||
|
* - sourceType: 数据来源(ENTRY/FURNACE/COAT/EXIT)
|
||||||
|
* - paramFields: 对应AppMeasure*Message中的字段
|
||||||
*/
|
*/
|
||||||
|
@Getter
|
||||||
public enum DeviceEnum {
|
public enum DeviceEnum {
|
||||||
POR1(0, "1#开卷机", 0.0, Arrays.asList("tensionPorBr1", "stripSpeed")),
|
// === 入口段 ===
|
||||||
POR2(1, "2#开卷机", 0.0, Arrays.asList("tensionPorBr2", "stripSpeed")),
|
POR1(0, "1#开卷机", 0.0, SectionType.ENTRY, SourceType.ENTRY, Arrays.asList("tensionPorBr1", "stripSpeed")),
|
||||||
WELDER(2, "焊机", 16.433, Arrays.asList("weldStatus")),
|
POR2(1, "2#开卷机", 0.0, SectionType.ENTRY, SourceType.ENTRY, Arrays.asList("tensionPorBr2", "stripSpeed")),
|
||||||
ENL1(3, "入口活套1", 58.987, Arrays.asList("celLength", "celCapacity", "tensionCel")),
|
WELDER(2, "焊机", 16.433, SectionType.ENTRY, SourceType.ENTRY, Arrays.asList("weldStatus")),
|
||||||
ENL2(4, "入口活套2", 118.518, Arrays.asList("celLength", "celCapacity", "tensionCel")),
|
ENL1(3, "入口活套1", 75.42, SectionType.PROCESS, SourceType.ENTRY, Arrays.asList("celLength", "celCapacity", "tensionCel")),
|
||||||
CLEAN(5, "清洗段", 97.607, Arrays.asList("cleaningVoltage", "cleaningCurrent", "alkaliConcentration", "alkaliTemperature")),
|
ENL2(4, "入口活套2", 193.94, SectionType.PROCESS, SourceType.ENTRY, Arrays.asList("celLength", "celCapacity", "tensionCel")),
|
||||||
FUR1(6, "退火炉-预热段", 63.013, Arrays.asList("phfExitStripTemp","potTemperature","gasConsumption")),
|
|
||||||
FUR2(7, "退火炉-加热段", 72.01, Arrays.asList("rtfExitStripTemp","zincPotPower")),
|
// === 工艺段 ===
|
||||||
FUR3(8, "退火炉-冷却段", 30.71, Arrays.asList("jcsExitStripTemp","coolingTowerStripTemp")),
|
CLEAN(5, "清洗段", 291.545, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("cleaningVoltage", "cleaningCurrent", "alkaliConcentration", "alkaliTemperature")),
|
||||||
FUR4(9, "退火炉-均衡段", 14.607, Arrays.asList("scsExitStripTemp")),
|
FUR1(6, "退火炉-预热段", 354.558, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("phfExitStripTemp", "potTemperature", "gasConsumption")),
|
||||||
POT(10, "锌锅", 10.373, Arrays.asList("scsExitStripTemp")),
|
FUR2(7, "退火炉-加热段", 426.568, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("rtfExitStripTemp", "zincPotPower")),
|
||||||
TOWER(11, "后处理", 144.412, Arrays.asList("scsExitStripTemp")),
|
FUR3(8, "退火炉-冷却段", 457.278, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("jcsExitStripTemp", "coolingTowerStripTemp")),
|
||||||
TM(11, "光整机", 26.527, Arrays.asList("tensionBr5Tm", "stripSpeedTmExit")),
|
FUR4(9, "退火炉-均衡段", 471.885, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("scsExitStripTemp")),
|
||||||
TL(12, "拉矫机", 15.027, Arrays.asList("tlElongation", "tensionTlBr7")),
|
POT(10, "锌锅", 482.258, SectionType.PROCESS, SourceType.COAT, Arrays.asList("scsExitStripTemp")),
|
||||||
COAT(13, "后处理段", 145.5, Arrays.asList(
|
TOWER(11, "冷却塔", 626.67, SectionType.PROCESS, SourceType.COAT, Arrays.asList("scsExitStripTemp")),
|
||||||
|
TM(12, "光整机", 653.197, SectionType.PROCESS, SourceType.COAT, Arrays.asList("tensionBr5Tm", "stripSpeedTmExit")),
|
||||||
|
TL(13, "拉矫机", 668.224, SectionType.PROCESS, SourceType.COAT, Arrays.asList("tlElongation", "tensionTlBr7")),
|
||||||
|
COAT(14, "后处理段", 813.724, SectionType.PROCESS, SourceType.COAT, Arrays.asList(
|
||||||
"avrCoatingWeightTop","stdCoatingWeightTop","maxCoatingWeightTop","minCoatingWeightTop",
|
"avrCoatingWeightTop","stdCoatingWeightTop","maxCoatingWeightTop","minCoatingWeightTop",
|
||||||
"avrCoatingWeightBottom","stdCoatingWeightBottom","maxCoatingWeightBottom","minCoatingWeightBottom",
|
"avrCoatingWeightBottom","stdCoatingWeightBottom","maxCoatingWeightBottom","minCoatingWeightBottom",
|
||||||
"airKnifePressure","airKnifeFlow","airKnifeGap","stripSpeedTmExit","tensionBr5Tm",
|
"airKnifePressure","airKnifeFlow","airKnifeGap","stripSpeedTmExit","tensionBr5Tm",
|
||||||
@@ -36,21 +39,64 @@ public enum DeviceEnum {
|
|||||||
"tensionTlBr7","tensionBr6Br7","tlFlag","tlElongation","levelingUnit1Mesh","levelingUnit2Mesh",
|
"tensionTlBr7","tensionBr6Br7","tlFlag","tlElongation","levelingUnit1Mesh","levelingUnit2Mesh",
|
||||||
"antiCrossBowUnitMesh","tensionBr7Br8","stripSpeedAfp","stripTempAfp"
|
"antiCrossBowUnitMesh","tensionBr7Br8","stripSpeedAfp","stripTempAfp"
|
||||||
)),
|
)),
|
||||||
CXL1(14, "出口活套1", 1, Arrays.asList("cxlLength", "cxlCapacity", "tensionCxl")),
|
|
||||||
CXL2(15, "出口活套2", 126.837, Arrays.asList("cxlLength", "cxlCapacity", "tensionCxl")),
|
// === 出口段 ===
|
||||||
INS(16, "检查站", 0, Arrays.asList("inspectionStatus")),
|
CXL1(15, "出口活套1", 900.561, SectionType.EXIT, SourceType.EXIT, Arrays.asList("cxlLength", "cxlCapacity", "tensionCxl")),
|
||||||
TR(17, "卷取机", 21.549, Arrays.asList("coilLength", "speedExitSection", "tensionBr9Tr")),
|
CXL2(16, "出口活套2", 920.561, SectionType.EXIT, SourceType.EXIT, Arrays.asList("cxlLength", "cxlCapacity", "tensionCxl")),
|
||||||
EXC(18, "卸卷小车", 999999.0, Collections.emptyList()),
|
INS(17, "检查站", 940.561, SectionType.EXIT, SourceType.EXIT, Arrays.asList("inspectionStatus")),
|
||||||
WEIGHT(19, "称重鞍座", 999999.0, Collections.emptyList());
|
TR(18, "卷取机", 962.11, SectionType.EXIT, SourceType.EXIT, Arrays.asList("coilLength", "speedExitSection", "tensionBr9Tr")),
|
||||||
|
EXC(19, "卸卷小车", 9999999.0, SectionType.EXIT, SourceType.EXIT, Collections.emptyList()),
|
||||||
|
WEIGHT(20, "称重鞍座", 9999999.0, SectionType.EXIT, SourceType.EXIT, Collections.emptyList());
|
||||||
|
|
||||||
private final int idx;
|
private final int idx;
|
||||||
private final String desc;
|
private final String desc;
|
||||||
private final double basePosition;
|
private final double basePosition;
|
||||||
|
private final SectionType sectionType;
|
||||||
|
private final SourceType sourceType;
|
||||||
private final List<String> paramFields;
|
private final List<String> paramFields;
|
||||||
|
|
||||||
private static final Map<String, DeviceEnum> NAME_MAP = new HashMap<>();
|
private static final Map<String, DeviceEnum> NAME_MAP = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (DeviceEnum d : values()) NAME_MAP.put(d.name(), d);
|
for (DeviceEnum d : values()) NAME_MAP.put(d.name(), d);
|
||||||
}
|
}
|
||||||
public static DeviceEnum fromName(String name) { return NAME_MAP.get(name); }
|
|
||||||
}
|
DeviceEnum(int idx, String desc, double basePosition, SectionType sectionType, SourceType sourceType, List<String> paramFields) {
|
||||||
|
this.idx = idx;
|
||||||
|
this.desc = desc;
|
||||||
|
this.basePosition = basePosition;
|
||||||
|
this.sectionType = sectionType;
|
||||||
|
this.sourceType = sourceType;
|
||||||
|
this.paramFields = paramFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DeviceEnum fromName(String name) {
|
||||||
|
return NAME_MAP.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEntrySection() {
|
||||||
|
return sectionType == SectionType.ENTRY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProcessSection() {
|
||||||
|
return sectionType == SectionType.PROCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isExitSection() {
|
||||||
|
return sectionType == SectionType.EXIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 段类型:用于区分入口 / 工艺 / 出口
|
||||||
|
*/
|
||||||
|
public enum SectionType {
|
||||||
|
ENTRY, PROCESS, EXIT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源类型:用于确定从哪个 AppMeasureXXXMessage 中取值
|
||||||
|
*/
|
||||||
|
public enum SourceType {
|
||||||
|
ENTRY, FURNACE, COAT, EXIT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +1,34 @@
|
|||||||
package com.fizz.business.service.hanle;
|
package com.fizz.business.service.hanle;
|
||||||
|
|
||||||
import com.fizz.business.anno.OpcMessageHandlerType;
|
import com.fizz.business.anno.OpcMessageHandlerType;
|
||||||
import com.fizz.business.constants.enums.DeviceEnum;
|
|
||||||
import com.fizz.business.constants.enums.OpcMessageType;
|
import com.fizz.business.constants.enums.OpcMessageType;
|
||||||
import com.fizz.business.domain.msg.*;
|
import com.fizz.business.domain.msg.*;
|
||||||
|
import com.fizz.business.dto.MatmapDTO;
|
||||||
|
import com.fizz.business.service.LogDataService;
|
||||||
import com.fizz.business.service.OpcMessageHandler;
|
import com.fizz.business.service.OpcMessageHandler;
|
||||||
import com.fizz.business.service.strip.SegmentTrackerService;
|
import com.fizz.business.service.strip.SegmentTrackerService;
|
||||||
import com.fizz.business.service.strip.StripPositionService;
|
import com.fizz.business.utils.MatmapUtil;
|
||||||
import com.fizz.business.utils.WebSocketUtil;
|
import com.fizz.business.utils.WebSocketUtil;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@Component
|
|
||||||
@OpcMessageHandlerType(OpcMessageType.APP_MEASURE)
|
@OpcMessageHandlerType(OpcMessageType.APP_MEASURE)
|
||||||
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class AppMeasureHandler implements OpcMessageHandler<AppMeasureMessage> {
|
public class AppMeasureHandler implements OpcMessageHandler<AppMeasureMessage> {
|
||||||
|
|
||||||
private final SegmentTrackerService tracker;
|
private final SegmentTrackerService tracker;
|
||||||
|
private final LogDataService logDataService;
|
||||||
|
private final Executor coilTrackExecutor; // 注入线程池接口
|
||||||
|
|
||||||
|
private static final AtomicInteger LOG_COUNTER = new AtomicInteger(0);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(AppMeasureMessage message) {
|
public void handle(AppMeasureMessage message) {
|
||||||
@@ -31,15 +41,51 @@ public class AppMeasureHandler implements OpcMessageHandler<AppMeasureMessage> {
|
|||||||
|
|
||||||
WebSocketUtil.sendMeasureMsg(message);
|
WebSocketUtil.sendMeasureMsg(message);
|
||||||
|
|
||||||
// 2) extract key fields
|
// 处理钢卷ID为空的情况
|
||||||
String coilId = entry == null ? "UNKNOWN" : entry.getEntryCoilId();
|
String coilId ="";
|
||||||
|
if (entry != null && entry.getEntryCoilId() != null){
|
||||||
|
coilId = entry.getEntryCoilId();
|
||||||
|
|
||||||
|
}else {
|
||||||
|
MatmapDTO matmapDTO =getCurrentRunningCoilId(entry);
|
||||||
|
coilId = matmapDTO.getMatId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(coilId) || "".equals(coilId)){
|
||||||
|
log.error("钢卷号为空,无法执行焊缝跟踪!!!!!!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
BigDecimal lengthAtWelder = entry == null || entry.getStripLocation() == null ? BigDecimal.ZERO : entry.getStripLocation();
|
BigDecimal lengthAtWelder = entry == null || entry.getStripLocation() == null ? BigDecimal.ZERO : entry.getStripLocation();
|
||||||
|
|
||||||
// 3) delegate core processing to tracker
|
// 每10次请求输出一次日志
|
||||||
|
int count = LOG_COUNTER.incrementAndGet();
|
||||||
|
if (count % 10 == 0) {
|
||||||
|
logDataService.logInfo("WELDER",
|
||||||
|
"接收到钢卷焊缝跟踪信号:当前焊缝长度 lengthAtWelder:{} 当前钢卷信息 coilId:{}",
|
||||||
|
lengthAtWelder, coilId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 核心逻辑处理
|
||||||
tracker.handleMeasure(coilId, lengthAtWelder, entry, furnace, coat, exit);
|
tracker.handleMeasure(coilId, lengthAtWelder, entry, furnace, coat, exit);
|
||||||
|
|
||||||
// 调用新的方法来跟踪钢卷头部位置,并处理快照和 matmap 更新
|
// 异步执行 trackCoilHeadPosition
|
||||||
// 使用焊机长度作为最接近的钢卷头部位置
|
// coilTrackExecutor.execute(() -> {
|
||||||
tracker.trackCoilHeadPosition(coilId, lengthAtWelder, entry, exit);
|
// try {
|
||||||
|
// log.warn(">>> trackCoilHeadPosition start, thread: {}, coilId: {}",
|
||||||
|
// Thread.currentThread().getName(), coilId);
|
||||||
|
//
|
||||||
|
// tracker.trackCoilHeadPosition(coilId, lengthAtWelder, entry, exit);
|
||||||
|
//
|
||||||
|
// } catch (Exception e) {
|
||||||
|
// log.error("trackCoilHeadPosition 执行异常,coilId=" + coilId, e);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private MatmapDTO getCurrentRunningCoilId(AppMeasureEntryMessage entry) {
|
||||||
|
|
||||||
|
return MatmapUtil.getMatmap(entry.getPayOffReelNumber());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package com.fizz.business.service.hanle;
|
||||||
|
|
||||||
|
public class CoilAsyncService {
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.fizz.business.service.hanle;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
|
||||||
|
@Configuration("coilTrackThreadPoolConfig")
|
||||||
|
public class CoilTrackThreadPoolConfig {
|
||||||
|
|
||||||
|
@Bean("coilTrackExecutor")
|
||||||
|
public ThreadPoolTaskExecutor coilTrackExecutor() {
|
||||||
|
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||||
|
executor.setCorePoolSize(10); // 核心线程数
|
||||||
|
executor.setMaxPoolSize(50); // 最大线程数
|
||||||
|
executor.setQueueCapacity(2000); // 队列容量
|
||||||
|
executor.setKeepAliveSeconds(60); // 空闲线程存活时间
|
||||||
|
executor.setThreadNamePrefix("CoilTrackPool-");
|
||||||
|
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); // 队列满策略
|
||||||
|
executor.initialize();
|
||||||
|
return executor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,29 +4,40 @@ import com.fizz.business.anno.OpcMessageHandlerType;
|
|||||||
import com.fizz.business.constants.enums.OpcMessageType;
|
import com.fizz.business.constants.enums.OpcMessageType;
|
||||||
import com.fizz.business.domain.msg.OpcMessage;
|
import com.fizz.business.domain.msg.OpcMessage;
|
||||||
import com.fizz.business.service.OpcMessageHandler;
|
import com.fizz.business.service.OpcMessageHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@Slf4j
|
||||||
public class OpcMessageDispatcher {
|
public class OpcMessageDispatcher {
|
||||||
|
|
||||||
private final Map<OpcMessageType, OpcMessageHandler<? extends OpcMessage>> handlerMap = new HashMap<>();
|
private final Map<OpcMessageType, OpcMessageHandler<? extends OpcMessage>> handlerMap = new HashMap<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public OpcMessageDispatcher(ApplicationContext context) {
|
public OpcMessageDispatcher(ApplicationContext context) {
|
||||||
Map<String, OpcMessageHandler> beans = context.getBeansOfType(OpcMessageHandler.class);
|
|
||||||
beans.values().forEach(handler -> {
|
Collection<OpcMessageHandler> handlers = context.getBeansOfType(OpcMessageHandler.class).values();
|
||||||
OpcMessageHandlerType annotation = handler.getClass().getAnnotation(OpcMessageHandlerType.class);
|
|
||||||
if (annotation != null) {
|
for (OpcMessageHandler handler : handlers) {
|
||||||
handlerMap.put(annotation.value(), handler);
|
|
||||||
} else {
|
// ⭐⭐ 从代理类中获取注解的正确方式
|
||||||
throw new IllegalStateException("Handler " + handler.getClass().getSimpleName() + " 未标注 @OpcMessageHandlerType");
|
OpcMessageHandlerType annotation =
|
||||||
|
AnnotationUtils.findAnnotation(handler.getClass(), OpcMessageHandlerType.class);
|
||||||
|
|
||||||
|
if (annotation == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
handler.getClass().getSimpleName() + " 未标注 @OpcMessageHandlerType");
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
handlerMap.put(annotation.value(), handler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -35,7 +46,7 @@ public class OpcMessageDispatcher {
|
|||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.handle(message);
|
handler.handle(message);
|
||||||
} else {
|
} else {
|
||||||
System.out.println("未注册的 OPC 消息类型: " + type);
|
log.warn("未注册的 OPC 消息类型: {}", type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||||||
import com.fizz.business.dto.SegmentDTO;
|
import com.fizz.business.dto.SegmentDTO;
|
||||||
import com.fizz.business.mapper.SegmentMapper;
|
import com.fizz.business.mapper.SegmentMapper;
|
||||||
import com.fizz.business.vo.SegmentParamVO;
|
import com.fizz.business.vo.SegmentParamVO;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
@@ -15,6 +16,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class SegmentService {
|
public class SegmentService {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@@ -29,7 +31,7 @@ public class SegmentService {
|
|||||||
*/
|
*/
|
||||||
public void saveTotalSegment(SegmentDTO segment) {
|
public void saveTotalSegment(SegmentDTO segment) {
|
||||||
if (segment == null || segment.getEnCoilID() == null || segment.getEnCoilID().isEmpty()) {
|
if (segment == null || segment.getEnCoilID() == null || segment.getEnCoilID().isEmpty()) {
|
||||||
System.err.println("警告: Coil ID 为空,无法保存数据。");
|
log.warn("警告: Coil ID 为空,无法保存数据。");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ public class SegmentService {
|
|||||||
String jsonString = objectMapper.writeValueAsString(totalValuesAvg);
|
String jsonString = objectMapper.writeValueAsString(totalValuesAvg);
|
||||||
segment.setTotalValuesJson(jsonString);
|
segment.setTotalValuesJson(jsonString);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (JsonProcessingException e) {
|
||||||
System.err.println("转换 JSON 失败: " + e.getMessage());
|
log.warn("转换 JSON 失败: " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ public class SegmentService {
|
|||||||
// 3. 执行插入操作,MyBatis-Plus 会自动处理 totalValuesJson 字段的保存
|
// 3. 执行插入操作,MyBatis-Plus 会自动处理 totalValuesJson 字段的保存
|
||||||
segmentMapper.insert(segment);
|
segmentMapper.insert(segment);
|
||||||
|
|
||||||
System.out.println("段数据保存成功。钢卷号: " + segment.getEnCoilID() + ", 段号: " + segment.getSegNo());
|
log.info("段数据保存成功。钢卷号: " + segment.getEnCoilID() + ", 段号: " + segment.getSegNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<SegmentParamVO> queryParamByEnCoilId(String enCoilID, String paramField) {
|
public List<SegmentParamVO> queryParamByEnCoilId(String enCoilID, String paramField) {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.fizz.business.service.strip;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
public class LogRateLimiter {
|
||||||
|
private static final Map<String, Long> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static boolean shouldLog(String key, long intervalMillis) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
Long last = cache.get(key);
|
||||||
|
if (last == null || now - last > intervalMillis) {
|
||||||
|
cache.put(key, now);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.fizz.business.service.strip;
|
package com.fizz.business.service.strip;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.fizz.business.constants.enums.DeviceEnum;
|
import com.fizz.business.constants.enums.DeviceEnum;
|
||||||
import com.fizz.business.constants.enums.L1OperateMatEnum;
|
import com.fizz.business.constants.enums.L1OperateMatEnum;
|
||||||
import com.fizz.business.domain.msg.AppMeasureCoatMessage;
|
import com.fizz.business.domain.msg.AppMeasureCoatMessage;
|
||||||
@@ -10,11 +11,12 @@ import com.fizz.business.dto.MatmapDTO;
|
|||||||
import com.fizz.business.dto.SegValue;
|
import com.fizz.business.dto.SegValue;
|
||||||
import com.fizz.business.dto.SegmentDTO;
|
import com.fizz.business.dto.SegmentDTO;
|
||||||
import com.fizz.business.form.L1OperateMatForm;
|
import com.fizz.business.form.L1OperateMatForm;
|
||||||
|
import com.fizz.business.service.LogDataService;
|
||||||
|
import com.fizz.business.service.ProMatmapService;
|
||||||
import com.fizz.business.service.TrackService;
|
import com.fizz.business.service.TrackService;
|
||||||
import com.fizz.business.service.impl.SegmentService;
|
import com.fizz.business.service.impl.SegmentService;
|
||||||
import com.fizz.business.utils.MatmapUtil;
|
import com.fizz.business.utils.MatmapUtil;
|
||||||
import com.fizz.business.utils.WebSocketUtil;
|
import com.fizz.business.utils.WebSocketUtil;
|
||||||
import com.fizz.business.vo.SegmentParamVO;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
@@ -27,8 +29,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import static com.fizz.business.constants.enums.DeviceEnum.WELDER;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -37,7 +37,10 @@ public class SegmentTrackerService {
|
|||||||
private final StripPositionService stripPositionService;
|
private final StripPositionService stripPositionService;
|
||||||
private final TrackService trackService;
|
private final TrackService trackService;
|
||||||
private final SegmentService segmentService; // 注入新创建的段服务
|
private final SegmentService segmentService; // 注入新创建的段服务
|
||||||
|
private final LogDataService logDataService; // 注入新创建的段服务
|
||||||
|
private final ProMatmapService proMatmapService; // 注入新创建的段服务
|
||||||
|
|
||||||
|
private static final long intervalTime = 10000; // 每 10 秒写一次
|
||||||
private int traceCount = 0;
|
private int traceCount = 0;
|
||||||
private boolean firstMeasure = true;
|
private boolean firstMeasure = true;
|
||||||
private BigDecimal weldLength = BigDecimal.ZERO;
|
private BigDecimal weldLength = BigDecimal.ZERO;
|
||||||
@@ -49,6 +52,15 @@ public class SegmentTrackerService {
|
|||||||
private final ConcurrentMap<String, Set<DeviceEnum>> coilReachedDevices = new ConcurrentHashMap<>();
|
private final ConcurrentMap<String, Set<DeviceEnum>> coilReachedDevices = new ConcurrentHashMap<>();
|
||||||
private static final double LOWSPEEDLIMIT = 0.05;
|
private static final double LOWSPEEDLIMIT = 0.05;
|
||||||
|
|
||||||
|
// 定义一个缓存记录上次写入日志的时间
|
||||||
|
private final Map<String, Long> lastLogTimeMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// --- 定义一个缓存,用于控制同一 CoilId 新卷日志写入频率 ---
|
||||||
|
private final Map<String, Long> lastNewCoilLogTimeMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// 放在类的字段中,全局共享
|
||||||
|
private final Map<String, Long> lastTrackLogTimeMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 【主入口】
|
* 【主入口】
|
||||||
* 负责处理新的测量数据,生成新段,并对新到达的设备做一次性快照。
|
* 负责处理新的测量数据,生成新段,并对新到达的设备做一次性快照。
|
||||||
@@ -60,52 +72,65 @@ public class SegmentTrackerService {
|
|||||||
AppMeasureCoatMessage coat,
|
AppMeasureCoatMessage coat,
|
||||||
AppMeasureExitMessage exit) {
|
AppMeasureExitMessage exit) {
|
||||||
|
|
||||||
// 1. 首次测量初始化或新卷判定
|
long now = System.currentTimeMillis();
|
||||||
|
Long lastTime = lastLogTimeMap.get(coilId);
|
||||||
|
if (lastTime == null || now - lastTime > intervalTime) {
|
||||||
|
lastLogTimeMap.put(coilId, now);
|
||||||
|
log.info("【TRACK-START】接收到测量数据 -> CoilId: {}, LengthAtWelder: {}", coilId, entryLengthAtWelder);
|
||||||
|
logDataService.logInfo("WELDER", "接收到测量数据 -> CoilId: " + coilId + ", LengthAtWelder: " + entryLengthAtWelder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. 新卷或首次测量初始化
|
||||||
BigDecimal weldDev;
|
BigDecimal weldDev;
|
||||||
if (firstMeasure || entryLengthAtWelder.compareTo(weldLength) < 0) {
|
if (firstMeasure || entryLengthAtWelder.compareTo(weldLength) < 0) {
|
||||||
weldDev = entryLengthAtWelder; // 新卷偏移量等于当前长度
|
weldDev = entryLengthAtWelder;
|
||||||
coilSegNum = 1;
|
coilSegNum = 1;
|
||||||
weldLength = entryLengthAtWelder;
|
weldLength = entryLengthAtWelder;
|
||||||
firstMeasure = false;
|
firstMeasure = false;
|
||||||
listSegment.clear(); // 清空旧段
|
listSegment.clear();
|
||||||
coilReachedDevices.remove(coilId); // 清空旧钢卷的快照记录
|
coilReachedDevices.remove(coilId);
|
||||||
log.info("New coil detected or initialized:{} " ,coilId);
|
|
||||||
|
long now1 = System.currentTimeMillis();
|
||||||
|
Long lastTime1 = lastNewCoilLogTimeMap.get(coilId);
|
||||||
|
if (lastTime1 == null || now1 - lastTime1 > intervalTime) {
|
||||||
|
lastNewCoilLogTimeMap.put(coilId, now1);
|
||||||
|
log.info("【WELDER】检测到新钢卷或初始化 -> CoilId: {}", coilId);
|
||||||
|
logDataService.logInfo("WELDER", "检测到新钢卷或初始化 -> CoilId: " + coilId);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
weldDev = entryLengthAtWelder.subtract(weldLength);
|
weldDev = entryLengthAtWelder.subtract(weldLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新焊机长度
|
|
||||||
weldLength = entryLengthAtWelder;
|
weldLength = entryLengthAtWelder;
|
||||||
|
|
||||||
// trace 控制
|
// 2. 新段生成逻辑
|
||||||
traceCount++;
|
|
||||||
if (traceCount > 5) {
|
|
||||||
log.info(String.format("Trace: weldLen=%.2f, speed=%s",
|
|
||||||
entryLengthAtWelder, procStripSpeed(entry, furnace, exit)));
|
|
||||||
traceCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 2. 检查是否达到生成新段的条件
|
|
||||||
BigDecimal segThreshold = BigDecimal.valueOf(coilSegNum * SEGSTRIPLEN);
|
BigDecimal segThreshold = BigDecimal.valueOf(coilSegNum * SEGSTRIPLEN);
|
||||||
if (entryLengthAtWelder.compareTo(segThreshold) >= 0) {
|
if (entryLengthAtWelder.compareTo(segThreshold) >= 0) {
|
||||||
|
|
||||||
// 2.1. 创建新的 SegmentDTO 并初始化基本位置
|
|
||||||
SegmentDTO newSeg = createNewSegment(coilId, entryLengthAtWelder);
|
SegmentDTO newSeg = createNewSegment(coilId, entryLengthAtWelder);
|
||||||
|
|
||||||
// 2.3. 将新段添加到队列中
|
|
||||||
listSegment.addLast(newSeg);
|
listSegment.addLast(newSeg);
|
||||||
if (listSegment.size() > MAX_SEG_COUNT) {
|
if (listSegment.size() > MAX_SEG_COUNT) listSegment.removeFirst();
|
||||||
listSegment.removeFirst();
|
log.info("【TRACK】新段生成 -> CoilId: {}, SegmentNo: {}, StartLen: {}", coilId, coilSegNum, entryLengthAtWelder);
|
||||||
}
|
logDataService.logInfo("WELDER", "新段生成 -> CoilId: " + coilId + ", SegmentNo: " + coilSegNum + ", StartLen: " + entryLengthAtWelder);
|
||||||
coilSegNum++;
|
coilSegNum++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 处理队列中所有已存在的段
|
// 3. 更新已有段
|
||||||
treatSegAsync(entry, furnace, coat, exit, weldDev);
|
treatSeg(entry, furnace, coat, exit, weldDev);
|
||||||
|
|
||||||
|
// 4. trackCoilHeadPosition 同步执行
|
||||||
|
try {
|
||||||
|
log.warn(">>> trackCoilHeadPosition start, thread: {}, coilId: {}", Thread.currentThread().getName(), coilId);
|
||||||
|
trackCoilHeadPosition(coilId, entryLengthAtWelder, entry, exit);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("trackCoilHeadPosition 执行异常,coilId=" + coilId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 完成日志
|
||||||
|
log.info("【TRACK-END】CoilId: {}, 当前长度: {}, 已生成段数: {}", coilId, entryLengthAtWelder, coilSegNum);
|
||||||
|
logDataService.logInfo("TRACK", "处理完成 -> CoilId: " + coilId + ", 当前长度: " + entryLengthAtWelder + ", 已生成段数: " + coilSegNum);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 【核心功能】
|
* 【核心功能】
|
||||||
* 遍历所有已存在的段,更新其位置,并累积其在设备区域内的实时数据。
|
* 遍历所有已存在的段,更新其位置,并累积其在设备区域内的实时数据。
|
||||||
@@ -117,12 +142,19 @@ public class SegmentTrackerService {
|
|||||||
AppMeasureExitMessage exit,
|
AppMeasureExitMessage exit,
|
||||||
BigDecimal weldDev) {
|
BigDecimal weldDev) {
|
||||||
if (listSegment.isEmpty()) {
|
if (listSegment.isEmpty()) {
|
||||||
|
// === 【处理开始日志】===
|
||||||
|
log.warn("【TRACK-TREAT】,listSegment为空,直接返回");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal celLength = entry != null ? entry.getCelLength() : BigDecimal.ZERO;
|
BigDecimal celLength = entry != null ? entry.getCelLength() : BigDecimal.ZERO;
|
||||||
BigDecimal cxlLength = exit != null ? exit.getCxlLength() : BigDecimal.ZERO;
|
BigDecimal cxlLength = exit != null ? exit.getCxlLength() : BigDecimal.ZERO;
|
||||||
|
|
||||||
|
// === 【处理开始日志】===
|
||||||
|
log.info("【TRACK-TREAT】开始处理段数据,共 {} 段,weldDev={},celLength = {},cxlLength = {}。 ", listSegment.size(), weldDev, celLength, cxlLength);
|
||||||
|
logDataService.logInfo("TRACK", "开始处理段数据,共 " + listSegment.size() + " 段,weldDev=" + weldDev);
|
||||||
|
|
||||||
Iterator<SegmentDTO> iterator = listSegment.descendingIterator();
|
Iterator<SegmentDTO> iterator = listSegment.descendingIterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
SegmentDTO segment = iterator.next();
|
SegmentDTO segment = iterator.next();
|
||||||
@@ -139,8 +171,14 @@ public class SegmentTrackerService {
|
|||||||
if (segment.getHeadPos().compareTo(BigDecimal.valueOf(currentDevicePos)) > 0 &&
|
if (segment.getHeadPos().compareTo(BigDecimal.valueOf(currentDevicePos)) > 0 &&
|
||||||
segment.getTailPos().compareTo(BigDecimal.valueOf(currentDevicePos)) < 0) {
|
segment.getTailPos().compareTo(BigDecimal.valueOf(currentDevicePos)) < 0) {
|
||||||
|
|
||||||
|
log.info("【TRACK-TREAT】段 {} 进入设备区域 [{}],当前设备长度:{}", segment.getSegNo(), device.name(), currentDevicePos);
|
||||||
|
logDataService.logInfo("TRACK",
|
||||||
|
"段 " + segment.getSegNo() + " 进入设备区域 [" + device.name() + "],当前设备长度:" + currentDevicePos);
|
||||||
double currentSpeed = getSpeedForDevice(device, entry, coat, exit);
|
double currentSpeed = getSpeedForDevice(device, entry, coat, exit);
|
||||||
if (currentSpeed > LOWSPEEDLIMIT) {
|
if (currentSpeed > LOWSPEEDLIMIT) {
|
||||||
|
log.info("【TRACK-TREAT】段 {} 速度大于基准速度, 进入设备区域 [{}],当前速度:{}", segment.getSegNo(), device.name(), currentSpeed);
|
||||||
|
logDataService.logInfo("TRACK",
|
||||||
|
"段 " + segment.getSegNo() + " 进入设备区域 [" + device.name() + "],当前速度:" + currentSpeed);
|
||||||
for (String fieldName : device.getParamFields()) {
|
for (String fieldName : device.getParamFields()) {
|
||||||
Object message = getMessageForDevice(device, entry, furnace, coat, exit);
|
Object message = getMessageForDevice(device, entry, furnace, coat, exit);
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
@@ -155,7 +193,10 @@ public class SegmentTrackerService {
|
|||||||
|
|
||||||
double exitPlantPos = stripPositionService.calculate(DeviceEnum.TR, celLength, cxlLength);
|
double exitPlantPos = stripPositionService.calculate(DeviceEnum.TR, celLength, cxlLength);
|
||||||
if (segment.getTailPos().compareTo(BigDecimal.valueOf(exitPlantPos)) >= 0) {
|
if (segment.getTailPos().compareTo(BigDecimal.valueOf(exitPlantPos)) >= 0) {
|
||||||
System.out.println("钢卷 " + segment.getEnCoilID() + " 的段号 " + segment.getSegNo() + " 已离开产线,开始持久化数据。");
|
log.info("【TRACK-END】钢卷 {} 的段号 {} 已离开产线,开始持久化数据{}。",
|
||||||
|
segment.getEnCoilID(), segment.getSegNo(), JSONUtil.toJsonStr(segment.getTotalValues()));
|
||||||
|
logDataService.logInfo("SEGMENT",
|
||||||
|
"钢卷 " + segment.getEnCoilID() + " 的段号 " + segment.getSegNo() + " 已离开产线,开始持久化数据。");
|
||||||
segmentService.saveTotalSegment(segment); // 调用服务进行持久化
|
segmentService.saveTotalSegment(segment); // 调用服务进行持久化
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
@@ -163,22 +204,22 @@ public class SegmentTrackerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- 新增的辅助方法,将逻辑封装起来 ---
|
// --- 新增的辅助方法,将逻辑封装起来 ---
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 【异步方法】
|
* 【异步方法】
|
||||||
* 异步处理队列中所有已存在的段,更新其位置和累积数据。
|
* 异步处理队列中所有已存在的段,更新其位置和累积数据。
|
||||||
*/
|
*/
|
||||||
@Async("taskExecutor") // 指定使用名为 "taskExecutor" 的线程池
|
@Async("taskExecutor") // 指定使用名为 "taskExecutor" 的线程池
|
||||||
public void treatSegAsync(AppMeasureEntryMessage entry,
|
public void treatSegAsync(AppMeasureEntryMessage entry,
|
||||||
AppMeasureFurnaceMessage furnace,
|
AppMeasureFurnaceMessage furnace,
|
||||||
AppMeasureCoatMessage coat,
|
AppMeasureCoatMessage coat,
|
||||||
AppMeasureExitMessage exit,
|
AppMeasureExitMessage exit,
|
||||||
BigDecimal weldDev) {
|
BigDecimal weldDev) {
|
||||||
// 调用原有的同步方法
|
// 调用原有的同步方法
|
||||||
this.treatSeg(entry, furnace, coat, exit, weldDev);
|
this.treatSeg(entry, furnace, coat, exit, weldDev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建并初始化一个新的 SegmentDTO。
|
* 创建并初始化一个新的 SegmentDTO。
|
||||||
*/
|
*/
|
||||||
@@ -208,30 +249,54 @@ public class SegmentTrackerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// --- 辅助方法,与消息处理和反射相关 ---
|
private Object getMessageForDevice(DeviceEnum device,
|
||||||
|
AppMeasureEntryMessage entry,
|
||||||
private Object getMessageForDevice(DeviceEnum device, AppMeasureEntryMessage entry, AppMeasureFurnaceMessage furnace, AppMeasureCoatMessage coat, AppMeasureExitMessage exit) {
|
AppMeasureFurnaceMessage furnace,
|
||||||
if (device.getDesc().contains("开卷机") || device.getDesc().contains("活套") || device.getDesc().contains("焊机")) {
|
AppMeasureCoatMessage coat,
|
||||||
return entry;
|
AppMeasureExitMessage exit) {
|
||||||
} else if (device.getDesc().contains("清洗段") || device.getDesc().contains("退火炉")) {
|
switch (device.getSourceType()) {
|
||||||
return furnace;
|
case ENTRY:
|
||||||
} else if (device.getDesc().contains("涂机")) {
|
return entry;
|
||||||
return coat;
|
case FURNACE:
|
||||||
} else {
|
return furnace;
|
||||||
return exit;
|
case COAT:
|
||||||
|
return coat;
|
||||||
|
case EXIT:
|
||||||
|
return exit;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getSpeedForDevice(DeviceEnum device, AppMeasureEntryMessage entry, AppMeasureCoatMessage coat, AppMeasureExitMessage exit) {
|
|
||||||
if (device.getDesc().contains("开卷机") || device.getDesc().contains("活套") || device.getDesc().contains("焊机")) {
|
private double getSpeedForDevice(DeviceEnum device,
|
||||||
return entry != null && entry.getStripSpeed() != null ? entry.getStripSpeed().doubleValue() : 0.0;
|
AppMeasureEntryMessage entry,
|
||||||
} else if (device.getDesc().contains("清洗段") || device.getDesc().contains("退火炉")) {
|
AppMeasureCoatMessage coat,
|
||||||
return coat != null && coat.getStripSpeedTmExit() != null ? coat.getStripSpeedTmExit().doubleValue() : 0.0;
|
AppMeasureExitMessage exit) {
|
||||||
} else {
|
switch (device.getSectionType()) {
|
||||||
return exit != null && exit.getSpeedExitSection() != null ? exit.getSpeedExitSection().doubleValue() : 0.0;
|
case ENTRY:
|
||||||
|
// 入口段设备:速度来自 entry
|
||||||
|
return entry != null && entry.getStripSpeed() != null
|
||||||
|
? entry.getStripSpeed().doubleValue()
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
case PROCESS:
|
||||||
|
|
||||||
|
return coat != null && coat.getStripSpeedTmExit() != null
|
||||||
|
? coat.getStripSpeedTmExit().doubleValue()
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
case EXIT:
|
||||||
|
// 出口段设备:速度来自 exit
|
||||||
|
return exit != null && exit.getSpeedExitSection() != null
|
||||||
|
? exit.getSpeedExitSection().doubleValue()
|
||||||
|
: 0.0;
|
||||||
|
default:
|
||||||
|
return 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private double procStripSpeed(AppMeasureEntryMessage entry, AppMeasureFurnaceMessage furnace, AppMeasureExitMessage exit) {
|
private double procStripSpeed(AppMeasureEntryMessage entry, AppMeasureFurnaceMessage furnace, AppMeasureExitMessage exit) {
|
||||||
if (entry != null && entry.getStripSpeed() != null) return entry.getStripSpeed().doubleValue();
|
if (entry != null && entry.getStripSpeed() != null) return entry.getStripSpeed().doubleValue();
|
||||||
// if (furnace != null && furnace.getStripSpeed() != null) return furnace.getStripSpeed().doubleValue();
|
// if (furnace != null && furnace.getStripSpeed() != null) return furnace.getStripSpeed().doubleValue();
|
||||||
@@ -265,48 +330,55 @@ public class SegmentTrackerService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 本地缓存,每个 coilId 对应已到达设备
|
||||||
* 【新方法】
|
|
||||||
* 专门用于处理钢卷头部在设备间的移动和物料跟踪。
|
|
||||||
* 这部分逻辑包含了对 Redis/DB 的写入,最适合异步化。
|
|
||||||
*/
|
|
||||||
@Async("taskExecutor")
|
|
||||||
public void trackCoilHeadPosition(String coilId, BigDecimal headPos,
|
public void trackCoilHeadPosition(String coilId, BigDecimal headPos,
|
||||||
AppMeasureEntryMessage entry, AppMeasureExitMessage exit) {
|
AppMeasureEntryMessage entry, AppMeasureExitMessage exit) {
|
||||||
|
|
||||||
Set<DeviceEnum> prevReached = coilReachedDevices.computeIfAbsent(coilId, k -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
|
log.warn(">>> trackCoilHeadPosition 当前线程:{}", Thread.currentThread().getName());
|
||||||
|
|
||||||
|
if (LogRateLimiter.shouldLog("TRACK:" + coilId, 5000)) {
|
||||||
|
log.info("焊缝位置匹配逻辑,当前焊缝长度{},", headPos);
|
||||||
|
logDataService.logInfo("MATMAP-TRACK", "CoilId=" + coilId + " ...");
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<DeviceEnum> prevReached = coilReachedDevices.computeIfAbsent(coilId,
|
||||||
|
k -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
|
||||||
|
|
||||||
|
if (LogRateLimiter.shouldLog("TRACK:" + coilId, 5000)) {
|
||||||
|
logDataService.logInfo("MATMAP-TRACK", "构建的本地matmap缓存为=" + JSONUtil.toJsonStr(prevReached) + " ...");
|
||||||
|
}
|
||||||
|
|
||||||
BigDecimal celLength = entry != null ? entry.getCelLength() : BigDecimal.ZERO;
|
BigDecimal celLength = entry != null ? entry.getCelLength() : BigDecimal.ZERO;
|
||||||
BigDecimal cxlLength = exit != null ? exit.getCxlLength() : BigDecimal.ZERO;
|
BigDecimal cxlLength = exit != null ? exit.getCxlLength() : BigDecimal.ZERO;
|
||||||
|
|
||||||
for (DeviceEnum d : DeviceEnum.values()) {
|
for (DeviceEnum d : DeviceEnum.values()) {
|
||||||
double dynPos = stripPositionService.calculate(d, celLength, cxlLength);
|
double dynPos = stripPositionService.calculate(d, celLength, cxlLength);
|
||||||
|
log.info("焊缝位置匹配逻辑,当前焊缝长度{},当前计算的设备长度:{}", headPos, dynPos);
|
||||||
|
|
||||||
// 判断钢卷的头部是否首次到达该设备
|
if (headPos.compareTo(BigDecimal.valueOf(dynPos)) >= 0 && !prevReached.contains(d)) {
|
||||||
if (headPos.compareTo(BigDecimal.valueOf(dynPos)) >= 0) {
|
|
||||||
if (!prevReached.contains(d)) {
|
|
||||||
|
|
||||||
// 1. 如果是焊机,则调用 CRM 更新计划状态
|
if (d == DeviceEnum.WELDER) {
|
||||||
if (d == WELDER) {
|
logDataService.logInfo("MATMAP-TRACK", "钢卷到达焊机,更新钢卷计划状态coil=" + coilId + "当前长度为:" + headPos);
|
||||||
|
MatmapDTO matmap = MatmapUtil.getMatmap(DeviceEnum.WELDER.getIdx());
|
||||||
MatmapDTO matmap = MatmapUtil.getMatmap(WELDER.getIdx());
|
trackService.l1OperateMat(L1OperateMatForm.builder()
|
||||||
trackService.l1OperateMat(L1OperateMatForm.builder()
|
.entryMatId(coilId)
|
||||||
.entryMatId(coilId)
|
.planId(matmap.getPlanId())
|
||||||
.planId(matmap.getPlanId())
|
.porIdx(entry.getPayOffReelNumber())
|
||||||
.porIdx(entry.getPayOffReelNumber())
|
.operation(L1OperateMatEnum.PRODUCING)
|
||||||
.operation(L1OperateMatEnum.PRODUCING)
|
.build());
|
||||||
.build());
|
} else {
|
||||||
}
|
logDataService.logInfo("MATMAP-TRACK", "钢卷:" + coilId + "到达设备:" + d.getDesc() + "当前长度为:" + headPos);
|
||||||
|
|
||||||
// 2. 更新 Matmap
|
|
||||||
MatmapUtil.setMatId(d.getIdx(), coilId);
|
MatmapUtil.setMatId(d.getIdx(), coilId);
|
||||||
|
proMatmapService.flushMatmap();
|
||||||
WebSocketUtil.sendMatmapMsg();
|
WebSocketUtil.sendMatmapMsg();
|
||||||
// 3. 标记为已到达,防止重复操作
|
|
||||||
prevReached.add(d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevReached.add(d); // 标记为已到达
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,68 +1,70 @@
|
|||||||
package com.fizz.business.service.strip;
|
package com.fizz.business.service.strip;
|
||||||
|
|
||||||
import com.fizz.business.constants.enums.DeviceEnum;
|
import com.fizz.business.constants.enums.DeviceEnum;
|
||||||
import com.fizz.business.domain.msg.AppMeasureEntryMessage;
|
import com.fizz.business.service.LogDataService;
|
||||||
import com.fizz.business.domain.msg.AppMeasureExitMessage;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 位置计算服务 —— 根据 entry.stripLocation(焊机参考)和活套长度动态判断设备的比较位置
|
* 位置计算服务 —— 根据 entry.stripLocation(焊机参考)和活套长度动态判断设备的比较位置
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class StripPositionService {
|
public class StripPositionService {
|
||||||
|
|
||||||
/**
|
@Resource
|
||||||
* 计算设备在 entry 坐标系的“比较位置”
|
LogDataService logDataService;
|
||||||
*
|
|
||||||
* 规则:
|
|
||||||
* - device.basePosition < ENL_MIN : 不加活套
|
|
||||||
* - ENL_MIN <= device.basePosition < CXL_MIN : 加 celLength
|
|
||||||
* - >= CXL_MIN : 加 cxlLength
|
|
||||||
*/
|
|
||||||
public double calculate(DeviceEnum device, BigDecimal celLength, BigDecimal cxlLength) {
|
public double calculate(DeviceEnum device, BigDecimal celLength, BigDecimal cxlLength) {
|
||||||
double base = device.getBasePosition();
|
double base = device.getBasePosition();
|
||||||
double cel = celLength == null ? 0.0 : celLength.doubleValue();
|
double cel = celLength == null ? 0.0 : celLength.doubleValue();
|
||||||
double cxl = cxlLength == null ? 0.0 : cxlLength.doubleValue();
|
double cxl = cxlLength == null ? 0.0 : cxlLength.doubleValue();
|
||||||
|
|
||||||
double enlMin = findEnlMin();
|
double result;
|
||||||
double cxlMin = findCxlMin();
|
|
||||||
|
|
||||||
if (base < enlMin) {
|
switch (device.getSectionType()) {
|
||||||
return base;
|
case ENTRY:
|
||||||
} else if (base < cxlMin) {
|
// 入口段:不开活套
|
||||||
return base + cel;
|
result = base;
|
||||||
} else {
|
break;
|
||||||
return base + cxl;
|
|
||||||
|
case PROCESS:
|
||||||
|
// 工艺段:加入口活套偏移
|
||||||
|
result = base + cel;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EXIT:
|
||||||
|
// 出口段:加入口 + 出口活套偏移
|
||||||
|
result = base + cel + cxl;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
result = base;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public double calculate(DeviceEnum device,AppMeasureEntryMessage entry,
|
// === 日志输出(控制频率,防止刷屏)===
|
||||||
AppMeasureExitMessage exit) {
|
if (log.isDebugEnabled()) {
|
||||||
BigDecimal cel = entry == null ? null : entry.getCelLength();
|
log.debug("【POSITION-CALC】设备: {}, 区域: {}, Base: {}, Cel: {}, Cxl: {}, 结果: {}",
|
||||||
BigDecimal cxl = exit == null ? null : exit.getCxlLength();
|
device.getDesc(),
|
||||||
return calculate(device, cel, cxl);
|
device.getSectionType(),
|
||||||
}
|
String.format("%.3f", base),
|
||||||
|
String.format("%.3f", cel),
|
||||||
private double findEnlMin() {
|
String.format("%.3f", cxl),
|
||||||
double min = Double.MAX_VALUE;
|
String.format("%.3f", result)
|
||||||
for (DeviceEnum d : DeviceEnum.values()) {
|
);
|
||||||
if (d.name().startsWith("ENL")) {
|
|
||||||
min = Math.min(min, d.getBasePosition());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return min == Double.MAX_VALUE ? 0.0 : min;
|
|
||||||
|
// 可选:持久化关键信息(只针对关键设备或每隔一段时间)
|
||||||
|
if (device == DeviceEnum.WELDER || device == DeviceEnum.COAT || device == DeviceEnum.TR) {
|
||||||
|
logDataService.logInfo("POSITION",
|
||||||
|
String.format("设备:%s | 区域:%s | Base:%.3f | Cel:%.3f | Cxl:%.3f | Result:%.3f",
|
||||||
|
device.getDesc(), device.getSectionType(), base, cel, cxl, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double findCxlMin() {
|
|
||||||
double min = Double.MAX_VALUE;
|
|
||||||
for (DeviceEnum d : DeviceEnum.values()) {
|
|
||||||
if (d.name().startsWith("CXL")) {
|
|
||||||
min = Math.min(min, d.getBasePosition());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return min == Double.MAX_VALUE ? Double.MAX_VALUE : min;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.fizz.business.domain.ProMatmap;
|
import com.fizz.business.domain.ProMatmap;
|
||||||
import com.fizz.business.dto.MatmapDTO;
|
import com.fizz.business.dto.MatmapDTO;
|
||||||
import com.fizz.business.mapper.ProMatmapMapper;
|
import com.fizz.business.mapper.ProMatmapMapper;
|
||||||
|
import com.fizz.business.service.LogDataService;
|
||||||
import com.fizz.business.service.client.RedisCacheManager;
|
import com.fizz.business.service.client.RedisCacheManager;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -29,8 +30,14 @@ import java.util.stream.Collectors;
|
|||||||
public class MatmapUtil {
|
public class MatmapUtil {
|
||||||
|
|
||||||
private static ProMatmapMapper romtbMatmapMapper;
|
private static ProMatmapMapper romtbMatmapMapper;
|
||||||
|
private static LogDataService logDataService;
|
||||||
private static RedisCacheManager redisCacheManager;
|
private static RedisCacheManager redisCacheManager;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setLogDataService(LogDataService logDataService) {
|
||||||
|
MatmapUtil.logDataService = logDataService;
|
||||||
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public void setRomtbMatmapMapper(ProMatmapMapper romtbMatmapMapper) {
|
public void setRomtbMatmapMapper(ProMatmapMapper romtbMatmapMapper) {
|
||||||
MatmapUtil.romtbMatmapMapper = romtbMatmapMapper;
|
MatmapUtil.romtbMatmapMapper = romtbMatmapMapper;
|
||||||
@@ -69,6 +76,9 @@ public class MatmapUtil {
|
|||||||
}
|
}
|
||||||
matmap.setPosIdx(index);
|
matmap.setPosIdx(index);
|
||||||
matmap.setMatId(matId);
|
matmap.setMatId(matId);
|
||||||
|
log.info("钢卷焊缝跟踪:matId{},位置id:{}",matId,index);
|
||||||
|
logDataService.logInfo("MATMAP-SAVE","钢卷焊缝跟踪:matId"+matId+",位置id:"+index);
|
||||||
|
|
||||||
redisCacheManager.setMatmap(index, matmap);
|
redisCacheManager.setMatmap(index, matmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication;
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,6 +14,7 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
|||||||
*/
|
*/
|
||||||
@ComponentScan(basePackages = {"com.ruoyi","com.fizz"})
|
@ComponentScan(basePackages = {"com.ruoyi","com.fizz"})
|
||||||
@EnableAsync // 启用异步处理
|
@EnableAsync // 启用异步处理
|
||||||
|
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||||
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
|
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
|
||||||
public class RuoYiApplication
|
public class RuoYiApplication
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user