com.fasterxml.jackson.datatype
jackson-datatype-jsr310
@@ -197,7 +214,7 @@
org.junit.jupiter
junit-jupiter
- ${junit.version}
+ ${junit.jupiter.version}
test
@@ -226,4 +243,67 @@
-
\ No newline at end of file
+
+
+ cache-it
+
+
+ org.testcontainers
+ testcontainers
+ 1.19.8
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.19.8
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ **/*IntegrationTest.java
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.5.0
+
+
+ add-cache-integration-tests
+ generate-test-sources
+ add-test-source
+
+ src/cache-it/java
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+ 2.22.2
+
+
+ **/*IntegrationTest.java
+
+
+
+
+ integration-testverify
+
+
+
+
+
+
+
+
+
diff --git a/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/AbstractJetCacheRedisIntegrationTest.java b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/AbstractJetCacheRedisIntegrationTest.java
new file mode 100644
index 0000000..f0d8b0a
--- /dev/null
+++ b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/AbstractJetCacheRedisIntegrationTest.java
@@ -0,0 +1,55 @@
+package com.Modules.NormalData.cache;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+@Testcontainers
+abstract class AbstractJetCacheRedisIntegrationTest {
+ @Container
+ static final GenericContainer> REDIS = new GenericContainer<>(DockerImageName.parse("redis:7.2-alpine"))
+ .withExposedPorts(6379);
+
+ ConfigurableApplicationContext first;
+ ConfigurableApplicationContext second;
+
+ @BeforeEach
+ void startContexts() {
+ first = context();
+ second = context();
+ }
+
+ @AfterEach
+ void closeContexts() {
+ if (second != null) second.close();
+ if (first != null) first.close();
+ }
+
+ ConfigurableApplicationContext context() {
+ String uri = "redis://" + REDIS.getHost() + ':' + REDIS.getMappedPort(6379) + "/15";
+ return new SpringApplicationBuilder(CacheItApplication.class)
+ .web(WebApplicationType.NONE)
+ .properties("jetcache.statIntervalMinutes=0",
+ "jetcache.areaInCacheName=false",
+ "jetcache.penetrationProtect=true",
+ "jetcache.enableMethodCache=true",
+ "jetcache.local.default.type=caffeine",
+ "jetcache.local.default.keyConvertor=fastjson2",
+ "jetcache.remote.default.type=redis.lettuce",
+ "jetcache.remote.default.keyConvertor=fastjson2",
+ "jetcache.remote.default.serialPolicy=KRYO5",
+ "jetcache.remote.default.broadcastChannel=xdr:norm:v1:cache-invalidate",
+ "jetcache.remote.default.uri.0=" + uri)
+ .run();
+ }
+
+ CacheItApplication.CacheItFacade facade(ConfigurableApplicationContext context) {
+ return context.getBean(CacheItApplication.CacheItFacade.class);
+ }
+}
diff --git a/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/CacheItApplication.java b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/CacheItApplication.java
new file mode 100644
index 0000000..53cc583
--- /dev/null
+++ b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/CacheItApplication.java
@@ -0,0 +1,39 @@
+package com.Modules.NormalData.cache;
+
+import com.alicp.jetcache.anno.CacheType;
+import com.alicp.jetcache.anno.Cached;
+import com.alicp.jetcache.anno.CacheInvalidate;
+import com.alicp.jetcache.anno.SerialPolicy;
+import com.alicp.jetcache.anno.config.EnableCreateCacheAnnotation;
+import com.alicp.jetcache.anno.config.EnableMethodCache;
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@SpringBootConfiguration
+@EnableAutoConfiguration
+@EnableMethodCache(basePackages = "com.Modules.NormalData.cache")
+@EnableCreateCacheAnnotation
+class CacheItApplication {
+ @Bean CacheItFacade cacheItFacade() { return new CacheItFacade(); }
+
+ static final class CacheItFacade {
+ private final AtomicInteger loads = new AtomicInteger();
+
+ @Cached(name = "xdr:norm:v1:cache-it:", key = "#key", cacheType = CacheType.BOTH,
+ expire = 60, localExpire = 60, timeUnit = TimeUnit.SECONDS,
+ localLimit = 100, syncLocal = true, serialPolicy = SerialPolicy.KRYO5)
+ public Object get(String key, Object loadedValue) {
+ loads.incrementAndGet();
+ return loadedValue;
+ }
+
+ @CacheInvalidate(name = "xdr:norm:v1:cache-it:", key = "#key")
+ public void invalidate(String key) { }
+
+ int loads() { return loads.get(); }
+ }
+}
diff --git a/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheCrossContextInvalidationIntegrationTest.java b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheCrossContextInvalidationIntegrationTest.java
new file mode 100644
index 0000000..7c2f0f5
--- /dev/null
+++ b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheCrossContextInvalidationIntegrationTest.java
@@ -0,0 +1,17 @@
+package com.Modules.NormalData.cache;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class JetCacheCrossContextInvalidationIntegrationTest extends AbstractJetCacheRedisIntegrationTest {
+ @Test
+ void invalidationBroadcastRemovesOtherContextsLocalEntry() throws Exception {
+ facade(first).get("broadcast", "before");
+ assertEquals("before", facade(second).get("broadcast", "unused"));
+ facade(first).invalidate("broadcast");
+ Thread.sleep(300L);
+ assertEquals("after", facade(second).get("broadcast", "after"));
+ assertEquals(1, facade(second).loads());
+ }
+}
diff --git a/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheRedisFailureIntegrationTest.java b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheRedisFailureIntegrationTest.java
new file mode 100644
index 0000000..7ca6b9e
--- /dev/null
+++ b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheRedisFailureIntegrationTest.java
@@ -0,0 +1,28 @@
+package com.Modules.NormalData.cache;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class JetCacheRedisFailureIntegrationTest extends AbstractJetCacheRedisIntegrationTest {
+ @Test
+ void cacheFailureDoesNotReturnAFalseNullBusinessValue() {
+ REDIS.getDockerClient().pauseContainerCmd(REDIS.getContainerId()).exec();
+ RuntimeException failure = null;
+ Object value = null;
+ try {
+ value = facade(first).get("redis-down", "database-value");
+ } catch (RuntimeException expected) {
+ failure = expected;
+ } finally {
+ REDIS.getDockerClient().unpauseContainerCmd(REDIS.getContainerId()).exec();
+ }
+ // JetCache 2.7 may fail-open or propagate according to error handler; either path must be explicit.
+ if (failure != null) {
+ assertNotNull(failure.getCause());
+ } else {
+ assertEquals("database-value", value);
+ }
+ }
+}
diff --git a/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheSerializationIntegrationTest.java b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheSerializationIntegrationTest.java
new file mode 100644
index 0000000..2b0591a
--- /dev/null
+++ b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheSerializationIntegrationTest.java
@@ -0,0 +1,22 @@
+package com.Modules.NormalData.cache;
+
+import com.Modules.NormalData.compiled.RuleSource;
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class JetCacheSerializationIntegrationTest extends AbstractJetCacheRedisIntegrationTest {
+ @Test
+ void immutableSnapshotRoundTripsAcrossContextsWithKryo5() {
+ RuleSourceSnapshot source = new RuleSourceSnapshot(7L, Collections.singletonList(
+ new RuleSource(9L, "rule", "Rule", "json", "{\"mappers\":[]}", true, "0")),
+ Instant.EPOCH);
+ facade(first).get("snapshot", source);
+ RuleSourceSnapshot decoded = (RuleSourceSnapshot) facade(second).get("snapshot", null);
+ assertEquals(7L, decoded.getDeviceId());
+ assertEquals(source.getRules().get(0).getFingerprint(), decoded.getRules().get(0).getFingerprint());
+ }
+}
diff --git a/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheTwoLevelIntegrationTest.java b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheTwoLevelIntegrationTest.java
new file mode 100644
index 0000000..5c8f1e9
--- /dev/null
+++ b/haobang-security-dm/syslog-consumer-rule/src/cache-it/java/com/Modules/NormalData/cache/JetCacheTwoLevelIntegrationTest.java
@@ -0,0 +1,15 @@
+package com.Modules.NormalData.cache;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class JetCacheTwoLevelIntegrationTest extends AbstractJetCacheRedisIntegrationTest {
+ @Test
+ void secondContextReadsFirstContextRemoteValueThenItsOwnL1() {
+ assertEquals("from-a", facade(first).get("two-level", "from-a"));
+ assertEquals("from-a", facade(second).get("two-level", "from-b"));
+ assertEquals("from-a", facade(second).get("two-level", "from-c"));
+ assertEquals(0, facade(second).loads());
+ }
+}
diff --git a/haobang-security-dm/syslog-consumer-rule/src/main/java/com/Modules/NormalData/LogNormalProcessor.java b/haobang-security-dm/syslog-consumer-rule/src/main/java/com/Modules/NormalData/LogNormalProcessor.java
index 7b260a1..d03436f 100644
--- a/haobang-security-dm/syslog-consumer-rule/src/main/java/com/Modules/NormalData/LogNormalProcessor.java
+++ b/haobang-security-dm/syslog-consumer-rule/src/main/java/com/Modules/NormalData/LogNormalProcessor.java
@@ -1,8 +1,7 @@
package com.Modules.NormalData;
import cn.hutool.core.date.DateTime;
-import com.common.entity.DeviceCollectTask;
-import com.common.entity.DeviceDevice;
+import com.Modules.NormalData.compiled.*;
import com.common.entity.RuleContent.*;
import com.common.entity.SyslogNonNormalMessage;
import com.common.mapper.DmColumnMapper;
@@ -11,6 +10,8 @@ import com.common.service.*;
import com.common.util.*;
import com.config.AppProperties;
import com.config.KafkaConsumerProperties;
+import com.config.NormalizationOptimizationProperties;
+import com.Modules.NormalData.metrics.NormalizationRuleMetrics;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONObject;
@@ -19,7 +20,15 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.common.entity.SyslogMessage;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicLong;
/**
* 日志标准化处理器(规则引擎模块 - Spring 管理的单例 Bean)
@@ -27,6 +36,45 @@ import java.util.*;
*/
@Service
public class LogNormalProcessor {
+
+ public enum ProcessingOutcome { SUCCESS, FILTERED }
+
+ /**
+ * 一条原始日志完成规则计算后的纯结果。
+ *
+ * 这里故意只保存“应该写什么”,不负责“怎样写入达梦”。活动 Kafka 链路会先并行产生
+ * 多个 {@code NormalizationResult},再由专用批写器集中提交。这样计算线程不会继续执行
+ * 每行一个事务,同时旧的 {@link #process(String, String, String, LocalDateTime)} 入口仍可按
+ * 单条模式兼容历史调用方。
+ */
+ public static final class NormalizationResult {
+ private final ProcessingOutcome outcome;
+ private final List