feat():跟踪逻辑调整

This commit is contained in:
Allenxy
2025-11-17 09:24:53 +08:00
parent 74f4d48884
commit 6704a1eae8
11 changed files with 409 additions and 171 deletions

View File

@@ -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
}
}

View File

@@ -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());
}
}

View File

@@ -0,0 +1,4 @@
package com.fizz.business.service.hanle;
public class CoilAsyncService {
}

View File

@@ -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;
}
}

View File

@@ -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);
} }
} }
} }

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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); // 标记为已到达
} }
} }
} }
} }

View File

@@ -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;
}
} }

View File

@@ -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);
} }

View File

@@ -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
{ {