refactor: require identity resolver for jt1078 malformed rtp
This commit is contained in:
@@ -10,10 +10,10 @@ import java.util.concurrent.ConcurrentMap;
|
|||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 纯内存会话存储。支持按 sessionId / vin / phone 三种 key 查询。
|
* 测试/本地用纯内存会话存储。支持按 sessionId / vin / phone 三种 key 查询。
|
||||||
*
|
*
|
||||||
* <p>失活清理:基于 {@link Caffeine} 的 {@code expireAfterAccess 30 分钟}。
|
* <p>失活清理:基于 {@link Caffeine} 的 {@code expireAfterAccess 30 分钟}。
|
||||||
* 生产环境若需多节点一致性,可用 Redis 实现替换此 Bean。
|
* 生产自动配置拒绝 memory 模式,应使用 Redis 会话索引。
|
||||||
*/
|
*/
|
||||||
public final class InMemorySessionStore implements SessionStore {
|
public final class InMemorySessionStore implements SessionStore {
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.util.Optional;
|
|||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设备会话存储 SPI。默认实现是内存 + Caffeine,生产可替换为 Redis 兜底。
|
* 设备会话存储 SPI。生产自动配置只创建 Redis 实现;测试可显式提供轻量实现。
|
||||||
*/
|
*/
|
||||||
public interface SessionStore {
|
public interface SessionStore {
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
package com.lingniu.ingest.identity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stateless production fallback used when a caller has no injected identity service.
|
|
||||||
*
|
|
||||||
* <p>Persistent binding belongs to {@link MySqlVehicleIdentityService}; this resolver only keeps
|
|
||||||
* ingestion flowing with explicit VIN or stable external identifiers.
|
|
||||||
*/
|
|
||||||
public final class StatelessVehicleIdentityResolver implements VehicleIdentityResolver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public VehicleIdentity resolve(VehicleIdentityLookup lookup) {
|
|
||||||
if (lookup == null) {
|
|
||||||
return unknown();
|
|
||||||
}
|
|
||||||
if (!lookup.vin().isBlank()) {
|
|
||||||
return new VehicleIdentity(lookup.vin(), true, VehicleIdentitySource.EXPLICIT_VIN);
|
|
||||||
}
|
|
||||||
if (!lookup.deviceId().isBlank()) {
|
|
||||||
return new VehicleIdentity(lookup.deviceId(), false, VehicleIdentitySource.FALLBACK_DEVICE_ID);
|
|
||||||
}
|
|
||||||
if (!lookup.phone().isBlank()) {
|
|
||||||
return new VehicleIdentity(lookup.phone(), false, VehicleIdentitySource.FALLBACK_PHONE);
|
|
||||||
}
|
|
||||||
if (!lookup.plate().isBlank()) {
|
|
||||||
return new VehicleIdentity(lookup.plate(), false, VehicleIdentitySource.FALLBACK_PLATE);
|
|
||||||
}
|
|
||||||
return unknown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static VehicleIdentity unknown() {
|
|
||||||
return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.lingniu.ingest.identity;
|
|
||||||
|
|
||||||
import com.lingniu.ingest.api.ProtocolId;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
class StatelessVehicleIdentityResolverTest {
|
|
||||||
|
|
||||||
private final StatelessVehicleIdentityResolver resolver = new StatelessVehicleIdentityResolver();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void explicitVinWinsWithoutPersistingAnyBinding() {
|
|
||||||
VehicleIdentity identity = resolver.resolve(new VehicleIdentityLookup(
|
|
||||||
ProtocolId.MQTT_YUTONG, "LNVIN000000000001", "13900000000", "DEV001", "粤B12345"));
|
|
||||||
|
|
||||||
assertThat(identity.vin()).isEqualTo("LNVIN000000000001");
|
|
||||||
assertThat(identity.resolved()).isTrue();
|
|
||||||
assertThat(identity.source()).isEqualTo(VehicleIdentitySource.EXPLICIT_VIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void fallsBackToStableExternalIdentifiersWithoutMarkingResolved() {
|
|
||||||
assertThat(resolver.resolve(new VehicleIdentityLookup(
|
|
||||||
ProtocolId.JT808, "", "13900000001", "DEV001", "粤B12345")))
|
|
||||||
.satisfies(identity -> {
|
|
||||||
assertThat(identity.vin()).isEqualTo("DEV001");
|
|
||||||
assertThat(identity.resolved()).isFalse();
|
|
||||||
assertThat(identity.source()).isEqualTo(VehicleIdentitySource.FALLBACK_DEVICE_ID);
|
|
||||||
});
|
|
||||||
|
|
||||||
assertThat(resolver.resolve(new VehicleIdentityLookup(
|
|
||||||
ProtocolId.JT808, "", "13900000001", "", "粤B12345")))
|
|
||||||
.satisfies(identity -> {
|
|
||||||
assertThat(identity.vin()).isEqualTo("13900000001");
|
|
||||||
assertThat(identity.resolved()).isFalse();
|
|
||||||
assertThat(identity.source()).isEqualTo(VehicleIdentitySource.FALLBACK_PHONE);
|
|
||||||
});
|
|
||||||
|
|
||||||
assertThat(resolver.resolve(new VehicleIdentityLookup(
|
|
||||||
ProtocolId.JT808, "", "", "", "粤B12345")))
|
|
||||||
.satisfies(identity -> {
|
|
||||||
assertThat(identity.vin()).isEqualTo("粤B12345");
|
|
||||||
assertThat(identity.resolved()).isFalse();
|
|
||||||
assertThat(identity.source()).isEqualTo(VehicleIdentitySource.FALLBACK_PLATE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void returnsUnknownWhenNoIdentitySignalExists() {
|
|
||||||
VehicleIdentity identity = resolver.resolve(new VehicleIdentityLookup(ProtocolId.JT808, "", "", "", ""));
|
|
||||||
|
|
||||||
assertThat(identity.vin()).isEqualTo("unknown");
|
|
||||||
assertThat(identity.resolved()).isFalse();
|
|
||||||
assertThat(identity.source()).isEqualTo(VehicleIdentitySource.UNKNOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import com.lingniu.ingest.api.annotation.EventEmit;
|
|||||||
import com.lingniu.ingest.api.annotation.MessageMapping;
|
import com.lingniu.ingest.api.annotation.MessageMapping;
|
||||||
import com.lingniu.ingest.api.annotation.ProtocolHandler;
|
import com.lingniu.ingest.api.annotation.ProtocolHandler;
|
||||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||||
import com.lingniu.ingest.identity.StatelessVehicleIdentityResolver;
|
|
||||||
import com.lingniu.ingest.identity.VehicleIdentity;
|
import com.lingniu.ingest.identity.VehicleIdentity;
|
||||||
import com.lingniu.ingest.identity.VehicleIdentityLookup;
|
import com.lingniu.ingest.identity.VehicleIdentityLookup;
|
||||||
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
||||||
@@ -17,6 +16,7 @@ import java.time.Instant;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ProtocolHandler(protocol = ProtocolId.JT1078)
|
@ProtocolHandler(protocol = ProtocolId.JT1078)
|
||||||
@@ -24,12 +24,8 @@ public final class Jt1078MalformedRtpHandler {
|
|||||||
|
|
||||||
private final VehicleIdentityResolver identityResolver;
|
private final VehicleIdentityResolver identityResolver;
|
||||||
|
|
||||||
public Jt1078MalformedRtpHandler() {
|
|
||||||
this(new StatelessVehicleIdentityResolver());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Jt1078MalformedRtpHandler(VehicleIdentityResolver identityResolver) {
|
public Jt1078MalformedRtpHandler(VehicleIdentityResolver identityResolver) {
|
||||||
this.identityResolver = identityResolver == null ? new StatelessVehicleIdentityResolver() : identityResolver;
|
this.identityResolver = Objects.requireNonNull(identityResolver, "identityResolver must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping(command = Jt1078MediaMessageId.MALFORMED_RTP, desc = "JT1078 RTP 入口坏包兜底")
|
@MessageMapping(command = Jt1078MediaMessageId.MALFORMED_RTP, desc = "JT1078 RTP 入口坏包兜底")
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.lingniu.ingest.protocol.jt1078.media;
|
|||||||
|
|
||||||
import com.lingniu.ingest.api.ProtocolId;
|
import com.lingniu.ingest.api.ProtocolId;
|
||||||
import com.lingniu.ingest.api.pipeline.RawFrame;
|
import com.lingniu.ingest.api.pipeline.RawFrame;
|
||||||
import com.lingniu.ingest.identity.StatelessVehicleIdentityResolver;
|
|
||||||
import com.lingniu.ingest.identity.VehicleIdentity;
|
import com.lingniu.ingest.identity.VehicleIdentity;
|
||||||
import com.lingniu.ingest.identity.VehicleIdentityLookup;
|
import com.lingniu.ingest.identity.VehicleIdentityLookup;
|
||||||
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
import com.lingniu.ingest.identity.VehicleIdentityResolver;
|
||||||
@@ -11,6 +10,7 @@ import com.lingniu.ingest.identity.VehicleIdentitySource;
|
|||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public final class Jt1078MalformedRtpEventPublisher {
|
public final class Jt1078MalformedRtpEventPublisher {
|
||||||
@@ -18,14 +18,10 @@ public final class Jt1078MalformedRtpEventPublisher {
|
|||||||
private final VehicleIdentityResolver identityResolver;
|
private final VehicleIdentityResolver identityResolver;
|
||||||
private final Consumer<RawFrame> dispatcher;
|
private final Consumer<RawFrame> dispatcher;
|
||||||
|
|
||||||
public Jt1078MalformedRtpEventPublisher(Consumer<RawFrame> dispatcher) {
|
|
||||||
this(new StatelessVehicleIdentityResolver(), dispatcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Jt1078MalformedRtpEventPublisher(VehicleIdentityResolver identityResolver,
|
public Jt1078MalformedRtpEventPublisher(VehicleIdentityResolver identityResolver,
|
||||||
Consumer<RawFrame> dispatcher) {
|
Consumer<RawFrame> dispatcher) {
|
||||||
this.identityResolver = identityResolver == null ? new StatelessVehicleIdentityResolver() : identityResolver;
|
this.identityResolver = Objects.requireNonNull(identityResolver, "identityResolver must not be null");
|
||||||
this.dispatcher = dispatcher;
|
this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher must not be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void publish(Jt1078MalformedRtpPacket malformed) {
|
public void publish(Jt1078MalformedRtpPacket malformed) {
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ class Jt1078MalformedRtpEventPublisherTest {
|
|||||||
@Test
|
@Test
|
||||||
void malformedRtpPacketProducesRawFrameForUnifiedArchiveAndPassthrough() {
|
void malformedRtpPacketProducesRawFrameForUnifiedArchiveAndPassthrough() {
|
||||||
ArrayList<RawFrame> frames = new ArrayList<>();
|
ArrayList<RawFrame> frames = new ArrayList<>();
|
||||||
Jt1078MalformedRtpEventPublisher publisher = new Jt1078MalformedRtpEventPublisher(frames::add);
|
Jt1078MalformedRtpEventPublisher publisher = new Jt1078MalformedRtpEventPublisher(
|
||||||
|
new InMemoryVehicleIdentityService(), frames::add);
|
||||||
Instant receivedAt = Instant.parse("2026-06-22T08:30:00Z");
|
Instant receivedAt = Instant.parse("2026-06-22T08:30:00Z");
|
||||||
byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64};
|
byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64};
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,27 @@ import com.lingniu.ingest.protocol.jt1078.handler.Jt1078MalformedRtpHandler;
|
|||||||
import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket;
|
import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class Jt1078MalformedRtpHandlerTest {
|
class Jt1078MalformedRtpHandlerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void malformedRtpComponentsRequireInjectedIdentityResolver() {
|
||||||
|
assertThat(Arrays.stream(Jt1078MalformedRtpHandler.class.getConstructors())
|
||||||
|
.map(Constructor::getParameterCount))
|
||||||
|
.doesNotContain(0);
|
||||||
|
assertThat(Arrays.stream(com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpEventPublisher.class
|
||||||
|
.getConstructors()))
|
||||||
|
.noneSatisfy(constructor -> assertThat(constructor.getParameterTypes())
|
||||||
|
.containsExactly(Consumer.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void malformedRtpRawFramePayloadMapsToPassthroughWithDiagnostics() {
|
void malformedRtpRawFramePayloadMapsToPassthroughWithDiagnostics() {
|
||||||
byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64};
|
byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64};
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket;
|
|||||||
import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaArchiveService;
|
import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaArchiveService;
|
||||||
import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration;
|
import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration;
|
||||||
import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper;
|
import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper;
|
||||||
|
import com.lingniu.ingest.session.DeviceSession;
|
||||||
|
import com.lingniu.ingest.session.SessionStore;
|
||||||
import com.lingniu.ingest.session.config.SessionCoreAutoConfiguration;
|
import com.lingniu.ingest.session.config.SessionCoreAutoConfiguration;
|
||||||
import com.lingniu.ingest.sink.archive.ArchiveStore;
|
import com.lingniu.ingest.sink.archive.ArchiveStore;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -28,8 +30,10 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -149,6 +153,11 @@ class Jt1078AutoConfigurationTest {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
SessionStore sessionStore() {
|
||||||
|
return new EmptySessionStore();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean(destroyMethod = "close")
|
@Bean(destroyMethod = "close")
|
||||||
DisruptorEventBus disruptorEventBus(CapturingSink sink) {
|
DisruptorEventBus disruptorEventBus(CapturingSink sink) {
|
||||||
return new DisruptorEventBus(1024, "blocking", List.of(sink));
|
return new DisruptorEventBus(1024, "blocking", List.of(sink));
|
||||||
@@ -160,6 +169,41 @@ class Jt1078AutoConfigurationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class EmptySessionStore implements SessionStore {
|
||||||
|
@Override
|
||||||
|
public void put(DeviceSession session) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DeviceSession> findBySessionId(String sessionId) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DeviceSession> findByVin(String vin) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DeviceSession> findByPhone(String phone) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<DeviceSession> update(String sessionId, UnaryOperator<DeviceSession> updater) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String sessionId) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class CapturingSink implements EventSink {
|
private static final class CapturingSink implements EventSink {
|
||||||
private final CopyOnWriteArrayList<VehicleEvent> events = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<VehicleEvent> events = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user