From 6704a1eae8f5e1de2475e327c0735f826a474569 Mon Sep 17 00:00:00 2001 From: Allenxy <13762749+chaosallen@user.noreply.gitee.com> Date: Mon, 17 Nov 2025 09:24:53 +0800 Subject: [PATCH] =?UTF-8?q?feat():=E8=B7=9F=E8=B8=AA=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business/constants/enums/DeviceEnum.java | 104 +++++--- .../service/hanle/AppMeasureHandler.java | 66 ++++- .../service/hanle/CoilAsyncService.java | 4 + .../hanle/CoilTrackThreadPoolConfig.java | 25 ++ .../service/hanle/OpcMessageDispatcher.java | 29 ++- .../business/service/impl/SegmentService.java | 8 +- .../service/strip/LogRateLimiter.java | 18 ++ .../service/strip/SegmentTrackerService.java | 226 ++++++++++++------ .../service/strip/StripPositionService.java | 88 +++---- .../com/fizz/business/utils/MatmapUtil.java | 10 + .../main/java/com/ruoyi/RuoYiApplication.java | 2 + 11 files changed, 409 insertions(+), 171 deletions(-) create mode 100644 business/src/main/java/com/fizz/business/service/hanle/CoilAsyncService.java create mode 100644 business/src/main/java/com/fizz/business/service/hanle/CoilTrackThreadPoolConfig.java create mode 100644 business/src/main/java/com/fizz/business/service/strip/LogRateLimiter.java diff --git a/business/src/main/java/com/fizz/business/constants/enums/DeviceEnum.java b/business/src/main/java/com/fizz/business/constants/enums/DeviceEnum.java index 7e73366..10daef2 100644 --- a/business/src/main/java/com/fizz/business/constants/enums/DeviceEnum.java +++ b/business/src/main/java/com/fizz/business/constants/enums/DeviceEnum.java @@ -1,33 +1,36 @@ package com.fizz.business.constants.enums; -import lombok.AllArgsConstructor; import lombok.Getter; import java.util.*; -@Getter -@AllArgsConstructor /** - * 细粒度设备枚举(按你给出的17台设备) - * position 为基准位置(单位:m),用于和 stripLocation 比较。 - * paramFields 列出该设备需要采样/缓存的字段名(与 AppMeasure*Message 中字段名对应)。 + * 细粒度设备枚举 + * - position: 基准位置(单位:m) + * - sectionType: 所属段(入口 / 工艺 / 出口) + * - sourceType: 数据来源(ENTRY/FURNACE/COAT/EXIT) + * - paramFields: 对应AppMeasure*Message中的字段 */ +@Getter public enum DeviceEnum { - POR1(0, "1#开卷机", 0.0, Arrays.asList("tensionPorBr1", "stripSpeed")), - POR2(1, "2#开卷机", 0.0, Arrays.asList("tensionPorBr2", "stripSpeed")), - WELDER(2, "焊机", 16.433, Arrays.asList("weldStatus")), - ENL1(3, "入口活套1", 58.987, Arrays.asList("celLength", "celCapacity", "tensionCel")), - ENL2(4, "入口活套2", 118.518, Arrays.asList("celLength", "celCapacity", "tensionCel")), - CLEAN(5, "清洗段", 97.607, Arrays.asList("cleaningVoltage", "cleaningCurrent", "alkaliConcentration", "alkaliTemperature")), - 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")), - FUR4(9, "退火炉-均衡段", 14.607, Arrays.asList("scsExitStripTemp")), - POT(10, "锌锅", 10.373, Arrays.asList("scsExitStripTemp")), - TOWER(11, "后处理", 144.412, Arrays.asList("scsExitStripTemp")), - TM(11, "光整机", 26.527, Arrays.asList("tensionBr5Tm", "stripSpeedTmExit")), - TL(12, "拉矫机", 15.027, Arrays.asList("tlElongation", "tensionTlBr7")), - COAT(13, "后处理段", 145.5, Arrays.asList( + // === 入口段 === + POR1(0, "1#开卷机", 0.0, SectionType.ENTRY, SourceType.ENTRY, Arrays.asList("tensionPorBr1", "stripSpeed")), + POR2(1, "2#开卷机", 0.0, SectionType.ENTRY, SourceType.ENTRY, Arrays.asList("tensionPorBr2", "stripSpeed")), + WELDER(2, "焊机", 16.433, SectionType.ENTRY, SourceType.ENTRY, Arrays.asList("weldStatus")), + ENL1(3, "入口活套1", 75.42, SectionType.PROCESS, SourceType.ENTRY, Arrays.asList("celLength", "celCapacity", "tensionCel")), + ENL2(4, "入口活套2", 193.94, SectionType.PROCESS, SourceType.ENTRY, Arrays.asList("celLength", "celCapacity", "tensionCel")), + + // === 工艺段 === + CLEAN(5, "清洗段", 291.545, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("cleaningVoltage", "cleaningCurrent", "alkaliConcentration", "alkaliTemperature")), + FUR1(6, "退火炉-预热段", 354.558, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("phfExitStripTemp", "potTemperature", "gasConsumption")), + FUR2(7, "退火炉-加热段", 426.568, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("rtfExitStripTemp", "zincPotPower")), + FUR3(8, "退火炉-冷却段", 457.278, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("jcsExitStripTemp", "coolingTowerStripTemp")), + FUR4(9, "退火炉-均衡段", 471.885, SectionType.PROCESS, SourceType.FURNACE, Arrays.asList("scsExitStripTemp")), + POT(10, "锌锅", 482.258, SectionType.PROCESS, SourceType.COAT, Arrays.asList("scsExitStripTemp")), + 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", "avrCoatingWeightBottom","stdCoatingWeightBottom","maxCoatingWeightBottom","minCoatingWeightBottom", "airKnifePressure","airKnifeFlow","airKnifeGap","stripSpeedTmExit","tensionBr5Tm", @@ -36,21 +39,64 @@ public enum DeviceEnum { "tensionTlBr7","tensionBr6Br7","tlFlag","tlElongation","levelingUnit1Mesh","levelingUnit2Mesh", "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")), - TR(17, "卷取机", 21.549, Arrays.asList("coilLength", "speedExitSection", "tensionBr9Tr")), - EXC(18, "卸卷小车", 999999.0, Collections.emptyList()), - WEIGHT(19, "称重鞍座", 999999.0, Collections.emptyList()); + + // === 出口段 === + CXL1(15, "出口活套1", 900.561, SectionType.EXIT, SourceType.EXIT, Arrays.asList("cxlLength", "cxlCapacity", "tensionCxl")), + CXL2(16, "出口活套2", 920.561, SectionType.EXIT, SourceType.EXIT, Arrays.asList("cxlLength", "cxlCapacity", "tensionCxl")), + INS(17, "检查站", 940.561, SectionType.EXIT, SourceType.EXIT, Arrays.asList("inspectionStatus")), + 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 String desc; private final double basePosition; + private final SectionType sectionType; + private final SourceType sourceType; private final List paramFields; private static final Map NAME_MAP = new HashMap<>(); + static { for (DeviceEnum d : values()) NAME_MAP.put(d.name(), d); } - public static DeviceEnum fromName(String name) { return NAME_MAP.get(name); } -} \ No newline at end of file + + DeviceEnum(int idx, String desc, double basePosition, SectionType sectionType, SourceType sourceType, List 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 + } +} diff --git a/business/src/main/java/com/fizz/business/service/hanle/AppMeasureHandler.java b/business/src/main/java/com/fizz/business/service/hanle/AppMeasureHandler.java index 5856050..2caae19 100644 --- a/business/src/main/java/com/fizz/business/service/hanle/AppMeasureHandler.java +++ b/business/src/main/java/com/fizz/business/service/hanle/AppMeasureHandler.java @@ -1,24 +1,34 @@ package com.fizz.business.service.hanle; import com.fizz.business.anno.OpcMessageHandlerType; -import com.fizz.business.constants.enums.DeviceEnum; import com.fizz.business.constants.enums.OpcMessageType; 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.strip.SegmentTrackerService; -import com.fizz.business.service.strip.StripPositionService; +import com.fizz.business.utils.MatmapUtil; import com.fizz.business.utils.WebSocketUtil; +import com.ruoyi.common.utils.StringUtils; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.math.BigDecimal; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; -@Component @OpcMessageHandlerType(OpcMessageType.APP_MEASURE) +@Component @RequiredArgsConstructor +@Slf4j public class AppMeasureHandler implements OpcMessageHandler { private final SegmentTrackerService tracker; + private final LogDataService logDataService; + private final Executor coilTrackExecutor; // 注入线程池接口 + + private static final AtomicInteger LOG_COUNTER = new AtomicInteger(0); @Override public void handle(AppMeasureMessage message) { @@ -31,15 +41,51 @@ public class AppMeasureHandler implements OpcMessageHandler { WebSocketUtil.sendMeasureMsg(message); - // 2) extract key fields - String coilId = entry == null ? "UNKNOWN" : entry.getEntryCoilId(); + // 处理钢卷ID为空的情况 + 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(); - // 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); - // 调用新的方法来跟踪钢卷头部位置,并处理快照和 matmap 更新 - // 使用焊机长度作为最接近的钢卷头部位置 - tracker.trackCoilHeadPosition(coilId, lengthAtWelder, entry, exit); + // 异步执行 trackCoilHeadPosition +// coilTrackExecutor.execute(() -> { +// 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); +// } +// }); } -} \ No newline at end of file + + private MatmapDTO getCurrentRunningCoilId(AppMeasureEntryMessage entry) { + + return MatmapUtil.getMatmap(entry.getPayOffReelNumber()); + + } +} diff --git a/business/src/main/java/com/fizz/business/service/hanle/CoilAsyncService.java b/business/src/main/java/com/fizz/business/service/hanle/CoilAsyncService.java new file mode 100644 index 0000000..6df61dc --- /dev/null +++ b/business/src/main/java/com/fizz/business/service/hanle/CoilAsyncService.java @@ -0,0 +1,4 @@ +package com.fizz.business.service.hanle; + +public class CoilAsyncService { +} diff --git a/business/src/main/java/com/fizz/business/service/hanle/CoilTrackThreadPoolConfig.java b/business/src/main/java/com/fizz/business/service/hanle/CoilTrackThreadPoolConfig.java new file mode 100644 index 0000000..6692662 --- /dev/null +++ b/business/src/main/java/com/fizz/business/service/hanle/CoilTrackThreadPoolConfig.java @@ -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; + } +} + diff --git a/business/src/main/java/com/fizz/business/service/hanle/OpcMessageDispatcher.java b/business/src/main/java/com/fizz/business/service/hanle/OpcMessageDispatcher.java index 23aa29f..905e719 100644 --- a/business/src/main/java/com/fizz/business/service/hanle/OpcMessageDispatcher.java +++ b/business/src/main/java/com/fizz/business/service/hanle/OpcMessageDispatcher.java @@ -4,29 +4,40 @@ import com.fizz.business.anno.OpcMessageHandlerType; import com.fizz.business.constants.enums.OpcMessageType; import com.fizz.business.domain.msg.OpcMessage; import com.fizz.business.service.OpcMessageHandler; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Component; +import java.util.Collection; import java.util.HashMap; import java.util.Map; @Component +@Slf4j public class OpcMessageDispatcher { private final Map> handlerMap = new HashMap<>(); @Autowired public OpcMessageDispatcher(ApplicationContext context) { - Map beans = context.getBeansOfType(OpcMessageHandler.class); - beans.values().forEach(handler -> { - OpcMessageHandlerType annotation = handler.getClass().getAnnotation(OpcMessageHandlerType.class); - if (annotation != null) { - handlerMap.put(annotation.value(), handler); - } else { - throw new IllegalStateException("Handler " + handler.getClass().getSimpleName() + " 未标注 @OpcMessageHandlerType"); + + Collection handlers = context.getBeansOfType(OpcMessageHandler.class).values(); + + for (OpcMessageHandler handler : handlers) { + + // ⭐⭐ 从代理类中获取注解的正确方式 + 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") @@ -35,7 +46,7 @@ public class OpcMessageDispatcher { if (handler != null) { handler.handle(message); } else { - System.out.println("未注册的 OPC 消息类型: " + type); + log.warn("未注册的 OPC 消息类型: {}", type); } } } diff --git a/business/src/main/java/com/fizz/business/service/impl/SegmentService.java b/business/src/main/java/com/fizz/business/service/impl/SegmentService.java index e12adc8..a75fdca 100644 --- a/business/src/main/java/com/fizz/business/service/impl/SegmentService.java +++ b/business/src/main/java/com/fizz/business/service/impl/SegmentService.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fizz.business.dto.SegmentDTO; import com.fizz.business.mapper.SegmentMapper; import com.fizz.business.vo.SegmentParamVO; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -15,6 +16,7 @@ import java.util.List; import java.util.Map; @Service +@Slf4j public class SegmentService { @Resource @@ -29,7 +31,7 @@ public class SegmentService { */ public void saveTotalSegment(SegmentDTO segment) { if (segment == null || segment.getEnCoilID() == null || segment.getEnCoilID().isEmpty()) { - System.err.println("警告: Coil ID 为空,无法保存数据。"); + log.warn("警告: Coil ID 为空,无法保存数据。"); return; } @@ -45,7 +47,7 @@ public class SegmentService { String jsonString = objectMapper.writeValueAsString(totalValuesAvg); segment.setTotalValuesJson(jsonString); } catch (JsonProcessingException e) { - System.err.println("转换 JSON 失败: " + e.getMessage()); + log.warn("转换 JSON 失败: " + e.getMessage()); return; } @@ -58,7 +60,7 @@ public class SegmentService { // 3. 执行插入操作,MyBatis-Plus 会自动处理 totalValuesJson 字段的保存 segmentMapper.insert(segment); - System.out.println("段数据保存成功。钢卷号: " + segment.getEnCoilID() + ", 段号: " + segment.getSegNo()); + log.info("段数据保存成功。钢卷号: " + segment.getEnCoilID() + ", 段号: " + segment.getSegNo()); } public List queryParamByEnCoilId(String enCoilID, String paramField) { diff --git a/business/src/main/java/com/fizz/business/service/strip/LogRateLimiter.java b/business/src/main/java/com/fizz/business/service/strip/LogRateLimiter.java new file mode 100644 index 0000000..1dc506d --- /dev/null +++ b/business/src/main/java/com/fizz/business/service/strip/LogRateLimiter.java @@ -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 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; + } +} \ No newline at end of file diff --git a/business/src/main/java/com/fizz/business/service/strip/SegmentTrackerService.java b/business/src/main/java/com/fizz/business/service/strip/SegmentTrackerService.java index 6a7d5b0..93eec36 100644 --- a/business/src/main/java/com/fizz/business/service/strip/SegmentTrackerService.java +++ b/business/src/main/java/com/fizz/business/service/strip/SegmentTrackerService.java @@ -1,5 +1,6 @@ package com.fizz.business.service.strip; +import cn.hutool.json.JSONUtil; import com.fizz.business.constants.enums.DeviceEnum; import com.fizz.business.constants.enums.L1OperateMatEnum; 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.SegmentDTO; 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.impl.SegmentService; import com.fizz.business.utils.MatmapUtil; import com.fizz.business.utils.WebSocketUtil; -import com.fizz.business.vo.SegmentParamVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; @@ -27,8 +29,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentMap; -import static com.fizz.business.constants.enums.DeviceEnum.WELDER; - @Service @Slf4j @RequiredArgsConstructor @@ -37,7 +37,10 @@ public class SegmentTrackerService { private final StripPositionService stripPositionService; private final TrackService trackService; private final SegmentService segmentService; // 注入新创建的段服务 + private final LogDataService logDataService; // 注入新创建的段服务 + private final ProMatmapService proMatmapService; // 注入新创建的段服务 + private static final long intervalTime = 10000; // 每 10 秒写一次 private int traceCount = 0; private boolean firstMeasure = true; private BigDecimal weldLength = BigDecimal.ZERO; @@ -49,6 +52,15 @@ public class SegmentTrackerService { private final ConcurrentMap> coilReachedDevices = new ConcurrentHashMap<>(); private static final double LOWSPEEDLIMIT = 0.05; + // 定义一个缓存记录上次写入日志的时间 + private final Map lastLogTimeMap = new ConcurrentHashMap<>(); + + // --- 定义一个缓存,用于控制同一 CoilId 新卷日志写入频率 --- + private final Map lastNewCoilLogTimeMap = new ConcurrentHashMap<>(); + + // 放在类的字段中,全局共享 + private final Map lastTrackLogTimeMap = new ConcurrentHashMap<>(); + /** * 【主入口】 * 负责处理新的测量数据,生成新段,并对新到达的设备做一次性快照。 @@ -60,52 +72,65 @@ public class SegmentTrackerService { AppMeasureCoatMessage coat, 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; if (firstMeasure || entryLengthAtWelder.compareTo(weldLength) < 0) { - weldDev = entryLengthAtWelder; // 新卷偏移量等于当前长度 + weldDev = entryLengthAtWelder; coilSegNum = 1; weldLength = entryLengthAtWelder; firstMeasure = false; - listSegment.clear(); // 清空旧段 - coilReachedDevices.remove(coilId); // 清空旧钢卷的快照记录 - log.info("New coil detected or initialized:{} " ,coilId); + listSegment.clear(); + coilReachedDevices.remove(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 { weldDev = entryLengthAtWelder.subtract(weldLength); } - // 更新焊机长度 weldLength = entryLengthAtWelder; - // trace 控制 - traceCount++; - if (traceCount > 5) { - log.info(String.format("Trace: weldLen=%.2f, speed=%s", - entryLengthAtWelder, procStripSpeed(entry, furnace, exit))); - traceCount = 0; - } - - - // 2. 检查是否达到生成新段的条件 + // 2. 新段生成逻辑 BigDecimal segThreshold = BigDecimal.valueOf(coilSegNum * SEGSTRIPLEN); if (entryLengthAtWelder.compareTo(segThreshold) >= 0) { - - // 2.1. 创建新的 SegmentDTO 并初始化基本位置 SegmentDTO newSeg = createNewSegment(coilId, entryLengthAtWelder); - - // 2.3. 将新段添加到队列中 listSegment.addLast(newSeg); - if (listSegment.size() > MAX_SEG_COUNT) { - listSegment.removeFirst(); - } + if (listSegment.size() > MAX_SEG_COUNT) listSegment.removeFirst(); + log.info("【TRACK】新段生成 -> CoilId: {}, SegmentNo: {}, StartLen: {}", coilId, coilSegNum, entryLengthAtWelder); + logDataService.logInfo("WELDER", "新段生成 -> CoilId: " + coilId + ", SegmentNo: " + coilSegNum + ", StartLen: " + entryLengthAtWelder); coilSegNum++; } - // 3. 处理队列中所有已存在的段 - treatSegAsync(entry, furnace, coat, exit, weldDev); + // 3. 更新已有段 + 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, BigDecimal weldDev) { if (listSegment.isEmpty()) { + // === 【处理开始日志】=== + log.warn("【TRACK-TREAT】,listSegment为空,直接返回"); + return; } BigDecimal celLength = entry != null ? entry.getCelLength() : 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 iterator = listSegment.descendingIterator(); while (iterator.hasNext()) { SegmentDTO segment = iterator.next(); @@ -139,8 +171,14 @@ public class SegmentTrackerService { if (segment.getHeadPos().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); 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()) { Object message = getMessageForDevice(device, entry, furnace, coat, exit); if (message != null) { @@ -155,7 +193,10 @@ public class SegmentTrackerService { double exitPlantPos = stripPositionService.calculate(DeviceEnum.TR, celLength, cxlLength); 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); // 调用服务进行持久化 iterator.remove(); } @@ -163,22 +204,22 @@ public class SegmentTrackerService { } // --- 新增的辅助方法,将逻辑封装起来 --- + /** * 【异步方法】 * 异步处理队列中所有已存在的段,更新其位置和累积数据。 */ @Async("taskExecutor") // 指定使用名为 "taskExecutor" 的线程池 public void treatSegAsync(AppMeasureEntryMessage entry, - AppMeasureFurnaceMessage furnace, - AppMeasureCoatMessage coat, - AppMeasureExitMessage exit, - BigDecimal weldDev) { + AppMeasureFurnaceMessage furnace, + AppMeasureCoatMessage coat, + AppMeasureExitMessage exit, + BigDecimal weldDev) { // 调用原有的同步方法 this.treatSeg(entry, furnace, coat, exit, weldDev); } - /** * 创建并初始化一个新的 SegmentDTO。 */ @@ -208,30 +249,54 @@ public class SegmentTrackerService { } - // --- 辅助方法,与消息处理和反射相关 --- - - private Object getMessageForDevice(DeviceEnum device, AppMeasureEntryMessage entry, AppMeasureFurnaceMessage furnace, AppMeasureCoatMessage coat, AppMeasureExitMessage exit) { - if (device.getDesc().contains("开卷机") || device.getDesc().contains("活套") || device.getDesc().contains("焊机")) { - return entry; - } else if (device.getDesc().contains("清洗段") || device.getDesc().contains("退火炉")) { - return furnace; - } else if (device.getDesc().contains("涂机")) { - return coat; - } else { - return exit; + private Object getMessageForDevice(DeviceEnum device, + AppMeasureEntryMessage entry, + AppMeasureFurnaceMessage furnace, + AppMeasureCoatMessage coat, + AppMeasureExitMessage exit) { + switch (device.getSourceType()) { + case ENTRY: + return entry; + case FURNACE: + return furnace; + 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("焊机")) { - return entry != null && entry.getStripSpeed() != null ? entry.getStripSpeed().doubleValue() : 0.0; - } else if (device.getDesc().contains("清洗段") || device.getDesc().contains("退火炉")) { - return coat != null && coat.getStripSpeedTmExit() != null ? coat.getStripSpeedTmExit().doubleValue() : 0.0; - } else { - return exit != null && exit.getSpeedExitSection() != null ? exit.getSpeedExitSection().doubleValue() : 0.0; + + private double getSpeedForDevice(DeviceEnum device, + AppMeasureEntryMessage entry, + AppMeasureCoatMessage coat, + AppMeasureExitMessage exit) { + switch (device.getSectionType()) { + 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) { if (entry != null && entry.getStripSpeed() != null) return entry.getStripSpeed().doubleValue(); // if (furnace != null && furnace.getStripSpeed() != null) return furnace.getStripSpeed().doubleValue(); @@ -265,48 +330,55 @@ public class SegmentTrackerService { } } - /** - * 【新方法】 - * 专门用于处理钢卷头部在设备间的移动和物料跟踪。 - * 这部分逻辑包含了对 Redis/DB 的写入,最适合异步化。 - */ - @Async("taskExecutor") + // 本地缓存,每个 coilId 对应已到达设备 public void trackCoilHeadPosition(String coilId, BigDecimal headPos, AppMeasureEntryMessage entry, AppMeasureExitMessage exit) { - Set 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 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 cxlLength = exit != null ? exit.getCxlLength() : BigDecimal.ZERO; for (DeviceEnum d : DeviceEnum.values()) { double dynPos = stripPositionService.calculate(d, celLength, cxlLength); + log.info("焊缝位置匹配逻辑,当前焊缝长度{},当前计算的设备长度:{}", headPos, dynPos); - // 判断钢卷的头部是否首次到达该设备 - if (headPos.compareTo(BigDecimal.valueOf(dynPos)) >= 0) { - if (!prevReached.contains(d)) { + if (headPos.compareTo(BigDecimal.valueOf(dynPos)) >= 0 && !prevReached.contains(d)) { - // 1. 如果是焊机,则调用 CRM 更新计划状态 - if (d == WELDER) { - - MatmapDTO matmap = MatmapUtil.getMatmap(WELDER.getIdx()); - trackService.l1OperateMat(L1OperateMatForm.builder() - .entryMatId(coilId) - .planId(matmap.getPlanId()) - .porIdx(entry.getPayOffReelNumber()) - .operation(L1OperateMatEnum.PRODUCING) - .build()); - } - - // 2. 更新 Matmap + if (d == DeviceEnum.WELDER) { + logDataService.logInfo("MATMAP-TRACK", "钢卷到达焊机,更新钢卷计划状态coil=" + coilId + "当前长度为:" + headPos); + MatmapDTO matmap = MatmapUtil.getMatmap(DeviceEnum.WELDER.getIdx()); + trackService.l1OperateMat(L1OperateMatForm.builder() + .entryMatId(coilId) + .planId(matmap.getPlanId()) + .porIdx(entry.getPayOffReelNumber()) + .operation(L1OperateMatEnum.PRODUCING) + .build()); + } else { + logDataService.logInfo("MATMAP-TRACK", "钢卷:" + coilId + "到达设备:" + d.getDesc() + "当前长度为:" + headPos); MatmapUtil.setMatId(d.getIdx(), coilId); + proMatmapService.flushMatmap(); WebSocketUtil.sendMatmapMsg(); - // 3. 标记为已到达,防止重复操作 - prevReached.add(d); } + + prevReached.add(d); // 标记为已到达 } } } + + } \ No newline at end of file diff --git a/business/src/main/java/com/fizz/business/service/strip/StripPositionService.java b/business/src/main/java/com/fizz/business/service/strip/StripPositionService.java index ef5c52e..5539df7 100644 --- a/business/src/main/java/com/fizz/business/service/strip/StripPositionService.java +++ b/business/src/main/java/com/fizz/business/service/strip/StripPositionService.java @@ -1,68 +1,70 @@ package com.fizz.business.service.strip; import com.fizz.business.constants.enums.DeviceEnum; -import com.fizz.business.domain.msg.AppMeasureEntryMessage; -import com.fizz.business.domain.msg.AppMeasureExitMessage; +import com.fizz.business.service.LogDataService; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; - +import javax.annotation.Resource; import java.math.BigDecimal; /** * 位置计算服务 —— 根据 entry.stripLocation(焊机参考)和活套长度动态判断设备的比较位置 */ @Service +@Slf4j public class StripPositionService { - /** - * 计算设备在 entry 坐标系的“比较位置” - * - * 规则: - * - device.basePosition < ENL_MIN : 不加活套 - * - ENL_MIN <= device.basePosition < CXL_MIN : 加 celLength - * - >= CXL_MIN : 加 cxlLength - */ + @Resource + LogDataService logDataService; + public double calculate(DeviceEnum device, BigDecimal celLength, BigDecimal cxlLength) { double base = device.getBasePosition(); double cel = celLength == null ? 0.0 : celLength.doubleValue(); double cxl = cxlLength == null ? 0.0 : cxlLength.doubleValue(); - double enlMin = findEnlMin(); - double cxlMin = findCxlMin(); + double result; - if (base < enlMin) { - return base; - } else if (base < cxlMin) { - return base + cel; - } else { - return base + cxl; + switch (device.getSectionType()) { + case ENTRY: + // 入口段:不开活套 + result = base; + break; + + 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) { - BigDecimal cel = entry == null ? null : entry.getCelLength(); - BigDecimal cxl = exit == null ? null : exit.getCxlLength(); - return calculate(device, cel, cxl); - } - - private double findEnlMin() { - double min = Double.MAX_VALUE; - for (DeviceEnum d : DeviceEnum.values()) { - if (d.name().startsWith("ENL")) { - min = Math.min(min, d.getBasePosition()); - } + // === 日志输出(控制频率,防止刷屏)=== + if (log.isDebugEnabled()) { + log.debug("【POSITION-CALC】设备: {}, 区域: {}, Base: {}, Cel: {}, Cxl: {}, 结果: {}", + device.getDesc(), + device.getSectionType(), + String.format("%.3f", base), + String.format("%.3f", cel), + String.format("%.3f", cxl), + String.format("%.3f", result) + ); } - 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; - } } \ No newline at end of file diff --git a/business/src/main/java/com/fizz/business/utils/MatmapUtil.java b/business/src/main/java/com/fizz/business/utils/MatmapUtil.java index 409f615..fd098af 100644 --- a/business/src/main/java/com/fizz/business/utils/MatmapUtil.java +++ b/business/src/main/java/com/fizz/business/utils/MatmapUtil.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fizz.business.domain.ProMatmap; import com.fizz.business.dto.MatmapDTO; import com.fizz.business.mapper.ProMatmapMapper; +import com.fizz.business.service.LogDataService; import com.fizz.business.service.client.RedisCacheManager; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -29,8 +30,14 @@ import java.util.stream.Collectors; public class MatmapUtil { private static ProMatmapMapper romtbMatmapMapper; + private static LogDataService logDataService; private static RedisCacheManager redisCacheManager; + @Autowired + public void setLogDataService(LogDataService logDataService) { + MatmapUtil.logDataService = logDataService; + } + @Autowired public void setRomtbMatmapMapper(ProMatmapMapper romtbMatmapMapper) { MatmapUtil.romtbMatmapMapper = romtbMatmapMapper; @@ -69,6 +76,9 @@ public class MatmapUtil { } matmap.setPosIdx(index); matmap.setMatId(matId); + log.info("钢卷焊缝跟踪:matId{},位置id:{}",matId,index); + logDataService.logInfo("MATMAP-SAVE","钢卷焊缝跟踪:matId"+matId+",位置id:"+index); + redisCacheManager.setMatmap(index, matmap); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java index 31d70e0..f94224b 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java @@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.scheduling.annotation.EnableAsync; /** @@ -13,6 +14,7 @@ import org.springframework.scheduling.annotation.EnableAsync; */ @ComponentScan(basePackages = {"com.ruoyi","com.fizz"}) @EnableAsync // 启用异步处理 +@EnableAspectJAutoProxy(exposeProxy = true) @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) public class RuoYiApplication {