From a66f765e1658a4d9d3118d4871bb99d5073ede12 Mon Sep 17 00:00:00 2001 From: lingniu Date: Thu, 2 Jul 2026 09:58:44 +0800 Subject: [PATCH] chore: keep go branch go-only --- .dockerignore | 5 - CHANGELOG.md | 113 -- DECISIONS.md | 105 -- Dockerfile | 19 - README.md | 104 -- deploy/portainer/docker-compose-go.yml | 117 -- deploy/portainer/docker-compose.yml | 216 --- deploy/systemd/README.md | 66 - deploy/systemd/lingniu-go-gateway.service | 18 - .../systemd/lingniu-go-history-writer.service | 18 - .../systemd/lingniu-go-realtime-api.service | 18 - deploy/systemd/lingniu-go-stat-writer.service | 18 - deploy/tools/EnsureKafkaTopics.java | 86 - docs/module-data-flow.html | 749 -------- docs/operations/current-ecs-deployment.md | 227 --- .../gb32960-service-split-runbook.md | 273 --- ...-gateway-verification-result-2026-07-01.md | 888 --------- .../go-vehicle-gateway-verification.md | 158 -- .../operations/jt808-daily-mileage-runbook.md | 34 - .../vehicle-ingest-tdengine-verification.md | 351 ---- ...-20-gb32960-body-parser-block-isolation.md | 600 ------ ...-06-23-gb32960-history-duckdb-hot-store.md | 817 -------- .../plans/2026-06-23-gb32960-service-split.md | 1649 ----------------- ...26-06-29-vehicle-ingest-redesign-phase1.md | 1519 --------------- .../2026-06-30-kafka-streaming-mileage.md | 46 - .../2026-07-01-go-ingest-redesign-phase1.md | 720 ------- ...-23-gb32960-production-readiness-design.md | 481 ----- ...2026-06-23-gb32960-service-split-design.md | 411 ---- .../2026-06-29-vehicle-ingest-redesign.md | 621 ------- .../2026-06-29-vehicle-ingest-redesign.zh.md | 624 ------- .../2026-07-01-go-ingest-redesign-design.md | 498 ----- docs/target-architecture.md | 202 -- docs/vehicle-telemetry-internal-fields.md | 146 -- modules/apps/command-gateway/pom.xml | 58 - .../CommandGatewayAutoConfiguration.java | 15 - .../lingniu/ingest/gateway/CommandResult.java | 24 - .../gateway/TerminalCommandController.java | 281 --- ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../CommandGatewayAutoConfigurationTest.java | 62 - .../TerminalCommandControllerTest.java | 147 -- modules/apps/gb32960-ingest-app/pom.xml | 68 - .../gb32960app/Gb32960IngestApplication.java | 21 - .../actuator/Gb32960SessionsEndpoint.java | 27 - .../src/main/resources/application.yml | 137 -- .../Gb32960IngestAppCompositionTest.java | 78 - .../Gb32960IngestAppDefaultsTest.java | 91 - .../actuator/Gb32960SessionsEndpointTest.java | 33 - modules/apps/jt808-ingest-app/pom.xml | 68 - .../jt808app/Jt808IngestApplication.java | 21 - .../src/main/resources/application.yml | 97 - .../Jt808IngestAppCompositionTest.java | 77 - .../jt808app/Jt808IngestAppDefaultsTest.java | 78 - modules/apps/vehicle-analytics-app/pom.xml | 64 - .../VehicleAnalyticsApplication.java | 21 - .../src/main/resources/application.yml | 71 - .../VehicleAnalyticsAppCompositionTest.java | 74 - .../VehicleAnalyticsAppDefaultsTest.java | 77 - modules/apps/vehicle-history-app/pom.xml | 68 - .../historyapp/VehicleHistoryApplication.java | 21 - ...icleHistoryKafkaConsumerConfiguration.java | 46 - .../src/main/resources/application.yml | 103 - .../historyapp/MavenModuleProfileTest.java | 878 --------- .../PortainerComposeResourceLimitsTest.java | 679 ------- .../VehicleHistoryAppCompositionTest.java | 208 --- .../VehicleHistoryAppDefaultsTest.java | 151 -- .../historyapp/WoodpeckerPipelineTest.java | 94 - modules/apps/yutong-mqtt-app/pom.xml | 64 - .../yutongmqttapp/YutongMqttApplication.java | 21 - .../src/main/resources/application.yml | 108 -- .../YutongMqttAppCompositionTest.java | 83 - .../YutongMqttAppDefaultsTest.java | 71 - .../YutongMqttPortainerComposeTest.java | 50 - modules/core/ingest-api/pom.xml | 30 - .../com/lingniu/ingest/api/ProtocolId.java | 14 - .../ingest/api/annotation/AsyncBatch.java | 30 - .../ingest/api/annotation/EventEmit.java | 17 - .../ingest/api/annotation/IdempotentKey.java | 19 - .../ingest/api/annotation/MessageMapping.java | 33 - .../api/annotation/ProtocolHandler.java | 30 - .../ingest/api/annotation/RateLimited.java | 23 - .../ingest/api/annotation/TraceSpan.java | 15 - .../api/consumer/EnvelopeBatchIngestor.java | 12 - .../consumer/EnvelopeConsumerProcessor.java | 113 -- .../api/consumer/EnvelopeConsumerRecord.java | 20 - .../consumer/EnvelopeDeadLetterRecord.java | 36 - .../api/consumer/EnvelopeDeadLetterSink.java | 6 - .../api/consumer/EnvelopeIngestResult.java | 45 - .../ingest/api/consumer/EnvelopeIngestor.java | 20 - .../ingest/api/event/AlarmPayload.java | 65 - .../ingest/api/event/LocationPayload.java | 23 - .../ingest/api/event/RawArchiveKeys.java | 59 - .../ingest/api/event/RealtimePayload.java | 49 - .../ingest/api/event/TelemetryFieldValue.java | 79 - .../api/event/TelemetryMetadataValues.java | 22 - .../ingest/api/event/TelemetrySnapshot.java | 147 -- .../ingest/api/event/VehicleEvent.java | 186 -- .../VehicleEventTelemetrySnapshotMapper.java | 206 -- .../com/lingniu/ingest/api/package-info.java | 13 - .../ingest/api/pipeline/IngestContext.java | 50 - .../api/pipeline/IngestInterceptor.java | 21 - .../lingniu/ingest/api/pipeline/RawFrame.java | 30 - .../lingniu/ingest/api/sink/EventSink.java | 28 - .../ingest/api/spi/DecodeException.java | 12 - .../lingniu/ingest/api/spi/EventMapper.java | 16 - .../lingniu/ingest/api/spi/FrameDecoder.java | 13 - .../lingniu/ingest/api/spi/FrameEncoder.java | 9 - .../ingest/api/spi/MessageHandler.java | 16 - .../ingest/api/spi/ProtocolPlugin.java | 31 - .../EnvelopeConsumerProcessorTest.java | 55 - .../ingest/api/event/RawArchiveKeysTest.java | 40 - .../api/event/TelemetryFieldValueTest.java | 39 - .../event/TelemetryMetadataValuesTest.java | 23 - .../api/event/TelemetrySnapshotTest.java | 58 - ...hicleEventTelemetrySnapshotMapperTest.java | 175 -- modules/core/ingest-codec-common/pom.xml | 30 - .../com/lingniu/ingest/codec/BccChecksum.java | 19 - .../com/lingniu/ingest/codec/BcdCodec.java | 30 - .../com/lingniu/ingest/codec/BitUtils.java | 34 - .../lingniu/ingest/codec/BcdCodecTest.java | 28 - modules/core/ingest-core/pom.xml | 74 - .../core/concurrency/AsyncBatchExecutor.java | 206 -- .../core/concurrency/DisruptorEventBus.java | 136 -- .../core/concurrency/VehicleEventSlot.java | 12 - .../config/IngestCoreAutoConfiguration.java | 92 - .../core/config/IngestCoreProperties.java | 62 - .../AnnotationHandlerBeanPostProcessor.java | 86 - .../ingest/core/dispatcher/Dispatcher.java | 304 --- .../core/dispatcher/HandlerDefinition.java | 31 - .../core/dispatcher/HandlerInvoker.java | 34 - .../core/dispatcher/HandlerRegistry.java | 51 - .../core/pipeline/InterceptorChain.java | 51 - .../pipeline/builtin/DedupInterceptor.java | 80 - .../builtin/RateLimitInterceptor.java | 81 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../DisruptorEventBusAwaitTest.java | 127 -- .../IngestCoreAutoConfigurationTest.java | 20 - .../DispatcherDurableAckBoundaryTest.java | 217 --- .../DispatcherRawArchiveMetadataTest.java | 232 --- .../core/dispatcher/HandlerRegistryTest.java | 51 - .../builtin/DedupInterceptorTest.java | 66 - .../builtin/RateLimitInterceptorTest.java | 55 - modules/core/ingest-facts/pom.xml | 30 - .../com/lingniu/ingest/facts/DecodedFact.java | 215 --- .../com/lingniu/ingest/facts/FactIds.java | 50 - .../com/lingniu/ingest/facts/ParseStatus.java | 7 - .../lingniu/ingest/facts/RawFrameFact.java | 65 - .../ingest/facts/RawFrameIdentity.java | 43 - .../com/lingniu/ingest/facts/VehicleKey.java | 46 - .../lingniu/ingest/facts/DecodedFactTest.java | 39 - .../com/lingniu/ingest/facts/FactIdsTest.java | 38 - .../ingest/facts/RawFrameFactTest.java | 63 - .../ingest/facts/RawFrameIdentityTest.java | 36 - .../lingniu/ingest/facts/VehicleKeyTest.java | 42 - modules/core/observability/pom.xml | 38 - .../ingest/observability/IngestMetrics.java | 104 -- .../ObservabilityAutoConfiguration.java | 18 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../observability/IngestMetricsTest.java | 88 - modules/core/session-core/pom.xml | 52 - .../ingest/session/CommandDispatcher.java | 23 - .../lingniu/ingest/session/DeviceSession.java | 46 - .../ingest/session/NoopCommandDispatcher.java | 31 - .../ingest/session/RedisSessionStore.java | 191 -- .../ingest/session/SessionProperties.java | 35 - .../lingniu/ingest/session/SessionStore.java | 25 - .../config/SessionCoreAutoConfiguration.java | 37 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../ingest/session/RedisSessionStoreTest.java | 137 -- .../SessionCoreAutoConfigurationTest.java | 84 - modules/core/vehicle-identity/pom.xml | 61 - .../identity/MySqlVehicleIdentityService.java | 232 --- .../ingest/identity/VehicleIdentity.java | 16 - .../identity/VehicleIdentityBinding.java | 37 - .../identity/VehicleIdentityLookup.java | 22 - .../identity/VehicleIdentityRegistry.java | 9 - .../identity/VehicleIdentityResolver.java | 6 - .../identity/VehicleIdentitySource.java | 13 - .../identity/VehicleRegistrationBinding.java | 36 - .../VehicleIdentityAutoConfiguration.java | 43 - .../config/VehicleIdentityProperties.java | 34 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../MySqlVehicleIdentityServiceJdbcTest.java | 90 - .../MySqlVehicleIdentityServiceTest.java | 157 -- .../VehicleIdentityAutoConfigurationTest.java | 84 - modules/inbound/inbound-mqtt/pom.xml | 60 - .../mqtt/client/MqttEndpointManager.java | 380 ---- .../inbound/mqtt/client/MqttTlsSupport.java | 260 --- .../config/MqttInboundAutoConfiguration.java | 83 - .../config/MqttInboundEnabledCondition.java | 40 - .../mqtt/config/MqttInboundProperties.java | 93 - .../mqtt/handler/MqttRealtimeHandler.java | 35 - .../mqtt/mapper/YutongEventMapper.java | 260 --- .../inbound/mqtt/model/MqttPayload.java | 82 - .../mqtt/parser/MqttPayloadParser.java | 73 - .../inbound/mqtt/profile/MqttProfile.java | 20 - .../mqtt/profile/MqttProfileRegistry.java | 164 -- .../mqtt/profile/YutongMqttProfile.java | 36 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../inbound/mqtt/MqttEndpointManagerTest.java | 473 ----- .../inbound/mqtt/MqttProfileRegistryTest.java | 139 -- .../inbound/mqtt/MqttTlsSupportTest.java | 101 - .../inbound/mqtt/YutongEventMapperTest.java | 235 --- .../MqttInboundAutoConfigurationTest.java | 36 - .../MqttInboundEnabledConditionTest.java | 68 - modules/protocols/protocol-gb32960/pom.xml | 64 - .../auth/Gb32960PlatformAuthorizer.java | 103 - .../gb32960/auth/Gb32960VinAuthorizer.java | 67 - .../gb32960/codec/Gb32960BodyParser.java | 239 --- .../gb32960/codec/Gb32960CommandParser.java | 296 --- .../gb32960/codec/Gb32960FrameDecoder.java | 104 -- .../gb32960/codec/Gb32960FrameEncoder.java | 114 -- .../gb32960/codec/Gb32960MessageDecoder.java | 180 -- .../gb32960/codec/Gb32960ParserContext.java | 23 - .../gb32960/codec/InfoBlockParser.java | 34 - .../codec/InfoBlockParserRegistry.java | 35 - .../protocol/gb32960/codec/ValueDecoder.java | 82 - .../parser/v2016/AlarmV2016BlockParser.java | 46 - .../v2016/DriveMotorV2016BlockParser.java | 46 - .../parser/v2016/EngineV2016BlockParser.java | 28 - .../v2016/ExtremeValueV2016BlockParser.java | 41 - .../v2016/FuelCellV2016BlockParser.java | 55 - .../v2016/PositionV2016BlockParser.java | 33 - .../v2016/TemperatureV2016BlockParser.java | 40 - .../parser/v2016/VehicleV2016BlockParser.java | 44 - .../parser/v2016/VoltageV2016BlockParser.java | 51 - .../parser/v2025/AlarmV2025BlockParser.java | 53 - .../BatteryTemperatureV2025BlockParser.java | 38 - .../v2025/DriveMotorV2025BlockParser.java | 48 - .../parser/v2025/EngineV2025BlockParser.java | 27 - .../v2025/FuelCellStackV2025BlockParser.java | 46 - .../v2025/FuelCellV2025BlockParser.java | 36 - .../MinParallelVoltageV2025BlockParser.java | 42 - .../v2025/PositionV2025BlockParser.java | 34 - ...SuperCapacitorExtremeV2025BlockParser.java | 40 - .../v2025/SuperCapacitorV2025BlockParser.java | 42 - .../parser/v2025/VehicleV2025BlockParser.java | 39 - .../GdFcAirConditionerBlockParser.java | 34 - .../guangdong/GdFcAuxiliaryBlockParser.java | 61 - .../vendor/guangdong/GdFcDcDcBlockParser.java | 39 - .../GdFcDemoExtensionBlockParser.java | 47 - .../guangdong/GdFcStackBlockParser.java | 77 - .../guangdong/GdFcVehicleInfoBlockParser.java | 37 - .../guangdong/GdFcVendorTlvBlockParser.java | 57 - .../codec/profile/Gb32960ProfileRegistry.java | 65 - .../RuleBasedVendorExtensionSelector.java | 118 -- .../codec/profile/VendorExtensionCatalog.java | 41 - .../profile/VendorExtensionSelector.java | 21 - .../config/Gb32960AutoConfiguration.java | 253 --- .../gb32960/config/Gb32960Properties.java | 277 --- .../handler/Gb32960RealtimeHandler.java | 62 - .../gb32960/inbound/Gb32960AccessService.java | 77 - .../gb32960/inbound/Gb32960AckService.java | 121 -- .../inbound/Gb32960ChannelHandler.java | 510 ----- .../inbound/Gb32960ConnectionDiagnostics.java | 113 -- .../inbound/Gb32960FrameDiagnostics.java | 87 - .../gb32960/inbound/Gb32960NettyServer.java | 181 -- .../gb32960/mapper/Gb32960EventMapper.java | 344 ---- .../protocol/gb32960/model/CommandBody.java | 225 --- .../protocol/gb32960/model/CommandType.java | 60 - .../protocol/gb32960/model/EncryptType.java | 38 - .../protocol/gb32960/model/Gb32960Header.java | 36 - .../gb32960/model/Gb32960Message.java | 43 - .../gb32960/model/GeneralAlarmFlagBit.java | 104 -- .../protocol/gb32960/model/InfoBlock.java | 463 ----- .../protocol/gb32960/model/InfoBlockType.java | 63 - .../gb32960/model/ProtocolVersion.java | 37 - .../protocol/gb32960/model/ResponseFlag.java | 46 - .../protocol/gb32960/model/SignatureInfo.java | 66 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../codec/Gb32960BodyParserIsolationTest.java | 172 -- .../codec/Gb32960DecoderGoldenTest.java | 106 -- .../gb32960/codec/Gb32960DecoderTest.java | 158 -- .../codec/Gb32960FrameDecoderTest.java | 59 - .../gb32960/codec/Gb32960FullBlocksTest.java | 152 -- .../codec/GuangdongFcEndToEndTest.java | 296 --- .../vendor/guangdong/GdFcParserTest.java | 87 - .../GdFcVendorTlvBlockParserTest.java | 118 -- .../RuleBasedVendorExtensionSelectorTest.java | 106 -- .../config/Gb32960AutoConfigurationTest.java | 37 - .../handler/Gb32960RealtimeHandlerTest.java | 79 - .../inbound/Gb32960AccessServiceTest.java | 86 - .../inbound/Gb32960AckServiceTest.java | 42 - .../Gb32960ChannelHandlerAckBoundaryTest.java | 235 --- .../Gb32960ConnectionDiagnosticsTest.java | 78 - .../inbound/Gb32960FrameDiagnosticsTest.java | 45 - .../mapper/Gb32960EventMapperTest.java | 88 - .../test/resources/samples/gb32960/README.md | 41 - .../samples/gb32960/heartbeat_001.hex | 1 - .../samples/gb32960/heartbeat_002.hex | 1 - .../samples/gb32960/realtime_001.hex | 1 - .../samples/gb32960/realtime_002.hex | 1 - .../samples/gb32960/realtime_003.hex | 1 - .../samples/gb32960/realtime_010.hex | 1 - .../samples/gb32960/realtime_020.hex | 1 - .../samples/gb32960/realtime_100.hex | 1 - .../samples/gb32960/realtime_200.hex | 1 - .../resources/samples/gb32960/resend_001.hex | 1 - .../resources/samples/gb32960/resend_005.hex | 1 - .../samples/gb32960/vehicle_login_001.hex | 1 - modules/protocols/protocol-jsatl12/pom.xml | 66 - .../jsatl12/codec/Jsatl12FrameDecoder.java | 102 - .../jsatl12/codec/Jsatl12RawFrame.java | 8 - .../config/Jsatl12AutoConfiguration.java | 58 - .../jsatl12/config/Jsatl12Properties.java | 25 - .../handler/Jsatl12ArchiveEventHandler.java | 137 -- .../inbound/Jsatl12ArchiveFailure.java | 12 - .../jsatl12/inbound/Jsatl12ArchivedChunk.java | 45 - .../inbound/Jsatl12AttachmentTracker.java | 165 -- .../inbound/Jsatl12ChannelHandler.java | 249 --- .../jsatl12/inbound/Jsatl12DataPacket.java | 66 - .../inbound/Jsatl12JtMessageBridge.java | 184 -- .../inbound/Jsatl12MalformedFrame.java | 10 - .../inbound/Jsatl12MalformedJtMessage.java | 10 - .../jsatl12/inbound/Jsatl12MessageId.java | 11 - .../jsatl12/inbound/Jsatl12NettyServer.java | 111 -- .../inbound/Jsatl12UploadCompletionAck.java | 17 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../codec/Jsatl12DataPacketDecoderTest.java | 47 - .../codec/Jsatl12FrameDecoderTest.java | 72 - .../Jsatl12ArchiveEventHandlerTest.java | 175 -- .../inbound/Jsatl12AttachmentTrackerTest.java | 101 - .../inbound/Jsatl12ChannelHandlerTest.java | 297 --- .../inbound/Jsatl12JtMessageBridgeTest.java | 237 --- modules/protocols/protocol-jt1078/pom.xml | 71 - .../codec/parser/FileListBodyParser.java | 23 - .../parser/FileUploadCompleteBodyParser.java | 23 - .../parser/MediaAttributesBodyParser.java | 39 - .../parser/PassengerVolumeBodyParser.java | 56 - .../config/Jt1078AutoConfiguration.java | 105 -- .../jt1078/config/Jt1078Properties.java | 49 - .../config/Jt1078SignalAutoConfiguration.java | 25 - .../jt1078/downlink/Jt1078Commands.java | 198 -- .../handler/Jt1078MalformedRtpHandler.java | 84 - .../handler/Jt1078MediaSignalHandler.java | 36 - .../Jt1078MalformedRtpEventPublisher.java | 76 - .../media/Jt1078MalformedRtpPacket.java | 22 - .../media/Jt1078MediaArchiveService.java | 197 -- .../jt1078/media/Jt1078MediaMessageId.java | 9 - .../media/Jt1078MediaStreamHandler.java | 36 - .../jt1078/media/Jt1078MediaStreamServer.java | 83 - .../Jt1078ReceivedMediaStreamHandler.java | 27 - .../jt1078/media/Jt1078ReceivedRtpPacket.java | 10 - .../jt1078/media/Jt1078RtpPacket.java | 11 - .../jt1078/media/Jt1078RtpPacketDecoder.java | 83 - .../jt1078/media/Jt1078RtpPacketParser.java | 61 - .../media/Jt1078UdpMediaStreamServer.java | 78 - .../media/Jt1078UdpRtpPacketDecoder.java | 59 - .../jt1078/model/Jt1078MessageId.java | 21 - ...ot.autoconfigure.AutoConfiguration.imports | 2 - .../jt1078/Jt1078EventMappingTest.java | 93 - .../Jt1078MalformedRtpEventPublisherTest.java | 109 -- .../jt1078/Jt1078MalformedRtpHandlerTest.java | 134 -- .../jt1078/Jt1078MediaArchiveServiceTest.java | 198 -- .../protocol/jt1078/Jt1078ParserTest.java | 87 - .../jt1078/Jt1078RtpPacketDecoderTest.java | 91 - .../jt1078/Jt1078UdpRtpPacketDecoderTest.java | 89 - .../config/Jt1078AutoConfigurationTest.java | 270 --- .../jt1078/downlink/Jt1078CommandsTest.java | 127 -- modules/protocols/protocol-jt808/pom.xml | 64 - .../protocol/jt808/codec/BodyParser.java | 19 - .../jt808/codec/BodyParserRegistry.java | 25 - .../protocol/jt808/codec/Jt808Escape.java | 59 - .../jt808/codec/Jt808FrameDecoder.java | 67 - .../jt808/codec/Jt808FrameEncoder.java | 137 -- .../jt808/codec/Jt808MalformedFrame.java | 7 - .../jt808/codec/Jt808MessageDecoder.java | 96 - .../jt808/codec/parser/AuthBodyParser.java | 49 - .../codec/parser/HeartbeatBodyParser.java | 14 - .../codec/parser/LocationBatchBodyParser.java | 40 - .../codec/parser/LocationBodyParser.java | 95 - .../codec/parser/MediaEventBodyParser.java | 26 - .../codec/parser/MediaUploadBodyParser.java | 38 - .../codec/parser/PassthroughBodyParser.java | 24 - .../codec/parser/RegisterBodyParser.java | 54 - .../codec/parser/TerminalAttrsBodyParser.java | 58 - .../TerminalParamsReportBodyParser.java | 36 - .../codec/parser/UnregisterBodyParser.java | 22 - .../jt808/config/Jt808AutoConfiguration.java | 117 -- .../jt808/config/Jt808Properties.java | 20 - .../downlink/Jt808CommandDispatcher.java | 134 -- .../jt808/downlink/Jt808Commands.java | 118 -- .../jt808/handler/Jt808LocationHandler.java | 81 - .../jt808/inbound/Jt808ChannelHandler.java | 547 ------ .../jt808/inbound/Jt808NettyServer.java | 104 -- .../jt808/mapper/Jt808EventMapper.java | 379 ---- .../protocol/jt808/model/Jt808Body.java | 163 -- .../protocol/jt808/model/Jt808Header.java | 28 - .../model/Jt808LocationAdditionalInfo.java | 247 --- .../jt808/model/Jt808LocationFlagInfo.java | 99 - .../protocol/jt808/model/Jt808Message.java | 6 - .../protocol/jt808/model/Jt808MessageId.java | 91 - .../jt808/session/Jt808ChannelRegistry.java | 67 - .../jt808/session/Jt808PendingRequests.java | 52 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../protocol/jt808/TestSessionStore.java | 74 - .../jt808/codec/Jt808DecoderGoldenTest.java | 79 - .../jt808/codec/Jt808DecoderTest.java | 274 --- .../protocol/jt808/codec/Jt808EscapeTest.java | 25 - .../jt808/codec/Jt808FrameDecoderTest.java | 39 - .../jt808/codec/Jt808FrameEncoderTest.java | 91 - .../jt808/config/Jt808PropertiesTest.java | 34 - .../downlink/Jt808CommandDispatcherTest.java | 56 - .../jt808/downlink/Jt808CommandsTest.java | 21 - .../handler/Jt808LocationHandlerTest.java | 48 - .../inbound/Jt808ChannelHandlerTest.java | 857 --------- .../jt808/mapper/Jt808EventMapperTest.java | 326 ---- .../test/resources/samples/jt808/auth_001.hex | 1 - .../test/resources/samples/jt808/auth_002.hex | 1 - .../resources/samples/jt808/location_001.hex | 1 - .../resources/samples/jt808/location_002.hex | 1 - .../resources/samples/jt808/location_010.hex | 1 - .../resources/samples/jt808/location_100.hex | 1 - .../resources/samples/jt808/location_200.hex | 1 - .../resources/samples/jt808/location_400.hex | 1 - .../samples/jt808/location_batch_001.hex | 1 - .../services/event-history-service/pom.xml | 65 - .../eventhistory/ApiExceptionHandler.java | 114 -- .../DefaultProductionProtocols.java | 27 - .../EventHistoryEnvelopeIngestor.java | 126 -- .../Gb32960DecodedFrameService.java | 865 --------- .../eventhistory/Gb32960FieldDictionary.java | 207 --- .../eventhistory/Gb32960FrameController.java | 214 --- .../eventhistory/HistoryQueryOrder.java | 6 - .../Jt808LocationHistoryController.java | 148 -- .../Jt808RawFrameHistoryController.java | 178 -- .../LocationHistoryController.java | 183 -- .../ingest/eventhistory/QueryTimeRange.java | 87 - .../RawFrameHistoryController.java | 261 --- .../config/EventHistoryAutoConfiguration.java | 198 -- ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../eventhistory/ApiExceptionHandlerTest.java | 101 - .../EventHistoryEnvelopeIngestorTest.java | 176 -- .../Gb32960DecodedFrameServiceTest.java | 685 ------- .../Gb32960FieldDictionaryTest.java | 30 - .../Jt808LocationHistoryControllerTest.java | 140 -- .../Jt808RawFrameHistoryControllerTest.java | 124 -- .../LocationHistoryControllerTest.java | 132 -- .../RawFrameHistoryControllerTest.java | 277 --- .../EventHistoryAutoConfigurationTest.java | 165 -- .../config/EventHistoryPomBoundaryTest.java | 91 - modules/services/vehicle-stat-service/pom.xml | 66 - .../vehiclestat/DailyMileageStrategy.java | 13 - .../JdbcVehicleStatMetricRepository.java | 184 -- .../vehiclestat/VehicleDailyStatResult.java | 25 - .../vehiclestat/VehicleStatController.java | 52 - .../VehicleStatEnvelopeIngestor.java | 62 - .../vehiclestat/VehicleStatRepository.java | 11 - .../config/VehicleStatAutoConfiguration.java | 84 - .../config/VehicleStatProperties.java | 40 - .../vehiclestat/jt808/Jt808LocationPoint.java | 27 - .../jt808/Jt808LocationPointExtractor.java | 97 - .../jt808/Jt808MileageStreamProcessor.java | 42 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../JdbcVehicleStatMetricRepositoryTest.java | 203 -- .../VehicleStatControllerTest.java | 55 - .../VehicleStatEnvelopeIngestorTest.java | 136 -- .../VehicleStatRepositoryContractTest.java | 76 - .../VehicleStatAutoConfigurationTest.java | 113 -- .../Jt808LocationPointExtractorTest.java | 99 - .../Jt808MileageStreamProcessorTest.java | 135 -- .../services/vehicle-state-service/pom.xml | 57 - .../RedisVehicleStateRepository.java | 73 - .../vehiclestate/VehicleStateController.java | 56 - .../VehicleStateEnvelopeIngestor.java | 56 - .../vehiclestate/VehicleStateRepository.java | 22 - .../vehiclestate/VehicleStateUpdater.java | 145 -- .../config/VehicleStateAutoConfiguration.java | 58 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../InMemoryVehicleStateRepository.java | 50 - .../RedisVehicleStateRepositoryTest.java | 50 - .../VehicleStateControllerTest.java | 54 - .../VehicleStateEnvelopeIngestorTest.java | 111 -- .../vehiclestate/VehicleStateUpdaterTest.java | 80 - .../VehicleStateAutoConfigurationTest.java | 56 - modules/sinks/sink-archive/pom.xml | 39 - .../ingest/sink/archive/ArchiveStore.java | 17 - .../sink/archive/LocalArchiveStore.java | 150 -- .../sink/archive/RawArchiveEventSink.java | 150 -- .../config/SinkArchiveAutoConfiguration.java | 32 - .../archive/config/SinkArchiveProperties.java | 35 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../sink/archive/LocalArchiveStoreTest.java | 74 - .../sink/archive/RawArchiveEventSinkTest.java | 77 - .../SinkArchiveAutoConfigurationTest.java | 40 - modules/sinks/sink-kafka/pom.xml | 96 - .../ingest/sink/kafka/EnvelopeMapper.java | 226 --- .../kafka/KafkaEnvelopeConsumerFactory.java | 142 -- .../kafka/KafkaEnvelopeConsumerRunner.java | 182 -- .../kafka/KafkaEnvelopeConsumerWorker.java | 75 - .../kafka/KafkaEnvelopeDeadLetterSink.java | 53 - .../ingest/sink/kafka/KafkaEventSink.java | 118 -- .../kafka/KafkaSinkAutoConfiguration.java | 86 - .../KafkaSinkConsumerAutoConfiguration.java | 51 - .../sink/kafka/KafkaSinkProperties.java | 151 -- .../ingest/sink/kafka/TopicRouter.java | 29 - .../src/main/proto/vehicle_envelope.proto | 168 -- ...ot.autoconfigure.AutoConfiguration.imports | 2 - .../EnvelopeMapperTelemetrySnapshotTest.java | 102 - .../KafkaEnvelopeConsumerFactoryTest.java | 79 - .../KafkaEnvelopeConsumerRunnerTest.java | 53 - .../KafkaEnvelopeConsumerWorkerTest.java | 84 - .../KafkaEnvelopeDeadLetterSinkTest.java | 54 - .../ingest/sink/kafka/KafkaEventSinkTest.java | 83 - ...afkaSinkConsumerAutoConfigurationTest.java | 74 - .../sink/kafka/KafkaSinkPropertiesTest.java | 39 - .../ingest/sink/kafka/TopicRouterTest.java | 147 -- ...VehicleEnvelopeProtoCompatibilityTest.java | 65 - modules/sinks/tdengine-history-store/pom.xml | 60 - .../TdengineBatchStatement.java | 15 - .../tdenginehistory/TdengineEnvelopeRows.java | 164 -- .../TdengineHistoryQueries.java | 129 -- .../TdengineHistoryReader.java | 16 - .../TdengineHistorySchema.java | 90 - .../TdengineHistoryStatements.java | 64 - .../TdengineHistoryWriter.java | 19 - .../tdenginehistory/TdengineIdentifier.java | 42 - .../TdengineJdbcHistoryReader.java | 185 -- .../TdengineJdbcHistoryWriter.java | 248 --- .../TdengineLocationQuery.java | 31 - .../tdenginehistory/TdengineLocationRow.java | 24 - .../ingest/tdenginehistory/TdenginePage.java | 12 - .../tdenginehistory/TdenginePageCursor.java | 13 - .../tdenginehistory/TdengineQueryOrder.java | 6 - .../TdengineQueryStatement.java | 15 - .../TdengineRawFrameQuery.java | 60 - .../tdenginehistory/TdengineRawFrameRow.java | 25 - .../TdengineHistoryAutoConfiguration.java | 85 - .../config/TdengineHistoryProperties.java | 119 -- ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../TdengineEnvelopeRowsTest.java | 173 -- .../TdengineHistoryQueriesTest.java | 95 - .../TdengineHistorySchemaTest.java | 66 - .../TdengineHistoryStatementsTest.java | 85 - .../TdengineJdbcHistoryReaderTest.java | 199 -- .../TdengineJdbcHistoryWriterTest.java | 307 --- .../TdengineHistoryAutoConfigurationTest.java | 81 - .../vehicle-identity-test-support/pom.xml | 20 - .../InMemoryVehicleIdentityService.java | 48 - pom.xml | 379 ---- reference/GBT+32960.3-2016.pdf | Bin 1152211 -> 0 bytes reference/GBT+32960.3-2025.pdf | Bin 734436 -> 0 bytes ...综合监管平台-燃料电池汽车数据接入技术规范v1.0.220822.pdf | Bin 5029401 -> 0 bytes tools/gb32960_e2e_smoke.py | 358 ---- tools/go_kafka_prod_smoke.py | 162 -- tools/go_native_deploy.py | 253 --- tools/go_native_prod_smoke.py | 544 ------ tools/go_prod_acceptance.py | 136 -- tools/go_systemd_prod_smoke.py | 210 --- tools/jt808_e2e_smoke.py | 543 ------ tools/tdengine_smoke.py | 61 - tools/test_gb32960_e2e_smoke.py | 107 -- tools/test_go_kafka_prod_smoke.py | 62 - tools/test_go_native_deploy.py | 63 - tools/test_go_native_prod_smoke.py | 358 ---- tools/test_go_prod_acceptance.py | 53 - tools/test_go_systemd_prod_smoke.py | 101 - tools/test_jt808_e2e_smoke.py | 134 -- tools/test_tdengine_smoke.py | 35 - tools/test_vehicle_ingest_live_verify.py | 142 -- tools/vehicle_ingest_live_verify.py | 392 ---- woodpecker.yml | 78 - 561 files changed, 59865 deletions(-) delete mode 100644 .dockerignore delete mode 100644 CHANGELOG.md delete mode 100644 DECISIONS.md delete mode 100644 Dockerfile delete mode 100644 README.md delete mode 100644 deploy/portainer/docker-compose-go.yml delete mode 100644 deploy/portainer/docker-compose.yml delete mode 100644 deploy/systemd/README.md delete mode 100644 deploy/systemd/lingniu-go-gateway.service delete mode 100644 deploy/systemd/lingniu-go-history-writer.service delete mode 100644 deploy/systemd/lingniu-go-realtime-api.service delete mode 100644 deploy/systemd/lingniu-go-stat-writer.service delete mode 100644 deploy/tools/EnsureKafkaTopics.java delete mode 100644 docs/module-data-flow.html delete mode 100644 docs/operations/current-ecs-deployment.md delete mode 100644 docs/operations/gb32960-service-split-runbook.md delete mode 100644 docs/operations/go-vehicle-gateway-verification-result-2026-07-01.md delete mode 100644 docs/operations/go-vehicle-gateway-verification.md delete mode 100644 docs/operations/jt808-daily-mileage-runbook.md delete mode 100644 docs/operations/vehicle-ingest-tdengine-verification.md delete mode 100644 docs/superpowers/plans/2026-04-20-gb32960-body-parser-block-isolation.md delete mode 100644 docs/superpowers/plans/2026-06-23-gb32960-history-duckdb-hot-store.md delete mode 100644 docs/superpowers/plans/2026-06-23-gb32960-service-split.md delete mode 100644 docs/superpowers/plans/2026-06-29-vehicle-ingest-redesign-phase1.md delete mode 100644 docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md delete mode 100644 docs/superpowers/plans/2026-07-01-go-ingest-redesign-phase1.md delete mode 100644 docs/superpowers/specs/2026-06-23-gb32960-production-readiness-design.md delete mode 100644 docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md delete mode 100644 docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md delete mode 100644 docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.zh.md delete mode 100644 docs/superpowers/specs/2026-07-01-go-ingest-redesign-design.md delete mode 100644 docs/target-architecture.md delete mode 100644 docs/vehicle-telemetry-internal-fields.md delete mode 100644 modules/apps/command-gateway/pom.xml delete mode 100644 modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfiguration.java delete mode 100644 modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandResult.java delete mode 100644 modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/TerminalCommandController.java delete mode 100644 modules/apps/command-gateway/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfigurationTest.java delete mode 100644 modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/TerminalCommandControllerTest.java delete mode 100644 modules/apps/gb32960-ingest-app/pom.xml delete mode 100644 modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java delete mode 100644 modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpoint.java delete mode 100644 modules/apps/gb32960-ingest-app/src/main/resources/application.yml delete mode 100644 modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppCompositionTest.java delete mode 100644 modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppDefaultsTest.java delete mode 100644 modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpointTest.java delete mode 100644 modules/apps/jt808-ingest-app/pom.xml delete mode 100644 modules/apps/jt808-ingest-app/src/main/java/com/lingniu/ingest/jt808app/Jt808IngestApplication.java delete mode 100644 modules/apps/jt808-ingest-app/src/main/resources/application.yml delete mode 100644 modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppCompositionTest.java delete mode 100644 modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppDefaultsTest.java delete mode 100644 modules/apps/vehicle-analytics-app/pom.xml delete mode 100644 modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java delete mode 100644 modules/apps/vehicle-analytics-app/src/main/resources/application.yml delete mode 100644 modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppCompositionTest.java delete mode 100644 modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppDefaultsTest.java delete mode 100644 modules/apps/vehicle-history-app/pom.xml delete mode 100644 modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java delete mode 100644 modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryKafkaConsumerConfiguration.java delete mode 100644 modules/apps/vehicle-history-app/src/main/resources/application.yml delete mode 100644 modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java delete mode 100644 modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java delete mode 100644 modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppCompositionTest.java delete mode 100644 modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java delete mode 100644 modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/WoodpeckerPipelineTest.java delete mode 100644 modules/apps/yutong-mqtt-app/pom.xml delete mode 100644 modules/apps/yutong-mqtt-app/src/main/java/com/lingniu/ingest/yutongmqttapp/YutongMqttApplication.java delete mode 100644 modules/apps/yutong-mqtt-app/src/main/resources/application.yml delete mode 100644 modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppCompositionTest.java delete mode 100644 modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppDefaultsTest.java delete mode 100644 modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttPortainerComposeTest.java delete mode 100644 modules/core/ingest-api/pom.xml delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/ProtocolId.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/AsyncBatch.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/EventEmit.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/IdempotentKey.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/MessageMapping.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/ProtocolHandler.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/RateLimited.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/TraceSpan.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeBatchIngestor.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessor.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerRecord.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterRecord.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterSink.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestResult.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestor.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/AlarmPayload.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/LocationPayload.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RawArchiveKeys.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RealtimePayload.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryFieldValue.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryMetadataValues.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetrySnapshot.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEvent.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapper.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/package-info.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestContext.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestInterceptor.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/RawFrame.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/sink/EventSink.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/DecodeException.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/EventMapper.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameDecoder.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameEncoder.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/MessageHandler.java delete mode 100644 modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/ProtocolPlugin.java delete mode 100644 modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessorTest.java delete mode 100644 modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/RawArchiveKeysTest.java delete mode 100644 modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryFieldValueTest.java delete mode 100644 modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryMetadataValuesTest.java delete mode 100644 modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetrySnapshotTest.java delete mode 100644 modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapperTest.java delete mode 100644 modules/core/ingest-codec-common/pom.xml delete mode 100644 modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BccChecksum.java delete mode 100644 modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BcdCodec.java delete mode 100644 modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BitUtils.java delete mode 100644 modules/core/ingest-codec-common/src/test/java/com/lingniu/ingest/codec/BcdCodecTest.java delete mode 100644 modules/core/ingest-core/pom.xml delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/AsyncBatchExecutor.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/DisruptorEventBus.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/VehicleEventSlot.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreAutoConfiguration.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreProperties.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/AnnotationHandlerBeanPostProcessor.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/Dispatcher.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerDefinition.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerInvoker.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerRegistry.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/InterceptorChain.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptor.java delete mode 100644 modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptor.java delete mode 100644 modules/core/ingest-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/concurrency/DisruptorEventBusAwaitTest.java delete mode 100644 modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/config/IngestCoreAutoConfigurationTest.java delete mode 100644 modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherDurableAckBoundaryTest.java delete mode 100644 modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherRawArchiveMetadataTest.java delete mode 100644 modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/HandlerRegistryTest.java delete mode 100644 modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptorTest.java delete mode 100644 modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptorTest.java delete mode 100644 modules/core/ingest-facts/pom.xml delete mode 100644 modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/DecodedFact.java delete mode 100644 modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/FactIds.java delete mode 100644 modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/ParseStatus.java delete mode 100644 modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameFact.java delete mode 100644 modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameIdentity.java delete mode 100644 modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/VehicleKey.java delete mode 100644 modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/DecodedFactTest.java delete mode 100644 modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/FactIdsTest.java delete mode 100644 modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameFactTest.java delete mode 100644 modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameIdentityTest.java delete mode 100644 modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/VehicleKeyTest.java delete mode 100644 modules/core/observability/pom.xml delete mode 100644 modules/core/observability/src/main/java/com/lingniu/ingest/observability/IngestMetrics.java delete mode 100644 modules/core/observability/src/main/java/com/lingniu/ingest/observability/ObservabilityAutoConfiguration.java delete mode 100644 modules/core/observability/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/core/observability/src/test/java/com/lingniu/ingest/observability/IngestMetricsTest.java delete mode 100644 modules/core/session-core/pom.xml delete mode 100644 modules/core/session-core/src/main/java/com/lingniu/ingest/session/CommandDispatcher.java delete mode 100644 modules/core/session-core/src/main/java/com/lingniu/ingest/session/DeviceSession.java delete mode 100644 modules/core/session-core/src/main/java/com/lingniu/ingest/session/NoopCommandDispatcher.java delete mode 100644 modules/core/session-core/src/main/java/com/lingniu/ingest/session/RedisSessionStore.java delete mode 100644 modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionProperties.java delete mode 100644 modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionStore.java delete mode 100644 modules/core/session-core/src/main/java/com/lingniu/ingest/session/config/SessionCoreAutoConfiguration.java delete mode 100644 modules/core/session-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/core/session-core/src/test/java/com/lingniu/ingest/session/RedisSessionStoreTest.java delete mode 100644 modules/core/session-core/src/test/java/com/lingniu/ingest/session/config/SessionCoreAutoConfigurationTest.java delete mode 100644 modules/core/vehicle-identity/pom.xml delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/MySqlVehicleIdentityService.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentity.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityBinding.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityLookup.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityRegistry.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityResolver.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentitySource.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleRegistrationBinding.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfiguration.java delete mode 100644 modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityProperties.java delete mode 100644 modules/core/vehicle-identity/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceJdbcTest.java delete mode 100644 modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceTest.java delete mode 100644 modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfigurationTest.java delete mode 100644 modules/inbound/inbound-mqtt/pom.xml delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttEndpointManager.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttTlsSupport.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfiguration.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledCondition.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundProperties.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/handler/MqttRealtimeHandler.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/model/MqttPayload.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/parser/MqttPayloadParser.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfile.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfileRegistry.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/YutongMqttProfile.java delete mode 100644 modules/inbound/inbound-mqtt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttEndpointManagerTest.java delete mode 100644 modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttProfileRegistryTest.java delete mode 100644 modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttTlsSupportTest.java delete mode 100644 modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/YutongEventMapperTest.java delete mode 100644 modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfigurationTest.java delete mode 100644 modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledConditionTest.java delete mode 100644 modules/protocols/protocol-gb32960/pom.xml delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960PlatformAuthorizer.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960VinAuthorizer.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960CommandParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoder.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameEncoder.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960ParserContext.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParserRegistry.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/ValueDecoder.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/AlarmV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/DriveMotorV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/EngineV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/ExtremeValueV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/FuelCellV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/PositionV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/TemperatureV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VehicleV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VoltageV2016BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/AlarmV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/BatteryTemperatureV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/DriveMotorV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/EngineV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellStackV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/MinParallelVoltageV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/PositionV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorExtremeV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/VehicleV2025BlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAirConditionerBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAuxiliaryBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDcDcBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDemoExtensionBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcStackBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVehicleInfoBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParser.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/Gb32960ProfileRegistry.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelector.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionCatalog.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionSelector.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960Properties.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessService.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckService.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnostics.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnostics.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960NettyServer.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandBody.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandType.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/EncryptType.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Header.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Message.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/GeneralAlarmFlagBit.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ProtocolVersion.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ResponseFlag.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/SignatureInfo.java delete mode 100644 modules/protocols/protocol-gb32960/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParserIsolationTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderGoldenTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoderTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FullBlocksTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/GuangdongFcEndToEndTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcParserTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParserTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelectorTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfigurationTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandlerTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessServiceTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckServiceTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandlerAckBoundaryTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnosticsTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnosticsTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapperTest.java delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/README.md delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_001.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_002.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_001.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_002.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_003.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_010.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_020.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_100.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_200.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_001.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_005.hex delete mode 100644 modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/vehicle_login_001.hex delete mode 100644 modules/protocols/protocol-jsatl12/pom.xml delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoder.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12RawFrame.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12AutoConfiguration.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12Properties.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandler.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchiveFailure.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchivedChunk.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTracker.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandler.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12DataPacket.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridge.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedFrame.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedJtMessage.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MessageId.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12NettyServer.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12UploadCompletionAck.java delete mode 100644 modules/protocols/protocol-jsatl12/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12DataPacketDecoderTest.java delete mode 100644 modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoderTest.java delete mode 100644 modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandlerTest.java delete mode 100644 modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTrackerTest.java delete mode 100644 modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandlerTest.java delete mode 100644 modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridgeTest.java delete mode 100644 modules/protocols/protocol-jt1078/pom.xml delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileListBodyParser.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileUploadCompleteBodyParser.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/MediaAttributesBodyParser.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/PassengerVolumeBodyParser.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfiguration.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078Properties.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078SignalAutoConfiguration.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078Commands.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MalformedRtpHandler.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MediaSignalHandler.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpEventPublisher.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpPacket.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaArchiveService.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaMessageId.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamHandler.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamServer.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedMediaStreamHandler.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedRtpPacket.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacket.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketDecoder.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketParser.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpMediaStreamServer.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpRtpPacketDecoder.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/model/Jt1078MessageId.java delete mode 100644 modules/protocols/protocol-jt1078/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078EventMappingTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpEventPublisherTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpHandlerTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MediaArchiveServiceTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078ParserTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078RtpPacketDecoderTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078UdpRtpPacketDecoderTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfigurationTest.java delete mode 100644 modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078CommandsTest.java delete mode 100644 modules/protocols/protocol-jt808/pom.xml delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParserRegistry.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808Escape.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoder.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoder.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MalformedFrame.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MessageDecoder.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/AuthBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/HeartbeatBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/LocationBatchBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/LocationBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaEventBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaUploadBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/PassthroughBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/RegisterBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalAttrsBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalParamsReportBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/UnregisterBodyParser.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808AutoConfiguration.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808Properties.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcher.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808Commands.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandler.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808NettyServer.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Body.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Header.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationAdditionalInfo.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationFlagInfo.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Message.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808MessageId.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808ChannelRegistry.java delete mode 100644 modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808PendingRequests.java delete mode 100644 modules/protocols/protocol-jt808/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/TestSessionStore.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderGoldenTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808EscapeTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoderTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoderTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/config/Jt808PropertiesTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcherTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandsTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandlerTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_001.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_002.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_001.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_002.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_010.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_100.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_200.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_400.hex delete mode 100644 modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_batch_001.hex delete mode 100644 modules/services/event-history-service/pom.xml delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/DefaultProductionProtocols.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestor.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameService.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionary.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FrameController.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/HistoryQueryOrder.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryController.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryController.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/LocationHistoryController.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/QueryTimeRange.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/RawFrameHistoryController.java delete mode 100644 modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfiguration.java delete mode 100644 modules/services/event-history-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestorTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameServiceTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionaryTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryControllerTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryControllerTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/LocationHistoryControllerTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/RawFrameHistoryControllerTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfigurationTest.java delete mode 100644 modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryPomBoundaryTest.java delete mode 100644 modules/services/vehicle-stat-service/pom.xml delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/DailyMileageStrategy.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleDailyStatResult.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatController.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestor.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatRepository.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfiguration.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatProperties.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPoint.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractor.java delete mode 100644 modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessor.java delete mode 100644 modules/services/vehicle-stat-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java delete mode 100644 modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatControllerTest.java delete mode 100644 modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestorTest.java delete mode 100644 modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java delete mode 100644 modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfigurationTest.java delete mode 100644 modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractorTest.java delete mode 100644 modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessorTest.java delete mode 100644 modules/services/vehicle-state-service/pom.xml delete mode 100644 modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepository.java delete mode 100644 modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateController.java delete mode 100644 modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestor.java delete mode 100644 modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateRepository.java delete mode 100644 modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdater.java delete mode 100644 modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfiguration.java delete mode 100644 modules/services/vehicle-state-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/InMemoryVehicleStateRepository.java delete mode 100644 modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepositoryTest.java delete mode 100644 modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateControllerTest.java delete mode 100644 modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestorTest.java delete mode 100644 modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdaterTest.java delete mode 100644 modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfigurationTest.java delete mode 100644 modules/sinks/sink-archive/pom.xml delete mode 100644 modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/ArchiveStore.java delete mode 100644 modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/LocalArchiveStore.java delete mode 100644 modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/RawArchiveEventSink.java delete mode 100644 modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfiguration.java delete mode 100644 modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveProperties.java delete mode 100644 modules/sinks/sink-archive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/LocalArchiveStoreTest.java delete mode 100644 modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/RawArchiveEventSinkTest.java delete mode 100644 modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfigurationTest.java delete mode 100644 modules/sinks/sink-kafka/pom.xml delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/EnvelopeMapper.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactory.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunner.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorker.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSink.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEventSink.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkAutoConfiguration.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfiguration.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java delete mode 100644 modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/TopicRouter.java delete mode 100644 modules/sinks/sink-kafka/src/main/proto/vehicle_envelope.proto delete mode 100644 modules/sinks/sink-kafka/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/EnvelopeMapperTelemetrySnapshotTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactoryTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunnerTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorkerTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSinkTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEventSinkTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfigurationTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkPropertiesTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java delete mode 100644 modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/VehicleEnvelopeProtoCompatibilityTest.java delete mode 100644 modules/sinks/tdengine-history-store/pom.xml delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineBatchStatement.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRows.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueries.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryReader.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchema.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatements.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryWriter.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineIdentifier.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReader.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationQuery.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationRow.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePage.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePageCursor.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryOrder.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryStatement.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameQuery.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameRow.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfiguration.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryProperties.java delete mode 100644 modules/sinks/tdengine-history-store/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRowsTest.java delete mode 100644 modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueriesTest.java delete mode 100644 modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchemaTest.java delete mode 100644 modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatementsTest.java delete mode 100644 modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReaderTest.java delete mode 100644 modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java delete mode 100644 modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfigurationTest.java delete mode 100644 modules/testing/vehicle-identity-test-support/pom.xml delete mode 100644 modules/testing/vehicle-identity-test-support/src/main/java/com/lingniu/ingest/identity/InMemoryVehicleIdentityService.java delete mode 100644 pom.xml delete mode 100644 reference/GBT+32960.3-2016.pdf delete mode 100644 reference/GBT+32960.3-2025.pdf delete mode 100644 reference/广东燃料电池汽车示范应用城市群综合监管平台-燃料电池汽车数据接入技术规范v1.0.220822.pdf delete mode 100644 tools/gb32960_e2e_smoke.py delete mode 100755 tools/go_kafka_prod_smoke.py delete mode 100644 tools/go_native_deploy.py delete mode 100755 tools/go_native_prod_smoke.py delete mode 100755 tools/go_prod_acceptance.py delete mode 100755 tools/go_systemd_prod_smoke.py delete mode 100755 tools/jt808_e2e_smoke.py delete mode 100644 tools/tdengine_smoke.py delete mode 100644 tools/test_gb32960_e2e_smoke.py delete mode 100644 tools/test_go_kafka_prod_smoke.py delete mode 100644 tools/test_go_native_deploy.py delete mode 100644 tools/test_go_native_prod_smoke.py delete mode 100644 tools/test_go_prod_acceptance.py delete mode 100644 tools/test_go_systemd_prod_smoke.py delete mode 100644 tools/test_jt808_e2e_smoke.py delete mode 100644 tools/test_tdengine_smoke.py delete mode 100644 tools/test_vehicle_ingest_live_verify.py delete mode 100755 tools/vehicle_ingest_live_verify.py delete mode 100644 woodpecker.yml diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 08b6fcca..00000000 --- a/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -.git -.idea -.worktrees -archive -**/.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e85b9c04..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,113 +0,0 @@ -# Changelog - -本文件记录本仓库的显著变更。日期使用 `YYYY-MM-DD` 格式。 - -格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本遵循 [SemVer](https://semver.org/lang/zh-CN/)。 - -> 说明:v0.1.0 为本仓库 git 初始化时的基线快照。下面列出的"主题改动"实际上都被压在 -> `chore: initial import of lingniu-vehicle-ingest` 这一个 commit 里——这是 git -> 初始化前已经完成的工作,无法事后拆分。从下一次提交开始走"小步分主题提交"流程。 - ---- - -## [Unreleased] - -### Added —— 原始报文冷存(Raw Archive)接入 EventSink 扇出 -- 新增 `VehicleEvent.RawArchive` sealed 变体(`ingest-api`),携带一帧入站的原始字节 + - `command` / `infoType` 供按协议命令分片归档。 -- `Dispatcher.dispatch` 在 interceptor **之前**按 `RawFrame.rawBytes()` 产出一条 - `RawArchive` 事件,保证 dedup / rate-limit **不过滤** raw archive,满足"每帧必留痕"目标。 -- 新增 `ArchiveEventSink implements EventSink`(`sink-archive`): - - `accepts` 仅 `VehicleEvent.RawArchive` - - 写入 key `yyyy/MM/dd///.bin`(UTC 日期分片) - - 同 eventId 重复写按 put 语义覆盖 - - 失败 completeExceptionally,EventBus 打 WARN - - AutoConfig 在 `ArchiveStore` bean 存在时自动装配 -- `KafkaEventSink.accepts(RawArchive)` 返回 `false`:本期 Kafka 不投递原始字节, - 只落 ArchiveStore。未来需要 Kafka 回填 URI 时再开(`TopicRouter` / `EnvelopeMapper` - 已补好 switch 分支)。 -- 覆盖测试 `ArchiveEventSinkTest` —— accepts 过滤 / 稳定 key 写入 / 幂等覆盖 3 case。 - -### Changed —— GB/T 32960 Body Parser 单块异常隔离 -- `Gb32960BodyParser` 主循环对单信息块的 parser 异常不再放弃整帧: - - 固定长度块(`fixedLength ≥ 0`):回滚 reader → 按 `fixedLen` 截取字节兜成 - `InfoBlock.Raw` → `continue` 继续解析后续块; - - 变长块(`fixedLength == -1`)或剩余字节不足:剩余字节全部兜成 `Raw` 后终止循环; - - `parser` 契约违反(声明 `fixedLen=X` 但实际读了 `Y`)仍抛 `DecodeException`, - 以暴露 parser bug,不走 lenient 兜底,避免静默误解析长期误判; - - 只捕获 `DecodeException` / `BufferUnderflowException` / `IndexOutOfBoundsException`, - `RuntimeException` 继续向上抛,保留 bug 可见性。 -- 新增配置 `lingniu.ingest.gb32960.parse.lenient-block-failure`(默认 `true`); - 需要回退严格模式时设为 `false`。 -- `InfoBlock.Raw` record 结构**未变**,下游 `Gb32960EventMapper` 按 `findBlock(Class)` - 类型匹配,新增 Raw 不影响业务事件映射。 -- 新增测试 `Gb32960BodyParserIsolationTest` —— 3 种隔离场景 + 严格模式回退,共 4 case。 - ---- - -## [0.1.0] — 2026-04-15 - -初始基线。多模块 Spring Boot 车辆遥测接入服务。 - -### Added -- **GB/T 32960.3 接入**(`protocol-gb32960`):Netty server、帧分帧器、消息解码器、 - V2016/V2025 双版本信息体 parser、平台登入鉴权、VIN 白名单、读空闲告警。 -- **JT/T 808 / JT/T 1078 / JSATL12 接入**(`protocol-jt808` / `protocol-jt1078` / - `protocol-jsatl12`)。 -- **MQTT 入站**(`inbound-mqtt`)。 -- **Disruptor 事件总线**(`ingest-core`):高吞吐解耦解码与下游 sink。 -- **Sink**:本地归档(`sink-archive`)、Kafka(`sink-kafka`,含 protobuf envelope)。 -- **会话状态**(`session-core`)。 -- **可观测性**:Micrometer/Actuator 装配(`observability`)。 -- **终端控制命令网关**(`command-gateway`)。 - -### Removed -- 旧文件型事件索引模块和 DuckDB 依赖;历史查询统一收敛到 TDengine 与 `archive://...` 原始报文引用。 - -### Fixed —— GB/T 32960 应答帧时间字段 -- `Gb32960MessageDecoder` 对 `VEHICLE_LOGIN` / `VEHICLE_LOGOUT` / - `PLATFORM_LOGIN` / `PLATFORM_LOGOUT` 命令预解析 body 首 6B 采集时间,写入 - `header.eventTime`。修复前 handler 回 ACK 时落到 `Instant.now()`,导致应答时间与原 - 报文时间不一致,违反 §6.3.2 应答规则。 - -### Added —— Netty 读空闲告警 -- 配置项 `lingniu.ingest.gb32960.idle-read-seconds`(默认 60s,0 禁用)。 -- pipeline 注入 `IdleStateHandler(idleReadSeconds, 0, 0)`,仅打 WARN 日志不断链, - 用于排查"对端登入成功后长时间不推数据"场景。 - -### Changed —— GB32960 信息体 parser 包结构重构 -- 原 `codec/parser/` 下的 16 个共用类按版本严格拆分到 `parser/v2016/`(10 类) - 和 `parser/v2025/`(11 类)两个独立子包。 -- 取消 `Vehicle/DriveMotor/Engine/Position` 的 `v2016()/v2025()` 工厂方法,每个 - 版本独立类,零代码共用。 -- `Alarm` 解析中跨版本静态调用 `readFaultList` 也已改为各版本类内部私有方法。 -- `Gb32960AutoConfiguration` 同步更新所有 import 与 `@Bean` 工厂。 - -### Removed —— 违反 2016 标准的 typeCode 注册 -- 经核对 GB/T 32960.3-2016 附录 B 表 B.3,`0x30~0x7F` 整段为预留区, - **2016 标准里没有 0x30 / 0x31 / 0x32 任何字段定义**。 -- 删除 `FuelCellStackV2016BlockParser`(曾把 V2025 字段布局硬套到 V2016 帧的 0x30 段, - 解出来的电压/电流远超规范上限,是非物理值)。 -- `Gb32960AutoConfiguration` 移除 `gb32960V2016FuelCellStack` bean。 -- `Gb32960DecoderGoldenTest` 中 749B 真实生产帧的断言由 - "`isEmpty()`(不应有 Raw 兜底)"翻转为 "`isPresent()`(peer 越界使用 0x30+ 预留 - typeCode,应有 Raw 兜底)"。这才是符合 2016 国标的正确预期。 - -### Diagnostics —— BodyParser hex dump 增强 -- `Gb32960BodyParser` 在遇到未知 typeCode 触发 unknown 路径时,额外打一条 WARN, - 内容为整段 info-block 区域的 16 字节/行 hex dump,并在 `typeStartPos` 字节后插入 - `<` 标记,便于人工反查上一个块的字段对齐。 - -### Notes —— 待办 -- peer 在 V2016 帧(`2323` 起始)里下发 0x30/0x31/0x32 是越界使用 2016 预留区段。 - 建议推动对端: - 1. 切换到 V2025 帧头(`2424`); - 2. 或迁移到 `0x80~0xFE` 用户自定义区段; - 3. 或提供私有协议文档,再决定是否实现 vendor-specific parser。 -- `Gb32960BodyParser` 当前对 unknown 块的策略是"吞掉剩余所有字节为单个 Raw 后终止 - 循环",未做单块异常隔离。后续可改为对每个 parser.parse 调用加 try/catch + - position 回滚,实现单块失败不拖垮整帧。 -- `platformAuthorizer.authenticate` 是 EventLoop 同步调用,存在 worker 阻塞风险, - 建议挪到独立业务线程池(与 0x30 议题无关,但属于同模块潜在风险)。 - -[0.1.0]: #010--2026-04-15 diff --git a/DECISIONS.md b/DECISIONS.md deleted file mode 100644 index 7acb356f..00000000 --- a/DECISIONS.md +++ /dev/null @@ -1,105 +0,0 @@ -# 架构决策记录(ADR 汇总) - -> 本文件记录 lingniu-vehicle-ingest v2 的关键架构决策,后续每次重大调整追加条目(不删除旧条目)。 - -## ADR-001 消息通道:Kafka -- **Status**: Accepted -- **Context**: 需要高吞吐、严格单车有序、成熟生态 -- **Decision**: 使用 Kafka,分区 key = VIN -- **Consequences**: 下游消费者需使用消费组 + 分区内顺序消费;生产链路只支持 Kafka。 - -## ADR-002 线上消息格式:Protobuf -- **Status**: Accepted -- **Context**: 需要向前兼容、体积小、性能好 -- **Decision**: 线上用 Protobuf,调试/诊断保留 JSON 序列化能力 -- **Consequences**: 需维护 `.proto` schema,`ingest-api` 模块负责生成 Java stub - -## ADR-003 command-gateway 独立模块 -- **Status**: Accepted -- **Context**: 下行命令链路不应与接入进程耦合 -- **Decision**: 拆出独立 `command-gateway` 模块,复用 `session-core`;本次重构同步交付 -- **Consequences**: HTTP 对外接口(原 JT808Controller / JT1078Controller)迁移到此模块 - -## ADR-004 信达 Push:Removed -- **Status**: Superseded by ADR-011 -- **Context**: 旧实现硬编码凭证、无重连、0300/0401 空实现 -- **Decision**: 删除信达 Push 源码、Maven profile、私仓依赖和部署入口。 -- **Consequences**: 信达 Push 后续已从源码和构建面删除,优化优先投向 GB32960、JT808、宇通 MQTT、历史和统计链路。 - -## ADR-005 JT1078 / JSATL12 进第一批 -- **Status**: Superseded by ADR-012 -- **Decision**: 第一批迁移即覆盖 JT1078 信令 + JSATL12 报警附件上传 -- **Consequences**: Phase 2 工期相应拉长;JSATL12 需要对象存储后端(本地 / S3 / OSS) - -## ADR-006 部署形态:GB32960 三应用拆分 -- **Status**: Superseded by ADR-011 -- **Context**: 原 `bootstrap-all` 一体化启动已被 GB32960 拆分部署替代 -- **Decision**: 当时生产默认部署 `gb32960-ingest-app`、`vehicle-history-app`、`vehicle-analytics-app`;旧 `bootstrap-all` 模块删除 -- **Consequences**: 该三应用边界已由 ADR-011 的三协议接入 + 历史 + 统计生产面取代;协议接入、历史查询、统计消费仍保持独立发布和回滚。 - -## ADR-007 Java 25 + 虚拟线程 + Disruptor -- **Status**: Accepted -- **Decision**: - - Netty EventLoop 只做解码与 RingBuffer 投递,严禁阻塞 - - 业务 Handler 走虚拟线程(`Thread.ofVirtual()`) - - 协议间背压通过 Disruptor RingBuffer 表达 - - 禁用 `synchronized`,使用 `ReentrantLock` 避免虚拟线程 pinning -- **Consequences**: 需开启 `jdk.VirtualThreadPinned` JFR 监控 - -## ADR-008 协议接入应用不直接写业务库 -- **Status**: Accepted -- **Decision**: GB32960、JT808、Yutong MQTT 等协议接入应用只负责收、解析、校验、规整和投递 Kafka,不直接写业务表。 -- **Consequences**: 业务落库由独立 Kafka 消费应用承接:`vehicle-history-app` 写入 TDengine 历史与 RAW JSON,`vehicle-analytics-app` 写入 MySQL `vehicle_stat_metric`。 - -## ADR-009 构建工具:Maven -- **Status**: Accepted -- **Decision**: 沿用 Maven,统一 BOM 管理版本,Spotless 格式化,ArchUnit 守护分层 - -## ADR-010 框架:Spring Boot 3.5 -- **Status**: Accepted -- **Decision**: Spring Boot 3.5.x,支持 JDK 25;AutoConfiguration 用于按需启停 - -## ADR-011 默认生产面:三协议接入 + 历史 + 统计 -- **Status**: Accepted -- **Context**: 生产接入范围已经从 GB32960 三应用拆分,扩展为三条活跃接入链路和两个消费应用;信达 Push 已废弃并删除,不应再出现在构建、部署或优化目标里。 -- **Decision**: - 1. 默认生产面包含 GB32960、JT808、Yutong MQTT、vehicle-history-app、vehicle-analytics-app。 - 2. Xinda Push 源码、Maven profile、Woodpecker 镜像发布和历史消费绑定全部移除。 - 3. `command-gateway` 和 JT1078 继续作为可选能力,通过 `optional-command-gateway` profile 显式启用。 -- **Consequences**: 默认构建和部署保持精简;后续性能、可靠性、字段解析和存储优化都服务三条活跃协议链路。 - -## ADR-012 JSATL12 附件上传:Optional only -- **Status**: Accepted -- **Context**: 当前默认生产面只部署 GB32960、JT808、Yutong MQTT、vehicle-history-app、vehicle-analytics-app;JSATL12 附件上传没有独立生产 app,也不在 Portainer 和 Woodpecker 活跃镜像列表中。 -- **Decision**: - 1. `protocol-jsatl12` 不进入默认 Maven reactor。 - 2. JSATL12 仅保留在 `optional-attachments` profile,需要附件上传能力时显式构建。 - 3. 默认优化和验证优先覆盖活跃接入链路,附件上传能力保持源码可用但不增加默认构建面。 -- **Consequences**: 默认构建更轻,生产部署边界更清晰;附件上传相关测试需要通过 `-Poptional-attachments` 显式运行。 - -## ADR-013 最新状态服务:Optional only -- **Status**: Accepted -- **Context**: Redis 最新状态查询是独立消费能力,但当前默认 Portainer 部署和 Woodpecker 镜像发布只包含三条接入链路、history 和 analytics;`vehicle-state-service` 没有独立 app,也不应增加默认构建面。 -- **Decision**: - 1. `vehicle-state-service` 不进入默认 Maven reactor。 - 2. vehicle-state-service 仅保留在 `optional-latest-state` profile,需要 Redis 最新状态查询能力时显式构建。 - 3. 默认优化和验证优先覆盖活跃接入、TDengine 历史和 MySQL 指标链路。 -- **Consequences**: 默认构建和部署边界继续收窄;最新状态能力保持源码可用,但其测试需要通过 `-Poptional-latest-state` 显式运行。 - -## ADR-014 raw-archive-store 原型已删除 -- **Status**: Accepted -- **Context**: 默认生产 raw bytes 写入由 `sink-archive` 负责,历史查询通过 TDengine `raw_frames` 和 `archive://...` 引用追溯;`raw-archive-store` 只是未部署的独立读写原型,继续保留会造成 raw archive 路径歧义。 -- **Decision**: - 1. 删除 `raw-archive-store` 模块和对应 optional profile。 - 2. 默认生产链路只保留 `sink-archive`、Kafka raw topic、TDengine raw_frames 这条 raw 路径。 - 3. 如需新的 archive store 能力,先以生产链路需求重新设计,不恢复旧原型。 -- **Consequences**: 默认构建面和可选构建面继续收窄;raw bytes 写入职责集中在 `sink-archive`。 - -## ADR-015 文件型事件索引:Removed -- **Status**: Accepted -- **Context**: 默认历史查询已收敛到 TDengine `raw_frames`、`vehicle_locations` 和按需解码;旧文件型索引会增加一套无生产部署的查询和依赖边界。 -- **Decision**: - 1. 删除旧文件型事件索引模块和 profile。 - 2. 父 POM 不再管理旧索引驱动依赖。 - 3. 历史查询和 RAW 回放统一以 TDengine + `archive://...` 引用为准。 -- **Consequences**: 默认构建面和可选构建面都更小;需要历史查询时只维护 TDengine 一条路径。 diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 41021641..00000000 --- a/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM eclipse-temurin:25-jre - -ARG APP_NAME -ARG APP_VERSION - -ENV APP_NAME=${APP_NAME} -ENV APP_VERSION=${APP_VERSION} -ENV JAVA_OPTS="--sun-misc-unsafe-memory-access=allow -Dio.netty.transport.noNative=true" - -RUN apt-get update \ - && apt-get install -y --no-install-recommends curl \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app -COPY modules/apps/${APP_NAME}/target/${APP_NAME}.jar /app/app.jar - -EXPOSE 20100 20200 20310 20400 20500 32960 808 - -ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"] diff --git a/README.md b/README.md deleted file mode 100644 index bad5be4d..00000000 --- a/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# lingniu-vehicle-ingest - -> 羚牛车辆数据接入平台 v2 - -## 设计目标 - -- **协议接入统一抽象**:GB/T 32960、JT/T 808、Yutong MQTT 是默认生产接入;JT/T 1078、JSATL12 作为显式 profile 的可选能力;信达 Push 已废弃并删除源码 -- **原子能力化**:每个协议一个独立 Maven 模块 + 独立 AutoConfiguration + 独立配置开关 -- **业务 / 实时彻底解耦**:协议接入应用只负责收、解析、校验、规整和投递 Kafka;历史与统计由独立 Kafka 消费应用落库 -- **高并发低延迟**:Netty + Disruptor + Java 25 虚拟线程;目标单节点 ≥ 5 万 msg/s,P99 < 50 ms -- **可观测、可回放、可灰度**:统一 traceId、Envelope、幂等键、DLQ、原始报文冷存 - -## 技术栈 - -| 类别 | 选型 | -|---|---| -| 语言 | Java **25** | -| 框架 | Spring Boot 3.5.3 | -| 网络 | Netty 4.2.9.Final | -| 并发 | Disruptor 4 + 虚拟线程 | -| 事件流 | **Kafka**(唯一生产消息通道,vin 分区,保证单车有序) | -| 线上消息格式 | **Protobuf**,调试走 JSON | -| MQTT 客户端 | Eclipse Paho 1.2.5 | -| 会话索引 | Redis | -| 熔断/限流 | Resilience4j 2 | -| 可观测 | Micrometer + Prometheus | -| 冷存 | 本地文件系统 | -| 构建 | Maven | - -## 模块划分 - -``` -lingniu-vehicle-ingest/ -├── modules/ -│ ├── core/ -│ │ ├── ingest-api/ SPI + sealed 领域事件 + 注解 -│ │ ├── ingest-codec-common/ 公共编解码工具(BCD/CRC/BCC/bit utils) -│ │ ├── ingest-core/ Pipeline / Dispatcher / Disruptor / Session 桥 -│ │ ├── session-core/ 设备会话 + 鉴权 + Token + Redis SessionStore -│ │ ├── vehicle-identity/ 跨协议车辆身份解析 + MySQL 外部标识绑定 -│ │ └── observability/ metrics / health -│ ├── protocols/ -│ │ ├── protocol-gb32960/ GB/T 32960 -│ │ ├── protocol-jt808/ JT/T 808(统一身份映射 + 事件 metadata 内部 VIN + 注册/鉴权/心跳/注销/位置/批量位置/参数/属性/媒体/透传/未知上行和坏帧兜底/断链清理会话/下行分包) -│ │ ├── protocol-jt1078/ JT/T 1078(optional-command-gateway profile;808 信令按需桥接 + 常用下行信令编码 + TCP/UDP RTP 媒体流分段归档 + 事件 metadata 内部 VIN + 坏 RTP 统一 RawArchive/Passthrough + 归档失败兜底 + SIM 身份映射) -│ │ └── protocol-jsatl12/ 苏标主动安全报警附件(optional-attachments profile) -│ ├── inbound/ -│ │ └── inbound-mqtt/ MQTT 接入(endpoint 生命周期 + profile 注册扩展 + 统一身份映射 + PEM 双向 TLS + 未知 profile/解析失败/profile异常/连接订阅失败兜底 + 统一 UNKNOWN 身份 metadata) -│ ├── sinks/ -│ │ ├── sink-kafka/ Kafka producer + Protobuf Envelope -│ │ └── sink-archive/ 原始报文冷存 -│ ├── services/ -│ │ ├── event-history-service/ Kafka 全字段事件消费 + 历史查询 -│ │ ├── vehicle-state-service/ Kafka 全字段事件消费 + Redis 热状态查询(optional-latest-state profile) -│ │ └── vehicle-stat-service/ Kafka 全字段事件消费 + 可配置日统计 -│ ├── testing/ -│ │ └── vehicle-identity-test-support/ 协议测试共享身份解析夹具 -│ └── apps/ -│ ├── command-gateway/ 可选 HTTP → 设备下行命令(optional-command-gateway profile) -│ ├── gb32960-ingest-app/ GB32960 TCP 接入 + Kafka 投递 -│ ├── jt808-ingest-app/ JT808 TCP 接入 + Kafka 投递 -│ ├── yutong-mqtt-app/ 宇通 MQTT 接入 + Kafka 投递 -│ ├── vehicle-history-app/ TDengine 历史查询 + RAW JSON -│ └── vehicle-analytics-app/ JT808 每日里程指标消费 -├── docs/ 架构文档、模块图、实施计划 -└── reference/ 参考资料 -``` - -## 快速开始 - -```bash -# 要求:JDK 25, Maven 3.9+ -mvn -v -mvn -pl :gb32960-ingest-app,:jt808-ingest-app,:yutong-mqtt-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true -``` - -生产服务只在 ECS/Portainer 上运行;本机只用于源码检查、Maven 构建和单元测试。部署、健康检查和真实流量验证步骤见 `docs/operations/gb32960-service-split-runbook.md` 与 `docs/operations/vehicle-ingest-tdengine-verification.md`。 - -信达 Push 已废弃并删除源码,不参与 Maven reactor、CI、Portainer 部署和历史消费链路。 - -JSATL12 附件上传不在默认 Maven reactor 中;需要时使用 `-Poptional-attachments` 构建。 - -Command Gateway/JT1078 下行与音视频信令不在默认 Maven reactor 中;需要时使用 `-Poptional-command-gateway` 构建。 - -Redis 最新状态查询不在默认 Maven reactor 中;需要时使用 `-Poptional-latest-state` 构建 `vehicle-state-service`。 - -## 迁移说明 - -本项目是 `lingniu-vehicle-data-reception` 的 v2 重构,采用 strangler fig 渐进式迁移,旧项目保留只读参考。迁移路径与决策参见 `../REFRACTOR_PLAN.md`。 - -## 架构文档 - -- 目标架构:`docs/target-architecture.md` -- 内部字段模型:`docs/vehicle-telemetry-internal-fields.md` -- 模块与数据流:`docs/module-data-flow.html` - -## 核心原则 - -1. **接入与业务落库分离**:协议接入应用只负责收、解析、校验、规整和投递 Kafka;`vehicle-history-app` 写入 TDengine 历史与 RAW JSON,`vehicle-analytics-app` 将 JT808 `daily_mileage_km` 写入 MySQL `vehicle_stat_metric` -2. **Kafka 唯一消息通道**:生产链路只支持 Kafka;接入、历史、统计统一通过 Kafka Envelope 解耦 -3. **协议即插拔**:每个 `protocol-*` / `inbound-*` 都可独立开关(`lingniu.ingest..enabled`) -4. **顺序保证**:同一 VIN 严格有序(Disruptor hash + Kafka 分区 key) -5. **幂等消费**:Envelope 带 `eventId`,下游去重 -6. **原始可回放**:协议接入会把 `rawArchiveKey/rawArchiveUri` 追加到事件 metadata;Kafka Envelope、TDengine `raw_frames` 和导出都使用 `archive://...` 逻辑 URI 追溯原始 bytes,实际文件由 `sink-archive` 管理 diff --git a/deploy/portainer/docker-compose-go.yml b/deploy/portainer/docker-compose-go.yml deleted file mode 100644 index 32bbadd5..00000000 --- a/deploy/portainer/docker-compose-go.yml +++ /dev/null @@ -1,117 +0,0 @@ -version: "3.8" - -# Go sidecar stack for the vehicle ingest redesign. -# It is intentionally separate from deploy/portainer/docker-compose.yml so the -# current Java production stack can keep running until Go evidence is captured. - -x-common-env: &common-env - TZ: Asia/Shanghai - KAFKA_BROKERS: ${KAFKA_BROKERS:-172.17.111.56:9092} - KAFKA_TOPIC_GB32960_RAW: ${KAFKA_TOPIC_GB32960_RAW:-vehicle.raw.gb32960.v1} - KAFKA_TOPIC_JT808_RAW: ${KAFKA_TOPIC_JT808_RAW:-vehicle.raw.jt808.v1} - KAFKA_TOPIC_YUTONG_MQTT_RAW: ${KAFKA_TOPIC_YUTONG_MQTT_RAW:-vehicle.raw.yutong-mqtt.v1} - KAFKA_TOPIC_UNIFIED: ${KAFKA_TOPIC_UNIFIED:-vehicle.event.unified.v1} - -x-restart: &restart-policy - restart: unless-stopped - networks: - - vehicle-ingest - logging: - driver: json-file - options: - max-size: "100m" - max-file: "5" - -services: - go-vehicle-gateway: - <<: *restart-policy - image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} - container_name: go-vehicle-gateway - command: ["/app/gateway"] - mem_limit: ${GO_GATEWAY_MEM_LIMIT:-512m} - environment: - <<: *common-env - GB32960_TCP_ADDR: ":32960" - JT808_TCP_ADDR: ":808" - TCP_READ_BUFFER_BYTES: ${TCP_READ_BUFFER_BYTES:-65536} - TCP_IDLE_TIMEOUT_SECONDS: ${TCP_IDLE_TIMEOUT_SECONDS:-180} - TCP_MAX_CONNECTIONS: ${TCP_MAX_CONNECTIONS:-20000} - YUTONG_MQTT_ENABLED: ${YUTONG_MQTT_ENABLED:-false} - YUTONG_MQTT_ENDPOINT: ${YUTONG_MQTT_ENDPOINT:-yutong} - YUTONG_MQTT_URI: ${YUTONG_MQTT_URI:-} - YUTONG_MQTT_TOPICS: ${YUTONG_MQTT_TOPICS:-/ytforward/shln/+} - YUTONG_MQTT_QOS: ${YUTONG_MQTT_QOS:-2} - YUTONG_MQTT_CLIENT_ID: ${YUTONG_MQTT_CLIENT_ID:-lingniu-go-yutong-mqtt} - YUTONG_MQTT_USERNAME: ${YUTONG_MQTT_USERNAME:-} - YUTONG_MQTT_PASSWORD: ${YUTONG_MQTT_PASSWORD:-} - YUTONG_MQTT_CLEAN_SESSION: ${YUTONG_MQTT_CLEAN_SESSION:-false} - YUTONG_MQTT_KEEP_ALIVE_SECONDS: ${YUTONG_MQTT_KEEP_ALIVE_SECONDS:-20} - YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS: ${YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS:-10} - YUTONG_MQTT_TLS_CA_PEM: ${YUTONG_MQTT_TLS_CA_PEM:-} - YUTONG_MQTT_TLS_CLIENT_PEM: ${YUTONG_MQTT_TLS_CLIENT_PEM:-} - YUTONG_MQTT_TLS_CLIENT_KEY: ${YUTONG_MQTT_TLS_CLIENT_KEY:-} - YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED: ${YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED:-true} - IDENTITY_MYSQL_DSN: ${IDENTITY_MYSQL_DSN:-} - VEHICLE_IDENTITY_TABLE: ${VEHICLE_IDENTITY_TABLE:-vehicle_identity_binding} - ports: - - "${GO_GB32960_TCP_PORT:-32960}:32960" - - "${GO_JT808_TCP_PORT:-808}:808" - volumes: - - "${KAFKA_SPOOL_HOST_DIR:-/opt/lingniu-go/spool/gateway}:${KAFKA_SPOOL_DIR:-/data/spool/gateway}" - - "${YUTONG_MQTT_CERT_HOST_DIR:-/opt/lingniuServices/certificate/yutong/vehicledatareception}:${YUTONG_MQTT_CERT_CONTAINER_DIR:-/opt/lingniuServices/certificate/yutong/vehicledatareception}:ro" - - go-history-writer: - <<: *restart-policy - image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} - container_name: go-history-writer - command: ["/app/history-writer"] - mem_limit: ${GO_HISTORY_WRITER_MEM_LIMIT:-768m} - environment: - <<: *common-env - KAFKA_GROUP: ${KAFKA_GROUP_GO_HISTORY:-go-history-writer} - KAFKA_TOPICS: ${KAFKA_TOPICS_GO_HISTORY:-vehicle.raw.gb32960.v1,vehicle.raw.jt808.v1,vehicle.raw.yutong-mqtt.v1} - TDENGINE_DRIVER: ${TDENGINE_DRIVER:-taosWS} - TDENGINE_DSN: ${TDENGINE_DSN:?set TDENGINE_DSN, example root:password@ws(172.17.111.57:6041)/lingniu_vehicle_ts} - TDENGINE_DATABASE: ${TDENGINE_DATABASE:-lingniu_vehicle_ts} - TDENGINE_ENSURE_SCHEMA: ${TDENGINE_ENSURE_SCHEMA:-true} - - go-stat-writer: - <<: *restart-policy - image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} - container_name: go-stat-writer - command: ["/app/stat-writer"] - mem_limit: ${GO_STAT_WRITER_MEM_LIMIT:-512m} - environment: - <<: *common-env - KAFKA_GROUP: ${KAFKA_GROUP_GO_STAT:-go-stat-writer} - KAFKA_TOPICS: ${KAFKA_TOPICS_GO_STAT:-vehicle.raw.gb32960.v1,vehicle.raw.jt808.v1} - MYSQL_DSN: ${MYSQL_DSN:?set MYSQL_DSN} - MYSQL_ENSURE_SCHEMA: ${MYSQL_ENSURE_SCHEMA:-true} - LOCAL_TZ: ${LOCAL_TZ:-Asia/Shanghai} - - go-realtime-api: - <<: *restart-policy - image: ${LINGNIU_GO_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_GO_IMAGE_NAMESPACE:-oneos}/vehicle-gateway-go:${LINGNIU_GO_IMAGE_VERSION:?set LINGNIU_GO_IMAGE_VERSION} - container_name: go-realtime-api - command: ["/app/realtime-api"] - mem_limit: ${GO_REALTIME_API_MEM_LIMIT:-512m} - environment: - <<: *common-env - HTTP_ADDR: ":20210" - KAFKA_GROUP: ${KAFKA_GROUP_GO_REALTIME:-go-realtime-api} - KAFKA_TOPICS: ${KAFKA_TOPICS_GO_REALTIME:-vehicle.event.unified.v1} - REDIS_ADDR: ${REDIS_ADDR:-r-bp1u741kij7e51i481.redis.rds.aliyuncs.com:6379} - REDIS_USERNAME: ${REDIS_USERNAME:-} - REDIS_PASSWORD: ${REDIS_PASSWORD:?set REDIS_PASSWORD} - REDIS_DB: ${REDIS_DB:-50} - ONLINE_TTL_SECONDS: ${ONLINE_TTL_SECONDS:-600} - MYSQL_DSN: ${MYSQL_DSN:-} - TDENGINE_DRIVER: ${TDENGINE_DRIVER:-taosWS} - TDENGINE_DSN: ${TDENGINE_DSN:-} - TDENGINE_DATABASE: ${TDENGINE_DATABASE:-lingniu_vehicle_ts} - ports: - - "${GO_REALTIME_HTTP_PORT:-20210}:20210" - -networks: - vehicle-ingest: - name: vehicle-ingest diff --git a/deploy/portainer/docker-compose.yml b/deploy/portainer/docker-compose.yml deleted file mode 100644 index 5d601133..00000000 --- a/deploy/portainer/docker-compose.yml +++ /dev/null @@ -1,216 +0,0 @@ -version: "3.8" - -# Default production stack: GB32960, JT808, Yutong MQTT, history, analytics. -# Legacy and optional services stay outside this compose file. -# Set LINGNIU_IMAGE_VERSION in Portainer Stack env, for example: -# main-0.1.0-SNAPSHOT-22468e7 -# Override LINGNIU_IMAGE_REGISTRY / LINGNIU_IMAGE_NAMESPACE only when publishing to another registry. -x-common-env: &common-env - TZ: Asia/Shanghai - JAVA_OPTS: ${JAVA_OPTS:---sun-misc-unsafe-memory-access=allow -Dio.netty.transport.noNative=true -XX:+UseZGC -XX:MaxRAMPercentage=75} - NACOS_CONFIG_ENABLED: ${NACOS_CONFIG_ENABLED:-true} - NACOS_SERVER_ADDR: ${NACOS_SERVER_ADDR:-127.0.0.1:8848} - NACOS_NAMESPACE: ${NACOS_NAMESPACE:-} - NACOS_GROUP: ${NACOS_GROUP:-DEFAULT_GROUP} - NACOS_CONFIG_FILE_EXTENSION: ${NACOS_CONFIG_FILE_EXTENSION:-yml} - NACOS_REFRESH_ENABLED: ${NACOS_REFRESH_ENABLED:-true} - NACOS_USERNAME: ${NACOS_USERNAME:-} - NACOS_PASSWORD: ${NACOS_PASSWORD:-} - KAFKA_BROKERS: ${KAFKA_BROKERS:-172.17.111.56:9092} - -x-identity-mysql-env: &identity-mysql-env - VEHICLE_IDENTITY_MYSQL_JDBC_URL: ${MYSQL_JDBC_URL:-} - VEHICLE_IDENTITY_MYSQL_USERNAME: ${MYSQL_USERNAME:-} - VEHICLE_IDENTITY_MYSQL_PASSWORD: ${MYSQL_PASSWORD:-} - -x-redis-session-env: &redis-session-env - REDIS_HOST: ${REDIS_HOST:-r-bp1u741kij7e51i481.redis.rds.aliyuncs.com} - REDIS_PORT: ${REDIS_PORT:-6379} - REDIS_DATABASE: ${REDIS_DATABASE:-50} - REDIS_USERNAME: ${REDIS_USERNAME:-} - REDIS_PASSWORD: ${REDIS_PASSWORD:-} - -x-readiness-healthcheck: &readiness-healthcheck - interval: 30s - timeout: 5s - retries: 3 - start_period: 60s - -services: - gb32960-ingest-app: - image: ${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_IMAGE_NAMESPACE:-oneos}/gb32960-ingest-app:${LINGNIU_IMAGE_VERSION:?set LINGNIU_IMAGE_VERSION} - container_name: gb32960-ingest-app - restart: unless-stopped - mem_limit: ${GB32960_MEM_LIMIT:-768m} - environment: - <<: - - *common-env - - *identity-mysql-env - - *redis-session-env - HTTP_PORT: 20100 - GB32960_PORT: 32960 - KAFKA_NODE_ID: ${KAFKA_NODE_ID:-gb32960-ingest-portainer} - KAFKA_TOPIC_GB32960_EVENT: ${KAFKA_TOPIC_GB32960_EVENT:-vehicle.event.gb32960.v1} - KAFKA_TOPIC_GB32960_RAW: ${KAFKA_TOPIC_GB32960_RAW:-vehicle.raw.gb32960.v1} - KAFKA_TOPIC_GB32960_DLQ: ${KAFKA_TOPIC_GB32960_DLQ:-vehicle.dlq.gb32960.v1} - GB32960_AUTH_ENABLED: ${GB32960_AUTH_ENABLED:-false} - GB32960_PLATFORM_USER_HYUNDAI: ${GB32960_PLATFORM_USER_HYUNDAI:-Hyundai} - GB32960_PLATFORM_PWD_HYUNDAI: ${GB32960_PLATFORM_PWD_HYUNDAI:-} - GB32960_PLATFORM_IP_HYUNDAI: ${GB32960_PLATFORM_IP_HYUNDAI:-} - GB32960_PLATFORM_USER_YUEJIN: ${GB32960_PLATFORM_USER_YUEJIN:-YueJin} - GB32960_PLATFORM_PWD_YUEJIN: ${GB32960_PLATFORM_PWD_YUEJIN:-} - GB32960_PLATFORM_IP_YUEJIN: ${GB32960_PLATFORM_IP_YUEJIN:-} - GB32960_TLS_ENABLED: ${GB32960_TLS_ENABLED:-false} - SINK_ARCHIVE_PATH: ${GB32960_ARCHIVE_PATH:-/data/archive} - ports: - - "${GB32960_HTTP_PORT:-20100}:20100" - - "${GB32960_TCP_PORT:-32960}:32960" - volumes: - - gb32960-ingest-data:/data - networks: - - vehicle-ingest - healthcheck: - <<: *readiness-healthcheck - test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:20100/actuator/health/readiness >/dev/null"] - - jt808-ingest-app: - image: ${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_IMAGE_NAMESPACE:-oneos}/jt808-ingest-app:${LINGNIU_IMAGE_VERSION:?set LINGNIU_IMAGE_VERSION} - container_name: jt808-ingest-app - restart: unless-stopped - mem_limit: ${JT808_MEM_LIMIT:-768m} - environment: - <<: - - *common-env - - *identity-mysql-env - - *redis-session-env - HTTP_PORT: 20400 - JT808_PORT: 808 - KAFKA_NODE_ID: ${KAFKA_NODE_ID_JT808:-jt808-ingest-portainer} - KAFKA_TOPIC_JT808_EVENT: ${KAFKA_TOPIC_JT808_EVENT:-vehicle.event.jt808.v1} - KAFKA_TOPIC_JT808_RAW: ${KAFKA_TOPIC_JT808_RAW:-vehicle.raw.jt808.v1} - KAFKA_TOPIC_JT808_DLQ: ${KAFKA_TOPIC_JT808_DLQ:-vehicle.dlq.jt808.v1} - SINK_ARCHIVE_PATH: ${JT808_ARCHIVE_PATH:-/data/archive} - ports: - - "${JT808_HTTP_PORT:-20400}:20400" - - "${JT808_TCP_PORT:-808}:808" - volumes: - - jt808-ingest-data:/data - networks: - - vehicle-ingest - healthcheck: - <<: *readiness-healthcheck - test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:20400/actuator/health/readiness >/dev/null"] - - yutong-mqtt-app: - image: ${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_IMAGE_NAMESPACE:-oneos}/yutong-mqtt-app:${LINGNIU_IMAGE_VERSION:?set LINGNIU_IMAGE_VERSION} - container_name: yutong-mqtt-app - restart: unless-stopped - mem_limit: ${YUTONG_MQTT_MEM_LIMIT:-512m} - environment: - <<: - - *common-env - - *identity-mysql-env - HTTP_PORT: 20500 - YUTONG_MQTT_ENABLED: ${YUTONG_MQTT_ENABLED:-false} - YUTONG_MQTT_AUTO_STARTUP: ${YUTONG_MQTT_AUTO_STARTUP:-true} - KAFKA_NODE_ID: ${KAFKA_NODE_ID_MQTT:-yutong-mqtt-portainer} - KAFKA_TOPIC_YUTONG_MQTT_EVENT: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:-vehicle.event.mqtt-yutong.v1} - KAFKA_TOPIC_YUTONG_MQTT_RAW: ${KAFKA_TOPIC_YUTONG_MQTT_RAW:-vehicle.raw.mqtt-yutong.v1} - KAFKA_TOPIC_YUTONG_MQTT_DLQ: ${KAFKA_TOPIC_YUTONG_MQTT_DLQ:-vehicle.dlq.mqtt-yutong.v1} - YUTONG_MQTT_ENDPOINT_NAME: ${YUTONG_MQTT_ENDPOINT_NAME:-yutong} - YUTONG_MQTT_URI: ${YUTONG_MQTT_URI:-} - YUTONG_MQTT_TOPIC: "${YUTONG_MQTT_TOPIC:-#}" - YUTONG_MQTT_QOS: ${YUTONG_MQTT_QOS:-1} - YUTONG_MQTT_CLIENT_ID: ${YUTONG_MQTT_CLIENT_ID:-lingniu-yutong-mqtt} - YUTONG_MQTT_USERNAME: ${YUTONG_MQTT_USERNAME:-} - YUTONG_MQTT_PASSWORD: ${YUTONG_MQTT_PASSWORD:-} - YUTONG_MQTT_CLEAN_SESSION: ${YUTONG_MQTT_CLEAN_SESSION:-false} - YUTONG_MQTT_KEEP_ALIVE_SECONDS: ${YUTONG_MQTT_KEEP_ALIVE_SECONDS:-20} - YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS: ${YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS:-10} - YUTONG_MQTT_TLS_CA_PEM: ${YUTONG_MQTT_TLS_CA_PEM:-} - YUTONG_MQTT_TLS_CLIENT_PEM: ${YUTONG_MQTT_TLS_CLIENT_PEM:-} - YUTONG_MQTT_TLS_CLIENT_KEY: ${YUTONG_MQTT_TLS_CLIENT_KEY:-} - YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED: ${YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED:-true} - SINK_ARCHIVE_PATH: ${YUTONG_MQTT_ARCHIVE_PATH:-/data/archive} - ports: - - "${YUTONG_MQTT_HTTP_PORT:-20500}:20500" - volumes: - - yutong-mqtt-data:/data - networks: - - vehicle-ingest - healthcheck: - <<: *readiness-healthcheck - test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:20500/actuator/health/readiness >/dev/null"] - - vehicle-history-app: - image: ${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_IMAGE_NAMESPACE:-oneos}/vehicle-history-app:${LINGNIU_IMAGE_VERSION:?set LINGNIU_IMAGE_VERSION} - container_name: vehicle-history-app - restart: unless-stopped - mem_limit: ${VEHICLE_HISTORY_MEM_LIMIT:-1536m} - environment: - <<: *common-env - HTTP_PORT: 20200 - KAFKA_CONSUMER_ENABLED: ${KAFKA_CONSUMER_ENABLED_HISTORY:-true} - KAFKA_CONSUMER_CLIENT_ID_PREFIX: ${KAFKA_CONSUMER_CLIENT_ID_PREFIX_HISTORY:-vehicle-history} - KAFKA_CONSUMER_MAX_POLL_RECORDS: ${KAFKA_CONSUMER_MAX_POLL_RECORDS_HISTORY:-2000} - KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS: ${KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS_HISTORY:-1800000} - KAFKA_TOPIC_GB32960_EVENT: ${KAFKA_TOPIC_GB32960_EVENT:-vehicle.event.gb32960.v1} - KAFKA_TOPIC_GB32960_RAW: ${KAFKA_TOPIC_GB32960_RAW:-vehicle.raw.gb32960.v1} - KAFKA_TOPIC_JT808_EVENT: ${KAFKA_TOPIC_JT808_EVENT:-vehicle.event.jt808.v1} - KAFKA_TOPIC_JT808_RAW: ${KAFKA_TOPIC_JT808_RAW:-vehicle.raw.jt808.v1} - KAFKA_TOPIC_YUTONG_MQTT_EVENT: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:-vehicle.event.mqtt-yutong.v1} - KAFKA_TOPIC_YUTONG_MQTT_RAW: ${KAFKA_TOPIC_YUTONG_MQTT_RAW:-vehicle.raw.mqtt-yutong.v1} - KAFKA_TOPIC_HISTORY_DLQ: ${KAFKA_TOPIC_HISTORY_DLQ:-vehicle.dlq.history.v1} - KAFKA_GROUP_HISTORY: ${KAFKA_GROUP_HISTORY:-vehicle-history} - TDENGINE_HISTORY_ENABLED: ${TDENGINE_HISTORY_ENABLED:-true} - TDENGINE_JDBC_URL: ${TDENGINE_JDBC_URL:-jdbc:TAOS-WS://172.17.111.57:6041/vehicle_ts} - TDENGINE_HISTORY_DATABASE: ${TDENGINE_HISTORY_DATABASE:-vehicle_ts} - TDENGINE_USERNAME: ${TDENGINE_USERNAME:-root} - TDENGINE_PASSWORD: ${TDENGINE_PASSWORD:-taosdata} - TDENGINE_CONNECTION_TIMEOUT_MILLIS: ${TDENGINE_CONNECTION_TIMEOUT_MILLIS:-30000} - TDENGINE_MIN_IDLE: ${TDENGINE_MIN_IDLE:-1} - TDENGINE_MAX_POOL_SIZE: ${TDENGINE_MAX_POOL_SIZE:-32} - ports: - - "${VEHICLE_HISTORY_HTTP_PORT:-20200}:20200" - networks: - - vehicle-ingest - healthcheck: - <<: *readiness-healthcheck - test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:20200/actuator/health/readiness >/dev/null"] - - vehicle-analytics-app: - image: ${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}/${LINGNIU_IMAGE_NAMESPACE:-oneos}/vehicle-analytics-app:${LINGNIU_IMAGE_VERSION:?set LINGNIU_IMAGE_VERSION} - container_name: vehicle-analytics-app - restart: unless-stopped - mem_limit: ${VEHICLE_ANALYTICS_MEM_LIMIT:-1024m} - environment: - <<: *common-env - HTTP_PORT: 20310 - KAFKA_CONSUMER_ENABLED: ${KAFKA_CONSUMER_ENABLED_ANALYTICS:-true} - KAFKA_CONSUMER_CLIENT_ID_PREFIX: ${KAFKA_CONSUMER_CLIENT_ID_PREFIX_ANALYTICS:-vehicle-analytics} - KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS: ${KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS_ANALYTICS:-900000} - KAFKA_TOPIC_JT808_EVENT: ${KAFKA_TOPIC_JT808_EVENT:-vehicle.event.jt808.v1} - KAFKA_TOPIC_JT808_DLQ: ${KAFKA_TOPIC_JT808_DLQ:-vehicle.dlq.jt808.v1} - KAFKA_GROUP_STAT: ${KAFKA_GROUP_STAT:-vehicle-stat} - VEHICLE_STAT_ENABLED: ${VEHICLE_STAT_ENABLED:-true} - VEHICLE_STAT_ZONE_ID: ${VEHICLE_STAT_ZONE_ID:-Asia/Shanghai} - VEHICLE_STAT_JT808_MILEAGE_ENABLED: ${VEHICLE_STAT_JT808_MILEAGE_ENABLED:-true} - MYSQL_JDBC_URL: ${MYSQL_JDBC_URL:-} - MYSQL_USERNAME: ${MYSQL_USERNAME:-} - MYSQL_PASSWORD: ${MYSQL_PASSWORD:-} - ports: - - "${VEHICLE_ANALYTICS_HTTP_PORT:-20310}:20310" - networks: - - vehicle-ingest - healthcheck: - <<: *readiness-healthcheck - test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:20310/actuator/health/readiness >/dev/null"] - -networks: - vehicle-ingest: - name: vehicle-ingest - -volumes: - gb32960-ingest-data: - jt808-ingest-data: - yutong-mqtt-data: diff --git a/deploy/systemd/README.md b/deploy/systemd/README.md deleted file mode 100644 index 3a6f16b2..00000000 --- a/deploy/systemd/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Go Vehicle Gateway Native Deployment - -本目录用于 ECS 原生部署。当前 goal 后续不再使用 Docker/Portainer 作为 Go 接入链路的部署方式。 - -## Runtime Layout - -```text -/opt/lingniu-go-native/ - current -> /opt/lingniu-go-native/releases/ - releases// - gateway - history-writer - stat-writer - realtime-api - env/ - gateway.env - history-writer.env - stat-writer.env - realtime-api.env - spool/gateway/ -``` - -## Services - -| systemd unit | Binary | Purpose | -|---|---|---| -| `lingniu-go-gateway.service` | `gateway` | GB32960 TCP `32960`、JT808 TCP `808`、宇通 MQTT 接入,写 Kafka RAW/unified | -| `lingniu-go-history-writer.service` | `history-writer` | 消费 RAW topic,写 TDengine `raw_frames` 和核心时序表 | -| `lingniu-go-stat-writer.service` | `stat-writer` | 消费 RAW topic,写 MySQL `vehicle_daily_metric` | -| `lingniu-go-realtime-api.service` | `realtime-api` | 消费 unified topic,写 Redis,并提供 realtime/raw/stat 查询 API | - -## Build - -在开发机或 CI 上构建 Linux amd64 二进制: - -```bash -cd go/vehicle-gateway -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-native/gateway ./cmd/gateway -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-native/history-writer ./cmd/history-writer -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-native/stat-writer ./cmd/stat-writer -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' -o /tmp/lingniu-go-native/realtime-api ./cmd/realtime-api -``` - -## Cutover Notes - -1. 先上传二进制到新 release 目录并更新 `current` symlink。 -2. 从现有生产 env 生成四个 systemd 专用 env 文件;不要把密钥写入仓库。 -3. 停止旧 Docker Go 容器或 Java 容器,释放 `808`、`32960`、`20210`。 -4. 执行: - -```bash -systemctl daemon-reload -systemctl enable lingniu-go-gateway lingniu-go-history-writer lingniu-go-stat-writer lingniu-go-realtime-api -systemctl restart lingniu-go-gateway lingniu-go-history-writer lingniu-go-stat-writer lingniu-go-realtime-api -``` - -## Verification - -```bash -systemctl is-active lingniu-go-gateway lingniu-go-history-writer lingniu-go-stat-writer lingniu-go-realtime-api -ss -lntp | egrep ':(808|32960|20210)\b' -journalctl -u lingniu-go-gateway --since '5 minutes ago' --no-pager -curl -sS 'http://127.0.0.1:20210/api/history/raw-frames?protocol=JT808&limit=1' -curl -sS 'http://127.0.0.1:20210/api/history/raw-frames?protocol=GB32960&limit=1' -curl -sS 'http://127.0.0.1:20210/api/history/raw-frames?protocol=YUTONG_MQTT&limit=1' -``` diff --git a/deploy/systemd/lingniu-go-gateway.service b/deploy/systemd/lingniu-go-gateway.service deleted file mode 100644 index 40ef12f2..00000000 --- a/deploy/systemd/lingniu-go-gateway.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Lingniu Go Vehicle Gateway -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -WorkingDirectory=/opt/lingniu-go-native/current -EnvironmentFile=/opt/lingniu-go-native/env/gateway.env -ExecStart=/opt/lingniu-go-native/current/gateway -Restart=always -RestartSec=3 -LimitNOFILE=1048576 -KillSignal=SIGTERM -TimeoutStopSec=30 - -[Install] -WantedBy=multi-user.target diff --git a/deploy/systemd/lingniu-go-history-writer.service b/deploy/systemd/lingniu-go-history-writer.service deleted file mode 100644 index b08bed08..00000000 --- a/deploy/systemd/lingniu-go-history-writer.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Lingniu Go History Writer -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -WorkingDirectory=/opt/lingniu-go-native/current -EnvironmentFile=/opt/lingniu-go-native/env/history-writer.env -ExecStart=/opt/lingniu-go-native/current/history-writer -Restart=always -RestartSec=3 -LimitNOFILE=1048576 -KillSignal=SIGTERM -TimeoutStopSec=30 - -[Install] -WantedBy=multi-user.target diff --git a/deploy/systemd/lingniu-go-realtime-api.service b/deploy/systemd/lingniu-go-realtime-api.service deleted file mode 100644 index 6ef81160..00000000 --- a/deploy/systemd/lingniu-go-realtime-api.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Lingniu Go Realtime API -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -WorkingDirectory=/opt/lingniu-go-native/current -EnvironmentFile=/opt/lingniu-go-native/env/realtime-api.env -ExecStart=/opt/lingniu-go-native/current/realtime-api -Restart=always -RestartSec=3 -LimitNOFILE=1048576 -KillSignal=SIGTERM -TimeoutStopSec=30 - -[Install] -WantedBy=multi-user.target diff --git a/deploy/systemd/lingniu-go-stat-writer.service b/deploy/systemd/lingniu-go-stat-writer.service deleted file mode 100644 index 6b46c108..00000000 --- a/deploy/systemd/lingniu-go-stat-writer.service +++ /dev/null @@ -1,18 +0,0 @@ -[Unit] -Description=Lingniu Go Stat Writer -After=network-online.target -Wants=network-online.target - -[Service] -Type=simple -WorkingDirectory=/opt/lingniu-go-native/current -EnvironmentFile=/opt/lingniu-go-native/env/stat-writer.env -ExecStart=/opt/lingniu-go-native/current/stat-writer -Restart=always -RestartSec=3 -LimitNOFILE=1048576 -KillSignal=SIGTERM -TimeoutStopSec=30 - -[Install] -WantedBy=multi-user.target diff --git a/deploy/tools/EnsureKafkaTopics.java b/deploy/tools/EnsureKafkaTopics.java deleted file mode 100644 index e99e0c42..00000000 --- a/deploy/tools/EnsureKafkaTopics.java +++ /dev/null @@ -1,86 +0,0 @@ -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; -import org.apache.kafka.clients.admin.AdminClient; -import org.apache.kafka.clients.admin.AdminClientConfig; -import org.apache.kafka.clients.admin.NewPartitions; -import org.apache.kafka.clients.admin.NewTopic; -import org.apache.kafka.clients.admin.TopicDescription; - -public final class EnsureKafkaTopics { - private static final short REPLICATION_FACTOR = 1; - - private record TopicSpec(String name, int partitions) {} - - private EnsureKafkaTopics() {} - - public static void main(String[] args) throws Exception { - String brokers = args.length > 0 ? args[0] : "172.17.111.56:9092"; - List specs = - List.of( - topic("vehicle.event.gb32960.v1", 12), - topic("vehicle.raw.gb32960.v1", 12), - topic("vehicle.dlq.gb32960.v1", 3), - topic("vehicle.event.jt808.v1", 12), - topic("vehicle.raw.jt808.v1", 12), - topic("vehicle.dlq.jt808.v1", 3), - topic("vehicle.event.mqtt-yutong.v1", 12), - topic("vehicle.raw.mqtt-yutong.v1", 12), - topic("vehicle.dlq.mqtt-yutong.v1", 3)); - - Properties props = new Properties(); - props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, brokers); - props.put(AdminClientConfig.REQUEST_TIMEOUT_MS_CONFIG, "15000"); - props.put(AdminClientConfig.DEFAULT_API_TIMEOUT_MS_CONFIG, "30000"); - - try (AdminClient admin = AdminClient.create(props)) { - Set existing = admin.listTopics().names().get(); - List missing = - specs.stream() - .filter(spec -> !existing.contains(spec.name())) - .map(spec -> new NewTopic(spec.name(), spec.partitions(), REPLICATION_FACTOR)) - .toList(); - if (!missing.isEmpty()) { - try { - admin.createTopics(missing).all().get(); - } catch (ExecutionException e) { - System.out.println("createTopics result=" + e.getCause()); - } - } - - Map byName = - specs.stream().collect(Collectors.toMap(TopicSpec::name, spec -> spec)); - Map descriptions = - admin.describeTopics(byName.keySet()).allTopicNames().get(); - - Map increases = - descriptions.entrySet().stream() - .filter(entry -> entry.getValue().partitions().size() < byName.get(entry.getKey()).partitions()) - .collect( - Collectors.toMap( - Map.Entry::getKey, - entry -> NewPartitions.increaseTo(byName.get(entry.getKey()).partitions()))); - if (!increases.isEmpty()) { - admin.createPartitions(increases).all().get(); - Thread.sleep(Duration.ofSeconds(2).toMillis()); - descriptions = admin.describeTopics(byName.keySet()).allTopicNames().get(); - } - - List lines = new ArrayList<>(); - descriptions.entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .forEach(entry -> lines.add(entry.getKey() + " partitions=" + entry.getValue().partitions().size())); - System.out.println("bootstrap=" + brokers); - lines.forEach(System.out::println); - } - } - - private static TopicSpec topic(String name, int partitions) { - return new TopicSpec(name, partitions); - } -} diff --git a/docs/module-data-flow.html b/docs/module-data-flow.html deleted file mode 100644 index 377abbc0..00000000 --- a/docs/module-data-flow.html +++ /dev/null @@ -1,749 +0,0 @@ - - - - - - lingniu-vehicle-ingest 模块与数据流 - - - -
-

lingniu-vehicle-ingest 模块与数据流

-

- 本图基于当前 Maven 多模块项目整理,用于后续讨论协议接入、内部字段、氢能车辆运营统计、安全告警和下游消费边界。 - 当前架构将接入层、历史明细、Redis 热状态和日统计拆成独立模块,通过统一 Kafka Envelope 解耦。 -

-
- -
-
-

一、系统定位

-

- 当前项目不是业务库写入服务。它的核心职责是把 GB/T 32960、JT/T 808、Yutong MQTT - 等默认生产来源的车辆数据统一转换为 VehicleEvent 和全字段 TelemetrySnapshot; - JT/T 1078、JSATL12 作为显式 profile 的可选能力保留, - 再通过 Kafka、原始报文归档、TDengine 历史索引、Redis 热状态和独立统计模块交给下游业务。 -

-
-
- 接入方式 - Netty TCP、MQTT、后续可扩展更多 Inbound Adapter;信达 Push 源码已删除。 -
-
- 统一模型 - 所有协议归一到 VehicleEvent 和全字段内部 TelemetrySnapshot -
-
- 输出方式 - Kafka Protobuf Envelope + 原始 bytes 冷存 + TDengine raw_frames/locations + Redis 热状态。 -
-
- 业务边界 - 日统计由 vehicle-stat-service 消费 Kafka 实现,告警工单、资产业务继续由下游消费。 -
-
-
- -
-

二、模块分层图

-
-
-flowchart TB
-  subgraph external["外部来源"]
-    vehicle["车辆终端
GB/T 32960 / JT808 / JT1078"] - mqttSource["车企或平台 MQTT"] - commandClient["业务系统 / 运维平台
HTTP 下行命令"] - end - - subgraph protocolModules["协议模块 modules/protocols"] - gb["protocol-gb32960
32960 Netty 接入、鉴权、ACK、解析"] - jt808["protocol-jt808
808 Netty 接入、会话、位置/批量位置、媒体、透传、下行"] - jt1078["protocol-jt1078
1078 信令 + TCP/UDP RTP 媒体流归档"] - jsatl12["protocol-jsatl12
主动安全附件流接入"] - end - - subgraph inbound["入口适配 modules/inbound"] - mqtt["inbound-mqtt
MQTT endpoint 生命周期、PEM TLS"] - mqttProfile["MqttProfileRegistry
按 endpoint profile 解析/映射"] - end - - subgraph protocolBase["核心与公共能力 modules/core"] - codec["ingest-codec-common
BCD / CRC / BCC / bit 工具"] - session["session-core
设备会话、命令分发接口、会话存储"] - identity["vehicle-identity
跨协议身份解析、MySQL 绑定表"] - obs["observability
metrics / health"] - api["ingest-api
ProtocolId / RawFrame / VehicleEvent / 注解 SPI"] - registry["HandlerRegistry
按协议、命令、infoType 路由"] - dispatcher["Dispatcher
RawFrame 到 Handler 到 EventBus"] - bus["DisruptorEventBus
高吞吐事件发布"] - end - - subgraph sink["输出与明细存储 modules/sinks"] - kafka["sink-kafka
VehicleEvent 到 Protobuf Envelope 到 Kafka"] - archive["sink-archive
RawArchive 到本地文件系统冷存"] - tdengineStore["tdengine-history-store
TDengine raw_frames + locations"] - end - - subgraph services["消费服务 modules/services"] - history["event-history-service
Kafka event/raw 到 TDengine 历史查询"] - state["vehicle-state-service
可选 Redis 热状态
optional-latest-state"] - stat["vehicle-stat-service
Kafka 全字段事件到指标表"] - end - - subgraph apps["应用入口 modules/apps"] - gbApp["gb32960-ingest-app
GB32960 TCP 接入"] - jtApp["jt808-ingest-app
JT808 TCP 接入"] - yutongApp["yutong-mqtt-app
宇通 MQTT 接入"] - historyApp["vehicle-history-app
历史查询与 TDengine 写入"] - analyticsApp["vehicle-analytics-app
808 日里程指标消费"] - gateway["command-gateway
可选 HTTP 设备命令入口"] - end - - vehicle --> gbApp - vehicle --> jtApp - vehicle --> jt1078 - vehicle --> jsatl12 - mqttSource --> yutongApp - gbApp --> gb - jtApp --> jt808 - yutongApp --> mqtt - mqtt --> mqttProfile - mqttProfile --> api - commandClient --> gateway - - gb --> codec - jt808 --> codec - jt1078 --> codec - jsatl12 --> codec - - gb --> api - jt808 --> api - jsatl12 --> archive - jsatl12 --> jt808 - - api --> registry - registry --> dispatcher - dispatcher --> bus - bus --> kafka - bus --> archive - kafka --> historyApp - kafka --> analyticsApp - historyApp --> history - analyticsApp --> stat - kafka -.可选热状态.-> state - history --> tdengineStore - - gateway --> session - session --> jt808 - session --> gb - identity --> jt808 - identity --> mqttProfile - - obs -.监控.-> dispatcher - obs -.监控.-> bus - obs -.监控.-> kafka - - classDef entry fill:#eff6ff,stroke:#2563eb,color:#1e3a8a; - classDef core fill:#ecfdf3,stroke:#0f766e,color:#134e48; - classDef sink fill:#fffbeb,stroke:#b45309,color:#7c2d12; - classDef command fill:#f5f3ff,stroke:#6d28d9,color:#4c1d95; - classDef support fill:#f8fafc,stroke:#64748b,color:#334155; - - class gb,jt808,jt1078,jsatl12,mqtt,mqttProfile,gbApp,jtApp,yutongApp,historyApp,analyticsApp entry; - class api,registry,dispatcher,bus,identity core; - class kafka,archive,tdengineStore sink; - class gateway,session command; - class codec,obs support; -
-
-
- 入口/协议模块 - 核心管线 - 输出层 - 下行命令/会话 -
-
- -
-

三、上行数据流转

-
-
-sequenceDiagram
-  autonumber
-  participant Device as 车辆/平台
-  participant Inbound as Inbound Adapter
Netty/MQTT - participant Decoder as FrameDecoder
MessageDecoder - participant Dispatcher as Dispatcher - participant Handler as Protocol Handler
Mapper - participant EventBus as DisruptorEventBus - participant Kafka as sink-kafka
Kafka Envelope - participant Archive as sink-archive
ArchiveStore - participant History as vehicle-history-app - participant TDengine as tdengine-history-store
raw_frames + locations - participant Consumer as 下游业务消费者 - - Device->>Inbound: 原始报文 bytes / MQTT message - Inbound->>Decoder: 粘包拆帧、校验、协议体解析 - Inbound->>Inbound: MQTT 按 endpoint profile 选择厂商解析器 - Decoder->>Dispatcher: RawFrame(protocol, command, infoType, payload, rawBytes) - Dispatcher->>EventBus: 先发布 RawArchive 事件 - EventBus->>Archive: 保存原始 bytes,生成可回放材料 - Dispatcher->>Handler: 根据 protocol + command + infoType 路由 - Handler->>Handler: 协议字段映射为内部字段 - Dispatcher->>Handler: 绑定 rawArchiveKey / rawArchiveUri - Handler->>EventBus: VehicleEvent(Realtime / Location / Alarm / Login ...) - EventBus->>Kafka: 投递规整后的业务事件 - Kafka->>Kafka: EnvelopeMapper 转 Protobuf - Kafka->>History: event/raw topic,key=vin,单车有序 - History->>TDengine: 写 raw_frames、locations 和 raw parsed JSON - TDengine->>Consumer: 分页历史查询、原始帧追溯、位置查询 - Kafka->>Consumer: Redis 热状态、指标统计、告警工单等下游消费 -
-
-
- 关键边界: - 协议字段只在协议模块内解释,出了 Mapper 以后都应该使用内部字段。统计每日里程、每日用电量、每日用氢量、储氢安全和氢泄露告警时,应消费全字段 TelemetrySnapshot,不要直接依赖某个协议的原始字段名。 -
-
- 原始追溯: - Dispatcher 为所有带 rawBytes 的 RawFrame 生成统一的 rawArchiveKey,业务事件 metadata 使用 rawArchiveUri=archive://... 指向同一份原始 bytes。归档文件的真实本地路径只属于 ArchiveStore,查询、导出和 Kafka Envelope 使用逻辑 URI 解耦存储实现。 -
-
- -
-

四、32960 细化链路

-
-
-flowchart LR
-  terminal["氢能车 TBOX
GB/T 32960"] --> netty["Gb32960NettyServer
TCP / TLS / Idle 检测"] - netty --> access["Gb32960AccessService
VIN 白名单 / 平台登录鉴权"] - netty --> frame["Gb32960FrameDecoder
拆帧、转义、校验"] - frame --> decoder["Gb32960MessageDecoder
Header + Body 解析"] - decoder --> diag["Gb32960FrameDiagnostics
首帧、Raw 块、异常诊断"] - decoder --> handler["Gb32960ChannelHandler
ACK / NACK / dispatch"] - handler --> ack["Gb32960AckService
登录应答、失败断开"] - handler --> dispatcher["Dispatcher"] - dispatcher --> rtHandler["Gb32960RealtimeHandler"] - rtHandler --> mapper["Gb32960EventMapper
协议字段到内部字段"] - mapper --> realtime["RealtimePayload
速度、里程、电量、氢量、压力、温度"] - mapper --> alarm["AlarmPayload
安全分类、氢泄露、告警等级"] - mapper --> login["Login / Logout / Heartbeat"] - realtime --> bus["DisruptorEventBus"] - alarm --> bus - login --> bus - bus --> kafka["Kafka Envelope"] - bus --> archive["RawArchive 冷存"] - - classDef safety fill:#fff1f3,stroke:#b42318,color:#7a271a; - classDef core fill:#ecfdf3,stroke:#0f766e,color:#134e48; - classDef entry fill:#eff6ff,stroke:#2563eb,color:#1e3a8a; - classDef sink fill:#fffbeb,stroke:#b45309,color:#7c2d12; - - class terminal,netty,frame,decoder,handler entry; - class access,diag,ack,dispatcher,rtHandler,mapper,bus core; - class alarm safety; - class kafka,archive sink; -
-
-
- -
-

五、模块职责表

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
模块职责主要输入主要输出业务开发关注点
modules/core/ingest-api定义系统边界:协议 ID、RawFrame、IngestContext、VehicleEvent、Payload、Handler 注解、Sink SPI;ProtocolId 提供 UNKNOWN 隔离桶,供消费侧兼容未来协议或脏数据,不作为正式接入协议使用;consumer 包定义 EnvelopeIngestor、EnvelopeConsumerProcessor 和 EnvelopeDeadLetterSink,统一 Kafka 消费结果与死信边界。无直接外部输入。全项目共享的接口和内部事件模型。内部字段定义新增事件类型字段兼容性
modules/core/ingest-codec-common公共编解码工具,承载 BCD、CRC、BCC、bit 操作等协议底层能力。协议模块传入的 byte、bit、校验材料。解析辅助结果。协议基础能力
modules/core/ingest-core核心管线。扫描 Handler,按协议和命令路由,执行拦截器,发布 VehicleEvent 到 Disruptor。RawFrame。VehicleEvent、RawArchive。吞吐路由原始可回放
modules/core/session-core设备会话、命令下发抽象、SessionStore、CommandDispatcher 默认实现。SessionStore 仅保留 Redis 后端;协议进程本地只保存真实 Netty Channel,Redis 保存 sessionId、VIN 和 phone 三索引及 TTL,协议模块只依赖 SPI,不感知存储细节。设备连接、命令请求。会话状态、命令分发结果、Redis 会话索引。在线状态下行控制多实例会话索引
modules/core/vehicle-identity跨协议车辆身份解析,维护 phone、deviceId、plate 到 VIN 的绑定关系;生产运行使用 MySQL 绑定表,避免重启丢失绑定并保证多实例一致。协议模块传入的外部标识。稳定 VIN、解析来源、是否已命中绑定、MySQL 绑定记录。统计主键多协议归一持久化绑定
modules/core/observability监控、指标、健康检查等横切能力。核心管线和 Sink 运行状态。Micrometer 指标、健康信息。可用性延迟监控
modules/protocols/protocol-gb32960GB/T 32960 接入、鉴权、ACK、报文解析、实时数据和告警映射。32960 TCP 报文。Realtime、Location、Alarm、Login、Logout、Heartbeat、RawFrame。氢泄露储罐安全电量/氢量里程
modules/protocols/protocol-jt808JT/T 808 接入、注册/鉴权/心跳/注销、位置/位置附加项、批量位置、参数/属性、多媒体、透传、未知上行兜底透传、分帧边界异常和协议解析异常坏帧兜底、下行命令和超长下行分包能力;0x0200 位置附加项 0x01 总里程按 0.1km 解码为内部字段 total_mileage_km,后续每日里程统计不依赖 808 原始字段;0x0102 鉴权帧解析会保留完整 token,并尽量提取 IMEI 和软件版本;注册和鉴权会话均使用共享身份解析后的内部 VIN,避免 deviceId/IMEI 污染后续会话和下行边界;终端连接断开时同步解绑 Channel 并清理 SessionStore 会话,避免 command-gateway 误判离线车辆仍可下行;正常上行 RawArchive 和业务事件 metadata 均使用共享身份解析后的内部 vin,并保留 phone、identityResolved、identitySource 便于冷存和业务事件按同一车辆查询;无法解析终端身份的坏帧 RawArchive 和 Passthrough 均按 vin=unknown、identityResolved=false、identitySource=UNKNOWN 标记;0x0900 透传事件按原始消息 ID 归类,透传类型写入 passthroughType metadata。808 TCP 报文。Location、Login、Logout、Heartbeat、MediaMeta、Passthrough、未知上行 Raw 兜底、坏帧 Passthrough、会话状态、下行响应。GPS 位置在线状态媒体证据命令链路
modules/protocols/protocol-jt1078JT/T 1078 音视频能力。信令在 JT808 mapper 存在时桥接复用 JT808 连接和包头;0x1005 乘客流量保留原始 body,同时结构化 channelId、startTime、endTime、passengerGetOn、passengerGetOff metadata 便于查询/导出;0x1205 文件列表保留原始 body,同时结构化 responseSerialNo、fileCount 摘要;下行信令编码由本模块提供,覆盖 0x1003 音视频属性查询、0x9101 实时预览、0x9102 实时控制、0x9201 历史回放、0x9202 回放控制、0x9205 资源列表查询、0x9206 文件上传和 0x9207 文件上传控制,命令发送仍由 command-gateway 经 CommandDispatcher 复用 JT808 在线通道;TCP/UDP RTP 媒体流独立端口接入,生产默认端口按旧接收服务对齐为 11078,按 VIN/通道/时间分段写 ArchiveStore,Kafka 只发送 MediaMeta 引用,事件 metadata 暴露内部 vin、sim、channelId、dataType、packetType、segment、sequence、segmentSizeBytes、archiveKey 和 archiveRef,MediaMeta.sizeBytes 同步当前片段大小,MediaMeta 按 archiveKey 去重,避免同一 SIM 运行中从 fallback 身份切换到内部 VIN 时漏发新归档引用,便于文件明细库追踪、展示和导出;超长/短包/坏魔数等 RTP 入口异常和媒体归档失败都会兜底产出 Passthrough 错误事件,坏 RTP 统一走 Dispatcher 产出 RawArchive 和 Passthrough,统一标记 parseError 和 parseErrorMessage,并保留内部 vin、原始 bytes、peer、长度、原因、RawArchive 引用、segment 和 archiveKey 便于排障;坏 RTP 头部可读时会提取 SIM 并通过共享身份服务映射 VIN。1078 信令报文、TCP/UDP RTP 媒体流。MediaMeta、乘客流量 Passthrough、文件列表 Passthrough、坏 RTP/归档失败 Passthrough、归档媒体分段。视频扩展信令事件化大流冷存
modules/protocols/protocol-jsatl12苏标主动安全报警附件接入(仅 optional-attachments profile 显式构建,不属于默认生产面)。依赖 ArchiveStore 和 JT808 decoder;端口按旧接收服务对齐为 7612;附件 DataPacket 按旧服务真实布局 01cd + 50B文件名 + offset + length + data 分帧解析,不再把文件名前 4 字节误判为帧长;附件数据写 ArchiveStore 后通过 Dispatcher 产出 MediaMeta 引用事件,事件 metadata 保留 fileName、fileOffset、declaredChunkSizeBytes、archiveKey 和 archiveRef,便于附件明细查询、导出和补传诊断;内置附件状态机处理 T1210 文件清单、DataPacket 分块区间和 T1212 上传完成消息,数据分块会按 T1210 文件清单中的文件名继承手机号并通过共享身份解析器得到内部 VIN,入口 RawFrame、ArchivedChunk 和 MediaMeta 均保留 phone、vin、identityResolved、identitySource,只有找不到文件归属时才标记 UNKNOWN;按文件名合并无身份数据分块并返回 0x9212 完成/补传应答,补传应答包含缺失 offset/length 区间;附件归档失败会产出 JSATL12 Passthrough 错误事件并保留原始分块;JT_MESSAGE 信令复用 JT808 decoder 后进入 Dispatcher,桥接 RawFrame 使用共享身份解析后的内部 VIN并保留 phone、identityResolved、identitySource,坏 JT 信令入口 RawFrame 同样标记 UNKNOWN 身份;超长/畸形附件帧、未闭合超长 JT 信令和坏 JT 信令都以 JSATL12 Passthrough 兜底,附件帧异常同时标记 frameError 与统一 parseError/parseErrorMessage,保留原始字节、peer、长度和错误元数据,帧长和 worker 线程支持部署配置。主动安全附件流、JT808 信令帧。归档文件、MediaMeta、JT808 统一事件、坏帧/坏信令/归档失败 Passthrough。附件归档报警证据信令复用错误隔离
modules/inbound/inbound-mqttMQTT 多 endpoint 生命周期管理,支持 PEM CA、客户端证书/私钥双向 TLS,按 endpoint profile 路由到厂商解析/映射实现,默认提供宇通 profile;生产默认接入口径参考旧接收服务:ssl://cpxlm.axxc.cn:38883、topic /ytforward/shln/+、QoS 2、cleanSession=false、keepAlive=20s、connectTimeout=10s,用户名和密码仍通过环境变量注入;入站 RawFrame 和事件映射均接入统一车辆身份解析,解析器保留 externalVin、phone、mqttDeviceId、plateNo,优先按设备号/终端 ID/IMEI 绑定解析内部 VIN,未绑定且字段形态为 VIN 时再作为显式 VIN 兜底,冷存和事件 metadata 均保留这些外部标识、identityResolved、identitySource;未知 profile、解析失败、profile 运行时异常、endpoint 初始化/连接/订阅失败均兜底为 Passthrough,解析失败诊断和 profile 异常诊断都会同步写入 RawArchive metadata,并在最终 Passthrough 事件保留 parseErrorMessage 或 operational reason,无法解析设备身份的兜底事件和运行类归档都按 vin=unknown、identityResolved=false、identitySource=UNKNOWN 标记,保留原始 payload 或 operational 错误信息便于排障和归档追溯。MQTT topic/message、endpoint profile、TLS PEM 配置。统一 RawFrame、Realtime、Location、Passthrough。车企平台接入profile 扩展身份映射双向 TLS错误隔离
modules/sinks/sink-kafkaKafka Sink。把 VehicleEvent 转成 Protobuf Envelope,按 VIN 分区投递;业务事件携带全字段 TelemetrySnapshot 和 rawArchiveUri,显式 RawArchive Envelope 会填充 archive:// 逻辑 URI 与 size,默认 Kafka Sink 仍不发送原始 bytes 本体;同时提供 KafkaEnvelopeConsumerFactory、KafkaEnvelopeConsumerRunner、KafkaEnvelopeConsumerWorker 和 KafkaEnvelopeDeadLetterSink,统一服务侧 Kafka 消费启动、独立 group 绑定、死信发布和 topic 到 EnvelopeConsumerProcessor 的分发。VehicleEvent。Kafka Protobuf 消息、消费侧 DLQ 记录。单车有序Schema 演进安全字段下发给消费者
modules/sinks/sink-archive原始报文冷存,当前为本地文件系统实现;写入键与业务事件 metadata 中的 rawArchiveKey 保持一致。RawArchive 事件、附件流。可回放原始文件、archive://... 逻辑引用。问题排查合规留痕
modules/sinks/tdengine-history-store生产历史库边界,负责 TDengine schema、raw_frames/location 行映射、批量写入和分页查询语句;raw_frames 保留完整 payloadJson.parsed,位置表保持轻量字段并通过 rawUri 关联原始帧。Kafka Protobuf Envelope、Raw Envelope。TDengine raw_frames 和位置表。生产历史分页查询raw 追溯
modules/services/event-history-service消费 32960、808、宇通 MQTT 的 Kafka event/raw topic,生产默认写入 TDengine raw_frames 和位置表,并提供 raw 帧、位置历史等分页查询边界;Raw 查询返回完整 parsed JSON,位置历史只保留核心字段并通过 rawUri 关联原始帧;生产消费可使用 tryIngest 和自动装配的 EnvelopeConsumerProcessor 将坏 protobuf、缺快照和存储异常收敛为结构化结果,并通过 EnvelopeDeadLetterSink 发布死信,避免阻塞 Kafka 分区。Kafka Protobuf Envelope。TDengine raw_frames、位置分页结果、raw archive 逻辑引用。历史明细分页查询raw JSON
modules/services/vehicle-state-service消费 Kafka 全字段事件,更新车辆最新状态、位置、安全和最后事件到 Redis;仅通过 optional-latest-state profile 显式构建,不属于默认生产 reactor;生产消费可使用 tryIngest 和自动装配的 EnvelopeConsumerProcessor 隔离坏 protobuf 和缺快照 Envelope,并把失败记录交给死信出口。Kafka Protobuf Envelope。vehicle:state:{vin}vehicle:location:{vin}vehicle:safety:{vin}毫秒级热查询氢泄露安全
modules/services/vehicle-stat-service消费 Kafka 全字段事件,808 每日里程只使用 0x0200 附加项 0x01 上报的总里程做当日首末差值;生产消费可使用 tryIngest 和自动装配的 EnvelopeConsumerProcessor 隔离坏 protobuf,缺少 telemetry_snapshot 的消息会标记为 SKIPPED 并进入死信出口。Kafka Protobuf Envelope、808 total_mileage_kmvehicle_stat_metricmetric_key=daily_mileage_kmcalculation_method=JT808_TOTAL_MILEAGE_DIFF每日里程指标表
modules/apps/command-gateway可选 HTTP 到设备下行命令入口。支持会话查询、808 位置查询、参数查询/设置、终端控制、平台通用应答、终端属性查询 0x8107、区域删除 0x8601、人工报警确认 0x8203,以及 1078 音视频属性查询、实时预览/控制、历史回放/控制、资源列表查询、文件上传/控制;通过 CommandDispatcher 复用 protocol-jt808 的在线通道、流水号和同步应答等待能力。业务系统命令请求。CommandDispatcher 调用、设备应答摘要。远程控制参数设置属性查询区域删除报警确认视频控制
modules/apps/gb32960-ingest-app生产 GB32960 TCP 接入应用,只负责接收、鉴权、解析、冷存引用和 Kafka 投递。GB/T 32960 TCP 32960。vehicle.raw.gb32960.v1vehicle.event.gb32960.v1生产接入32960
modules/apps/jt808-ingest-app生产 JT808 TCP 接入应用,只负责 808 注册/鉴权/位置等上行解析、冷存引用和 Kafka 投递。JT/T 808 TCP 808。vehicle.raw.jt808.v1vehicle.event.jt808.v1生产接入808
modules/apps/yutong-mqtt-app生产宇通 MQTT 接入应用,按 endpoint profile 解析 MQTT 报文并投递 Kafka。宇通 MQTT topic。vehicle.raw.mqtt-yutong.v1vehicle.event.mqtt-yutong.v1生产接入MQTT
modules/apps/vehicle-history-app生产历史应用,消费 32960、808、宇通 MQTT 的 raw/event Kafka Envelope,写 TDengine 并提供历史查询。Kafka Protobuf Envelope。TDengine raw_frames、位置表和历史查询 API。历史查询raw JSON
modules/apps/vehicle-analytics-app生产指标应用,当前只消费 JT808 事件并用 GPS 总里程首末差值写每日里程指标。vehicle.event.jt808.v1MySQL vehicle_stat_metric每日里程指标表
-
- -
-

六、氢能业务开发落点

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
业务主题当前建议落点说明
车辆状态、速度、位置RealtimePayloadLocationPayload32960 和 808 都可能提供位置。统计侧需要明确优先级,例如优先 32960,缺失时用 808 补点。
每日里程Kafka 下游统计服务接入层只投递总里程和实时点位;日增里程建议下游按 VIN、自然日、事件时间聚合,处理回补和乱序。
每日用电量内部电池字段 + 下游统计接入层保留 SOC、电压、电流等瞬时字段;若有累计电耗字段,可加入内部字段模型后统一投递。
每日用氢量hydrogenRemainingKg + 下游统计优先使用累计氢耗字段;没有累计值时用氢余量差值估算,并在统计结果里标记估算口径。
储氢安全RealtimePayload 压力/温度 + AlarmPayload.safetyCategory高压、低压、温度、异常 bit 都应统一归入储罐安全域,下游可做趋势、阈值和连续异常判断。
氢气泄露AlarmPayload.hydrogenLeakDetected当前已作为高优先级安全事件。32960 中出现 HYDROGEN_LEAK 时强制映射为 CRITICAL
原始报文追溯RawArchive + rawArchiveUri + sink-archive业务统计出现争议时,用事件 metadata 中的 archive://... 逻辑 URI 回查原始 bytes,结合 eventId、traceId、VIN、时间复现解析链路。
明细展示和导出vehicle-history-app + tdengine-history-store生产查询通过 TDengine raw_frames、位置表和 rawUri 关联完成;不再维护文件型事件索引旁路。
-
- 后续开发建议: - 当前 Kafka Protobuf 已加入全字段 TelemetrySnapshot。后续业务统计服务继续只依赖内部字段,不依赖 32960 原始字段名,这样接入 808、MQTT、车企私有协议时不会重写统计口径。 -
-
-
- - - - diff --git a/docs/operations/current-ecs-deployment.md b/docs/operations/current-ecs-deployment.md deleted file mode 100644 index cd6b8bf2..00000000 --- a/docs/operations/current-ecs-deployment.md +++ /dev/null @@ -1,227 +0,0 @@ -# 当前 ECS Go 原生部署说明 - -更新时间:2026-07-02 01:30 CST - -本文档记录当前 `lingniu-vehicle-ingest` 的生产运行面。当前 goal 的接入链路已经切到 Go 原生 systemd 部署,不再以 Docker/Portainer 作为生产运行方式。密钥只维护在 ECS 环境文件或受控凭据中,不写入 Git。 - -## 部署范围 - -| systemd 服务 | 二进制 | 说明 | 对外端口 | -| --- | --- | --- | --- | -| `lingniu-go-gateway.service` | `gateway` | GB32960 TCP、JT808 TCP、宇通 MQTT 接入,解析后写 Kafka RAW 和统一事件 | TCP `32960`、TCP `808` | -| `lingniu-go-history-writer.service` | `history-writer` | 消费 Kafka RAW,写 TDengine RAW、位置点、里程点核心表 | 无 HTTP | -| `lingniu-go-stat-writer.service` | `stat-writer` | 消费 Kafka RAW,按总里程差值法写 MySQL 每日指标 | 无 HTTP | -| `lingniu-go-realtime-api.service` | `realtime-api` | 消费统一事件写 Redis,并提供实时、历史、统计查询 API | HTTP `20210` | - -信达 Push 已废弃,不参与当前 Go 生产链路。旧 Java/Docker 服务不应占用 `808`、`32960`、`20210`。 - -## 应用 ECS - -| 项 | 值 | -| --- | --- | -| 公网 IP | `115.29.187.205` | -| 登录用户 | `root` | -| 部署目录 | `/opt/lingniu-go-native` | -| 当前 release | `/opt/lingniu-go-native/current` | -| 环境文件目录 | `/opt/lingniu-go-native/env` | -| systemd unit 目录 | `/etc/systemd/system` | -| 对外服务 | `32960`、`808`、`20210` | - -运行目录结构: - -```text -/opt/lingniu-go-native/ - current -> /opt/lingniu-go-native/releases/ - releases// - gateway - history-writer - stat-writer - realtime-api - env/ - gateway.env - history-writer.env - stat-writer.env - realtime-api.env - spool/gateway/ -``` - -## 中间件 - -| 组件 | 内网地址 | 公网地址 | 用途 | -| --- | --- | --- | --- | -| Kafka | `172.17.111.56:9092` | `114.55.58.251:9092` | RAW 和统一事件消息总线 | -| TDengine | `172.17.111.57:6041` | `115.29.185.82:6041` | RAW、位置、里程点时序热存储 | -| MySQL RDS | `rm-bp179zbv481rnw3e2.mysql.rds.aliyuncs.com:3306` | `rm-bp179zbv481rnw3e2no.mysql.rds.aliyuncs.com:3306` | 身份映射、JT808 注册、每日指标 | -| Redis RDS | `r-bp1u741kij7e51i481.redis.rds.aliyuncs.com:6379` | 无 | 准实时车辆状态缓存 | - -生产应用之间访问中间件优先使用内网地址。RDS 白名单需要允许应用 ECS 私网访问。 - -## Kafka Topic - -| Topic | 生产者 | 消费者 | 内容 | -| --- | --- | --- | --- | -| `vehicle.raw.go.gb32960.v1` | `gateway` | `history-writer`、`stat-writer` | GB32960 完整 RAW 记录 | -| `vehicle.raw.go.jt808.v1` | `gateway` | `history-writer`、`stat-writer` | JT808 完整 RAW 记录 | -| `vehicle.raw.go.yutong-mqtt.v1` | `gateway` | `history-writer` | 宇通 MQTT 完整 RAW 记录 | -| `vehicle.event.go.unified.v1` | `gateway` | `realtime-api` | 三类协议统一事件,用于 Redis 实时状态 | - -`gateway` 配置了 Kafka retry;生产 env 中启用 `KAFKA_SPOOL_DIR` 时,本地 spool 可在 Kafka 短暂不可用后回放。 - -Kafka 消费组: - -| Consumer Group | Topic | 当前验证 | -| --- | --- | --- | -| `go-history-writer` | 三个 `vehicle.raw.go.*` topic | 2026-07-02 验证 lag 为 `0` | -| `go-stat-writer` | `vehicle.raw.go.gb32960.v1`、`vehicle.raw.go.jt808.v1` | 2026-07-02 验证 lag 为 `0` | -| `go-realtime-api` | `vehicle.event.go.unified.v1` | 2026-07-02 验证 lag 为 `0` | - -可重复 smoke: - -```bash -python3 tools/go_kafka_prod_smoke.py --host 114.55.58.251 --user root --max-lag 100 -``` - -该脚本通过 SSH 登录 Kafka ECS,检查 Go 生产 topic 是否存在,并校验 `go-history-writer`、`go-stat-writer`、`go-realtime-api` 的消费组 lag。运行环境需要已配置 SSH 免密或已建立可用的 SSH 认证方式。 - -## TDengine 数据层 - -默认数据库:`lingniu_vehicle_ts` - -| 表 | 类型 | 说明 | -| --- | --- | --- | -| `raw_frames` | stable | 完整 RAW 帧,包含 `raw_hex`、`parsed_json`、`fields_json`、`source_endpoint` 和协议/车辆 tags | -| `vehicle_locations` | stable | 最小化位置历史,保留 `frame_id` 回链 RAW | -| `vehicle_mileage_points` | stable | 最小化总里程点,供历史查询和统计复核 | - -设计原则: - -- 完整结构化 payload 只落在 `raw_frames.parsed_json` / `fields_json`。 -- `vehicle_locations` 和 `vehicle_mileage_points` 只保留核心查询字段,不重复保存完整 JSON。 -- `telemetry_fields` 不由当前服务维护,字段配置解析由独立子服务处理。 -- API 传入东八区时间时,服务会转换为 TDengine 当前存储使用的 UTC 字面量再查询。 - -## MySQL 数据层 - -默认业务库:`lingniu_vehicle_data` - -| 表 | 维护方 | 说明 | -| --- | --- | --- | -| `vehicle_identity_binding` | 人工/外部主数据 | VIN 映射主表,用 `phone`、`device_id`、`plate` 降级定位 VIN | -| `jt808_registration` | `gateway` 自动写入 | 仅 JT808 注册和鉴权记录,以 `phone` 作为主键,记录设备、车牌、厂家、鉴权、来源端点 | -| `vehicle_daily_metric` | `stat-writer` 自动写入 | 每日指标表,保存 `daily_mileage_km` 和 `daily_total_mileage_km` | - -每日里程算法: - -```text -daily_mileage_km = 当日最大 total_mileage_km - 当日最小 total_mileage_km -daily_total_mileage_km = 当日最大 total_mileage_km -``` - -统计按 `Asia/Shanghai` 自然日归档,当前支持 GB32960、JT808、宇通 MQTT 中能解析出 `total_mileage_km` 的数据。JT808 的总里程来自位置附加信息 `0x01`,单位按协议转换为 km;宇通 MQTT 的 `TOTAL_MILEAGE` 原始单位为米,入库前转换为 km。 - -## Redis 实时层 - -`realtime-api` 消费 `vehicle.event.go.unified.v1`,按 VIN/vehicle key 维护准实时快照。主要能力: - -| API | 说明 | -| --- | --- | -| `/api/realtime/vehicles/{vin}` | 查询车辆合并实时快照 | -| `/api/realtime/vehicles/{vin}/online` | 查询车辆是否在线 | -| `/api/realtime/vehicles/{vin}/protocols/{protocol}` | 查询指定协议的实时快照 | - -Redis 只作为实时缓存;历史和统计以 TDengine/MySQL 为准。 - -## API 入口 - -公网基础地址: - -```text -http://115.29.187.205:20210 -``` - -常用查询: - -```bash -curl -sS 'http://115.29.187.205:20210/api/history/raw-frames?protocol=GB32960&dateFrom=2026-07-02T00:00:00%2B08:00&dateTo=2026-07-03T00:00:00%2B08:00&limit=1' -curl -sS 'http://115.29.187.205:20210/api/history/raw-frames?protocol=JT808&dateFrom=2026-07-02T00:00:00%2B08:00&dateTo=2026-07-03T00:00:00%2B08:00&limit=1' -curl -sS 'http://115.29.187.205:20210/api/history/raw-frames?protocol=YUTONG_MQTT&dateFrom=2026-07-02T00:00:00%2B08:00&dateTo=2026-07-03T00:00:00%2B08:00&limit=1' -curl -sS 'http://115.29.187.205:20210/api/history/locations?protocol=JT808&dateFrom=2026-07-02T00:00:00%2B08:00&dateTo=2026-07-03T00:00:00%2B08:00&limit=1' -curl -sS 'http://115.29.187.205:20210/api/history/mileage-points?protocol=JT808&dateFrom=2026-07-02T00:00:00%2B08:00&dateTo=2026-07-03T00:00:00%2B08:00&limit=1' -curl -sS 'http://115.29.187.205:20210/api/stats/daily-metrics?dateFrom=2026-07-02&dateTo=2026-07-02&limit=20' -``` - -分页参数统一使用 `limit` 和 `offset`。 - -部署后推荐直接运行 Go 原生生产 smoke: - -```bash -python3 tools/go_prod_acceptance.py --date 2026-07-02 -``` - -该聚合脚本会串行执行 systemd/端口检查、Kafka topic/consumer lag 检查、HTTP 数据链路检查,任一子检查失败时整体退出码为非 0。需要 SSH 可登录应用 ECS 和 Kafka ECS。 - -也可以单独运行子检查: - -```bash -python3 tools/go_systemd_prod_smoke.py --host 115.29.187.205 --user root -python3 tools/go_native_prod_smoke.py --date 2026-07-02 --timeout 8 -``` - -`go_systemd_prod_smoke.py` 通过 SSH 检查四个 Go systemd 服务是否 `active` 且 `enabled`,并确认 `808`、`32960` 由 `gateway` 监听、`20210` 由 `realtime-api` 监听,同时要求 `/opt/lingniu-go-native/current` 指向有效 release、四个生产二进制均可执行、`/opt/lingniu-go-native/spool/gateway` 没有待回放文件,避免旧 Java/Docker 进程占用生产端口、部署目录错乱或 Kafka spool 静默堆积。运行环境需要已配置 SSH 免密或已建立可用的 SSH 认证方式。 - -`go_native_prod_smoke.py` 通过 `20210` HTTP API 验证 GB32960、JT808、宇通 MQTT 的 RAW 查询及结构化 `parsed_json`,三类协议的位置和里程点查询及 `frame_id` RAW 回链,GB32960/JT808 的 `daily_mileage_km`、`daily_total_mileage_km` 统计公式,以及三类协议的 Redis realtime snapshot、online、protocol 查询。实时 snapshot/protocol 查询会校验 `updated_at_ms` 新鲜度;默认检查今天东八区数据时,还会要求三类 RAW 最新样本不超过 15 分钟,避免旧数据误判为接收正常;任一检查不达标时退出码为非 0。 - -## 部署命令 - -推荐使用仓库脚本发布完整 Go 原生 release。脚本会在本机/CI 构建 Linux amd64 四个二进制,打包上传到应用 ECS,切换 `/opt/lingniu-go-native/current`,逐个重启四个 systemd 服务,等待 gateway Kafka spool 清空,并在发布后运行聚合生产验收: - -```bash -python3 tools/go_native_deploy.py --date 2026-07-02 -``` - -脚本不会保存 SSH 或数据库密钥;认证仍使用当前操作环境可用的 SSH 凭据。需要跳过发布后验收时可显式追加 `--skip-acceptance`,但生产发布默认应保留验收。发布后 spool 默认最多等待 `900` 秒,可用 `--spool-drain-timeout` 和 `--spool-poll-interval` 调整。 - -脚本执行的发布动作等价于: - -```bash -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' ./cmd/gateway -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' ./cmd/history-writer -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' ./cmd/stat-writer -CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags='-s -w' ./cmd/realtime-api -scp release.tar.gz root@115.29.187.205:/tmp/ -ln -sfn /opt/lingniu-go-native/releases/ /opt/lingniu-go-native/current -systemctl daemon-reload -systemctl restart lingniu-go-gateway lingniu-go-history-writer lingniu-go-stat-writer lingniu-go-realtime-api -``` - -只更新查询 API 时,可以仅替换 `/opt/lingniu-go-native/current/realtime-api` 并重启: - -```bash -systemctl restart lingniu-go-realtime-api.service -``` - -## 运行检查 - -```bash -python3 tools/go_systemd_prod_smoke.py --host 115.29.187.205 --user root - -systemctl is-active lingniu-go-gateway lingniu-go-history-writer lingniu-go-stat-writer lingniu-go-realtime-api -ss -lntp | egrep ':(808|32960|20210)\b' -journalctl -u lingniu-go-gateway --since '5 minutes ago' --no-pager -journalctl -u lingniu-go-history-writer --since '5 minutes ago' --no-pager -journalctl -u lingniu-go-stat-writer --since '5 minutes ago' --no-pager -journalctl -u lingniu-go-realtime-api --since '5 minutes ago' --no-pager -``` - -2026-07-02 01:30 CST 已验证: - -- 四个 Go systemd 服务均为 `active` 且 `enabled`。 -- `/opt/lingniu-go-native/current` 指向有效 release,四个生产二进制均存在且可执行。 -- `gateway` 正在监听 `32960` 和 `808`。 -- `realtime-api` 正在监听 `20210`。 -- gateway Kafka spool 当前无待回放文件。 -- GB32960、JT808、YUTONG_MQTT 的东八区 RAW 查询均可命中生产数据,且最新 RAW 样本包含结构化 `parsed_json`。 -- GB32960、JT808、YUTONG_MQTT 的最新 RAW 样本均在 15 分钟内。 -- GB32960、JT808、YUTONG_MQTT 的 `vehicle_locations`、`vehicle_mileage_points` 查询均可命中生产数据,且样本包含 `frame_id` 回链 RAW。 -- `vehicle_daily_metric` 可查到 `daily_mileage_km` 和 `daily_total_mileage_km`,且公式满足 `daily_mileage_km = latest_total_mileage_km - first_total_mileage_km`、`daily_total_mileage_km = latest_total_mileage_km`。 -- Redis realtime 可查到 GB32960、JT808、YUTONG_MQTT 的在线状态和协议快照,且 snapshot/protocol 快照最近刷新。 diff --git a/docs/operations/gb32960-service-split-runbook.md b/docs/operations/gb32960-service-split-runbook.md deleted file mode 100644 index 05005a66..00000000 --- a/docs/operations/gb32960-service-split-runbook.md +++ /dev/null @@ -1,273 +0,0 @@ -# GB32960 Split Service Runbook - -This runbook covers the default production runtimes: GB32960 ingest, JT808 ingest, Yutong MQTT ingest, history, and analytics. Kafka is the durability boundary between protocol ingest services and downstream business services. - -## Service Roles - -| Service | Module | Role | Production ports | -| --- | --- | --- | --- | -| GB32960 ingest | `:gb32960-ingest-app` | Accept GB32960 TCP connections, decode/authenticate frames, produce raw and normalized records to Kafka, and send protocol ACKs after required Kafka production succeeds. | TCP `32960`, HTTP `20100` | -| JT808 ingest | `:jt808-ingest-app` | Accept JT/T 808 TCP connections on the production receive port, parse raw frames, archive frame bytes, and produce raw/event envelopes to Kafka. | TCP `808`, HTTP `20400` | -| Yutong MQTT ingest | `:yutong-mqtt-app` | Connect to Yutong MQTT, parse subscribed messages, archive payloads, and produce raw/event envelopes to Kafka. | HTTP `20500` | -| Vehicle history | `:vehicle-history-app` | Consume GB32960/JT808/Yutong MQTT raw/event Kafka records, store compact history rows plus TDengine `raw_frames` `parsedJson`/`metadataJson`/`rawUri`, and expose TDengine-backed history APIs. | HTTP `20200` | -| Vehicle analytics | `:vehicle-analytics-app` | Consume JT808 event Kafka records, calculate daily mileage from reported GPS total mileage, and write metrics to `vehicle_stat_metric`. | HTTP `20310` | - -## Kafka Contract - -| Topic | Producer | Consumers | Purpose | -| --- | --- | --- | --- | -| `vehicle.raw.gb32960.v1` | `gb32960-ingest-app` | `vehicle-history-app` | Raw frame records. History writes the parsed JSON, metadata JSON, and raw URI to TDengine `raw_frames`. | -| `vehicle.event.gb32960.v1` | `gb32960-ingest-app` | `vehicle-history-app` | Normalized vehicle events keyed by VIN when available. | -| `vehicle.dlq.gb32960.v1` | Kafka producer/consumer error paths | Operators/replay tooling | Dead-letter records for failed production or consumer processing. | -| `vehicle.raw.jt808.v1` | `jt808-ingest-app` | `vehicle-history-app` | JT808 raw frame records for TDengine `raw_frames` and raw-frame troubleshooting APIs. | -| `vehicle.event.jt808.v1` | `jt808-ingest-app` | `vehicle-history-app`, `vehicle-analytics-app` | JT808 parsed location/session/alarm events keyed by phone or mapped VIN; analytics uses location total mileage for daily mileage metrics. | -| `vehicle.dlq.jt808.v1` | Kafka producer/consumer error paths | Operators/replay tooling | Dead-letter records for failed JT808 production or consumer processing. | -| `vehicle.raw.mqtt-yutong.v1` | `yutong-mqtt-app` | `vehicle-history-app` | Yutong MQTT raw payload records for TDengine `raw_frames` and troubleshooting APIs. | -| `vehicle.event.mqtt-yutong.v1` | `yutong-mqtt-app` | `vehicle-history-app` | Yutong MQTT parsed telemetry events keyed by mapped VIN or device identity. | -| `vehicle.dlq.mqtt-yutong.v1` | Kafka producer/consumer error paths | Operators/replay tooling | Dead-letter records for failed Yutong MQTT production or consumer processing. | -| `vehicle.dlq.history.v1` | History app consumer error paths | Operators/replay tooling | Dead-letter records for failed history storage processing. | - -Default consumer groups: - -- History: `vehicle-history` -- Analytics statistics: `vehicle-stat` - -## ACK Semantics - -GB32960 success ACKs are sent only after the ingest service completes the required Kafka production boundary for the accepted frame. If required Kafka production fails, the ingest service must not claim a successful ACK for that frame. - -History and analytics failures do not block GB32960 ACKs. They are downstream Kafka consumer failures and should be handled through retry, DLQ inspection, offset replay, and service-specific recovery. - -Authorization failures are rejected before the Kafka durability boundary and can be ACKed/rejected immediately according to protocol handling. - -## Build Prerequisites - -- Java 25 and Maven available on PATH. -- Kafka CLI tools such as `kafka-topics` and optionally `kafka-console-consumer` available on the ECS/operator host. -- Session state requires Redis. GB32960/JT808 ingest services keep live Netty - channels in-process, but session indexes and TTL metadata use Redis only. -- Redis is configured through Portainer environment variables such as `REDIS_HOST` and `REDIS_DATABASE=50`; there is no memory session-store fallback. - -If your shell needs an explicit JDK: - -```bash -export JAVA_HOME=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home -export PATH="$JAVA_HOME/bin:/opt/homebrew/bin:$PATH" -``` - -## Build - -From the repository root: - -```bash -mvn -pl :gb32960-ingest-app,:jt808-ingest-app,:yutong-mqtt-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true -``` - -Expected result: `BUILD SUCCESS` and these runnable jars: - -- `modules/apps/gb32960-ingest-app/target/gb32960-ingest-app.jar` -- `modules/apps/jt808-ingest-app/target/jt808-ingest-app.jar` -- `modules/apps/yutong-mqtt-app/target/yutong-mqtt-app.jar` -- `modules/apps/vehicle-history-app/target/vehicle-history-app.jar` -- `modules/apps/vehicle-analytics-app/target/vehicle-analytics-app.jar` - -## Create Topics - -Run these only on the ECS/operator host after Kafka is reachable. The default bootstrap address is the ECS private Kafka address used by the production stack: - -```bash -KAFKA_BOOTSTRAP="${KAFKA_BOOTSTRAP:-172.17.111.56:9092}" -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.raw.gb32960.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.event.gb32960.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.dlq.gb32960.v1 --partitions 3 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.raw.jt808.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.event.jt808.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.dlq.jt808.v1 --partitions 3 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.raw.mqtt-yutong.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.event.mqtt-yutong.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.dlq.mqtt-yutong.v1 --partitions 3 --replication-factor 1 -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --create --if-not-exists --topic vehicle.dlq.history.v1 --partitions 3 --replication-factor 1 -``` - -Optional sanity check: - -```bash -kafka-topics --bootstrap-server "$KAFKA_BOOTSTRAP" --list | grep -E 'vehicle\.(raw|event|dlq)\.(gb32960|jt808|mqtt-yutong|history)\.v1' -``` - -## Production Runtime - -Production services run on ECS through Portainer/Docker. Do not start GB32960, JT808, Yutong MQTT, history, or analytics services on local developer machines; local machines are for code build, unit tests, and source inspection only. - -Use `deploy/portainer/docker-compose.yml` as the production runtime manifest. The default stack is: - -- `gb32960-ingest-app` -- `jt808-ingest-app` -- `yutong-mqtt-app` -- `vehicle-history-app` -- `vehicle-analytics-app` - -`vehicle-history-app` intentionally has no raw archive volume. The history hot path is Kafka to TDengine: raw envelopes are stored in TDengine `raw_frames` with `parsedJson`, `metadataJson`, and `rawUri`. Ingest services may still write original `.bin` files as cold backup, but history API queries must not depend on a shared local archive mount. - -Keep `TDENGINE_HISTORY_ENABLED=true` in the `vehicle-history-app` environment so history reads and writes use TDengine in production. - -`vehicle-analytics-app` is intentionally a metric runtime only. Latest-state APIs belong to `vehicle-state-service`, which stays outside the default reactor and should be built with `-Poptional-latest-state` and deployed as a separate consumer if that surface is needed. - -## Health Verification - -Run these from the ECS host or inside the matching container: - -```bash -curl -sS http://127.0.0.1:20100/actuator/health -curl -sS http://127.0.0.1:20100/actuator/health/liveness -curl -sS http://127.0.0.1:20100/actuator/health/readiness -curl -sS http://127.0.0.1:20400/actuator/health -curl -sS http://127.0.0.1:20400/actuator/health/liveness -curl -sS http://127.0.0.1:20400/actuator/health/readiness -curl -sS http://127.0.0.1:20500/actuator/health -curl -sS http://127.0.0.1:20500/actuator/health/liveness -curl -sS http://127.0.0.1:20500/actuator/health/readiness -curl -sS http://127.0.0.1:20200/actuator/health -curl -sS http://127.0.0.1:20200/actuator/health/liveness -curl -sS http://127.0.0.1:20200/actuator/health/readiness -curl -sS http://127.0.0.1:20310/actuator/health -curl -sS http://127.0.0.1:20310/actuator/health/liveness -curl -sS http://127.0.0.1:20310/actuator/health/readiness -``` - -Expected: each aggregate, liveness, and readiness endpoint returns `{"status":"UP"}` or an equivalent Spring Boot health JSON with top-level status `UP`. - -## End-to-End Verification - -Use a known-valid GB32960 fixture, for example one of the hex samples under `modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/`. - -Example send command: - -```bash -xxd -r -p modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_001.hex | nc 127.0.0.1 32960 -``` - -To capture and inspect the binary ACK, write the response to a file: - -```bash -rm -f /tmp/gb32960-ack.bin -xxd -r -p modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_001.hex \ - | nc -w 3 127.0.0.1 32960 > /tmp/gb32960-ack.bin -xxd -p /tmp/gb32960-ack.bin -``` - -Expected: the ACK file is non-empty and the decoded bytes begin with the GB32960 frame marker `2323`. Treat the ACK as verified only after checking the response bytes from your run. - -Verify only what was actually run in your environment: - -```bash -kafka-console-consumer --bootstrap-server "$KAFKA_BOOTSTRAP" --topic vehicle.raw.gb32960.v1 --from-beginning --max-messages 1 --timeout-ms 10000 -kafka-console-consumer --bootstrap-server "$KAFKA_BOOTSTRAP" --topic vehicle.event.gb32960.v1 --from-beginning --max-messages 1 --timeout-ms 10000 -kafka-console-consumer --bootstrap-server "$KAFKA_BOOTSTRAP" --topic vehicle.raw.mqtt-yutong.v1 --from-beginning --max-messages 1 --timeout-ms 10000 -kafka-console-consumer --bootstrap-server "$KAFKA_BOOTSTRAP" --topic vehicle.event.mqtt-yutong.v1 --from-beginning --max-messages 1 --timeout-ms 10000 -``` - -Use TDengine CLI or a JDBC client to verify history writes: - -```sql -USE vehicle_ts; -SELECT COUNT(*) FROM raw_frames WHERE protocol = 'GB32960'; -SELECT COUNT(*) FROM vehicle_locations WHERE protocol = 'GB32960'; -SELECT COUNT(*) FROM raw_frames WHERE protocol = 'JT808'; -SELECT COUNT(*) FROM jt808_locations WHERE protocol = 'JT808'; -SELECT COUNT(*) FROM raw_frames WHERE protocol = 'MQTT_YUTONG'; -SELECT COUNT(*) FROM vehicle_locations WHERE protocol = 'MQTT_YUTONG'; -``` - -Useful HTTP query checks after events are consumed. For `realtime_001.hex`, the fixture VIN is `LTEST000000000001`; replace ``, ``, and `` with values that cover the consumed record's event time: - -```bash -curl -sS 'http://127.0.0.1:20200/api/event-history/locations?protocol=GB32960&dateFrom=&dateTo=&vin=LTEST000000000001&limit=10' -curl -sS 'http://127.0.0.1:20200/api/event-history/raw-frames?protocol=GB32960&dateFrom=&dateTo=&vin=LTEST000000000001&limit=10' -curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/dictionary' -curl -sS 'http://127.0.0.1:20310/api/vehicle-stat/LTEST000000000001/daily?date=' -``` - -Expected E2E result when Kafka and all services are running: - -- A GB32960 client receives a binary success ACK only after required Kafka production succeeds, and the captured ACK bytes are inspected. -- `vehicle.raw.gb32960.v1` receives a raw frame envelope. -- `vehicle.event.gb32960.v1` receives one or more normalized records. -- TDengine `raw_frames` receives GB32960/JT808 RAW rows with parsed JSON and metadata. -- TDengine `raw_frames` receives Yutong MQTT RAW rows with parsed JSON and metadata when MQTT payloads are consumed. -- TDengine location tables receive compact GB32960/JT808/Yutong MQTT location rows when applicable telemetry is present. -- JT808 analytics writes daily mileage rows to MySQL `vehicle_stat_metric` after consuming applicable `vehicle.event.jt808.v1` records. - -Do not expect `vehicle-history-app` to create raw `.bin` archive files from Kafka raw records in the current implementation. `RawArchiveEventSink` can write archive files only when it receives `VehicleEvent.RawArchive.rawBytes()` inside the same JVM; the Kafka envelope carries only `RawArchiveRef` metadata. - -Do not mark any of these as verified unless the matching command was run and the output was inspected. - -## Runtime Location Policy - -Production services run on ECS through Portainer/Docker. - -- Do not create local service runners for GB32960, JT808, Yutong MQTT, history, or analytics. -- Do not run the service jars directly on developer machines for production verification. -- Keep local work limited to source edits, builds, and automated tests. - -For Portainer deployments, keep history without an archive volume: history consumes raw/event Kafka topics and writes TDengine history rows. - -## Rollback Guidance - -If the split deployment is unhealthy: - -1. Stop `gb32960-ingest-app` first so new GB32960 ACKs are not issued against an unhealthy Kafka boundary. -2. Keep Kafka topics intact for replay unless storage or privacy policy requires deletion. -3. Restart or roll back `vehicle-history-app` and `vehicle-analytics-app` independently; their failures do not require protocol ACK rollback because they consume from Kafka offsets. -4. If Kafka itself is unavailable, stop `gb32960-ingest-app` or route GB32960 traffic to a previously verified release that preserves the Kafka durability boundary. -5. After rollback, compare Kafka consumer group lag and DLQ contents before resuming the split services. - -Rollback commands depend on the deployment environment. For the default production stack, roll back the Portainer stack image version and let Docker restart the affected containers. - -## Task 8 Verification Notes - -Observed on 2026-06-23 in worktree `.worktrees/gb32960-service-split`: - -- Package build should use the current active-app command shown in the Build section. -- Repository-local Kafka setup inspection found no Kafka script, no Docker Compose file, and no compose YAML within the searched repository paths. -- `nc -z -w 2 127.0.0.1 9092` exited `1`, so no local Kafka broker was reachable at `127.0.0.1:9092`. -- `kafka-topics`, `kafka-topics.sh`, `docker`, and `docker-compose` were not found on PATH. -- Kafka topic creation, service startup, health checks, Kafka record checks, archive checks, TDengine checks, stat output checks, and ACK observation were not run because the local Kafka prerequisite was absent. - -## Latest Build Verification - -Observed on 2026-06-23 in worktree `.worktrees/gb32960-service-split`: - -- Targeted module tests: `mvn -pl :sink-kafka,:ingest-core,:protocol-gb32960,:event-history-service,:vehicle-state-service,:vehicle-stat-service test` ended with `BUILD SUCCESS`; Maven reported 55 protocol/sink/core tests, 30 event-history tests, 14 vehicle-state tests, and 19 vehicle-stat tests with 0 failures/errors/skips. -- Split app tests should cover the current active apps listed in the Service Roles section. -- Package verification should use the active-app package command from the Build section. -- Split app startup: not run in this verification pass because an ECS/Portainer runtime was outside that build-only check. -- Kafka raw records: not verified in that build-only check. -- Kafka event records: not verified in that build-only check. -- TDengine history rows: not verified; downstream services were not started without Kafka. -- Analytics output: not verified; downstream services were not started without Kafka. -- ACK behavior: not verified against a live broker in this pass. Unit tests cover the GB32960 ACK boundary and ordering, but an operator should still run the ACK capture command above on ECS or staging. - -## Remote Kafka Live Verification - -Observed on 2026-06-23 with Kafka `114.55.58.251:9092`: - -- `gb32960-ingest-app` handled TCP `32960` and HTTP `20100`. -- Ingest app health check `curl -sS http://127.0.0.1:20100/actuator/health` returned `{"status":"UP"}`. -- Kafka AdminClient confirmed topics `vehicle.raw.gb32960.v1` and `vehicle.event.gb32960.v1` existed, and created `vehicle.dlq.gb32960.v1`. -- The app accepted the live Hyundai platform connection from `8.134.95.166`, authenticated platform login, and received real GB32960 realtime/report/login/logout frames. -- Probe consumer with a temporary group read records from `vehicle.event.gb32960.v1`, including VIN keys such as `LB9A32A2XR0LS1575` and `LNXNEGRR3SR319485`. -- Probe consumer with a temporary group read records from `vehicle.raw.gb32960.v1`, including VIN keys such as `LNXNEGRR0SR319444`, `LNXNEGRR2SR318201`, and `LB9A32A23R0LS1532`. -- Runtime logs showed successful GB32960 ACK flushes for platform login, vehicle login, and vehicle logout after the Kafka-backed split ingest service was running. - -This verifies the live path `GB32960 TCP 32960 -> gb32960-ingest-app -> Kafka raw/event topics` against the remote Kafka broker. Downstream `vehicle-history-app` and `vehicle-analytics-app` were not started in this pass. - -## 2026-06-29 Superseded Local Verification Record - -Observed on 2026-06-29 in worktree `.worktrees/vehicle-ingest-redesign-phase1`. This record is retained only as historical evidence of parser/storage behavior; do not repeat it as a deployment procedure because production services now run on ECS through Portainer/Docker. - -- External JT808 traffic on TCP `808` was parsed and written through Kafka into TDengine. Direct TDengine checks showed more than `6000` JT808 `raw_frames` and more than `1900` JT808 `vehicle_locations`; current production history does not maintain a `telemetry_fields` table. -- A GB32960 synthetic realtime frame sent to TCP `32960` received a binary ACK beginning with `2323`. The same VIN `LTEST202606290001` was queryable from TDengine-backed history APIs with speed, mileage, longitude, and latitude fields. -- History raw and event Kafka consumption used separate bindings so raw-frame writes remained current while location/field event ingestion continued. - -The verified behavior was `GB32960/JT808 TCP -> protocol app -> Kafka raw/event topics -> vehicle-history-app -> TDengine raw_frames/location tables -> Swagger/API query`; the deployment method from that historical run is superseded. diff --git a/docs/operations/go-vehicle-gateway-verification-result-2026-07-01.md b/docs/operations/go-vehicle-gateway-verification-result-2026-07-01.md deleted file mode 100644 index 1c766bb8..00000000 --- a/docs/operations/go-vehicle-gateway-verification-result-2026-07-01.md +++ /dev/null @@ -1,888 +0,0 @@ -# Go Vehicle Gateway 旁路验证记录 - -验证时间:2026-07-01 22:00-22:10 CST - -## 部署版本 - -- Git commit:`60ca42e` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-60ca42e-20260701220622` -- 部署方式:ECS `115.29.187.205` 上 Docker Compose 旁路部署 -- Compose 目录:`/opt/lingniu-go` - -## 旁路端口和 topic - -为了不影响现有 Java 生产服务,本次没有占用生产端口和生产 topic。 - -| 项 | 值 | -|---|---| -| GB32960 旁路 TCP | `115.29.187.205:23296 -> container :32960` | -| JT808 旁路 TCP | `115.29.187.205:18080 -> container :808` | -| Realtime API | `115.29.187.205:20210` | -| GB32960 RAW topic | `vehicle.raw.go.gb32960.v1` | -| JT808 RAW topic | `vehicle.raw.go.jt808.v1` | -| Yutong MQTT RAW topic | `vehicle.raw.go.yutong-mqtt.v1` | -| Unified topic | `vehicle.event.go.unified.v1` | - -## 容器状态 - -ECS 上四个 Go 容器均已启动: - -```text -go-vehicle-gateway Up -go-history-writer Up -go-stat-writer Up -go-realtime-api Up -``` - -## Kafka 验证 - -向 `18080` 注入 JT808 样例帧后,`vehicle.raw.go.jt808.v1` 和 -`vehicle.event.go.unified.v1` 均消费到统一 JSON envelope。 - -样例解析结果包含: - -- `protocol=JT808` -- `message_id=0x0200` -- `phone=013307795425` -- `longitude=121.069881` -- `latitude=30.590151` -- `speed_kmh=23` -- `total_mileage_km=10241.2` -- 表 27 附加项 `0x01` 原始值 `0001900C` - -## TDengine 验证 - -目标库:`lingniu_vehicle_ts` - -```text -raw_frames count = 3 -vehicle_locations count = 2 -vehicle_mileage_points count = 2 -``` - -最新 RAW 标识: - -```text -GB32960 | LNBSCB3D4R1234567 | LNBSCB3D4R1234567 | -JT808 | JT808:013307795425 | | 013307795425 -JT808 | JT808:013307795425 | | 013307795425 -``` - -说明:JT808 样例 phone 当前没有在 `vehicle_identity_binding` 中解析到 VIN,因此 -TDengine tag 使用 `vehicle_key=JT808:013307795425`。 - -## Redis 验证 - -向 `23296` 注入带 VIN 的 GB32960 合成帧后,Realtime API 可查询: - -```text -GET /api/realtime/vehicles/LNBSCB3D4R1234567 -``` - -返回字段包含: - -- `vin=LNBSCB3D4R1234567` -- `protocols=["GB32960"]` -- `online=true` -- `speed_kmh=30` -- `total_mileage_km=10000` -- `soc_percent=85` -- `longitude=121` -- `latitude=30.56` - -## MySQL 统计验证 - -向 `23296` 注入带 VIN 的 GB32960 合成帧后,`vehicle_daily_metric` 写入: - -```text -LNBSCB3D4R1234567 | 2020-07-01 | GB32960 | daily_mileage_km | 0.000 | 10000.000 | 10000.000 | 1 -LNBSCB3D4R1234567 | 2020-07-01 | GB32960 | daily_total_mileage_km | 10000.000 | 10000.000 | 10000.000 | 1 -``` - -说明:本次 GB32960 合成帧的设备时间为测试值,因此统计日期为 `2020-07-01`。 - -## 已修复问题 - -- TDengine WebSocket 普通 `INSERT` 使用参数占位符时,字符串未按预期加引号,导致语法错误。已在 `60ca42e` 中改为显式 SQL literal 转义。 -- MySQL Go DSN 中 `charset=utf8mb4%2Cutf8` 会被 driver 传给 MySQL,导致语法错误;ECS 旁路 env 已改为 `charset=utf8mb4`。 -- TDengine ECS 当前可用 root 密码与早先记录不一致,本次旁路 env 已按实际可认证值修正。 - -## 未完成 - -- 未切换真实生产端口 `32960/808`。 -- 未将真实外部 32960/808 流量转发到 Go 旁路端口。 -- 未开启宇通 MQTT 正式订阅。 -- JT808 样例未解析到 VIN,需维护 `vehicle_identity_binding` 后再验证 808 每日统计。 - -## 2026-07-01 22:22 真实归档帧复验 - -部署版本已更新: - -- Git commit:`5f4f4fd` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-5f4f4fd-20260701222128` - -本轮补充验证内容: - -- JT808 支持 2019 版 versioned header,真实帧 `0x0200` 可解析出 `phone=14894135060`、`longitude=117.178187`、`latitude=30.570155`、`speed_kmh=33.4`、`total_mileage_km=7868.9`。 -- GB32960 真实燃料电池帧 `VIN=LB9A32A21R0LS1707` 可解析 `0x01` 整车、`0x02` 驱动电机、`0x03` 燃料电池、`0x04` 发动机、`0x05` 位置、`0x06` 极值、`0x07` 报警、`0x08` 电压、`0x09` 温度;后续 `0x30` 广东燃料电池扩展暂以 unknown raw 保留。 -- GB32960 真实帧字段包含 `fuel_cell_hydrogen_consumption_kg_per_100km=1.8`、`longitude=120.800326`、`latitude=31.634907`、`total_mileage_km=53490.9`。 -- Realtime API `GET /api/realtime/vehicles/LB9A32A21R0LS1707` 已返回上述经纬度和氢耗字段。 -- TDengine `vehicle_locations` 和 `vehicle_mileage_points` 已写入真实 GB32960 位置和里程点: - -```text -2020-07-01 08:09:22 | GB32960 | LB9A32A21R0LS1707 | lon=120.800326 | lat=31.634907 | mileage=53490.9 -``` - -后续仍需补齐: - -- JT808 真实 phone `14894135060` 仍未解析到 VIN,需维护身份绑定后再验证 VIN 维度实时和统计。 - -## 2026-07-01 22:28 广东燃料电池扩展复验 - -部署版本已更新: - -- Git commit:`a15de91` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-a15de91-20260701222743` - -本轮补充验证内容: - -- GB32960 真实燃料电池帧 `VIN=LB9A32A21R0LS1707` 的 vendor 尾部已从 `unknown_unit` 补为结构化块: - `0x30 gd_fc_stack`、`0x31 gd_fc_auxiliary`、`0x32 gd_fc_dcdc`、`0x33 gd_fc_air_conditioner`、 - `0x34 gd_fc_vehicle_info`、`0x80 gd_fc_demo_extension`、`0x83 gd_fc_vendor_tlv`。 -- Kafka `vehicle.raw.go.gb32960.v1` 最新消息包含上述 block,并输出关键扁平字段: - `gd_fc_stack_hydrogen_inlet_pressure_kpa=97`、`gd_fc_stack_cell_count=108`、 - `gd_fc_vehicle_hydrogen_mass_kg=4.3`、`gd_fc_demo_stack_temp_c=65`。 -- Realtime API `GET /api/realtime/vehicles/LB9A32A21R0LS1707` 已返回 vendor 扁平字段: - -```text -gd_fc_dcdc_controller_temp_c=47 -gd_fc_demo_stack_temp_c=65 -gd_fc_stack_cell_count=108 -gd_fc_stack_frame_cell_count=108 -gd_fc_stack_hydrogen_inlet_pressure_kpa=97 -gd_fc_stack_water_outlet_temp_c=65 -gd_fc_vehicle_hydrogen_mass_kg=4.3 -``` - -- TDengine `raw_frames` 中 GB32960 行数增加到 `4`,最新 RAW 标识: - -```text -2026-07-01 14:28:24.869 | 172.20.0.1:47082 | GB32960 | LB9A32A21R0LS1707 | OK -``` - -后续仍需补齐: - -- JT808 真实 phone `14894135060` 到 VIN 的绑定数据维护与统计复验。 -- Go 旁路尚未切换到生产 `32960/808` 端口;仍运行在 `23296/18080`。 - -## 2026-07-01 22:43 JT808 前导零 phone 身份解析复验 - -部署版本已更新: - -- Git commit:`6b55694` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-6b55694-20260701224300` - -本轮修复内容: - -- JT808 2011 老版包头的终端手机号按 BCD[6] 解析会保留前导 `0`,例如包头为 `013079963379`。 -- `vehicle_identity_binding.phone` 中实际维护的是去前导零后的手机号 `13079963379`。 -- 身份解析候选键已调整为先查原始 phone,再查去前导零 phone,之后再按 `device_id`、`plate` 降级。 - -真实帧复验样本: - -- 归档帧:`/opt/lingniu/vehicle-ingest/gb32960/archive/2026/07/01/JT808/unknown/1782903942845000.bin` -- 消息:`0x0200` 位置信息汇报 -- 包头 phone:`013079963379` -- 绑定表 phone:`13079963379` -- 解析 VIN:`LKLG7C4E3NA774736` - -Kafka `vehicle.raw.go.jt808.v1` 最新消息确认: - -```text -vin=LKLG7C4E3NA774736 -phone=013079963379 -parsed.header.phone_trim_zero=13079963379 -parsed.identity.resolved=true -parsed.identity.source=phone -parsed.identity.value=13079963379 -``` - -Realtime API 确认: - -```text -GET /api/realtime/vehicles/LKLG7C4E3NA774736 - -longitude=119.557997 -latitude=29.049092 -speed_kmh=0 -total_mileage_km=0 -device_time=2026-07-01T19:05:40+08:00 -``` - -TDengine 确认: - -```text -raw_frames: -2026-07-01 14:49:10.913 | JT808 | LKLG7C4E3NA774736 | phone=013079963379 | message_id=512 - -vehicle_locations: -2026-07-01 11:05:40.000 | JT808 | LKLG7C4E3NA774736 | lon=119.557997 | lat=29.049092 | speed=0 | mileage=0 - -vehicle_mileage_points: -2026-07-01 11:05:40.000 | JT808 | LKLG7C4E3NA774736 | mileage=0 -``` - -MySQL `vehicle_daily_metric` 确认: - -```text -LKLG7C4E3NA774736 | JT808 | 2026-07-01 | daily_mileage_km | 0.000 | sample_count=1 | TOTAL_MILEAGE_DIFF -LKLG7C4E3NA774736 | JT808 | 2026-07-01 | daily_total_mileage_km | 0.000 | sample_count=1 | TOTAL_MILEAGE_DIFF -``` - -说明:该真实位置帧携带的表 27 附加项 `0x01` 总里程值为 `0`,因此本次只验证身份解析、链路落地和差值统计写入;要验证非零日里程,需要回放或等待同一 VIN 的非零总里程连续位置点。 - -## 2026-07-01 22:55 Kafka 发布重试部署复验 - -部署版本已更新: - -- Git commit:`7e7066d` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-7e7066d-20260701225503` - -本轮修复内容: - -- gateway 的 Kafka 发布增加同步重试封装。 -- 默认配置: - - `KAFKA_PUBLISH_ATTEMPTS=3` - - `KAFKA_PUBLISH_BACKOFF_MS=100` - - `KAFKA_PUBLISH_TIMEOUT_MS=3000` -- RAW 仍先于 unified event 写入;RAW 写失败不会继续写 unified。 -- 单元测试覆盖首次失败后成功、重试耗尽返回错误、上下文取消时停止重试。 - -部署后容器状态: - -```text -go-vehicle-gateway | go-7e7066d-20260701225503 | Up -go-history-writer | go-7e7066d-20260701225503 | Up -go-stat-writer | go-7e7066d-20260701225503 | Up -go-realtime-api | go-7e7066d-20260701225503 | Up -``` - -真实 808 帧回放复验: - -```text -source_endpoint=172.20.0.1:51966 -protocol=JT808 -message_id=0x0200 -phone=013079963379 -vin=LKLG7C4E3NA774736 -parsed.identity.value=13079963379 -``` - -Kafka `vehicle.raw.go.jt808.v1` 确认新消息写入,partition 6 offset 从 `1` 增加到 `2`。最新消息包含: - -```text -received_at_ms=1782917751903 -source_endpoint=172.20.0.1:51966 -vin=LKLG7C4E3NA774736 -fields.longitude=119.557997 -fields.latitude=29.049092 -fields.total_mileage_km=0 -``` - -TDengine `raw_frames` 确认新 RAW 落地: - -```text -2026-07-01 14:55:51.903 | JT808 | LKLG7C4E3NA774736 | phone=013079963379 | source=172.20.0.1:51966 -``` - -Realtime API 确认 Redis 已刷新: - -```text -GET /api/realtime/vehicles/LKLG7C4E3NA774736 - -received_at_ms=1782917751903 -source_endpoint=172.20.0.1:51966 -longitude=119.557997 -latitude=29.049092 -total_mileage_km=0 -``` - -说明:当前重试层只能覆盖 Kafka 短暂抖动;如果 Kafka 长时间不可用,仍需后续增加本地磁盘 spool/WAL 和恢复补发。 - -## 2026-07-01 23:02 切换 Go 到生产 32960/808 端口 - -本轮操作: - -- 停止 ECS 上原有 Java Docker 容器: - - `gb32960-ingest-app` - - `jt808-ingest-app` - - `yutong-mqtt-app` - - `vehicle-history-app` - - `vehicle-analytics-app` - - `vehicle-state-app` - - `telemetry-field-parser-app` -- 保留 Go sidecar 和 Portainer agent。 -- 修改 `/opt/lingniu-go/.env`: - - `GO_GB32960_TCP_PORT=32960` - - `GO_JT808_TCP_PORT=808` -- Go gateway 使用镜像: - - `crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-243de5c-20260701230028` -- 开启 Kafka 本地 spool: - - host:`/opt/lingniu-go/spool/gateway` - - container:`/data/spool/gateway` - -端口验证: - -```text -0.0.0.0:32960 -> go-vehicle-gateway:32960 -0.0.0.0:808 -> go-vehicle-gateway:808 -18080/23296 -> 已释放 -``` - -启动日志确认: - -```text -kafka durable spool enabled dir=/data/spool/gateway replay_interval_ms=1000 -tcp listener started protocol=GB32960 addr=[::]:32960 -tcp listener started protocol=JT808 addr=[::]:808 -``` - -真实连接确认: - -```text -GB32960 external remote: 8.134.95.166:50084 -JT808 external remote: 115.231.168.135:13801 -``` - -生产 `808` 回放复验: - -```text -source_endpoint=172.20.0.1:54680 -vin=LKLG7C4E3NA774736 -phone=013079963379 -longitude=119.557997 -latitude=29.049092 -total_mileage_km=0 -``` - -TDengine 最新 RAW 复验显示生产 808 已有真实外部数据持续落地: - -```text -2026-07-01 15:03:02.960 | JT808 | LKLG7C4E1NA774802 | 013079963291 | 222.66.200.68:28394 -2026-07-01 15:03:02.114 | JT808 | | 14894135583 | 122.152.221.156:33871 -2026-07-01 15:03:01.352 | JT808 | | 013307795519 | 115.231.168.135:13801 -2026-07-01 15:03:01.330 | JT808 | LKLG7C4E3NA774736 | 013079963379 | 222.66.200.68:28393 -``` - -Kafka spool 目录确认: - -```text -/opt/lingniu-go/spool/gateway file count = 0 -``` - -说明:生产 808 中仍有部分 phone 未解析出 VIN,后续需要继续维护 `vehicle_identity_binding` 的 phone/device_id/plate 到 VIN 映射,或者增强 808 注册/鉴权帧中的 identity 回写。 - -## 2026-07-01 23:09 808 里程统计防 0 污染复验 - -部署版本已更新: - -- Git commit:`d347525` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-d347525-20260701230845` - -本轮修复内容: - -- `stat-writer` 不再把 `total_mileage_km <= 0` 的采样写入 MySQL 每日指标。 -- MySQL upsert 对历史 `first_total_mileage_km=0` / `latest_total_mileage_km=0` 做纠偏;后续首次有效总里程会替换掉历史 0。 -- `realtime-api` 合并实时快照时,非正 `total_mileage_km` 不再覆盖已有正值;速度、位置、状态等其他字段仍按事件时间更新。 - -测试验证: - -```text -go test ./... -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/gateway -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/history-writer -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/stat-writer -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/realtime-api -``` - -生产回放验证: - -- 第一条 808 帧:`device_time=2026-07-01T23:09:35+08:00`,`total_mileage_km=12345.6` -- 第二条 808 帧:`device_time=2026-07-01T23:09:36+08:00`,`total_mileage_km=0` -- 两条帧使用同一 VIN:`LKLG7C4E3NA774736` - -Redis merged 快照确认:第二条 0 里程帧没有覆盖已有有效总里程。 - -```text -event_time_ms=1782918576000 -source_endpoint=172.20.0.1:40946 -speed_kmh=0 -longitude=119.557997 -latitude=29.049092 -total_mileage_km=12345.6 -field_times_ms.total_mileage_km=1782918575000 -``` - -MySQL `vehicle_daily_metric` 确认:0 里程采样未进入统计,sample_count 只因有效总里程采样增加一次。 - -```text -LKLG7C4E3NA774736 | JT808 | 2026-07-01 | daily_mileage_km | 0.000 | sample_count=13 | first=12345.600 | latest=12345.600 -LKLG7C4E3NA774736 | JT808 | 2026-07-01 | daily_total_mileage_km | 12345.600 | sample_count=13 | first=12345.600 | latest=12345.600 -``` - -说明:生产 TDengine 中已经存在大量 808 非零 `total_mileage_km` 采样,但许多 phone 尚未映射到 VIN,因此 MySQL 按 VIN 的统计只会覆盖已解析 VIN 的车辆。下一步应继续补齐 `vehicle_identity_binding`,让更多 808 车辆进入统计。 - -## 2026-07-01 23:23 808 注册身份自动维护复验 - -部署版本已更新: - -- Git commit:`7b44f97` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-7b44f97-20260701232200` - -本轮修复内容: - -- 808 `0x0100` 注册帧解析 `province`、`city`、`manufacturer`、`device_type`、`device_id`、`plate_color`、`plate`。 -- 808 `0x0102` 鉴权帧解析 `auth_token`。 -- Go gateway 在 identity resolve 后写入 `jt808_registration`,普通上行也会刷新 `latest_seen_at`。 -- 注册帧通过车牌解析到 VIN 后,会尝试把 phone/device/plate 反写到 `vehicle_identity_binding`。 -- 注册帧车牌支持 GBK 解码,避免 `Incorrect string value` 写入错误。 - -验证命令: - -```text -go test ./... -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/gateway -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/history-writer -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/stat-writer -GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/realtime-api -``` - -ECS 运行状态: - -```text -go-vehicle-gateway | go-7b44f97-20260701232200 | 0.0.0.0:808->808, 0.0.0.0:32960->32960 -go-history-writer | go-7b44f97-20260701232200 | Up -go-stat-writer | go-7b44f97-20260701232200 | Up -go-realtime-api | go-7b44f97-20260701232200 | 0.0.0.0:20210->20210 -``` - -生产日志确认: - -- 新版本启动后真实 808、32960 连接持续进入。 -- GBK 车牌注册帧不再出现 `Incorrect string value`。 - -MySQL `jt808_registration` 确认: - -```text -registration_rows=214 -distinct_phone=214 -vin_rows=74 -plate_rows=74 -device_rows=72 - -013079963379 | device_id=9963379 | plate=沪A06788F | vin=LKLG7C4E3NA774736 | latest_registered_at=2026-07-01 23:23:01 -013079963321 | device_id=9963321 | plate=沪A59613F | vin=LKLG7C4EXNA774796 | latest_registered_at=2026-07-01 23:23:04 -``` - -说明:`jt808_registration.phone` 保存 808 包头原始 phone;`vehicle_identity_binding.phone` 中已有部分去前导零 phone。Go resolver 会同时查询原始 phone 和去前导零 phone。 - -## 2026-07-01 23:28 生产链路与指标清理复验 - -本轮复验目标: - -- 确认 Go gateway 的 Kafka durable spool 是重启窗口残留,而不是持续发布失败。 -- 确认 stat-writer / history-writer / realtime-api 的 Kafka consumer group 无 lag。 -- 确认真实 808 车辆 registration、TDengine RAW/里程点、Redis 实时查询均在持续更新。 -- 清理之前生产合成回放产生的 JT808 当日指标测试数据。 - -Kafka topic / consumer group: - -```text -vehicle.raw.go.jt808.v1 有持续 offset -vehicle.raw.go.gb32960.v1 有持续 offset -vehicle.event.go.unified.v1 有持续 offset - -go-stat-writer LAG=0 -go-history-writer LAG=0 -go-realtime-api LAG=0 -``` - -TDengine: - -```text -raw_frames: -JT808 2922 -GB32960 27 - -vehicle_mileage_points: -JT808 2050 -GB32960 2 -``` - -Redis 实时查询样例: - -```text -GET /api/realtime/vehicles/LKLG7C4E3NA774736 -vin=LKLG7C4E3NA774736 -source_endpoint=222.66.200.68:29646 -plate=沪A06788F -longitude=119.556866 -latitude=29.047911 -total_mileage_km=12345.6 -``` - -说明:该 Redis merged 快照保留了此前有效 `total_mileage_km=12345.6`,后续真实 808 位置帧上报的 `total_mileage_km=0` 不会覆盖该字段。TDengine 中该 VIN 后续真实里程点的 `total_mileage_km` 均为 0,因此 stat-writer 按“只统计正总里程”的规则不再写 JT808 每日指标。 - -MySQL 指标清理: - -```text -删除前: -LKLG7C4E2NA774775 | JT808 | 2026-07-01 | daily_mileage_km | first=0 | latest=0 | sample_count=7 -LKLG7C4E2NA774775 | JT808 | 2026-07-01 | daily_total_mileage_km | first=0 | latest=0 | sample_count=7 -LKLG7C4E3NA774736 | JT808 | 2026-07-01 | daily_mileage_km | first=12345.6 | latest=12345.6 | sample_count=13 -LKLG7C4E3NA774736 | JT808 | 2026-07-01 | daily_total_mileage_km | first=12345.6 | latest=12345.6 | sample_count=13 - -删除后等待真实流量窗口: -JT808 2026-07-01 metric rows = 0 -``` - -清理原因: - -- `LKLG7C4E3NA774736` 的 12345.6 来自此前生产连通性合成回放,不是真实平台上报。 -- `LKLG7C4E2NA774775` 的 0 指标来自旧逻辑污染,当前 stat-writer 已跳过非正总里程。 - -Kafka spool: - -```text -23:24 spool_count=904 -23:25 spool_count=862 -23:28 spool_count=722 -new_spool_1min=0 -``` - -说明:spool 正在回放下降,且最近 1 分钟没有新增文件;生产 gateway 日志未见 `Incorrect string value`、Kafka publish error 或 replay error。 - -## 2026-07-01 23:34 每日指标查询 API 复验 - -部署版本已更新: - -- Git commit:`56f4811` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-56f4811-20260701233311` - -新增接口: - -```text -GET /api/stats/daily-metrics -``` - -查询参数: - -```text -vin 可选 -protocol 可选,GB32960 / JT808 / YUTONG_MQTT -metricKey 可选,daily_mileage_km / daily_total_mileage_km -dateFrom 可选,YYYY-MM-DD -dateTo 可选,YYYY-MM-DD -limit 可选,默认 50,最大 1000 -offset 可选,默认 0 -``` - -生产可访问样例: - -```text -http://115.29.187.205:20210/api/stats/daily-metrics?vin=LB9A32A21R0LS1707&protocol=GB32960&dateFrom=2020-07-01&dateTo=2020-07-01&limit=10 -``` - -返回确认: - -```json -{ - "items": [ - { - "vin": "LB9A32A21R0LS1707", - "stat_date": "2020-07-01", - "protocol": "GB32960", - "metric_key": "daily_mileage_km", - "metric_value": 0, - "metric_unit": "km", - "first_total_mileage_km": 53490.9, - "latest_total_mileage_km": 53490.9, - "sample_count": 3, - "calculation_method": "TOTAL_MILEAGE_DIFF", - "created_at": "2026-07-01 22:14:13", - "updated_at": "2026-07-01 22:28:25" - }, - { - "vin": "LB9A32A21R0LS1707", - "stat_date": "2020-07-01", - "protocol": "GB32960", - "metric_key": "daily_total_mileage_km", - "metric_value": 53490.9, - "metric_unit": "km", - "first_total_mileage_km": 53490.9, - "latest_total_mileage_km": 53490.9, - "sample_count": 3, - "calculation_method": "TOTAL_MILEAGE_DIFF", - "created_at": "2026-07-01 22:14:13", - "updated_at": "2026-07-01 22:28:25" - } - ], - "limit": 10, - "offset": 0, - "total": 2 -} -``` - -JT808 真实生产查询: - -```text -http://115.29.187.205:20210/api/stats/daily-metrics?protocol=JT808&dateFrom=2026-07-01&dateTo=2026-07-01&limit=10 -``` - -返回 `total=0`。原因是此前合成回放产生的测试指标已清理,当前真实 808 已绑定 VIN 的车辆仍上报 `total_mileage_km=0`,stat-writer 按规则不写非正总里程指标。 - -实时接口回归: - -```text -http://115.29.187.205:20210/api/realtime/vehicles/LKLG7C4E3NA774736/online -``` - -返回 `online=true`,说明新增 stats API 没有影响 Redis 实时查询。 - -发布后 spool 观察: - -```text -发布后立即:spool_count=1101,new_spool_1min=102 -等待 60 秒:spool_count=1033,new_spool_1min=0 -``` - -说明:本次发布重启窗口产生的 spool 正在回放下降,且最近 1 分钟没有新增;gateway/realtime-api 最近日志未见 error、failed、Kafka publish error。 - -## 2026-07-01 23:50 生产端口接管与 RAW 查询 API 复验 - -部署版本: - -- Git commit:`5937132` -- 镜像:`crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com/oneos/vehicle-gateway-go:go-5937132-20260701234220` - -生产端口状态: - -```text -go-vehicle-gateway 0.0.0.0:808->808/tcp -go-vehicle-gateway 0.0.0.0:32960->32960/tcp -``` - -旧 Java 容器状态: - -```text -gb32960-ingest-app restart=no status=exited -jt808-ingest-app restart=no status=exited -yutong-mqtt-app restart=no status=exited -vehicle-history-app restart=no status=exited -vehicle-analytics-app restart=no status=exited -vehicle-state-app restart=no status=exited -telemetry-field-parser-app restart=no status=exited -``` - -生产连接确认: - -- JT808 端口 `808` 已收到外部连接:`222.66.200.68`、`115.231.168.135`。 -- GB32960 端口 `32960` 已收到外部连接:`8.134.95.166`、`117.160.0.65`。 -- ECS 当前没有 `8089` 监听。 - -RAW 查询 API: - -```text -GET /api/history/raw-frames -``` - -生产可访问样例: - -```text -http://115.29.187.205:20210/api/history/raw-frames?protocol=JT808&vin=LKLG7C4E3NA774736&messageId=0x0200&limit=1 -http://115.29.187.205:20210/api/history/raw-frames?protocol=GB32960&vin=LB9A32A21R0LS1707&limit=1 -``` - -接口复验结果: - -```text -JT808_RAW: -total=1 -protocol=JT808 -vin=LKLG7C4E3NA774736 -phone=013079963379 -message_id=512 -message_id_hex=0x0200 -parse_status=OK -raw_hex_len=94 -parsed_json_len=651 -fields_json_len=186 - -GB32960_RAW: -total=1 -protocol=GB32960 -vin=LB9A32A21R0LS1707 -message_id=2 -message_id_hex=0x0002 -parse_status=OK -raw_hex_len=1548 -parsed_json_len=3698 -fields_json_len=563 -``` - -同轮回归验证: - -```text -GET /api/realtime/vehicles/LKLG7C4E3NA774736/online -online=true -protocols=["JT808"] - -GET /api/stats/daily-metrics?vin=LB9A32A21R0LS1707&protocol=GB32960&dateFrom=2020-07-01&dateTo=2026-07-01&limit=1 -total=1 -protocol=GB32960 -metric_key=daily_mileage_km -calculation_method=TOTAL_MILEAGE_DIFF -``` - -说明: - -- RAW 查询已返回 `raw_hex`、`parsed_json`、`fields_json`,可以用于查看完整解析结果。 -- 日统计和实时查询在生产端口接管后仍可用。 - -## 2026-07-02 00:00 原生 systemd 部署与宇通 MQTT 复验 - -根据本次 goal 的新要求,Go 接入链路后续不再使用 Docker 部署。本轮已中断 Docker 镜像构建路径,改为 ECS 原生 Linux 二进制 + systemd。 - -部署版本: - -- Git commit:`594ab2d` -- 部署目录:`/opt/lingniu-go-native` -- 当前 release:`/opt/lingniu-go-native/releases/594ab2d` -- 当前指针:`/opt/lingniu-go-native/current -> /opt/lingniu-go-native/releases/594ab2d` - -systemd 服务: - -```text -lingniu-go-gateway.service active -lingniu-go-history-writer.service active -lingniu-go-stat-writer.service active -lingniu-go-realtime-api.service active -``` - -端口确认: - -```text -[::]:32960 gateway -[::]:808 gateway -[::]:20210 realtime-api -``` - -Go Docker 容器已停止: - -```text -go-vehicle-gateway Exited -go-history-writer Exited -go-stat-writer Exited -go-realtime-api Exited -``` - -宇通 MQTT: - -```text -yutong mqtt client started -mqtt subscribed broker=ssl://cpxlm.axxc.cn:38883 topic=/ytforward/shln/+ qos=1 -``` - -说明:MQTT broker 存在短连接 `EOF` 后自动重连现象,但订阅后已收到真实数据并落 RAW。 - -RAW 查询复验: - -```text -GET /api/history/raw-frames?protocol=YUTONG_MQTT&limit=3 - -total=3 -protocol=YUTONG_MQTT -vin=LMRKH9AC1R1004131 -vehicle_key=LMRKH9AC1R1004131 -parse_status=OK -parsed_json_len=1104 -fields_json_len=116 -``` - -同轮回归: - -```text -GET /api/history/raw-frames?protocol=JT808&vin=LKLG7C4E3NA774736&messageId=0x0200&limit=1 -total=1 -parse_status=OK - -GET /api/history/raw-frames?protocol=GB32960&vin=LB9A32A21R0LS1707&limit=1 -total=1 -parse_status=OK - -GET /api/realtime/vehicles/LKLG7C4E3NA774736/online -online=true -protocols=["JT808"] -``` - -本轮落地文件: - -- `deploy/systemd/README.md` -- `deploy/systemd/lingniu-go-gateway.service` -- `deploy/systemd/lingniu-go-history-writer.service` -- `deploy/systemd/lingniu-go-stat-writer.service` -- `deploy/systemd/lingniu-go-realtime-api.service` - -## 2026-07-02 00:04 宇通 MQTT EOF 诊断 - -现象: - -Go 原生部署后,`lingniu-go-gateway` 日志中出现多次: - -```text -mqtt connection lost error=EOF -mqtt connection lost error=write: broken pipe -mqtt subscribed topic=/ytforward/shln/+ qos=1 -``` - -10 分钟窗口统计: - -```text -mqtt subscribed = 33 -mqtt connection lost = 32 -``` - -对照旧 Java `yutong-mqtt-app` 历史日志,停止前也存在同类行为: - -```text -mqtt endpoint [yutong] received topic=/ytforward/shln/1 bytes=574 -mqtt endpoint [yutong] connection lost -Caused by: java.io.EOFException -mqtt endpoint [yutong] re-subscribed topic=/ytforward/shln/+ qos=1 -mqtt endpoint [yutong] received topic=/ytforward/shln/3 bytes=586 -``` - -判断: - -- EOF/短连接不是 Go TLS 支持或 systemd 原生部署新引入的问题。 -- 旧 Java 和新 Go 都表现为:订阅、收到数据、broker 断开、自动重连。 -- 当前链路可持续收到宇通 MQTT 数据并写入 TDengine RAW。 - -复验: - -```text -GET /api/history/raw-frames?protocol=YUTONG_MQTT&limit=1 - -total=1 -protocol=YUTONG_MQTT -vin=LMRKH9ACXR1004094 -parse_status=OK -parsed_json_len=1080 -fields_json_len=115 -``` - -运维建议: - -- 单条 EOF 不作为不可用判断。 -- 以 `YUTONG_MQTT` RAW 入库增长、最近成功 `mqtt subscribed` 时间、服务 `systemctl is-active` 作为可用性判断。 -- 如果 EOF 频率继续升高且 RAW 不再增长,再联系宇通侧确认 broker 长连接策略、同一 clientId 并发限制或网络出口策略。 diff --git a/docs/operations/go-vehicle-gateway-verification.md b/docs/operations/go-vehicle-gateway-verification.md deleted file mode 100644 index 0646e840..00000000 --- a/docs/operations/go-vehicle-gateway-verification.md +++ /dev/null @@ -1,158 +0,0 @@ -# Go Vehicle Gateway 验证手册 - -本文档用于验证 Go 重构运行面: - -- `go-vehicle-gateway`:GB32960 TCP、JT808 TCP、宇通 MQTT 接入。 -- `go-history-writer`:Kafka RAW 写 TDengine。 -- `go-stat-writer`:Kafka RAW 写 MySQL 每日指标。 -- `go-realtime-api`:Kafka unified event 写 Redis,并提供实时查询 API。 - -## 本地构建验证 - -```bash -cd go/vehicle-gateway -go test ./... -go build ./cmd/gateway -go build ./cmd/history-writer -go build ./cmd/stat-writer -go build ./cmd/realtime-api -``` - -## 生产原生部署验证 - -```bash -python3 tools/go_prod_acceptance.py --date 2026-07-02 -``` - -该命令会聚合执行: - -- `go_systemd_prod_smoke.py`:应用 ECS systemd 服务 active/enabled、端口归属和 gateway spool -- `go_kafka_prod_smoke.py`:Kafka topic 和消费组 lag -- `go_native_prod_smoke.py`:HTTP API、RAW `parsed_json`、历史核心表 `frame_id` 回链、每日指标公式、Redis 实时新鲜度、TDengine/MySQL/Redis 数据链路 - -如需单独检查 systemd: - -```bash -python3 tools/go_systemd_prod_smoke.py --host 115.29.187.205 --user root -``` - -脚本通过 SSH 检查: - -- `lingniu-go-gateway.service` -- `lingniu-go-history-writer.service` -- `lingniu-go-stat-writer.service` -- `lingniu-go-realtime-api.service` -- 上述服务均为 `active` 且 `enabled` -- `808`、`32960` 由 `gateway` 监听 -- `20210` 由 `realtime-api` 监听 -- `/opt/lingniu-go-native/spool/gateway` 无待回放文件 - -## 生产环境变量 - -当前 goal 的生产面使用 ECS 原生 systemd 部署,不再以 Docker/Portainer 作为生产运行方式。以下环境变量维护在 `/opt/lingniu-go-native/env/*.env`: - -```bash -KAFKA_BROKERS=172.17.111.56:9092 -TDENGINE_DSN=root:@ws(172.17.111.57:6041)/lingniu_vehicle_ts -MYSQL_DSN=lingniu_vehicle:@tcp(rm-bp179zbv481rnw3e2.mysql.rds.aliyuncs.com:3306)/lingniu_vehicle_data?parseTime=true&charset=utf8mb4,utf8&loc=Asia%2FShanghai -IDENTITY_MYSQL_DSN=${MYSQL_DSN} -VEHICLE_IDENTITY_TABLE=vehicle_identity_binding -REDIS_ADDR=r-bp1u741kij7e51i481.redis.rds.aliyuncs.com:6379 -REDIS_PASSWORD= -REDIS_DB=50 -``` - -宇通 MQTT 开启时再配置: - -```bash -YUTONG_MQTT_ENABLED=true -YUTONG_MQTT_URI= -YUTONG_MQTT_TOPICS=/ytforward/shln/+ -YUTONG_MQTT_CLIENT_ID=lingniu-go-yutong-mqtt -YUTONG_MQTT_USERNAME= -YUTONG_MQTT_PASSWORD= -``` - -## ECS 进程检查 - -```bash -systemctl is-active lingniu-go-gateway lingniu-go-history-writer lingniu-go-stat-writer lingniu-go-realtime-api - -ss -lntp | egrep ':(808|32960|20210)\b' -``` - -## Kafka 验证 - -优先使用 smoke 脚本做可重复检查: - -```bash -python3 tools/go_kafka_prod_smoke.py --host 114.55.58.251 --user root --max-lag 100 -``` - -脚本会检查 `vehicle.raw.go.gb32960.v1`、`vehicle.raw.go.jt808.v1`、`vehicle.raw.go.yutong-mqtt.v1`、`vehicle.event.go.unified.v1` 是否存在,并汇总 `go-history-writer`、`go-stat-writer`、`go-realtime-api` 的消费 lag。运行环境需要 SSH 免密或已建立可用的 SSH 认证方式。 - -手工核对命令: - -```bash -kafka-console-consumer --bootstrap-server 172.17.111.56:9092 \ - --topic vehicle.raw.go.jt808.v1 --max-messages 1 --timeout-ms 10000 - -kafka-console-consumer --bootstrap-server 172.17.111.56:9092 \ - --topic vehicle.raw.go.gb32960.v1 --max-messages 1 --timeout-ms 10000 - -kafka-console-consumer --bootstrap-server 172.17.111.56:9092 \ - --topic vehicle.raw.go.yutong-mqtt.v1 --max-messages 1 --timeout-ms 10000 - -kafka-console-consumer --bootstrap-server 172.17.111.56:9092 \ - --topic vehicle.event.go.unified.v1 --max-messages 1 --timeout-ms 10000 - -kafka-consumer-groups --bootstrap-server 172.17.111.56:9092 \ - --describe --group go-history-writer - -kafka-consumer-groups --bootstrap-server 172.17.111.56:9092 \ - --describe --group go-stat-writer - -kafka-consumer-groups --bootstrap-server 172.17.111.56:9092 \ - --describe --group go-realtime-api -``` - -## TDengine 验证 - -```sql -USE lingniu_vehicle_ts; -SHOW STABLES; -SELECT COUNT(*) FROM raw_frames; -SELECT COUNT(*) FROM vehicle_locations; -SELECT COUNT(*) FROM vehicle_mileage_points; -``` - -## MySQL 验证 - -```sql -SELECT protocol, metric_key, COUNT(*) -FROM vehicle_daily_metric -GROUP BY protocol, metric_key; - -SELECT * -FROM vehicle_daily_metric -WHERE metric_key IN ('daily_mileage_km', 'daily_total_mileage_km') -ORDER BY updated_at DESC -LIMIT 20; -``` - -## Redis 实时查询 - -```bash -curl -sS 'http://115.29.187.205:20210/api/realtime/vehicles/' -curl -sS 'http://115.29.187.205:20210/api/realtime/vehicles//online' -curl -sS 'http://115.29.187.205:20210/api/realtime/vehicles//protocols/JT808' -``` - -## 验收证据 - -正式切换前至少记录: - -- 一个真实 GB32960 VIN 的 RAW、location、mileage point、Redis snapshot。 -- 一个真实 JT808 VIN/phone 的 RAW、location、mileage point、daily metric、Redis snapshot。 -- 一个真实宇通 MQTT VIN 或 device_id 的 RAW、Redis snapshot。 -- `go-history-writer` 和 `go-stat-writer` 重启后 Kafka offset 能继续推进。 diff --git a/docs/operations/jt808-daily-mileage-runbook.md b/docs/operations/jt808-daily-mileage-runbook.md deleted file mode 100644 index 982d9e75..00000000 --- a/docs/operations/jt808-daily-mileage-runbook.md +++ /dev/null @@ -1,34 +0,0 @@ -# JT808 Daily Mileage Streaming - -## Scope - -The analytics app can calculate daily mileage from JT808 telemetry only. It consumes `vehicle.event.jt808.v1` and saves the current daily result into the common `vehicle-stat` metric repository. - -Only supported message backbone: Kafka. - -## Storage - -Runtime state: none outside `vehicle_stat_metric`. There is no separate JT808 daily-mileage table. The derived value is stored as one `daily_mileage_km` metric row in the common JDBC/MySQL table `vehicle_stat_metric`; the local-day minimum and maximum GPS total mileage values are kept on that same row as calculation source columns. - -The JT808 daily-mileage value is calculated from the GPS total mileage reported in location additional information: - -```text -daily_mileage_km = max_total_mileage_km - min_total_mileage_km -calculation_method = JT808_TOTAL_MILEAGE_DIFF -``` - -The first valid JT808 location point for a vehicle and local day stores both calculation endpoints on the metric row and writes `daily_mileage_km=0.0`. Later ordered or replayed points update the lower endpoint when the reported GPS total mileage is smaller, update the upper endpoint when it is larger, and rewrite the derived `daily_mileage_km` on the same row. - -## Runtime Settings - -Set these in Portainer or Nacos, without committing secrets: - -```text -KAFKA_TOPIC_JT808_EVENT=vehicle.event.jt808.v1 -VEHICLE_STAT_JT808_MILEAGE_ENABLED=true -MYSQL_JDBC_URL= -MYSQL_USERNAME= -MYSQL_PASSWORD= -``` - -Algorithm defaults use the Asia/Shanghai daily boundary. Restart recovery reads the same `daily_mileage_km` metric row. diff --git a/docs/operations/vehicle-ingest-tdengine-verification.md b/docs/operations/vehicle-ingest-tdengine-verification.md deleted file mode 100644 index 9d7f8cdd..00000000 --- a/docs/operations/vehicle-ingest-tdengine-verification.md +++ /dev/null @@ -1,351 +0,0 @@ -# Vehicle Ingest TDengine 链路验收手册 - -这份手册用于验收当前 GB32960/JT808/Yutong MQTT 接入重构链路: - -1. 协议 App 接收 TCP 报文。 -2. 协议 App 归档原始帧元数据,并把事件信封发布到 Kafka。 -3. `vehicle-history-app` 消费 Kafka。 -4. history 写入 raw、location 到 TDengine;不写 telemetry_fields。 -5. Swagger/API 通过 TDengine 做历史分页查询。 - -每一步都必须执行命令并检查输出后,才能标记为已验证。 - -## 必需运行参数 - -启动服务前先设置: - -```bash -export KAFKA_BROKERS=114.55.58.251:9092 -export TDENGINE_HISTORY_ENABLED=true -export TDENGINE_HISTORY_DATABASE=vehicle_ts -export TDENGINE_JDBC_URL='jdbc:TAOS-WS://:6041/vehicle_ts' -export TDENGINE_DRIVER_CLASS_NAME=com.taosdata.jdbc.ws.WebSocketDriver -export TDENGINE_USERNAME=root -export TDENGINE_PASSWORD='' -export TDENGINE_MIN_IDLE=0 -export TDENGINE_MAX_POOL_SIZE=32 -``` - -`TDENGINE_MIN_IDLE=0` 用于减少冷启动时 TDengine 连接重试日志。TDengine 地址稳定后,再按生产并发调大连接池。 - -高吞吐生产验收只验证 TDengine `raw_frames`、位置表和分页查询闭环。RAW 帧的 `parsedJson`、`metadataJson`、`rawUri` 直接进入 TDengine,GB32960 snapshot/fields 接口基于 `raw_frames` 与当前解析器即时返回结构化结果;接入服务写出的原始 `.bin` 只作为冷备复核材料。逐字段趋势宽表由独立字段解析服务消费 Kafka RAW/事件后维护,不由 history-app 写入。 - -## 构建 - -```bash -mvn -pl :gb32960-ingest-app,:jt808-ingest-app,:yutong-mqtt-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true -``` - -预期产物: - -- `modules/apps/gb32960-ingest-app/target/gb32960-ingest-app.jar` -- `modules/apps/jt808-ingest-app/target/jt808-ingest-app.jar` -- `modules/apps/yutong-mqtt-app/target/yutong-mqtt-app.jar` -- `modules/apps/vehicle-history-app/target/vehicle-history-app.jar` -- `modules/apps/vehicle-analytics-app/target/vehicle-analytics-app.jar` - -## ECS 服务状态验证 - -生产服务只在 ECS 上运行,通过 Portainer/Docker 管理。不要在本机用 `java -jar`、plist 或其它守护进程方式启动 GB32960、JT808、Yutong MQTT、history、analytics。 - -```bash -docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' \ - | egrep 'gb32960|jt808|yutong|vehicle-history|vehicle-analytics|NAMES' - -ss -lntp | egrep ':(808|32960|20100|20200|20310|20400|20500)\b' - -curl -sS http://127.0.0.1:20400/actuator/health -curl -sS http://127.0.0.1:20400/actuator/health/liveness -curl -sS http://127.0.0.1:20400/actuator/health/readiness -curl -sS http://127.0.0.1:20100/actuator/health -curl -sS http://127.0.0.1:20100/actuator/health/liveness -curl -sS http://127.0.0.1:20100/actuator/health/readiness -curl -sS http://127.0.0.1:20500/actuator/health -curl -sS http://127.0.0.1:20500/actuator/health/liveness -curl -sS http://127.0.0.1:20500/actuator/health/readiness -curl -sS http://127.0.0.1:20200/actuator/health -curl -sS http://127.0.0.1:20200/actuator/health/liveness -curl -sS http://127.0.0.1:20200/actuator/health/readiness -curl -sS http://127.0.0.1:20200/v3/api-docs \ - | grep -E '/api/event-history/locations|/api/event-history/raw-frames' -curl -sS http://127.0.0.1:20310/actuator/health -curl -sS http://127.0.0.1:20310/actuator/health/liveness -curl -sS http://127.0.0.1:20310/actuator/health/readiness -``` - -预期:五个容器均为运行状态;健康检查为 `UP`;JT808/GB32960 TCP 端口在 ECS 上监听;OpenAPI 中包含通用位置分页查询和 RAW 帧查询接口。history-app 不暴露 `/api/event-history/telemetry/fields`,也不持续写入逐字段宽表。JT808 位置帧中有 GPS 总里程时,`vehicle-analytics-app` 按差值法写入 MySQL `vehicle_stat_metric` 的 `daily_mileage_km` 指标。 - -## 运行位置约束 - -生产身份绑定固定使用 MySQL,不再提供 file/memory/sqlite 运行时切换入口。生产服务固定运行在 ECS/Portainer,不提供本机守护进程模板。 - -- `jt808-ingest-app` 监听 TCP `808`,HTTP `20400`。 -- `gb32960-ingest-app` 监听 TCP `32960`,HTTP `20100`。 -- `yutong-mqtt-app` 监听 HTTP `20500`。 -- `vehicle-history-app` 监听 HTTP `20200`。 -- `vehicle-analytics-app` 监听 HTTP `20310`。 -- history 热查询只依赖 Kafka 和 TDengine `raw_frames`;history 不需要共享 archive volume。 -- GB32960/JT808/Yutong MQTT 接入服务可以继续使用 `SINK_ARCHIVE_PATH` 保存原始冷备,但这不是 history API 的实时查询前置条件。 - -JT808 注册身份绑定: - -- 生产需要把 0x0100 注册信息维护到 MySQL 时,只需要配置 `VEHICLE_IDENTITY_MYSQL_*` 连接参数。 -- 需要提供 `VEHICLE_IDENTITY_MYSQL_JDBC_URL`、`VEHICLE_IDENTITY_MYSQL_USERNAME`、`VEHICLE_IDENTITY_MYSQL_PASSWORD`。 -- 服务启动时自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表。`vehicle_identity_binding` 只维护 `plate`、`vin` 两列;`vehicle_identity_binding_registration` 按 `protocol + phone` 保存 JT808 0x0100 注册字段。 -- 注册帧无真实 VIN 时也会写入 registration 表,`vin` 默认为 `unknown`。外部系统识别车辆后只需要维护车牌和 VIN,例如:`INSERT INTO vehicle_identity_binding (plate, vin) VALUES ('沪A61559F', 'LNVIN000000000001') ON DUPLICATE KEY UPDATE vin = VALUES(vin);` -- MySQL 绑定在启动时加载到内存索引,`plate/vin` 反写后会按 `VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL` 周期刷新,默认 `60s`;帧解析热路径只查内存,不按帧访问 MySQL。 -- 后续同一终端上报 raw/event 时,服务会用 registration 表里的 `phone/device_id/plate` 加绑定表里的 `plate/vin` 解析成真实 VIN。 - -健康检查: - -```bash -curl -sS http://127.0.0.1:20400/actuator/health -curl -sS http://127.0.0.1:20400/actuator/health/liveness -curl -sS http://127.0.0.1:20400/actuator/health/readiness -curl -sS http://127.0.0.1:20100/actuator/health -curl -sS http://127.0.0.1:20100/actuator/health/liveness -curl -sS http://127.0.0.1:20100/actuator/health/readiness -curl -sS http://127.0.0.1:20500/actuator/health -curl -sS http://127.0.0.1:20500/actuator/health/liveness -curl -sS http://127.0.0.1:20500/actuator/health/readiness -curl -sS http://127.0.0.1:20200/actuator/health -curl -sS http://127.0.0.1:20200/actuator/health/liveness -curl -sS http://127.0.0.1:20200/actuator/health/readiness -curl -sS http://127.0.0.1:20310/actuator/health -curl -sS http://127.0.0.1:20310/actuator/health/liveness -curl -sS http://127.0.0.1:20310/actuator/health/readiness -``` - -API / Swagger: - -- JT808 ingest health: `http://127.0.0.1:20400/actuator/health` -- GB32960 ingest health: `http://127.0.0.1:20100/actuator/health` -- Yutong MQTT ingest health: `http://127.0.0.1:20500/actuator/health` -- History query: `http://127.0.0.1:20200/swagger-ui/index.html` -- Analytics metrics: `http://127.0.0.1:20310/swagger-ui/index.html` - -## Kafka Topic 验证 - -历史服务必须消费事件 topic 和 raw topic: - -- `vehicle.event.gb32960.v1` -- `vehicle.raw.gb32960.v1` -- `vehicle.event.jt808.v1` -- `vehicle.raw.jt808.v1` -- `vehicle.event.mqtt-yutong.v1` -- `vehicle.raw.mqtt-yutong.v1` - -使用 Kafka CLI 或管理工具检查 topic 是否存在,并确认 history consumer group 在测试流量后没有持续堆积。默认 `KAFKA_GROUP_HISTORY=vehicle-history` 时,实际 group 会拆成: - -- `vehicle-history-gb32960-event` -- `vehicle-history-gb32960-raw` -- `vehicle-history-jt808-event` -- `vehicle-history-jt808-raw` -- `vehicle-history-yutong-mqtt-event` -- `vehicle-history-yutong-mqtt-raw` - -## JT808 实时转发验收 - -如果外部平台已经把 JT808 报文转发到 ECS TCP `808`,至少观察 60 秒日志和 Kafka 数据。 - -必须拿到以下证据: - -- `jt808-ingest-app` 日志显示接入连接或解析到上游消息 ID。 -- Kafka 的 `vehicle.event.jt808.v1` 和 `vehicle.raw.jt808.v1` 有新增记录。 -- `vehicle-history-app` 日志没有持续出现 consumer 或 TDengine 写入失败。 - -消费完成后,使用已知终端手机号查询: - -```bash -curl -sS 'http://127.0.0.1:20200/api/event-history/jt808/locations?phone=&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10' -``` - -预期:响应包含 `items`,并且每条记录包含 `eventTime`、`phone`、`longitude`、`latitude`、`speedKmh`、`rawUri`、`metadataJson`。 - -## GB32960 Snapshot / 字段投影验收 - -默认高吞吐模式下,GB32960 全字段查询不依赖 telemetry_fields 宽表。history 先查 TDengine `raw_frames`,直接使用 RAW 行中的 `parsedJson`/`parsedFields` 生成 snapshot 和字段投影: - -```bash -curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots?vin=&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10' - -curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots/fields?vin=&fields=VEHICLE.speedKmh,VEHICLE.totalMileageKm,POSITION_V2016.longitude,POSITION_V2016.latitude&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10' -``` - -预期:snapshot 返回 `sourceFrames.rawArchiveUri` 和解析后的 `blocks`;字段投影返回所选字段。新增协议字段后,先通过 replay 或专用解析任务补写 `raw_frames.parsedJson`,再基于历史 RAW 结构化结果查询。 - -## TDengine 直接检查 - -使用 TDengine CLI 或 JDBC 客户端检查超级表和子表是否创建: - -```sql -USE vehicle_ts; -SHOW STABLES; -SELECT COUNT(*) FROM raw_frames; -SELECT COUNT(*) FROM vehicle_locations; -SELECT COUNT(*) FROM jt808_locations; -``` - -预期: - -- `raw_frames`、`vehicle_locations`、`jt808_locations` 存在;有对应协议实时流量后,计数持续增长。 -- 字段趋势宽表不属于 history-app 验收范围,由独立字段解析服务负责。 -- 有实时流量后,计数持续增加。 - -## 失败语义 - -- TDengine 不可达时,TDengine 查询接口应该返回 HTTP `503`,消息类似 `tdengine history query failed`。 -- TDengine 未启用时,TDengine 专属接口不应该出现在 OpenAPI 中。 -- 路径不存在时,API 层应该返回 HTTP `404`,而不是泛化的存储失败。 - -## 2026-06-29 Superseded Local Live Verification Record - -该记录仅保留为解析和存储行为的历史证据;生产运行位置已经收敛到 ECS/Portainer。 - -当时参与验证的服务: - -- `jt808-ingest-app`:TCP `808`,HTTP `20400` -- `gb32960-ingest-app`:TCP `32960`,HTTP `20100` -- `vehicle-history-app`:HTTP `20200` - -当次健康检查均返回 `{"status":"UP"}`。 - -可重复运行 live 验收工具: - -```bash -python3 tools/vehicle_ingest_live_verify.py \ - --tdengine-rest-url 'http://115.29.185.82:6041/rest/sql/vehicle_ts' \ - --tdengine-username root \ - --tdengine-password '' \ - --history-base-url 'http://127.0.0.1:20200' \ - --date-from '2026-06-29 00:00:00' \ - --date-to '2026-06-29 23:59:59' \ - --jt808-peer-like '222.66.200.68:%' \ - --gb32960-peer-like '115.29.187.205:%' -``` - -该工具输出 `pass`、`warn` 或 `fail`:当前正式 GB32960 只有平台登录、没有车辆 `0x02` 时会输出 `warn`,避免把平台在线误判为车辆数据在线。正式车辆数据验收时增加 `--require-gb32960-vehicle-realtime`,缺少非测试 VIN 的 `0x02` 会直接返回非 0 退出码。 - -JT808 真实转发链路已验证: - -- ECS TCP `808` 有外部平台 `222.66.200.68` 持续发送 JT808 报文。 -- `vehicle.raw.jt808.v1` 和 `vehicle.event.jt808.v1` 被 history 消费。 -- `raw_frames` 中 `protocol='JT808'` 的真实行数已超过 `36000`。 -- 外部平台真实 0x0100 注册帧已超过 `347` 条,真实 0x0200 位置帧已超过 `12400` 条。 -- `jt808_locations` 中位置行数已超过 `8783`。 -- 当前高吞吐默认链路不维护 `telemetry_fields`;JT808 明细验证以 `raw_frames`、`vehicle_locations` 和 API 分页结果为准。 -- 分页 API 已用终端号 `13079963301` 验证第一页和 nextCursor 第二页,样例位置包含 `longitude=118.913846`、`latitude=31.927309`、`statusFlag=3` 和 `archive://2026/06/29/JT808/...` 原始帧引用。 -- 注册帧样例:终端号 `13079963320`、deviceId `9963320`、deviceType `SEG-9888G`、plate `沪A61559F`、maker `70112`、province `31`、city `113`、plateColor `2`。生产 VIN 反写依赖 MySQL identity store,后续 raw/event 的 VIN 解析也从 MySQL 绑定读取。 - -## JT808 ECS smoke / 轻压测工具 - -仓库提供 `tools/jt808_e2e_smoke.py`,用于重复验证 TCP `808` 到 TDengine history 的闭环: - -```bash -export TDENGINE_REST_URL='http://:6041/rest/sql/vehicle_ts' -export TDENGINE_USERNAME='root' -export TDENGINE_PASSWORD='' - -python3 tools/jt808_e2e_smoke.py \ - --connection-mode session \ - --start-phone 13079962000 \ - --frames 3 \ - --expect-history-count 3 \ - --verify-pagination \ - --archive-root "$PROJECT_ROOT/data/archive-jt808" \ - --tdengine-rest-url "$TDENGINE_REST_URL" \ - --tdengine-username "$TDENGINE_USERNAME" \ - --tdengine-password "$TDENGINE_PASSWORD" -``` - -默认 `--connection-mode per-frame`,适合单帧连接 smoke。生产验收建议使用 `--connection-mode session` 覆盖同一连接内连续位置帧。脚本会输出发送数量、history 可见数量、分页验证结果、raw archive 检查结果和 `tdengineRawFrames`。`tdengineRawFrames` 表示本次可见位置记录中的唯一 `rawUri` 有多少个已确认写入 TDengine `raw_frames`。 -- 合成 0x0100 注册帧终端号 `13079969999` 已验证:TCP `808` 返回 `0x8100` 注册 ACK,`raw_frames.metadata_json` 包含 `jt808.register.province`、`jt808.register.city`、`jt808.register.maker`、`jt808.register.deviceType`、`jt808.register.deviceId`、`jt808.register.plateColor`、`jt808.register.plate`,对应 raw archive 文件存在。 - -复查 SQL: - -```sql -USE vehicle_ts; -SELECT COUNT(*) FROM raw_frames WHERE protocol = 'JT808'; -SELECT COUNT(*) FROM jt808_locations WHERE protocol = 'JT808'; -SELECT event_time, vehicle_key, phone, message_id, raw_uri - FROM raw_frames - WHERE protocol = 'JT808' - ORDER BY event_time DESC - LIMIT 5; -SELECT ts, frame_id, phone, message_id, metadata_json, raw_uri - FROM raw_frames - WHERE protocol = 'JT808' AND phone = '13079969999' AND message_id = 256 - ORDER BY ts DESC - LIMIT 3; -``` - -复查 API: - -```bash -curl -sS 'http://127.0.0.1:20200/api/event-history/jt808/locations?phone=13079963301&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&order=DESC&limit=3' -curl -sS 'http://127.0.0.1:20200/api/event-history/jt808/raw-frames?phone=13079963320&messageId=256&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&order=DESC&limit=3' -``` - -`/api/event-history/jt808/raw-frames` 用于排查注册、鉴权、心跳、位置等原始帧索引。返回项会保留 `messageId`、`messageIdHex`、`rawUri`、`parseStatus`、`peer`、`vin`、`phone` 和完整 `metadataJson`;注册帧的 `metadataJson` 包含 `jt808.register.province`、`jt808.register.city`、`jt808.register.maker`、`jt808.register.deviceType`、`jt808.register.deviceId`、`jt808.register.plateColor`、`jt808.register.plate`、`identitySource`、`identityResolved` 等字段。 - -GB32960 链路已用正式平台登录和合成实时帧验证: - -- 正式平台 `115.29.187.205` 已连接 TCP `32960`,服务收到 `0x05 PLATFORM_LOGIN` 并返回 ACK;日志显示账号 `Hyundai`、策略 `ALLOW`。 -- 2026-06-29 正式平台登录 raw 已有 `3` 条,均落入 TDengine `raw_frames`,可通过 RAW 查询看到 `parsedJson` 中的 `PLATFORM_LOGIN` 解析结果。 -- 当前正式平台尚未发送车辆实时上报 `0x02`;非测试 VIN 的 `message_id=2` 计数为 `0`,因此不能把平台登录误判为车辆数据在线。 -- TCP `32960` 返回 GB32960 `2323` ACK。 -- `raw_frames` 中测试 VIN `LTEST202606290001` 已有 `2` 条 raw 记录。 -- 当前默认通过 GB32960 snapshot/fields 即时解析 `raw_frames` 中的 RAW JSON,不维护字段宽表增长。 -- latest raw 包含 `archive://2026/06/29/GB32960/LTEST202606290001/...`。 -- snapshots API 可查到 `speedKmh=62.4`、`totalMileageKm=223456.7`、`longitude=116.397128`、`latitude=39.916527`。 - -复查 SQL: - -```sql -USE vehicle_ts; -SELECT COUNT(*) FROM raw_frames - WHERE protocol = 'GB32960' AND vin = 'LTEST202606290001'; -SELECT event_time, vin, raw_uri, frame_id - FROM raw_frames - WHERE protocol = 'GB32960' AND vin = 'LTEST202606290001' - ORDER BY event_time DESC - LIMIT 5; -SELECT ts, vin, message_id, event_time, raw_uri, peer, metadata_json - FROM raw_frames - WHERE protocol = 'GB32960' AND peer LIKE '115.29.187.205:%' - ORDER BY ts DESC - LIMIT 5; -SELECT COUNT(*) FROM raw_frames - WHERE protocol = 'GB32960' AND message_id = 2 AND vin <> '' AND vin NOT LIKE 'LTEST%'; -``` - -也可以直接运行仓库 smoke 工具验证 TCP `32960`、GB32960 snapshot/fields、TDengine `raw_frames`。如果需要复核冷备 `.bin`,再传入接入服务 archive 根目录: - -```bash -export TDENGINE_REST_URL='http://:6041/rest/sql/vehicle_ts' -export TDENGINE_USERNAME='root' -export TDENGINE_PASSWORD='' - -python3 tools/gb32960_e2e_smoke.py \ - --archive-root "$PROJECT_ROOT/data/archive-jt808" \ - --tdengine-rest-url "$TDENGINE_REST_URL" \ - --tdengine-username "$TDENGINE_USERNAME" \ - --tdengine-password "$TDENGINE_PASSWORD" -``` - -预期输出包含 `records >= 1`、`fieldCounts` 中关键字段均大于 0、`tdengineRawFrames` 等于本次可见唯一 `rawUri` 数量。传入 `--archive-root` 时,`archiveChecked >= 1` 说明冷备文件也能复核;这里的 `records` 字段表示 snapshot 数量,用于兼容早期脚本输出名。 - -复查 API: - -```bash -curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots?vin=LTEST202606290001&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10' -curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots/fields?vin=LTEST202606290001&fields=VEHICLE.speedKmh,VEHICLE.totalMileageKm,POSITION_V2016.longitude,POSITION_V2016.latitude&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10' -``` - -## 当前注意事项 - -- 当前生产链路使用 TDengine WebSocket JDBC,例如 `jdbc:TAOS-WS://:6041/vehicle_ts`。 -- `KAFKA_CONSUMER_AUTO_OFFSET_RESET=latest` 适合生产接入新流量;如果要回放历史 Kafka 数据,需要切换 consumer group 或重置 offset。 -- `vehicle-history-app` raw 和 event 使用不同 consumer binding;raw consumer 必须开启,否则 `raw_frames` 不会持续增长。 -- JT808 设备没有 VIN 映射时,`vehicle_key` 使用 `jt808:`;启用 MySQL identity store 后,registration 表中已反写 VIN 的 phone/deviceId/plate 会用于后续 raw/event 的 VIN 解析。 -- GB32960 正式对端目前只验证到平台登录;等待真实车辆 `0x02` 到达后,再用非测试 VIN 复查 `vehicle_locations`、snapshot/fields API 和导出能力。 diff --git a/docs/superpowers/plans/2026-04-20-gb32960-body-parser-block-isolation.md b/docs/superpowers/plans/2026-04-20-gb32960-body-parser-block-isolation.md deleted file mode 100644 index ad1c2ff3..00000000 --- a/docs/superpowers/plans/2026-04-20-gb32960-body-parser-block-isolation.md +++ /dev/null @@ -1,600 +0,0 @@ -# GB32960 Body Parser — Per-Block Exception Isolation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** `Gb32960BodyParser` 主循环对单信息块的解析异常实现**单块隔离**:一块解析失败不再整帧放弃,改为把失败块兜成 `InfoBlock.Raw` 并继续解析后续信息块。 - -**Architecture:** 在主循环每次 `parser.parse(body)` 外面套 try/catch(仅捕获 `DecodeException` / `BufferUnderflowException` / `IndexOutOfBoundsException`,放行 RuntimeException 以免掩盖 parser bug)。失败时: -- 若 `fixedLen ≥ 0` 且剩余字节 ≥ fixedLen → 回滚 reader → 按 fixedLen 截取 Raw → position 前进 fixedLen → `continue` 循环 -- 否则(变长 parser 或剩余不足)→ 剩余字节全部兜成 Raw → `break` 循环(变长块无法安全找下一块边界) - -通过 `Gb32960Properties.Parse.lenientBlockFailure`(默认 true)控制开关,可以回退到严格模式。**不改 `InfoBlock.Raw` record 结构**(避免 downstream 兼容风险——`Gb32960EventMapper` 按 `findBlock(Class)` 类型查找,新增 Raw 不影响其行为)。 - -**Tech Stack:** Java 25, JUnit 5, AssertJ, Spring Boot ConfigurationProperties, Maven。仅改动 `protocol-gb32960` 模块。 - ---- - -## File Structure - -- **Modify** `protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960Properties.java` — 新增 `Parse` 内嵌类 + `parse` 字段/getter/setter -- **Modify** `protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java` — 主循环 try/catch + recovery 逻辑 + `lenientBlockFailure` 字段 -- **Modify** `protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java` — 把配置穿给 BodyParser -- **Create** `protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParserIsolationTest.java` — 3 个隔离场景 + 1 个严格模式回退场景 -- **Modify** `bootstrap-all/src/main/resources/application.yml` — 添加 `parse:` 注释段示例 -- **Modify** `CHANGELOG.md` — 追加本次变更条目 - ---- - -## Task 1: 基线验证 —— 现有测试全绿 - -**Files:** 无 - -- [ ] **Step 1.1: 跑 protocol-gb32960 模块测试** - -Run: `mvn -pl protocol-gb32960 test -q` -Expected: BUILD SUCCESS,所有现有测试通过(`Gb32960DecoderGoldenTest`、`Gb32960FullBlocksTest`、`Gb32960DecoderTest`、`GuangdongFcEndToEndTest`、parser/* 各 Block Parser 单测、profile/* 选择器单测)。 - -如果基线红了,**先停下来修复基线**再进入 Task 2。 - ---- - -## Task 2: 添加 Parse 配置子节点 - -**Files:** -- Modify: `protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960Properties.java` - -- [ ] **Step 2.1: 在 `Gb32960Properties` 类体中(在 `Auth` 字段后、`Tls` 字段前)新增 `parse` 字段** - -位置参考:现在 L24 `private Auth auth = new Auth();` 下面一行,在 L27 `private Tls tls = new Tls();` 之前插入: - -```java - /** - * 报文解析行为配置。 - * - *

默认 {@code lenientBlockFailure=true}:单个信息块解析异常时兜成 - * {@link com.lingniu.ingest.protocol.gb32960.model.InfoBlock.Raw} 继续解析, - * 不再整帧放弃。关闭后恢复旧行为(任意异常直接抛 {@code DecodeException} - * 放弃整帧),仅在灰度回滚时使用。 - */ - private Parse parse = new Parse(); -``` - -- [ ] **Step 2.2: 在类底部(`Tls` 静态类之后、`VendorExtension` 静态类之前)添加 `Parse` 静态内嵌类** - -```java - /** - * 报文解析容错策略。 - */ - public static class Parse { - /** - * 单块解析异常时是否兜底为 {@link com.lingniu.ingest.protocol.gb32960.model.InfoBlock.Raw} - * 继续解析。默认开启。 - * - *

    - *
  • {@code true}(默认):parser 抛 DecodeException / BufferUnderflowException / - * IndexOutOfBoundsException 时,固定长度块按 fixedLen 截取 Raw 后 continue; - * 变长块或剩余字节不足时,剩余全部兜成 Raw 后 break 循环。 - *
  • {@code false}:保留旧行为,任意异常直接抛出 DecodeException 放弃整帧。 - *
- */ - private boolean lenientBlockFailure = true; - - public boolean isLenientBlockFailure() { return lenientBlockFailure; } - public void setLenientBlockFailure(boolean lenientBlockFailure) { - this.lenientBlockFailure = lenientBlockFailure; - } - } -``` - -- [ ] **Step 2.3: 在属性 getter/setter 区域(现有 `getTls` 之后)补充 `getParse` / `setParse`** - -```java - public Parse getParse() { return parse; } - public void setParse(Parse parse) { this.parse = parse; } -``` - -- [ ] **Step 2.4: 编译验证** - -Run: `mvn -pl protocol-gb32960 compile -q` -Expected: BUILD SUCCESS。 - -- [ ] **Step 2.5: 提交** - -```bash -git add protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960Properties.java -git commit -m "$(cat <<'EOF' -config(gb32960): add parse.lenientBlockFailure toggle - -新增 lingniu.ingest.gb32960.parse.lenientBlockFailure 配置开关(默认 true), -为 Gb32960BodyParser 的单块异常隔离行为做回退开关。实现在后续 commit。 - -Co-Authored-By: Claude Opus 4.7 (1M context) -EOF -)" -``` - ---- - -## Task 3: RED —— 写三个失败测试覆盖隔离场景 - -**Files:** -- Create: `protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParserIsolationTest.java` - -- [ ] **Step 3.1: 创建测试文件** - -完整内容: - -```java -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.api.spi.DecodeException; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.AlarmV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlockType; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * 验证 {@link Gb32960BodyParser} 单块异常隔离行为。 - * - *

三个隔离场景: - *

    - *
  1. 固定长度块 parser 抛异常 → 失败块兜 Raw(按 fixedLen 截取)+ 后续块继续解析 - *
  2. 固定长度块剩余字节不足(截断帧) → 兜 Raw(剩余) + break 循环 - *
  3. 变长块 parser 抛异常 → 兜 Raw(从失败块起剩余全部) + break 循环 - *
- * - *

外加一个严格模式回退测试:{@code lenientBlockFailure=false} 时任意异常应抛 DecodeException。 - */ -class Gb32960BodyParserIsolationTest { - - /** 构造一个"看起来合法但中途 parser 失败"用来注入失败的 Position parser。 */ - private static final InfoBlockParser EXPLODING_FIXED_LEN_POSITION = new InfoBlockParser() { - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x05; } - @Override public int fixedLength() { return 9; } - @Override public InfoBlock parse(ByteBuffer buffer) { - // 模拟 parser 读了几字节后发现不合法就抛 - buffer.get(); buffer.get(); - throw new DecodeException("simulated parser failure in Position"); - } - }; - - /** 变长块(fixedLength=-1),parse 时消费若干字节后抛。模拟 Alarm/Voltage 类列表读越界。 */ - private static final InfoBlockParser EXPLODING_VAR_LEN_ALARM = new InfoBlockParser() { - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x07; } - @Override public int fixedLength() { return -1; } - @Override public InfoBlock parse(ByteBuffer buffer) { - // 假装读了个长度字段就爆 - buffer.get(); - throw new DecodeException("simulated parser failure in Alarm list-length read"); - } - }; - - @Test - void fixedLengthBlockFailure_isIsolated_subsequentBlocksStillParsed() { - // 帧布局:Vehicle(0x01, 20B) + Position(0x05, 9B 但 parser 爆) + Engine(0x04) 不构造, - // 简化为 Vehicle + FailingPosition + Vehicle 再次,验证"Position 之后还能继续"。 - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - EXPLODING_FIXED_LEN_POSITION)); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); // 1+20 = 21B - writePositionTypeAnd9ByteBody(os); // 1+9 = 10B (parser 会爆) - writeValidVehicle(os); // 1+20 = 21B - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - var result = parser.parse(ProtocolVersion.V2016, body); - - assertThat(result.blocks()).hasSize(3); - assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> { - assertThat(raw.typeCode()).isEqualTo(0x05); - assertThat(raw.type()).isEqualTo(InfoBlockType.RAW); - assertThat(raw.bytes()).hasSize(9); // 按 fixedLen 截取 - }); - assertThat(result.blocks().get(2)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(body.hasRemaining()).isFalse(); - } - - @Test - void truncatedFixedLengthBlock_isWrappedAsRaw_loopTerminates() { - // Vehicle(21B) + Position(type 0x05)但 body 只给 3B —— 不足 9B - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - new PositionV2016BlockParser())); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); - os.write(0x05); // Position typeCode - os.write(0); os.write(0); os.write(0); // 只 3B,远远不够 9B - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - var result = parser.parse(ProtocolVersion.V2016, body); - - assertThat(result.blocks()).hasSize(2); - assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> { - assertThat(raw.typeCode()).isEqualTo(0x05); - assertThat(raw.bytes()).hasSize(3); // 剩余字节全兜成 Raw - }); - } - - @Test - void variableLengthBlockFailure_swallowsRemainderAsRaw_loopBreaks() { - // Vehicle(21B) + FailingAlarm(0x07)后面还有一个 Vehicle,但因 Alarm 变长无法安全跳过, - // 整段 Alarm 起的剩余字节都被兜成 Raw 后 break。 - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - EXPLODING_VAR_LEN_ALARM)); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); // 21B - os.write(0x07); // Alarm typeCode - for (int i = 0; i < 10; i++) os.write(0xAA); // 10B 的 alarm body(parser 爆) - writeValidVehicle(os); // 21B(应当不再被解析) - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - var result = parser.parse(ProtocolVersion.V2016, body); - - assertThat(result.blocks()).hasSize(2); - assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> { - assertThat(raw.typeCode()).isEqualTo(0x07); - // 剩余 10B alarm body + 1+20=21B 后续 Vehicle = 31B - assertThat(raw.bytes()).hasSize(31); - }); - } - - @Test - void strictMode_throwsOnAnyBlockFailure() { - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - EXPLODING_FIXED_LEN_POSITION)); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - parser.setLenientBlockFailure(false); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); - writePositionTypeAnd9ByteBody(os); - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - assertThatThrownBy(() -> parser.parse(ProtocolVersion.V2016, body)) - .isInstanceOf(DecodeException.class); - } - - // ------------------------------------------------------------------------ - // helpers - // ------------------------------------------------------------------------ - - /** 写一个完整的合法 V2016 Vehicle 块(typeCode + 20B body)。 */ - private static void writeValidVehicle(ByteArrayOutputStream os) { - os.write(0x01); // typeCode Vehicle - os.write(0x01); // vehicleState=1 - os.write(0x01); // chargingState=1 - os.write(0x01); // runningMode=1 - os.write(0); os.write(0); // speed=0 - os.write(0); os.write(0); os.write(0); os.write(0); // mileage=0 - os.write(0); os.write(0); // totalVoltage=0 - os.write(0); os.write(0); // totalCurrent=0(offset 1000,原值0) - os.write(50); // soc=50% - os.write(0x01); // dcdc - os.write(0); // gear - os.write(0); os.write(0); // insulation - os.write(0); // accelerator - os.write(0); // brake - } - - /** 写 Position typeCode(0x05) + 9 字节 body(内容不重要,parser 替换成 EXPLODING 的)。 */ - private static void writePositionTypeAnd9ByteBody(ByteArrayOutputStream os) { - os.write(0x05); - for (int i = 0; i < 9; i++) os.write(0); - } -} -``` - -- [ ] **Step 3.2: 跑测试验证 RED** - -Run: `mvn -pl protocol-gb32960 test -Dtest=Gb32960BodyParserIsolationTest -q` -Expected: -- `fixedLengthBlockFailure_isIsolated_subsequentBlocksStillParsed` FAIL(当前会抛 DecodeException) -- `truncatedFixedLengthBlock_isWrappedAsRaw_loopTerminates` FAIL(当前 L132 会抛 DecodeException) -- `variableLengthBlockFailure_swallowsRemainderAsRaw_loopBreaks` FAIL(异常冒出整帧失败) -- `strictMode_throwsOnAnyBlockFailure` FAIL(没有 `setLenientBlockFailure` 方法,编译就红) - -**验证点**:编译失败(strictMode test 里 `setLenientBlockFailure` 尚未存在)是**预期** RED 信号——下一步实现。 - ---- - -## Task 4: GREEN —— 实现单块异常隔离 - -**Files:** -- Modify: `protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java` - -- [ ] **Step 4.1: 在类字段区新增 `lenientBlockFailure` 字段 + setter** - -在 `private final VendorExtensionSelector selector;`(现 L54)下面插入: - -```java - /** - * 单块解析异常时是否兜底为 Raw 后继续。由 - * {@link com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties.Parse#isLenientBlockFailure()} - * 注入;默认 true。 - */ - private boolean lenientBlockFailure = true; - - public void setLenientBlockFailure(boolean lenientBlockFailure) { - this.lenientBlockFailure = lenientBlockFailure; - } -``` - -- [ ] **Step 4.2: 改造主循环:替换 L130~L150 的整个 "parse + 长度校验" 段** - -**完整替换块**:以下代码替换原来从 `int fixedLen = parser.fixedLength();`(L130)开始到 `blocks.add(block);`(L150)结束的整块。 - -```java - int fixedLen = parser.fixedLength(); - int posBefore = body.position(); - - // 旧行为:fixedLen 预检不足 → 直接抛。新行为(lenient 模式):走统一恢复路径。 - if (fixedLen >= 0 && body.remaining() < fixedLen) { - if (!lenientBlockFailure) { - throw new DecodeException( - "info block 0x" + Integer.toHexString(typeCode) - + " needs " + fixedLen + " bytes but got " + body.remaining()); - } - // 剩余字节数不足 fixedLen —— 无法按块截取,整尾兜 Raw + break - int remaining = body.remaining(); - byte[] tail = new byte[remaining]; - body.get(tail); - log.warn("[gb32960] truncated block typeCode=0x{} declaredFixedLen={} remaining={} — wrapping tail as Raw", - Integer.toHexString(typeCode), fixedLen, remaining); - blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail)); - break; - } - - InfoBlock block; - try { - block = parser.parse(body); - } catch (DecodeException | java.nio.BufferUnderflowException | IndexOutOfBoundsException e) { - if (!lenientBlockFailure) { - if (e instanceof DecodeException de) throw de; - throw new DecodeException( - "parser " + parser.getClass().getSimpleName() + " failed: " + e.getMessage(), e); - } - // 恢复路径:先回滚 position 到 parse 入口 - body.position(posBefore); - if (fixedLen >= 0) { - // 固定长度块:按 fixedLen 截取 → 兜 Raw → continue 循环 - byte[] corrupt = new byte[fixedLen]; - body.get(corrupt); - log.warn("[gb32960] block parse failed typeCode=0x{} parser={} fixedLen={} — isolated as Raw, continuing", - Integer.toHexString(typeCode), parser.getClass().getSimpleName(), fixedLen, e); - blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, corrupt)); - continue; - } else { - // 变长块:无法安全找下一块边界 → 剩余整段兜 Raw + break - int remaining = body.remaining(); - byte[] tail = new byte[remaining]; - body.get(tail); - log.warn("[gb32960] variable-length block parse failed typeCode=0x{} parser={} remaining={} — wrapping remainder as Raw, stopping loop", - Integer.toHexString(typeCode), parser.getClass().getSimpleName(), remaining, e); - blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail)); - break; - } - } - - int consumed = body.position() - posBefore; - if (fixedLen >= 0 && consumed != fixedLen) { - // 这是 parser 契约违反(声明 fixedLen=X 但实际读了 Y),保持抛异常以暴露 parser bug。 - throw new DecodeException( - "parser " + parser.getClass().getSimpleName() - + " consumed " + consumed - + " bytes but declared " + fixedLen); - } - if (log.isDebugEnabled()) { - log.debug("[gb32960] block typeCode=0x{} parser={} pos {}→{} consumed={} declaredFixed={}", - Integer.toHexString(typeCode), parser.getClass().getSimpleName(), - typeStartPos, body.position(), consumed, fixedLen); - } - blocks.add(block); -``` - -- [ ] **Step 4.3: 运行隔离测试验证 GREEN** - -Run: `mvn -pl protocol-gb32960 test -Dtest=Gb32960BodyParserIsolationTest -q` -Expected: 4 个测试全部 PASS。 - -- [ ] **Step 4.4: 运行全模块测试检查回归** - -Run: `mvn -pl protocol-gb32960 test -q` -Expected: BUILD SUCCESS,所有原有测试(含 Golden、FullBlocks、GuangdongFcEndToEnd)依旧通过。 - -如果有原有测试红了:**停下来诊断**。最可能的原因是某个现有测试之前靠"抛异常"行为验证错误帧的,需要手动判断这是测试问题还是实现问题。 - -- [ ] **Step 4.5: 提交** - -```bash -git add protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java \ - protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParserIsolationTest.java -git commit -m "$(cat <<'EOF' -feat(gb32960): isolate per-block parse failures in body parser - -主循环改造:单信息块 parser 抛 DecodeException / BufferUnderflowException / -IndexOutOfBoundsException 时不再放弃整帧。 - -- 固定长度块(fixedLen≥0):回滚 reader,按 fixedLen 截取字节兜成 InfoBlock.Raw, - position 前进 fixedLen,continue 循环继续解析后续块 -- 变长块(fixedLen=-1)或剩余不足 fixedLen:剩余字节全部兜成 Raw 后 break -- parser 契约违反(consumed != declared fixedLen)仍抛异常,以暴露 parser bug -- 通过 lingniu.ingest.gb32960.parse.lenientBlockFailure=false 可回退严格模式 -- 新增 Gb32960BodyParserIsolationTest 覆盖 3 种失败场景 + 1 严格模式 - -不改 InfoBlock.Raw record 结构;下游 Gb32960EventMapper 按 findBlock(Class) 类型 -查找,新增 Raw 不影响其行为。 - -Co-Authored-By: Claude Opus 4.7 (1M context) -EOF -)" -``` - ---- - -## Task 5: 把配置注入 BodyParser(AutoConfiguration 连线) - -**Files:** -- Modify: `protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java` - -- [ ] **Step 5.1: 定位 `Gb32960BodyParser` bean 定义** - -先查看:`grep -n "Gb32960BodyParser" protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java` - -根据返回的行号,在构造 `Gb32960BodyParser` 的 @Bean 方法里,构造完成后立即调用: - -```java - Gb32960BodyParser parser = new Gb32960BodyParser(profileRegistry, selector); - parser.setLenientBlockFailure(properties.getParse().isLenientBlockFailure()); - return parser; -``` - -(如果目前的构造返回一行表达式,改成先赋给 local 变量再设 flag 再 return。) - -- [ ] **Step 5.2: 编译并运行全模块测试** - -Run: `mvn -pl protocol-gb32960 test -q` -Expected: BUILD SUCCESS。 - -- [ ] **Step 5.3: 提交** - -```bash -git add protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java -git commit -m "$(cat <<'EOF' -config(gb32960): wire parse.lenientBlockFailure into BodyParser bean - -Gb32960AutoConfiguration 在创建 Gb32960BodyParser bean 后,将 -Gb32960Properties.Parse.lenientBlockFailure 通过 setter 注入,让运行期配置生效。 - -Co-Authored-By: Claude Opus 4.7 (1M context) -EOF -)" -``` - ---- - -## Task 6: bootstrap-all 配置文档化 - -**Files:** -- Modify: `bootstrap-all/src/main/resources/application.yml` - -- [ ] **Step 6.1: 在 `lingniu.ingest.gb32960` 节点的 `vendor-extensions` 段之后、`jt808` 之前,插入 parse 配置示例** - -参考位置:现有 application.yml L57 末尾(`vendor-extensions` list 结束)。在 L58 `jt808:` 之前加入: - -```yaml - # 报文解析容错。默认启用单块异常隔离:某个信息块解析失败时兜成 Raw 后继续, - # 不再整帧丢弃。仅在需要严格失败语义(灰度回滚或抓虫)时设为 false。 - parse: - lenient-block-failure: true -``` - -- [ ] **Step 6.2: 启动一次 bootstrap-all 的 dry-run 编译** - -Run: `mvn -pl bootstrap-all compile -q` -Expected: BUILD SUCCESS(只是配置段注释变化,不会影响编译)。 - -- [ ] **Step 6.3: 提交** - -```bash -git add bootstrap-all/src/main/resources/application.yml -git commit -m "$(cat <<'EOF' -config(gb32960): document parse.lenient-block-failure in application.yml - -Co-Authored-By: Claude Opus 4.7 (1M context) -EOF -)" -``` - ---- - -## Task 7: 全仓库回归测试 - -**Files:** 无(只是验证) - -- [ ] **Step 7.1: 跑完整项目测试** - -Run: `mvn test -q` -Expected: BUILD SUCCESS。重点关注: -- `protocol-gb32960` 模块全绿(Golden、FullBlocks、Isolation、GuangdongFcEndToEnd、Decoder、Mapper、所有 parser 单测、profile) -- 其它模块(`ingest-core`、`sink-kafka` 等)不受影响(无改动应自动通过) - -若有红,停下来诊断。 - ---- - -## Task 8: 更新 CHANGELOG - -**Files:** -- Modify: `CHANGELOG.md` - -- [ ] **Step 8.1: 在 CHANGELOG.md 文件顶部(在 `## [0.1.0] — 2026-04-15` 标题之前)新增一个 `## [Unreleased]` 段落;如已存在则追加** - -新增段落内容(如已存在 Unreleased 段则在其 `### Added` / `### Changed` 内部追加): - -```markdown -## [Unreleased] - -### Changed —— GB/T 32960 Body Parser 单块异常隔离 -- `Gb32960BodyParser` 主循环对单信息块的 parser 异常不再放弃整帧: - - 固定长度块(`fixedLength ≥ 0`):回滚 reader → 按 fixedLen 截取字节兜成 - `InfoBlock.Raw` → 继续解析后续块; - - 变长块或剩余字节不足:剩余字节全部兜成 `Raw` 后终止循环; - - `parser` 契约违反(声明 fixedLen=X 但实际读了 Y)仍抛 `DecodeException`, - 以暴露 parser bug,不被静默吞掉; - - 只捕获 `DecodeException` / `BufferUnderflowException` / `IndexOutOfBoundsException`, - `RuntimeException` 继续向上抛,保留 bug 可见性。 -- 新增配置 `lingniu.ingest.gb32960.parse.lenient-block-failure`(默认 `true`); - 回退严格模式用 `false`。 -- `InfoBlock.Raw` record 结构**未变**,下游 `Gb32960EventMapper` 按 `findBlock(Class)` - 类型匹配,新增 Raw 不影响业务事件映射。 -- 覆盖测试:`Gb32960BodyParserIsolationTest` —— 3 种隔离场景 + 严格模式回退。 -``` - -- [ ] **Step 8.2: 提交** - -```bash -git add CHANGELOG.md -git commit -m "$(cat <<'EOF' -docs(changelog): record gb32960 body parser block isolation - -Co-Authored-By: Claude Opus 4.7 (1M context) -EOF -)" -``` - ---- - -## Self-Review Checklist - -- [x] **Spec coverage**: 原讨论的 A1~A7 全部覆盖——A1 状态机(Task 4 Step 4.2)、A2 三类错误策略(同)、A3 收窄异常种类(同,只 catch 三种)、A4 日志分类(warn 带 parser 名/typeCode/原因)、A5 配置开关(Task 2)、A6 合成帧测试(Task 3 三场景 + 严格回退)、A7 下游兼容(不改 Raw record,`findBlock` 类型匹配不受影响,plan 中已说明)。 -- [x] **Placeholder scan**: 无 TBD / TODO / "实现上面的" 等占位;每个 Step 含实际代码 or 命令。 -- [x] **Type consistency**: `lenientBlockFailure` 在 Properties / BodyParser 字段 / setter / 测试中拼写一致。`InfoBlockType.RAW` 枚举值依赖当前存在(已核对 InfoBlock.Raw record)。 -- [x] **测试命名一致**:`Gb32960BodyParserIsolationTest` 在 Task 3 创建、Task 4/7 引用一致。 -- [x] **未引入新枚举/proto 变更**:InfoBlock.Raw 沿用现有 `(int typeCode, InfoBlockType type, byte[] bytes)` 构造。 diff --git a/docs/superpowers/plans/2026-06-23-gb32960-history-duckdb-hot-store.md b/docs/superpowers/plans/2026-06-23-gb32960-history-duckdb-hot-store.md deleted file mode 100644 index 8e118f95..00000000 --- a/docs/superpowers/plans/2026-06-23-gb32960-history-duckdb-hot-store.md +++ /dev/null @@ -1,817 +0,0 @@ -> **Superseded:** This 2026-06-23 DuckDB hot-store plan is historical context. -> Use `docs/target-architecture.md` and -> `docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md` for the -> current production architecture: TDengine stores hot history, `sink-archive` -> owns raw bytes, and `event-file-store` / DuckDB are no longer part of the -> current build surface. - -# GB32960 History DuckDB Hot Store Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Replace the current high-write-risk Parquet rewrite history path with a DuckDB hot history store that supports fast `vin + time` queries and full RAW frame replay. - -**Architecture:** Add a new `DuckDbHotEventFileStore` implementation behind the existing `EventFileStore` interface, backed by append/upsert DuckDB tables instead of rewriting per-vehicle Parquet files. Keep the current HTTP history API and RAW archive reader intact, then switch `vehicle-history-app` to the hot DuckDB store by configuration. - -**Tech Stack:** Java 26, Spring Boot, DuckDB JDBC, JUnit 5, AssertJ, existing Kafka `VehicleEnvelope`, existing `ArchiveStore`. - ---- - -## Scope - -This plan implements Phase 1 from `docs/superpowers/specs/2026-06-23-gb32960-production-readiness-design.md`. - -Included: - -- DuckDB hot event table for `EventFileRecord`. -- Idempotent writes by `event_id`. -- Query by protocol/date/VIN/eventType/eventTime/order/limit. -- Lookup by `rawArchiveUri`. -- Spring configuration to select the hot store. -- Tests that prove history endpoints still replay RAW frames. -- Local verification using the existing `vehicle-history-app`. - -Not included in this plan: - -- MySQL daily statistics. -- VIN-to-platform local mapping. -- Full telemetry point columnar table. -- Alarm timeline MySQL tables. -- Cold Parquet export. - -Those will be separate plans after this foundation lands. - -## Files - -- Create: `modules/sinks/event-file-store/src/main/java/com/lingniu/ingest/eventfilestore/DuckDbHotEventFileStore.java` -- Create: `modules/sinks/event-file-store/src/test/java/com/lingniu/ingest/eventfilestore/DuckDbHotEventFileStoreTest.java` -- Modify: `modules/sinks/event-file-store/src/main/java/com/lingniu/ingest/eventfilestore/config/EventFileStoreProperties.java` -- Modify: `modules/sinks/event-file-store/src/main/java/com/lingniu/ingest/eventfilestore/config/EventFileStoreAutoConfiguration.java` -- Modify: `modules/sinks/event-file-store/src/test/java/com/lingniu/ingest/eventfilestore/config/EventFileStoreAutoConfigurationTest.java` -- Modify: `modules/apps/vehicle-history-app/src/main/resources/application.yml` -- Modify: `modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java` -- Modify: `modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameServiceTest.java` - -## Task 1: Add Hot Store Configuration - -**Files:** -- Modify: `modules/sinks/event-file-store/src/main/java/com/lingniu/ingest/eventfilestore/config/EventFileStoreProperties.java` -- Modify: `modules/sinks/event-file-store/src/main/java/com/lingniu/ingest/eventfilestore/config/EventFileStoreAutoConfiguration.java` -- Test: `modules/sinks/event-file-store/src/test/java/com/lingniu/ingest/eventfilestore/config/EventFileStoreAutoConfigurationTest.java` - -- [ ] **Step 1: Write the failing auto-configuration test** - -Add a test that sets `lingniu.ingest.event-file-store.storage=duckdb-hot` and expects the bean class to be `DuckDbHotEventFileStore`. - -```java -@Test -void createsDuckDbHotStoreWhenStorageIsDuckDbHot() { - contextRunner - .withPropertyValues( - "lingniu.ingest.event-file-store.enabled=true", - "lingniu.ingest.event-file-store.storage=duckdb-hot", - "lingniu.ingest.event-file-store.path=" + tempDir.resolve("history")) - .run(context -> assertThat(context) - .hasSingleBean(EventFileStore.class) - .getBean(EventFileStore.class) - .isInstanceOf(DuckDbHotEventFileStore.class)); -} -``` - -- [ ] **Step 2: Run the new test and verify it fails** - -Run: - -```bash -mvn -pl :event-file-store -Dtest=EventFileStoreAutoConfigurationTest#createsDuckDbHotStoreWhenStorageIsDuckDbHot test -``` - -Expected: compilation failure because `DuckDbHotEventFileStore` and `storage` property do not exist. - -- [ ] **Step 3: Add the `storage` property** - -Add to `EventFileStoreProperties`: - -```java -/** - * Storage backend. `duckdb-hot` is the production backend; `parquet-sidecar` - * keeps the previous implementation available for compatibility tests. - */ -private String storage = "duckdb-hot"; - -public String getStorage() { - return storage; -} - -public void setStorage(String storage) { - this.storage = storage; -} -``` - -- [ ] **Step 4: Switch auto-configuration by storage mode** - -Change `eventFileStore(...)` to: - -```java -@Bean -@ConditionalOnMissingBean -public EventFileStore eventFileStore(EventFileStoreProperties properties, - ObjectProvider objectMapper) { - ObjectMapper mapper = mapper(objectMapper); - Path root = Path.of(properties.getPath()); - ZoneId zoneId = ZoneId.of(properties.getZoneId()); - String storage = properties.getStorage() == null ? "" : properties.getStorage().trim(); - return switch (storage) { - case "", "duckdb-hot" -> new DuckDbHotEventFileStore(root, zoneId, mapper); - case "parquet-sidecar" -> new DuckDbParquetEventFileStore(root, zoneId, mapper); - default -> throw new IllegalStateException( - "unsupported event-file-store storage: " + properties.getStorage()); - }; -} -``` - -Import `DuckDbHotEventFileStore`. - -- [ ] **Step 5: Run configuration tests** - -Run: - -```bash -mvn -pl :event-file-store -Dtest=EventFileStoreAutoConfigurationTest test -``` - -Expected: tests pass after the store class exists in Task 2. - -## Task 2: Implement DuckDB Hot Store Schema and Writes - -**Files:** -- Create: `modules/sinks/event-file-store/src/main/java/com/lingniu/ingest/eventfilestore/DuckDbHotEventFileStore.java` -- Create: `modules/sinks/event-file-store/src/test/java/com/lingniu/ingest/eventfilestore/DuckDbHotEventFileStoreTest.java` - -- [ ] **Step 1: Write failing append/query/idempotency tests** - -Create `DuckDbHotEventFileStoreTest`: - -```java -class DuckDbHotEventFileStoreTest { - - @TempDir - Path tempDir; - - @Test - void appendsRecordsAndQueriesByVinTypeAndTime() throws Exception { - EventFileStore store = store(); - store.append(rawRecord("raw-1", "VIN001", "2026-06-23T01:00:00Z")); - store.append(rawRecord("raw-2", "VIN002", "2026-06-23T01:00:01Z")); - store.append(rawRecord("raw-3", "VIN001", "2026-06-23T01:00:02Z")); - - EventFileQuery query = new EventFileQuery( - ProtocolId.GB32960, - LocalDate.parse("2026-06-23"), - LocalDate.parse("2026-06-23"), - Instant.parse("2026-06-23T01:00:00Z"), - Instant.parse("2026-06-23T01:00:03Z"), - EventFileQuery.Order.DESC, - 2, - "VIN001", - "RAW_ARCHIVE"); - - assertThat(store.query(query)) - .extracting(EventFileRecord::eventId) - .containsExactly("raw-3", "raw-1"); - } - - @Test - void appendAllIsIdempotentByEventId() throws Exception { - EventFileStore store = store(); - EventFileRecord original = rawRecord("same-id", "VIN001", "2026-06-23T01:00:00Z"); - EventFileRecord replacement = new EventFileRecord( - "same-id", - ProtocolId.GB32960, - "RAW_ARCHIVE", - "VIN001", - Instant.parse("2026-06-23T01:00:05Z"), - Instant.parse("2026-06-23T01:00:06Z"), - "archive://replacement.bin", - Map.of("source", "replacement"), - "{\"replacement\":true}"); - - store.appendAll(List.of(original, replacement)); - - assertThat(store.query(new EventFileQuery( - ProtocolId.GB32960, - LocalDate.parse("2026-06-23"), - LocalDate.parse("2026-06-23"), - EventFileQuery.Order.ASC, - 10, - "VIN001", - "RAW_ARCHIVE"))) - .singleElement() - .satisfies(record -> { - assertThat(record.eventId()).isEqualTo("same-id"); - assertThat(record.rawArchiveUri()).isEqualTo("archive://replacement.bin"); - }); - } - - @Test - void findsRecordByRawArchiveUri() throws Exception { - EventFileStore store = store(); - EventFileRecord record = rawRecord("raw-uri", "VIN001", "2026-06-23T01:00:00Z"); - store.append(record); - - EventFileRecord found = store.findByRawArchiveUri(record.rawArchiveUri()); - - assertThat(found).isNotNull(); - assertThat(found.eventId()).isEqualTo("raw-uri"); - } - - private EventFileStore store() { - return new DuckDbHotEventFileStore(tempDir, ZoneId.of("Asia/Shanghai"), new ObjectMapper()); - } - - private static EventFileRecord rawRecord(String id, String vin, String eventTime) { - return new EventFileRecord( - id, - ProtocolId.GB32960, - "RAW_ARCHIVE", - vin, - Instant.parse(eventTime), - Instant.parse(eventTime).plusMillis(100), - "archive://" + id + ".bin", - Map.of("platformAccount", "Hyundai", "command", "REALTIME_REPORT"), - "{\"eventId\":\"" + id + "\"}"); - } -} -``` - -- [ ] **Step 2: Run the test and verify it fails** - -Run: - -```bash -mvn -pl :event-file-store -Dtest=DuckDbHotEventFileStoreTest test -``` - -Expected: compilation failure because `DuckDbHotEventFileStore` does not exist. - -- [ ] **Step 3: Create the hot store class** - -Create `DuckDbHotEventFileStore` with this structure: - -```java -public final class DuckDbHotEventFileStore implements EventFileStore { - - private static final TypeReference> STRING_MAP = new TypeReference<>() {}; - - private final Path root; - private final Path dbPath; - private final ZoneId partitionZone; - private final ObjectMapper objectMapper; - private volatile boolean initialized; - - public DuckDbHotEventFileStore(Path root, ZoneId partitionZone) { - this(root, partitionZone, new ObjectMapper()); - } - - public DuckDbHotEventFileStore(Path root, ZoneId partitionZone, ObjectMapper objectMapper) { - if (root == null) { - throw new IllegalArgumentException("root must not be null"); - } - this.root = root.toAbsolutePath(); - this.dbPath = this.root.resolve("events.duckdb"); - this.partitionZone = partitionZone == null ? ZoneId.of("Asia/Shanghai") : partitionZone; - this.objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper; - } - - @Override - public synchronized void appendAll(List records) throws IOException { - if (records == null || records.isEmpty()) { - return; - } - ensureInitialized(); - try (Connection connection = DriverManager.getConnection(jdbcUrl())) { - connection.setAutoCommit(false); - try { - upsertRecords(connection, records); - connection.commit(); - } catch (SQLException | IOException e) { - connection.rollback(); - throw e; - } finally { - connection.setAutoCommit(true); - } - } catch (SQLException e) { - throw new IOException("write duckdb hot event store failed", e); - } - } - - @Override - public List query(EventFileQuery query) throws IOException { - ensureInitialized(); - // Implement in Task 3. - return List.of(); - } - - @Override - public EventFileRecord findByRawArchiveUri(String rawArchiveUri) throws IOException { - ensureInitialized(); - // Implement in Task 3. - return null; - } -} -``` - -- [ ] **Step 4: Add schema initialization** - -Add: - -```java -private void ensureInitialized() throws IOException { - if (initialized) { - return; - } - synchronized (this) { - if (initialized) { - return; - } - Files.createDirectories(root); - try (Connection connection = DriverManager.getConnection(jdbcUrl()); - Statement statement = connection.createStatement()) { - statement.execute(""" - CREATE TABLE IF NOT EXISTS event_records ( - event_id VARCHAR PRIMARY KEY, - protocol VARCHAR NOT NULL, - event_type VARCHAR NOT NULL, - vin VARCHAR NOT NULL, - event_time_ms BIGINT NOT NULL, - ingest_time_ms BIGINT NOT NULL, - partition_date DATE NOT NULL, - raw_archive_uri VARCHAR NOT NULL, - metadata_json VARCHAR NOT NULL, - payload_json VARCHAR NOT NULL - ) - """); - statement.execute(""" - CREATE INDEX IF NOT EXISTS event_records_protocol_date_time_idx - ON event_records(protocol, partition_date, event_time_ms) - """); - statement.execute(""" - CREATE INDEX IF NOT EXISTS event_records_vin_date_time_idx - ON event_records(protocol, vin, partition_date, event_time_ms) - """); - statement.execute(""" - CREATE INDEX IF NOT EXISTS event_records_vin_type_date_time_idx - ON event_records(protocol, vin, event_type, partition_date, event_time_ms) - """); - statement.execute(""" - CREATE INDEX IF NOT EXISTS event_records_raw_archive_uri_idx - ON event_records(raw_archive_uri) - """); - initialized = true; - } catch (SQLException e) { - throw new IOException("initialize duckdb hot event store failed", e); - } - } -} -``` - -- [ ] **Step 5: Add idempotent batch upsert** - -Use DuckDB `INSERT OR REPLACE` inside one transaction: - -```java -private void upsertRecords(Connection connection, List records) - throws SQLException, IOException { - try (PreparedStatement ps = connection.prepareStatement(""" - INSERT OR REPLACE INTO event_records VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """)) { - for (EventFileRecord record : records) { - ps.setString(1, record.eventId()); - ps.setString(2, record.protocol().name()); - ps.setString(3, record.eventType()); - ps.setString(4, record.vin()); - ps.setLong(5, record.eventTime().toEpochMilli()); - ps.setLong(6, record.ingestTime().toEpochMilli()); - ps.setString(7, LocalDate.ofInstant(record.eventTime(), partitionZone).toString()); - ps.setString(8, record.rawArchiveUri()); - ps.setString(9, objectMapper.writeValueAsString(record.metadata())); - ps.setString(10, record.payloadJson()); - ps.addBatch(); - } - ps.executeBatch(); - } -} -``` - -- [ ] **Step 6: Run the hot store tests** - -Run: - -```bash -mvn -pl :event-file-store -Dtest=DuckDbHotEventFileStoreTest test -``` - -Expected: query tests still fail until Task 3 implements reads; append initialization should compile. - -## Task 3: Implement Hot Store Queries and Raw URI Lookup - -**Files:** -- Modify: `modules/sinks/event-file-store/src/main/java/com/lingniu/ingest/eventfilestore/DuckDbHotEventFileStore.java` -- Test: `modules/sinks/event-file-store/src/test/java/com/lingniu/ingest/eventfilestore/DuckDbHotEventFileStoreTest.java` - -- [ ] **Step 1: Implement `query(EventFileQuery)`** - -Use prepared statements for every external value: - -```java -@Override -public List query(EventFileQuery query) throws IOException { - ensureInitialized(); - String order = query.order() == EventFileQuery.Order.DESC ? "DESC" : "ASC"; - StringBuilder where = new StringBuilder(""" - WHERE protocol = ? - AND partition_date BETWEEN CAST(? AS DATE) AND CAST(? AS DATE) - """); - if (query.vin() != null) { - where.append(" AND vin = ?\n"); - } - if (query.eventType() != null) { - where.append(" AND event_type = ?\n"); - } - if (query.eventTimeFrom() != null) { - where.append(" AND event_time_ms >= ?\n"); - } - if (query.eventTimeTo() != null) { - where.append(" AND event_time_ms <= ?\n"); - } - String sql = """ - SELECT event_id, protocol, event_type, vin, event_time_ms, ingest_time_ms, - raw_archive_uri, metadata_json, payload_json - FROM event_records - %s - ORDER BY event_time_ms %s, ingest_time_ms %s, event_id %s - LIMIT ? - """.formatted(where, order, order, order); - try (Connection connection = DriverManager.getConnection(jdbcUrl()); - PreparedStatement ps = connection.prepareStatement(sql)) { - bindQuery(ps, query); - try (ResultSet rs = ps.executeQuery()) { - List out = new ArrayList<>(); - while (rs.next()) { - out.add(record(rs)); - } - return out; - } - } catch (SQLException e) { - throw new IOException("query duckdb hot event store failed", e); - } -} -``` - -- [ ] **Step 2: Add query binding helper** - -```java -private static void bindQuery(PreparedStatement ps, EventFileQuery query) throws SQLException { - int index = 1; - ps.setString(index++, query.protocol().name()); - ps.setString(index++, query.dateFrom().toString()); - ps.setString(index++, query.dateTo().toString()); - if (query.vin() != null) { - ps.setString(index++, query.vin()); - } - if (query.eventType() != null) { - ps.setString(index++, query.eventType()); - } - if (query.eventTimeFrom() != null) { - ps.setLong(index++, query.eventTimeFrom().toEpochMilli()); - } - if (query.eventTimeTo() != null) { - ps.setLong(index++, query.eventTimeTo().toEpochMilli()); - } - ps.setInt(index, query.limit()); -} -``` - -- [ ] **Step 3: Implement `findByRawArchiveUri`** - -```java -@Override -public EventFileRecord findByRawArchiveUri(String rawArchiveUri) throws IOException { - if (rawArchiveUri == null || rawArchiveUri.isBlank()) { - return null; - } - ensureInitialized(); - try (Connection connection = DriverManager.getConnection(jdbcUrl()); - PreparedStatement ps = connection.prepareStatement(""" - SELECT event_id, protocol, event_type, vin, event_time_ms, ingest_time_ms, - raw_archive_uri, metadata_json, payload_json - FROM event_records - WHERE raw_archive_uri = ? - ORDER BY event_type = 'RAW_ARCHIVE' DESC, ingest_time_ms DESC, event_id DESC - LIMIT 1 - """)) { - ps.setString(1, rawArchiveUri); - try (ResultSet rs = ps.executeQuery()) { - return rs.next() ? record(rs) : null; - } - } catch (SQLException e) { - throw new IOException("query duckdb hot store by raw archive uri failed", e); - } -} -``` - -- [ ] **Step 4: Add record mapper and helpers** - -```java -private EventFileRecord record(ResultSet rs) throws SQLException, IOException { - return new EventFileRecord( - rs.getString("event_id"), - protocol(rs.getString("protocol")), - rs.getString("event_type"), - rs.getString("vin"), - Instant.ofEpochMilli(rs.getLong("event_time_ms")), - Instant.ofEpochMilli(rs.getLong("ingest_time_ms")), - rs.getString("raw_archive_uri"), - readMetadata(rs.getString("metadata_json")), - rs.getString("payload_json")); -} - -private Map readMetadata(String json) throws IOException { - if (json == null || json.isBlank()) { - return Map.of(); - } - return objectMapper.readValue(json, STRING_MAP); -} - -private static ProtocolId protocol(String value) { - if (value == null || value.isBlank()) { - return ProtocolId.UNKNOWN; - } - try { - return ProtocolId.valueOf(value); - } catch (IllegalArgumentException ex) { - return ProtocolId.UNKNOWN; - } -} - -private String jdbcUrl() { - return "jdbc:duckdb:" + dbPath; -} -``` - -- [ ] **Step 5: Run hot store tests** - -Run: - -```bash -mvn -pl :event-file-store -Dtest=DuckDbHotEventFileStoreTest test -``` - -Expected: all `DuckDbHotEventFileStoreTest` tests pass. - -## Task 4: Preserve Legacy Store Tests and Update Defaults - -**Files:** -- Modify: `modules/sinks/event-file-store/src/test/java/com/lingniu/ingest/eventfilestore/DuckDbParquetEventFileStoreTest.java` -- Modify: `modules/apps/vehicle-history-app/src/main/resources/application.yml` -- Modify: `modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java` - -- [ ] **Step 1: Keep Parquet tests explicitly legacy** - -No behavior change is needed in `DuckDbParquetEventFileStoreTest`; leave it instantiating `DuckDbParquetEventFileStore` directly. Add a class comment: - -```java -/** - * Compatibility coverage for the legacy Parquet sidecar backend. - * Production history uses DuckDbHotEventFileStore through auto-configuration. - */ -class DuckDbParquetEventFileStoreTest { -``` - -- [ ] **Step 2: Set vehicle-history-app storage default** - -Add to `modules/apps/vehicle-history-app/src/main/resources/application.yml`: - -```yaml - event-file-store: - enabled: ${EVENT_FILE_STORE_ENABLED:true} - storage: ${EVENT_FILE_STORE_STORAGE:duckdb-hot} - path: ${EVENT_FILE_STORE_PATH:./target/event-store/} - zone-id: ${EVENT_FILE_STORE_ZONE_ID:Asia/Shanghai} - batch-size: ${EVENT_FILE_STORE_BATCH_SIZE:1000} - flush-interval-millis: ${EVENT_FILE_STORE_FLUSH_INTERVAL_MILLIS:1000} -``` - -Keep existing indentation and only add `storage`; if `batch-size` already exists, update it to `1000`. - -- [ ] **Step 3: Update app default test** - -In `VehicleHistoryAppDefaultsTest`, assert: - -```java -assertThat(context.getEnvironment() - .getProperty("lingniu.ingest.event-file-store.storage")) - .isEqualTo("duckdb-hot"); -``` - -- [ ] **Step 4: Run app default tests** - -Run: - -```bash -mvn -pl :vehicle-history-app -Dtest=VehicleHistoryAppDefaultsTest test -``` - -Expected: default configuration test passes. - -## Task 5: Verify History Ingest Still Archives RAW Bytes - -**Files:** -- Modify: `modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestorTest.java` -- Test: `modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameServiceTest.java` - -- [ ] **Step 1: Add an integration-style test using the hot store** - -Add a test that writes a RAW envelope through `EventHistoryEnvelopeIngestor` into `DuckDbHotEventFileStore`, then finds it by URI. - -```java -@Test -void rawArchiveEnvelopeCanBeFoundFromDuckDbHotStoreByUri(@TempDir Path tempDir) throws Exception { - EventFileStore store = new DuckDbHotEventFileStore(tempDir, ZoneId.of("Asia/Shanghai"), OBJECT_MAPPER); - CapturingArchiveStore archive = new CapturingArchiveStore(); - EventHistoryEnvelopeIngestor ingestor = - new EventHistoryEnvelopeIngestor(store, new TelemetryEnvelopeRecordMapper(), archive); - String rawArchiveKey = "2026/06/23/GB32960/VINRAW001/raw-event-hot.bin"; - String rawArchiveUri = "archive://" + rawArchiveKey; - byte[] rawBytes = new byte[]{0x23, 0x23, 0x02, 0x01}; - - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setSchemaVersion("1.0") - .setEventId("raw-event-hot") - .setVin("VINRAW001") - .setSource("GB32960") - .setProtocolVersion("V2016") - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .putMetadata(RawArchiveKeys.META_KEY, rawArchiveKey) - .putMetadata(RawArchiveKeys.META_URI, rawArchiveUri) - .setRawArchive(RawArchiveRef.newBuilder() - .setUri(rawArchiveUri) - .setSizeBytes(rawBytes.length) - .setData(ByteString.copyFrom(rawBytes)) - .build()) - .build(); - - EnvelopeIngestResult result = ingestor.tryIngest(envelope.toByteArray()); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.STORED); - assertThat(store.findByRawArchiveUri(rawArchiveUri)) - .isNotNull() - .extracting(EventFileRecord::eventId) - .isEqualTo("raw-event-hot"); - assertThat(archive.bytesByKey).containsEntry(rawArchiveKey, rawBytes); -} -``` - -- [ ] **Step 2: Run the event history ingestor test** - -Run: - -```bash -mvn -pl :event-history-service -Dtest=EventHistoryEnvelopeIngestorTest test -``` - -Expected: test passes. - -- [ ] **Step 3: Run decoded frame service tests** - -Run: - -```bash -mvn -pl :event-history-service -Dtest=Gb32960DecodedFrameServiceTest test -``` - -Expected: existing replay/snapshot tests pass. If they use a fake store, no change is needed. - -## Task 6: Full Module Verification - -**Files:** -- No source changes unless failures expose missing imports or config assertions. - -- [ ] **Step 1: Run sink module tests** - -Run: - -```bash -mvn -pl :event-file-store test -``` - -Expected: all event-file-store tests pass, including both hot and legacy stores. - -- [ ] **Step 2: Run event-history-service tests** - -Run: - -```bash -mvn -pl :event-history-service test -``` - -Expected: all event-history-service tests pass. - -- [ ] **Step 3: Package vehicle-history-app** - -Run: - -```bash -mvn -pl :vehicle-history-app -am package -DskipTests -``` - -Expected: package succeeds. - -## Task 7: Local Runtime Verification - -**Files:** -- No committed source changes. - -- [ ] **Step 1: Stop any old history service on port 20200** - -Run: - -```bash -lsof -tiTCP:20200 -sTCP:LISTEN | xargs -r kill -``` - -Expected: no command output, or the old process exits. - -- [ ] **Step 2: Start vehicle-history-app with hot DuckDB store** - -Run: - -```bash -EVENT_FILE_STORE_STORAGE=duckdb-hot \ -EVENT_FILE_STORE_PATH=./target/live-history-event-store \ -SINK_ARCHIVE_PATH=./target/live-history-archive \ -KAFKA_BROKERS=114.55.58.251:9092 \ -KAFKA_CONSUMER_ENABLED=true \ -KAFKA_GROUP_HISTORY=vehicle-history-hot-$(date +%Y%m%d%H%M%S) \ -java --sun-misc-unsafe-memory-access=allow \ - -jar modules/apps/vehicle-history-app/target/vehicle-history-app.jar -``` - -Expected: - -- app starts on `http://127.0.0.1:20200`; -- logs show Kafka consumer subscribed; -- `target/live-history-event-store/events.duckdb` is created. - -- [ ] **Step 3: Verify health** - -Run: - -```bash -curl -sS http://127.0.0.1:20200/actuator/health -``` - -Expected: - -```json -{"status":"UP"} -``` - -- [ ] **Step 4: Verify a recent VIN query** - -Run with a VIN observed in live archive: - -```bash -curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/telemetry-snapshots?vin=LNXNEGRR9SR318194&platformAccount=Hyundai&dateFrom=2026-06-23&dateTo=2026-06-24&order=DESC&limit=3' -``` - -Expected: - -- response is a JSON array; -- if live traffic for that VIN exists after service start, at least one snapshot appears; -- `rawArchiveUris` point to existing files under `target/live-history-archive`. - -- [ ] **Step 5: Verify raw frame replay** - -Use one `rawArchiveUri` from Step 4: - -```bash -curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/frame?rawArchiveUri=archive://REPLACE_ME&platformAccount=Hyundai' -``` - -Expected: - -- response contains `vin`, `command`, `eventTime`, and parsed `blocks`; -- no `raw archive is missing` warning for the selected URI. - -## Self-Review Checklist - -- [ ] The new hot store does not rewrite Parquet files on every append. -- [ ] `appendAll` writes one batch in one transaction. -- [ ] Duplicate `event_id` replay is idempotent. -- [ ] Existing history HTTP APIs still use `EventFileStore`, so controller contracts are unchanged. -- [ ] RAW bytes still land in `ArchiveStore`. -- [ ] `findByRawArchiveUri` is backed by DuckDB index. -- [ ] No MySQL credential, RDS host, username, or password appears in the plan or source files. -- [ ] This plan does not claim the full production goal is complete; it only lands the history foundation. diff --git a/docs/superpowers/plans/2026-06-23-gb32960-service-split.md b/docs/superpowers/plans/2026-06-23-gb32960-service-split.md deleted file mode 100644 index eb1316f3..00000000 --- a/docs/superpowers/plans/2026-06-23-gb32960-service-split.md +++ /dev/null @@ -1,1649 +0,0 @@ -> **Superseded:** This 2026-06-23 service-split plan is historical context. -> Use `docs/target-architecture.md` and -> `docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md` for the -> current production architecture: GB32960, JT808, and Yutong MQTT are the -> active ingest apps, history is backed by TDengine, and Xinda/event-file-store -> paths have been removed from the current build surface. - -# GB32960 Service Split Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Split the current all-in-one GB32960 production path into separate ingest, history, and analytics runnable apps with Kafka as the durability boundary and ACK after Kafka success. - -**Architecture:** Add three Spring Boot app modules while keeping `bootstrap-all` available for development and rollback. First isolate runtime composition, then introduce GB32960-specific Kafka topics, then change the GB32960 ACK path so realtime/report ACKs are written after required Kafka production succeeds. History and analytics become Kafka consumers in separate apps and no longer share a JVM with TCP ingest. - -**Tech Stack:** Java 25, Spring Boot 3.5, Maven multi-module build, Netty, Kafka clients, Protobuf envelope, DuckDB/Parquet event store, local archive sink. - ---- - -## Current-State Notes - -- Existing all-in-one entrypoint: `modules/apps/bootstrap-all/src/main/java/com/lingniu/ingest/bootstrap/IngestApplication.java`. -- Existing all-in-one module at the time of this plan imported protocols, - sinks, history, state, stat, command gateway, MQTT, and JT modules. Xinda was - later removed from source and build configuration. -- Existing GB32960 handler: `modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java`. -- Existing handler currently writes report ACK before `dispatcher.dispatch(rf)`. -- Existing `DisruptorEventBus` publishes to sinks asynchronously and does not wait for sink completion. -- Existing `KafkaEventSink.accepts()` rejects `VehicleEvent.RawArchive`. -- Existing versioned topic names are not yet present; current topic names live under `lingniu.ingest.sink.kafka.topics`. -- Existing history consumer logic: `modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestor.java`. -- Existing analytics consumers: `VehicleStateEnvelopeIngestor` and `VehicleStatEnvelopeIngestor`. -- There is an unrelated working-tree change in `modules/apps/bootstrap-all/src/main/resources/application.yml` for GB32960 diagnostics capacity. Do not revert it. Do not include it in service-split commits unless the user explicitly asks. - -## File Structure - -Create app modules: - -- `modules/apps/gb32960-ingest-app/pom.xml` -- `modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java` -- `modules/apps/gb32960-ingest-app/src/main/resources/application.yml` -- `modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppCompositionTest.java` -- `modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppDefaultsTest.java` - -- `modules/apps/vehicle-history-app/pom.xml` -- `modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java` -- `modules/apps/vehicle-history-app/src/main/resources/application.yml` -- `modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppCompositionTest.java` -- `modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java` - -- `modules/apps/vehicle-analytics-app/pom.xml` -- `modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java` -- `modules/apps/vehicle-analytics-app/src/main/resources/application.yml` -- `modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppCompositionTest.java` -- `modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppDefaultsTest.java` - -Modify shared build and Kafka contract files: - -- `pom.xml` -- `modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java` -- `modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/TopicRouter.java` -- `modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEventSink.java` -- `modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java` -- `modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEventSinkTest.java` - -Modify ACK/Kafka durability boundary files: - -- `modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/sink/EventSink.java` -- `modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/Dispatcher.java` -- `modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/DisruptorEventBus.java` -- `modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java` -- `modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandlerAckBoundaryTest.java` - -Optional later follow-up files after the first split is running: - -- `docs/operations/gb32960-service-split-runbook.md` -- Deployment manifests or service manager files used by the user's environment. - ---- - -### Task 1: Register the Three New App Modules - -**Files:** -- Modify: `pom.xml` -- Create: `modules/apps/gb32960-ingest-app/pom.xml` -- Create: `modules/apps/vehicle-history-app/pom.xml` -- Create: `modules/apps/vehicle-analytics-app/pom.xml` - -- [ ] **Step 1: Add failing module-presence test by running Maven before modules exist** - -Run: - -```bash -export JAVA_HOME=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home -export PATH="$JAVA_HOME/bin:/opt/homebrew/bin:$PATH" -mvn -pl :gb32960-ingest-app -am validate -``` - -Expected: FAIL with a message that Maven cannot find project `:gb32960-ingest-app`. - -- [ ] **Step 2: Add modules to the root reactor** - -In `pom.xml`, add these module entries after `modules/apps/command-gateway` and before `modules/apps/bootstrap-all`: - -```xml - modules/apps/gb32960-ingest-app - modules/apps/vehicle-history-app - modules/apps/vehicle-analytics-app -``` - -Also add dependency management entries near the existing `command-gateway` entry: - -```xml - - com.lingniu.ingest - gb32960-ingest-app - ${project.version} - - - com.lingniu.ingest - vehicle-history-app - ${project.version} - - - com.lingniu.ingest - vehicle-analytics-app - ${project.version} - -``` - -- [ ] **Step 3: Create `gb32960-ingest-app` POM** - -Create `modules/apps/gb32960-ingest-app/pom.xml`: - -```xml - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - gb32960-ingest-app - gb32960-ingest-app - GB32960 protocol ingress runtime: TCP decode, auth, Kafka production, and ACK. - - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - session-core - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - protocol-gb32960 - - - com.lingniu.ingest - sink-kafka - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-test - test - - - - - gb32960-ingest-app - - - org.springframework.boot - spring-boot-maven-plugin - - --sun-misc-unsafe-memory-access=allow - - - - - repackage - - - - - - - -``` - -- [ ] **Step 4: Create `vehicle-history-app` POM** - -Create `modules/apps/vehicle-history-app/pom.xml`: - -```xml - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - vehicle-history-app - vehicle-history-app - Vehicle raw archive, event history, and GB32960 frame query runtime. - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - sink-archive - - - com.lingniu.ingest - event-file-store - - - com.lingniu.ingest - event-history-service - - - com.lingniu.ingest - protocol-gb32960 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - - - vehicle-history-app - - - org.springframework.boot - spring-boot-maven-plugin - - --sun-misc-unsafe-memory-access=allow - - - - - repackage - - - - - - - -``` - -- [ ] **Step 5: Create `vehicle-analytics-app` POM** - -Create `modules/apps/vehicle-analytics-app/pom.xml`: - -```xml - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - vehicle-analytics-app - vehicle-analytics-app - Vehicle state, daily statistics, alarms, and analytics runtime. - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - vehicle-state-service - - - com.lingniu.ingest - vehicle-stat-service - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - - - vehicle-analytics-app - - - org.springframework.boot - spring-boot-maven-plugin - - --sun-misc-unsafe-memory-access=allow - - - - - repackage - - - - - - - -``` - -- [ ] **Step 6: Verify the modules are visible to Maven** - -Run: - -```bash -export JAVA_HOME=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home -export PATH="$JAVA_HOME/bin:/opt/homebrew/bin:$PATH" -mvn -pl :gb32960-ingest-app,:vehicle-history-app,:vehicle-analytics-app -am validate -``` - -Expected: BUILD SUCCESS. - -- [ ] **Step 7: Commit** - -```bash -git add pom.xml modules/apps/gb32960-ingest-app/pom.xml modules/apps/vehicle-history-app/pom.xml modules/apps/vehicle-analytics-app/pom.xml -git commit -m "build: add split service app modules" -``` - ---- - -### Task 2: Add App Entrypoints and Runtime Defaults - -**Files:** -- Create: `modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java` -- Create: `modules/apps/gb32960-ingest-app/src/main/resources/application.yml` -- Create: `modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java` -- Create: `modules/apps/vehicle-history-app/src/main/resources/application.yml` -- Create: `modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java` -- Create: `modules/apps/vehicle-analytics-app/src/main/resources/application.yml` - -- [ ] **Step 1: Create the GB32960 ingest main class** - -Create `modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java`: - -```java -package com.lingniu.ingest.gb32960app; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication(scanBasePackages = "com.lingniu.ingest") -public class Gb32960IngestApplication { - - private static final Logger log = LoggerFactory.getLogger(Gb32960IngestApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(Gb32960IngestApplication.class, args); - } catch (Throwable t) { - log.error("GB32960 ingest application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} -``` - -- [ ] **Step 2: Create the GB32960 ingest config** - -Create `modules/apps/gb32960-ingest-app/src/main/resources/application.yml`: - -```yaml -spring: - application: - name: gb32960-ingest-app - threads: - virtual: - enabled: true - -server: - port: ${HTTP_PORT:20100} - -lingniu: - ingest: - gb32960: - enabled: true - port: ${GB32960_PORT:32960} - boss-threads: ${GB32960_BOSS_THREADS:1} - worker-threads: ${GB32960_WORKER_THREADS:0} - auth: - enabled: ${GB32960_AUTH_ENABLED:false} - case-sensitive: false - whitelist: [] - platforms: - - username: ${GB32960_PLATFORM_USER_HYUNDAI:Hyundai} - password: ${GB32960_PLATFORM_PWD_HYUNDAI:f2e3445d7cda409fb4f278f6fb890734} - allowed-ips: - - ${GB32960_PLATFORM_IP_HYUNDAI:8.134.95.166} - description: 外部下级平台 - Hyundai - tls: - enabled: ${GB32960_TLS_ENABLED:false} - cert-path: ${GB32960_TLS_CERT:} - key-path: ${GB32960_TLS_KEY:} - trust-cert-path: ${GB32960_TLS_CA:} - require-client-auth: true - vendor-extensions: - - name: guangdong-fc - match: - platform-accounts: - - Hyundai - vin-prefixes: [] - vins: [] - parse: - lenient-block-failure: true - diagnostics: - max-logged-frame-keys-per-channel: ${GB32960_DIAGNOSTICS_MAX_LOGGED_FRAME_KEYS_PER_CHANNEL:4096} - pipeline: - disruptor: - ring-buffer-size: ${PIPELINE_RING_BUFFER_SIZE:131072} - wait-strategy: ${PIPELINE_WAIT_STRATEGY:yielding} - producer-type: multi - dedup: - enabled: true - cache-size: 200000 - ttl-seconds: 600 - rate-limit: - per-vin-qps: 50 - session: - store: ${SESSION_STORE:redis} - ttl: ${SESSION_TTL:30m} - identity: - store: ${VEHICLE_IDENTITY_STORE:mysql} - mysql: - jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai} - username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root} - password: ${VEHICLE_IDENTITY_MYSQL_PASSWORD:} - table-name: ${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings} - initialize-schema: ${VEHICLE_IDENTITY_MYSQL_INITIALIZE_SCHEMA:true} - sink: - kafka: - enabled: ${KAFKA_ENABLED:true} - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - compression-type: zstd - linger-ms: 20 - batch-size: 65536 - acks: all - enable-idempotence: true - node-id: ${KAFKA_NODE_ID:gb32960-ingest-local} - topics: - realtime: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - location: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - alarm: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - session: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - media-meta: ${KAFKA_TOPIC_MEDIA_META:vehicle.media.meta.v1} - raw-archive: ${KAFKA_TOPIC_GB32960_RAW:vehicle.raw.gb32960.v1} - dlq: ${KAFKA_TOPIC_GB32960_DLQ:vehicle.dlq.gb32960.v1} - consumer: - enabled: false - event-file-store: - enabled: false - event-history: - enabled: false - vehicle-state: - enabled: false - vehicle-stat: - enabled: false - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus,env - health: - redis: - enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false} - metrics: - tags: - application: gb32960-ingest-app - -logging: - level: - root: INFO - com.lingniu.ingest: INFO -``` - -- [ ] **Step 3: Create the history app main class** - -Create `modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java`: - -```java -package com.lingniu.ingest.historyapp; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication(scanBasePackages = "com.lingniu.ingest") -public class VehicleHistoryApplication { - - private static final Logger log = LoggerFactory.getLogger(VehicleHistoryApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(VehicleHistoryApplication.class, args); - } catch (Throwable t) { - log.error("Vehicle history application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} -``` - -- [ ] **Step 4: Create the history app config** - -Create `modules/apps/vehicle-history-app/src/main/resources/application.yml`: - -```yaml -spring: - application: - name: vehicle-history-app - threads: - virtual: - enabled: true - -server: - port: ${HTTP_PORT:20200} - -lingniu: - ingest: - gb32960: - enabled: false - sink: - kafka: - enabled: ${KAFKA_ENABLED:true} - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - topics: - realtime: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - raw-archive: ${KAFKA_TOPIC_GB32960_RAW:vehicle.raw.gb32960.v1} - dlq: ${KAFKA_TOPIC_GB32960_DLQ:vehicle.dlq.gb32960.v1} - consumer: - enabled: ${KAFKA_CONSUMER_ENABLED:true} - client-id-prefix: ${KAFKA_CONSUMER_CLIENT_ID_PREFIX:vehicle-history} - auto-offset-reset: ${KAFKA_CONSUMER_AUTO_OFFSET_RESET:earliest} - max-poll-records: ${KAFKA_CONSUMER_MAX_POLL_RECORDS:500} - bindings: - eventHistoryEnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_HISTORY:vehicle-history} - topics: - - ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - - ${KAFKA_TOPIC_GB32960_RAW:vehicle.raw.gb32960.v1} - archive: - enabled: ${SINK_ARCHIVE_ENABLED:true} - type: local - path: ${SINK_ARCHIVE_PATH:./archive/} - event-file-store: - enabled: ${EVENT_FILE_STORE_ENABLED:true} - path: ${EVENT_FILE_STORE_PATH:./target/event-store/} - zone-id: ${EVENT_FILE_STORE_ZONE_ID:Asia/Shanghai} - batch-size: ${EVENT_FILE_STORE_BATCH_SIZE:500} - flush-interval-millis: ${EVENT_FILE_STORE_FLUSH_INTERVAL_MILLIS:1000} - event-history: - enabled: true - vehicle-state: - enabled: false - vehicle-stat: - enabled: false - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus,env - health: - redis: - enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false} - metrics: - tags: - application: vehicle-history-app -``` - -- [ ] **Step 5: Create the analytics app main class** - -Create `modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java`: - -```java -package com.lingniu.ingest.analyticsapp; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication(scanBasePackages = "com.lingniu.ingest") -public class VehicleAnalyticsApplication { - - private static final Logger log = LoggerFactory.getLogger(VehicleAnalyticsApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(VehicleAnalyticsApplication.class, args); - } catch (Throwable t) { - log.error("Vehicle analytics application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} -``` - -- [ ] **Step 6: Create the analytics app config** - -Create `modules/apps/vehicle-analytics-app/src/main/resources/application.yml`: - -```yaml -spring: - application: - name: vehicle-analytics-app - threads: - virtual: - enabled: true - -server: - port: ${HTTP_PORT:20300} - -lingniu: - ingest: - gb32960: - enabled: false - sink: - kafka: - enabled: ${KAFKA_ENABLED:true} - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - topics: - realtime: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - dlq: ${KAFKA_TOPIC_GB32960_DLQ:vehicle.dlq.gb32960.v1} - consumer: - enabled: ${KAFKA_CONSUMER_ENABLED:true} - client-id-prefix: ${KAFKA_CONSUMER_CLIENT_ID_PREFIX:vehicle-analytics} - auto-offset-reset: ${KAFKA_CONSUMER_AUTO_OFFSET_RESET:earliest} - max-poll-records: ${KAFKA_CONSUMER_MAX_POLL_RECORDS:500} - bindings: - vehicleStateEnvelopeConsumerProcessor: - enabled: ${VEHICLE_STATE_ENABLED:false} - group-id: ${KAFKA_GROUP_STATE:vehicle-state} - topics: - - ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - vehicleStatEnvelopeConsumerProcessor: - enabled: ${VEHICLE_STAT_ENABLED:true} - group-id: ${KAFKA_GROUP_STAT:vehicle-stat} - topics: - - ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - archive: - enabled: false - event-file-store: - enabled: false - event-history: - enabled: false - vehicle-state: - enabled: ${VEHICLE_STATE_ENABLED:false} - vehicle-stat: - enabled: ${VEHICLE_STAT_ENABLED:true} - file-path: ${VEHICLE_STAT_FILE_PATH:./target/vehicle-stat/} - zone-id: ${VEHICLE_STAT_ZONE_ID:Asia/Shanghai} - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus,env - health: - redis: - enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false} - metrics: - tags: - application: vehicle-analytics-app -``` - -- [ ] **Step 7: Build all three app jars** - -Run: - -```bash -export JAVA_HOME=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home -export PATH="$JAVA_HOME/bin:/opt/homebrew/bin:$PATH" -mvn -pl :gb32960-ingest-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true -``` - -Expected: BUILD SUCCESS and jars under each app module's `target/`. - -- [ ] **Step 8: Commit** - -```bash -git add modules/apps/gb32960-ingest-app modules/apps/vehicle-history-app modules/apps/vehicle-analytics-app -git commit -m "feat: add split service entrypoints" -``` - ---- - -### Task 3: Add Composition Tests for Service Boundaries - -**Files:** -- Create: `modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppCompositionTest.java` -- Create: `modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppCompositionTest.java` -- Create: `modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppCompositionTest.java` - -- [ ] **Step 1: Write GB32960 ingest composition test** - -Create `modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppCompositionTest.java`: - -```java -package com.lingniu.ingest.gb32960app; - -import com.lingniu.ingest.eventfilestore.EventFileStore; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960AutoConfiguration; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.kafka.KafkaEventSink; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960IngestAppCompositionTest { - - @Test - void gb32960IngestRuntimeStartsProtocolAndKafkaWithoutHistoryStorage() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(Gb32960AutoConfiguration.class)) - .withPropertyValues( - "lingniu.ingest.gb32960.enabled=true", - "lingniu.ingest.gb32960.port=0", - "lingniu.ingest.sink.kafka.enabled=false", - "lingniu.ingest.sink.archive.enabled=false", - "lingniu.ingest.event-file-store.enabled=false", - "lingniu.ingest.event-history.enabled=false", - "lingniu.ingest.vehicle-state.enabled=false", - "lingniu.ingest.vehicle-stat.enabled=false") - .run(context -> { - assertThat(context).hasSingleBean(Gb32960NettyServer.class); - assertThat(context).doesNotHaveBean(ArchiveStore.class); - assertThat(context).doesNotHaveBean(EventFileStore.class); - assertThat(context).doesNotHaveBean(KafkaEventSink.class); - }); - } -} -``` - -If this test does not create required supporting beans, add the same auto-config classes used by existing bootstrap tests: `IngestCoreAutoConfiguration`, `SessionCoreAutoConfiguration`, and `VehicleIdentityAutoConfiguration`. - -- [ ] **Step 2: Run GB32960 ingest composition test** - -Run: - -```bash -mvn -pl :gb32960-ingest-app -Dtest=Gb32960IngestAppCompositionTest test -``` - -Expected after adding required auto-config classes: PASS. - -- [ ] **Step 3: Write history composition test** - -Create `modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppCompositionTest.java`: - -```java -package com.lingniu.ingest.historyapp; - -import com.lingniu.ingest.eventfilestore.EventFileStore; -import com.lingniu.ingest.eventhistory.EventHistoryController; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleHistoryAppCompositionTest { - - @TempDir - Path tempDir; - - @Test - void historyRuntimeCreatesStorageAndQueryBeansWithoutProtocolListener() { - new ApplicationContextRunner() - .withPropertyValues( - "lingniu.ingest.gb32960.enabled=false", - "lingniu.ingest.sink.archive.enabled=true", - "lingniu.ingest.sink.archive.path=" + tempDir.resolve("archive"), - "lingniu.ingest.event-file-store.enabled=true", - "lingniu.ingest.event-file-store.path=" + tempDir.resolve("event-store"), - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.vehicle-state.enabled=false", - "lingniu.ingest.vehicle-stat.enabled=false", - "lingniu.ingest.sink.kafka.enabled=false") - .withConfiguration(AutoConfigurations.of( - com.lingniu.ingest.sink.archive.config.SinkArchiveAutoConfiguration.class, - com.lingniu.ingest.eventfilestore.config.EventFileStoreAutoConfiguration.class, - com.lingniu.ingest.eventhistory.config.EventHistoryAutoConfiguration.class)) - .run(context -> { - assertThat(context).hasSingleBean(ArchiveStore.class); - assertThat(context).hasSingleBean(EventFileStore.class); - assertThat(context).hasSingleBean(EventHistoryController.class); - assertThat(context).doesNotHaveBean(Gb32960NettyServer.class); - }); - } -} -``` - -- [ ] **Step 4: Run history composition test** - -Run: - -```bash -mvn -pl :vehicle-history-app -Dtest=VehicleHistoryAppCompositionTest test -``` - -Expected: PASS. - -- [ ] **Step 5: Write analytics composition test** - -Create `modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppCompositionTest.java`: - -```java -package com.lingniu.ingest.analyticsapp; - -import com.lingniu.ingest.eventfilestore.EventFileStore; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer; -import com.lingniu.ingest.vehiclestat.DailyVehicleStatService; -import com.lingniu.ingest.vehiclestate.VehicleStateRepository; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleAnalyticsAppCompositionTest { - - @TempDir - Path tempDir; - - @Test - void analyticsRuntimeCreatesStatisticsWithoutProtocolOrHistoryStorage() { - new ApplicationContextRunner() - .withPropertyValues( - "lingniu.ingest.gb32960.enabled=false", - "lingniu.ingest.sink.archive.enabled=false", - "lingniu.ingest.event-file-store.enabled=false", - "lingniu.ingest.event-history.enabled=false", - "lingniu.ingest.vehicle-state.enabled=false", - "lingniu.ingest.vehicle-stat.enabled=true", - "lingniu.ingest.vehicle-stat.file-path=" + tempDir.resolve("vehicle-stat"), - "lingniu.ingest.sink.kafka.enabled=false") - .withConfiguration(AutoConfigurations.of( - com.lingniu.ingest.vehiclestat.config.VehicleStatAutoConfiguration.class, - com.lingniu.ingest.vehiclestate.config.VehicleStateAutoConfiguration.class)) - .run(context -> { - assertThat(context).hasSingleBean(DailyVehicleStatService.class); - assertThat(context).doesNotHaveBean(VehicleStateRepository.class); - assertThat(context).doesNotHaveBean(Gb32960NettyServer.class); - assertThat(context).doesNotHaveBean(EventFileStore.class); - }); - } -} -``` - -- [ ] **Step 6: Run analytics composition test** - -Run: - -```bash -mvn -pl :vehicle-analytics-app -Dtest=VehicleAnalyticsAppCompositionTest test -``` - -Expected: PASS. - -- [ ] **Step 7: Commit** - -```bash -git add modules/apps/gb32960-ingest-app/src/test modules/apps/vehicle-history-app/src/test modules/apps/vehicle-analytics-app/src/test -git commit -m "test: cover split service composition" -``` - ---- - -### Task 4: Version Kafka Topics for GB32960 Raw/Event/DLQ - -**Files:** -- Modify: `modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java` -- Modify: `modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/TopicRouter.java` -- Modify: `modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEventSink.java` -- Create or modify: `modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java` -- Create or modify: `modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEventSinkTest.java` - -- [ ] **Step 1: Write topic routing tests** - -Create `modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java` if it does not exist. Add tests proving GB32960 raw and normalized events can route to versioned topics. Use the existing `VehicleEvent` constructors from `EventFileStoreSinkTest` and `EnvelopeMapperTelemetrySnapshotTest` as examples. - -Expected test names: - -```java -@Test -void rawArchiveRoutesToVersionedGb32960RawTopic() { - KafkaSinkProperties.Topics topics = new KafkaSinkProperties.Topics(); - topics.setRawArchive("vehicle.raw.gb32960.v1"); - TopicRouter router = new TopicRouter(topics); - - VehicleEvent.RawArchive raw = new VehicleEvent.RawArchive( - "event-1", - "trace-1", - ProtocolId.GB32960, - "VIN001", - Instant.parse("2026-06-23T07:00:00Z"), - 0x02, - new byte[] {0x23, 0x23}, - Map.of("platformAccount", "Hyundai")); - - assertThat(router.route(raw)).isEqualTo("vehicle.raw.gb32960.v1"); -} -``` - -Also add a normalized realtime event test: - -```java -@Test -void realtimeRoutesToVersionedGb32960EventTopic() { - KafkaSinkProperties.Topics topics = new KafkaSinkProperties.Topics(); - topics.setRealtime("vehicle.event.gb32960.v1"); - TopicRouter router = new TopicRouter(topics); - - VehicleEvent.Realtime realtime = new VehicleEvent.Realtime( - "event-2", - "trace-2", - ProtocolId.GB32960, - "VIN001", - Instant.parse("2026-06-23T07:00:01Z"), - 0x02, - Map.of("soc", "80"), - Map.of("platformAccount", "Hyundai")); - - assertThat(router.route(realtime)).isEqualTo("vehicle.event.gb32960.v1"); -} -``` - -Adjust constructor arguments to match the current sealed `VehicleEvent` definitions in `modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEvent.java`. - -- [ ] **Step 2: Run topic router test and verify failure if topic defaults are still old** - -Run: - -```bash -mvn -pl :sink-kafka -Dtest=TopicRouterTest test -``` - -Expected before implementation: compile failure if constructors are wrong or assertion failure if defaults are old. Fix constructors first; keep routing assertions. - -- [ ] **Step 3: Update `KafkaSinkProperties.Topics` defaults** - -In `KafkaSinkProperties.Topics`, change defaults: - -```java -private String realtime = "vehicle.event.gb32960.v1"; -private String location = "vehicle.event.gb32960.v1"; -private String alarm = "vehicle.event.gb32960.v1"; -private String session = "vehicle.event.gb32960.v1"; -private String mediaMeta = "vehicle.media.meta.v1"; -private String rawArchive = "vehicle.raw.gb32960.v1"; -private String dlq = "vehicle.dlq.gb32960.v1"; -``` - -- [ ] **Step 4: Allow raw archive records in KafkaEventSink** - -Change `KafkaEventSink.accepts()` to: - -```java -@Override -public boolean accepts(VehicleEvent event) { - return true; -} -``` - -Update the JavaDoc above it to say Kafka carries both normalized events and raw archive records in split-service mode. - -- [ ] **Step 5: Run sink-kafka tests** - -Run: - -```bash -mvn -pl :sink-kafka test -``` - -Expected: PASS. - -- [ ] **Step 6: Commit** - -```bash -git add modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java \ - modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/TopicRouter.java \ - modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEventSink.java \ - modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java \ - modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEventSinkTest.java -git commit -m "feat: route gb32960 kafka topics" -``` - ---- - -### Task 5: Introduce an ACK-Aware GB32960 Dispatch Boundary - -**Files:** -- Modify: `modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/Dispatcher.java` -- Modify: `modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/DisruptorEventBus.java` -- Modify: `modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java` -- Create: `modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandlerAckBoundaryTest.java` - -Design choice for first implementation: - -- Keep `Dispatcher.dispatch(RawFrame)` for existing async protocols and `bootstrap-all`. -- Add `Dispatcher.dispatchAndAwait(RawFrame, Duration)` returning `CompletableFuture` or throwing a checked runtime exception on timeout/failure. -- Implement `DisruptorEventBus.publishAndAwait(VehicleEvent, Duration)` by synchronously calling accepted sinks' `publish()` futures and waiting for all to complete. -- Use the synchronous path only from GB32960 report/heartbeat/session commands that require ACK after Kafka success. - -- [ ] **Step 1: Write failing ACK boundary test** - -Create `Gb32960ChannelHandlerAckBoundaryTest` using `EmbeddedChannel` and a fake dispatcher that records whether ACK happens before the future completes. - -Core assertion: - -```java -@Test -void realtimeReportAckWaitsForKafkaDurabilityBoundary() { - CompletableFuture durability = new CompletableFuture<>(); - RecordingAckService ackService = new RecordingAckService(); - AwaitingDispatcher dispatcher = new AwaitingDispatcher(durability); - Gb32960ChannelHandler handler = handlerWith(dispatcher, ackService); - - EmbeddedChannel channel = new EmbeddedChannel(handler); - channel.writeInbound(validRealtimeFrameBytes()); - - assertThat(ackService.writtenTags()).isEmpty(); - - durability.complete(null); - channel.runPendingTasks(); - - assertThat(ackService.writtenTags()).contains("report-ack-0x2"); -} -``` - -Implementation notes: - -- Reuse valid frame fixture builders from existing GB32960 codec tests. -- If `Gb32960AckService` is hard to spy because methods are concrete, subclass it in test and override `writeResponse`/`writeAndClose` to record tags instead of writing bytes. -- If `Dispatcher` is not subclassable, extract a small interface such as `FrameDispatcher` in `ingest-core` and adapt `Dispatcher` to implement it. - -- [ ] **Step 2: Run the failing test** - -Run: - -```bash -mvn -pl :protocol-gb32960 -Dtest=Gb32960ChannelHandlerAckBoundaryTest test -``` - -Expected before implementation: FAIL because current handler writes ACK before dispatch completion. - -- [ ] **Step 3: Add await API to event bus** - -In `DisruptorEventBus`, add: - -```java -public CompletableFuture publishAndAwait(VehicleEvent event) { - List> futures = sinks.stream() - .filter(sink -> sink.accepts(event)) - .map(sink -> sink.publish(event)) - .toList(); - if (futures.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); -} -``` - -This requires promoting `sinks` from constructor-local variable to a field: - -```java -private final List sinks; -``` - -Set it in the constructor: - -```java -this.sinks = List.copyOf(sinks); -``` - -- [ ] **Step 4: Add await API to Dispatcher** - -In `Dispatcher`, add: - -```java -public CompletableFuture dispatchAndAwait(RawFrame frame) { - List events = mapFrameToEvents(frame); - CompletableFuture[] futures = events.stream() - .map(eventBus::publishAndAwait) - .toArray(CompletableFuture[]::new); - return CompletableFuture.allOf(futures); -} -``` - -Refactor existing `dispatch(RawFrame)` to reuse `mapFrameToEvents(frame)`: - -```java -public void dispatch(RawFrame frame) { - for (VehicleEvent e : mapFrameToEvents(frame)) { - eventBus.publish(e); - } -} -``` - -If `Dispatcher` currently interleaves raw archive event publication and handler mapping, move that logic into a private method: - -```java -private List mapFrameToEvents(RawFrame frame) { - List events = new ArrayList<>(); - events.add(toRawArchive(frame)); - events.addAll(handlerEvents(frame)); - return events; -} -``` - -Use exact existing helper names from `Dispatcher.java`; do not duplicate mapping logic. - -- [ ] **Step 5: Move GB32960 ACK after dispatch completion** - -In `Gb32960ChannelHandler.channelRead0`, for commands that currently ACK before `dispatcher.dispatch(rf)`, build `RawFrame` first, call `dispatcher.dispatchAndAwait(rf)`, and write ACK in the completion callback: - -```java -dispatcher.dispatchAndAwait(rf).whenComplete((ignored, ex) -> { - if (ex != null) { - log.warn("[gb32960] dispatch failed before ack peer={} vin={} cmd=0x{}", - addr(ctx), vin, Integer.toHexString(cmd.code()), ex); - ctx.close(); - return; - } - writeAckForCommand(ctx, msg, rawVin); -}); -``` - -Extract current ACK logic into command-specific method: - -```java -private void writeAckForCommand(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - switch (msg.header().command()) { - case VEHICLE_LOGIN -> writeVehicleLoginAck(ctx, msg, rawVin); - case VEHICLE_LOGOUT -> writeVehicleLogoutAck(ctx, msg, rawVin); - case PLATFORM_LOGIN -> writePlatformLoginAck(ctx, msg, rawVin); - case PLATFORM_LOGOUT -> writePlatformLogoutAck(ctx, msg, rawVin); - case REALTIME_REPORT, RESEND_REPORT, HEARTBEAT -> writeReportOrHeartbeatAck(ctx, msg, rawVin); - case TIME_CALIBRATION -> writeTimeCalibrationAck(ctx, msg, rawVin); - default -> { } - } -} -``` - -Keep authorization failure ACKs immediate because they are not accepted frames and do not cross the Kafka durability boundary. - -- [ ] **Step 6: Run ACK boundary test** - -Run: - -```bash -mvn -pl :protocol-gb32960 -Dtest=Gb32960ChannelHandlerAckBoundaryTest test -``` - -Expected: PASS. - -- [ ] **Step 7: Run core dispatcher tests** - -Run: - -```bash -mvn -pl :ingest-core test -``` - -Expected: PASS. - -- [ ] **Step 8: Commit** - -```bash -git add modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/Dispatcher.java \ - modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/DisruptorEventBus.java \ - modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java \ - modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandlerAckBoundaryTest.java -git commit -m "feat: ack gb32960 after kafka boundary" -``` - ---- - -### Task 6: Wire History and Analytics Consumer Bindings - -**Files:** -- Modify: `modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestor.java` -- Modify: `modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestor.java` -- Modify: `modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestor.java` -- Modify tests under the same service modules. - -- [ ] **Step 1: Verify event-history accepts raw and normalized envelopes** - -Run: - -```bash -rg -n "RawArchive|raw_archive|TelemetrySnapshot|Envelope" modules/services/event-history-service/src/main/java modules/services/event-history-service/src/test/java -``` - -Expected: identify whether `EventHistoryEnvelopeIngestor` already persists raw archive envelopes. If raw archive envelopes are ignored, continue with Step 2. - -- [ ] **Step 2: Add event-history raw archive ingestion test** - -Add a test to the existing event-history test suite that creates an envelope with `raw_archive` populated and asserts an `EventFileRecord` with `eventType = RAW_ARCHIVE` is written with `rawArchiveUri`. - -Expected assertion shape: - -```java -assertThat(record.eventType()).isEqualTo("RAW_ARCHIVE"); -assertThat(record.rawArchiveUri()).isEqualTo("archive://2026/06/23/GB32960/VIN001/event-1.bin"); -``` - -- [ ] **Step 3: Implement raw archive ingestion if missing** - -In `EventHistoryEnvelopeIngestor`, map raw archive envelopes to `EventFileRecord` using the same conventions as `EventFileStoreSink`. - -Implementation rule: - -- Do not embed raw bytes into Parquet history records. -- Store `rawArchiveUri`, event id, protocol, VIN, command, event time, and metadata. - -- [ ] **Step 4: Verify analytics consumers ignore raw topic records** - -Add or update tests for `VehicleStatEnvelopeIngestor` and `VehicleStateEnvelopeIngestor`: - -```java -@Test -void ignoresRawArchiveEnvelopeForAnalytics() { - ConsumerProcessResult result = ingestor.ingest(rawArchiveEnvelopeRecord()); - assertThat(result.success()).isTrue(); - assertThat(repositoryWrites()).isEmpty(); -} -``` - -Use the actual result type currently returned by the ingestor. - -- [ ] **Step 5: Run service consumer tests** - -Run: - -```bash -mvn -pl :event-history-service,:vehicle-state-service,:vehicle-stat-service test -``` - -Expected: PASS. - -- [ ] **Step 6: Commit** - -```bash -git add modules/services/event-history-service modules/services/vehicle-state-service modules/services/vehicle-stat-service -git commit -m "feat: split history and analytics consumers" -``` - ---- - -### Task 7: Add App Default Tests - -**Files:** -- Create: `modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppDefaultsTest.java` -- Create: `modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java` -- Create: `modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppDefaultsTest.java` - -- [ ] **Step 1: Add YAML property loader helper to each test** - -Use this helper in each defaults test: - -```java -private static Properties loadApplicationProperties() { - YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean(); - yaml.setResources(new ClassPathResource("application.yml")); - Properties props = yaml.getObject(); - assertThat(props).isNotNull(); - return props; -} -``` - -Imports: - -```java -import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; -import org.springframework.core.io.ClassPathResource; -import java.util.Properties; -import static org.assertj.core.api.Assertions.assertThat; -``` - -- [ ] **Step 2: Assert GB32960 ingest defaults** - -Create `Gb32960IngestAppDefaultsTest`: - -```java -@Test -void gb32960IngestDefaultsOnlyEnableProtocolAndKafkaProducer() { - Properties props = loadApplicationProperties(); - - assertThat(props.getProperty("spring.application.name")).isEqualTo("gb32960-ingest-app"); - assertThat(props.getProperty("lingniu.ingest.gb32960.enabled")).isEqualTo("true"); - assertThat(props.getProperty("lingniu.ingest.gb32960.port")).isEqualTo("${GB32960_PORT:32960}"); - assertThat(props.getProperty("lingniu.ingest.sink.kafka.enabled")).isEqualTo("${KAFKA_ENABLED:true}"); - assertThat(props.getProperty("lingniu.ingest.sink.kafka.consumer.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.event-file-store.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.event-history.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.vehicle-state.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.vehicle-stat.enabled")).isEqualTo("false"); -} -``` - -- [ ] **Step 3: Assert history defaults** - -Create `VehicleHistoryAppDefaultsTest`: - -```java -@Test -void historyDefaultsEnableStorageAndHistoryConsumerOnly() { - Properties props = loadApplicationProperties(); - - assertThat(props.getProperty("spring.application.name")).isEqualTo("vehicle-history-app"); - assertThat(props.getProperty("lingniu.ingest.gb32960.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.sink.archive.enabled")).isEqualTo("${SINK_ARCHIVE_ENABLED:true}"); - assertThat(props.getProperty("lingniu.ingest.event-file-store.enabled")).isEqualTo("${EVENT_FILE_STORE_ENABLED:true}"); - assertThat(props.getProperty("lingniu.ingest.event-history.enabled")).isEqualTo("true"); - assertThat(props.getProperty("lingniu.ingest.vehicle-state.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.vehicle-stat.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryEnvelopeConsumerProcessor.group-id")) - .isEqualTo("${KAFKA_GROUP_HISTORY:vehicle-history}"); -} -``` - -- [ ] **Step 4: Assert analytics defaults** - -Create `VehicleAnalyticsAppDefaultsTest`: - -```java -@Test -void analyticsDefaultsEnableStatConsumerAndDisableProtocolAndHistoryStorage() { - Properties props = loadApplicationProperties(); - - assertThat(props.getProperty("spring.application.name")).isEqualTo("vehicle-analytics-app"); - assertThat(props.getProperty("lingniu.ingest.gb32960.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.sink.archive.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.event-file-store.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.event-history.enabled")).isEqualTo("false"); - assertThat(props.getProperty("lingniu.ingest.vehicle-stat.enabled")).isEqualTo("${VEHICLE_STAT_ENABLED:true}"); - assertThat(props.getProperty("lingniu.ingest.sink.kafka.consumer.bindings.vehicleStatEnvelopeConsumerProcessor.group-id")) - .isEqualTo("${KAFKA_GROUP_STAT:vehicle-stat}"); -} -``` - -- [ ] **Step 5: Run app default tests** - -Run: - -```bash -mvn -pl :gb32960-ingest-app,:vehicle-history-app,:vehicle-analytics-app test -``` - -Expected: PASS. - -- [ ] **Step 6: Commit** - -```bash -git add modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppDefaultsTest.java \ - modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java \ - modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppDefaultsTest.java -git commit -m "test: assert split app defaults" -``` - ---- - -### Task 8: End-to-End Verification With Local Kafka - -**Files:** -- Create: `docs/operations/gb32960-service-split-runbook.md` - -- [ ] **Step 1: Build all modules** - -Run: - -```bash -export JAVA_HOME=/opt/homebrew/opt/openjdk@25/libexec/openjdk.jdk/Contents/Home -export PATH="$JAVA_HOME/bin:/opt/homebrew/bin:$PATH" -mvn -pl :gb32960-ingest-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true -``` - -Expected: BUILD SUCCESS. - -- [ ] **Step 2: Start Kafka test environment** - -Use the repository's existing Kafka setup if present. If there is no repository-local Kafka script, use a local Kafka compatible with `KAFKA_BROKERS=127.0.0.1:9092`. - -Create topics: - -```bash -kafka-topics --bootstrap-server 127.0.0.1:9092 --create --if-not-exists --topic vehicle.raw.gb32960.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server 127.0.0.1:9092 --create --if-not-exists --topic vehicle.event.gb32960.v1 --partitions 12 --replication-factor 1 -kafka-topics --bootstrap-server 127.0.0.1:9092 --create --if-not-exists --topic vehicle.dlq.gb32960.v1 --partitions 3 --replication-factor 1 -``` - -- [ ] **Step 3: Start split services on local ports** - -Run each command in a separate terminal: - -```bash -KAFKA_BROKERS=127.0.0.1:9092 \ -GB32960_PORT=32960 \ -HTTP_PORT=20100 \ -java --sun-misc-unsafe-memory-access=allow \ - -jar modules/apps/gb32960-ingest-app/target/gb32960-ingest-app.jar -``` - -```bash -KAFKA_BROKERS=127.0.0.1:9092 \ -HTTP_PORT=20200 \ -EVENT_FILE_STORE_PATH=./target/split-event-store \ -SINK_ARCHIVE_PATH=./target/split-archive \ -java --sun-misc-unsafe-memory-access=allow \ - -jar modules/apps/vehicle-history-app/target/vehicle-history-app.jar -``` - -```bash -KAFKA_BROKERS=127.0.0.1:9092 \ -HTTP_PORT=20300 \ -VEHICLE_STAT_FILE_PATH=./target/split-vehicle-stat \ -java --sun-misc-unsafe-memory-access=allow \ - -jar modules/apps/vehicle-analytics-app/target/vehicle-analytics-app.jar -``` - -- [ ] **Step 4: Verify health endpoints** - -Run: - -```bash -curl -sS http://127.0.0.1:20100/actuator/health -curl -sS http://127.0.0.1:20200/actuator/health -curl -sS http://127.0.0.1:20300/actuator/health -``` - -Expected: each returns `{"status":"UP"}`. - -- [ ] **Step 5: Send a known-valid GB32960 frame** - -Use an existing GB32960 test fixture from `modules/protocols/protocol-gb32960/src/test/java`. If no command-line sender exists, add a small test-only utility or use `nc` with a hex-decoded fixture: - -```bash -xxd -r -p /tmp/gb32960-valid-realtime.hex | nc 127.0.0.1 32960 -``` - -Expected: - -- Kafka topic `vehicle.raw.gb32960.v1` receives one raw record. -- Kafka topic `vehicle.event.gb32960.v1` receives one or more normalized records. -- Client receives a GB32960 success ACK. -- `target/split-archive` contains a `.bin` file. -- `target/split-event-store` contains `events.duckdb` or Parquet files after flush. -- `target/split-vehicle-stat` contains updated stat files after analytics consumes the event. - -- [ ] **Step 6: Write runbook** - -Create `docs/operations/gb32960-service-split-runbook.md` with: - -```markdown -# GB32960 Split Service Runbook - -## Services - -- `gb32960-ingest-app`: TCP 32960, HTTP 20100 -- `vehicle-history-app`: HTTP 20200 -- `vehicle-analytics-app`: HTTP 20300 - -## Kafka Topics - -- `vehicle.raw.gb32960.v1` -- `vehicle.event.gb32960.v1` -- `vehicle.dlq.gb32960.v1` - -## ACK Semantics - -GB32960 success ACK is sent after required Kafka production succeeds. History and analytics failures do not block ACK. - -## Local Start - -Use the commands from Task 8 Step 3. - -## Verification - -Use health endpoints, Kafka topic consumption, archive file count, event-store query APIs, analytics output files, and ACK latency metrics. - -## Rollback - -Stop split services and start `bootstrap-all` with the previous environment. Keep `bootstrap-all` available until split service counts and sample queries match production expectations. -``` - -- [ ] **Step 7: Commit** - -```bash -git add docs/operations/gb32960-service-split-runbook.md -git commit -m "docs: add gb32960 split service runbook" -``` - ---- - -### Task 9: Final Verification and Cutover Checklist - -**Files:** -- Modify: `docs/operations/gb32960-service-split-runbook.md` - -- [ ] **Step 1: Run targeted tests** - -Run: - -```bash -mvn -pl :sink-kafka,:ingest-core,:protocol-gb32960,:event-history-service,:vehicle-state-service,:vehicle-stat-service test -``` - -Expected: BUILD SUCCESS. - -- [ ] **Step 2: Run split app tests** - -Run: - -```bash -mvn -pl :gb32960-ingest-app,:vehicle-history-app,:vehicle-analytics-app test -``` - -Expected: BUILD SUCCESS. - -- [ ] **Step 3: Run package verification** - -Run: - -```bash -mvn -pl :gb32960-ingest-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true -``` - -Expected: BUILD SUCCESS. - -- [ ] **Step 4: Update runbook with measured results** - -Append a section: - -```markdown -## Latest Local Verification - -- Build: -- Unit tests: -- Split app startup: -- Kafka raw records: -- Kafka event records: -- Archive files: -- Event-store files: -- Analytics output: -- ACK behavior: -``` - -Fill the fields with the actual command outputs and counts from the run. - -- [ ] **Step 5: Commit final verification docs** - -```bash -git add docs/operations/gb32960-service-split-runbook.md -git commit -m "docs: record gb32960 split verification" -``` - ---- - -## Self-Review - -Spec coverage: - -- Three services are covered by Tasks 1, 2, 3, and 7. -- Kafka topic and routing contract is covered by Task 4. -- ACK after Kafka success is covered by Task 5. -- History consumption and storage is covered by Task 6 and Task 8. -- Analytics consumption and processing is covered by Task 6 and Task 8. -- `bootstrap-all` remains untouched except for pre-existing unrelated worktree changes. -- Verification and runbook are covered by Tasks 8 and 9. - -Implementation risk notes: - -- Task 5 is the most invasive task because it changes protocol ACK ordering. Implement it behind the smallest possible new API and keep `Dispatcher.dispatch(RawFrame)` behavior for other protocols. -- If Kafka transactions are needed to atomically produce raw and normalized records, add that as a follow-up after the split app boundary is working. The first implementation can use idempotent producer plus shared `eventId`/`sourceRawEventId`. -- If composition tests need additional auto-config classes, copy the exact pattern from `ProtocolAutoConfigurationCompositionTest` rather than widening app dependencies. diff --git a/docs/superpowers/plans/2026-06-29-vehicle-ingest-redesign-phase1.md b/docs/superpowers/plans/2026-06-29-vehicle-ingest-redesign-phase1.md deleted file mode 100644 index 598ab42d..00000000 --- a/docs/superpowers/plans/2026-06-29-vehicle-ingest-redesign-phase1.md +++ /dev/null @@ -1,1519 +0,0 @@ -> **Superseded:** This 2026-06-29 phase-1 implementation plan is historical -> context. Use `docs/target-architecture.md` and -> `docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md` for the -> current production architecture: raw bytes are owned by `sink-archive`, -> history reads come from TDengine, and the former `raw-archive-store` -> prototype has been removed from the current build surface. - -# Vehicle Ingest Redesign Phase 1 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Build the foundation for the new GB32960/JT808 architecture: shared fact model, raw archive contract, and byte-light Kafka schema. - -**Architecture:** Phase 1 adds new modules beside existing runtime paths. It does not change current Netty handlers, ACK behavior, or TDengine writes yet. The output is a tested set of immutable facts and contracts that later phases will wire into protocol apps and the history writer. - -**Tech Stack:** Java 25, Maven, JUnit 5, AssertJ, Protobuf 4, existing `ProtocolId`. - ---- - -## Scope - -This plan implements only the first slice of the redesign: - -- `modules/core/ingest-facts`: protocol-neutral fact records and helpers. -- `modules/sinks/raw-archive-store`: raw archive write/read contract and local URI-safe implementation skeleton. -- `modules/sinks/sink-kafka`: protobuf messages for raw frame facts and decoded facts. -- Parent Maven wiring and focused tests. - -It intentionally does not yet modify: - -- `gb32960-ingest-app` -- `jt808-ingest-app` -- existing `Dispatcher` -- existing `VehicleEvent` -- existing `vehicle-history-app` TDengine writer - -Those changes belong to later implementation plans. - -## File Structure - -Create: - -- `modules/core/ingest-facts/pom.xml`: Maven module for fact model. -- `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/ParseStatus.java`: parse status enum. -- `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/VehicleKey.java`: stable vehicle key derivation. -- `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameFact.java`: raw frame fact record. -- `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/DecodedFact.java`: sealed decoded fact hierarchy. -- `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/FactIds.java`: deterministic id helpers. -- `modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/VehicleKeyTest.java` -- `modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameFactTest.java` -- `modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/FactIdsTest.java` -- `modules/sinks/raw-archive-store/pom.xml`: Maven module for raw archive contracts. -- `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveWriteRequest.java` -- `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveReceipt.java` -- `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveWriter.java` -- `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveReader.java` -- `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/LocalRawArchiveStore.java` -- `modules/sinks/raw-archive-store/src/test/java/com/lingniu/ingest/rawarchive/LocalRawArchiveStoreTest.java` - -Modify: - -- `pom.xml`: add modules and dependency management entries. -- `modules/sinks/sink-kafka/pom.xml`: add dependency on `ingest-facts`. -- `modules/sinks/sink-kafka/src/main/proto/vehicle_envelope.proto`: add `RawFrameFactPayload`, `DecodedFactPayload`, and related enums/messages. -- `modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/VehicleEnvelopeProtoCompatibilityTest.java`: verify new proto messages can round-trip. - ---- - -### Task 1: Add `ingest-facts` Maven Module - -**Files:** - -- Modify: `pom.xml` -- Create: `modules/core/ingest-facts/pom.xml` -- Test later in Task 2 through Task 4. - -- [ ] **Step 1: Add failing Maven target check** - -Run: - -```bash -mvn -pl modules/core/ingest-facts test -``` - -Expected: FAIL because Maven cannot find the selected project. - -- [ ] **Step 2: Add module to root `pom.xml`** - -Add this module after `modules/core/ingest-api`: - -```xml -modules/core/ingest-facts -``` - -Add this dependency management entry after `ingest-api`: - -```xml - - com.lingniu.ingest - ingest-facts - ${project.version} - -``` - -- [ ] **Step 3: Create module POM** - -Create `modules/core/ingest-facts/pom.xml`: - -```xml - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - ingest-facts - ingest-facts - Protocol-neutral raw frame and decoded fact model for vehicle ingestion. - - - - com.lingniu.ingest - ingest-api - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - -``` - -- [ ] **Step 4: Run Maven target check** - -Run: - -```bash -mvn -pl modules/core/ingest-facts test -``` - -Expected: PASS with no tests. - -- [ ] **Step 5: Commit** - -```bash -git add pom.xml modules/core/ingest-facts/pom.xml -git commit -m "build: add ingest facts module" -``` - ---- - -### Task 2: Implement Vehicle Key Derivation - -**Files:** - -- Create: `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/VehicleKey.java` -- Create: `modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/VehicleKeyTest.java` - -- [ ] **Step 1: Write failing tests** - -Create `VehicleKeyTest.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class VehicleKeyTest { - - @Test - void usesVinForGb32960() { - assertThat(VehicleKey.derive(ProtocolId.GB32960, " LB9A32A20P0LS1257 ", "", "abc")) - .isEqualTo("LB9A32A20P0LS1257"); - } - - @Test - void usesResolvedVinForJt808WhenPresent() { - assertThat(VehicleKey.derive(ProtocolId.JT808, "VIN001", "013912345678", "abc")) - .isEqualTo("VIN001"); - } - - @Test - void usesPhoneForJt808WhenVinIsMissing() { - assertThat(VehicleKey.derive(ProtocolId.JT808, "", "013912345678", "abc")) - .isEqualTo("jt808:013912345678"); - } - - @Test - void usesStableUnknownHashWhenVehicleIdentifiersAreMissing() { - assertThat(VehicleKey.derive(ProtocolId.JT808, "", "", "abc")) - .isEqualTo(VehicleKey.derive(ProtocolId.JT808, null, null, "abc")) - .startsWith("unknown:JT808:"); - } - - @Test - void rejectsMissingProtocol() { - assertThatThrownBy(() -> VehicleKey.derive(null, "VIN001", "", "abc")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("protocol"); - } -} -``` - -- [ ] **Step 2: Run test and verify RED** - -Run: - -```bash -mvn -pl modules/core/ingest-facts -Dtest=VehicleKeyTest test -``` - -Expected: FAIL because `VehicleKey` does not exist. - -- [ ] **Step 3: Implement `VehicleKey`** - -Create `VehicleKey.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HexFormat; - -public final class VehicleKey { - - private VehicleKey() { - } - - public static String derive(ProtocolId protocol, String vin, String phone, String rawIdentitySeed) { - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - String normalizedVin = clean(vin); - if (!normalizedVin.isBlank()) { - return normalizedVin; - } - String normalizedPhone = clean(phone); - if (protocol == ProtocolId.JT808 && !normalizedPhone.isBlank()) { - return "jt808:" + normalizedPhone; - } - String seed = clean(rawIdentitySeed); - if (seed.isBlank()) { - seed = protocol.name(); - } - return "unknown:" + protocol.name() + ":" + sha256(seed).substring(0, 16); - } - - private static String clean(String value) { - return value == null ? "" : value.trim(); - } - - private static String sha256(String value) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(digest.digest(value.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is unavailable", e); - } - } -} -``` - -- [ ] **Step 4: Run test and verify GREEN** - -Run: - -```bash -mvn -pl modules/core/ingest-facts -Dtest=VehicleKeyTest test -``` - -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/VehicleKey.java \ - modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/VehicleKeyTest.java -git commit -m "feat: add vehicle key derivation" -``` - ---- - -### Task 3: Implement Raw Frame Fact and Id Helpers - -**Files:** - -- Create: `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/ParseStatus.java` -- Create: `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/FactIds.java` -- Create: `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameFact.java` -- Create: `modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/FactIdsTest.java` -- Create: `modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameFactTest.java` - -- [ ] **Step 1: Write failing tests** - -Create `FactIdsTest.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class FactIdsTest { - - @Test - void rawFrameIdIsStableForSameInputs() { - String first = FactIds.rawFrameId( - ProtocolId.GB32960, - "VIN001", - 2, - 7, - Instant.parse("2026-06-29T00:00:00Z"), - "sha256:abc"); - String second = FactIds.rawFrameId( - ProtocolId.GB32960, - "VIN001", - 2, - 7, - Instant.parse("2026-06-29T00:00:00Z"), - "sha256:abc"); - - assertThat(first).isEqualTo(second).startsWith("rf_"); - } - - @Test - void decodedFactIdIncludesKind() { - String id = FactIds.decodedFactId("rf_123", "location", 0); - - assertThat(id).isEqualTo("df_rf_123_location_0"); - } -} -``` - -Create `RawFrameFactTest.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class RawFrameFactTest { - - @Test - void normalizesOptionalStringsAndMetadata() { - RawFrameFact fact = new RawFrameFact( - "frame-1", - ProtocolId.JT808, - "jt808:013912345678", - null, - "013912345678", - 0x0200, - 0, - null, - Instant.parse("2026-06-29T00:00:00Z"), - null, - "archive://2026/06/29/JT808/jt808-013912345678/frame-1.bin", - "sha256:abc", - 128, - ParseStatus.SUCCEEDED, - null, - Map.of("messageName", "LOCATION")); - - assertThat(fact.vin()).isEmpty(); - assertThat(fact.eventTime()).isEqualTo(fact.receivedAt()); - assertThat(fact.peer()).isEmpty(); - assertThat(fact.parseError()).isEmpty(); - assertThat(fact.metadata()).containsEntry("messageName", "LOCATION"); - } - - @Test - void rejectsMissingRequiredFields() { - assertThatThrownBy(() -> new RawFrameFact( - "", - ProtocolId.GB32960, - "VIN001", - "VIN001", - "", - 2, - 0, - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-29T00:00:00Z"), - "", - "archive://x", - "sha256:abc", - 1, - ParseStatus.SUCCEEDED, - "", - Map.of())) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("frameId"); - } -} -``` - -- [ ] **Step 2: Run tests and verify RED** - -Run: - -```bash -mvn -pl modules/core/ingest-facts -Dtest=FactIdsTest,RawFrameFactTest test -``` - -Expected: FAIL because `FactIds`, `RawFrameFact`, and `ParseStatus` do not exist. - -- [ ] **Step 3: Implement `ParseStatus`** - -Create `ParseStatus.java`: - -```java -package com.lingniu.ingest.facts; - -public enum ParseStatus { - NOT_PARSED, - SUCCEEDED, - FAILED -} -``` - -- [ ] **Step 4: Implement `FactIds`** - -Create `FactIds.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.HexFormat; - -public final class FactIds { - - private FactIds() { - } - - public static String rawFrameId(ProtocolId protocol, - String vehicleKey, - int messageId, - int subType, - Instant receivedAt, - String checksum) { - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - String input = protocol.name() + "|" - + clean(vehicleKey) + "|" - + messageId + "|" - + subType + "|" - + (receivedAt == null ? "" : receivedAt.toString()) + "|" - + clean(checksum); - return "rf_" + sha256(input).substring(0, 32); - } - - public static String decodedFactId(String frameId, String kind, int ordinal) { - return "df_" + clean(frameId) + "_" + clean(kind) + "_" + ordinal; - } - - private static String clean(String value) { - return value == null ? "" : value.trim(); - } - - private static String sha256(String value) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(digest.digest(value.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is unavailable", e); - } - } -} -``` - -- [ ] **Step 5: Implement `RawFrameFact`** - -Create `RawFrameFact.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.Map; - -public record RawFrameFact( - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - int messageId, - int subType, - Instant eventTime, - Instant receivedAt, - String peer, - String rawUri, - String checksum, - long rawSizeBytes, - ParseStatus parseStatus, - String parseError, - Map metadata -) { - public RawFrameFact { - frameId = required(frameId, "frameId"); - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - vehicleKey = required(vehicleKey, "vehicleKey"); - receivedAt = required(receivedAt, "receivedAt"); - eventTime = eventTime == null ? receivedAt : eventTime; - rawUri = required(rawUri, "rawUri"); - checksum = required(checksum, "checksum"); - if (rawSizeBytes < 0) { - throw new IllegalArgumentException("rawSizeBytes must not be negative"); - } - parseStatus = parseStatus == null ? ParseStatus.NOT_PARSED : parseStatus; - vin = clean(vin); - phone = clean(phone); - peer = clean(peer); - parseError = clean(parseError); - metadata = metadata == null ? Map.of() : Map.copyOf(metadata); - } - - private static String required(String value, String field) { - String cleaned = clean(value); - if (cleaned.isBlank()) { - throw new IllegalArgumentException(field + " must not be blank"); - } - return cleaned; - } - - private static Instant required(Instant value, String field) { - if (value == null) { - throw new IllegalArgumentException(field + " must not be null"); - } - return value; - } - - private static String clean(String value) { - return value == null ? "" : value.trim(); - } -} -``` - -- [ ] **Step 6: Run tests and verify GREEN** - -Run: - -```bash -mvn -pl modules/core/ingest-facts -Dtest=FactIdsTest,RawFrameFactTest test -``` - -Expected: PASS. - -- [ ] **Step 7: Commit** - -```bash -git add modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts \ - modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts -git commit -m "feat: add raw frame fact model" -``` - ---- - -### Task 4: Implement Decoded Fact Records - -**Files:** - -- Create: `modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/DecodedFact.java` -- Create: `modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/DecodedFactTest.java` - -- [ ] **Step 1: Write failing test** - -Create `DecodedFactTest.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class DecodedFactTest { - - @Test - void locationFactCarriesRawReferenceAndMetadata() { - DecodedFact.Location location = new DecodedFact.Location( - "fact-1", - "frame-1", - ProtocolId.JT808, - "jt808:013912345678", - "", - "013912345678", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-29T00:00:01Z"), - "archive://jt808/frame-1.bin", - Map.of("messageId", "0x0200"), - 120.123456, - 30.123456, - 10.0, - 42.0, - 180.0, - 0L, - 2L, - 1000.5); - - assertThat(location.factId()).isEqualTo("fact-1"); - assertThat(location.rawUri()).isEqualTo("archive://jt808/frame-1.bin"); - assertThat(location.metadata()).containsEntry("messageId", "0x0200"); - } -} -``` - -- [ ] **Step 2: Run test and verify RED** - -Run: - -```bash -mvn -pl modules/core/ingest-facts -Dtest=DecodedFactTest test -``` - -Expected: FAIL because `DecodedFact` does not exist. - -- [ ] **Step 3: Implement `DecodedFact`** - -Create `DecodedFact.java`: - -```java -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.Map; - -public sealed interface DecodedFact - permits DecodedFact.Location, - DecodedFact.Realtime, - DecodedFact.Alarm, - DecodedFact.Session, - DecodedFact.Register, - DecodedFact.Extension { - - String factId(); - String frameId(); - ProtocolId protocol(); - String vehicleKey(); - String vin(); - String phone(); - Instant eventTime(); - Instant receivedAt(); - String rawUri(); - Map metadata(); - - record Location( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - Double longitude, - Double latitude, - Double altitudeM, - Double speedKmh, - Double directionDeg, - Long alarmFlag, - Long statusFlag, - Double totalMileageKm - ) implements DecodedFact { - public Location { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - metadata = BaseFields.metadata(metadata); - } - } - - record Realtime( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - Map fields - ) implements DecodedFact { - public Realtime { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - record Alarm( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - String alarmLevel, - Long alarmCode, - String alarmName, - Map fields - ) implements DecodedFact { - public Alarm { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - alarmLevel = BaseFields.clean(alarmLevel); - alarmName = BaseFields.clean(alarmName); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - record Session( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - String sessionType, - Integer resultCode, - Map fields - ) implements DecodedFact { - public Session { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - sessionType = BaseFields.clean(sessionType); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - record Register( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - Map registrationFields - ) implements DecodedFact { - public Register { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - metadata = BaseFields.metadata(metadata); - registrationFields = registrationFields == null ? Map.of() : Map.copyOf(registrationFields); - } - } - - record Extension( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - String extensionType, - Map fields - ) implements DecodedFact { - public Extension { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - extensionType = BaseFields.clean(extensionType); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - final class BaseFields { - private BaseFields() { - } - - static void validate(String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - Instant eventTime, - Instant receivedAt, - String rawUri) { - required(factId, "factId"); - required(frameId, "frameId"); - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - required(vehicleKey, "vehicleKey"); - if (eventTime == null) { - throw new IllegalArgumentException("eventTime must not be null"); - } - if (receivedAt == null) { - throw new IllegalArgumentException("receivedAt must not be null"); - } - required(rawUri, "rawUri"); - } - - static String clean(String value) { - return value == null ? "" : value.trim(); - } - - static Map metadata(Map metadata) { - return metadata == null ? Map.of() : Map.copyOf(metadata); - } - - private static void required(String value, String field) { - if (clean(value).isBlank()) { - throw new IllegalArgumentException(field + " must not be blank"); - } - } - } -} -``` - -- [ ] **Step 4: Run test and verify GREEN** - -Run: - -```bash -mvn -pl modules/core/ingest-facts -Dtest=DecodedFactTest test -``` - -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/DecodedFact.java \ - modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/DecodedFactTest.java -git commit -m "feat: add decoded fact model" -``` - ---- - -### Task 5: Add Raw Archive Store Contract and Local Implementation - -**Files:** - -- Modify: `pom.xml` -- Create: `modules/sinks/raw-archive-store/pom.xml` -- Create: `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveWriteRequest.java` -- Create: `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveReceipt.java` -- Create: `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveWriter.java` -- Create: `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/RawArchiveReader.java` -- Create: `modules/sinks/raw-archive-store/src/main/java/com/lingniu/ingest/rawarchive/LocalRawArchiveStore.java` -- Create: `modules/sinks/raw-archive-store/src/test/java/com/lingniu/ingest/rawarchive/LocalRawArchiveStoreTest.java` - -- [ ] **Step 1: Write failing local archive test** - -Create `LocalRawArchiveStoreTest.java`: - -```java -package com.lingniu.ingest.rawarchive; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.nio.file.Path; -import java.time.Instant; -import java.util.HexFormat; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class LocalRawArchiveStoreTest { - - @TempDir - Path tempDir; - - @Test - void writesReadableRawObjectWithArchiveUriAndChecksum() throws Exception { - LocalRawArchiveStore store = new LocalRawArchiveStore(tempDir, "archive://raw"); - byte[] bytes = HexFormat.of().parseHex("7e020000007e"); - - RawArchiveReceipt receipt = store.write(new RawArchiveWriteRequest( - ProtocolId.JT808, - "jt808:013912345678", - "frame-1", - Instant.parse("2026-06-29T00:00:00Z"), - bytes)); - - assertThat(receipt.rawUri()).isEqualTo("archive://raw/2026/06/29/JT808/jt808_013912345678/frame-1.bin"); - assertThat(receipt.checksum()).startsWith("sha256:"); - assertThat(receipt.sizeBytes()).isEqualTo(bytes.length); - assertThat(store.read(receipt.rawUri())).containsExactly(bytes); - } - - @Test - void rejectsBlankFrameId() { - LocalRawArchiveStore store = new LocalRawArchiveStore(tempDir, "archive://raw"); - - assertThatThrownBy(() -> store.write(new RawArchiveWriteRequest( - ProtocolId.GB32960, - "VIN001", - "", - Instant.parse("2026-06-29T00:00:00Z"), - new byte[]{1}))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("frameId"); - } -} -``` - -- [ ] **Step 2: Run test and verify RED** - -Run: - -```bash -mvn -pl modules/sinks/raw-archive-store test -``` - -Expected: FAIL because the module does not exist. - -- [ ] **Step 3: Add module and POM wiring** - -Add this root module after `modules/sinks/sink-archive`: - -```xml -modules/sinks/raw-archive-store -``` - -Add dependency management after `sink-archive`: - -```xml - - com.lingniu.ingest - raw-archive-store - ${project.version} - -``` - -Create `modules/sinks/raw-archive-store/pom.xml`: - -```xml - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - raw-archive-store - raw-archive-store - Raw frame archive writer and reader contracts. - - - - com.lingniu.ingest - ingest-api - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - -``` - -- [ ] **Step 4: Implement contracts** - -Create `RawArchiveWriteRequest.java`: - -```java -package com.lingniu.ingest.rawarchive; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; - -public record RawArchiveWriteRequest( - ProtocolId protocol, - String vehicleKey, - String frameId, - Instant receivedAt, - byte[] rawBytes -) { - public RawArchiveWriteRequest { - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - vehicleKey = required(vehicleKey, "vehicleKey"); - frameId = required(frameId, "frameId"); - if (receivedAt == null) { - throw new IllegalArgumentException("receivedAt must not be null"); - } - if (rawBytes == null || rawBytes.length == 0) { - throw new IllegalArgumentException("rawBytes must not be empty"); - } - rawBytes = rawBytes.clone(); - } - - @Override - public byte[] rawBytes() { - return rawBytes.clone(); - } - - private static String required(String value, String field) { - String cleaned = value == null ? "" : value.trim(); - if (cleaned.isBlank()) { - throw new IllegalArgumentException(field + " must not be blank"); - } - return cleaned; - } -} -``` - -Create `RawArchiveReceipt.java`: - -```java -package com.lingniu.ingest.rawarchive; - -public record RawArchiveReceipt(String rawUri, String checksum, long sizeBytes) { - public RawArchiveReceipt { - rawUri = required(rawUri, "rawUri"); - checksum = required(checksum, "checksum"); - if (sizeBytes < 0) { - throw new IllegalArgumentException("sizeBytes must not be negative"); - } - } - - private static String required(String value, String field) { - String cleaned = value == null ? "" : value.trim(); - if (cleaned.isBlank()) { - throw new IllegalArgumentException(field + " must not be blank"); - } - return cleaned; - } -} -``` - -Create `RawArchiveWriter.java`: - -```java -package com.lingniu.ingest.rawarchive; - -import java.io.IOException; - -public interface RawArchiveWriter { - RawArchiveReceipt write(RawArchiveWriteRequest request) throws IOException; -} -``` - -Create `RawArchiveReader.java`: - -```java -package com.lingniu.ingest.rawarchive; - -import java.io.IOException; - -public interface RawArchiveReader { - byte[] read(String rawUri) throws IOException; -} -``` - -- [ ] **Step 5: Implement local store** - -Create `LocalRawArchiveStore.java`: - -```java -package com.lingniu.ingest.rawarchive; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.ZoneId; -import java.util.HexFormat; - -public final class LocalRawArchiveStore implements RawArchiveWriter, RawArchiveReader { - - private static final ZoneId PARTITION_ZONE = ZoneId.of("Asia/Shanghai"); - - private final Path root; - private final String uriPrefix; - - public LocalRawArchiveStore(Path root, String uriPrefix) { - if (root == null) { - throw new IllegalArgumentException("root must not be null"); - } - this.root = root.toAbsolutePath().normalize(); - String prefix = uriPrefix == null || uriPrefix.isBlank() ? "archive://raw" : uriPrefix.trim(); - this.uriPrefix = prefix.endsWith("/") ? prefix.substring(0, prefix.length() - 1) : prefix; - } - - @Override - public RawArchiveReceipt write(RawArchiveWriteRequest request) throws IOException { - String key = key(request); - Path target = resolveKey(key); - Files.createDirectories(target.getParent()); - rejectSymlinkPath(target); - byte[] bytes = request.rawBytes(); - Files.write(target, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); - return new RawArchiveReceipt(uriPrefix + "/" + key, checksum(bytes), bytes.length); - } - - @Override - public byte[] read(String rawUri) throws IOException { - String key = keyFromUri(rawUri); - Path target = resolveKey(key); - rejectSymlinkPath(target); - return Files.readAllBytes(target); - } - - private String key(RawArchiveWriteRequest request) { - var date = request.receivedAt().atZone(PARTITION_ZONE).toLocalDate(); - return "%04d/%02d/%02d/%s/%s/%s.bin".formatted( - date.getYear(), - date.getMonthValue(), - date.getDayOfMonth(), - request.protocol().name(), - safeSegment(request.vehicleKey()), - safeSegment(request.frameId())); - } - - private String keyFromUri(String rawUri) { - if (rawUri == null || rawUri.isBlank()) { - throw new IllegalArgumentException("rawUri must not be blank"); - } - String normalized = rawUri.trim(); - String expected = uriPrefix + "/"; - if (!normalized.startsWith(expected)) { - throw new IllegalArgumentException("rawUri does not use expected prefix: " + rawUri); - } - return normalized.substring(expected.length()); - } - - private Path resolveKey(String key) { - Path target = root.resolve(key).normalize(); - if (!target.startsWith(root)) { - throw new IllegalArgumentException("raw archive key escapes root: " + key); - } - return target; - } - - private void rejectSymlinkPath(Path target) throws IOException { - Path current = root; - Path relative = root.relativize(target); - for (Path segment : relative) { - current = current.resolve(segment); - if (Files.isSymbolicLink(current)) { - throw new IOException("raw archive path traverses symlink: " + current); - } - if (Files.notExists(current, LinkOption.NOFOLLOW_LINKS)) { - return; - } - } - } - - private static String safeSegment(String value) { - String cleaned = value == null ? "" : value.trim(); - if (cleaned.isBlank()) { - return "unknown"; - } - return cleaned.replaceAll("[^A-Za-z0-9._-]", "_"); - } - - private static String checksum(byte[] bytes) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return "sha256:" + HexFormat.of().formatHex(digest.digest(bytes)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is unavailable", e); - } - } -} -``` - -- [ ] **Step 6: Run tests and verify GREEN** - -Run: - -```bash -mvn -pl modules/sinks/raw-archive-store -Dtest=LocalRawArchiveStoreTest test -``` - -Expected: PASS. - -- [ ] **Step 7: Commit** - -```bash -git add pom.xml modules/sinks/raw-archive-store -git commit -m "feat: add raw archive store contract" -``` - ---- - -### Task 6: Add Kafka Proto Messages for Facts - -**Files:** - -- Modify: `modules/sinks/sink-kafka/pom.xml` -- Modify: `modules/sinks/sink-kafka/src/main/proto/vehicle_envelope.proto` -- Create: `modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/VehicleEnvelopeProtoCompatibilityTest.java` - -- [ ] **Step 1: Write failing proto compatibility test** - -Create `VehicleEnvelopeProtoCompatibilityTest.java`: - -```java -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.sink.kafka.proto.DecodedFactPayload; -import com.lingniu.ingest.sink.kafka.proto.ParseStatusProto; -import com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleEnvelopeProtoCompatibilityTest { - - @Test - void rawFrameFactPayloadRoundTripsThroughEnvelope() throws Exception { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setSchemaVersion("2.0") - .setEventId("frame-1") - .setVin("VIN001") - .setSource("GB32960") - .setEventTimeMs(1782662400000L) - .setIngestTimeMs(1782662400001L) - .setRawFrameFact(RawFrameFactPayload.newBuilder() - .setFrameId("frame-1") - .setVehicleKey("VIN001") - .setMessageId(2) - .setRawUri("archive://raw/2026/06/29/GB32960/VIN001/frame-1.bin") - .setChecksum("sha256:abc") - .setRawSizeBytes(128) - .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED) - .build()) - .build(); - - VehicleEnvelope parsed = VehicleEnvelope.parseFrom(envelope.toByteArray()); - - assertThat(parsed.hasRawFrameFact()).isTrue(); - assertThat(parsed.getRawFrameFact().getFrameId()).isEqualTo("frame-1"); - assertThat(parsed.getRawFrameFact().getParseStatus()).isEqualTo(ParseStatusProto.PARSE_STATUS_SUCCEEDED); - } - - @Test - void decodedFactPayloadRoundTripsThroughEnvelope() throws Exception { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setSchemaVersion("2.0") - .setEventId("fact-1") - .setVin("VIN001") - .setSource("JT808") - .setEventTimeMs(1782662400000L) - .setIngestTimeMs(1782662400001L) - .setDecodedFact(DecodedFactPayload.newBuilder() - .setFactId("fact-1") - .setFrameId("frame-1") - .setFactType("location") - .setVehicleKey("jt808:013912345678") - .setRawUri("archive://raw/2026/06/29/JT808/jt808_013912345678/frame-1.bin") - .putFields("longitude", "120.1") - .putFields("latitude", "30.1") - .build()) - .build(); - - VehicleEnvelope parsed = VehicleEnvelope.parseFrom(envelope.toByteArray()); - - assertThat(parsed.hasDecodedFact()).isTrue(); - assertThat(parsed.getDecodedFact().getFieldsMap()).containsEntry("longitude", "120.1"); - } -} -``` - -- [ ] **Step 2: Run test and verify RED** - -Run: - -```bash -mvn -pl modules/sinks/sink-kafka -Dtest=VehicleEnvelopeProtoCompatibilityTest test -``` - -Expected: FAIL because proto generated classes do not exist. - -- [ ] **Step 3: Add `ingest-facts` dependency to sink-kafka** - -Add this dependency to `modules/sinks/sink-kafka/pom.xml` after `ingest-api`: - -```xml - - com.lingniu.ingest - ingest-facts - -``` - -- [ ] **Step 4: Extend proto schema** - -Modify `vehicle_envelope.proto`. - -Add to `VehicleEnvelope` after `TelemetrySnapshot telemetry_snapshot = 50;`: - -```proto - RawFrameFactPayload raw_frame_fact = 60; - DecodedFactPayload decoded_fact = 61; -``` - -Add these messages after `TelemetryField`: - -```proto -enum ParseStatusProto { - PARSE_STATUS_UNSPECIFIED = 0; - PARSE_STATUS_NOT_PARSED = 1; - PARSE_STATUS_SUCCEEDED = 2; - PARSE_STATUS_FAILED = 3; -} - -message RawFrameFactPayload { - string frame_id = 1; - string vehicle_key = 2; - string vin = 3; - string phone = 4; - int32 message_id = 5; - int32 sub_type = 6; - string raw_uri = 7; - string checksum = 8; - int64 raw_size_bytes = 9; - ParseStatusProto parse_status = 10; - string parse_error = 11; - string peer = 12; - map metadata = 13; -} - -message DecodedFactPayload { - string fact_id = 1; - string frame_id = 2; - string fact_type = 3; - string vehicle_key = 4; - string vin = 5; - string phone = 6; - string raw_uri = 7; - map fields = 8; - map metadata = 9; -} -``` - -- [ ] **Step 5: Run test and verify GREEN** - -Run: - -```bash -mvn -pl modules/sinks/sink-kafka -Dtest=VehicleEnvelopeProtoCompatibilityTest test -``` - -Expected: PASS. - -- [ ] **Step 6: Run existing sink-kafka tests** - -Run: - -```bash -mvn -pl modules/sinks/sink-kafka test -``` - -Expected: PASS. Existing envelope tests must continue to pass because new proto fields are additive. - -- [ ] **Step 7: Commit** - -```bash -git add modules/sinks/sink-kafka/pom.xml \ - modules/sinks/sink-kafka/src/main/proto/vehicle_envelope.proto \ - modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/VehicleEnvelopeProtoCompatibilityTest.java -git commit -m "feat: add fact payloads to kafka envelope" -``` - ---- - -### Task 7: Verify Phase 1 Build - -**Files:** - -- No new files. - -- [ ] **Step 1: Run focused module tests** - -Run: - -```bash -mvn -pl modules/core/ingest-facts,modules/sinks/raw-archive-store,modules/sinks/sink-kafka -am test -``` - -Expected: PASS. - -- [ ] **Step 2: Run dependency tree smoke check** - -Run: - -```bash -mvn -pl modules/sinks/sink-kafka -am -DskipTests package -``` - -Expected: PASS. Generated protobuf Java sources include `RawFrameFactPayload`, `DecodedFactPayload`, and `ParseStatusProto`. - -- [ ] **Step 3: Search for accidental runtime wiring** - -Run: - -```bash -rg -n "RawFrameFact|DecodedFact|RawArchiveWriter|vehicle.raw-frame.v1|vehicle.decoded-fact.v1" \ - modules/apps modules/protocols modules/services modules/core modules/sinks -``` - -Expected: Matches only in the new modules, sink-kafka proto/test, and planned references. No existing handler behavior should be changed in Phase 1. - -- [ ] **Step 4: Commit final verification note if any docs changed** - -If the implementation updated this plan with checked boxes, commit it: - -```bash -git add docs/superpowers/plans/2026-06-29-vehicle-ingest-redesign-phase1.md -git commit -m "docs: update phase 1 implementation checklist" -``` - -If no docs changed, no commit is needed. - ---- - -## Self-Review - -Spec coverage: - -- Raw original frame preservation is covered by `RawFrameFact`, raw archive contracts, and local archive tests. -- Historical query storage is prepared by stable fact model and raw URI references; TDengine writer is intentionally deferred to Phase 3. -- Extension is covered by `DecodedFact.Extension` and byte-light proto maps. -- Performance foundations are covered by immutable facts, Kafka key design, and no handler wiring in Phase 1. - -Placeholder scan: - -- This plan contains no unfinished markers or unspecified implementation steps. - -Type consistency: - -- `RawFrameFact`, `ParseStatus`, `DecodedFact`, `RawArchiveWriteRequest`, `RawArchiveReceipt`, and proto payload names are used consistently across tasks. - -Next plans: - -- Phase 2: wire GB32960/JT808 durable raw boundary and fact publication. -- Phase 3: implement TDengine schema manager and history writer. -- Phase 4: implement history query APIs and raw replay endpoints. diff --git a/docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md b/docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md deleted file mode 100644 index 1cfd6828..00000000 --- a/docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md +++ /dev/null @@ -1,46 +0,0 @@ -# JT808 Kafka Streaming Mileage Plan - -## Current Goal - -Consume JT808 Kafka location events and write the derived daily mileage into the common vehicle-stat metric repository. - -## Current Design - -- Only supported message backbone: Kafka. -- Source topic: `vehicle.event.jt808.v1` -- Runtime app: `vehicle-analytics-app` -- Runtime state: none outside `vehicle_stat_metric` -- Metric output: `VehicleStatRepository.recordDailyMileageSample(...)` -- Production metric storage: JDBC/MySQL `vehicle_stat_metric` -- Date boundary: `Asia/Shanghai` -- Calculation method: `JT808_TOTAL_MILEAGE_DIFF` - -JT808 daily mileage is calculated only from the GPS total mileage reported by JT808 location additional information: - -```text -daily_mileage_km = max_total_mileage_km - min_total_mileage_km -``` - -The first valid local-day sample writes `daily_mileage_km=0.0`. Later ordered or replayed samples update the same metric row with: - -```text -metric_value = max_total_mileage_km - min_total_mileage_km -metric_key = daily_mileage_km -``` - -The local-day minimum and maximum GPS total mileage values stay on that same metric row as calculation source columns so restarts recover from MySQL without Redis or another state table. - -## Runtime Properties - -```text -KAFKA_TOPIC_JT808_EVENT=vehicle.event.jt808.v1 -VEHICLE_STAT_ENABLED=true -VEHICLE_STAT_JT808_MILEAGE_ENABLED=true -MYSQL_JDBC_URL= -MYSQL_USERNAME= -MYSQL_PASSWORD= -``` - -## Notes - -Do not create or write a protocol-specific JT808 daily-mileage table. Do not add another message backbone, distance accumulation, integral calculation, Redis state, or memory state back into this path unless the mileage definition changes again. diff --git a/docs/superpowers/plans/2026-07-01-go-ingest-redesign-phase1.md b/docs/superpowers/plans/2026-07-01-go-ingest-redesign-phase1.md deleted file mode 100644 index 20314115..00000000 --- a/docs/superpowers/plans/2026-07-01-go-ingest-redesign-phase1.md +++ /dev/null @@ -1,720 +0,0 @@ -# Go Vehicle Ingest Redesign Phase 1 Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Build the first production-capable Go runtime for GB32960, JT808, and Yutong MQTT ingestion with unified Kafka, TDengine, MySQL statistics, and Redis realtime state boundaries. - -**Architecture:** Create a new single Go module at `go/vehicle-gateway` and migrate only the useful pieces from the existing `go/ingest-edge` and `go/vehicle-state` prototypes. The gateway produces unified envelopes to Kafka; independent consumers write TDengine history, MySQL daily metrics, and Redis realtime state. - -**Tech Stack:** Go 1.26+, Kafka (`segmentio/kafka-go`), Redis (`redis/go-redis`), MySQL (`go-sql-driver/mysql`), TDengine official Go connector, MQTT (`eclipse/paho.mqtt.golang`), standard library TCP. - ---- - -## File Structure - -- Create: `go/vehicle-gateway/go.mod` -- Create: `go/vehicle-gateway/cmd/gateway/main.go` -- Create: `go/vehicle-gateway/cmd/history-writer/main.go` -- Create: `go/vehicle-gateway/cmd/stat-writer/main.go` -- Create: `go/vehicle-gateway/cmd/realtime-api/main.go` -- Create: `go/vehicle-gateway/internal/envelope/envelope.go` -- Create: `go/vehicle-gateway/internal/envelope/envelope_test.go` -- Create: `go/vehicle-gateway/internal/protocol/jt808/*` -- Create: `go/vehicle-gateway/internal/protocol/gb32960/*` -- Create: `go/vehicle-gateway/internal/protocol/yutongmqtt/*` -- Create: `go/vehicle-gateway/internal/gateway/*` -- Create: `go/vehicle-gateway/internal/identity/*` -- Create: `go/vehicle-gateway/internal/eventbus/*` -- Create: `go/vehicle-gateway/internal/history/*` -- Create: `go/vehicle-gateway/internal/stats/*` -- Create: `go/vehicle-gateway/internal/realtime/*` -- Create: `go/vehicle-gateway/internal/observability/*` -- Modify: `README.md` -- Modify: `docs/target-architecture.md` -- Create: `deploy/portainer/docker-compose-go.yml` - -The existing `go/ingest-edge` and `go/vehicle-state` directories are migration sources only. After phase 1 is verified, remove or mark them superseded. - ---- - -### Task 1: Create Single Go Module - -**Files:** -- Create: `go/vehicle-gateway/go.mod` -- Create: `go/vehicle-gateway/internal/observability/logger.go` -- Create: `go/vehicle-gateway/cmd/gateway/main.go` - -- [ ] **Step 1: Create module manifest** - -Add `go/vehicle-gateway/go.mod`: - -```go -module lingniu-vehicle-ingest/go/vehicle-gateway - -go 1.26 - -require ( - github.com/eclipse/paho.mqtt.golang v1.5.1 - github.com/go-sql-driver/mysql v1.9.3 - github.com/redis/go-redis/v9 v9.17.2 - github.com/segmentio/kafka-go v0.4.49 - github.com/taosdata/driver-go/v3 v3.8.1 -) -``` - -- [ ] **Step 2: Add logger helper** - -Add `go/vehicle-gateway/internal/observability/logger.go`: - -```go -package observability - -import ( - "log/slog" - "os" -) - -func NewLogger(service string) *slog.Logger { - handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: true}) - return slog.New(handler).With("service", service) -} -``` - -- [ ] **Step 3: Add temporary gateway entrypoint** - -Add `go/vehicle-gateway/cmd/gateway/main.go`: - -```go -package main - -import "lingniu-vehicle-ingest/go/vehicle-gateway/internal/observability" - -func main() { - logger := observability.NewLogger("vehicle-gateway") - logger.Info("vehicle gateway scaffold started") -} -``` - -- [ ] **Step 4: Verify scaffold builds** - -Run: - -```bash -cd go/vehicle-gateway -go mod tidy -go test ./... -go build ./cmd/gateway -``` - -Expected: all commands exit `0`. - ---- - -### Task 2: Define Unified Envelope - -**Files:** -- Create: `go/vehicle-gateway/internal/envelope/envelope.go` -- Create: `go/vehicle-gateway/internal/envelope/envelope_test.go` - -- [ ] **Step 1: Write envelope tests** - -Add `go/vehicle-gateway/internal/envelope/envelope_test.go`: - -```go -package envelope - -import "testing" - -func TestFrameEnvelopeVehicleKeyPrefersVIN(t *testing.T) { - e := FrameEnvelope{Protocol: ProtocolJT808, VIN: "LNBVIN00000000001", Phone: "013307795425"} - if got := e.VehicleKey(); got != "LNBVIN00000000001" { - t.Fatalf("VehicleKey() = %q", got) - } -} - -func TestFrameEnvelopeVehicleKeyFallsBackToPhone(t *testing.T) { - e := FrameEnvelope{Protocol: ProtocolJT808, Phone: "013307795425"} - if got := e.VehicleKey(); got != "JT808:013307795425" { - t.Fatalf("VehicleKey() = %q", got) - } -} - -func TestFrameEnvelopeEventIDStable(t *testing.T) { - e := FrameEnvelope{ - Protocol: ProtocolJT808, - MessageID: "0x0200", - Phone: "013307795425", - Sequence: 1, - EventTimeMS: 1782745114000, - ReceivedAtMS: 1782745114999, - RawHex: "7e02000000ff7e", - } - a := e.StableEventID() - b := e.StableEventID() - if a == "" || a != b { - t.Fatalf("event id must be non-empty and stable: %q %q", a, b) - } -} -``` - -- [ ] **Step 2: Run tests and confirm failure** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/envelope -``` - -Expected: fail because `FrameEnvelope` is not defined. - -- [ ] **Step 3: Implement envelope** - -Add `go/vehicle-gateway/internal/envelope/envelope.go`: - -```go -package envelope - -import ( - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "strings" -) - -type Protocol string - -const ( - ProtocolGB32960 Protocol = "GB32960" - ProtocolJT808 Protocol = "JT808" - ProtocolYutongMQTT Protocol = "YUTONG_MQTT" -) - -type ParseStatus string - -const ( - ParseOK ParseStatus = "OK" - ParsePartial ParseStatus = "PARTIAL" - ParseBadFrame ParseStatus = "BAD_FRAME" -) - -type FrameEnvelope struct { - EventID string `json:"event_id"` - TraceID string `json:"trace_id"` - Protocol Protocol `json:"protocol"` - MessageID string `json:"message_id"` - Sequence uint16 `json:"sequence"` - VIN string `json:"vin,omitempty"` - VehicleKeyHint string `json:"vehicle_key,omitempty"` - Phone string `json:"phone,omitempty"` - DeviceID string `json:"device_id,omitempty"` - Plate string `json:"plate,omitempty"` - SourceEndpoint string `json:"source_endpoint,omitempty"` - EventTimeMS int64 `json:"event_time_ms"` - ReceivedAtMS int64 `json:"received_at_ms"` - RawHex string `json:"raw_hex,omitempty"` - RawText string `json:"raw_text,omitempty"` - Parsed map[string]any `json:"parsed,omitempty"` - Fields map[string]any `json:"fields,omitempty"` - ParseStatus ParseStatus `json:"parse_status"` - ParseError string `json:"parse_error,omitempty"` -} - -func (e FrameEnvelope) VehicleKey() string { - if key := strings.TrimSpace(e.VIN); key != "" { - return key - } - if key := strings.TrimSpace(e.VehicleKeyHint); key != "" { - return key - } - if key := strings.TrimSpace(e.Phone); key != "" { - return string(e.Protocol) + ":" + key - } - if key := strings.TrimSpace(e.DeviceID); key != "" { - return string(e.Protocol) + ":" + key - } - return string(e.Protocol) + ":unknown" -} - -func (e FrameEnvelope) StableEventID() string { - if strings.TrimSpace(e.EventID) != "" { - return e.EventID - } - input := fmt.Sprintf("%s|%s|%s|%d|%d|%s", - e.Protocol, e.MessageID, e.VehicleKey(), e.Sequence, e.EventTimeMS, e.RawHex) - sum := sha256.Sum256([]byte(input)) - return hex.EncodeToString(sum[:16]) -} - -func (e FrameEnvelope) MarshalJSONBytes() ([]byte, error) { - if e.EventID == "" { - e.EventID = e.StableEventID() - } - if e.ParseStatus == "" { - e.ParseStatus = ParseOK - } - return json.Marshal(e) -} -``` - -- [ ] **Step 4: Verify tests pass** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/envelope -``` - -Expected: pass. - ---- - -### Task 3: Implement JT808 Frame and 0200 Parser - -**Files:** -- Create: `go/vehicle-gateway/internal/protocol/jt808/parser.go` -- Create: `go/vehicle-gateway/internal/protocol/jt808/parser_test.go` - -- [ ] **Step 1: Add sample frame tests** - -Use the production sample provided in the thread: - -```text -7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F26063016235701040001900C2504000000000202000030011F31010F867E -``` - -Expected assertions: - -- message id is `0x0200` -- phone keeps normalized BCD string `013307795425` -- sequence is `1` -- `fields.total_mileage_km` is parsed from additional item `0x01` -- latitude, longitude, speed, direction, alarm, status, and device time are present - -- [ ] **Step 2: Implement parser** - -Implementation rules: - -- strip `0x7e` start/end delimiters -- unescape `0x7d 0x02 -> 0x7e` -- unescape `0x7d 0x01 -> 0x7d` -- verify XOR checksum -- parse 2011/2013 common header -- parse BCD phone from 6 bytes and keep both raw and normalized values in `parsed.header` -- parse 0200 fixed body -- parse additional item list as `id,length,value_hex` -- parse additional `0x01` as `total_mileage_km = uint32 / 10` - -- [ ] **Step 3: Verify** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/protocol/jt808 -``` - -Expected: pass. - ---- - -### Task 4: Implement GB32960 Frame and Data Unit Parser - -**Files:** -- Create: `go/vehicle-gateway/internal/protocol/gb32960/parser.go` -- Create: `go/vehicle-gateway/internal/protocol/gb32960/parser_test.go` - -- [ ] **Step 1: Add parser tests** - -Tests must cover: - -- `##` frame boundary -- command id -- response flag -- 17-byte VIN -- encryption flag -- payload length -- BCC verification -- realtime data command `0x02` -- reissue data command `0x03` -- data unit `0x01` vehicle status fields -- data unit `0x05` position fields - -- [ ] **Step 2: Implement parser** - -Implementation rules: - -- parse header without allocating large temporary buffers -- keep RAW as hex in envelope -- write all recognized data units to `Parsed` -- write core fields to `Fields` -- unsupported data unit stays in `Parsed["unknown_units"]` - -- [ ] **Step 3: Verify** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/protocol/gb32960 -``` - -Expected: pass. - ---- - -### Task 5: Implement Kafka Sink - -**Files:** -- Create: `go/vehicle-gateway/internal/eventbus/kafka_sink.go` -- Create: `go/vehicle-gateway/internal/eventbus/kafka_sink_test.go` - -- [ ] **Step 1: Add sink interface** - -Create a sink interface: - -```go -package eventbus - -import ( - "context" - "lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope" -) - -type Sink interface { - PublishRaw(context.Context, envelope.FrameEnvelope) error - PublishUnified(context.Context, envelope.FrameEnvelope) error - Close() error -} -``` - -- [ ] **Step 2: Implement Kafka topic routing** - -Rules: - -- `GB32960 -> vehicle.raw.gb32960.v1` -- `JT808 -> vehicle.raw.jt808.v1` -- `YUTONG_MQTT -> vehicle.raw.yutong-mqtt.v1` -- unified topic is `vehicle.event.unified.v1` -- message key is `env.VehicleKey()` - -- [ ] **Step 3: Verify routing with unit tests** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/eventbus -``` - -Expected: pass. - ---- - -### Task 6: Implement TDengine History Writer - -**Files:** -- Create: `go/vehicle-gateway/internal/history/schema.go` -- Create: `go/vehicle-gateway/internal/history/writer.go` -- Create: `go/vehicle-gateway/internal/history/writer_test.go` -- Create: `go/vehicle-gateway/cmd/history-writer/main.go` - -- [ ] **Step 1: Add schema bootstrap SQL** - -Implement schema strings for: - -- database `lingniu_vehicle_ts` -- stable `raw_frames` -- stable `vehicle_locations` -- stable `vehicle_mileage_points` - -- [ ] **Step 2: Implement writer** - -Rules: - -- `AppendRawFrame` always writes one raw row. -- `AppendLocation` writes only when longitude and latitude exist. -- `AppendMileagePoint` writes only when `total_mileage_km` exists. -- child table name is deterministic hash of `protocol + vehicle_key`. -- escape tag values. - -- [ ] **Step 3: Use TDengine official driver** - -Import WebSocket driver in command: - -```go -import _ "github.com/taosdata/driver-go/v3/taosWS" -``` - -Default driver name: - -```text -TDENGINE_DRIVER=taosWS -``` - -Short-term compatibility: - -```text -TDENGINE_DRIVER=taosSql -``` - -Only use `taosSql` when ECS TDengine WebSocket is not available. - -- [ ] **Step 4: Verify** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/history -go build ./cmd/history-writer -``` - -Expected: pass. - ---- - -### Task 7: Implement MySQL Daily Metric Writer - -**Files:** -- Create: `go/vehicle-gateway/internal/stats/schema.go` -- Create: `go/vehicle-gateway/internal/stats/daily_metric.go` -- Create: `go/vehicle-gateway/internal/stats/daily_metric_test.go` -- Create: `go/vehicle-gateway/cmd/stat-writer/main.go` - -- [ ] **Step 1: Add schema bootstrap** - -Implement `vehicle_daily_metric` schema from the design spec. - -- [ ] **Step 2: Add metric derivation tests** - -Test cases: - -- no `total_mileage_km` produces no metric -- one sample produces: - - `daily_mileage_km = 0` - - `daily_total_mileage_km = sample` -- later larger sample updates: - - `latest_total_mileage_km` - - `daily_mileage_km` - - `daily_total_mileage_km` -- out-of-order smaller sample updates: - - `first_total_mileage_km` - - `daily_mileage_km` - -- [ ] **Step 3: Implement MySQL upsert** - -Use one table and one idempotent upsert: - -```sql -INSERT INTO vehicle_daily_metric - (vin, stat_date, protocol, metric_key, metric_value, metric_unit, - first_total_mileage_km, latest_total_mileage_km, sample_count, calculation_method) -VALUES (?, ?, ?, ?, ?, 'km', ?, ?, 1, 'TOTAL_MILEAGE_DIFF') -ON DUPLICATE KEY UPDATE - first_total_mileage_km = LEAST(first_total_mileage_km, VALUES(first_total_mileage_km)), - latest_total_mileage_km = GREATEST(latest_total_mileage_km, VALUES(latest_total_mileage_km)), - metric_value = CASE - WHEN metric_key = 'daily_mileage_km' - THEN GREATEST(latest_total_mileage_km, VALUES(latest_total_mileage_km)) - - LEAST(first_total_mileage_km, VALUES(first_total_mileage_km)) - WHEN metric_key = 'daily_total_mileage_km' - THEN GREATEST(latest_total_mileage_km, VALUES(latest_total_mileage_km)) - ELSE VALUES(metric_value) - END, - sample_count = sample_count + 1, - updated_at = CURRENT_TIMESTAMP -``` - -- [ ] **Step 4: Verify** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/stats -go build ./cmd/stat-writer -``` - -Expected: pass. - ---- - -### Task 8: Implement Redis Realtime API - -**Files:** -- Create: `go/vehicle-gateway/internal/realtime/repository.go` -- Create: `go/vehicle-gateway/internal/realtime/repository_test.go` -- Create: `go/vehicle-gateway/internal/realtime/http.go` -- Create: `go/vehicle-gateway/cmd/realtime-api/main.go` - -- [ ] **Step 1: Add repository contract** - -Methods: - -- `Update(ctx, envelope.FrameEnvelope) error` -- `GetMerged(ctx, vin string) (Snapshot, error)` -- `GetProtocol(ctx, vin string, protocol envelope.Protocol) (Snapshot, error)` -- `IsOnline(ctx, vin string) (OnlineStatus, error)` - -- [ ] **Step 2: Implement merge logic** - -Rules: - -- update `vehicle:latest:{vin}:{protocol}` -- update `vehicle:latest:{vin}` with newest fields -- update `vehicle:online:{vin}` -- update sorted set `vehicle:last_seen` - -- [ ] **Step 3: Implement HTTP API** - -Routes: - -- `GET /api/realtime/vehicles/{vin}` -- `GET /api/realtime/vehicles/{vin}/online` -- `GET /api/realtime/vehicles/{vin}/protocols/{protocol}` - -- [ ] **Step 4: Verify** - -Run: - -```bash -cd go/vehicle-gateway -go test ./internal/realtime -go build ./cmd/realtime-api -``` - -Expected: pass. - ---- - -### Task 9: Wire Gateway Runtime - -**Files:** -- Create: `go/vehicle-gateway/internal/gateway/tcp_server.go` -- Create: `go/vehicle-gateway/internal/gateway/mqtt_client.go` -- Modify: `go/vehicle-gateway/cmd/gateway/main.go` - -- [ ] **Step 1: Implement TCP server** - -Rules: - -- one goroutine per accepted connection -- bounded max connections -- read timeout and idle timeout -- protocol-specific frame extractor -- structured peer endpoint -- graceful shutdown on SIGTERM - -- [ ] **Step 2: Implement MQTT client** - -Rules: - -- connect with official production config from environment -- subscribe configured topic list -- convert each message into envelope -- publish raw and unified events -- reconnect with backoff - -- [ ] **Step 3: Verify local JSON mode** - -Run without Kafka: - -```bash -cd go/vehicle-gateway -GB32960_TCP_ADDR=:132960 JT808_TCP_ADDR=:18080 go run ./cmd/gateway -``` - -Expected: service starts and logs configured listeners. - ---- - -### Task 10: Docker and ECS Deployment - -**Files:** -- Create: `go/vehicle-gateway/Dockerfile` -- Create: `deploy/portainer/docker-compose-go.yml` -- Modify: `docs/operations/current-ecs-deployment.md` - -- [ ] **Step 1: Add multi-stage Dockerfile** - -Build all commands: - -- `gateway` -- `history-writer` -- `stat-writer` -- `realtime-api` - -- [ ] **Step 2: Add Portainer compose** - -Services: - -- `go-vehicle-gateway` -- `go-history-writer` -- `go-stat-writer` -- `go-realtime-api` - -Each service must include: - -- restart policy -- memory limit -- Kafka env -- MySQL/TDengine/Redis env as needed -- logging options - -- [ ] **Step 3: Verify Linux build** - -Run: - -```bash -cd go/vehicle-gateway -GOOS=linux GOARCH=amd64 go build ./cmd/gateway -GOOS=linux GOARCH=amd64 go build ./cmd/history-writer -GOOS=linux GOARCH=amd64 go build ./cmd/stat-writer -GOOS=linux GOARCH=amd64 go build ./cmd/realtime-api -``` - -Expected: all commands exit `0`. - ---- - -### Task 11: Production Verification - -**Files:** -- Create: `docs/operations/go-vehicle-gateway-verification.md` - -- [ ] **Step 1: Record test commands** - -Document commands to verify: - -- gateway process health -- Kafka topic consumption -- TDengine row counts -- MySQL daily metric rows -- Redis realtime lookup - -- [ ] **Step 2: Validate real traffic** - -Evidence required: - -- one real 32960 VIN with RAW, location, mileage point, daily metric, Redis snapshot -- one real JT808 phone/VIN with RAW, location, mileage point, daily metric, Redis snapshot -- one real Yutong MQTT VIN with RAW and Redis snapshot - -- [ ] **Step 3: Keep old Java services until evidence is captured** - -Only disable Java equivalents after the evidence file contains successful command outputs and timestamps. - ---- - -## Self-Review Checklist - -- The plan creates one new Go module instead of extending scattered prototypes. -- The plan covers GB32960, JT808, and Yutong MQTT ingress. -- The plan covers Kafka, TDengine, MySQL, and Redis. -- The plan includes 32960 and 808 daily mileage and daily total mileage. -- The plan includes local and ECS verification. -- The plan does not restore Xinda Push. -- The plan keeps Java services untouched until Go evidence exists. diff --git a/docs/superpowers/specs/2026-06-23-gb32960-production-readiness-design.md b/docs/superpowers/specs/2026-06-23-gb32960-production-readiness-design.md deleted file mode 100644 index 0f023d5f..00000000 --- a/docs/superpowers/specs/2026-06-23-gb32960-production-readiness-design.md +++ /dev/null @@ -1,481 +0,0 @@ -> **Superseded:** This 2026-06-23 DuckDB-based design is historical context. -> Use `docs/target-architecture.md` and -> `docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md` for the -> current production architecture: TDengine is the default hot history store, -> `event-file-store` and Xinda Push have been removed from the production codebase. - -# GB32960 Production Readiness Design - -## Goal - -Make the GB32960 ingestion, history, and analytics services production-ready: - -- receive and parse GB32960 traffic quickly and accurately; -- maximize VIN-to-platform/vendor-profile association through platform login and local mappings; -- store complete RAW frames and queryable telemetry history with DuckDB; -- support full frame replay from persisted RAW bytes; -- compute real-time daily vehicle metrics and alarm timelines into MySQL; -- reduce service coupling and improve cohesion inside each module. - -## Non-Goals - -- Do not put database credentials in code, YAML files, tests, docs, or commits. -- Do not make MySQL the source for full telemetry history. MySQL is for derived daily metrics and alarm timelines. -- Do not require every private GB32960 extension to be perfect before storing data. RAW bytes must always be kept when a valid frame is received. - -## Production Architecture - -```mermaid -flowchart LR - tcp["GB32960 TCP:32960"] --> ingest["gb32960-ingest-app"] - mapping["local vin-platform-profile.jsonl"] --> ingest - ingest --> kafkaEvent["Kafka vehicle.event.gb32960.v1"] - ingest --> kafkaRaw["Kafka vehicle.raw.gb32960.v1"] - kafkaEvent --> history["vehicle-history-app"] - kafkaRaw --> history - history --> duckdb["DuckDB hot history"] - history --> raw["RAW frame archive"] - kafkaEvent --> analytics["vehicle-analytics-app"] - analytics --> mysql["MySQL daily stats + alarms"] -``` - -## Service Boundaries - -### 1. `gb32960-ingest-app` - -Responsibilities: - -- Netty TCP accept, frame splitting, GB32960 decode, auth, ACK, and Kafka production. -- Platform login handling and connection-local `platformAccount`. -- VIN-to-platform/vendor-profile resolution. -- Produce structured telemetry, session, alarm, diagnostics, and RAW envelope messages. - -It must not: - -- write DuckDB, MySQL, or local archive files; -- run historical queries; -- run daily statistics. - -ACK policy: - -- login/report-like GB32960 commands use durable ACK: ACK only after Kafka dispatch succeeds; -- malformed bytes that cannot form a valid frame are dropped with diagnostics; -- valid frames with partial private-block parse failures still go to Kafka with `parseStatus=PARTIAL`. - -VIN/profile resolution priority: - -1. active platform login on the current TCP channel; -2. exact VIN entry from local mapping file; -3. VIN prefix entry from local mapping file; -4. configured platform extension rule; -5. standard GB32960 profile. - -Local mapping file format: - -```jsonl -{"vin":"LNXNEGRR2SR321390","platformAccount":"Hyundai","vendorProfile":"guangdong-fc","enabled":true} -{"vinPrefix":"LNXNEGRR","platformAccount":"Hyundai","vendorProfile":"guangdong-fc","enabled":true} -``` - -The mapping file is loaded at startup and can be reloaded by an actuator endpoint or file watcher in a later phase. - -### 2. `vehicle-history-app` - -Responsibilities: - -- Consume Kafka event and RAW topics. -- Persist RAW bytes to an append-only archive. -- Persist query indexes and compact telemetry points to DuckDB. -- Serve high-performance query APIs: - - full frame list by `vin + eventTime`; - - telemetry fields by `vin + eventTime`; - - one-frame replay by `rawArchiveUri`; - - parse diagnostics and missing-profile troubleshooting. - -It must not: - -- listen on GB32960 TCP; -- run MySQL statistics; -- mutate daily business metrics. - -### 3. `vehicle-analytics-app` - -Responsibilities: - -- Consume Kafka event topic. -- Maintain real-time daily stats in MySQL. -- Maintain full alarm timelines in MySQL. -- Expose metrics query APIs and operational health. - -It must not: - -- parse RAW frames for normal operation; -- write DuckDB history; -- own GB32960 TCP connectivity. - -## Kafka Contract - -Keep Protobuf `VehicleEnvelope`, but make the producer contract stricter: - -- `eventId`: stable idempotency key, deterministic from protocol, VIN, command, event time, raw checksum, and sequence when available. -- `vin`: required for vehicle frames; platform-only commands use `_platform` or empty plus `platformAccount`. -- `eventTimeMs`: GB32960 data collection time when available. -- `ingestTimeMs`: service receive time. -- `metadata.platformAccount`: resolved platform account. -- `metadata.vendorProfile`: selected parser profile. -- `metadata.parseStatus`: `OK`, `PARTIAL`, or `FAILED`. -- `metadata.parseErrorCode`: stable code for failures. -- `raw_archive.data`: present on RAW topic only; event topic keeps `rawArchiveUri` pointer. -- `telemetry_snapshot`: standard field projection for downstream services. - -Consumer rule: - -- History consumes both event and RAW topics. -- Analytics consumes only event topic. -- Consumer offset is committed only after the target store commit succeeds. -- Reprocessing must be safe through deterministic ids and database upsert/ignore semantics. - -## DuckDB History Design - -Use DuckDB as the hot historical database, not a Parquet-rewrite sidecar. - -DuckDB official guidance says row-by-row insert loops are inefficient for bulk loading, and Appender/batched writes are the intended high-throughput path. Therefore history writes use a single writer worker with bounded batches and one DuckDB connection. - -### Tables - -`gb32960_frame_index` - -```sql -CREATE TABLE IF NOT EXISTS gb32960_frame_index ( - event_id VARCHAR PRIMARY KEY, - vin VARCHAR NOT NULL, - platform_account VARCHAR, - vendor_profile VARCHAR, - command VARCHAR NOT NULL, - command_code INTEGER NOT NULL, - event_time TIMESTAMP NOT NULL, - event_time_ms BIGINT NOT NULL, - ingest_time TIMESTAMP NOT NULL, - ingest_time_ms BIGINT NOT NULL, - partition_date DATE NOT NULL, - raw_archive_uri VARCHAR NOT NULL, - raw_checksum VARCHAR, - raw_size_bytes BIGINT NOT NULL, - parse_status VARCHAR NOT NULL, - parse_error_code VARCHAR, - block_count INTEGER NOT NULL, - metadata_json VARCHAR NOT NULL -); - -CREATE INDEX IF NOT EXISTS gb32960_frame_vin_time_idx - ON gb32960_frame_index(vin, event_time_ms); - -CREATE INDEX IF NOT EXISTS gb32960_frame_raw_uri_idx - ON gb32960_frame_index(raw_archive_uri); - -CREATE INDEX IF NOT EXISTS gb32960_frame_partition_time_idx - ON gb32960_frame_index(partition_date, event_time_ms); -``` - -`gb32960_telemetry_point` - -```sql -CREATE TABLE IF NOT EXISTS gb32960_telemetry_point ( - event_id VARCHAR NOT NULL, - vin VARCHAR NOT NULL, - event_time TIMESTAMP NOT NULL, - event_time_ms BIGINT NOT NULL, - ingest_time TIMESTAMP NOT NULL, - platform_account VARCHAR, - vendor_profile VARCHAR, - field_key VARCHAR NOT NULL, - value_type VARCHAR NOT NULL, - value_text VARCHAR NOT NULL, - value_num DOUBLE, - unit VARCHAR, - quality VARCHAR NOT NULL, - raw_archive_uri VARCHAR NOT NULL, - PRIMARY KEY(event_id, field_key) -); - -CREATE INDEX IF NOT EXISTS gb32960_point_vin_field_time_idx - ON gb32960_telemetry_point(vin, field_key, event_time_ms); -``` - -`gb32960_alarm_frame` - -```sql -CREATE TABLE IF NOT EXISTS gb32960_alarm_frame ( - event_id VARCHAR PRIMARY KEY, - vin VARCHAR NOT NULL, - event_time TIMESTAMP NOT NULL, - event_time_ms BIGINT NOT NULL, - level VARCHAR NOT NULL, - active_bits_json VARCHAR NOT NULL, - fault_codes_json VARCHAR NOT NULL, - raw_archive_uri VARCHAR NOT NULL -); - -CREATE INDEX IF NOT EXISTS gb32960_alarm_vin_time_idx - ON gb32960_alarm_frame(vin, event_time_ms); -``` - -### RAW Archive - -RAW archive remains file/object based: - -```text -archive://yyyy/MM/dd/GB32960/{vin}/{event_id}.bin -``` - -The archive writer verifies: - -- stored byte length equals envelope `raw_archive.size_bytes`; -- checksum matches; -- URI is unique for the deterministic event id. - -Frame replay path: - -1. query `gb32960_frame_index` by `rawArchiveUri`; -2. read RAW bytes from archive; -3. resolve `platformAccount/vendorProfile` from index metadata; -4. decode using current parser; -5. return decoded frame plus original indexed parse diagnostics. - -## Query APIs - -Keep existing endpoints but back them by the new DuckDB tables: - -- `GET /api/event-history/gb32960/frames` - - full frame query by VIN/time. -- `GET /api/event-history/gb32960/frame` - - one RAW frame replay by `rawArchiveUri`. -- `GET /api/event-history/gb32960/telemetry-snapshots` - - compact telemetry snapshot by VIN/time. -- `GET /api/event-history/gb32960/telemetry-fields` - - field projection optimized by `gb32960_telemetry_point`. -- `GET /api/event-history/gb32960/diagnostics` - - parse status and missing-profile investigation by VIN/time. - -Time semantics: - -- `eventTime` is business query time. -- `ingestTime` is kept for operational replay, late-arrival detection, and Kafka lag investigation. -- Dates without timezone are interpreted in `Asia/Shanghai`. - -## MySQL Analytics Design - -Credentials are supplied only by environment variables: - -```text -MYSQL_HOST -MYSQL_PORT -MYSQL_DATABASE -MYSQL_USERNAME -MYSQL_PASSWORD -``` - -Daily derived metrics are stored in the common metric table, not in a -protocol-specific daily-mileage table. - -```sql -CREATE TABLE IF NOT EXISTS vehicle_stat_metric ( - vin VARCHAR(64) NOT NULL, - stat_date DATE NOT NULL, - metric_key VARCHAR(64) NOT NULL, - metric_value DECIMAL(18,6) NULL, - metric_unit VARCHAR(16) NOT NULL, - calculation_method VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (vin, stat_date, metric_key) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -``` - -`vehicle_alarm_timeline` - -```sql -CREATE TABLE IF NOT EXISTS vehicle_alarm_timeline ( - alarm_id VARCHAR(128) NOT NULL, - vin VARCHAR(32) NOT NULL, - alarm_key VARCHAR(128) NOT NULL, - level VARCHAR(32) NOT NULL, - start_time DATETIME(3) NOT NULL, - end_time DATETIME(3), - last_seen_time DATETIME(3) NOT NULL, - start_raw_archive_uri VARCHAR(512), - last_raw_archive_uri VARCHAR(512), - status VARCHAR(16) NOT NULL, - details_json JSON, - updated_at DATETIME(3) NOT NULL, - PRIMARY KEY (alarm_id), - KEY vehicle_alarm_vin_time_idx (vin, start_time), - KEY vehicle_alarm_open_idx (vin, status, alarm_key) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -``` - -`vehicle_stat_event_dedup` - -```sql -CREATE TABLE IF NOT EXISTS vehicle_stat_event_dedup ( - event_id VARCHAR(128) NOT NULL, - consumer_group VARCHAR(128) NOT NULL, - processed_at DATETIME(3) NOT NULL, - PRIMARY KEY (event_id, consumer_group) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -``` - -Writes use `INSERT ... ON DUPLICATE KEY UPDATE` so replayed Kafka messages update the same daily row instead of creating duplicates. - -## Daily Metric Algorithms - -All daily statistics are keyed by `eventTime` in `Asia/Shanghai`. - -### Total Mileage - -Use the latest valid `totalMileageKm` seen for the day. - -Validation: - -- reject negative values; -- reject impossible jumps based on configurable max speed and elapsed time; -- keep the sample as diagnostic if rejected. - -### Daily Mileage - -JT808 daily mileage uses the GPS total mileage reported in location additional -information. The local-day minimum and maximum GPS total mileage values rewrite -the same metric row, so ordered streaming and historical replay use one -calculation path. - -```text -dailyMileage = today.maxTotalMileage - today.minTotalMileage -calculationMethod = JT808_TOTAL_MILEAGE_DIFF -``` - -The result is stored in `vehicle_stat_metric` with -`metric_key=daily_mileage_km`. There is no JT808-specific daily mileage table, -no Redis mileage state, and no previous-day baseline calculation. - -### Daily Power Consumption - -Preferred: - -- vendor cumulative power-consumption field if available. - -Fallback: - -```text -deltaKwh = sum(max(0, totalVoltageV * totalCurrentA) * deltaSeconds / 3600000) -``` - -The sign convention is configurable per vendor profile because GB32960 vehicle current may be positive or negative depending on charging/discharging representation. - -### Daily Hydrogen Consumption - -Use a tiered market-practical algorithm instead of a single fixed formula: - -1. `DIRECT_COUNTER`: vendor cumulative hydrogen consumption or remaining hydrogen mass delta. -2. `PVT_MASS_DELTA`: pressure/temperature/tank-volume mass estimate when storage data is available. -3. `FUEL_CELL_ENERGY_MODEL`: integrate fuel-cell electric output and divide by calibrated efficiency. -4. `UNKNOWN`: keep null when required signals are missing. - -The PVT approach follows the industry pattern behind compressed hydrogen consumption tests, where pressure, temperature, and tank volume can be used to estimate hydrogen mass. The implementation must expose calibration constants per vehicle model/profile: - -```json -{ - "vinPrefix": "LNXNEGRR", - "tankVolumeLiter": 140, - "hydrogenModel": "PVT_MASS_DELTA", - "fuelCellEfficiency": 0.52, - "currentDischargeSign": "POSITIVE" -} -``` - -### Daily Hydrogen Consumption Rate - -```text -dailyHydrogenKgPer100km = dailyHydrogenKg / dailyMileageKm * 100 -``` - -If mileage is missing or less than the configured minimum distance, keep the rate null and set quality to `INSUFFICIENT_DISTANCE`. - -### Alarm Timeline - -For each telemetry frame: - -- derive active alarm keys from alarm bits and fault code arrays; -- open a timeline row when a key first appears; -- update `last_seen_time` while it remains active; -- close open alarms when the key disappears after a configurable grace window; -- keep start/end raw archive URIs for replay. - -## Reliability and Observability - -Required metrics: - -- TCP active connections; -- frames received per command; -- decode failures by code; -- missing platform login rejections; -- vendor profile resolution source; -- Kafka send latency and failures; -- history write batch size, latency, failure count; -- DuckDB query latency by endpoint; -- MySQL upsert latency and failure count; -- consumer lag by group/topic/partition; -- late sample count by VIN/date. - -Required logs: - -- one structured line for every dropped or rejected valid frame; -- no per-frame INFO spam for successful reports after first frame per VIN/profile unless debug is enabled; -- include `vin`, `platformAccount`, `command`, `eventId`, `reasonCode`, and `peer`. - -## Migration Plan - -Phase 1: History foundation - -- Replace Parquet rewrite store with DuckDB hot tables and batch writer. -- Keep RAW bytes archive. -- Move existing history endpoints onto the new store. -- Add diagnostic endpoint for missing VIN/profile cases. - -Phase 2: Ingest association and diagnostics - -- Add local `vin-platform-profile.jsonl` loader. -- Use resolved profile before private block parsing. -- Add structured parse/reject diagnostics to Kafka metadata and logs. -- Add tests for platform login, mapping fallback, and partial private parsing. - -Phase 3: MySQL analytics - -- Add MySQL repository and migrations. -- Implement daily stat accumulator with idempotent upsert. -- Implement alarm timeline open/update/close logic. -- Add integration tests using Testcontainers or a local MySQL profile. - -Phase 4: Production hardening - -- Add backfill/replay commands from DuckDB RAW archive to analytics. -- Add retention/export from DuckDB to Parquet for cold storage. -- Add dashboards and operational runbook. - -## Acceptance Criteria - -- A valid GB32960 frame received on port 32960 appears in Kafka with RAW bytes or RAW URI. -- A valid frame is queryable by `vin + eventTime` from history. -- A returned frame can be replayed from `rawArchiveUri` and decoded with the stored profile. -- A VIN with only local mapping, and no current platform login, can still use the correct private parser profile. -- A private block parse failure does not drop the frame. -- Daily MySQL rows update in real time for mileage, total mileage, power consumption, hydrogen consumption, hydrogen rate, and sample metadata. -- Alarm timeline rows preserve full alarm start, update, and close history. -- Replaying the same Kafka event does not duplicate daily stats or alarm rows. -- No secret appears in committed files. - -## References - -- DuckDB Appender: https://duckdb.org/docs/current/data/appender -- DuckDB INSERT performance guidance: https://duckdb.org/docs/current/data/insert -- MySQL `INSERT ... ON DUPLICATE KEY UPDATE`: https://dev.mysql.com/doc/refman/9.7/en/insert-on-duplicate.html -- SAE J2572 overview: https://h2tools.org/fuel-cell-codes-and-standards/sae-j2572-measuring-exhaust-emissions-energy-consumption-and-range diff --git a/docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md b/docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md deleted file mode 100644 index a3f84303..00000000 --- a/docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md +++ /dev/null @@ -1,411 +0,0 @@ -> **Superseded:** This 2026-06-23 split design is historical context. -> Use `docs/target-architecture.md` and -> `docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md` for the -> current production architecture: TDengine is the default hot history store, -> `sink-archive` owns raw bytes, and `event-file-store` is optional -> compatibility only. - -# GB32960 Service Split Design - -Date: 2026-06-23 - -## Goal - -Split the current all-in-one vehicle ingest runtime into lower-coupled, higher-cohesion services. The first production slice focuses on GB/T 32960 because it is the active high-pressure path: persistent TCP connections, protocol ACKs, raw frame volume, history queries, and downstream analytics are currently assembled in one `bootstrap-all` process. - -The target outcome is: - -- Keep GB32960 ingest reliable and fast. -- Move disk writes and history queries out of the protocol process. -- Move statistics and analysis out of the protocol process. -- Use Kafka as the durable boundary between ingest, history, and analytics. -- Preserve `bootstrap-all` during migration as a local development and rollback entry point. - -## Decision - -Use ACK semantics A: - -> A GB32960 frame is ACKed successfully after the protocol service parses/accepts it and writes the required Kafka message(s) successfully. - -The protocol service must not wait for raw archive, DuckDB/Parquet writes, snapshot generation, or statistics computation before sending the GB32960 response frame. - -## Service Boundaries - -### `gb32960-ingest-app` - -Purpose: own GB32960 connection handling and Kafka production. - -Responsibilities: - -- Listen on the GB32960 TCP port. -- Handle platform login, VIN authorization, TLS if enabled, and session state required for protocol responses. -- Decode GB32960 frames and apply vendor extension parsing. -- Produce raw frame records and normalized event records to Kafka. -- ACK successful reports only after Kafka production succeeds. -- Publish malformed or rejected frames to a DLQ when possible. -- Expose operational health and metrics for connections, decode rate, Kafka latency, ACK latency, and DLQ count. - -Non-responsibilities: - -- No local raw archive writes. -- No DuckDB or Parquet history writes. -- No snapshot query API. -- No vehicle statistics or analysis. -- No long-running analytical calculations. - -Initial module dependencies: - -- `ingest-api` -- `ingest-core` -- `ingest-codec-common` -- `session-core` -- `protocol-gb32960` -- `sink-kafka` -- `observability` - -### `vehicle-history-app` - -Purpose: own raw storage, historical index storage, and history query APIs. - -Responsibilities: - -- Consume GB32960 raw records and normalized event records from Kafka. -- Write raw `.bin` payloads through `sink-archive`. -- Write queryable event indexes through `event-file-store`. -- Provide APIs for raw frame lookup, decoded frame lookup, history query, and snapshot/source-frame lookup. -- Rebuild or backfill the DuckDB sidecar index from stored Parquet files when needed. -- Track consumer lag, write latency, archive failures, index failures, and query latency. - -Non-responsibilities: - -- No TCP protocol listener. -- No GB32960 ACK decisions. -- No online statistics computation. -- No direct coupling to `gb32960-ingest-app` internals. - -Initial module dependencies: - -- `ingest-api` -- `sink-kafka` -- `sink-archive` -- `event-file-store` -- `event-history-service` -- `protocol-gb32960` only for raw-frame decode/query support -- `observability` - -### `vehicle-analytics-app` - -Purpose: own vehicle state, daily statistics, alarms, and later analytics. - -Responsibilities: - -- Consume normalized vehicle event records from Kafka. -- Maintain latest vehicle state if enabled. -- Compute daily vehicle statistics and alarm-derived metrics. -- Persist analytical outputs to the selected backend. -- Support reprocessing from Kafka offsets when analytical logic changes. -- Track consumer lag, per-VIN processing latency, aggregation failures, and output write latency. - -Non-responsibilities: - -- No protocol listener. -- No GB32960 ACK decisions. -- No raw archive writes. -- No normal-path dependency on raw archive or history APIs. - -Initial module dependencies: - -- `ingest-api` -- `sink-kafka` -- `vehicle-state-service` -- `vehicle-stat-service` -- `observability` - -## Kafka Contract - -Kafka is the service boundary. Messages must be stable enough that `vehicle-history-app` and `vehicle-analytics-app` do not depend on protocol service internals. - -### Topics - -Use versioned topics: - -- `vehicle.raw.gb32960.v1` -- `vehicle.event.gb32960.v1` -- `vehicle.dlq.gb32960.v1` - -The current topic names under `lingniu.ingest.sink.kafka.topics` can remain temporarily for compatibility, but the split apps should converge on versioned topic names. - -### Keys - -Use VIN as the Kafka key whenever a VIN is available. - -Benefits: - -- Preserves per-vehicle ordering within a partition. -- Lets history and analytics scale by partition. -- Keeps stateful analytics simpler. - -When VIN is unavailable, use a stable fallback key such as platform account plus peer address plus receive time bucket. - -### Raw Record - -Topic: `vehicle.raw.gb32960.v1` - -Minimum fields: - -- `schemaVersion` -- `protocol = GB32960` -- `eventId` -- `receiveTime` -- `eventTime` if decoded -- `peer` -- `platformAccount` -- `vin` if decoded -- `command` -- `protocolVersion` -- `rawBytes` -- `checksumStatus` -- `decodeStatus` -- `metadata` - -Consumers: - -- `vehicle-history-app` writes archive `.bin` files and raw-frame query indexes. -- Future replay tools can regenerate normalized events from raw records. - -### Normalized Event Record - -Topic: `vehicle.event.gb32960.v1` - -Minimum fields: - -- `schemaVersion` -- `protocol = GB32960` -- `eventId` -- `sourceRawEventId` -- `receiveTime` -- `eventTime` -- `platformAccount` -- `vin` -- `command` -- `eventType` -- `normalizedFields` -- `vendorFields` -- `alarmFields` -- `metadata` - -Consumers: - -- `vehicle-history-app` writes queryable history records. -- `vehicle-analytics-app` updates state and statistics. - -### DLQ Record - -Topic: `vehicle.dlq.gb32960.v1` - -Minimum fields: - -- `schemaVersion` -- `stage` -- `errorCode` -- `errorMessage` -- `receiveTime` -- `peer` -- `platformAccount` if known -- `vin` if known -- `rawBytes` when available -- `metadata` - -Typical stages: - -- `FRAME_DECODE` -- `BODY_PARSE` -- `AUTH` -- `KAFKA_PRODUCE` -- `ACK_WRITE` - -## ACK and Failure Semantics - -### Success Path - -1. Receive TCP bytes. -2. Decode a complete GB32960 frame. -3. Validate checksum/auth/session rules. -4. Build raw and normalized Kafka records. -5. Produce required Kafka records successfully. -6. Send GB32960 success ACK. - -### Kafka Produce Failure - -If required Kafka production fails, the service must not send a success ACK. - -Allowed behavior: - -- Retry within a bounded timeout. -- Return a GB32960 failure response when the protocol allows it. -- Close the channel after repeated produce failures. -- Emit local error logs and metrics. - -Do not silently ACK frames that have not crossed the Kafka durability boundary. - -### History or Analytics Failure - -Failures in `vehicle-history-app` or `vehicle-analytics-app` must not affect GB32960 ACKs. They are handled by Kafka offset retry, DLQ, operational alerting, and backfill/replay. - -## Current Coupling to Remove - -`bootstrap-all` currently assembles protocol modules, Kafka producer/consumer, archive sink, event file store, history API, vehicle state, vehicle statistics, command gateway, and multiple inbound protocols in one application. - -The split should remove these production couplings: - -- Protocol runtime directly containing archive and event-file-store writers. -- Protocol runtime directly containing statistics processors. -- History query API sharing the same process as TCP ingest. -- Kafka consumer workers living in the same all-in-one app as protocol listeners. -- Operational scaling tied to one JVM for ingest, storage, queries, and analytics. - -## Migration Plan - -### Phase 1: Add Split App Entrypoints - -Add new app modules: - -- `modules/apps/gb32960-ingest-app` -- `modules/apps/vehicle-history-app` -- `modules/apps/vehicle-analytics-app` - -Keep `modules/apps/bootstrap-all` unchanged as the fallback runtime. - -### Phase 2: GB32960 Ingest App - -Create a production profile for `gb32960-ingest-app`: - -- Enable `protocol-gb32960`. -- Enable Kafka producer. -- Disable local archive. -- Disable event-file-store. -- Disable history API. -- Disable vehicle state/stat services. -- Disable unrelated protocols. - -Acceptance: - -- GB32960 TCP port starts. -- Platform login works. -- Realtime report frames are parsed. -- Kafka records are produced with VIN keys. -- ACK is sent only after Kafka success. - -### Phase 3: History App - -Create `vehicle-history-app`: - -- Enable Kafka consumer. -- Enable archive sink. -- Enable event-file-store. -- Enable event-history HTTP API. -- Disable protocol listeners. -- Disable analytics processors. - -Acceptance: - -- Consumes `vehicle.raw.gb32960.v1`. -- Writes raw `.bin` files. -- Consumes `vehicle.event.gb32960.v1`. -- Writes Parquet/DuckDB indexes. -- Queries can locate raw frames and decoded snapshots. - -### Phase 4: Analytics App - -Create `vehicle-analytics-app`: - -- Enable Kafka consumer. -- Enable vehicle state if a state backend is configured. -- Enable vehicle statistics. -- Disable protocol listeners. -- Disable archive and event-file-store. - -Acceptance: - -- Consumes `vehicle.event.gb32960.v1`. -- Maintains expected per-VIN state/stat outputs. -- Can be restarted and resume from Kafka offsets. -- Does not affect ingest ACK latency. - -### Phase 5: Parallel Run and Cutover - -Run old and new paths in parallel for a bounded validation window. - -Compare: - -- Received frame count. -- Kafka produced record count. -- Raw archive file count. -- Unique VIN count. -- History query count and sample query correctness. -- Analytics output count and sample values. -- GB32960 ACK latency before and after split. - -Cut over only after counts and sample queries match within the agreed tolerance. - -## Operational Guidance - -Scale services independently: - -- Scale `gb32960-ingest-app` by connection count and Kafka produce latency. -- Scale `vehicle-history-app` by disk throughput, consumer lag, and query latency. -- Scale `vehicle-analytics-app` by consumer lag and computation latency. - -Monitor: - -- Kafka producer error rate and p99 produce latency. -- ACK p99 latency. -- TCP active connections. -- Decode failures and DLQ counts. -- Consumer lag per group. -- Archive write latency and failure rate. -- Event-file-store flush latency and failure rate. -- Analytics processing latency. - -## Risks and Mitigations - -Risk: Kafka outage blocks GB32960 ACKs. - -Mitigation: bounded producer retry, clear failure ACK/close behavior, Kafka cluster monitoring, and optional local emergency spool only as a later explicit design. - -Risk: Two Kafka topics for raw and normalized events can diverge. - -Mitigation: include `eventId` and `sourceRawEventId`, produce records transactionally if required, or publish a single envelope containing both raw and normalized sections in the first implementation if transaction support is not ready. - -Risk: History app reprocessing duplicates archive/index records. - -Mitigation: deterministic archive keys and idempotent index writes keyed by `eventId` or `rawArchiveUri`. - -Risk: Analytics logic changes require backfill. - -Mitigation: keep raw and normalized topics with sufficient retention; allow analytics consumers to reset offsets or run a backfill group. - -Risk: Query APIs accidentally depend on ingest internals. - -Mitigation: history APIs should depend on `event-file-store`, `sink-archive`, and decode libraries only, not `gb32960-ingest-app`. - -## Open Decisions - -These are intentionally left for implementation planning: - -- Whether Kafka production uses two independent sends or a transaction for raw + normalized records. -- Exact protobuf/JSON schema shape for `vehicle.raw.gb32960.v1` and `vehicle.event.gb32960.v1`. -- Whether latest snapshot state belongs only to `vehicle-analytics-app` or is also materialized by `vehicle-history-app` for query convenience. -- Initial Kafka partition count and retention duration. -- Whether command downlink stays with `command-gateway` or gets a separate command service later. - -## Completion Criteria - -The split is complete when: - -- `gb32960-ingest-app`, `vehicle-history-app`, and `vehicle-analytics-app` are separate runnable app modules. -- `gb32960-ingest-app` can receive live GB32960 data and ACK after Kafka success. -- `vehicle-history-app` can consume Kafka and provide raw/history/snapshot query APIs. -- `vehicle-analytics-app` can consume Kafka and compute state/stat outputs. -- `bootstrap-all` remains available for development and rollback. -- Verification covers build, app startup, Kafka production/consumption, raw archive writes, history queries, analytics outputs, and ACK latency. diff --git a/docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md b/docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md deleted file mode 100644 index 531c4f37..00000000 --- a/docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md +++ /dev/null @@ -1,621 +0,0 @@ -# Vehicle Ingest Redesign Design - -## Background - -Current GB32960 and JT808 ingestion can receive, decode, publish, and query data, but the responsibilities are split across several partially overlapping paths: - -- Netty handlers produce `RawFrame` and the dispatcher emits both raw archive events and normalized vehicle events. -- Kafka envelopes carry raw archive references, while raw bytes are stored separately by the archive sink. -- GB32960 historical queries use raw archive records plus file replay decoding. -- JT808 location history is materialized separately into TDengine. -- DuckDB/Parquet, local raw archive, Kafka, and TDengine each own part of the story, so query and durability semantics are hard to reason about. - -The new design intentionally keeps useful protocol parsers and app deployment knowledge, but replaces the ingestion, durability, storage, and query boundaries with a first-principles model. - -## Goals - -1. Store every original inbound protocol frame, including malformed frames when bytes are available. -2. Query historical uploaded data for GB32960 and JT808 by vehicle, protocol, message type, and time range. -3. Extend to new fields, protocol messages, and vendor profiles without rewriting the core pipeline. -4. Sustain production load for 10,000 vehicles and 1,000 concurrent historical query requests per second. -5. Make ACK and durability rules explicit enough that an acknowledged frame is not silently lost. - -## Non-Goals - -- Do not make Kafka the long-term historical query database. -- Do not store large raw byte arrays directly in Kafka topics. -- Do not keep DuckDB/Parquet as the production hot query store for GB32960 and JT808. -- Do not build one generic API that hides protocol-specific semantics. Shared internals are preferred; external APIs may remain protocol-aware. - -## Recommended Architecture - -Use `Raw Archive + Kafka + TDengine` as the core architecture. - -```mermaid -flowchart LR - GB["GB32960 TCP"] --> Edge["Protocol Edge"] - JT["JT808 TCP"] --> Edge - Edge --> RawStore["Raw Archive"] - RawStore --> RawTopic["Kafka vehicle.raw-frame.v1"] - Edge --> Decode["Protocol Decoder"] - Decode --> FactTopic["Kafka vehicle.decoded-fact.v1"] - RawTopic --> Writer["History Writer"] - FactTopic --> Writer - Writer --> TD["TDengine"] - Writer --> Redis["Realtime State"] - API["History API"] --> TD - API --> RawStore - RealtimeAPI["Realtime API"] --> Redis -``` - -The architecture has three facts: - -- `RawFrameFact`: one row per inbound frame, whether parsing succeeds or fails. -- `DecodedFact`: zero or more normalized facts derived from a raw frame. -- `RawArchiveObject`: immutable raw bytes addressed by `raw_uri`. - -The raw archive is the forensic source of truth. TDengine is the query source of truth. Kafka is the decoupling and replay pipe. - -## Module Boundaries - -### Protocol Edge Apps - -Apps: - -- `gb32960-ingest-app` -- `jt808-ingest-app` - -Responsibilities: - -- Accept TCP connections. -- Split frames. -- Perform protocol-level validation, authentication, session binding, and ACK. -- Create `RawFrameFact`. -- Persist raw bytes through `RawArchiveWriter`. -- Publish raw frame fact to Kafka. -- Decode frames and publish decoded facts. - -They do not query history, export data, or write business tables directly. - -### Ingest Core - -New module: - -- `modules/core/ingest-facts` - -Core types: - -```java -public record RawFrameFact( - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - int messageId, - int subType, - Instant eventTime, - Instant receivedAt, - String peer, - String rawUri, - String checksum, - long rawSizeBytes, - ParseStatus parseStatus, - String parseError, - Map metadata -) {} -``` - -```java -public sealed interface DecodedFact permits - DecodedFact.Location, - DecodedFact.Realtime, - DecodedFact.Alarm, - DecodedFact.Session, - DecodedFact.Register, - DecodedFact.Extension { - - String factId(); - String frameId(); - ProtocolId protocol(); - String vehicleKey(); - String vin(); - Instant eventTime(); - Instant receivedAt(); - String rawUri(); - Map metadata(); -} -``` - -`vehicleKey` is the stable partition key: - -- GB32960: VIN. -- JT808: resolved VIN when known, otherwise `jt808:`. -- Unknown/malformed: `unknown::`. - -### Raw Archive - -New or refactored module: - -- `modules/sinks/raw-archive-store` - -Interface: - -```java -public interface RawArchiveWriter { - RawArchiveReceipt write(RawArchiveWriteRequest request) throws IOException; -} -``` - -Receipt: - -```java -public record RawArchiveReceipt( - String rawUri, - String checksum, - long sizeBytes -) {} -``` - -Rules: - -- Raw bytes are immutable once written. -- Write path is content-addressed or deterministic by `protocol/date/vehicleKey/frameId.bin`. -- Write must be fsync-capable for local storage and pluggable for object storage. -- `rawUri` must be resolvable by history API for single-frame replay. -- Malformed frames are written with `parseStatus=FAILED` when bytes are available. - -Deployment constraints: - -- Raw archive, TDengine, and the history service may run on different ECS instances, but should use private-network access in the same VPC and availability zone. -- High-frequency historical queries only read TDengine and must not read raw archive. -- Raw archive is only used by single-frame detail, troubleshooting, replay, and asynchronous export paths. -- `rawUri` must be globally resolvable by services and must not be a private local absolute path on one ECS instance. -- The first local raw archive implementation must stay behind a replaceable interface so it can later move to OSS, MinIO, or another object store. - -### Kafka Topics - -Use stable versioned topics: - -- `vehicle.raw-frame.v1` -- `vehicle.decoded-fact.v1` -- `vehicle.dlq.v1` - -Kafka key: - -```text -: -``` - -`vehicle.raw-frame.v1` payload contains `RawFrameFact` without raw bytes. It carries `rawUri`, checksum, byte size, message id, parse status, and metadata. - -`vehicle.decoded-fact.v1` payload contains one `DecodedFact`. It always references `frameId` and `rawUri`. - -`vehicle.dlq.v1` carries failed store, publish, decode, and write attempts with enough metadata to replay from raw archive when possible. - -### History Writer - -New app or refactored app: - -- `vehicle-history-app` - -Responsibilities: - -- Consume `vehicle.raw-frame.v1`. -- Consume `vehicle.decoded-fact.v1`. -- Batch write TDengine. -- Update realtime state store for latest vehicle state. -- Write DLQ on invalid payloads or storage failures. - -The writer is idempotent by `(frame_id)` for raw facts and `(fact_id)` for decoded facts. - -## TDengine Model - -TDengine is the production historical query store. - -### Raw Frames Super Table - -```sql -CREATE STABLE IF NOT EXISTS raw_frames ( - ts TIMESTAMP, - frame_id NCHAR(64), - received_at TIMESTAMP, - message_id INT, - sub_type INT, - event_time TIMESTAMP, - raw_uri NCHAR(512), - checksum NCHAR(128), - raw_size_bytes BIGINT, - parse_status NCHAR(16), - parse_error NCHAR(512), - peer NCHAR(128), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -Child table: - -```text -raw__ -``` - -Primary query patterns: - -- Raw frames by vehicle and time. -- Raw frames by protocol, message id, parse status, and time. -- Single raw frame by `frame_id` or `raw_uri`. - -### Location Super Table - -```sql -CREATE STABLE IF NOT EXISTS vehicle_locations ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - longitude DOUBLE, - latitude DOUBLE, - altitude_m DOUBLE, - speed_kmh DOUBLE, - direction_deg DOUBLE, - alarm_flag BIGINT, - status_flag BIGINT, - total_mileage_km DOUBLE, - raw_uri NCHAR(512), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -### Realtime Super Table - -```sql -CREATE STABLE IF NOT EXISTS vehicle_realtime ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - speed_kmh DOUBLE, - total_mileage_km DOUBLE, - battery_soc DOUBLE, - total_voltage_v DOUBLE, - total_current_a DOUBLE, - running_mode NCHAR(64), - vehicle_state NCHAR(64), - charging_state NCHAR(64), - raw_uri NCHAR(512), - fields_json NCHAR(8192), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -`fields_json` stores sparse full-field values for low-frequency inspection. High-frequency queried fields should be promoted to explicit columns or a dedicated extension table. - -### Alarm Super Table - -```sql -CREATE STABLE IF NOT EXISTS vehicle_alarms ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - alarm_level NCHAR(32), - alarm_code BIGINT, - alarm_name NCHAR(128), - longitude DOUBLE, - latitude DOUBLE, - raw_uri NCHAR(512), - fields_json NCHAR(8192), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -### Session and Registration Tables - -```sql -CREATE STABLE IF NOT EXISTS vehicle_sessions ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - session_type NCHAR(32), - result_code INT, - raw_uri NCHAR(512), - fields_json NCHAR(8192), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -JT808 registration details go into `vehicle_sessions` first and may later be projected into MySQL identity bindings for durable identity lookup. - -## ACK and Durability Rules - -For frames that require a protocol ACK: - -1. Frame bytes are accepted from Netty. -2. Raw bytes are written to raw archive. -3. `RawFrameFact` is published to Kafka. -4. Protocol-specific ACK is sent. -5. Decoding and `DecodedFact` publication may run before or after ACK, but failures must emit a DLQ event and update raw frame parse status. - -For GB32960 realtime/report frames, this prevents acknowledging a frame whose original bytes cannot be recovered. - -For JT808 register/auth/heartbeat, ACK follows the same raw archive and raw fact boundary. Register ACK can still include token generation before raw fact publish, but must not be flushed to the channel until the durability boundary succeeds. - -If raw archive write fails, close or keep the channel according to protocol tolerance, but do not ACK success. - -If Kafka raw fact publish fails, do not ACK success for durable-ACK frames. Non-durable frames go to local DLQ when configured. - -## Protocol Decoding and Extension - -Use a plugin contract: - -```java -public interface ProtocolFrameDecoder { - ProtocolId protocol(); - DecodeResult decode(RawFrameFact rawFact, byte[] rawBytes); -} -``` - -```java -public record DecodeResult( - ParseStatus status, - List facts, - String errorMessage, - Map metadata -) {} -``` - -GB32960: - -- Frame command maps to session, realtime, location, alarm, and extension facts. -- Vendor extensions are selected by platform account, VIN, or configured profile. -- Full raw replay remains possible by reading `rawUri` and running the current decoder. - -JT808: - -- Message id maps to register, auth, heartbeat, location, batch location, media metadata, and extension facts. -- Identity resolution updates `vehicleKey`. -- Registration facts carry province, city, maker, device id, plate, plate color, and auth token metadata. - -New protocol messages do not require TDengine schema changes unless they become high-frequency query dimensions. Unknown fields are stored as extension facts with `fields_json`. - -## Query APIs - -Keep API surfaces explicit. - -Raw frame APIs: - -- `GET /api/history/raw-frames` -- Filters: `protocol`, `vin`, `phone`, `vehicleKey`, `messageId`, `parseStatus`, `dateFrom`, `dateTo`, `pageSize`, `cursor`. -- Returns frame metadata, raw URI, checksum, parse status, and compact decoded summary when available. - -Single raw frame API: - -- `GET /api/history/raw-frames/{frameId}` -- Returns raw metadata and decoded detail. -- Supports `includeRawHex=true` with a strict size limit. - -Location APIs: - -- `GET /api/history/locations` -- Filters: `protocol`, `vin`, `phone`, `vehicleKey`, time range, cursor. - -Realtime APIs: - -- `GET /api/history/realtime` -- Historical time-series query. - -Realtime state APIs: - -- `GET /api/realtime/vehicles/{vehicleKey}` -- Reads Redis latest state, not TDengine. - -Export: - -- Export is asynchronous. -- Request creates an export job. -- Worker streams TDengine query results to object storage or local export files. -- API returns job status and download URI. - -## Pagination - -Use cursor pagination, not deep offset, for high-QPS history queries. - -Cursor fields: - -```text -ts, received_at, fact_id -``` - -Descending query predicate: - -```sql -WHERE ( - ts < :cursorTs - OR (ts = :cursorTs AND received_at < :cursorReceivedAt) - OR (ts = :cursorTs AND received_at = :cursorReceivedAt AND fact_id < :cursorFactId) -) -``` - -Page size defaults to 100 and is capped at 1000. - -## Capacity Design - -Assumptions: - -- 10,000 vehicles. -- Normal report interval: 5 to 10 seconds. -- Expected writes: 1,000 to 2,000 frames per second. -- Peak burst factor: 3x. -- Historical query target: 1,000 requests per second. - -Write path: - -- Netty worker threads stay CPU-light. -- Raw archive writes use bounded async workers. -- Kafka producer uses compression, batching, idempotence, and vehicle-key partitioning. -- History writer batches TDengine inserts by super table and child table. -- TDengine child tables are partitioned by vehicle key to make single-vehicle queries cheap. - -Read path: - -- Most high-QPS queries require vehicle key and time range. -- Unbounded cross-vehicle queries are admin-only and capped. -- Realtime latest state is served from Redis latest-state cache. -- Raw detail replay reads one raw object and decodes on demand. -- Common dictionary and metadata responses are cached in memory. - -Backpressure: - -- Raw archive worker queue has a fixed bound. -- Kafka publish futures are tracked. -- If durability boundary cannot complete within timeout, durable ACK fails and the channel is closed or throttled. -- History writer lag is observable by Kafka consumer lag and TDengine batch latency. - -## Migration Plan - -Phase 1: Build new fact model beside existing code. - -- Add `ingest-facts`. -- Add raw archive receipt contract. -- Add serializers for raw frame and decoded fact Kafka messages. -- Add tests for stable ids, vehicle key, and raw URI generation. - -Phase 2: Harden raw durability boundary. - -- Refactor GB32960 and JT808 handlers so ACK waits for raw archive and raw fact publish. -- Keep existing parsers. -- Emit decoded facts to the new Kafka topic. - -Phase 3: Build TDengine writer. - -- Create TDengine schema manager. -- Write raw frame facts. -- Write location, realtime, alarm, session facts. -- Add idempotent insert behavior by stable ids. - -Phase 4: Build query APIs. - -- Raw frame query. -- Single frame replay. -- Location history query. -- Realtime history query. -- Latest realtime state query. - -Phase 5: Cut over and retire old hot paths. - -- Stop using DuckDB/Parquet as production hot history for GB32960/JT808. -- Keep raw replay compatibility while historical data migrates. -- Remove protocol-specific ad hoc history storage once TDengine coverage is verified. - -## Testing Strategy - -Unit tests: - -- `RawFrameFact` validation and vehicle key derivation. -- Raw archive path generation and checksum. -- GB32960 decoder facts from golden frames. -- JT808 decoder facts from sample frames. -- ACK boundary success and failure cases. -- TDengine SQL generation. - -Integration tests: - -- Start app with embedded or mocked Kafka producer and fake raw archive. -- Send GB32960 sample frame and verify raw fact, decoded fact, raw archive receipt. -- Send JT808 register/auth/location frames and verify registration/session/location facts. -- Simulate raw archive failure and verify no success ACK. -- Simulate Kafka publish failure and verify durable ACK failure. - -Storage tests: - -- TDengine schema initialization. -- Batch insert raw frames and decoded facts. -- Query by vehicle key and time range. -- Cursor pagination correctness. -- Single raw frame replay by `rawUri`. - -Load tests: - -- 2,000 frames/s sustained write for at least 30 minutes. -- 6,000 frames/s burst for 5 minutes. -- 1,000 QPS location/realtime history queries with bounded p95 latency. -- Verify no raw frame count gap between raw archive receipts, Kafka raw facts, and TDengine raw frame rows. - -## Operational Requirements - -Metrics: - -- TCP connections. -- Frame receive rate. -- Raw archive write latency and failure count. -- Kafka publish latency and failure count. -- Decoder success/failure count by protocol and message id. -- TDengine batch size, latency, and failure count. -- Query QPS and latency by endpoint. -- Kafka consumer lag. - -Logs: - -- One structured log per failed frame with `frameId`, protocol, vehicle key, message id, raw URI, and error. -- No full raw bytes in normal logs. -- Raw hex only in explicit debug tools with size limits. - -Alerts: - -- Raw archive write failure. -- Durable ACK timeout. -- Kafka producer failure. -- History writer lag. -- TDengine write failure. -- Raw archive count and TDengine raw frame row count divergence. - -## Acceptance Criteria - -The redesign is complete only when all of the following are true: - -1. Every accepted GB32960 and JT808 frame creates a raw archive object and a TDengine `raw_frames` row. -2. Malformed frames with bytes are queryable from raw history with parse status and error reason. -3. GB32960 and JT808 location history can be queried by vehicle and time range with cursor pagination. -4. GB32960 realtime history can be queried by vehicle and time range. -5. JT808 registration/session data is queryable and can update identity binding. -6. Single-frame detail can reload raw bytes by `rawUri` and decode with the current decoder. -7. ACK-required frames do not receive success ACK before raw archive and raw fact publication succeed. -8. New protocol fields can be added by implementing a decoder/projector and, only when needed, a TDengine table migration. -9. Load tests demonstrate sustained 2,000 frames/s writes and 1,000 QPS bounded historical queries. -10. Old DuckDB/Parquet hot-history paths are no longer required for GB32960/JT808 production queries. - -## Open Decisions - -The following choices are intentionally fixed for the first implementation: - -- Use local raw archive first, with the interface shaped for object storage later. -- Use TDengine as the only production hot historical query store for GB32960/JT808. -- Use explicit protocol-aware query APIs instead of a single generic query API. -- Use cursor pagination for all high-QPS historical queries. -- Keep Kafka payloads byte-light by sending raw references instead of raw bytes. diff --git a/docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.zh.md b/docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.zh.md deleted file mode 100644 index b3b457a7..00000000 --- a/docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.zh.md +++ /dev/null @@ -1,624 +0,0 @@ -# 车辆接入重设计方案 - -## 背景 - -当前 GB32960 和 JT808 链路已经能够完成接收、解析、发布和查询,但职责边界被拆散在多条路径里: - -- Netty Handler 生成 `RawFrame`,Dispatcher 同时发出原始归档事件和标准化车辆事件。 -- Kafka Envelope 只携带 raw archive 引用,原始 bytes 由 archive sink 另行存储。 -- GB32960 历史查询依赖 raw archive 记录,再回读文件重新解析。 -- JT808 位置历史又单独物化到 TDengine。 -- DuckDB/Parquet、本地 raw archive、Kafka、TDengine 分别承担一部分职责,导致查询语义和持久化边界不好推理。 - -新设计保留已有协议解析器和部署经验,但重新定义接入、持久化、存储和查询边界。 - -## 目标 - -1. 保存每一条入站协议原始帧,包括可拿到 bytes 的异常帧。 -2. 支持按车辆、协议、消息类型、时间范围查询 GB32960 和 JT808 历史上报数据。 -3. 新增字段、协议消息、厂商扩展时,不需要重写核心管线。 -4. 支撑 10,000 辆车生产写入,以及 1,000 QPS 历史查询。 -5. 明确 ACK 和持久化规则,避免“已经 ACK 但数据悄悄丢失”。 - -## 非目标 - -- 不把 Kafka 作为长期历史查询数据库。 -- 不把大块原始 bytes 直接塞进 Kafka topic。 -- 不继续把 DuckDB/Parquet 作为 GB32960/JT808 的生产热查询库。 -- 不做一个掩盖协议差异的万能接口。内部可以统一,外部 API 保持协议语义清晰。 - -## 推荐架构 - -采用 `Raw Archive + Kafka + TDengine` 作为核心架构。 - -```mermaid -flowchart LR - GB["GB32960 TCP"] --> Edge["协议接入边界"] - JT["JT808 TCP"] --> Edge - Edge --> RawStore["Raw Archive"] - RawStore --> RawTopic["Kafka vehicle.raw-frame.v1"] - Edge --> Decode["协议解析器"] - Decode --> FactTopic["Kafka vehicle.decoded-fact.v1"] - RawTopic --> Writer["History Writer"] - FactTopic --> Writer - Writer --> TD["TDengine"] - Writer --> Redis["实时状态"] - API["历史 API"] --> TD - API --> RawStore - RealtimeAPI["实时 API"] --> Redis -``` - -架构里只有三类事实: - -- `RawFrameFact`:每一条入站帧一条记录,不管解析成功还是失败。 -- `DecodedFact`:从 raw frame 派生出来的零条或多条标准事实。 -- `RawArchiveObject`:不可变原始 bytes,通过 `raw_uri` 寻址。 - -raw archive 是排障和回放的事实源。TDengine 是查询事实源。Kafka 是解耦和重放管道。 - -## 模块边界 - -### 协议接入 APP - -APP: - -- `gb32960-ingest-app` -- `jt808-ingest-app` - -职责: - -- 接收 TCP 连接。 -- 完成帧切分。 -- 执行协议层校验、鉴权、会话绑定和 ACK。 -- 创建 `RawFrameFact`。 -- 通过 `RawArchiveWriter` 持久化原始 bytes。 -- 发布 raw frame fact 到 Kafka。 -- 解析协议帧并发布 decoded facts。 - -接入 APP 不负责历史查询、导出,也不直接写业务查询表。 - -### Ingest Core - -新增模块: - -- `modules/core/ingest-facts` - -核心类型: - -```java -public record RawFrameFact( - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - int messageId, - int subType, - Instant eventTime, - Instant receivedAt, - String peer, - String rawUri, - String checksum, - long rawSizeBytes, - ParseStatus parseStatus, - String parseError, - Map metadata -) {} -``` - -```java -public sealed interface DecodedFact permits - DecodedFact.Location, - DecodedFact.Realtime, - DecodedFact.Alarm, - DecodedFact.Session, - DecodedFact.Register, - DecodedFact.Extension { - - String factId(); - String frameId(); - ProtocolId protocol(); - String vehicleKey(); - String vin(); - Instant eventTime(); - Instant receivedAt(); - String rawUri(); - Map metadata(); -} -``` - -`vehicleKey` 是稳定分区键: - -- GB32960:VIN。 -- JT808:已解析到 VIN 时使用 VIN,否则使用 `jt808:`。 -- 未知或异常帧:使用 `unknown::`。 - -### Raw Archive - -新增或重构模块: - -- `modules/sinks/raw-archive-store` - -接口: - -```java -public interface RawArchiveWriter { - RawArchiveReceipt write(RawArchiveWriteRequest request) throws IOException; -} -``` - -写入回执: - -```java -public record RawArchiveReceipt( - String rawUri, - String checksum, - long sizeBytes -) {} -``` - -规则: - -- 原始 bytes 一旦写入,不允许原地修改。 -- 写入路径可以按内容寻址,也可以按 `protocol/date/vehicleKey/frameId.bin` 生成。 -- 本地存储需要支持 fsync 能力,接口要预留对象存储实现。 -- `rawUri` 必须能被历史 API 解析,用于单帧回放。 -- 异常帧只要有 bytes,也必须写入,并记录 `parseStatus=FAILED`。 - -部署约束: - -- Raw archive、TDengine、history 服务可以不在同一台 ECS,但应在同一 VPC 和同一可用区内通过内网访问。 -- 高频历史查询只访问 TDengine,不访问 raw archive。 -- Raw archive 只进入单帧详情、问题排查、回放和异步导出路径。 -- `rawUri` 必须是全局可解析地址,不能是某台 ECS 私有的本地绝对路径。 -- 第一版本地 raw archive 需要通过可替换接口封装,后续可以切换到 OSS、MinIO 或其它对象存储。 - -### Kafka Topic - -使用稳定的版本化 topic: - -- `vehicle.raw-frame.v1` -- `vehicle.decoded-fact.v1` -- `vehicle.dlq.v1` - -Kafka key: - -```text -: -``` - -`vehicle.raw-frame.v1` 的 payload 是不含 raw bytes 的 `RawFrameFact`,携带 `rawUri`、checksum、byte size、message id、parse status 和 metadata。 - -`vehicle.decoded-fact.v1` 的 payload 是单条 `DecodedFact`,必须引用 `frameId` 和 `rawUri`。 - -`vehicle.dlq.v1` 保存存储失败、发布失败、解析失败和写入失败事件,并携带足够元数据,尽量支持从 raw archive 重放。 - -### History Writer - -新增或重构 APP: - -- `vehicle-history-app` - -职责: - -- 消费 `vehicle.raw-frame.v1`。 -- 消费 `vehicle.decoded-fact.v1`。 -- 批量写 TDengine。 -- 更新实时状态存储。 -- 对非法 payload 或存储失败写 DLQ。 - -Writer 写入需要幂等: - -- raw fact 以 `(frame_id)` 幂等。 -- decoded fact 以 `(fact_id)` 幂等。 - -## TDengine 模型 - -TDengine 是生产历史查询主库。 - -### 原始帧超级表 - -```sql -CREATE STABLE IF NOT EXISTS raw_frames ( - ts TIMESTAMP, - frame_id NCHAR(64), - received_at TIMESTAMP, - message_id INT, - sub_type INT, - event_time TIMESTAMP, - raw_uri NCHAR(512), - checksum NCHAR(128), - raw_size_bytes BIGINT, - parse_status NCHAR(16), - parse_error NCHAR(512), - peer NCHAR(128), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -子表命名: - -```text -raw__ -``` - -主要查询模式: - -- 按车辆和时间查询原始帧。 -- 按协议、消息 ID、解析状态和时间查询原始帧。 -- 按 `frame_id` 或 `raw_uri` 查询单帧。 - -### 位置超级表 - -```sql -CREATE STABLE IF NOT EXISTS vehicle_locations ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - longitude DOUBLE, - latitude DOUBLE, - altitude_m DOUBLE, - speed_kmh DOUBLE, - direction_deg DOUBLE, - alarm_flag BIGINT, - status_flag BIGINT, - total_mileage_km DOUBLE, - raw_uri NCHAR(512), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -### 实时数据超级表 - -```sql -CREATE STABLE IF NOT EXISTS vehicle_realtime ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - speed_kmh DOUBLE, - total_mileage_km DOUBLE, - battery_soc DOUBLE, - total_voltage_v DOUBLE, - total_current_a DOUBLE, - running_mode NCHAR(64), - vehicle_state NCHAR(64), - charging_state NCHAR(64), - raw_uri NCHAR(512), - fields_json NCHAR(8192), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -`fields_json` 存放稀疏全字段值,用于低频排查。高频查询字段应提升为明确列,或放入专用扩展表。 - -### 报警超级表 - -```sql -CREATE STABLE IF NOT EXISTS vehicle_alarms ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - alarm_level NCHAR(32), - alarm_code BIGINT, - alarm_name NCHAR(128), - longitude DOUBLE, - latitude DOUBLE, - raw_uri NCHAR(512), - fields_json NCHAR(8192), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -### 会话和注册表 - -```sql -CREATE STABLE IF NOT EXISTS vehicle_sessions ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - session_type NCHAR(32), - result_code INT, - raw_uri NCHAR(512), - fields_json NCHAR(8192), - metadata_json NCHAR(4096) -) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) -); -``` - -JT808 注册详情先进入 `vehicle_sessions`,后续可以投影到 MySQL 车辆身份绑定表,用于持久身份解析。 - -## ACK 和持久化规则 - -对需要协议 ACK 的帧: - -1. Netty 接收到 frame bytes。 -2. 原始 bytes 写入 raw archive。 -3. `RawFrameFact` 发布到 Kafka。 -4. 发送协议 ACK。 -5. 解码和 `DecodedFact` 发布可以在 ACK 前或 ACK 后执行,但失败必须发 DLQ,并更新 raw frame 解析状态。 - -对 GB32960 实时上报和补发帧,这条规则保证不会 ACK 一条无法恢复原始 bytes 的帧。 - -对 JT808 注册、鉴权、心跳,也遵循相同 raw archive 和 raw fact 边界。注册 ACK 可以先生成 token,但在 durability boundary 成功前不能 flush 到 channel。 - -如果 raw archive 写入失败,不返回成功 ACK。具体是关闭连接还是保留连接,由协议容忍度决定。 - -如果 Kafka raw fact 发布失败,durable ACK 帧不返回成功 ACK。非 durable 帧可以写本地 DLQ。 - -## 协议解析和扩展 - -使用插件契约: - -```java -public interface ProtocolFrameDecoder { - ProtocolId protocol(); - DecodeResult decode(RawFrameFact rawFact, byte[] rawBytes); -} -``` - -```java -public record DecodeResult( - ParseStatus status, - List facts, - String errorMessage, - Map metadata -) {} -``` - -GB32960: - -- 命令帧映射为会话、实时、位置、报警和扩展事实。 -- 厂商扩展按平台账号、VIN 或配置 profile 选择。 -- 全量 raw replay 仍通过读取 `rawUri` 并运行当前解析器完成。 - -JT808: - -- 消息 ID 映射为注册、鉴权、心跳、位置、批量位置、多媒体元数据和扩展事实。 -- 身份解析会更新 `vehicleKey`。 -- 注册事实携带省、市、厂商、设备 ID、车牌、车牌颜色和 auth token metadata。 - -新增协议消息通常不需要改 TDengine schema,除非它成为高频查询维度。未知字段先作为 extension fact 存入 `fields_json`。 - -## 查询 API - -API 保持明确,不做一个含糊的统一接口。 - -原始帧查询: - -- `GET /api/history/raw-frames` -- 过滤条件:`protocol`、`vin`、`phone`、`vehicleKey`、`messageId`、`parseStatus`、`dateFrom`、`dateTo`、`pageSize`、`cursor`。 -- 返回 frame metadata、raw URI、checksum、parse status,以及可选的紧凑解析摘要。 - -单帧详情: - -- `GET /api/history/raw-frames/{frameId}` -- 返回 raw metadata 和解码详情。 -- 支持 `includeRawHex=true`,但必须有严格大小限制。 - -位置历史: - -- `GET /api/history/locations` -- 过滤条件:`protocol`、`vin`、`phone`、`vehicleKey`、时间范围、cursor。 - -实时历史: - -- `GET /api/history/realtime` -- 查询历史时序数据。 - -实时状态: - -- `GET /api/realtime/vehicles/{vehicleKey}` -- 从 Redis 或内存 latest state 读取,不扫 TDengine。 - -导出: - -- 导出必须异步。 -- 请求创建 export job。 -- Worker 将 TDengine 查询结果流式写入对象存储或本地导出文件。 -- API 返回 job 状态和下载 URI。 - -## 分页 - -高 QPS 历史查询使用 cursor pagination,不使用深 offset。 - -Cursor 字段: - -```text -ts, received_at, fact_id -``` - -倒序查询谓词: - -```sql -WHERE ( - ts < :cursorTs - OR (ts = :cursorTs AND received_at < :cursorReceivedAt) - OR (ts = :cursorTs AND received_at = :cursorReceivedAt AND fact_id < :cursorFactId) -) -``` - -默认 page size 为 100,上限为 1000。 - -## 容量设计 - -假设: - -- 10,000 辆车。 -- 常规上报间隔 5 到 10 秒。 -- 预计写入 1,000 到 2,000 frames/s。 -- 峰值突发系数 3 倍。 -- 历史查询目标 1,000 requests/s。 - -写路径: - -- Netty worker 保持 CPU-light。 -- Raw archive 写入使用有界异步 worker。 -- Kafka producer 开启压缩、批量、幂等,并按 vehicle key 分区。 -- History writer 按超级表和子表批量写 TDengine。 -- TDengine 子表按 vehicle key 分区,使单车查询足够便宜。 - -读路径: - -- 高频查询必须带 vehicle key 和时间范围。 -- 无边界跨车查询仅限管理接口,并强制限流和限制返回量。 -- 实时 latest state 从 Redis 或内存状态读取。 -- Raw 详情只读取单个 raw object 并按需解析。 -- 字典和元数据接口使用内存缓存。 - -背压: - -- Raw archive worker queue 固定上限。 -- Kafka publish future 必须被跟踪。 -- Durability boundary 超时时,durable ACK 失败,channel 关闭或被限流。 -- History writer lag 通过 Kafka consumer lag 和 TDengine batch latency 观察。 - -## 迁移计划 - -阶段 1:在现有代码旁边建立新的 fact model。 - -- 新增 `ingest-facts`。 -- 新增 raw archive receipt contract。 -- 新增 raw frame 和 decoded fact 的 Kafka 序列化。 -- 为 stable id、vehicle key、raw URI 生成规则加测试。 - -阶段 2:强化 raw durability boundary。 - -- 重构 GB32960 和 JT808 handler,让 ACK 等待 raw archive 和 raw fact publish。 -- 保留现有解析器。 -- 向新 Kafka topic 发 decoded facts。 - -阶段 3:构建 TDengine writer。 - -- 创建 TDengine schema manager。 -- 写入 raw frame facts。 -- 写入 location、realtime、alarm、session facts。 -- 按 stable id 实现幂等写入。 - -阶段 4:构建查询 API。 - -- Raw frame 查询。 -- 单帧回放。 -- 位置历史查询。 -- 实时历史查询。 -- 最新实时状态查询。 - -阶段 5:切换并下线旧热路径。 - -- GB32960/JT808 生产热历史不再依赖 DuckDB/Parquet。 -- 历史迁移期间保留 raw replay 兼容。 -- TDengine 覆盖验证完成后,移除协议特定的临时历史存储。 - -## 测试策略 - -单元测试: - -- `RawFrameFact` 校验和 vehicle key 派生。 -- Raw archive 路径生成和 checksum。 -- GB32960 golden frame 到 decoded facts。 -- JT808 sample frame 到 decoded facts。 -- ACK boundary 成功和失败场景。 -- TDengine SQL 生成。 - -集成测试: - -- 使用 mocked Kafka producer 和 fake raw archive 启动 APP。 -- 发送 GB32960 sample frame,验证 raw fact、decoded fact 和 raw archive receipt。 -- 发送 JT808 注册、鉴权、位置帧,验证 registration、session、location facts。 -- 模拟 raw archive 失败,验证不发送成功 ACK。 -- 模拟 Kafka publish 失败,验证 durable ACK 失败。 - -存储测试: - -- TDengine schema 初始化。 -- 批量写入 raw frames 和 decoded facts。 -- 按 vehicle key 和时间范围查询。 -- Cursor 分页正确性。 -- 按 `rawUri` 单帧回放。 - -压测: - -- 持续 30 分钟 2,000 frames/s 写入。 -- 持续 5 分钟 6,000 frames/s 突发写入。 -- 1,000 QPS location/realtime 历史查询,p95 延迟受控。 -- 校验 raw archive receipt、Kafka raw fact、TDengine raw frame row 三者数量无缺口。 - -## 运维要求 - -指标: - -- TCP 连接数。 -- Frame 接收速率。 -- Raw archive 写入延迟和失败数。 -- Kafka 发布延迟和失败数。 -- 按协议和 message id 统计 decoder 成功/失败数。 -- TDengine batch size、延迟和失败数。 -- 各查询 endpoint 的 QPS 和延迟。 -- Kafka consumer lag。 - -日志: - -- 每个失败帧输出一条结构化日志,包含 `frameId`、protocol、vehicle key、message id、raw URI 和 error。 -- 正常日志不输出完整 raw bytes。 -- Raw hex 只允许在显式 debug 工具里输出,并限制大小。 - -告警: - -- Raw archive 写入失败。 -- Durable ACK 超时。 -- Kafka producer 失败。 -- History writer lag。 -- TDengine 写入失败。 -- Raw archive 数量和 TDengine raw frame row 数量不一致。 - -## 验收标准 - -重设计只有满足以下条件才算完成: - -1. 每一条被接收的 GB32960 和 JT808 帧都会创建 raw archive object 和 TDengine `raw_frames` 行。 -2. 有 bytes 的异常帧可以从 raw history 查询到,包含 parse status 和错误原因。 -3. GB32960 和 JT808 位置历史可以按车辆和时间范围 cursor 分页查询。 -4. GB32960 实时历史可以按车辆和时间范围查询。 -5. JT808 注册和会话数据可查询,并且可以更新 identity binding。 -6. 单帧详情可以通过 `rawUri` 重新读取 raw bytes,并使用当前解析器解码。 -7. 需要 ACK 的帧,在 raw archive 和 raw fact publish 成功前,不会发送成功 ACK。 -8. 新协议字段可以通过新增 decoder/projector 扩展;只有成为高频查询字段时才需要 TDengine 表迁移。 -9. 压测证明可以持续 2,000 frames/s 写入,并支持 1,000 QPS 有边界历史查询。 -10. GB32960/JT808 生产查询不再依赖旧 DuckDB/Parquet 热历史路径。 - -## 已固定决策 - -第一版实现固定以下选择: - -- 先使用本地 raw archive,实现接口时预留对象存储。 -- TDengine 是 GB32960/JT808 唯一生产热历史查询库。 -- 使用协议明确的查询 API,不做一个泛化万能查询 API。 -- 高频历史查询统一使用 cursor pagination。 -- Kafka payload 保持轻量,只传 raw 引用,不传 raw bytes。 diff --git a/docs/superpowers/specs/2026-07-01-go-ingest-redesign-design.md b/docs/superpowers/specs/2026-07-01-go-ingest-redesign-design.md deleted file mode 100644 index bf84986d..00000000 --- a/docs/superpowers/specs/2026-07-01-go-ingest-redesign-design.md +++ /dev/null @@ -1,498 +0,0 @@ -# Go 车辆接入重构设计规格 - -## 目标 - -使用 Go 重新设计车辆数据接入运行面,让系统回到第一性原则: - -- 接入层只负责可靠收包、协议解析、应答、身份解析和投递。 -- Kafka 是唯一在线消息通道。 -- TDengine 保存高吞吐时序明细和完整 RAW 解析 JSON。 -- MySQL 保存低频配置、身份映射和每日统计窄表。 -- Redis 保存可重建的准实时状态。 -- 32960、JT808、宇通 MQTT 使用同一套数据层接口,避免每个协议各自落库。 - -第一阶段只做生产链路的骨架和核心数据闭环:接收、解析、Kafka、TDengine、MySQL 统计、Redis 实时查询、ECS 验证。历史查询 API 和业务侧复杂分析放在后续阶段。 - -## 参考原则 - -本设计只吸收开源项目的边界思想,不复制外部实现代码。 - -- GB32960 参考 `DarkInno/gb32960-go-sdk`:连接管理、Handler、Forwarder、鉴权接口分离。 -- JT808 参考 `cuteLittleDevil/go-jt808`:高并发 Go 原生 TCP、协议核心小而可扩展、事件/适配器机制。 -- JT808 参考 `fakeyanss/jt808-server-go`:`FramePayload -> PacketData -> JT808Msg -> Reply` 的分层处理、Gateway 模式、保活和版本兼容。 -- TDengine 参考官方数据模型:按数据采集点建立 supertable,静态维度做 tags,明细数据按子表写入;Go 连接优先使用 WebSocket 驱动。 - -## 非目标 - -- 第一阶段不继续扩展 Java 服务。 -- 第一阶段不做统一大查询 API。 -- 第一阶段不恢复信达 Push。 -- 第一阶段不把所有协议字段展开成一张超宽 TDengine 表。 -- Redis 不作为历史事实源。 -- MySQL 不保存高频原始明细。 - -## 目标目录 - -新的 Go 运行面放在 `go/vehicle-gateway`,不继续沿用实验性的多 module 分散结构。现有 `go/ingest-edge` 和 `go/vehicle-state` 可以作为迁移素材,最终归并或删除。 - -```text -go/vehicle-gateway/ - cmd/ - gateway/ # 协议接入进程:32960 TCP、JT808 TCP、宇通 MQTT - history-writer/ # Kafka -> TDengine RAW/location/mileage points - stat-writer/ # Kafka -> MySQL 每日统计窄表 - realtime-api/ # Redis 准实时查询 API - internal/ - config/ # 环境变量、配置文件、校验 - gateway/ # TCP/MQTT 生命周期、连接限制、优雅停机 - protocol/ - gb32960/ # 32960 编解码 - jt808/ # 808 编解码 - yutongmqtt/ # 宇通 MQTT payload 解析 - identity/ # VIN、phone、device_id、plate 绑定 - envelope/ # 统一事件模型 - eventbus/ # Kafka producer/consumer - history/ # TDengine adapter - stats/ # 每日里程聚合 - realtime/ # Redis adapter 与快照合并 - observability/ # 日志、metrics、健康检查 -``` - -## 协议接入设计 - -### GB32960 - -职责: - -- 监听 TCP `32960`。 -- 支持车辆登入、实时信息上报、补发信息上报、车辆登出、平台登入/登出、心跳、校时。 -- 正确返回协议应答。 -- 完整解析 2016 数据单元: - - 整车数据 - - 驱动电机数据 - - 燃料电池数据 - - 发动机数据 - - 车辆位置数据 - - 极值数据 - - 报警数据 - - 可充电储能装置电压数据 - - 可充电储能装置温度数据 -- RAW 中保存完整 parsed JSON。 -- 关键字段标准化为统一 field: - - `speed_kmh` - - `total_mileage_km` - - `soc_percent` - - `longitude` - - `latitude` - - `vehicle_status` - - `charge_status` - -### JT808 - -职责: - -- 监听 TCP `808`。 -- 支持 2011/2013 基础兼容,预留 2019 版本标记。 -- 分层处理: - - Frame:`0x7e` 包边界、转义、校验。 - - Packet:消息头、手机号 BCD、流水号、分包。 - - Message:注册、鉴权、心跳、注销、位置、批量位置、未知上行。 - - Reply:注册应答、平台通用应答。 -- 终端手机号按协议头 BCD 解析,内部同时保留原始 BCD、规范化 phone、去前导零 phone。 -- 注册帧和鉴权帧写入 808 registration 表。 -- 如果设备直接上报位置帧,也要走 identity 解析,尝试用 phone、device_id、plate 找到 VIN。 -- 0200 附加信息必须解析表 27: - - `0x01` GPS 总里程,DWORD,单位 0.1 km。 - - 其他已知附加项结构化放入 `parsed_json.additional`。 -- 标准化字段: - - `speed_kmh` - - `total_mileage_km` - - `longitude` - - `latitude` - - `altitude_m` - - `direction_deg` - - `alarm_flag` - - `status_flag` - -### 宇通 MQTT - -职责: - -- 使用正式 MQTT 配置订阅生产 topic。 -- 接收 payload 后解析为统一 envelope。 -- 按 payload 中的车辆标识解析 VIN、车牌、设备号。 -- 保存完整 payload 和 parsed JSON。 -- 标准化字段向 32960/808 对齐: - - `speed_kmh` - - `total_mileage_km` - - `longitude` - - `latitude` - - `soc_percent` - - `vehicle_status` - -## 统一 Envelope - -所有协议接入后输出同一结构。 - -```json -{ - "event_id": "string", - "trace_id": "string", - "protocol": "GB32960|JT808|YUTONG_MQTT", - "message_id": "string", - "vin": "string", - "vehicle_key": "string", - "phone": "string", - "device_id": "string", - "plate": "string", - "source_endpoint": "ip:port or mqtt://broker/topic", - "event_time_ms": 0, - "received_at_ms": 0, - "raw_hex": "string", - "raw_text": "string", - "parsed": {}, - "fields": { - "speed_kmh": 0, - "total_mileage_km": 0, - "longitude": 0, - "latitude": 0 - }, - "parse_status": "OK|PARTIAL|BAD_FRAME", - "parse_error": "" -} -``` - -规则: - -- `event_id` 使用协议、设备标识、消息流水、事件时间、RAW hash 生成,保证幂等。 -- Kafka partition key 优先使用 VIN;没有 VIN 时使用 `protocol:phone/device_id`。 -- `parsed` 保存协议完整结构化字段。 -- `fields` 只保存跨协议核心字段,用于统计、位置和实时快照。 - -## Kafka Topic - -生产只支持 Kafka。 - -| Topic | 内容 | Key | -|---|---|---| -| `vehicle.raw.gb32960.v1` | 32960 完整 RAW envelope | VIN 或 vehicle_key | -| `vehicle.raw.jt808.v1` | 808 完整 RAW envelope | VIN 或 phone | -| `vehicle.raw.yutong-mqtt.v1` | 宇通 MQTT 完整 RAW envelope | VIN 或 vehicle_key | -| `vehicle.event.unified.v1` | 统一轻量事件,用于业务消费 | VIN 或 vehicle_key | - -接入层必须先写 RAW topic,再写 unified event。后续消费者只依赖 Kafka,不直接依赖接入进程内存。 - -发布可靠性: - -- Kafka 生产端必须开启同步写入,RAW 写成功后才允许写 unified event。 -- Kafka 写入必须支持可配置重试、单次写超时和短退避,默认值为 `KAFKA_PUBLISH_ATTEMPTS=3`、`KAFKA_PUBLISH_TIMEOUT_MS=3000`、`KAFKA_PUBLISH_BACKOFF_MS=100`。 -- gateway 支持本地磁盘 spool/WAL,配置 `KAFKA_SPOOL_DIR` 后启用;Kafka 长时间不可用时,发布失败的 envelope 先原子写入本地 JSON 文件。 -- spool 补发按文件名顺序执行,补发成功后删除文件;默认补发间隔由 `KAFKA_SPOOL_REPLAY_INTERVAL_MS=1000` 控制。 -- 如果 RAW 写 Kafka 失败并已落盘,同一个 event 的 unified event 不允许抢先写 Kafka,也必须进入 spool,确保恢复后按 RAW -> unified 顺序补发。 -- Kafka topic 不允许自动创建,topic 和分区数由部署脚本或运维初始化,避免生产拼写错误造成隐性分流。 - -## TDengine 数据库设计 - -如果现有 TDengine 库不符合目标,可以新建库并迁移。建议库名: - -```sql -CREATE DATABASE IF NOT EXISTS lingniu_vehicle_ts KEEP 7300 DURATION 10 BUFFER 256; -``` - -### raw_frames - -保存完整 RAW 和完整 parsed JSON。 - -```sql -CREATE STABLE IF NOT EXISTS raw_frames ( - ts TIMESTAMP, - frame_id NCHAR(64), - event_id NCHAR(64), - message_id INT, - event_time TIMESTAMP, - received_at TIMESTAMP, - raw_size_bytes INT, - raw_hex BINARY(16374), - raw_text BINARY(16374), - parsed_json BINARY(16374), - fields_json BINARY(4096), - parse_status NCHAR(16), - parse_error BINARY(1024), - source_endpoint NCHAR(128) -) TAGS ( - protocol NCHAR(32), - vehicle_key NCHAR(64), - vin NCHAR(32), - phone NCHAR(32), - device_id NCHAR(64) -); -``` - -说明: - -- 子表按 `protocol + vehicle_key hash` 创建。 -- RAW 查询优先按 tags 和时间范围过滤。 -- `parsed_json` 是完整协议字段;查询 API 可选择是否返回,避免默认大字段拖慢。 - -### vehicle_locations - -只保存位置查询核心字段。 - -```sql -CREATE STABLE IF NOT EXISTS vehicle_locations ( - ts TIMESTAMP, - event_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - longitude DOUBLE, - latitude DOUBLE, - altitude_m DOUBLE, - speed_kmh DOUBLE, - direction_deg INT, - alarm_flag BIGINT, - status_flag BIGINT, - total_mileage_km DOUBLE -) TAGS ( - protocol NCHAR(32), - vehicle_key NCHAR(64), - vin NCHAR(32), - phone NCHAR(32), - device_id NCHAR(64) -); -``` - -### vehicle_mileage_points - -保存总里程采样点,用于回放重算和查询。 - -```sql -CREATE STABLE IF NOT EXISTS vehicle_mileage_points ( - ts TIMESTAMP, - event_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - total_mileage_km DOUBLE, - speed_kmh DOUBLE, - longitude DOUBLE, - latitude DOUBLE -) TAGS ( - protocol NCHAR(32), - vehicle_key NCHAR(64), - vin NCHAR(32), - phone NCHAR(32), - device_id NCHAR(64) -); -``` - -## MySQL 数据库设计 - -MySQL 只保存配置、身份映射、808 注册鉴权和每日统计窄表。 - -### vehicle_identity_binding - -用于把外部标识定位到 VIN。 - -```sql -CREATE TABLE vehicle_identity_binding ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - vin VARCHAR(32) NOT NULL, - plate VARCHAR(32) NULL, - phone VARCHAR(32) NULL, - device_id VARCHAR(64) NULL, - remark VARCHAR(255) NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - UNIQUE KEY uk_vin (vin), - KEY idx_plate (plate), - KEY idx_phone (phone), - KEY idx_device_id (device_id) -); -``` - -### jt808_registration - -只记录 808 独有的注册、鉴权和在线身份信息。 - -```sql -CREATE TABLE jt808_registration ( - phone VARCHAR(32) PRIMARY KEY, - vin VARCHAR(32) NULL, - plate VARCHAR(32) NULL, - device_id VARCHAR(64) NULL, - manufacturer_id VARCHAR(32) NULL, - terminal_model VARCHAR(64) NULL, - terminal_id VARCHAR(64) NULL, - auth_code VARCHAR(128) NULL, - source_endpoint VARCHAR(128) NULL, - first_registered_at DATETIME NULL, - latest_registered_at DATETIME NULL, - latest_authed_at DATETIME NULL, - latest_seen_at DATETIME NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - KEY idx_vin (vin), - KEY idx_plate (plate), - KEY idx_device_id (device_id) -); -``` - -### vehicle_daily_metric - -32960 和 808 的每日里程、每日总里程都进入同一张窄表。 - -```sql -CREATE TABLE vehicle_daily_metric ( - id BIGINT PRIMARY KEY AUTO_INCREMENT, - vin VARCHAR(32) NOT NULL, - stat_date DATE NOT NULL, - protocol VARCHAR(32) NOT NULL, - metric_key VARCHAR(64) NOT NULL, - metric_value DECIMAL(18,3) NOT NULL, - metric_unit VARCHAR(16) NOT NULL, - first_total_mileage_km DECIMAL(18,3) NULL, - latest_total_mileage_km DECIMAL(18,3) NULL, - sample_count BIGINT NOT NULL DEFAULT 0, - calculation_method VARCHAR(64) NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - UNIQUE KEY uk_daily_metric (vin, stat_date, protocol, metric_key), - KEY idx_stat_date (stat_date), - KEY idx_protocol_metric (protocol, metric_key) -); -``` - -指标: - -- `daily_mileage_km = max(total_mileage_km) - min(total_mileage_km)` -- `daily_total_mileage_km = max(total_mileage_km)` - -统计消费者可幂等重放。每个样本 upsert 时只维护当天 `min/max total_mileage_km`,不依赖进程内状态。 - -## Redis 实时数据设计 - -Redis 保存可从 Kafka 重建的数据。 - -| Key | 内容 | -|---|---| -| `vehicle:online:{vin}` | 在线状态、最后活跃时间、协议列表 | -| `vehicle:latest:{vin}` | 合并后的 VIN 最新快照 | -| `vehicle:latest:{vin}:{protocol}` | 单协议最新快照 | -| `vehicle:last_seen` | VIN 到最后接收时间的 sorted set | - -合并规则: - -- 位置以最新 event_time 为准。 -- 总里程取最新上报值,不做递增假设。 -- 32960 多帧字段按 field timestamp 合并。 -- 808 和 MQTT 不覆盖其他协议独有字段。 -- Redis TTL 用于在线判断,不作为数据删除依据。 - -## 数据流 - -```mermaid -flowchart LR - source["车辆 / 平台"] --> gateway["Go gateway"] - gateway --> parser["protocol parser"] - parser --> identity["identity resolver"] - identity --> kafkaRaw["Kafka RAW topics"] - identity --> kafkaEvent["Kafka unified event"] - kafkaRaw --> history["history-writer"] - kafkaRaw --> stats["stat-writer"] - kafkaEvent --> realtime["realtime consumer"] - history --> td["TDengine raw_frames / locations / mileage_points"] - stats --> mysql["MySQL vehicle_daily_metric"] - realtime --> redis["Redis latest state"] -``` - -## 错误处理 - -- 协议坏帧仍写 RAW topic,`parse_status=BAD_FRAME`。 -- 可部分解析的帧写 `parse_status=PARTIAL`,保留 parse error。 -- Kafka 写失败时接入层先按配置重试;重试耗尽后不写 unified event,必须打错误日志和 metrics。 -- 启用 `KAFKA_SPOOL_DIR` 后,Kafka 重试耗尽会落本地 spool;如果 spool 写失败,才视为接入层最终失败。 -- TDengine 写失败不提交 Kafka offset。 -- MySQL 统计写失败不提交 Kafka offset。 -- Redis 写失败不影响历史和统计,但要通过 metrics 暴露。 -- 808 相同 phone 新连接时,旧连接应被替换并记录 latest_seen。 - -## 部署设计 - -生产 ECS 运行 Go 二进制或容器: - -- `vehicle-gateway` -- `vehicle-history-writer` -- `vehicle-stat-writer` -- `vehicle-realtime-api` - -配置来源: - -- 环境变量优先。 -- Nacos 可作为后续配置中心,但 Go 第一阶段必须能只靠环境变量启动,便于 Portainer 部署和故障恢复。 - -TDengine 连接: - -- 优先 WebSocket `taosWS`。 -- 若当前 ECS 只开放原生端口,可短期兼容 `taosSql`,但代码配置必须显式标记为兼容模式。 - -## 验证计划 - -### 本地验证 - -- 用样例 808 报文验证: - - phone BCD 解析。 - - 0200 经纬度、速度、方向、时间。 - - 表 27 `0x01` GPS 总里程。 -- 用真实 32960 报文验证: - - 登录应答正确。 - - 实时帧和补发帧都能写 RAW。 - - 位置和总里程字段进入统一 fields。 -- 用宇通 MQTT payload 验证: - - payload 完整保存。 - - 核心字段归一化。 -- 不连 Kafka 时可输出 JSON log。 -- 连接 Kafka 时 RAW topic 可消费到 envelope。 - -### ECS 验证 - -- 部署前先旁路接收或短窗口切流。 -- 验证端口: - - `32960` - - `808` - - MQTT 订阅连接。 -- 验证 Kafka topic 有持续写入。 -- 验证 TDengine: - - `raw_frames` 有三种协议数据。 - - `vehicle_locations` 能按 VIN、协议、时间分页查询。 - - `vehicle_mileage_points` 有 32960 和 808 总里程采样。 -- 验证 MySQL: - - `vehicle_daily_metric` 有 32960 和 808 的 `daily_mileage_km`、`daily_total_mileage_km`。 -- 验证 Redis: - - 查询 VIN 在线。 - - 查询 VIN 合并实时快照。 - - 查询 VIN 分协议实时快照。 - -## 验收标准 - -- Go gateway 能在 ECS 上接收真实 32960、808、宇通 MQTT 数据。 -- 三种协议 RAW 都进入 Kafka。 -- Kafka 短暂写失败时,gateway 按配置重试;单元测试覆盖首次失败后成功和重试耗尽返回错误。 -- Kafka 长时间不可用时,gateway 可把 RAW/unified 写入本地 spool;单元测试覆盖 RAW 已落盘时 unified 也落盘、恢复后按顺序补发并删除文件。 -- 三种协议 RAW 和 parsed JSON 都进入 TDengine。 -- 32960 和 808 的位置进入 `vehicle_locations`。 -- 32960 和 808 的总里程采样进入 `vehicle_mileage_points`。 -- 32960 和 808 的每日里程、每日总里程进入 MySQL 窄表。 -- Redis 可以查询 VIN 在线和实时数据。 -- 任一消费者宕机后可通过 Kafka offset 继续恢复。 -- 查询和统计验证使用 ECS 上的真实数据完成。 - -## 实施顺序 - -1. 创建 `go/vehicle-gateway` 单 module,迁移现有 Go 原型中可用的解析、Kafka、TDengine、MySQL、Redis 代码。 -2. 先写协议解析单元测试,覆盖 808 样例报文和 32960 核心字段。 -3. 完成统一 envelope 和 Kafka sink。 -4. 完成 TDengine schema bootstrap 与 history writer。 -5. 完成 MySQL schema bootstrap 与 stat writer。 -6. 完成 Redis realtime writer/API。 -7. 本地启动 gateway,用 ECS 转发流量验证。 -8. 构建 Linux 镜像,部署 ECS。 -9. 按验收标准逐项验证并记录证据。 diff --git a/docs/target-architecture.md b/docs/target-architecture.md deleted file mode 100644 index 2f636c4a..00000000 --- a/docs/target-architecture.md +++ /dev/null @@ -1,202 +0,0 @@ -# Target Architecture - -This document records the production target for `lingniu-vehicle-ingest`. -The project should stay small at runtime: protocol apps ingest and publish, -history consumes and indexes, analytics derives metrics, and business systems -read through explicit APIs or Kafka. - -Only supported message backbone: Kafka. - -## Active Scope - -Active production protocols: - -| Protocol | Runtime app | Event topic | Raw topic | -|---|---|---|---| -| GB/T 32960 | `gb32960-ingest-app` | `vehicle.event.gb32960.v1` | `vehicle.raw.gb32960.v1` | -| JT/T 808 | `jt808-ingest-app` | `vehicle.event.jt808.v1` | `vehicle.raw.jt808.v1` | -| Yutong MQTT | `yutong-mqtt-app` | `vehicle.event.mqtt-yutong.v1` | `vehicle.raw.mqtt-yutong.v1` | - -Xinda Push source, Maven profile, deployment bindings, and history consumers -are removed. New optimization work should target the active protocols above. - -JSATL12 attachment upload is also outside the default production reactor. Keep -it available through the `optional-attachments` profile until an attachment app -is explicitly deployed. - -## Goals - -- Keep protocol IO, raw archive, history, latest-state, and statistics as - separate responsibilities. -- Publish normalized Kafka envelopes with VIN as the partition key whenever a - VIN is known. -- Store complete raw payload metadata and parsed JSON in TDengine `raw_frames`. -- Store query-friendly location rows separately from raw payload JSON. -- Keep derived statistics in metric tables, not protocol-specific daily tables. -- Use Redis only for optional latest-state APIs that can be rebuilt from Kafka. -- Keep optional compatibility modules outside the production hot path. - -## Non-Goals - -- Ingest apps do not write business tables. -- Ingest apps do not serve history query APIs. -- Redis is not the long-term historical store. -- File-based event indexes are not part of the current build surface. -- `telemetry_fields` parsing and field-trend APIs are owned by a separate - field parsing service. This project stores RAW parsed JSON plus compact - location rows and does not expose a telemetry-fields history API. - -## Runtime Responsibilities - -### Protocol Apps - -Apps: - -- `gb32960-ingest-app` -- `jt808-ingest-app` -- `yutong-mqtt-app` - -Responsibilities: - -- Accept protocol traffic. -- Decode, authenticate, acknowledge, and maintain sessions where the protocol - requires it. -- Archive raw bytes through `sink-archive` when bytes are available. -- Publish parsed event envelopes to Kafka. -- Publish raw envelopes to Kafka. -- Resolve vehicle identity through the shared MySQL identity binding table. - -Protocol apps must stay independent from TDengine history readers, Redis state -repositories, and statistic calculators. - -### Event Contract - -The Kafka sink owns the protobuf envelope and consumer plumbing. -Consumers should use `EnvelopeConsumerProcessor` so invalid protobuf, skipped -envelopes, and downstream failures are converted to structured results and DLQ -records instead of blocking a partition. - -Core identity fields: - -| Field | Meaning | -|---|---| -| `event_id` | Parsed event idempotency key | -| `trace_id` | Cross-service trace id | -| `vin` | Vehicle id and Kafka partition key | -| `source` | Source protocol, for example `GB32960`, `JT808`, `YUTONG_MQTT` | -| `event_time_ms` | Device event time | -| `ingest_time_ms` | Platform receive time | -| `raw_uri` | `archive://...` reference to raw bytes when available | - -Full-field telemetry uses `TelemetrySnapshot` fields with stable keys such as -`total_mileage_km`, `speed_kmh`, `longitude`, and `latitude`. - -### History App - -App: `vehicle-history-app` - -Responsibilities: - -- Consume active protocol event topics. -- Consume active protocol raw topics. -- Write raw frame rows into TDengine `raw_frames`. -- Write compact location rows into TDengine location tables. -- Keep `payloadJson.parsed` on raw rows for full raw inspection. -- Expose paged history APIs for raw frames and locations. -- Keep specialized APIs off by default unless explicitly enabled. - -Production default: - -- `TDENGINE_HISTORY_ENABLED=true` in deployment. -- `EVENT_FILE_STORE_ENABLED=false`. - -File-based event indexes have been removed; this app should keep history -queries on TDengine and raw replay on `archive://...` references. - -### Analytics App - -App: `vehicle-analytics-app` - -Responsibilities: - -- Consume JT808 event envelopes. -- Calculate 808 daily mileage from the reported GPS total mileage only: - -```text -daily_mileage_km = max_total_mileage_km - min_total_mileage_km -calculation_method = JT808_TOTAL_MILEAGE_DIFF -``` - -- Store the metric in `vehicle_stat_metric` with - `metric_key = daily_mileage_km`; the local-day minimum and maximum GPS total - mileage used for the subtraction stay on the same metric row as calculation - source columns. - -Runtime state: none outside `vehicle_stat_metric`. There is no JT808-specific -daily mileage table. Restart recovery reads the same metric row. - -### Latest State - -Module: `vehicle-state-service` - -This module is outside the default production reactor. Build it with -`-Poptional-latest-state` when Redis latest-state APIs are explicitly needed. - -Responsibilities: - -- Consume normalized Kafka envelopes. -- Maintain latest state, location, and safety snapshots in Redis. -- Serve latest-state APIs for operational screens. - -Redis state must be rebuildable from Kafka replay and should not be used as the -source of truth for historical queries. - -## Data Flow - -Default production flow: - -```mermaid -flowchart LR - vehicle["Vehicles / Platforms"] --> ingest["Protocol apps"] - ingest --> archive["sink-archive
raw bytes"] - ingest --> kafka["Kafka
event + raw envelopes"] - kafka --> history["vehicle-history-app"] - kafka --> analytics["vehicle-analytics-app"] - history --> tdengine["TDengine
raw_frames + locations"] - analytics --> mysql["MySQL
vehicle_stat_metric"] -``` - -Optional latest-state flow, enabled only with `-Poptional-latest-state`, consumes -the same Kafka envelopes and rebuilds Redis snapshots outside the default -production deployment. - -## Failure Handling - -- Protocol apps continue accepting traffic when history, analytics, or Redis - consumers are unavailable. -- Kafka producer failures are handled by sink retry/circuit-breaker behavior and - DLQ topics where configured. -- History consumers should commit offsets only after TDengine writes succeed. -- Analytics daily mileage state can recover from `vehicle_stat_metric` or Kafka - replay. -- Raw archive failures must be observable, but event publication and history - indexing remain separate concerns. - -## Acceptance Criteria - -- Active production deployment contains GB32960, JT808, Yutong MQTT, - vehicle-history, and vehicle-analytics apps. -- Xinda Push source and deployment bindings are removed. -- JSATL12 is absent from the default Maven reactor and available through - `optional-attachments`. -- vehicle-state-service is absent from the default Maven reactor and available - through `optional-latest-state`. -- raw-archive-store prototype has been removed; raw bytes are written through - `sink-archive`. -- History APIs read from TDengine; file-based event indexes are not maintained. -- Raw frame queries can return complete parsed JSON through `payloadJson.parsed`. -- Location queries page over compact rows and reference raw records instead of - duplicating full raw JSON. -- JT808 daily mileage is stored only in `vehicle_stat_metric`. -- Runtime state needed for 808 mileage is also in `vehicle_stat_metric`. -- Build validation covers the active modules and their composition tests. diff --git a/docs/vehicle-telemetry-internal-fields.md b/docs/vehicle-telemetry-internal-fields.md deleted file mode 100644 index 3ecc50ad..00000000 --- a/docs/vehicle-telemetry-internal-fields.md +++ /dev/null @@ -1,146 +0,0 @@ -# Vehicle Telemetry Internal Fields - -This document defines the internal telemetry field model for Lingniu hydrogen -vehicle operations. Protocol fields such as GB/T 32960, JT808, MQTT, and vendor -extensions must be mapped into these fields before storage, statistics, or -business reporting. - -## Design Rules - -- Business statistics use internal fields, not protocol field names. -- Every internal field has one stable unit and one business meaning. -- Protocol-specific raw values remain traceable through `source_protocol`, - `protocol_version`, `metadata`, and raw archive references. -- Core operational fields are stored as typed columns or typed payload fields. -- Unstable vendor-specific fields can stay in extension metadata until promoted. -- Hydrogen leak safety is a first-class critical safety event, not a generic - alarm count. - -## Identity And Time - -| Internal field | Type | Unit | Meaning | GB/T 32960 source | -|---|---:|---|---|---| -| `vin` | string | - | Vehicle VIN and partition key | header VIN | -| `event_time` | instant | - | Device collection time | command body timestamp | -| `ingest_time` | instant | - | Platform receive time | ingest server clock | -| `source_protocol` | enum | - | Source adapter | `GB32960` | -| `protocol_version` | string | - | Source protocol version | `V2016` / `V2025` | - -## Real-Time Vehicle State - -| Internal field | Type | Unit | Meaning | GB/T 32960 source | -|---|---:|---|---|---| -| `vehicle_state` | enum | - | Started, shutdown, other, invalid | vehicle block `vehicleState` | -| `charging_state` | enum | - | Charging state | vehicle block `chargingState` | -| `running_mode` | enum | - | Electric, hybrid, fuel, other | vehicle block `runningMode` | -| `speed_kmh` | double | km/h | Current speed | vehicle block `speedKmh` | -| `total_mileage_km` | double | km | Odometer mileage | vehicle block `totalMileageKm` | -| `gear_level` | integer | - | Gear value | vehicle block `gearRaw` low 4 bits | -| `accelerator_pedal` | double | % | Accelerator pedal opening | V2016 vehicle block | -| `brake_pedal` | double | % | Brake pedal opening | V2016 vehicle block | - -## Location - -| Internal field | Type | Unit | Meaning | GB/T 32960 source | -|---|---:|---|---|---| -| `longitude` | double | deg | Longitude | position block | -| `latitude` | double | deg | Latitude | position block | -| `altitude_m` | double | m | Altitude when available | extension or other protocol | -| `direction_deg` | double | deg | Direction when available | extension or other protocol | -| `location_status_raw` | long | - | Raw location status bits | position block `statusFlag` | - -## Battery And Electricity - -| Internal field | Type | Unit | Meaning | GB/T 32960 source | -|---|---:|---|---|---| -| `battery_soc` | double | % | Battery SOC | vehicle block `socPercent` | -| `battery_voltage_v` | double | V | Battery total voltage | vehicle block `totalVoltageV` | -| `battery_current_a` | double | A | Battery total current | vehicle block `totalCurrentA` | -| `battery_power_kw` | double | kW | Derived instantaneous battery power | `voltage * current / 1000` in statistics | -| `battery_temperature_max_c` | integer | C | Max battery temperature | temperature or extreme blocks | -| `battery_temperature_min_c` | integer | C | Min battery temperature | temperature or extreme blocks | -| `daily_electricity_kwh` | double | kWh | Daily electricity usage | statistics time integral | - -Daily electricity must be computed in the statistics layer from typed telemetry -points. Do not accumulate protocol raw values directly. - -## Hydrogen And Fuel Cell - -| Internal field | Type | Unit | Meaning | GB/T 32960 source | -|---|---:|---|---|---| -| `fc_voltage_v` | double | V | Fuel cell voltage | fuel cell block | -| `fc_current_a` | double | A | Fuel cell current | fuel cell block | -| `fc_temp_c` | double | C | Fuel cell or hydrogen system temperature | fuel cell block | -| `hydrogen_remaining_kg` | double | kg | Remaining hydrogen mass | vendor extension or capacity conversion | -| `hydrogen_remaining_percent` | double | % | Remaining hydrogen percent | V2025 fuel cell block | -| `hydrogen_high_pressure_mpa` | double | MPa | Hydrogen high pressure | fuel cell block | -| `hydrogen_low_pressure_mpa` | double | MPa | Hydrogen low pressure when available | extension or other protocol | -| `daily_hydrogen_kg` | double | kg | Daily hydrogen consumed | statistics layer | -| `hydrogen_mileage_efficiency` | double | km/kg | Mileage per kg hydrogen | statistics layer | - -Daily hydrogen usage should be calculated from adjacent hydrogen remaining -values. A rising value indicates refueling and must not be counted as negative -consumption. - -## Hydrogen Tank Safety - -| Internal field | Type | Unit | Meaning | GB/T 32960 source | -|---|---:|---|---|---| -| `safety_category` | enum | - | `GENERAL`, `TANK_PRESSURE`, `TANK_TEMPERATURE`, `HYDROGEN_LEAK` | alarm bit classification | -| `hydrogen_leak_detected` | boolean | - | Whether hydrogen leak is detected | general alarm bit `HYDROGEN_LEAK` | -| `hydrogen_leak_level` | enum | - | `NONE`, `WARNING`, `CRITICAL`, `UNKNOWN` | internal rule | -| `hydrogen_leak_action_required` | boolean | - | Whether immediate handling is required | internal rule | -| `tank_pressure_status` | enum | - | Normal, warning, critical | hydrogen pressure bit or threshold rule | -| `tank_temperature_status` | enum | - | Normal, warning, critical | hydrogen temp bit or threshold rule | - -Safety rule: - -```text -If HYDROGEN_LEAK is present: - alarm.level = CRITICAL - safety_category = HYDROGEN_LEAK - hydrogen_leak_detected = true - hydrogen_leak_level = CRITICAL - hydrogen_leak_action_required = true -``` - -Hydrogen leak overrides otherwise normal vehicle, speed, battery, or hydrogen -state. It must be counted independently from generic alarms. - -## Daily Operation Statistics - -Daily statistics are generated by `stat_date + vin`. - -| Internal field | Type | Unit | Meaning | -|---|---:|---|---| -| `stat_date` | date | - | Statistics date | -| `vin` | string | - | Vehicle VIN | -| `first_event_time` | instant | - | First valid telemetry time | -| `last_event_time` | instant | - | Last valid telemetry time | -| `online_minutes` | double | min | Online duration | -| `running_minutes` | double | min | Running duration | -| `daily_mileage_km` | double | km | Daily mileage | -| `daily_electricity_kwh` | double | kWh | Daily electricity usage | -| `daily_hydrogen_kg` | double | kg | Daily hydrogen usage | -| `hydrogen_added_kg` | double | kg | Refueled hydrogen | -| `km_per_kg_hydrogen` | double | km/kg | Hydrogen efficiency | -| `kwh_per_100km` | double | kWh/100km | Electricity efficiency | -| `alarm_count` | integer | - | Generic alarm count | -| `tank_safety_alarm_count` | integer | - | Tank safety alarm count | -| `hydrogen_leak_alarm_count` | integer | - | Hydrogen leak alarm count | -| `hydrogen_leak_duration_seconds` | long | s | Hydrogen leak duration | -| `data_quality_level` | enum | - | Good, partial, bad | - -## Implementation Status - -- `RealtimePayload` already uses internal operational field names for state, - location, battery, fuel cell, and hydrogen fields. -- `AlarmPayload` carries internal hydrogen safety fields. -- `Gb32960EventMapper` maps GB/T 32960 alarm bits into internal safety fields. -- `TelemetrySnapshot` publishes full-field internal telemetry values through the - Kafka protobuf envelope. -- `event-history-service`, `vehicle-state-service`, and `vehicle-stat-service` - consume the same full-field telemetry snapshot contract. -- `event-history-service` CSV export can flatten selected internal fields with - `fields=field_a,field_b`; media archive references are exposed through - `rawArchiveUri` by falling back to `MediaMeta.archiveRef`. diff --git a/modules/apps/command-gateway/pom.xml b/modules/apps/command-gateway/pom.xml deleted file mode 100644 index 10eeedae..00000000 --- a/modules/apps/command-gateway/pom.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - command-gateway - command-gateway - - 下行命令网关:HTTP → 设备。复用 session-core 的 CommandDispatcher, - 覆盖原 JT808Controller / JT1078Controller 的 43 个端点(PoC 阶段先落核心子集)。 - - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - session-core - - - com.lingniu.ingest - protocol-jt808 - - - com.lingniu.ingest - protocol-jt1078 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.springframework.boot - spring-boot-test - test - - - diff --git a/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfiguration.java b/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfiguration.java deleted file mode 100644 index c987f13b..00000000 --- a/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfiguration.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.lingniu.ingest.gateway; - -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.web.servlet.DispatcherServlet; - -@AutoConfiguration -@ConditionalOnClass(DispatcherServlet.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.command-gateway", name = "enabled", havingValue = "true") -@ComponentScan(basePackageClasses = TerminalCommandController.class) -public class CommandGatewayAutoConfiguration { - // 只扫描 REST 下行命令网关;不参与 32960 接收、RAW 冷存或历史查询链路。 -} diff --git a/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandResult.java b/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandResult.java deleted file mode 100644 index d9ed98e0..00000000 --- a/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/CommandResult.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.lingniu.ingest.gateway; - -/** - * 统一命令响应 envelope。 - * - * @param success 是否成功 - * @param code 业务状态码(0=成功,其它=失败原因) - * @param message 人类可读消息 - * @param data 可选负载(设备应答对象、JSON 节点等) - */ -public record CommandResult(boolean success, int code, String message, Object data) { - - public static CommandResult ok(Object data) { - return new CommandResult(true, 0, "ok", data); - } - - public static CommandResult notImplemented(String hint) { - return new CommandResult(false, 501, "not_implemented: " + hint, null); - } - - public static CommandResult failure(int code, String message) { - return new CommandResult(false, code, message, null); - } -} diff --git a/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/TerminalCommandController.java b/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/TerminalCommandController.java deleted file mode 100644 index 5215c4d1..00000000 --- a/modules/apps/command-gateway/src/main/java/com/lingniu/ingest/gateway/TerminalCommandController.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.lingniu.ingest.gateway; - -import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt1078.downlink.Jt1078Commands; -import com.lingniu.ingest.session.CommandDispatcher; -import com.lingniu.ingest.session.DeviceSession; -import com.lingniu.ingest.session.SessionStore; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.stream.Collectors; - -/** - * 下行命令 REST 入口。命令通过 {@link CommandDispatcher} 发往协议模块, - * 由 protocol-jt808 的 {@code Jt808CommandDispatcher} 实际发送到终端。 - * - *

路径参数 {@code clientId} 可以是 sessionId / phone / vin, - * 由 {@link SessionStore} 三索引解析。 - */ -@RestController -@RequestMapping("/terminal") -public class TerminalCommandController { - - private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(20); - - private final SessionStore sessions; - private final CommandDispatcher dispatcher; - - public TerminalCommandController(SessionStore sessions, CommandDispatcher dispatcher) { - this.sessions = sessions; - this.dispatcher = dispatcher; - } - - @GetMapping("/session") - public CommandResult session(@RequestParam String clientId) { - Optional s = resolveSession(clientId); - return s.map(CommandResult::ok) - .orElseGet(() -> CommandResult.failure(404, "session not found: " + clientId)); - } - - @GetMapping("/location") - public CommandResult queryLocation(@RequestParam String clientId) { - return awaitSync(clientId, Jt808Commands.queryLocation(), "query-location"); - } - - @GetMapping("/parameters") - public CommandResult queryParameters(@RequestParam String clientId) { - return awaitSync(clientId, Jt808Commands.queryParameters(), "query-parameters"); - } - - @PutMapping("/parameters") - public CommandResult setParameters(@RequestParam String clientId, - @RequestBody Map body) { - List items = body.entrySet().stream() - .map(e -> new Jt808Commands.ParamItem( - parseId(e.getKey()), - e.getValue() == null ? new byte[0] : e.getValue().getBytes(StandardCharsets.UTF_8))) - .collect(Collectors.toList()); - return awaitSync(clientId, Jt808Commands.setParameters(items), "set-parameters"); - } - - @PostMapping("/control") - public CommandResult terminalControl(@RequestParam String clientId, - @RequestBody Map body) { - int word = Integer.parseInt(body.getOrDefault("command", "0").toString()); - String params = body.getOrDefault("params", "").toString(); - return awaitSync(clientId, Jt808Commands.terminalControl(word, params), "terminal-control"); - } - - @PostMapping("/ack") - public CommandResult ack(@RequestParam String clientId, @RequestBody Map body) { - int ackSerial = body.getOrDefault("ackSerial", 0); - int ackMsgId = body.getOrDefault("ackMsgId", 0); - int result = body.getOrDefault("result", 0); - return fireAndForget(clientId, Jt808Commands.platformAck(ackSerial, ackMsgId, result), "platform-ack"); - } - - @GetMapping("/attributes") - public CommandResult attributes(@RequestParam String clientId) { - return awaitSync(clientId, Jt808Commands.queryAttributes(), "query-attributes"); - } - - @DeleteMapping("/area") - public CommandResult deleteArea(@RequestParam String clientId, @RequestParam String ids) { - return awaitSync(clientId, Jt808Commands.deleteArea(parseIds(ids)), "delete-area"); - } - - @PostMapping("/alarm_ack") - public CommandResult alarmAck(@RequestParam String clientId, @RequestBody Map body) { - int responseSerialNo = Integer.parseInt(body.getOrDefault("responseSerialNo", "0").toString()); - long type = parseLong(body.getOrDefault("type", "0").toString()); - return fireAndForget(clientId, Jt808Commands.alarmAck(responseSerialNo, type), "alarm-ack"); - } - - @GetMapping("/jt1078/attributes") - public CommandResult jt1078Attributes(@RequestParam String clientId) { - return awaitSync(clientId, Jt1078Commands.queryMediaAttributes(), "jt1078-query-media-attributes"); - } - - @PostMapping("/jt1078/realtime") - public CommandResult jt1078RealtimePlay(@RequestParam String clientId, - @RequestBody Map body) { - return awaitSync(clientId, Jt1078Commands.realtimePlay( - stringValue(body, "ip", ""), - intValue(body, "tcpPort", 0), - intValue(body, "udpPort", 0), - intValue(body, "channelNo", 0), - intValue(body, "mediaType", 0), - intValue(body, "streamType", 0)), "jt1078-realtime-play"); - } - - @PostMapping("/jt1078/realtime_control") - public CommandResult jt1078RealtimeControl(@RequestParam String clientId, - @RequestBody Map body) { - return awaitSync(clientId, Jt1078Commands.realtimeControl( - intValue(body, "channelNo", 0), - intValue(body, "command", 0), - intValue(body, "closeType", 0), - intValue(body, "streamType", 0)), "jt1078-realtime-control"); - } - - @PostMapping("/jt1078/history") - public CommandResult jt1078HistoryPlay(@RequestParam String clientId, - @RequestBody Map body) { - return awaitSync(clientId, Jt1078Commands.historyPlay( - stringValue(body, "ip", ""), - intValue(body, "tcpPort", 0), - intValue(body, "udpPort", 0), - intValue(body, "channelNo", 0), - intValue(body, "mediaType", 0), - intValue(body, "streamType", 0), - intValue(body, "storageType", 0), - intValue(body, "playbackMode", 0), - intValue(body, "playbackSpeed", 0), - stringValue(body, "startTime", ""), - stringValue(body, "endTime", "")), "jt1078-history-play"); - } - - @PostMapping("/jt1078/history_control") - public CommandResult jt1078HistoryControl(@RequestParam String clientId, - @RequestBody Map body) { - return awaitSync(clientId, Jt1078Commands.historyControl( - intValue(body, "channelNo", 0), - intValue(body, "playbackMode", 0), - intValue(body, "playbackSpeed", 0), - stringValue(body, "playbackTime", "")), "jt1078-history-control"); - } - - @PostMapping("/jt1078/resource_search") - public CommandResult jt1078ResourceSearch(@RequestParam String clientId, - @RequestBody Map body) { - return awaitSync(clientId, Jt1078Commands.resourceSearch( - intValue(body, "channelNo", 0), - stringValue(body, "startTime", ""), - stringValue(body, "endTime", ""), - longValue(body, "warnBit1", 0), - longValue(body, "warnBit2", 0), - intValue(body, "mediaType", 0), - intValue(body, "streamType", 0), - intValue(body, "storageType", 0)), "jt1078-resource-search"); - } - - @PostMapping("/jt1078/file_upload") - public CommandResult jt1078FileUpload(@RequestParam String clientId, - @RequestBody Map body) { - return awaitSync(clientId, Jt1078Commands.fileUpload( - stringValue(body, "ip", ""), - intValue(body, "port", 0), - stringValue(body, "username", ""), - stringValue(body, "password", ""), - stringValue(body, "path", ""), - intValue(body, "channelNo", 0), - stringValue(body, "startTime", ""), - stringValue(body, "endTime", ""), - longValue(body, "warnBit1", 0), - longValue(body, "warnBit2", 0), - intValue(body, "mediaType", 0), - intValue(body, "streamType", 0), - intValue(body, "storageType", 0), - intValue(body, "condition", 0)), "jt1078-file-upload"); - } - - @PostMapping("/jt1078/file_upload_control") - public CommandResult jt1078FileUploadControl(@RequestParam String clientId, - @RequestBody Map body) { - return awaitSync(clientId, Jt1078Commands.fileUploadControl( - intValue(body, "responseSerialNo", 0), - intValue(body, "command", 0)), "jt1078-file-upload-control"); - } - - // ===== helpers ===== - - private Optional resolveSession(String clientId) { - // 运维侧常拿到 VIN、手机号或 sessionId 中任意一个;这里按三索引兜底解析到在线连接。 - return sessions.findBySessionId(clientId) - .or(() -> sessions.findByPhone(clientId)) - .or(() -> sessions.findByVin(clientId)); - } - - private CommandResult awaitSync(String clientId, Jt808Commands.DownlinkCommand cmd, String op) { - // 需要终端应答的命令走 request-response,超时时间由网关统一兜底,避免 HTTP 线程无限等待。 - CompletableFuture future = - dispatcher.request(clientId, cmd, Jt808Message.class, DEFAULT_TIMEOUT); - try { - Jt808Message resp = future.join(); - return CommandResult.ok(Map.of( - "messageId", "0x" + Integer.toHexString(resp.header().messageId()), - "serial", resp.header().serialNo(), - "phone", resp.header().phone())); - } catch (CompletionException e) { - Throwable cause = e.getCause() != null ? e.getCause() : e; - return CommandResult.failure(500, op + " failed: " + cause.getMessage()); - } catch (Exception e) { - return CommandResult.failure(500, op + " failed: " + e.getMessage()); - } - } - - private CommandResult fireAndForget(String clientId, Jt808Commands.DownlinkCommand cmd, String op) { - try { - // ACK 类命令不等待终端回包;只确认写入在线 session 对应的 channel。 - dispatcher.notify(clientId, cmd).join(); - return CommandResult.ok(Map.of("op", op)); - } catch (Exception e) { - return CommandResult.failure(500, op + " failed: " + e.getMessage()); - } - } - - private static long parseId(String key) { - if (key == null || key.isBlank()) return 0; - String k = key.startsWith("0x") ? key.substring(2) : key; - return Long.parseLong(k, 16); - } - - private static List parseIds(String ids) { - if (ids == null || ids.isBlank()) { - return List.of(); - } - return java.util.Arrays.stream(ids.split(",")) - .map(String::trim) - .filter(s -> !s.isBlank()) - .map(TerminalCommandController::parseLong) - .toList(); - } - - private static long parseLong(String value) { - if (value == null || value.isBlank()) return 0; - String v = value.startsWith("0x") || value.startsWith("0X") ? value.substring(2) : value; - return value.startsWith("0x") || value.startsWith("0X") - ? Long.parseLong(v, 16) - : Long.parseLong(v); - } - - private static String stringValue(Map body, String key, String defaultValue) { - Object value = body.get(key); - return value == null ? defaultValue : value.toString(); - } - - private static int intValue(Map body, String key, int defaultValue) { - Object value = body.get(key); - return value == null ? defaultValue : (int) parseLong(value.toString()); - } - - private static long longValue(Map body, String key, long defaultValue) { - Object value = body.get(key); - return value == null ? defaultValue : parseLong(value.toString()); - } -} diff --git a/modules/apps/command-gateway/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/apps/command-gateway/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index ac81ba27..00000000 --- a/modules/apps/command-gateway/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.gateway.CommandGatewayAutoConfiguration diff --git a/modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfigurationTest.java b/modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfigurationTest.java deleted file mode 100644 index 9f162bbe..00000000 --- a/modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/CommandGatewayAutoConfigurationTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.lingniu.ingest.gateway; - -import com.lingniu.ingest.session.CommandDispatcher; -import com.lingniu.ingest.session.DeviceSession; -import com.lingniu.ingest.session.SessionStore; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.function.UnaryOperator; - -import static org.assertj.core.api.Assertions.assertThat; - -class CommandGatewayAutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(CommandGatewayAutoConfiguration.class)) - .withBean(SessionStore.class, EmptySessionStore::new) - .withBean(CommandDispatcher.class, NoopCommandDispatcher::new); - - @Test - void backsOffByDefault() { - contextRunner.run(context -> assertThat(context).doesNotHaveBean(TerminalCommandController.class)); - } - - @Test - void createsControllerWhenExplicitlyEnabled() { - contextRunner - .withPropertyValues("lingniu.ingest.command-gateway.enabled=true") - .run(context -> assertThat(context).hasSingleBean(TerminalCommandController.class)); - } - - private static final class NoopCommandDispatcher implements CommandDispatcher { - @Override - public CompletableFuture notify(String sessionId, Object command) { - return CompletableFuture.completedFuture(null); - } - - @Override - public CompletableFuture request(String sessionId, - Object command, - Class responseType, - Duration timeout) { - return CompletableFuture.failedFuture(new UnsupportedOperationException("not used")); - } - } - - private static final class EmptySessionStore implements SessionStore { - @Override public void put(DeviceSession session) {} - @Override public Optional findBySessionId(String sessionId) { return Optional.empty(); } - @Override public Optional findByVin(String vin) { return Optional.empty(); } - @Override public Optional findByPhone(String phone) { return Optional.empty(); } - @Override public Optional update(String sessionId, UnaryOperator updater) { - return Optional.empty(); - } - @Override public void remove(String sessionId) {} - @Override public int size() { return 0; } - } -} diff --git a/modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/TerminalCommandControllerTest.java b/modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/TerminalCommandControllerTest.java deleted file mode 100644 index 7f18f127..00000000 --- a/modules/apps/command-gateway/src/test/java/com/lingniu/ingest/gateway/TerminalCommandControllerTest.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.lingniu.ingest.gateway; - -import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.session.CommandDispatcher; -import com.lingniu.ingest.session.DeviceSession; -import com.lingniu.ingest.session.SessionStore; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.function.UnaryOperator; - -import static org.assertj.core.api.Assertions.assertThat; - -class TerminalCommandControllerTest { - - @Test - void queryAttributesDispatchesJt8088107InsteadOfNotImplemented() { - RecordingDispatcher dispatcher = new RecordingDispatcher(); - TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher); - - CommandResult result = controller.attributes("13800138000"); - - assertThat(result.success()).isTrue(); - assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x8107); - assertThat(dispatcher.lastRequestCommand.body()).isEmpty(); - } - - @Test - void alarmAckDispatchesJt8088203WithResponseSerialAndAlarmType() { - RecordingDispatcher dispatcher = new RecordingDispatcher(); - TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher); - - CommandResult result = controller.alarmAck("13800138000", Map.of( - "responseSerialNo", 0x1234, - "type", 0x0000_0001)); - - assertThat(result.success()).isTrue(); - assertThat(dispatcher.lastNotifyCommand.messageId()).isEqualTo(0x8203); - assertThat(dispatcher.lastNotifyCommand.body()).containsExactly( - 0x12, 0x34, - 0x00, 0x00, 0x00, 0x01); - } - - @Test - void deleteAreaDispatchesJt8088601WithCommaSeparatedAreaIds() { - RecordingDispatcher dispatcher = new RecordingDispatcher(); - TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher); - - CommandResult result = controller.deleteArea("13800138000", "1,0x01020304"); - - assertThat(result.success()).isTrue(); - assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x8601); - assertThat(dispatcher.lastRequestCommand.body()).containsExactly( - 0x02, - 0x00, 0x00, 0x00, 0x01, - 0x01, 0x02, 0x03, 0x04); - } - - @Test - void jt1078RealtimePlayDispatches9101ThroughSharedDispatcher() { - RecordingDispatcher dispatcher = new RecordingDispatcher(); - TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher); - - CommandResult result = controller.jt1078RealtimePlay("13800138000", Map.of( - "ip", "10.1.2.3", - "tcpPort", 11078, - "udpPort", 11079, - "channelNo", 2, - "mediaType", 1, - "streamType", 0)); - - assertThat(result.success()).isTrue(); - assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x9101); - assertThat(dispatcher.lastRequestCommand.body()).containsExactly( - 0x08, '1', '0', '.', '1', '.', '2', '.', '3', - 0x2B, 0x46, - 0x2B, 0x47, - 0x02, - 0x01, - 0x00); - } - - @Test - void jt1078ResourceSearchDispatches9205AndAwaitsFileListResponse() { - RecordingDispatcher dispatcher = new RecordingDispatcher(); - TerminalCommandController controller = new TerminalCommandController(new EmptySessionStore(), dispatcher); - - CommandResult result = controller.jt1078ResourceSearch("13800138000", Map.of( - "channelNo", 3, - "startTime", "2026-06-22 08:09:10", - "endTime", "260622180000", - "warnBit1", "0x01020304", - "warnBit2", "0x05060708", - "mediaType", 3, - "streamType", 2, - "storageType", 1)); - - assertThat(result.success()).isTrue(); - assertThat(dispatcher.lastRequestCommand.messageId()).isEqualTo(0x9205); - assertThat(dispatcher.lastRequestCommand.body()).containsExactly( - 0x03, - 0x26, 0x06, 0x22, 0x08, 0x09, 0x10, - 0x26, 0x06, 0x22, 0x18, 0x00, 0x00, - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - 0x03, - 0x02, - 0x01); - } - - private static final class RecordingDispatcher implements CommandDispatcher { - private Jt808Commands.DownlinkCommand lastNotifyCommand; - private Jt808Commands.DownlinkCommand lastRequestCommand; - - @Override - public CompletableFuture notify(String sessionId, Object command) { - this.lastNotifyCommand = (Jt808Commands.DownlinkCommand) command; - return CompletableFuture.completedFuture(null); - } - - @Override - @SuppressWarnings("unchecked") - public CompletableFuture request(String sessionId, Object command, Class responseType, Duration timeout) { - this.lastRequestCommand = (Jt808Commands.DownlinkCommand) command; - Jt808Message response = new Jt808Message( - new Jt808Header(0x0107, 0, 0, false, Jt808Header.ProtocolVersion.V2013, - "13800138000", 1, 0, 0), - new com.lingniu.ingest.protocol.jt808.model.Jt808Body.Raw(0x0107, new byte[0])); - return CompletableFuture.completedFuture((T) response); - } - } - - private static final class EmptySessionStore implements SessionStore { - @Override public void put(DeviceSession session) {} - @Override public Optional findBySessionId(String sessionId) { return Optional.empty(); } - @Override public Optional findByVin(String vin) { return Optional.empty(); } - @Override public Optional findByPhone(String phone) { return Optional.empty(); } - @Override public Optional update(String sessionId, UnaryOperator updater) { return Optional.empty(); } - @Override public void remove(String sessionId) {} - @Override public int size() { return 0; } - } -} diff --git a/modules/apps/gb32960-ingest-app/pom.xml b/modules/apps/gb32960-ingest-app/pom.xml deleted file mode 100644 index 337adec8..00000000 --- a/modules/apps/gb32960-ingest-app/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - gb32960-ingest-app - gb32960-ingest-app - GB32960 protocol ingress runtime: TCP decode, auth, Kafka production, and ACK. - - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - session-core - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - vehicle-identity - - - com.lingniu.ingest - protocol-gb32960 - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - sink-archive - - - org.springframework.boot - spring-boot-starter-web - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-test - test - - - - - gb32960-ingest-app - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java b/modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java deleted file mode 100644 index 35019bbb..00000000 --- a/modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.gb32960app; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Gb32960IngestApplication { - - private static final Logger log = LoggerFactory.getLogger(Gb32960IngestApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(Gb32960IngestApplication.class, args); - } catch (Throwable t) { - log.error("GB32960 ingest application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} diff --git a/modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpoint.java b/modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpoint.java deleted file mode 100644 index 218e08c8..00000000 --- a/modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpoint.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.lingniu.ingest.gb32960app.actuator; - -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ConnectionDiagnostics; -import org.springframework.boot.actuate.endpoint.annotation.Endpoint; -import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; -import org.springframework.stereotype.Component; - -import java.util.Map; - -@Component -@Endpoint(id = "gb32960sessions") -public class Gb32960SessionsEndpoint { - - private final Gb32960ConnectionDiagnostics diagnostics; - - public Gb32960SessionsEndpoint(Gb32960ConnectionDiagnostics diagnostics) { - this.diagnostics = diagnostics; - } - - @ReadOperation - public Map sessions() { - var sessions = diagnostics.activeSessions(); - return Map.of( - "activeCount", sessions.size(), - "sessions", sessions); - } -} diff --git a/modules/apps/gb32960-ingest-app/src/main/resources/application.yml b/modules/apps/gb32960-ingest-app/src/main/resources/application.yml deleted file mode 100644 index ea6c320c..00000000 --- a/modules/apps/gb32960-ingest-app/src/main/resources/application.yml +++ /dev/null @@ -1,137 +0,0 @@ -spring: - application: - name: gb32960-ingest-app - config: - import: - - optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true} - cloud: - nacos: - config: - enabled: ${NACOS_CONFIG_ENABLED:true} - server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: ${NACOS_CONFIG_FILE_EXTENSION:yml} - username: ${NACOS_USERNAME:} - password: ${NACOS_PASSWORD:} - threads: - virtual: - enabled: true - data: - redis: - host: ${REDIS_HOST:127.0.0.1} - port: ${REDIS_PORT:6379} - username: ${REDIS_USERNAME:} - password: ${REDIS_PASSWORD:} - database: ${REDIS_DATABASE:50} - -server: - port: ${HTTP_PORT:20100} - -lingniu: - ingest: - gb32960: - enabled: true - server: - enabled: true - port: ${GB32960_PORT:32960} - boss-threads: ${GB32960_BOSS_THREADS:1} - worker-threads: ${GB32960_WORKER_THREADS:0} - auth: - enabled: ${GB32960_AUTH_ENABLED:false} - case-sensitive: false - whitelist: [] - platforms: - - username: ${GB32960_PLATFORM_USER_HYUNDAI:Hyundai} - password: ${GB32960_PLATFORM_PWD_HYUNDAI:} - allowed-ips: - - ${GB32960_PLATFORM_IP_HYUNDAI:} - description: 外部下级平台 - Hyundai - - username: ${GB32960_PLATFORM_USER_YUEJIN:YueJin} - password: ${GB32960_PLATFORM_PWD_YUEJIN:} - allowed-ips: - - ${GB32960_PLATFORM_IP_YUEJIN:} - description: 外部下级平台 - YueJin - tls: - enabled: ${GB32960_TLS_ENABLED:false} - cert-path: ${GB32960_TLS_CERT:} - key-path: ${GB32960_TLS_KEY:} - trust-cert-path: ${GB32960_TLS_CA:} - require-client-auth: true - vendor-extensions: - - name: guangdong-fc - match: - platform-accounts: - - Hyundai - - YueJin - vin-prefixes: [] - vins: [] - parse: - lenient-block-failure: true - diagnostics: - max-logged-frame-keys-per-channel: ${GB32960_DIAGNOSTICS_MAX_LOGGED_FRAME_KEYS_PER_CHANNEL:4096} - pipeline: - disruptor: - ring-buffer-size: ${PIPELINE_RING_BUFFER_SIZE:131072} - wait-strategy: ${PIPELINE_WAIT_STRATEGY:yielding} - producer-type: multi - dedup: - enabled: true - cache-size: 200000 - ttl-seconds: 600 - rate-limit: - per-vin-qps: 50 - session: - ttl: ${SESSION_TTL:30m} - identity: - mysql: - jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai} - username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root} - password: ${VEHICLE_IDENTITY_MYSQL_PASSWORD:} - table-name: ${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings} - initialize-schema: ${VEHICLE_IDENTITY_MYSQL_INITIALIZE_SCHEMA:true} - sink: - kafka: - enabled: true - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - compression-type: zstd - linger-ms: 20 - batch-size: 65536 - acks: all - enable-idempotence: true - node-id: ${KAFKA_NODE_ID:gb32960-ingest-local} - topics: - realtime: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - location: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - alarm: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - session: ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - media-meta: ${KAFKA_TOPIC_MEDIA_META:vehicle.media.meta.v1} - raw-archive: ${KAFKA_TOPIC_GB32960_RAW:vehicle.raw.gb32960.v1} - dlq: ${KAFKA_TOPIC_GB32960_DLQ:vehicle.dlq.gb32960.v1} - consumer: - enabled: false - archive: - enabled: ${SINK_ARCHIVE_ENABLED:true} - type: local - path: ${SINK_ARCHIVE_PATH:./archive/} - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus,gb32960sessions - endpoint: - health: - probes: - enabled: ${MANAGEMENT_HEALTH_PROBES_ENABLED:true} - health: - redis: - enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false} - metrics: - tags: - application: gb32960-ingest-app - -logging: - level: - root: INFO - com.lingniu.ingest: INFO diff --git a/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppCompositionTest.java b/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppCompositionTest.java deleted file mode 100644 index c863bda9..00000000 --- a/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppCompositionTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.lingniu.ingest.gb32960app; - -import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration; -import com.lingniu.ingest.identity.MySqlVehicleIdentityService; -import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960AutoConfiguration; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer; -import com.lingniu.ingest.session.SessionStore; -import com.lingniu.ingest.session.config.SessionCoreAutoConfiguration; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.RawArchiveEventSink; -import com.lingniu.ingest.sink.archive.config.SinkArchiveAutoConfiguration; -import com.lingniu.ingest.sink.kafka.KafkaEventSink; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeDeadLetterSink; -import com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.ApplicationContext; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class Gb32960IngestAppCompositionTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - IngestCoreAutoConfiguration.class, - SessionCoreAutoConfiguration.class, - VehicleIdentityAutoConfiguration.class, - KafkaSinkAutoConfiguration.class, - SinkArchiveAutoConfiguration.class, - Gb32960AutoConfiguration.class)) - .withAllowBeanDefinitionOverriding(true) - .withBean("kafkaProducer", KafkaProducer.class, Gb32960IngestAppCompositionTest::kafkaProducer) - .withBean(SessionStore.class, () -> mock(SessionStore.class)) - .withPropertyValues( - "lingniu.ingest.gb32960.enabled=true", - "lingniu.ingest.gb32960.server.enabled=true", - "lingniu.ingest.gb32960.port=0", - "lingniu.ingest.identity.mysql.initialize-schema=false", - "lingniu.ingest.sink.kafka.enabled=true", - "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092", - "lingniu.ingest.sink.kafka.consumer.enabled=false", - "lingniu.ingest.sink.archive.enabled=true", - "lingniu.ingest.sink.archive.path=target/test-archive-gb32960", - "lingniu.ingest.event-history.enabled=false"); - - @Test - void createsGb32960ListenerWithoutHistoryStorageBoundaries() { - contextRunner.run(context -> { - assertThat(context).hasSingleBean(Gb32960MessageDecoder.class); - assertThat(context).hasSingleBean(Gb32960NettyServer.class); - assertThat(context).hasSingleBean(MySqlVehicleIdentityService.class); - assertThat(context).hasSingleBean(KafkaEventSink.class); - assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class); - assertThat(context).hasSingleBean(ArchiveStore.class); - assertThat(context).hasSingleBean(RawArchiveEventSink.class); - - assertTypeNotPresent(context, "com.lingniu.ingest.eventfilestore.DuckDbParquetEventFileStore"); - assertTypeNotPresent(context, "com.lingniu.ingest.eventfilestore.config.EventFileStoreAutoConfiguration"); - }); - } - - @SuppressWarnings("unchecked") - private static KafkaProducer kafkaProducer() { - return mock(KafkaProducer.class); - } - - private static void assertTypeNotPresent(ApplicationContext context, String className) { - assertThat(ClassUtils.isPresent(className, context.getClassLoader())) - .as("%s must stay outside the GB32960 ingest runtime", className) - .isFalse(); - } -} diff --git a/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppDefaultsTest.java b/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppDefaultsTest.java deleted file mode 100644 index 30f285dc..00000000 --- a/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/Gb32960IngestAppDefaultsTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.lingniu.ingest.gb32960app; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960IngestAppDefaultsTest { - - @Test - void applicationDefaultsKeepGb32960IngestAsTcpKafkaProducerOnly() throws IOException { - Properties properties = applicationProperties(); - String legacyKafkaPrefix = "lingniu.ingest.sink." + "mq."; - String kafkaTypeProperty = "lingniu.ingest.sink.kafka." + "type"; - - assertThat(properties) - .containsEntry("spring.application.name", "gb32960-ingest-app") - .containsEntry( - "spring.config.import[0]", - "optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true}") - .containsEntry("spring.cloud.nacos.config.server-addr", "${NACOS_SERVER_ADDR:127.0.0.1:8848}") - .containsEntry("spring.data.redis.host", "${REDIS_HOST:127.0.0.1}") - .containsEntry("spring.data.redis.port", "${REDIS_PORT:6379}") - .containsEntry("spring.data.redis.database", "${REDIS_DATABASE:50}") - .containsEntry("lingniu.ingest.gb32960.enabled", true) - .containsEntry("lingniu.ingest.gb32960.server.enabled", true) - .containsEntry("lingniu.ingest.gb32960.port", "${GB32960_PORT:32960}") - .containsEntry("lingniu.ingest.gb32960.auth.platforms[0].username", - "${GB32960_PLATFORM_USER_HYUNDAI:Hyundai}") - .containsEntry("lingniu.ingest.gb32960.auth.platforms[0].password", - "${GB32960_PLATFORM_PWD_HYUNDAI:}") - .containsEntry("lingniu.ingest.gb32960.auth.platforms[1].username", - "${GB32960_PLATFORM_USER_YUEJIN:YueJin}") - .containsEntry("lingniu.ingest.gb32960.auth.platforms[1].password", - "${GB32960_PLATFORM_PWD_YUEJIN:}") - .containsEntry("lingniu.ingest.gb32960.vendor-extensions[0].match.platform-accounts[0]", - "Hyundai") - .containsEntry("lingniu.ingest.gb32960.vendor-extensions[0].match.platform-accounts[1]", - "YueJin") - .containsEntry("lingniu.ingest.sink.kafka.enabled", true) - .containsEntry("lingniu.ingest.sink.kafka.consumer.enabled", false) - .containsEntry("lingniu.ingest.sink.archive.enabled", "${SINK_ARCHIVE_ENABLED:true}") - .containsEntry("lingniu.ingest.sink.archive.path", "${SINK_ARCHIVE_PATH:./archive/}") - .containsEntry("lingniu.ingest.identity.mysql.table-name", - "${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}") - .containsEntry("management.endpoints.web.exposure.include", - "health,info,metrics,prometheus,gb32960sessions"); - - assertThat(properties.stringPropertyNames()) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-history.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-file-store.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-state.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-stat.")) - .noneMatch(name -> name.startsWith(legacyKafkaPrefix)) - .noneMatch(name -> name.equals(kafkaTypeProperty)) - .noneMatch(name -> name.equals("lingniu.ingest.session.store")) - .noneMatch(name -> name.equals("lingniu.ingest.identity.store")); - assertThat(applicationYaml()) - .doesNotContain("event-history:") - .doesNotContain("event-file-store:") - .doesNotContain("vehicle-state:") - .doesNotContain("vehicle-stat:") - .doesNotContain("springdoc:"); - assertThat(applicationYaml()) - .contains("password: ${GB32960_PLATFORM_PWD_HYUNDAI:}") - .contains("password: ${GB32960_PLATFORM_PWD_YUEJIN:}") - .doesNotContain("SESSION_STORE") - .doesNotContain("VEHICLE_IDENTITY_STORE") - .doesNotContain("KAFKA_ENABLED"); - } - - private static Properties applicationProperties() { - YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ClassPathResource("application.yml")); - - Properties properties = factory.getObject(); - - assertThat(properties).isNotNull(); - return properties; - } - - private static String applicationYaml() throws IOException { - return new String(new ClassPathResource("application.yml").getInputStream().readAllBytes(), - StandardCharsets.UTF_8); - } -} diff --git a/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpointTest.java b/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpointTest.java deleted file mode 100644 index 72d475b8..00000000 --- a/modules/apps/gb32960-ingest-app/src/test/java/com/lingniu/ingest/gb32960app/actuator/Gb32960SessionsEndpointTest.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.lingniu.ingest.gb32960app.actuator; - -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ConnectionDiagnostics; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960SessionsEndpointTest { - - @Test - void returnsActiveGb32960Sessions() { - Gb32960ConnectionDiagnostics diagnostics = new Gb32960ConnectionDiagnostics(); - diagnostics.opened("session-1", "127.0.0.1:65469"); - diagnostics.received("session-1", CommandType.PLATFORM_LOGIN, ""); - diagnostics.platformLoginAccepted("session-1", "Hyundai"); - - Gb32960SessionsEndpoint endpoint = new Gb32960SessionsEndpoint(diagnostics); - - Map result = endpoint.sessions(); - - assertThat(result).containsEntry("activeCount", 1); - assertThat(result.get("sessions")).isInstanceOf(List.class); - assertThat((List) result.get("sessions")).singleElement() - .hasFieldOrPropertyWithValue("sessionId", "session-1") - .hasFieldOrPropertyWithValue("peer", "127.0.0.1:65469") - .hasFieldOrPropertyWithValue("lastCommand", "PLATFORM_LOGIN") - .hasFieldOrPropertyWithValue("platformAccount", "Hyundai"); - } -} diff --git a/modules/apps/jt808-ingest-app/pom.xml b/modules/apps/jt808-ingest-app/pom.xml deleted file mode 100644 index fa39adda..00000000 --- a/modules/apps/jt808-ingest-app/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - jt808-ingest-app - jt808-ingest-app - JT808 protocol ingress runtime: TCP decode, identity mapping, Kafka production, and downlink session routing. - - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - session-core - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - vehicle-identity - - - com.lingniu.ingest - protocol-jt808 - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - sink-archive - - - org.springframework.boot - spring-boot-starter-web - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-test - test - - - - - jt808-ingest-app - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/modules/apps/jt808-ingest-app/src/main/java/com/lingniu/ingest/jt808app/Jt808IngestApplication.java b/modules/apps/jt808-ingest-app/src/main/java/com/lingniu/ingest/jt808app/Jt808IngestApplication.java deleted file mode 100644 index 5a833540..00000000 --- a/modules/apps/jt808-ingest-app/src/main/java/com/lingniu/ingest/jt808app/Jt808IngestApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.jt808app; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class Jt808IngestApplication { - - private static final Logger log = LoggerFactory.getLogger(Jt808IngestApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(Jt808IngestApplication.class, args); - } catch (Throwable t) { - log.error("JT808 ingest application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} diff --git a/modules/apps/jt808-ingest-app/src/main/resources/application.yml b/modules/apps/jt808-ingest-app/src/main/resources/application.yml deleted file mode 100644 index 546e2bf6..00000000 --- a/modules/apps/jt808-ingest-app/src/main/resources/application.yml +++ /dev/null @@ -1,97 +0,0 @@ -spring: - application: - name: jt808-ingest-app - config: - import: - - optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true} - cloud: - nacos: - config: - enabled: ${NACOS_CONFIG_ENABLED:true} - server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: ${NACOS_CONFIG_FILE_EXTENSION:yml} - username: ${NACOS_USERNAME:} - password: ${NACOS_PASSWORD:} - threads: - virtual: - enabled: true - data: - redis: - host: ${REDIS_HOST:127.0.0.1} - port: ${REDIS_PORT:6379} - username: ${REDIS_USERNAME:} - password: ${REDIS_PASSWORD:} - database: ${REDIS_DATABASE:50} - -server: - port: ${HTTP_PORT:20400} - -lingniu: - ingest: - jt808: - enabled: true - port: ${JT808_PORT:808} - worker-threads: ${JT808_WORKER_THREADS:0} - pipeline: - disruptor: - ring-buffer-size: ${PIPELINE_RING_BUFFER_SIZE:131072} - wait-strategy: ${PIPELINE_WAIT_STRATEGY:yielding} - producer-type: multi - dedup: - enabled: true - cache-size: 200000 - ttl-seconds: 600 - rate-limit: - per-vin-qps: 50 - session: - ttl: ${SESSION_TTL:30m} - identity: - mysql: - jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai} - username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root} - password: ${VEHICLE_IDENTITY_MYSQL_PASSWORD:} - table-name: ${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings} - initialize-schema: ${VEHICLE_IDENTITY_MYSQL_INITIALIZE_SCHEMA:true} - sink: - kafka: - enabled: true - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - compression-type: zstd - linger-ms: 20 - batch-size: 65536 - acks: all - enable-idempotence: true - node-id: ${KAFKA_NODE_ID:jt808-ingest-local} - topics: - realtime: ${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1} - location: ${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1} - alarm: ${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1} - session: ${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1} - media-meta: ${KAFKA_TOPIC_MEDIA_META:vehicle.media.meta.v1} - raw-archive: ${KAFKA_TOPIC_JT808_RAW:vehicle.raw.jt808.v1} - dlq: ${KAFKA_TOPIC_JT808_DLQ:vehicle.dlq.jt808.v1} - consumer: - enabled: false - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - probes: - enabled: ${MANAGEMENT_HEALTH_PROBES_ENABLED:true} - health: - redis: - enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false} - metrics: - tags: - application: jt808-ingest-app - -logging: - level: - root: INFO - com.lingniu.ingest: INFO diff --git a/modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppCompositionTest.java b/modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppCompositionTest.java deleted file mode 100644 index 0fd1a278..00000000 --- a/modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppCompositionTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.lingniu.ingest.jt808app; - -import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration; -import com.lingniu.ingest.identity.MySqlVehicleIdentityService; -import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration; -import com.lingniu.ingest.protocol.jt808.inbound.Jt808NettyServer; -import com.lingniu.ingest.session.SessionStore; -import com.lingniu.ingest.session.config.SessionCoreAutoConfiguration; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.RawArchiveEventSink; -import com.lingniu.ingest.sink.archive.config.SinkArchiveAutoConfiguration; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeDeadLetterSink; -import com.lingniu.ingest.sink.kafka.KafkaEventSink; -import com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.ApplicationContext; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class Jt808IngestAppCompositionTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - IngestCoreAutoConfiguration.class, - SessionCoreAutoConfiguration.class, - VehicleIdentityAutoConfiguration.class, - KafkaSinkAutoConfiguration.class, - SinkArchiveAutoConfiguration.class, - Jt808AutoConfiguration.class)) - .withAllowBeanDefinitionOverriding(true) - .withBean("kafkaProducer", KafkaProducer.class, Jt808IngestAppCompositionTest::kafkaProducer) - .withBean(SessionStore.class, () -> mock(SessionStore.class)) - .withPropertyValues( - "lingniu.ingest.jt808.enabled=true", - "lingniu.ingest.jt808.port=0", - "lingniu.ingest.identity.mysql.initialize-schema=false", - "lingniu.ingest.sink.kafka.enabled=true", - "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092", - "lingniu.ingest.sink.kafka.consumer.enabled=false", - "lingniu.ingest.sink.archive.enabled=true", - "lingniu.ingest.sink.archive.path=target/test-archive-jt808", - "lingniu.ingest.event-history.enabled=false"); - - @Test - void createsJt808ListenerWithoutHistoryStorageBoundaries() { - contextRunner.run(context -> { - assertThat(context).hasSingleBean(Jt808MessageDecoder.class); - assertThat(context).hasSingleBean(Jt808NettyServer.class); - assertThat(context).hasSingleBean(MySqlVehicleIdentityService.class); - assertThat(context).hasSingleBean(KafkaEventSink.class); - assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class); - assertThat(context).hasSingleBean(ArchiveStore.class); - assertThat(context).hasSingleBean(RawArchiveEventSink.class); - - assertTypeNotPresent(context, "com.lingniu.ingest.eventfilestore.DuckDbParquetEventFileStore"); - assertTypeNotPresent(context, "com.lingniu.ingest.eventfilestore.config.EventFileStoreAutoConfiguration"); - }); - } - - @SuppressWarnings("unchecked") - private static KafkaProducer kafkaProducer() { - return mock(KafkaProducer.class); - } - - private static void assertTypeNotPresent(ApplicationContext context, String className) { - assertThat(ClassUtils.isPresent(className, context.getClassLoader())) - .as("%s must stay outside the JT808 ingest runtime", className) - .isFalse(); - } -} diff --git a/modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppDefaultsTest.java b/modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppDefaultsTest.java deleted file mode 100644 index a896cefb..00000000 --- a/modules/apps/jt808-ingest-app/src/test/java/com/lingniu/ingest/jt808app/Jt808IngestAppDefaultsTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.lingniu.ingest.jt808app; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808IngestAppDefaultsTest { - - @Test - void applicationDefaultsKeepJt808IngestAsTcpKafkaProducerOnly() throws IOException { - Properties properties = applicationProperties(); - - assertThat(properties) - .containsEntry("spring.application.name", "jt808-ingest-app") - .containsEntry( - "spring.config.import[0]", - "optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true}") - .containsEntry("spring.cloud.nacos.config.server-addr", "${NACOS_SERVER_ADDR:127.0.0.1:8848}") - .containsEntry("spring.data.redis.host", "${REDIS_HOST:127.0.0.1}") - .containsEntry("spring.data.redis.port", "${REDIS_PORT:6379}") - .containsEntry("spring.data.redis.database", "${REDIS_DATABASE:50}") - .containsEntry("server.port", "${HTTP_PORT:20400}") - .containsEntry("lingniu.ingest.jt808.enabled", true) - .containsEntry("lingniu.ingest.jt808.port", "${JT808_PORT:808}") - .containsEntry("lingniu.ingest.sink.kafka.enabled", true) - .containsEntry("lingniu.ingest.sink.kafka.consumer.enabled", false) - .containsEntry("lingniu.ingest.sink.kafka.topics.realtime", "${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1}") - .containsEntry("lingniu.ingest.sink.kafka.topics.raw-archive", "${KAFKA_TOPIC_JT808_RAW:vehicle.raw.jt808.v1}") - .containsEntry("lingniu.ingest.sink.kafka.topics.dlq", "${KAFKA_TOPIC_JT808_DLQ:vehicle.dlq.jt808.v1}") - .containsEntry("lingniu.ingest.identity.mysql.table-name", - "${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}") - .containsEntry("lingniu.ingest.identity.mysql.jdbc-url", - "${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai}") - .containsEntry("lingniu.ingest.identity.mysql.username", "${VEHICLE_IDENTITY_MYSQL_USERNAME:root}") - .containsEntry("lingniu.ingest.identity.mysql.password", "${VEHICLE_IDENTITY_MYSQL_PASSWORD:}") - .containsEntry("lingniu.ingest.identity.mysql.initialize-schema", - "${VEHICLE_IDENTITY_MYSQL_INITIALIZE_SCHEMA:true}") - .containsEntry("management.endpoints.web.exposure.include", "health,info,metrics,prometheus"); - - assertThat(properties.stringPropertyNames()) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-history.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-file-store.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-state.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-stat.")) - .noneMatch(name -> name.equals("lingniu.ingest.session.store")) - .noneMatch(name -> name.equals("lingniu.ingest.identity.store")); - assertThat(applicationYaml()) - .doesNotContain("event-history:") - .doesNotContain("event-file-store:") - .doesNotContain("vehicle-state:") - .doesNotContain("vehicle-stat:") - .doesNotContain("springdoc:") - .doesNotContain("SESSION_STORE") - .doesNotContain("VEHICLE_IDENTITY_STORE") - .doesNotContain("KAFKA_ENABLED"); - } - - private static Properties applicationProperties() { - YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ClassPathResource("application.yml")); - - Properties properties = factory.getObject(); - - assertThat(properties).isNotNull(); - return properties; - } - - private static String applicationYaml() throws IOException { - return new String(new ClassPathResource("application.yml").getInputStream().readAllBytes(), - StandardCharsets.UTF_8); - } -} diff --git a/modules/apps/vehicle-analytics-app/pom.xml b/modules/apps/vehicle-analytics-app/pom.xml deleted file mode 100644 index f5668637..00000000 --- a/modules/apps/vehicle-analytics-app/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - vehicle-analytics-app - vehicle-analytics-app - Vehicle daily statistics and analytics runtime. - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - vehicle-stat-service - - - org.springframework.boot - spring-boot-starter-jdbc - - - org.springframework.boot - spring-boot-starter-web - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - org.springframework.boot - spring-boot-starter-test - test - - - - - vehicle-analytics-app - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java b/modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java deleted file mode 100644 index 3bf0d5a0..00000000 --- a/modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.analyticsapp; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class VehicleAnalyticsApplication { - - private static final Logger log = LoggerFactory.getLogger(VehicleAnalyticsApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(VehicleAnalyticsApplication.class, args); - } catch (Throwable t) { - log.error("Vehicle analytics application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} diff --git a/modules/apps/vehicle-analytics-app/src/main/resources/application.yml b/modules/apps/vehicle-analytics-app/src/main/resources/application.yml deleted file mode 100644 index aeefc92c..00000000 --- a/modules/apps/vehicle-analytics-app/src/main/resources/application.yml +++ /dev/null @@ -1,71 +0,0 @@ -spring: - application: - name: vehicle-analytics-app - config: - import: - - optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true} - cloud: - nacos: - config: - enabled: ${NACOS_CONFIG_ENABLED:true} - server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: ${NACOS_CONFIG_FILE_EXTENSION:yml} - username: ${NACOS_USERNAME:} - password: ${NACOS_PASSWORD:} - threads: - virtual: - enabled: true - datasource: - url: ${MYSQL_JDBC_URL:} - username: ${MYSQL_USERNAME:} - password: ${MYSQL_PASSWORD:} - driver-class-name: com.mysql.cj.jdbc.Driver - -server: - port: ${HTTP_PORT:20310} - -springdoc: - swagger-ui: - path: /swagger-ui.html - api-docs: - path: /v3/api-docs - -lingniu: - ingest: - sink: - kafka: - enabled: true - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - consumer: - dlq-topic: ${KAFKA_TOPIC_JT808_DLQ:vehicle.dlq.jt808.v1} - enabled: ${KAFKA_CONSUMER_ENABLED:true} - client-id-prefix: ${KAFKA_CONSUMER_CLIENT_ID_PREFIX:vehicle-analytics} - auto-offset-reset: ${KAFKA_CONSUMER_AUTO_OFFSET_RESET:earliest} - max-poll-records: ${KAFKA_CONSUMER_MAX_POLL_RECORDS:500} - max-poll-interval-millis: ${KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS:900000} - bindings: - vehicleStatEnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_STAT:vehicle-stat} - topics: - - ${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1} - vehicle-stat: - enabled: ${VEHICLE_STAT_ENABLED:true} - zone-id: ${VEHICLE_STAT_ZONE_ID:Asia/Shanghai} - jt808: - enabled: ${VEHICLE_STAT_JT808_MILEAGE_ENABLED:true} - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - probes: - enabled: ${MANAGEMENT_HEALTH_PROBES_ENABLED:true} - metrics: - tags: - application: vehicle-analytics-app diff --git a/modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppCompositionTest.java b/modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppCompositionTest.java deleted file mode 100644 index fb2f3a73..00000000 --- a/modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppCompositionTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.lingniu.ingest.analyticsapp; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeDeadLetterSink; -import com.lingniu.ingest.sink.kafka.KafkaEventSink; -import com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration; -import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository; -import com.lingniu.ingest.vehiclestat.VehicleStatController; -import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor; -import com.lingniu.ingest.vehiclestat.VehicleStatRepository; -import com.lingniu.ingest.vehiclestat.config.VehicleStatAutoConfiguration; -import com.lingniu.ingest.vehiclestat.jt808.Jt808MileageStreamProcessor; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.ApplicationContext; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class VehicleAnalyticsAppCompositionTest { - - @Test - void createsStatsBeansWithoutProtocolListenerOrFileHistoryStore() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - KafkaSinkAutoConfiguration.class, - VehicleStatAutoConfiguration.class)) - .withAllowBeanDefinitionOverriding(true) - .withBean("kafkaProducer", KafkaProducer.class, VehicleAnalyticsAppCompositionTest::kafkaProducer) - .withBean(JdbcTemplate.class, () -> mock(JdbcTemplate.class)) - .withBean(ObjectMapper.class, ObjectMapper::new) - .withPropertyValues( - "lingniu.ingest.sink.kafka.enabled=true", - "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092", - "lingniu.ingest.sink.kafka.consumer.enabled=false", - "lingniu.ingest.vehicle-stat.enabled=true", - "lingniu.ingest.vehicle-stat.jt808.enabled=true") - .run(context -> { - assertThat(context).hasSingleBean(VehicleStatRepository.class); - assertThat(context).hasSingleBean(JdbcVehicleStatMetricRepository.class); - assertThat(context).hasSingleBean(Jt808MileageStreamProcessor.class); - assertThat(context).hasSingleBean(VehicleStatEnvelopeIngestor.class); - assertThat(context).hasSingleBean(VehicleStatController.class); - assertThat(context).hasSingleBean(KafkaEventSink.class); - assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class); - assertThat(context).hasBean("vehicleStatEnvelopeConsumerProcessor"); - assertThat(context.getBean("vehicleStatEnvelopeConsumerProcessor")) - .isInstanceOf(EnvelopeConsumerProcessor.class); - - assertTypeNotPresent(context, "com.lingniu.ingest.vehiclestate.VehicleStateEnvelopeIngestor"); - assertTypeNotPresent(context, "com.lingniu.ingest.vehiclestate.config.VehicleStateAutoConfiguration"); - assertTypeNotPresent(context, "com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer"); - assertTypeNotPresent(context, "com.lingniu.ingest.eventfilestore.DuckDbParquetEventFileStore"); - assertTypeNotPresent(context, - "com.lingniu.ingest.eventfilestore.config.EventFileStoreAutoConfiguration"); - }); - } - - @SuppressWarnings("unchecked") - private static KafkaProducer kafkaProducer() { - return mock(KafkaProducer.class); - } - - private static void assertTypeNotPresent(ApplicationContext context, String className) { - assertThat(ClassUtils.isPresent(className, context.getClassLoader())) - .as("%s must stay outside the vehicle analytics runtime", className) - .isFalse(); - } -} diff --git a/modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppDefaultsTest.java b/modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppDefaultsTest.java deleted file mode 100644 index d8d14143..00000000 --- a/modules/apps/vehicle-analytics-app/src/test/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsAppDefaultsTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.lingniu.ingest.analyticsapp; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleAnalyticsAppDefaultsTest { - - @Test - void applicationDefaultsKeepAnalyticsAsStatConsumerRuntime() throws IOException { - Properties properties = applicationProperties(); - - assertThat(properties) - .containsEntry("spring.application.name", "vehicle-analytics-app") - .containsEntry( - "spring.config.import[0]", - "optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true}") - .containsEntry("spring.cloud.nacos.config.server-addr", "${NACOS_SERVER_ADDR:127.0.0.1:8848}") - .containsEntry("server.port", "${HTTP_PORT:20310}") - .containsEntry("lingniu.ingest.vehicle-stat.enabled", "${VEHICLE_STAT_ENABLED:true}") - .containsEntry( - "lingniu.ingest.vehicle-stat.jt808.enabled", - "${VEHICLE_STAT_JT808_MILEAGE_ENABLED:true}") - .containsEntry("lingniu.ingest.sink.kafka.enabled", true) - .containsEntry("lingniu.ingest.sink.kafka.consumer.enabled", "${KAFKA_CONSUMER_ENABLED:true}") - .containsEntry("lingniu.ingest.sink.kafka.consumer.dlq-topic", - "${KAFKA_TOPIC_JT808_DLQ:vehicle.dlq.jt808.v1}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.vehicleStatEnvelopeConsumerProcessor.enabled", - true) - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.vehicleStatEnvelopeConsumerProcessor.group-id", - "${KAFKA_GROUP_STAT:vehicle-stat}") - .containsEntry("management.endpoints.web.exposure.include", "health,info,metrics,prometheus"); - - assertThat(properties.stringPropertyNames()) - .noneMatch(name -> name.startsWith("lingniu.ingest.gb32960.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-history.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.sink.archive.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.sink.kafka.topics.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-file-store.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-state.")) - .noneMatch(name -> name.contains("vehicleStateEnvelopeConsumerProcessor")) - .noneMatch(name -> name.startsWith("spring.data.redis.")) - .noneMatch(name -> name.startsWith("management.health.redis.")); - assertThat(applicationYaml()) - .doesNotContain("gb32960:") - .doesNotContain("event-history:") - .doesNotContain("archive:") - .doesNotContain("event-file-store:") - .doesNotContain("vehicle-state:") - .doesNotContain("vehicleStateEnvelopeConsumerProcessor") - .doesNotContain("redis:") - .doesNotContain("KAFKA_ENABLED"); - } - - private static Properties applicationProperties() { - YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ClassPathResource("application.yml")); - - Properties properties = factory.getObject(); - - assertThat(properties).isNotNull(); - return properties; - } - - private static String applicationYaml() throws IOException { - return new String(new ClassPathResource("application.yml").getInputStream().readAllBytes(), - StandardCharsets.UTF_8); - } -} diff --git a/modules/apps/vehicle-history-app/pom.xml b/modules/apps/vehicle-history-app/pom.xml deleted file mode 100644 index 7a9719d2..00000000 --- a/modules/apps/vehicle-history-app/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - vehicle-history-app - vehicle-history-app - Vehicle raw archive, event history, and GB32960 frame query runtime. - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - tdengine-history-store - - - com.lingniu.ingest - event-history-service - - - com.lingniu.ingest - protocol-gb32960 - - - org.springframework.boot - spring-boot-starter-web - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - - - org.springframework.boot - spring-boot-starter-test - test - - - - - vehicle-history-app - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java b/modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java deleted file mode 100644 index 48ac2363..00000000 --- a/modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.historyapp; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class VehicleHistoryApplication { - - private static final Logger log = LoggerFactory.getLogger(VehicleHistoryApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(VehicleHistoryApplication.class, args); - } catch (Throwable t) { - log.error("Vehicle history application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} diff --git a/modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryKafkaConsumerConfiguration.java b/modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryKafkaConsumerConfiguration.java deleted file mode 100644 index 5884c23f..00000000 --- a/modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryKafkaConsumerConfiguration.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.lingniu.ingest.historyapp; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeConsumerFactory; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeConsumerRunner; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeConsumerWorker; -import com.lingniu.ingest.sink.kafka.KafkaSinkProperties; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.time.Duration; -import java.util.List; -import java.util.Map; - -@Configuration(proxyBeanMethods = false) -public class VehicleHistoryKafkaConsumerConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "lingniu.ingest.sink.kafka.consumer", name = "enabled", havingValue = "true") - public KafkaEnvelopeConsumerRunner vehicleHistoryKafkaEnvelopeConsumerRunner( - @Qualifier("eventHistoryEnvelopeConsumerProcessor") EnvelopeConsumerProcessor processor, - @Qualifier("eventHistoryRawEnvelopeConsumerProcessor") EnvelopeConsumerProcessor rawProcessor, - KafkaSinkProperties props) { - List workers = new KafkaEnvelopeConsumerFactory().createWorkers( - Map.of( - "eventHistoryGb32960EnvelopeConsumerProcessor", processor, - "eventHistoryJt808EnvelopeConsumerProcessor", processor, - "eventHistoryYutongMqttEnvelopeConsumerProcessor", processor, - "eventHistoryGb32960RawEnvelopeConsumerProcessor", rawProcessor, - "eventHistoryJt808RawEnvelopeConsumerProcessor", rawProcessor, - "eventHistoryYutongMqttRawEnvelopeConsumerProcessor", rawProcessor), - props); - if (workers.isEmpty()) { - throw new IllegalStateException("no vehicle history kafka consumer workers created; check consumer bindings"); - } - return new KafkaEnvelopeConsumerRunner( - workers, - Duration.ofMillis(props.getConsumer().getPollTimeoutMillis()), - Duration.ofMillis(props.getConsumer().getLoopBackoffMillis()), - props.getConsumer().isAutoStartup()); - } -} diff --git a/modules/apps/vehicle-history-app/src/main/resources/application.yml b/modules/apps/vehicle-history-app/src/main/resources/application.yml deleted file mode 100644 index 0b08b0de..00000000 --- a/modules/apps/vehicle-history-app/src/main/resources/application.yml +++ /dev/null @@ -1,103 +0,0 @@ -spring: - application: - name: vehicle-history-app - config: - import: - - optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true} - cloud: - nacos: - config: - enabled: ${NACOS_CONFIG_ENABLED:true} - server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: ${NACOS_CONFIG_FILE_EXTENSION:yml} - username: ${NACOS_USERNAME:} - password: ${NACOS_PASSWORD:} - threads: - virtual: - enabled: true - -server: - port: ${HTTP_PORT:20200} - -springdoc: - swagger-ui: - path: /swagger-ui.html - api-docs: - path: /v3/api-docs - -lingniu: - ingest: - sink: - kafka: - enabled: true - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - consumer: - dlq-topic: ${KAFKA_TOPIC_HISTORY_DLQ:vehicle.dlq.history.v1} - enabled: ${KAFKA_CONSUMER_ENABLED:true} - client-id-prefix: ${KAFKA_CONSUMER_CLIENT_ID_PREFIX:vehicle-history} - auto-offset-reset: ${KAFKA_CONSUMER_AUTO_OFFSET_RESET:earliest} - max-poll-records: ${KAFKA_CONSUMER_MAX_POLL_RECORDS:2000} - max-poll-interval-millis: ${KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS:1800000} - concurrency: ${KAFKA_CONSUMER_CONCURRENCY:3} - bindings: - eventHistoryGb32960EnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_HISTORY_GB32960_EVENT:${KAFKA_GROUP_HISTORY:vehicle-history}-gb32960-event} - topics: - - ${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1} - eventHistoryJt808EnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_HISTORY_JT808_EVENT:${KAFKA_GROUP_HISTORY:vehicle-history}-jt808-event} - topics: - - ${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1} - eventHistoryYutongMqttEnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_HISTORY_YUTONG_MQTT_EVENT:${KAFKA_GROUP_HISTORY:vehicle-history}-yutong-mqtt-event} - topics: - - ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1} - eventHistoryGb32960RawEnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_HISTORY_GB32960_RAW:${KAFKA_GROUP_HISTORY:vehicle-history}-gb32960-raw} - topics: - - ${KAFKA_TOPIC_GB32960_RAW:vehicle.raw.gb32960.v1} - eventHistoryJt808RawEnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_HISTORY_JT808_RAW:${KAFKA_GROUP_HISTORY:vehicle-history}-jt808-raw} - topics: - - ${KAFKA_TOPIC_JT808_RAW:vehicle.raw.jt808.v1} - eventHistoryYutongMqttRawEnvelopeConsumerProcessor: - enabled: true - group-id: ${KAFKA_GROUP_HISTORY_YUTONG_MQTT_RAW:${KAFKA_GROUP_HISTORY:vehicle-history}-yutong-mqtt-raw} - topics: - - ${KAFKA_TOPIC_YUTONG_MQTT_RAW:vehicle.raw.mqtt-yutong.v1} - tdengine-history: - enabled: ${TDENGINE_HISTORY_ENABLED:false} - database: ${TDENGINE_HISTORY_DATABASE:vehicle_history} - jdbc-url: ${TDENGINE_JDBC_URL:jdbc:TAOS-RS://${TDENGINE_HOST:127.0.0.1}:${TDENGINE_PORT:6041}/${TDENGINE_HISTORY_DATABASE:vehicle_history}} - username: ${TDENGINE_USERNAME:root} - password: ${TDENGINE_PASSWORD:taosdata} - driver-class-name: ${TDENGINE_DRIVER_CLASS_NAME:com.taosdata.jdbc.rs.RestfulDriver} - maximum-pool-size: ${TDENGINE_MAX_POOL_SIZE:16} - minimum-idle: ${TDENGINE_MIN_IDLE:0} - connection-timeout-millis: ${TDENGINE_CONNECTION_TIMEOUT_MILLIS:5000} - initialization-fail-timeout-millis: ${TDENGINE_INITIALIZATION_FAIL_TIMEOUT_MILLIS:-1} - event-history: - enabled: true - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - probes: - enabled: ${MANAGEMENT_HEALTH_PROBES_ENABLED:true} - health: - redis: - enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false} - metrics: - tags: - application: vehicle-history-app diff --git a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java deleted file mode 100644 index 2b1127ce..00000000 --- a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/MavenModuleProfileTest.java +++ /dev/null @@ -1,878 +0,0 @@ -package com.lingniu.ingest.historyapp; - -import org.junit.jupiter.api.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import javax.xml.parsers.DocumentBuilderFactory; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -class MavenModuleProfileTest { - - private static final List ACTIVE_APP_POMS = List.of( - "modules/apps/gb32960-ingest-app/pom.xml", - "modules/apps/jt808-ingest-app/pom.xml", - "modules/apps/yutong-mqtt-app/pom.xml", - "modules/apps/vehicle-history-app/pom.xml", - "modules/apps/vehicle-analytics-app/pom.xml"); - - @Test - void commandGatewayIsOutsideDefaultProductionReactor() throws Exception { - Document pom = rootPom(); - - assertThat(defaultModules(pom)) - .doesNotContain("modules/apps/command-gateway") - .doesNotContain("modules/protocols/protocol-jt1078"); - - assertThat(profileModules(pom, "optional-command-gateway")) - .containsExactly( - "modules/protocols/protocol-jt1078", - "modules/apps/command-gateway"); - } - - @Test - void jsatl12AttachmentProtocolIsOptionalAndOutsideDefaultProductionReactor() throws Exception { - Document pom = rootPom(); - - assertThat(defaultModules(pom)) - .doesNotContain("modules/protocols/protocol-jsatl12"); - - assertThat(profileModules(pom, "optional-attachments")) - .containsExactly("modules/protocols/protocol-jsatl12"); - } - - @Test - void latestStateServiceIsOptionalAndOutsideDefaultProductionReactor() throws Exception { - Document pom = rootPom(); - - assertThat(defaultModules(pom)) - .doesNotContain("modules/services/vehicle-state-service"); - - assertThat(profileModules(pom, "optional-latest-state")) - .containsExactly("modules/services/vehicle-state-service"); - } - - @Test - void rawArchiveStorePrototypeIsRemovedFromBuildSurface() throws Exception { - Document pom = rootPom(); - - assertThat(defaultModules(pom)) - .doesNotContain("modules/sinks/raw-archive-store"); - - assertThat(profileModules(pom, "optional-raw-archive-store")) - .as("raw bytes are written by sink-archive; raw-archive-store was an unused prototype") - .isEmpty(); - assertThat(Files.exists(repositoryRoot().resolve("modules/sinks/raw-archive-store"))) - .isFalse(); - } - - @Test - void eventFileStoreCompatibilityPathIsRemovedFromProductionStack() throws Exception { - Document pom = rootPom(); - String readme = Files.readString(repositoryRoot().resolve("README.md")); - - assertThat(defaultModules(pom)) - .doesNotContain("modules/sinks/event-file-store"); - assertThat(profileModules(pom, "optional-event-file-store")) - .as("DuckDB/Parquet event-file-store has been replaced by TDengine raw history") - .isEmpty(); - assertThat(Files.exists(repositoryRoot().resolve("modules/sinks/event-file-store"))) - .isFalse(); - assertThat(rootDependencyManagementArtifacts(pom)) - .doesNotContain("event-file-store"); - assertThat(hasProjectProperty(pom, "duckdb.version")) - .isFalse(); - assertThat(hasDependency(pom, "org.duckdb", "duckdb_jdbc")) - .isFalse(); - assertThat(readme) - .doesNotContain("event-file-store") - .doesNotContain("DuckDB") - .doesNotContain("Parquet"); - } - - @Test - void kafkaSinkModuleUsesKafkaNamingInsteadOfMqNaming() throws Exception { - Document pom = rootPom(); - Document kafkaSinkPom = modulePom("modules/sinks/sink-kafka/pom.xml"); - String readme = Files.readString(repositoryRoot().resolve("README.md")); - String legacyModule = "modules/sinks/sink-" + "mq"; - String legacyArtifact = "sink-" + "mq"; - - assertThat(defaultModules(pom)) - .contains("modules/sinks/sink-kafka") - .doesNotContain(legacyModule); - assertThat(rootDependencyManagementArtifacts(pom)) - .contains("sink-kafka") - .doesNotContain(legacyArtifact); - assertThat(firstDirectChild(kafkaSinkPom.getDocumentElement(), "artifactId").getTextContent()) - .isEqualTo("sink-kafka"); - assertThat(readme) - .contains("sink-kafka/") - .doesNotContain(legacyArtifact); - } - - @Test - void xindaModulesAreRemovedFromBuildSurface() throws Exception { - Document pom = rootPom(); - String rootPomText = Files.readString(repositoryRoot().resolve("pom.xml")); - String readme = Files.readString(repositoryRoot().resolve("README.md")); - - assertThat(defaultModules(pom)) - .doesNotContain("modules/inbound/inbound-xinda-push") - .doesNotContain("modules/apps/xinda-push-app"); - assertThat(profileModules(pom, "legacy-xinda")) - .as("xinda is deleted, not hidden behind an opt-in profile") - .isEmpty(); - assertThat(Files.exists(repositoryRoot().resolve("modules/inbound/inbound-xinda-push"))) - .isFalse(); - assertThat(Files.exists(repositoryRoot().resolve("modules/apps/xinda-push-app"))) - .isFalse(); - assertThat(rootPomText) - .doesNotContain("legacy-xinda") - .doesNotContain("inbound-xinda-push") - .doesNotContain("xinda-push-app"); - assertThat(readme) - .contains("信达 Push 已废弃并删除源码") - .doesNotContain("信达 Push 仅保留") - .doesNotContain("废弃兼容模块"); - } - - @Test - void privateLingniuRepositoryIsRemovedWithXinda() throws Exception { - Document pom = rootPom(); - - assertThat(repositoryIds(pom.getDocumentElement())) - .doesNotContain("lingniu-nexus"); - assertThat(profileRepositoryIds(pom, "legacy-xinda")) - .isEmpty(); - } - - @Test - void optionalModuleVersionsAreScopedToTheirProfiles() throws Exception { - Document pom = rootPom(); - - assertThat(rootDependencyManagementArtifacts(pom)) - .doesNotContain( - "protocol-jt1078", - "protocol-jsatl12", - "vehicle-state-service", - "raw-archive-store", - "command-gateway", - "inbound-xinda-push", - "xinda-push-app"); - - assertThat(profileDependencyManagementArtifacts(pom, "optional-command-gateway")) - .containsExactly("protocol-jt1078", "command-gateway"); - assertThat(profileDependencyManagementArtifacts(pom, "optional-attachments")) - .containsExactly("protocol-jsatl12"); - assertThat(profileDependencyManagementArtifacts(pom, "optional-latest-state")) - .containsExactly("vehicle-state-service"); - assertThat(profileDependencyManagementArtifacts(pom, "optional-raw-archive-store")) - .isEmpty(); - assertThat(profileDependencyManagementArtifacts(pom, "optional-event-file-store")) - .isEmpty(); - assertThat(profileDependencyManagementArtifacts(pom, "legacy-xinda")) - .isEmpty(); - } - - @Test - void appsDirectoryContainsOnlyRealMavenModules() throws Exception { - try (Stream apps = Files.list(repositoryRoot().resolve("modules/apps"))) { - assertThat(apps.filter(Files::isDirectory) - .filter(path -> !Files.exists(path.resolve("pom.xml"))) - .map(path -> path.getFileName().toString())) - .isEmpty(); - } - } - - @Test - void vehicleIdentityTestSupportIsCentralizedInsteadOfCopiedIntoProtocolTests() throws Exception { - Document pom = rootPom(); - List testSupportFixtures = javaFilesNamed("InMemoryVehicleIdentityService.java"); - - assertThat(defaultModules(pom)) - .contains("modules/testing/vehicle-identity-test-support"); - assertThat(rootDependencyManagementArtifacts(pom)) - .contains("vehicle-identity-test-support"); - assertThat(testSupportFixtures) - .containsExactly("modules/testing/vehicle-identity-test-support/src/main/java/com/lingniu/ingest/identity/InMemoryVehicleIdentityService.java"); - - for (String modulePom : List.of( - "modules/protocols/protocol-jt808/pom.xml", - "modules/inbound/inbound-mqtt/pom.xml", - "modules/protocols/protocol-jt1078/pom.xml", - "modules/protocols/protocol-jsatl12/pom.xml")) { - assertThat(hasDependency(modulePom(modulePom), "com.lingniu.ingest", "vehicle-identity-test-support")) - .as(modulePom) - .isTrue(); - } - } - - @Test - void readmeMatchesCurrentRuntimeVersionsAndSplitAppResponsibilities() throws Exception { - Document pom = rootPom(); - String readme = Files.readString(repositoryRoot().resolve("README.md")); - - assertThat(readme) - .contains("Spring Boot " + projectProperty(pom, "spring-boot.version")) - .contains("Netty " + projectProperty(pom, "netty.version")) - .contains("Eclipse Paho " + projectProperty(pom, "paho-mqtt.version")) - .contains("session-core/ 设备会话 + 鉴权 + Token + Redis SessionStore") - .contains("| 并发 | Disruptor 4 + 虚拟线程 |") - .contains("| 事件流 | **Kafka**(唯一生产消息通道,vin 分区,保证单车有序) |") - .contains("| 冷存 | 本地文件系统 |") - .contains("| 构建 | Maven |") - .contains("protocol-jt1078/ JT/T 1078(optional-command-gateway profile") - .contains("protocol-jsatl12/ 苏标主动安全报警附件(optional-attachments profile)") - .contains("vehicle-state-service/ Kafka 全字段事件消费 + Redis 热状态查询(optional-latest-state profile)") - .contains("command-gateway/ 可选 HTTP → 设备下行命令(optional-command-gateway profile)") - .contains("vehicle-history-app/ TDengine 历史查询 + RAW JSON") - .contains("vehicle-analytics-app/ JT808 每日里程指标消费") - .contains("协议接入应用只负责收、解析、校验、规整和投递 Kafka") - .contains("生产链路只支持 Kafka") - .contains("`vehicle-analytics-app` 将 JT808 `daily_mileage_km` 写入 MySQL `vehicle_stat_metric`") - .contains("mvn -pl :gb32960-ingest-app,:jt808-ingest-app,:yutong-mqtt-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true") - .contains("生产服务只在 ECS/Portainer 上运行") - .doesNotContain("Spring Boot 3.4") - .doesNotContain("Netty 4.1") - .doesNotContain("HiveMQ MQTT Client") - .doesNotContain("RabbitMQ") - .doesNotContain("RocketMQ") - .doesNotContain("其他 MQ 后端") - .doesNotContain("Spotless") - .doesNotContain("ArchUnit") - .doesNotContain("S3 / OSS / 本地") - .doesNotContain("StructuredTaskScope") - .doesNotContain("mvn clean install -DskipTests") - .doesNotContain("-DskipTests") - .doesNotContain("spring-boot:run") - .doesNotContain("Caffeine 内存降级") - .doesNotContain("Redis/Memory SessionStore") - .doesNotContain("event-file-store") - .doesNotContain("DuckDB") - .doesNotContain("Parquet") - .doesNotContain("Parquet + DuckDB 文件型明细库") - .doesNotContain("车辆状态/日统计消费") - .doesNotContain("本服务不写业务库") - .doesNotContain("里程统计全部由 Kafka 下游消费者实现") - .doesNotContain("| 消息队列 |"); - } - - @Test - void archiveBuildSurfaceDocumentsLocalFilesystemOnly() throws Exception { - String archivePom = Files.readString(repositoryRoot().resolve("modules/sinks/sink-archive/pom.xml")); - String moduleDataFlow = Files.readString(repositoryRoot().resolve("docs/module-data-flow.html")); - - assertThat(archivePom) - .contains("原始报文冷存:本地文件系统实现。") - .doesNotContain("S3/OSS") - .doesNotContain("MinIO") - .doesNotContain("software.amazon.awssdk") - .doesNotContain("com.aliyun.oss"); - assertThat(moduleDataFlow) - .contains("sink-archive
RawArchive 到本地文件系统冷存") - .contains("归档文件的真实本地路径只属于 ArchiveStore") - .contains("原始报文冷存,当前为本地文件系统实现") - .doesNotContain("本地/S3/OSS") - .doesNotContain("S3/OSS") - .doesNotContain("MinIO"); - } - - @Test - void yutongMqttBuildSurfaceUsesOnlyPahoClient() throws Exception { - Document pom = rootPom(); - - assertThat(hasProjectProperty(pom, "paho-mqtt.version")) - .isTrue(); - assertThat(hasDependency(pom, "org.eclipse.paho", "org.eclipse.paho.client.mqttv3")) - .isTrue(); - assertThat(hasProjectProperty(pom, "fusesource-mqtt.version")) - .isFalse(); - assertThat(hasDependency(pom, "org.fusesource.mqtt-client", "mqtt-client")) - .isFalse(); - } - - @Test - void activeAppBootPackagingConfigurationIsManagedOnceInRootPom() throws Exception { - Document pom = rootPom(); - Element managedBootPlugin = managedPlugin(pom, "org.springframework.boot", "spring-boot-maven-plugin"); - - assertThat(managedBootPlugin).isNotNull(); - assertThat(managedBootPlugin.getTextContent()) - .contains("${spring-boot.version}") - .contains("--sun-misc-unsafe-memory-access=allow") - .contains("-Dio.netty.transport.noNative=true") - .contains("repackage") - .contains("build-info"); - - for (String appPom : ACTIVE_APP_POMS) { - Document app = modulePom(appPom); - Element bootPlugin = buildPlugin(app, "org.springframework.boot", "spring-boot-maven-plugin"); - - assertThat(bootPlugin) - .as(appPom) - .isNotNull(); - assertThat(firstDirectChild(bootPlugin, "configuration")) - .as(appPom + " should inherit shared JVM arguments from root pluginManagement") - .isNull(); - assertThat(firstDirectChild(bootPlugin, "executions")) - .as(appPom + " should inherit repackage/build-info executions from root pluginManagement") - .isNull(); - } - } - - @Test - void observabilityBuildSurfaceUsesMicrometerWithoutUnusedOpenTelemetryBom() throws Exception { - Document pom = rootPom(); - Document observabilityPom = modulePom("modules/core/observability/pom.xml"); - String readme = Files.readString(repositoryRoot().resolve("README.md")); - String moduleDataFlow = Files.readString(repositoryRoot().resolve("docs/module-data-flow.html")); - String traceSpan = Files.readString(repositoryRoot() - .resolve("modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/TraceSpan.java")); - - assertThat(hasProjectProperty(pom, "micrometer.version")) - .isTrue(); - assertThat(hasDependency(pom, "io.micrometer", "micrometer-bom")) - .isTrue(); - assertThat(hasProjectProperty(pom, "otel.version")) - .isFalse(); - assertThat(hasDependency(pom, "io.opentelemetry", "opentelemetry-bom")) - .isFalse(); - assertThat(hasDependency(observabilityPom, "com.lingniu.ingest", "ingest-core")) - .as("observability is an interceptor API module and should not depend on core implementation") - .isFalse(); - assertThat(productionJavaContains("io.opentelemetry")) - .isFalse(); - assertThat(readme) - .contains("| 可观测 | Micrometer + Prometheus |") - .doesNotContain("OpenTelemetry"); - assertThat(moduleDataFlow) - .contains("observability
metrics / health") - .doesNotContain("observability
metrics / tracing / health"); - assertThat(traceSpan) - .doesNotContain("OpenTelemetry") - .doesNotContain("span"); - } - - @Test - void activeAppsDependOnSharedObservabilityForHealthAndMetrics() throws Exception { - for (String appPom : ACTIVE_APP_POMS) { - assertThat(hasDependency(modulePom(appPom), "com.lingniu.ingest", "observability")) - .as(appPom + " exposes actuator health/readiness and Prometheus metrics") - .isTrue(); - } - } - - @Test - void springdocUiIsOnlyPackagedWithAppsThatExposeRestApis() throws Exception { - for (String appPom : List.of( - "modules/apps/gb32960-ingest-app/pom.xml", - "modules/apps/jt808-ingest-app/pom.xml", - "modules/apps/yutong-mqtt-app/pom.xml")) { - assertThat(hasDependency(modulePom(appPom), "org.springdoc", "springdoc-openapi-starter-webmvc-ui")) - .as(appPom + " exposes Actuator only; Swagger UI belongs to query/API apps") - .isFalse(); - } - for (String appPom : List.of( - "modules/apps/vehicle-history-app/pom.xml", - "modules/apps/vehicle-analytics-app/pom.xml")) { - assertThat(hasDependency(modulePom(appPom), "org.springdoc", "springdoc-openapi-starter-webmvc-ui")) - .as(appPom + " exposes REST APIs") - .isTrue(); - } - } - - @Test - void buildSurfaceDoesNotManageLombokWhenProductionSourcesDoNotUseIt() throws Exception { - Document pom = rootPom(); - - assertThat(hasProjectProperty(pom, "lombok.version")) - .isFalse(); - assertThat(hasDependency(pom, "org.projectlombok", "lombok")) - .isFalse(); - assertThat(productionJavaContains("lombok.")) - .isFalse(); - } - - @Test - void buildSurfaceDoesNotManageArchUnitWhenThereAreNoArchitectureTests() throws Exception { - Document pom = rootPom(); - - assertThat(hasProjectProperty(pom, "archunit.version")) - .isFalse(); - assertThat(hasDependency(pom, "com.tngtech.archunit", "archunit-junit5")) - .isFalse(); - assertThat(javaSourcesContain("import com.tngtech." + "archunit")) - .isFalse(); - } - - @Test - void buildSurfaceDoesNotManageTestcontainersWhenKafkaTestsUseInMemoryFakes() throws Exception { - Document pom = rootPom(); - Document kafkaSinkPom = modulePom("modules/sinks/sink-kafka/pom.xml"); - - assertThat(hasProjectProperty(pom, "testcontainers.version")) - .isFalse(); - assertThat(hasDependency(pom, "org.testcontainers", "testcontainers-bom")) - .isFalse(); - assertThat(hasDependency(kafkaSinkPom, "org.testcontainers", "kafka")) - .isFalse(); - assertThat(javaSourcesContain("import org." + "testcontainers")) - .isFalse(); - } - - @Test - void productionDocsDoNotAdvertiseMemorySessionStore() throws Exception { - String runbook = Files.readString(repositoryRoot() - .resolve("docs/operations/gb32960-service-split-runbook.md")); - String moduleDataFlow = Files.readString(repositoryRoot().resolve("docs/module-data-flow.html")); - String redesignSpec = Files.readString(repositoryRoot() - .resolve("docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md")); - - assertThat(runbook) - .contains("Session state requires Redis") - .contains("REDIS_HOST") - .contains("REDIS_DATABASE=50") - .doesNotContain("SESSION_STORE=memory") - .doesNotContain("without Redis"); - assertThat(moduleDataFlow) - .contains("SessionStore 仅保留 Redis 后端") - .doesNotContain("SessionStore 支持 memory") - .doesNotContain("无 Redis 降级"); - assertThat(redesignSpec) - .contains("Reads Redis latest state") - .doesNotContain("Redis or in-memory") - .doesNotContain("Redis/in-memory"); - } - - @Test - void sessionCorePomDescriptionDoesNotAdvertiseRedisAsFallback() throws Exception { - Document sessionCorePom = modulePom("modules/core/session-core/pom.xml"); - String description = firstDirectChild(sessionCorePom.getDocumentElement(), "description").getTextContent(); - - assertThat(description) - .contains("Redis SessionStore") - .doesNotContain("兜底") - .doesNotContain("fallback") - .doesNotContain("memory"); - } - - @Test - void rootPomDescriptionMatchesCurrentRuntimeStack() throws Exception { - Document pom = rootPom(); - String description = firstDirectChild(pom.getDocumentElement(), "description").getTextContent(); - - assertThat(description) - .contains("只支持 Kafka") - .contains("Java 25") - .contains("Spring Boot " + projectProperty(pom, "spring-boot.version")) - .contains("Netty " + projectProperty(pom, "netty.version")) - .doesNotContain("Spring Boot 3.4"); - } - - @Test - void defaultProductionSurfaceOnlySupportsKafkaAsMessageBackbone() throws Exception { - List productionSurface = List.of( - repositoryRoot().resolve("README.md"), - repositoryRoot().resolve("pom.xml"), - repositoryRoot().resolve("deploy/portainer/docker-compose.yml"), - repositoryRoot().resolve("docs/operations/gb32960-service-split-runbook.md"), - repositoryRoot().resolve("docs/operations/vehicle-ingest-tdengine-verification.md"), - repositoryRoot().resolve("docs/operations/jt808-daily-mileage-runbook.md"), - repositoryRoot().resolve("docs/target-architecture.md")); - - for (Path path : productionSurface) { - String text = Files.readString(path); - - assertThat(text) - .as(path.toString()) - .doesNotContain("sink-mq") - .doesNotContain("RabbitMQ") - .doesNotContain("RocketMQ") - .doesNotContain("Pulsar") - .doesNotContain("AMQP") - .doesNotContain("其他 MQ 后端"); - } - } - - @Test - void architectureDecisionsMatchCurrentDefaultProductionSurface() throws Exception { - String decisions = Files.readString(repositoryRoot().resolve("DECISIONS.md")); - - assertThat(decisions) - .contains("## ADR-004 信达 Push:Removed") - .contains("## ADR-006 部署形态:GB32960 三应用拆分\n- **Status**: Superseded by ADR-011") - .contains("## ADR-011 默认生产面:三协议接入 + 历史 + 统计") - .contains("## ADR-012 JSATL12 附件上传:Optional only") - .contains("GB32960、JT808、Yutong MQTT、vehicle-history-app、vehicle-analytics-app") - .contains("Xinda Push 源码、Maven profile、Woodpecker 镜像发布和历史消费绑定全部移除") - .contains("JSATL12 仅保留在 `optional-attachments` profile") - .contains("vehicle-state-service 仅保留在 `optional-latest-state` profile") - .contains("raw-archive-store 原型已删除") - .contains("协议接入应用不直接写业务库") - .contains("`vehicle-analytics-app` 写入 MySQL `vehicle_stat_metric`") - .doesNotContain("event-file-store") - .doesNotContain("optional-event-file-store") - .doesNotContain("optional-raw-archive-store") - .doesNotContain("legacy-xinda") - .doesNotContain("## ADR-004 信达 Push:保留模块但彻底重写\n- **Status**: Accepted") - .doesNotContain("CI/CD 分别构建三个镜像") - .doesNotContain("Spring Boot 3.4.x"); - } - - @Test - void productionDocsMatchCompactHistoryAndJt808OnlyAnalyticsSurface() throws Exception { - String moduleDataFlow = Files.readString(repositoryRoot().resolve("docs/module-data-flow.html")); - String targetArchitecture = Files.readString(repositoryRoot().resolve("docs/target-architecture.md")); - String tdengineVerification = Files.readString(repositoryRoot() - .resolve("docs/operations/vehicle-ingest-tdengine-verification.md")); - String runbook = Files.readString(repositoryRoot() - .resolve("docs/operations/gb32960-service-split-runbook.md")); - - assertThat(moduleDataFlow) - .contains("TDengine raw_frames + locations") - .contains("TDengine raw_frames 和位置表") - .doesNotContain("telemetry field 行映射") - .doesNotContain("可选 telemetry field 表"); - assertThat(tdengineVerification) - .contains("history 写入 raw、location 到 TDengine;不写 telemetry_fields") - .doesNotContain("按需开启 telemetry field"); - assertThat(runbook) - .contains("| `vehicle.event.gb32960.v1` | `gb32960-ingest-app` | `vehicle-history-app` |") - .contains("| `vehicle.event.jt808.v1` | `jt808-ingest-app` | `vehicle-history-app`, `vehicle-analytics-app` |") - .doesNotContain("| `vehicle.event.gb32960.v1` | `gb32960-ingest-app` | `vehicle-history-app`, `vehicle-analytics-app` |"); - assertThat(targetArchitecture) - .contains("vehicle-state-service is absent from the default Maven reactor") - .doesNotContain("kafka --> state[\"vehicle-state-service\"]") - .doesNotContain("state --> redisState[\"Redis
latest state\"]"); - } - - @Test - void supersededDesignDocsPointToCurrentArchitecture() throws Exception { - for (String path : List.of( - "docs/superpowers/specs/2026-06-23-gb32960-production-readiness-design.md", - "docs/superpowers/specs/2026-06-23-gb32960-service-split-design.md")) { - String doc = Files.readString(repositoryRoot().resolve(path)); - - assertThat(doc) - .as(path) - .startsWith("> **Superseded:**") - .contains("docs/target-architecture.md") - .contains("docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md"); - } - } - - @Test - void supersededImplementationPlansPointToCurrentArchitecture() throws Exception { - for (String path : List.of( - "docs/superpowers/plans/2026-06-23-gb32960-history-duckdb-hot-store.md", - "docs/superpowers/plans/2026-06-23-gb32960-service-split.md", - "docs/superpowers/plans/2026-06-29-vehicle-ingest-redesign-phase1.md")) { - String doc = Files.readString(repositoryRoot().resolve(path)); - - assertThat(doc) - .as(path) - .startsWith("> **Superseded:**") - .contains("docs/target-architecture.md") - .contains("docs/superpowers/specs/2026-06-29-vehicle-ingest-redesign.md"); - } - } - - @Test - void duckDbAndParquetAreNotPartOfTheCurrentBuildSurface() throws Exception { - Document pom = rootPom(); - - assertThat(hasProjectProperty(pom, "duckdb.version")) - .isFalse(); - assertThat(hasDependency(pom, "org.duckdb", "duckdb_jdbc")) - .isFalse(); - assertThat(Files.exists(repositoryRoot().resolve("modules/sinks/event-file-store"))) - .isFalse(); - } - - @Test - void legacyEventFileStoreContractsAreRemovedFromProductionSources() throws Exception { - Path root = repositoryRoot(); - - assertThat(Files.exists(root.resolve("modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/history"))) - .isFalse(); - assertThat(Files.exists(root.resolve( - "modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/EventHistoryController.java"))) - .isFalse(); - assertThat(Files.exists(root.resolve( - "modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/TelemetryEnvelopeRecordMapper.java"))) - .isFalse(); - assertThat(productionJavaContains("EventFileStore")) - .isFalse(); - assertThat(productionJavaContains("EventFileRecord")) - .isFalse(); - assertThat(productionJavaContains("EventFileQuery")) - .isFalse(); - } - - private static Document rootPom() throws Exception { - return modulePom("pom.xml"); - } - - private static Document modulePom(String path) throws Exception { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - return factory.newDocumentBuilder().parse(Files.newInputStream(repositoryRoot().resolve(path))); - } - - private static String projectProperty(Document pom, String name) { - Element properties = firstDirectChild(pom.getDocumentElement(), "properties"); - Element property = firstDirectChild(properties, name); - if (property == null) { - throw new IllegalArgumentException("project property not found: " + name); - } - return property.getTextContent().trim(); - } - - private static boolean hasProjectProperty(Document pom, String name) { - Element properties = firstDirectChild(pom.getDocumentElement(), "properties"); - return properties != null && firstDirectChild(properties, name) != null; - } - - private static List defaultModules(Document pom) { - Element project = pom.getDocumentElement(); - return directModules(firstDirectChild(project, "modules")); - } - - private static List profileModules(Document pom, String profileId) { - Element profile = profile(pom, profileId); - if (profile == null) { - return List.of(); - } - return directModules(firstDirectChild(profile, "modules")); - } - - private static List rootDependencyManagementArtifacts(Document pom) { - return dependencyManagementArtifacts(pom.getDocumentElement()); - } - - private static List profileDependencyManagementArtifacts(Document pom, String profileId) { - Element profile = profile(pom, profileId); - if (profile == null) { - return List.of(); - } - return dependencyManagementArtifacts(profile); - } - - private static List profileRepositoryIds(Document pom, String profileId) { - Element profile = profile(pom, profileId); - if (profile == null) { - return List.of(); - } - return repositoryIds(profile); - } - - private static List repositoryIds(Element owner) { - Element repositories = firstDirectChild(owner, "repositories"); - if (repositories == null) { - return List.of(); - } - List ids = new ArrayList<>(); - NodeList children = repositories.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (!(children.item(i) instanceof Element repository) || !"repository".equals(repository.getTagName())) { - continue; - } - Element id = firstDirectChild(repository, "id"); - if (id != null) { - ids.add(id.getTextContent().trim()); - } - } - return List.copyOf(ids); - } - - private static Element profile(Document pom, String profileId) { - Element profiles = firstDirectChild(pom.getDocumentElement(), "profiles"); - if (profiles == null) { - return null; - } - NodeList children = profiles.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (!(children.item(i) instanceof Element profile) || !"profile".equals(profile.getTagName())) { - continue; - } - Element id = firstDirectChild(profile, "id"); - if (id != null && profileId.equals(id.getTextContent().trim())) { - return profile; - } - } - return null; - } - - private static List directModules(Element modules) { - if (modules == null) { - return List.of(); - } - List out = new ArrayList<>(); - NodeList children = modules.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (children.item(i) instanceof Element child && "module".equals(child.getTagName())) { - out.add(child.getTextContent().trim()); - } - } - return List.copyOf(out); - } - - private static List dependencyManagementArtifacts(Element owner) { - Element dependencyManagement = firstDirectChild(owner, "dependencyManagement"); - if (dependencyManagement == null) { - return List.of(); - } - Element dependencies = firstDirectChild(dependencyManagement, "dependencies"); - if (dependencies == null) { - return List.of(); - } - List artifacts = new ArrayList<>(); - NodeList children = dependencies.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (!(children.item(i) instanceof Element dependency) || !"dependency".equals(dependency.getTagName())) { - continue; - } - Element groupId = firstDirectChild(dependency, "groupId"); - Element artifactId = firstDirectChild(dependency, "artifactId"); - if (groupId != null - && artifactId != null - && "com.lingniu.ingest".equals(groupId.getTextContent().trim())) { - artifacts.add(artifactId.getTextContent().trim()); - } - } - return List.copyOf(artifacts); - } - - private static Element managedPlugin(Document pom, String groupId, String artifactId) { - Element build = firstDirectChild(pom.getDocumentElement(), "build"); - Element pluginManagement = build == null ? null : firstDirectChild(build, "pluginManagement"); - return plugin(pluginManagement, groupId, artifactId); - } - - private static Element buildPlugin(Document pom, String groupId, String artifactId) { - Element build = firstDirectChild(pom.getDocumentElement(), "build"); - return plugin(build, groupId, artifactId); - } - - private static Element plugin(Element owner, String groupId, String artifactId) { - Element plugins = owner == null ? null : firstDirectChild(owner, "plugins"); - if (plugins == null) { - return null; - } - NodeList children = plugins.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (!(children.item(i) instanceof Element plugin) || !"plugin".equals(plugin.getTagName())) { - continue; - } - Element currentGroupId = firstDirectChild(plugin, "groupId"); - Element currentArtifactId = firstDirectChild(plugin, "artifactId"); - if (currentGroupId != null - && currentArtifactId != null - && groupId.equals(currentGroupId.getTextContent().trim()) - && artifactId.equals(currentArtifactId.getTextContent().trim())) { - return plugin; - } - } - return null; - } - - private static boolean hasDependency(Document pom, String groupId, String artifactId) { - Element owner = pom.getDocumentElement(); - Element dependencyManagement = firstDirectChild(owner, "dependencyManagement"); - Element dependencies = firstDirectChild(owner, "dependencies"); - if (hasDependencyIn(dependencies, groupId, artifactId)) { - return true; - } - if (dependencyManagement == null) { - return false; - } - return hasDependencyIn(firstDirectChild(dependencyManagement, "dependencies"), groupId, artifactId); - } - - private static boolean hasDependencyIn(Element dependencies, String groupId, String artifactId) { - if (dependencies == null) { - return false; - } - NodeList children = dependencies.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (!(children.item(i) instanceof Element dependency) || !"dependency".equals(dependency.getTagName())) { - continue; - } - Element currentGroupId = firstDirectChild(dependency, "groupId"); - Element currentArtifactId = firstDirectChild(dependency, "artifactId"); - if (currentGroupId != null - && currentArtifactId != null - && groupId.equals(currentGroupId.getTextContent().trim()) - && artifactId.equals(currentArtifactId.getTextContent().trim())) { - return true; - } - } - return false; - } - - private static boolean productionJavaContains(String token) throws Exception { - return javaSourcesContain(token, false); - } - - private static boolean javaSourcesContain(String token) throws Exception { - return javaSourcesContain(token, true); - } - - private static boolean javaSourcesContain(String token, boolean includeTests) throws Exception { - try (Stream files = Files.walk(repositoryRoot().resolve("modules"))) { - return files - .filter(Files::isRegularFile) - .filter(path -> path.toString().endsWith(".java")) - .filter(path -> includeTests || !path.toString().contains("/src/test/")) - .anyMatch(path -> { - try { - return Files.readString(path).contains(token); - } catch (Exception e) { - throw new IllegalStateException("failed to read " + path, e); - } - }); - } - } - - private static List javaFilesNamed(String fileName) throws Exception { - Path root = repositoryRoot(); - try (Stream files = Files.walk(root.resolve("modules"))) { - return files - .filter(Files::isRegularFile) - .filter(path -> fileName.equals(path.getFileName().toString())) - .map(root::relativize) - .map(Path::toString) - .sorted() - .toList(); - } - } - - private static Element firstDirectChild(Element parent, String tagName) { - NodeList children = parent.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (children.item(i) instanceof Element child && tagName.equals(child.getTagName())) { - return child; - } - } - return null; - } - - private static Path repositoryRoot() { - Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath(); - while (current != null) { - if (Files.exists(current.resolve("pom.xml")) && Files.exists(current.resolve("modules"))) { - return current; - } - current = current.getParent(); - } - throw new IllegalStateException("repository root not found"); - } -} diff --git a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java deleted file mode 100644 index 98fa48a7..00000000 --- a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/PortainerComposeResourceLimitsTest.java +++ /dev/null @@ -1,679 +0,0 @@ -package com.lingniu.ingest.historyapp; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; -import org.springframework.core.io.FileSystemResource; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -class PortainerComposeResourceLimitsTest { - - @Test - void portainerStackDocumentsDefaultProductionSurfaceOnly() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - - assertThat(compose) - .contains("Default production stack: GB32960, JT808, Yutong MQTT, history, analytics.") - .contains("Legacy and optional services stay outside this compose file.") - .doesNotContain("KAFKA_ENABLED") - .doesNotContain("\n xinda-push-app:\n") - .doesNotContain("\n command-gateway:\n") - .doesNotContain("\n vehicle-state-service:\n") - .doesNotContain("\n raw-archive-store:\n") - .doesNotContain("\n event-file-store:\n"); - } - - @Test - void productionServicesHaveOverridableMemoryLimits() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - - assertServiceMemoryLimit(compose, "gb32960-ingest-app", "GB32960_MEM_LIMIT", "768m"); - assertServiceMemoryLimit(compose, "jt808-ingest-app", "JT808_MEM_LIMIT", "768m"); - assertServiceMemoryLimit(compose, "yutong-mqtt-app", "YUTONG_MQTT_MEM_LIMIT", "512m"); - assertServiceMemoryLimit(compose, "vehicle-history-app", "VEHICLE_HISTORY_MEM_LIMIT", "1536m"); - assertServiceMemoryLimit(compose, "vehicle-analytics-app", "VEHICLE_ANALYTICS_MEM_LIMIT", "1024m"); - } - - @Test - void productionImagesShareOverridableRegistryAndNamespace() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String registry = "${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}"; - String namespace = "${LINGNIU_IMAGE_NAMESPACE:-oneos}"; - - for (String serviceName : List.of( - "gb32960-ingest-app", - "jt808-ingest-app", - "yutong-mqtt-app", - "vehicle-history-app", - "vehicle-analytics-app")) { - assertThat(serviceBlock(compose, serviceName)) - .contains("image: " + registry + "/" + namespace + "/" + serviceName - + ":${LINGNIU_IMAGE_VERSION:?set LINGNIU_IMAGE_VERSION}"); - } - assertThat(countOccurrences(compose, "image: crpi-85r4m0ackrm3qpje")) - .isZero(); - assertThat(countOccurrences(compose, "/oneos/")).isZero(); - } - - @Test - void portainerServicesUseContainerReadinessHealthchecks() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String dockerfile = Files.readString(repositoryRoot().resolve("Dockerfile")); - - assertThat(dockerfile).contains("apt-get install -y --no-install-recommends curl"); - assertThat(compose) - .contains("x-readiness-healthcheck: &readiness-healthcheck\n" - + " interval: 30s\n" - + " timeout: 5s\n" - + " retries: 3\n" - + " start_period: 60s"); - assertServiceReadinessHealthcheck(compose, "gb32960-ingest-app", "20100"); - assertServiceReadinessHealthcheck(compose, "jt808-ingest-app", "20400"); - assertServiceReadinessHealthcheck(compose, "yutong-mqtt-app", "20500"); - assertServiceReadinessHealthcheck(compose, "vehicle-history-app", "20200"); - assertServiceReadinessHealthcheck(compose, "vehicle-analytics-app", "20310"); - } - - @Test - void containerDefaultsIncludeJava25UnsafeCompatibilityFlag() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String dockerfile = Files.readString(repositoryRoot().resolve("Dockerfile")); - - assertThat(dockerfile) - .contains("ENV JAVA_OPTS=\"--sun-misc-unsafe-memory-access=allow -Dio.netty.transport.noNative=true\""); - assertThat(commonEnvBlock(compose)) - .contains("JAVA_OPTS: ${JAVA_OPTS:---sun-misc-unsafe-memory-access=allow -Dio.netty.transport.noNative=true -XX:+UseZGC -XX:MaxRAMPercentage=75}"); - } - - @Test - void redisSessionDefaultsUseEcsPrivateEndpoint() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - - assertThat(compose) - .contains("REDIS_HOST: ${REDIS_HOST:-r-bp1u741kij7e51i481.redis.rds.aliyuncs.com}") - .doesNotContain("r-bp1u741kij7e51i481pd.redis.rds.aliyuncs.com"); - } - - @Test - void activeAppsExposeHealthProbeGroupsByDefault() { - Path root = repositoryRoot(); - List activeAppConfigs = List.of( - root.resolve("modules/apps/gb32960-ingest-app/src/main/resources/application.yml"), - root.resolve("modules/apps/jt808-ingest-app/src/main/resources/application.yml"), - root.resolve("modules/apps/yutong-mqtt-app/src/main/resources/application.yml"), - root.resolve("modules/apps/vehicle-history-app/src/main/resources/application.yml"), - root.resolve("modules/apps/vehicle-analytics-app/src/main/resources/application.yml")); - - for (Path config : activeAppConfigs) { - assertThat(yamlProperties(config)) - .as(config.toString()) - .containsEntry("management.endpoint.health.probes.enabled", - "${MANAGEMENT_HEALTH_PROBES_ENABLED:true}"); - } - } - - @Test - void productionRunbookChecksLivenessAndReadinessForActiveApps() throws IOException { - String runbook = Files.readString(repositoryRoot() - .resolve("docs/operations/gb32960-service-split-runbook.md")); - - for (String httpPort : List.of("20100", "20400", "20500", "20200", "20310")) { - assertThat(runbook) - .contains("curl -sS http://127.0.0.1:" + httpPort + "/actuator/health/liveness") - .contains("curl -sS http://127.0.0.1:" + httpPort + "/actuator/health/readiness"); - } - } - - @Test - void defaultProductionSurfaceDoesNotShipLocalLaunchctlRunners() throws IOException { - Path root = repositoryRoot(); - String runbook = Files.readString(root.resolve("docs/operations/gb32960-service-split-runbook.md")); - - assertThat(root.resolve("deploy/local/launchctl")).doesNotExist(); - assertThat(runbook) - .contains("Production services run on ECS through Portainer/Docker.") - .doesNotContain("Local launchctl Deployment") - .doesNotContain("deploy/local/launchctl") - .doesNotContain("launchctl bootstrap") - .doesNotContain("launchctl bootout"); - } - - @Test - void tdengineVerificationRunbookChecksLivenessAndReadinessForManagedApps() throws IOException { - String runbook = Files.readString(repositoryRoot() - .resolve("docs/operations/vehicle-ingest-tdengine-verification.md")); - - for (String httpPort : List.of("20100", "20400", "20500", "20200", "20310")) { - assertThat(runbook) - .contains("curl -sS http://127.0.0.1:" + httpPort + "/actuator/health/liveness") - .contains("curl -sS http://127.0.0.1:" + httpPort + "/actuator/health/readiness"); - } - } - - @Test - void operationRunbooksUseBuildFlagThatSkipsTestCompilation() throws IOException { - Path operations = repositoryRoot().resolve("docs/operations"); - String splitRunbook = Files.readString(operations.resolve("gb32960-service-split-runbook.md")); - String tdengineVerification = Files.readString(operations.resolve("vehicle-ingest-tdengine-verification.md")); - - assertThat(splitRunbook) - .contains("-Dmaven.test.skip=true") - .doesNotContain("-DskipTests"); - assertThat(tdengineVerification) - .contains("-Dmaven.test.skip=true") - .doesNotContain("-DskipTests"); - } - - @Test - void dockerEntrypointExecsJavaForSignalHandling() throws IOException { - String dockerfile = Files.readString(repositoryRoot().resolve("Dockerfile")); - - assertThat(dockerfile) - .contains("ENTRYPOINT [\"sh\", \"-c\", \"exec java $JAVA_OPTS -jar /app/app.jar\"]") - .doesNotContain("\"java $JAVA_OPTS -jar /app/app.jar\""); - } - - @Test - void gb32960ComposeExposesBothPlatformAccountsWithoutSecrets() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String gb32960Service = serviceBlock(compose, "gb32960-ingest-app"); - - assertThat(gb32960Service) - .contains("GB32960_PLATFORM_USER_HYUNDAI: ${GB32960_PLATFORM_USER_HYUNDAI:-Hyundai}") - .contains("GB32960_PLATFORM_PWD_HYUNDAI: ${GB32960_PLATFORM_PWD_HYUNDAI:-}") - .contains("GB32960_PLATFORM_IP_HYUNDAI: ${GB32960_PLATFORM_IP_HYUNDAI:-}") - .contains("GB32960_PLATFORM_USER_YUEJIN: ${GB32960_PLATFORM_USER_YUEJIN:-YueJin}") - .contains("GB32960_PLATFORM_PWD_YUEJIN: ${GB32960_PLATFORM_PWD_YUEJIN:-}") - .contains("GB32960_PLATFORM_IP_YUEJIN: ${GB32960_PLATFORM_IP_YUEJIN:-}"); - } - - @Test - void vehicleHistoryOnlyExposesBaseConsumerGroupEnvironmentName() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String historyService = serviceBlock(compose, "vehicle-history-app"); - - assertThat(historyService) - .contains("KAFKA_GROUP_HISTORY: ${KAFKA_GROUP_HISTORY:-vehicle-history}") - .doesNotContain("KAFKA_GROUP_HISTORY_GB32960_EVENT") - .doesNotContain("KAFKA_GROUP_HISTORY_JT808_EVENT") - .doesNotContain("KAFKA_GROUP_HISTORY_YUTONG_MQTT_EVENT") - .doesNotContain("KAFKA_GROUP_HISTORY_GB32960_RAW") - .doesNotContain("KAFKA_GROUP_HISTORY_JT808_RAW") - .doesNotContain("KAFKA_GROUP_HISTORY_YUTONG_MQTT_RAW"); - } - - @Test - void kafkaConsumerEnvironmentIsScopedToConsumerAppsOnly() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String commonEnv = commonEnvBlock(compose); - String ingestServices = serviceBlock(compose, "gb32960-ingest-app") - + serviceBlock(compose, "jt808-ingest-app") - + serviceBlock(compose, "yutong-mqtt-app"); - String historyService = serviceBlock(compose, "vehicle-history-app"); - String analyticsService = serviceBlock(compose, "vehicle-analytics-app"); - - assertThat(commonEnv) - .doesNotContain("KAFKA_CONSUMER_ENABLED") - .doesNotContain("KAFKA_CONSUMER_MAX_POLL_INTERVAL"); - assertThat(ingestServices) - .doesNotContain("KAFKA_CONSUMER_"); - assertThat(historyService) - .contains("KAFKA_CONSUMER_ENABLED: ${KAFKA_CONSUMER_ENABLED_HISTORY:-true}") - .contains("KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS: ${KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS_HISTORY:-1800000}") - .doesNotContain("KAFKA_CONSUMER_MAX_POLL_INTERVAL_MS"); - assertThat(analyticsService) - .contains("KAFKA_CONSUMER_ENABLED: ${KAFKA_CONSUMER_ENABLED_ANALYTICS:-true}") - .contains("KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS: ${KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS_ANALYTICS:-900000}"); - } - - @Test - void kafkaTopicEnvironmentIsScopedToServicesThatUseIt() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String commonEnv = commonEnvBlock(compose); - String gb32960Service = serviceBlock(compose, "gb32960-ingest-app"); - String jt808Service = serviceBlock(compose, "jt808-ingest-app"); - String mqttService = serviceBlock(compose, "yutong-mqtt-app"); - String historyService = serviceBlock(compose, "vehicle-history-app"); - String analyticsService = serviceBlock(compose, "vehicle-analytics-app"); - - assertThat(commonEnv).doesNotContain("KAFKA_TOPIC_"); - assertThat(gb32960Service) - .contains("KAFKA_TOPIC_GB32960_EVENT: ${KAFKA_TOPIC_GB32960_EVENT:-vehicle.event.gb32960.v1}") - .contains("KAFKA_TOPIC_GB32960_RAW: ${KAFKA_TOPIC_GB32960_RAW:-vehicle.raw.gb32960.v1}") - .contains("KAFKA_TOPIC_GB32960_DLQ: ${KAFKA_TOPIC_GB32960_DLQ:-vehicle.dlq.gb32960.v1}") - .doesNotContain("KAFKA_TOPIC_JT808") - .doesNotContain("KAFKA_TOPIC_YUTONG_MQTT"); - assertThat(jt808Service) - .contains("KAFKA_TOPIC_JT808_EVENT: ${KAFKA_TOPIC_JT808_EVENT:-vehicle.event.jt808.v1}") - .contains("KAFKA_TOPIC_JT808_RAW: ${KAFKA_TOPIC_JT808_RAW:-vehicle.raw.jt808.v1}") - .contains("KAFKA_TOPIC_JT808_DLQ: ${KAFKA_TOPIC_JT808_DLQ:-vehicle.dlq.jt808.v1}") - .doesNotContain("KAFKA_TOPIC_GB32960") - .doesNotContain("KAFKA_TOPIC_YUTONG_MQTT"); - assertThat(mqttService) - .contains("KAFKA_TOPIC_YUTONG_MQTT_EVENT: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:-vehicle.event.mqtt-yutong.v1}") - .contains("KAFKA_TOPIC_YUTONG_MQTT_RAW: ${KAFKA_TOPIC_YUTONG_MQTT_RAW:-vehicle.raw.mqtt-yutong.v1}") - .contains("KAFKA_TOPIC_YUTONG_MQTT_DLQ: ${KAFKA_TOPIC_YUTONG_MQTT_DLQ:-vehicle.dlq.mqtt-yutong.v1}") - .doesNotContain("KAFKA_TOPIC_GB32960") - .doesNotContain("KAFKA_TOPIC_JT808"); - assertThat(historyService) - .contains("KAFKA_TOPIC_GB32960_EVENT: ${KAFKA_TOPIC_GB32960_EVENT:-vehicle.event.gb32960.v1}") - .contains("KAFKA_TOPIC_GB32960_RAW: ${KAFKA_TOPIC_GB32960_RAW:-vehicle.raw.gb32960.v1}") - .contains("KAFKA_TOPIC_JT808_EVENT: ${KAFKA_TOPIC_JT808_EVENT:-vehicle.event.jt808.v1}") - .contains("KAFKA_TOPIC_JT808_RAW: ${KAFKA_TOPIC_JT808_RAW:-vehicle.raw.jt808.v1}") - .contains("KAFKA_TOPIC_YUTONG_MQTT_EVENT: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:-vehicle.event.mqtt-yutong.v1}") - .contains("KAFKA_TOPIC_YUTONG_MQTT_RAW: ${KAFKA_TOPIC_YUTONG_MQTT_RAW:-vehicle.raw.mqtt-yutong.v1}") - .contains("KAFKA_TOPIC_HISTORY_DLQ: ${KAFKA_TOPIC_HISTORY_DLQ:-vehicle.dlq.history.v1}") - .doesNotContain("KAFKA_TOPIC_GB32960_DLQ") - .doesNotContain("KAFKA_TOPIC_JT808_DLQ") - .doesNotContain("KAFKA_TOPIC_YUTONG_MQTT_DLQ"); - assertThat(analyticsService) - .contains("KAFKA_TOPIC_JT808_EVENT: ${KAFKA_TOPIC_JT808_EVENT:-vehicle.event.jt808.v1}") - .contains("KAFKA_TOPIC_JT808_DLQ: ${KAFKA_TOPIC_JT808_DLQ:-vehicle.dlq.jt808.v1}") - .doesNotContain("KAFKA_TOPIC_GB32960") - .doesNotContain("KAFKA_TOPIC_YUTONG_MQTT") - .doesNotContain("KAFKA_TOPIC_JT808_RAW"); - } - - @Test - void vehicleHistoryUsesTdengineHistoryEnvironmentNames() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - - assertThat(compose) - .contains("TDENGINE_HISTORY_ENABLED: ${TDENGINE_HISTORY_ENABLED:-true}") - .contains("TDENGINE_HISTORY_DATABASE: ${TDENGINE_HISTORY_DATABASE:-vehicle_ts}") - .doesNotContain("TDENGINE_TELEMETRY_FIELDS_ENABLED") - .doesNotContain("\n TDENGINE_ENABLED:") - .doesNotContain("\n TDENGINE_DATABASE:"); - } - - @Test - void vehicleHistoryDoesNotExposeUnusedArchiveSinkSettings() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String historyService = serviceBlock(compose, "vehicle-history-app"); - - assertThat(historyService) - .doesNotContain("SINK_ARCHIVE_ENABLED") - .doesNotContain("SINK_ARCHIVE_PATH") - .doesNotContain("/archive"); - assertThat(compose) - .doesNotContain("vehicle-history-archive:"); - } - - @Test - void portainerIngestAppsWriteColdArchiveToMountedDataVolume() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String gb32960Service = serviceBlock(compose, "gb32960-ingest-app"); - String jt808Service = serviceBlock(compose, "jt808-ingest-app"); - String mqttService = serviceBlock(compose, "yutong-mqtt-app"); - - assertThat(gb32960Service) - .contains("SINK_ARCHIVE_PATH: ${GB32960_ARCHIVE_PATH:-/data/archive}") - .contains("- gb32960-ingest-data:/data"); - assertThat(jt808Service) - .contains("SINK_ARCHIVE_PATH: ${JT808_ARCHIVE_PATH:-/data/archive}") - .contains("- jt808-ingest-data:/data"); - assertThat(mqttService) - .contains("SINK_ARCHIVE_PATH: ${YUTONG_MQTT_ARCHIVE_PATH:-/data/archive}") - .contains("- yutong-mqtt-data:/data"); - assertThat(compose).contains("\n yutong-mqtt-data:\n"); - } - - @Test - void operationRunbooksDescribeTdengineRawJsonHotPath() throws IOException { - Path operations = repositoryRoot().resolve("docs/operations"); - String tdengineVerification = Files.readString(operations.resolve("vehicle-ingest-tdengine-verification.md")); - String splitRunbook = Files.readString(operations.resolve("gb32960-service-split-runbook.md")); - - assertThat(tdengineVerification) - .contains("TDengine `raw_frames`") - .doesNotContain("20482") - .doesNotContain("三个服务必须共用同一个 `SINK_ARCHIVE_PATH`") - .doesNotContain("再回读共享 archive") - .doesNotContain("读取共享 archive 中的原始 `.bin`") - .doesNotContain("EVENT_FILE_STORE_ENABLED") - .doesNotContain("EVENT_FILE_STORE_PATH") - .doesNotContain("TDENGINE_TELEMETRY_FIELDS_ENABLED") - .doesNotContain("/api/event-history/telemetry/fields' 会存在") - .doesNotContain("/api/event-history/records"); - assertThat(splitRunbook) - .contains("TDengine `raw_frames`") - .doesNotContain("must share the same `SINK_ARCHIVE_PATH`") - .doesNotContain("history can only read raw frames") - .doesNotContain("shared volume mounted to all three services"); - } - - @Test - void splitRunbookDocumentsAllActiveProductionAppsAndTopics() throws IOException { - String splitRunbook = Files.readString(repositoryRoot() - .resolve("docs/operations/gb32960-service-split-runbook.md")); - String activeBuildCommand = - "mvn -pl :gb32960-ingest-app,:jt808-ingest-app,:yutong-mqtt-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true"; - - assertThat(splitRunbook) - .contains("| Yutong MQTT ingest | `:yutong-mqtt-app`") - .contains("`vehicle.raw.mqtt-yutong.v1`") - .contains("`vehicle.event.mqtt-yutong.v1`") - .contains("`vehicle.dlq.mqtt-yutong.v1`") - .contains("`vehicle.dlq.history.v1`") - .contains(activeBuildCommand) - .contains("modules/apps/yutong-mqtt-app/target/yutong-mqtt-app.jar") - .doesNotContain("20482") - .doesNotContain("bootstrap-all") - .doesNotContain("mvn -pl :gb32960-ingest-app,:vehicle-history-app,:vehicle-analytics-app") - .doesNotContain("mvn -pl :gb32960-ingest-app,:jt808-ingest-app,:vehicle-history-app,:vehicle-analytics-app") - .doesNotContain("repackaged all three app jars"); - } - - @Test - void tdengineVerificationRunbookDocumentsAllActiveProductionApps() throws IOException { - String runbook = Files.readString(repositoryRoot() - .resolve("docs/operations/vehicle-ingest-tdengine-verification.md")); - String activeBuildCommand = - "mvn -pl :gb32960-ingest-app,:jt808-ingest-app,:yutong-mqtt-app,:vehicle-history-app,:vehicle-analytics-app -am package -Dmaven.test.skip=true"; - - assertThat(runbook) - .contains(activeBuildCommand) - .contains("modules/apps/gb32960-ingest-app/target/gb32960-ingest-app.jar") - .contains("modules/apps/jt808-ingest-app/target/jt808-ingest-app.jar") - .contains("modules/apps/yutong-mqtt-app/target/yutong-mqtt-app.jar") - .contains("modules/apps/vehicle-history-app/target/vehicle-history-app.jar") - .contains("modules/apps/vehicle-analytics-app/target/vehicle-analytics-app.jar") - .contains("vehicle.raw.mqtt-yutong.v1") - .contains("vehicle.event.mqtt-yutong.v1") - .contains("vehicle-history-yutong-mqtt-event") - .contains("vehicle-history-yutong-mqtt-raw") - .contains("vehicle-analytics-app") - .doesNotContain("mvn -pl modules/apps/jt808-ingest-app,modules/apps/gb32960-ingest-app,modules/apps/vehicle-history-app") - .doesNotContain("健康检查已确认三个服务"); - } - - @Test - void architectureDocsNameKafkaInsteadOfGenericMq() throws IOException { - String decisions = Files.readString(repositoryRoot().resolve("DECISIONS.md")); - String dataFlow = Files.readString(repositoryRoot().resolve("docs/module-data-flow.html")); - - assertThat(decisions) - .contains("生产链路只支持 Kafka") - .doesNotContain("MQ 选型") - .doesNotContain("RocketMQ"); - assertThat(dataFlow) - .contains("participant Kafka as sink-kafka") - .doesNotContain("participant MQ") - .doesNotContain("->>MQ") - .doesNotContain("MQ->>"); - } - - @Test - void activeSpringBootAppsDoNotScanWholeIngestPackage() throws IOException { - Path root = repositoryRoot(); - List activeApps = List.of( - root.resolve("modules/apps/gb32960-ingest-app/src/main/java/com/lingniu/ingest/gb32960app/Gb32960IngestApplication.java"), - root.resolve("modules/apps/jt808-ingest-app/src/main/java/com/lingniu/ingest/jt808app/Jt808IngestApplication.java"), - root.resolve("modules/apps/yutong-mqtt-app/src/main/java/com/lingniu/ingest/yutongmqttapp/YutongMqttApplication.java"), - root.resolve("modules/apps/vehicle-history-app/src/main/java/com/lingniu/ingest/historyapp/VehicleHistoryApplication.java"), - root.resolve("modules/apps/vehicle-analytics-app/src/main/java/com/lingniu/ingest/analyticsapp/VehicleAnalyticsApplication.java")); - - for (Path app : activeApps) { - assertThat(Files.readString(app)) - .contains("@SpringBootApplication") - .doesNotContain("scanBasePackages = \"com.lingniu.ingest\""); - } - } - - @Test - void splitRunbookKeepsAnalyticsAsJt808MetricRuntimeOnly() throws IOException { - String splitRunbook = Files.readString(repositoryRoot() - .resolve("docs/operations/gb32960-service-split-runbook.md")); - - assertThat(splitRunbook) - .contains("Consume JT808 event Kafka records") - .contains("vehicle_stat_metric") - .doesNotContain("Analytics state:") - .doesNotContain("VEHICLE_STATE_ENABLED") - .doesNotContain("latest-state API in analytics") - .doesNotContain("Vehicle state can also be enabled from this runtime"); - } - - @Test - void splitRunbookUsesTdengineHistoryVerificationInsteadOfLegacyRecordApi() throws IOException { - String splitRunbook = Files.readString(repositoryRoot() - .resolve("docs/operations/gb32960-service-split-runbook.md")); - - assertThat(splitRunbook) - .contains("TDENGINE_HISTORY_ENABLED=true") - .contains("SELECT COUNT(*) FROM raw_frames") - .contains("SELECT COUNT(*) FROM vehicle_locations") - .doesNotContain("EVENT_FILE_STORE_PATH") - .doesNotContain("target/split-event-store") - .doesNotContain("event-store files") - .doesNotContain("/api/event-history/records?"); - } - - @Test - void vehicleAnalyticsComposeOnlyExposesStatConsumerEnvironment() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String analyticsService = serviceBlock(compose, "vehicle-analytics-app"); - - assertThat(analyticsService) - .contains("KAFKA_GROUP_STAT: ${KAFKA_GROUP_STAT:-vehicle-stat}") - .contains("VEHICLE_STAT_ENABLED: ${VEHICLE_STAT_ENABLED:-true}") - .contains("VEHICLE_STAT_JT808_MILEAGE_ENABLED: ${VEHICLE_STAT_JT808_MILEAGE_ENABLED:-true}") - .doesNotContain("KAFKA_GROUP_STATE") - .doesNotContain("VEHICLE_STATE_ENABLED"); - } - - @Test - void kafkaConsumerAppsDoNotDependOnProtocolIngestContainers() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String historyService = serviceBlock(compose, "vehicle-history-app"); - String analyticsService = serviceBlock(compose, "vehicle-analytics-app"); - - assertThat(historyService) - .doesNotContain(" depends_on:") - .doesNotContain("- gb32960-ingest-app") - .doesNotContain("- jt808-ingest-app") - .doesNotContain("- yutong-mqtt-app"); - assertThat(analyticsService) - .doesNotContain(" depends_on:") - .doesNotContain("- gb32960-ingest-app") - .doesNotContain("- jt808-ingest-app") - .doesNotContain("- yutong-mqtt-app"); - } - - @Test - void vehicleAnalyticsDoesNotInheritRedisStateEnvironment() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String commonEnv = commonEnvBlock(compose); - String analyticsService = serviceBlock(compose, "vehicle-analytics-app"); - - assertThat(analyticsService).contains("<<: *common-env"); - assertThat(commonEnv) - .doesNotContain("REDIS_HOST") - .doesNotContain("REDIS_PORT") - .doesNotContain("REDIS_DATABASE") - .doesNotContain("REDIS_USERNAME") - .doesNotContain("REDIS_PASSWORD"); - } - - @Test - void mysqlEnvironmentIsScopedToIdentityAndAnalyticsOnly() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String commonEnv = commonEnvBlock(compose); - String identityEnv = blockBetween(compose, "x-identity-mysql-env: &identity-mysql-env\n", - "\nx-redis-session-env:"); - String ingestServices = serviceBlock(compose, "gb32960-ingest-app") - + serviceBlock(compose, "jt808-ingest-app") - + serviceBlock(compose, "yutong-mqtt-app"); - String analyticsService = serviceBlock(compose, "vehicle-analytics-app"); - - assertThat(commonEnv) - .doesNotContain("MYSQL_JDBC_URL") - .doesNotContain("MYSQL_USERNAME") - .doesNotContain("MYSQL_PASSWORD"); - assertThat(identityEnv) - .contains("VEHICLE_IDENTITY_MYSQL_JDBC_URL: ${MYSQL_JDBC_URL:-}") - .contains("VEHICLE_IDENTITY_MYSQL_USERNAME: ${MYSQL_USERNAME:-}") - .contains("VEHICLE_IDENTITY_MYSQL_PASSWORD: ${MYSQL_PASSWORD:-}"); - assertThat(ingestServices) - .doesNotContain("\n MYSQL_JDBC_URL:") - .doesNotContain("\n MYSQL_USERNAME:") - .doesNotContain("\n MYSQL_PASSWORD:"); - assertThat(analyticsService) - .contains("MYSQL_JDBC_URL: ${MYSQL_JDBC_URL:-}") - .contains("MYSQL_USERNAME: ${MYSQL_USERNAME:-}") - .contains("MYSQL_PASSWORD: ${MYSQL_PASSWORD:-}"); - } - - @Test - void ingestAppsReuseSharedIdentityAndRedisEnvironmentAnchors() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - String gb32960Service = serviceBlock(compose, "gb32960-ingest-app"); - String jt808Service = serviceBlock(compose, "jt808-ingest-app"); - String mqttService = serviceBlock(compose, "yutong-mqtt-app"); - - assertThat(compose) - .contains("x-identity-mysql-env: &identity-mysql-env") - .contains("x-redis-session-env: &redis-session-env") - .doesNotContain("VEHICLE_IDENTITY_STORE"); - assertThat(gb32960Service) - .contains("- *identity-mysql-env") - .contains("- *redis-session-env"); - assertThat(jt808Service) - .contains("- *identity-mysql-env") - .contains("- *redis-session-env"); - assertThat(mqttService) - .contains("- *identity-mysql-env") - .doesNotContain("- *redis-session-env"); - - assertThat(gb32960Service + jt808Service + mqttService) - .doesNotContain("VEHICLE_IDENTITY_MYSQL_JDBC_URL") - .doesNotContain("VEHICLE_IDENTITY_MYSQL_USERNAME") - .doesNotContain("VEHICLE_IDENTITY_MYSQL_PASSWORD") - .doesNotContain("REDIS_HOST") - .doesNotContain("REDIS_PORT") - .doesNotContain("REDIS_DATABASE") - .doesNotContain("REDIS_USERNAME") - .doesNotContain("REDIS_PASSWORD"); - } - - @Test - void moduleDataFlowShowsSplitProductionAppsInsteadOfDeletedBootstrapAll() throws IOException { - String moduleDataFlow = Files.readString(repositoryRoot().resolve("docs/module-data-flow.html")); - - assertThat(moduleDataFlow) - .contains("gb32960-ingest-app") - .contains("jt808-ingest-app") - .contains("yutong-mqtt-app") - .contains("vehicle-history-app") - .contains("vehicle-analytics-app") - .doesNotContain("bootstrap-all"); - } - - private static void assertServiceMemoryLimit(String compose, - String serviceName, - String envName, - String defaultLimit) { - assertThat(compose) - .contains(serviceName + ":\n") - .contains("\n " + serviceName + ":\n") - .contains(" mem_limit: ${" + envName + ":-" + defaultLimit + "}"); - } - - private static void assertServiceReadinessHealthcheck(String compose, String serviceName, String httpPort) { - assertThat(serviceBlock(compose, serviceName)) - .contains(" healthcheck:\n") - .contains(" <<: *readiness-healthcheck\n") - .contains(" test: [\"CMD-SHELL\", \"curl -fsS http://127.0.0.1:" - + httpPort + "/actuator/health/readiness >/dev/null\"]") - .doesNotContain(" interval: 30s\n") - .doesNotContain(" timeout: 5s\n") - .doesNotContain(" retries: 3\n") - .doesNotContain(" start_period: 60s\n"); - } - - private static String serviceBlock(String compose, String serviceName) { - String marker = "\n " + serviceName + ":\n"; - int start = compose.indexOf(marker); - if (start < 0) { - throw new IllegalArgumentException("service not found: " + serviceName); - } - for (int cursor = start + marker.length(); cursor < compose.length(); ) { - int nextLine = compose.indexOf('\n', cursor); - if (nextLine < 0) { - return compose.substring(start); - } - int lineStart = nextLine + 1; - if (lineStart + 2 < compose.length() - && compose.charAt(lineStart) == ' ' - && compose.charAt(lineStart + 1) == ' ' - && compose.charAt(lineStart + 2) != ' ') { - return compose.substring(start, nextLine); - } - cursor = lineStart; - } - return compose.substring(start); - } - - private static String commonEnvBlock(String compose) { - String marker = "x-common-env: &common-env\n"; - int start = compose.indexOf(marker); - if (start < 0) { - throw new IllegalArgumentException("common env block not found"); - } - int end = compose.indexOf("\nx-identity-mysql-env:", start); - if (end < 0) { - end = compose.indexOf("\nservices:\n", start); - } - if (end < 0) { - throw new IllegalArgumentException("services block not found"); - } - return compose.substring(start, end); - } - - private static String blockBetween(String text, String startMarker, String endMarker) { - int start = text.indexOf(startMarker); - if (start < 0) { - throw new IllegalArgumentException("block start not found: " + startMarker); - } - int end = text.indexOf(endMarker, start + startMarker.length()); - if (end < 0) { - throw new IllegalArgumentException("block end not found: " + endMarker); - } - return text.substring(start, end); - } - - private static int countOccurrences(String text, String needle) { - int count = 0; - int index = 0; - while ((index = text.indexOf(needle, index)) >= 0) { - count++; - index += needle.length(); - } - return count; - } - - private static Properties yamlProperties(Path path) { - YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new FileSystemResource(path)); - Properties properties = factory.getObject(); - assertThat(properties).isNotNull(); - return properties; - } - - private static Path repositoryRoot() { - Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath(); - while (current != null) { - if (Files.exists(current.resolve("deploy/portainer/docker-compose.yml"))) { - return current; - } - current = current.getParent(); - } - throw new IllegalStateException("repository root not found"); - } -} diff --git a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppCompositionTest.java b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppCompositionTest.java deleted file mode 100644 index a7140237..00000000 --- a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppCompositionTest.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.lingniu.ingest.historyapp; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import com.lingniu.ingest.eventhistory.EventHistoryEnvelopeIngestor; -import com.lingniu.ingest.eventhistory.Gb32960DecodedFrameService; -import com.lingniu.ingest.eventhistory.Gb32960FrameController; -import com.lingniu.ingest.eventhistory.config.EventHistoryAutoConfiguration; -import com.lingniu.ingest.eventhistory.Jt808LocationHistoryController; -import com.lingniu.ingest.eventhistory.LocationHistoryController; -import com.lingniu.ingest.eventhistory.RawFrameHistoryController; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeDeadLetterSink; -import com.lingniu.ingest.sink.kafka.KafkaEventSink; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeConsumerRunner; -import com.lingniu.ingest.sink.kafka.KafkaSinkProperties; -import com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration; -import com.lingniu.ingest.tdenginehistory.TdengineHistorySchema; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter; -import com.lingniu.ingest.tdenginehistory.config.TdengineHistoryAutoConfiguration; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; - -import java.lang.reflect.Method; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class VehicleHistoryAppCompositionTest { - - @TempDir - Path tempDir; - - @Test - void kafkaConsumerConfigurationDoesNotRedeclareServiceOwnedHistoryBeans() { - List beanMethods = Arrays.stream(VehicleHistoryKafkaConsumerConfiguration.class.getDeclaredMethods()) - .filter(method -> method.isAnnotationPresent(Bean.class)) - .map(Method::getName) - .toList(); - - assertThat(beanMethods) - .doesNotContain("telemetryEnvelopeRecordMapper") - .doesNotContain("eventHistoryEnvelopeIngestor") - .doesNotContain("eventHistoryEnvelopeConsumerProcessor") - .doesNotContain("eventHistoryRawEnvelopeConsumerProcessor"); - } - - @Test - void kafkaConsumerRunnerRejectsGenericHistoryBindings() { - new ApplicationContextRunner() - .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class) - .withBean("eventHistoryEnvelopeConsumerProcessor", EnvelopeConsumerProcessor.class, - () -> processor("event-history")) - .withBean("eventHistoryRawEnvelopeConsumerProcessor", EnvelopeConsumerProcessor.class, - () -> processor("event-history-raw")) - .withBean(KafkaSinkProperties.class, VehicleHistoryAppCompositionTest::genericOnlyHistoryConsumerProps) - .withPropertyValues("lingniu.ingest.sink.kafka.consumer.enabled=true") - .run(context -> { - assertThat(context).hasFailed(); - assertThat(context.getStartupFailure()) - .hasRootCauseMessage("no vehicle history kafka consumer workers created; check consumer bindings"); - }); - } - - @Test - void createsTdengineHistoryStorageAndQueryBeansWithoutGb32960TcpServerOrLocalArchiveSink() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - TdengineHistoryAutoConfiguration.class, - KafkaSinkAutoConfiguration.class, - EventHistoryAutoConfiguration.class)) - .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class) - .withAllowBeanDefinitionOverriding(true) - .withBean("kafkaProducer", KafkaProducer.class, VehicleHistoryAppCompositionTest::kafkaProducer) - .withPropertyValues( - "lingniu.ingest.tdengine-history.enabled=true", - "lingniu.ingest.tdengine-history.database=vehicle_history_test", - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.sink.kafka.enabled=true", - "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092", - "lingniu.ingest.sink.kafka.consumer.enabled=false", - "lingniu.ingest.vehicle-state.enabled=false", - "lingniu.ingest.vehicle-stat.enabled=false") - .run(context -> { - assertThat(context).doesNotHaveBean("archiveStore"); - assertThat(context).doesNotHaveBean("rawArchiveEventSink"); - assertThat(context).doesNotHaveBean("eventFileStore"); - assertThat(context).doesNotHaveBean("eventFileStoreSink"); - assertThat(context).hasSingleBean(TdengineHistorySchema.class); - assertThat(context).hasSingleBean(KafkaEventSink.class); - assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class); - - assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class); - assertThat(context).hasSingleBean(Gb32960MessageDecoder.class); - assertThat(context).hasSingleBean(Gb32960DecodedFrameService.class); - assertThat(context).hasSingleBean(LocationHistoryController.class); - assertThat(context).hasSingleBean(RawFrameHistoryController.class); - assertThat(context).doesNotHaveBean("telemetryFieldHistoryController"); - assertThat(context).doesNotHaveBean(Gb32960FrameController.class); - assertThat(context).doesNotHaveBean(Jt808LocationHistoryController.class); - assertThat(context).doesNotHaveBean(Gb32960NettyServer.class); - }); - } - - @Test - void createsHistoryIngestorForTdengineOnlyRuntime() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EventHistoryAutoConfiguration.class)) - .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class) - .withBean(TdengineHistoryWriter.class, () -> mock(TdengineHistoryWriter.class)) - .withBean(EnvelopeDeadLetterSink.class, () -> mock(EnvelopeDeadLetterSink.class)) - .withPropertyValues( - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.sink.kafka.consumer.enabled=false") - .run(context -> { - assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class); - assertThat(context).doesNotHaveBean("eventFileStore"); - }); - } - - @Test - void applicationScanDoesNotExposeTelemetryFieldControllerByDefault() { - new ApplicationContextRunner() - .withUserConfiguration(VehicleHistoryApplication.class) - .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class)) - .withPropertyValues( - "spring.cloud.nacos.config.enabled=false", - "spring.config.import=", - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.tdengine-history.enabled=false", - "lingniu.ingest.sink.kafka.enabled=false", - "lingniu.ingest.sink.kafka.consumer.enabled=false") - .run(context -> { - assertThat(context).hasSingleBean(LocationHistoryController.class); - assertThat(context).hasSingleBean(RawFrameHistoryController.class); - assertThat(context).doesNotHaveBean("telemetryFieldHistoryController"); - }); - } - - @Test - void createsKafkaConsumerRunnerWhenTdengineHistoryRuntimeConsumesKafka() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - TdengineHistoryAutoConfiguration.class, - KafkaSinkAutoConfiguration.class, - EventHistoryAutoConfiguration.class)) - .withUserConfiguration(VehicleHistoryKafkaConsumerConfiguration.class) - .withAllowBeanDefinitionOverriding(true) - .withBean("kafkaProducer", KafkaProducer.class, VehicleHistoryAppCompositionTest::kafkaProducer) - .withPropertyValues( - "lingniu.ingest.tdengine-history.enabled=true", - "lingniu.ingest.tdengine-history.database=vehicle_history_test", - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.sink.kafka.enabled=true", - "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092", - "lingniu.ingest.sink.kafka.consumer.enabled=true", - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.enabled=true", - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.group-id=history-gb32960", - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.topics[0]=vehicle.event.gb32960.v1") - .run(context -> { - assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class); - assertThat(context.getBeanNamesForType(EnvelopeConsumerProcessor.class)) - .contains("eventHistoryEnvelopeConsumerProcessor"); - assertThat(context).hasSingleBean(KafkaEnvelopeConsumerRunner.class); - }); - } - - @SuppressWarnings("unchecked") - private static KafkaProducer kafkaProducer() { - return mock(KafkaProducer.class); - } - - private static EnvelopeConsumerProcessor processor(String service) { - return new EnvelopeConsumerProcessor( - service, - ignored -> EnvelopeIngestResult.processed("event-1", "VIN001"), - ignored -> {}); - } - - private static KafkaSinkProperties genericOnlyHistoryConsumerProps() { - KafkaSinkProperties props = new KafkaSinkProperties(); - props.setBootstrapServers("localhost:9092"); - props.getConsumer().setEnabled(true); - props.getConsumer().setBindings(Map.of( - "eventHistoryEnvelopeConsumerProcessor", binding("history-event", "vehicle.event.generic.v1"), - "eventHistoryRawEnvelopeConsumerProcessor", binding("history-raw", "vehicle.raw.generic.v1"))); - return props; - } - - private static KafkaSinkProperties.Binding binding(String groupId, String topic) { - KafkaSinkProperties.Binding binding = new KafkaSinkProperties.Binding(); - binding.setGroupId(groupId); - binding.setTopics(List.of(topic)); - return binding; - } - -} diff --git a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java deleted file mode 100644 index 313bd745..00000000 --- a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/VehicleHistoryAppDefaultsTest.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.lingniu.ingest.historyapp; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.List; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleHistoryAppDefaultsTest { - - @Test - void applicationDefaultsKeepHistoryAsDecoderConsumerAndStorageRuntime() throws IOException { - Properties properties = applicationProperties(); - - assertThat(properties) - .containsEntry("spring.application.name", "vehicle-history-app") - .containsEntry( - "spring.config.import[0]", - "optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true}") - .containsEntry("spring.cloud.nacos.config.server-addr", "${NACOS_SERVER_ADDR:127.0.0.1:8848}") - .containsEntry("lingniu.ingest.tdengine-history.enabled", "${TDENGINE_HISTORY_ENABLED:false}") - .containsEntry("lingniu.ingest.tdengine-history.database", "${TDENGINE_HISTORY_DATABASE:vehicle_history}") - .containsEntry( - "lingniu.ingest.tdengine-history.jdbc-url", - "${TDENGINE_JDBC_URL:jdbc:TAOS-RS://${TDENGINE_HOST:127.0.0.1}:${TDENGINE_PORT:6041}/${TDENGINE_HISTORY_DATABASE:vehicle_history}}") - .containsEntry("lingniu.ingest.tdengine-history.username", "${TDENGINE_USERNAME:root}") - .containsEntry("lingniu.ingest.tdengine-history.password", "${TDENGINE_PASSWORD:taosdata}") - .containsEntry( - "lingniu.ingest.tdengine-history.driver-class-name", - "${TDENGINE_DRIVER_CLASS_NAME:com.taosdata.jdbc.rs.RestfulDriver}") - .containsEntry("lingniu.ingest.tdengine-history.maximum-pool-size", "${TDENGINE_MAX_POOL_SIZE:16}") - .containsEntry("lingniu.ingest.tdengine-history.minimum-idle", "${TDENGINE_MIN_IDLE:0}") - .containsEntry("lingniu.ingest.event-history.enabled", true) - .containsEntry("lingniu.ingest.sink.kafka.enabled", true) - .containsEntry("lingniu.ingest.sink.kafka.consumer.enabled", "${KAFKA_CONSUMER_ENABLED:true}") - .containsEntry("lingniu.ingest.sink.kafka.consumer.dlq-topic", - "${KAFKA_TOPIC_HISTORY_DLQ:vehicle.dlq.history.v1}") - .containsEntry("lingniu.ingest.sink.kafka.consumer.max-poll-records", "${KAFKA_CONSUMER_MAX_POLL_RECORDS:2000}") - .containsEntry("lingniu.ingest.sink.kafka.consumer.concurrency", "${KAFKA_CONSUMER_CONCURRENCY:3}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.max-poll-interval-millis", - "${KAFKA_CONSUMER_MAX_POLL_INTERVAL_MILLIS:1800000}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.enabled", - true) - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.group-id", - "${KAFKA_GROUP_HISTORY_GB32960_EVENT:${KAFKA_GROUP_HISTORY:vehicle-history}-gb32960-event}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960EnvelopeConsumerProcessor.topics[0]", - "${KAFKA_TOPIC_GB32960_EVENT:vehicle.event.gb32960.v1}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryJt808EnvelopeConsumerProcessor.enabled", - true) - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryJt808EnvelopeConsumerProcessor.group-id", - "${KAFKA_GROUP_HISTORY_JT808_EVENT:${KAFKA_GROUP_HISTORY:vehicle-history}-jt808-event}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryJt808EnvelopeConsumerProcessor.topics[0]", - "${KAFKA_TOPIC_JT808_EVENT:vehicle.event.jt808.v1}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryYutongMqttEnvelopeConsumerProcessor.enabled", - true) - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryYutongMqttEnvelopeConsumerProcessor.group-id", - "${KAFKA_GROUP_HISTORY_YUTONG_MQTT_EVENT:${KAFKA_GROUP_HISTORY:vehicle-history}-yutong-mqtt-event}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryYutongMqttEnvelopeConsumerProcessor.topics[0]", - "${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960RawEnvelopeConsumerProcessor.enabled", - true) - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960RawEnvelopeConsumerProcessor.group-id", - "${KAFKA_GROUP_HISTORY_GB32960_RAW:${KAFKA_GROUP_HISTORY:vehicle-history}-gb32960-raw}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryGb32960RawEnvelopeConsumerProcessor.topics[0]", - "${KAFKA_TOPIC_GB32960_RAW:vehicle.raw.gb32960.v1}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryJt808RawEnvelopeConsumerProcessor.enabled", - true) - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryJt808RawEnvelopeConsumerProcessor.group-id", - "${KAFKA_GROUP_HISTORY_JT808_RAW:${KAFKA_GROUP_HISTORY:vehicle-history}-jt808-raw}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryJt808RawEnvelopeConsumerProcessor.topics[0]", - "${KAFKA_TOPIC_JT808_RAW:vehicle.raw.jt808.v1}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryYutongMqttRawEnvelopeConsumerProcessor.enabled", - true) - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryYutongMqttRawEnvelopeConsumerProcessor.group-id", - "${KAFKA_GROUP_HISTORY_YUTONG_MQTT_RAW:${KAFKA_GROUP_HISTORY:vehicle-history}-yutong-mqtt-raw}") - .containsEntry( - "lingniu.ingest.sink.kafka.consumer.bindings.eventHistoryYutongMqttRawEnvelopeConsumerProcessor.topics[0]", - "${KAFKA_TOPIC_YUTONG_MQTT_RAW:vehicle.raw.mqtt-yutong.v1}") - .containsEntry("management.endpoints.web.exposure.include", "health,info,metrics,prometheus"); - - assertThat(properties.stringPropertyNames()) - .noneMatch(name -> name.startsWith("lingniu.ingest.gb32960.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-state.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-stat.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.sink.archive.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.sink.kafka.topics.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-file-store.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-history.api.")); - assertThat(properties.stringPropertyNames()) - .noneMatch(name -> name.equals("lingniu.ingest.tdengine-history.telemetry-fields-enabled")); - assertThat(applicationYaml()) - .doesNotContain("\n archive:\n") - .doesNotContain("sink.archive") - .doesNotContain("raw-archive: ${KAFKA_TOPIC_GB32960_RAW") - .doesNotContain("EVENT_HISTORY_SPECIALIZED_API_ENABLED") - .doesNotContain("KAFKA_ENABLED"); - } - - @Test - void runtimeClasspathDoesNotContainLegacyCommonsLoggingJar() throws Exception { - List logFactoryResources = Collections.list(Thread.currentThread() - .getContextClassLoader() - .getResources("org/apache/commons/logging/LogFactory.class")) - .stream() - .map(URL::toExternalForm) - .toList(); - - assertThat(logFactoryResources) - .noneMatch(resource -> resource.contains("/commons-logging-")); - } - - private static Properties applicationProperties() { - YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ClassPathResource("application.yml")); - - Properties properties = factory.getObject(); - - assertThat(properties).isNotNull(); - return properties; - } - - private static String applicationYaml() throws IOException { - return new String(new ClassPathResource("application.yml").getInputStream().readAllBytes(), - StandardCharsets.UTF_8); - } -} diff --git a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/WoodpeckerPipelineTest.java b/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/WoodpeckerPipelineTest.java deleted file mode 100644 index 7da865b4..00000000 --- a/modules/apps/vehicle-history-app/src/test/java/com/lingniu/ingest/historyapp/WoodpeckerPipelineTest.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.lingniu.ingest.historyapp; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class WoodpeckerPipelineTest { - - @Test - void dockerPublishUsesOneProductionAppList() throws IOException { - String pipeline = Files.readString(repositoryRoot().resolve("woodpecker.yml")); - - assertThat(pipeline) - .contains("ACTIVE_APPS: &active_apps \"gb32960-ingest-app jt808-ingest-app yutong-mqtt-app vehicle-history-app vehicle-analytics-app\"") - .contains("ACTIVE_APPS: *active_apps") - .contains("- name: docker-build-apps") - .doesNotContain("xinda-push-app") - .doesNotContain("KAFKA_TOPIC_XINDA_PUSH"); - - assertThat(countOccurrences(pipeline, "- name: docker-build-")).isEqualTo(1); - } - - @Test - void activeAppListIsDefinedOnceAndReusedAcrossPipelineSteps() throws IOException { - String pipeline = Files.readString(repositoryRoot().resolve("woodpecker.yml")); - String activeApps = "gb32960-ingest-app jt808-ingest-app yutong-mqtt-app vehicle-history-app vehicle-analytics-app"; - - assertThat(countOccurrences(pipeline, activeApps)).isEqualTo(1); - assertThat(pipeline) - .contains("ACTIVE_APPS: &active_apps \"" + activeApps + "\"") - .contains("ACTIVE_APPS: *active_apps") - .doesNotContain("ACTIVE_APPS=\"" + activeApps + "\""); - } - - @Test - void mavenBuildSkipsTestExecutionAndCompilationInCi() throws IOException { - String pipeline = Files.readString(repositoryRoot().resolve("woodpecker.yml")); - - assertThat(pipeline) - .contains("mvn -B -ntp -am -pl \"$ACTIVE_MODULES\" package -Dmaven.test.skip=true") - .doesNotContain("-DskipTests"); - } - - @Test - void dockerPublishUsesOverridableImagePrefix() throws IOException { - String pipeline = Files.readString(repositoryRoot().resolve("woodpecker.yml")); - - assertThat(pipeline) - .contains("REGISTRY=${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com}") - .contains("NAMESPACE=${LINGNIU_IMAGE_NAMESPACE:-oneos}") - .contains("IMAGE=$REGISTRY/$NAMESPACE/$APP_NAME:$APP_VERSION") - .doesNotContain("\n REGISTRY=crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com") - .doesNotContain("\n NAMESPACE=oneos"); - } - - @Test - void dockerImageExposesOnlyCurrentProductionPorts() throws IOException { - String dockerfile = Files.readString(repositoryRoot().resolve("Dockerfile")); - - assertThat(dockerfile) - .contains("EXPOSE 20100 20200 20310 20400 20500 32960 808") - .doesNotContain("20300") - .doesNotContain("10808") - .doesNotContain("8089"); - } - - private static int countOccurrences(String value, String needle) { - int count = 0; - int from = 0; - while (true) { - int next = value.indexOf(needle, from); - if (next < 0) { - return count; - } - count++; - from = next + needle.length(); - } - } - - private static Path repositoryRoot() { - Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath(); - while (current != null) { - if (Files.exists(current.resolve("woodpecker.yml"))) { - return current; - } - current = current.getParent(); - } - throw new IllegalStateException("repository root not found"); - } -} diff --git a/modules/apps/yutong-mqtt-app/pom.xml b/modules/apps/yutong-mqtt-app/pom.xml deleted file mode 100644 index e2896cc3..00000000 --- a/modules/apps/yutong-mqtt-app/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - - yutong-mqtt-app - yutong-mqtt-app - Yutong MQTT ingress runtime: MQTT subscribe, JSON parse, identity mapping, Kafka production, and raw archive. - - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - observability - - - com.lingniu.ingest - vehicle-identity - - - com.lingniu.ingest - inbound-mqtt - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - sink-archive - - - org.springframework.boot - spring-boot-starter-web - - - com.alibaba.cloud - spring-cloud-starter-alibaba-nacos-config - - - org.springframework.boot - spring-boot-starter-test - test - - - - - yutong-mqtt-app - - - org.springframework.boot - spring-boot-maven-plugin - - - - diff --git a/modules/apps/yutong-mqtt-app/src/main/java/com/lingniu/ingest/yutongmqttapp/YutongMqttApplication.java b/modules/apps/yutong-mqtt-app/src/main/java/com/lingniu/ingest/yutongmqttapp/YutongMqttApplication.java deleted file mode 100644 index 274fa344..00000000 --- a/modules/apps/yutong-mqtt-app/src/main/java/com/lingniu/ingest/yutongmqttapp/YutongMqttApplication.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.yutongmqttapp; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class YutongMqttApplication { - - private static final Logger log = LoggerFactory.getLogger(YutongMqttApplication.class); - - public static void main(String[] args) { - try { - SpringApplication.run(YutongMqttApplication.class, args); - } catch (Throwable t) { - log.error("Yutong MQTT ingest application failed to start, forcing JVM exit", t); - System.exit(1); - } - } -} diff --git a/modules/apps/yutong-mqtt-app/src/main/resources/application.yml b/modules/apps/yutong-mqtt-app/src/main/resources/application.yml deleted file mode 100644 index 172326e0..00000000 --- a/modules/apps/yutong-mqtt-app/src/main/resources/application.yml +++ /dev/null @@ -1,108 +0,0 @@ -spring: - application: - name: yutong-mqtt-app - config: - import: - - optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true} - cloud: - nacos: - config: - enabled: ${NACOS_CONFIG_ENABLED:true} - server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848} - namespace: ${NACOS_NAMESPACE:} - group: ${NACOS_GROUP:DEFAULT_GROUP} - file-extension: ${NACOS_CONFIG_FILE_EXTENSION:yml} - username: ${NACOS_USERNAME:} - password: ${NACOS_PASSWORD:} - threads: - virtual: - enabled: true - -server: - port: ${HTTP_PORT:20500} - -lingniu: - ingest: - mqtt: - enabled: ${YUTONG_MQTT_ENABLED:false} - auto-startup: ${YUTONG_MQTT_AUTO_STARTUP:true} - endpoints: - - name: ${YUTONG_MQTT_ENDPOINT_NAME:yutong} - uri: ${YUTONG_MQTT_URI:} - topic: ${YUTONG_MQTT_TOPIC:#} - qos: ${YUTONG_MQTT_QOS:1} - client-id: ${YUTONG_MQTT_CLIENT_ID:lingniu-yutong-mqtt} - username: ${YUTONG_MQTT_USERNAME:} - password: ${YUTONG_MQTT_PASSWORD:} - clean-session: ${YUTONG_MQTT_CLEAN_SESSION:false} - keep-alive-seconds: ${YUTONG_MQTT_KEEP_ALIVE_SECONDS:20} - connection-timeout-seconds: ${YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS:10} - profile: yutong - tls: - ca-pem: ${YUTONG_MQTT_TLS_CA_PEM:} - client-pem: ${YUTONG_MQTT_TLS_CLIENT_PEM:} - client-key: ${YUTONG_MQTT_TLS_CLIENT_KEY:} - hostname-verification-enabled: ${YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED:true} - pipeline: - disruptor: - ring-buffer-size: ${PIPELINE_RING_BUFFER_SIZE:131072} - wait-strategy: ${PIPELINE_WAIT_STRATEGY:yielding} - producer-type: multi - dedup: - enabled: true - cache-size: 200000 - ttl-seconds: 600 - rate-limit: - per-vin-qps: 50 - identity: - mysql: - jdbc-url: ${VEHICLE_IDENTITY_MYSQL_JDBC_URL:jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai} - username: ${VEHICLE_IDENTITY_MYSQL_USERNAME:root} - password: ${VEHICLE_IDENTITY_MYSQL_PASSWORD:} - table-name: ${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings} - initialize-schema: ${VEHICLE_IDENTITY_MYSQL_INITIALIZE_SCHEMA:true} - sink: - kafka: - enabled: true - bootstrap-servers: ${KAFKA_BROKERS:114.55.58.251:9092} - compression-type: zstd - linger-ms: 20 - batch-size: 65536 - acks: all - enable-idempotence: true - node-id: ${KAFKA_NODE_ID:yutong-mqtt-local} - topics: - realtime: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1} - location: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1} - alarm: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1} - session: ${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1} - media-meta: ${KAFKA_TOPIC_MEDIA_META:vehicle.media.meta.v1} - raw-archive: ${KAFKA_TOPIC_YUTONG_MQTT_RAW:vehicle.raw.mqtt-yutong.v1} - dlq: ${KAFKA_TOPIC_YUTONG_MQTT_DLQ:vehicle.dlq.mqtt-yutong.v1} - consumer: - enabled: false - archive: - enabled: ${SINK_ARCHIVE_ENABLED:true} - type: local - path: ${SINK_ARCHIVE_PATH:./archive/} - -management: - endpoints: - web: - exposure: - include: health,info,metrics,prometheus - endpoint: - health: - probes: - enabled: ${MANAGEMENT_HEALTH_PROBES_ENABLED:true} - health: - redis: - enabled: ${MANAGEMENT_HEALTH_REDIS_ENABLED:false} - metrics: - tags: - application: yutong-mqtt-app - -logging: - level: - root: INFO - com.lingniu.ingest: INFO diff --git a/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppCompositionTest.java b/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppCompositionTest.java deleted file mode 100644 index c6da8a66..00000000 --- a/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppCompositionTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.lingniu.ingest.yutongmqttapp; - -import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration; -import com.lingniu.ingest.identity.MySqlVehicleIdentityService; -import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration; -import com.lingniu.ingest.inbound.mqtt.client.MqttEndpointManager; -import com.lingniu.ingest.inbound.mqtt.config.MqttInboundAutoConfiguration; -import com.lingniu.ingest.inbound.mqtt.handler.MqttRealtimeHandler; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.RawArchiveEventSink; -import com.lingniu.ingest.sink.archive.config.SinkArchiveAutoConfiguration; -import com.lingniu.ingest.sink.kafka.KafkaEnvelopeDeadLetterSink; -import com.lingniu.ingest.sink.kafka.KafkaEventSink; -import com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -@ExtendWith(OutputCaptureExtension.class) -class YutongMqttAppCompositionTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - IngestCoreAutoConfiguration.class, - VehicleIdentityAutoConfiguration.class, - KafkaSinkAutoConfiguration.class, - SinkArchiveAutoConfiguration.class, - MqttInboundAutoConfiguration.class)) - .withAllowBeanDefinitionOverriding(true) - .withBean("kafkaProducer", KafkaProducer.class, YutongMqttAppCompositionTest::kafkaProducer) - .withPropertyValues( - "lingniu.ingest.mqtt.enabled=true", - "lingniu.ingest.mqtt.auto-startup=false", - "lingniu.ingest.mqtt.endpoints[0].name=yutong-test", - "lingniu.ingest.mqtt.endpoints[0].uri=tcp://127.0.0.1:1883", - "lingniu.ingest.mqtt.endpoints[0].topic=/yutong/#", - "lingniu.ingest.mqtt.endpoints[0].profile=yutong", - "lingniu.ingest.identity.mysql.initialize-schema=false", - "lingniu.ingest.sink.kafka.enabled=true", - "lingniu.ingest.sink.kafka.bootstrap-servers=localhost:9092", - "lingniu.ingest.sink.kafka.consumer.enabled=false", - "lingniu.ingest.sink.archive.enabled=true", - "lingniu.ingest.sink.archive.path=target/test-archive-yutong", - "lingniu.ingest.event-history.enabled=false"); - - @Test - void createsMqttIngressAndKafkaSinksWithoutHistoryStorage(CapturedOutput output) { - contextRunner.run(context -> { - assertThat(context).hasSingleBean(MqttEndpointManager.class); - assertThat(context).hasSingleBean(MqttProfileRegistry.class); - assertThat(context).hasSingleBean(MqttRealtimeHandler.class); - assertThat(context).hasSingleBean(MySqlVehicleIdentityService.class); - assertThat(context).hasSingleBean(KafkaEventSink.class); - assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class); - assertThat(context).hasSingleBean(ArchiveStore.class); - assertThat(context).hasSingleBean(RawArchiveEventSink.class); - assertThat(context).doesNotHaveBean("mqttEndpointStartupRunner"); - }); - assertThat(output).doesNotContain("mqtt endpoint [yutong-test] initializing"); - assertThat(output).doesNotContain("mqtt endpoint [yutong-test] init failed"); - } - - @Test - void createsStartupRunnerWhenAutoStartupIsEnabled() { - contextRunner.withPropertyValues("lingniu.ingest.mqtt.auto-startup=true").run(context -> { - assertThat(context).hasSingleBean(MqttEndpointManager.class); - assertThat(context).hasBean("mqttEndpointStartupRunner"); - }); - } - - @SuppressWarnings("unchecked") - private static KafkaProducer kafkaProducer() { - return mock(KafkaProducer.class); - } -} diff --git a/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppDefaultsTest.java b/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppDefaultsTest.java deleted file mode 100644 index 08d02d65..00000000 --- a/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttAppDefaultsTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.lingniu.ingest.yutongmqttapp; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; -import org.springframework.core.io.ClassPathResource; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -class YutongMqttAppDefaultsTest { - - @Test - void applicationDefaultsKeepYutongMqttAsKafkaProducerOnly() throws IOException { - Properties properties = applicationProperties(); - - assertThat(properties) - .containsEntry("spring.application.name", "yutong-mqtt-app") - .containsEntry( - "spring.config.import[0]", - "optional:nacos:${spring.application.name}.${NACOS_CONFIG_FILE_EXTENSION:yml}?group=${NACOS_GROUP:DEFAULT_GROUP}&refreshEnabled=${NACOS_REFRESH_ENABLED:true}") - .containsEntry("spring.cloud.nacos.config.server-addr", "${NACOS_SERVER_ADDR:127.0.0.1:8848}") - .containsEntry("server.port", "${HTTP_PORT:20500}") - .containsEntry("lingniu.ingest.mqtt.enabled", "${YUTONG_MQTT_ENABLED:false}") - .containsEntry("lingniu.ingest.mqtt.auto-startup", "${YUTONG_MQTT_AUTO_STARTUP:true}") - .containsEntry("lingniu.ingest.mqtt.endpoints[0].uri", "${YUTONG_MQTT_URI:}") - .containsEntry("lingniu.ingest.mqtt.endpoints[0].topic", "${YUTONG_MQTT_TOPIC:#}") - .containsEntry("lingniu.ingest.mqtt.endpoints[0].profile", "yutong") - .containsEntry("lingniu.ingest.sink.kafka.enabled", true) - .containsEntry("lingniu.ingest.sink.kafka.consumer.enabled", false) - .containsEntry("lingniu.ingest.sink.kafka.topics.realtime", "${KAFKA_TOPIC_YUTONG_MQTT_EVENT:vehicle.event.mqtt-yutong.v1}") - .containsEntry("lingniu.ingest.sink.kafka.topics.raw-archive", "${KAFKA_TOPIC_YUTONG_MQTT_RAW:vehicle.raw.mqtt-yutong.v1}") - .containsEntry("lingniu.ingest.sink.kafka.topics.dlq", "${KAFKA_TOPIC_YUTONG_MQTT_DLQ:vehicle.dlq.mqtt-yutong.v1}") - .containsEntry("lingniu.ingest.identity.mysql.table-name", - "${VEHICLE_IDENTITY_MYSQL_TABLE:vehicle_identity_bindings}") - .containsEntry("lingniu.ingest.sink.archive.enabled", "${SINK_ARCHIVE_ENABLED:true}") - .containsEntry("management.endpoints.web.exposure.include", "health,info,metrics,prometheus"); - - assertThat(properties.stringPropertyNames()) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-history.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.event-file-store.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-state.")) - .noneMatch(name -> name.startsWith("lingniu.ingest.vehicle-stat.")) - .noneMatch(name -> name.equals("lingniu.ingest.identity.store")); - assertThat(applicationYaml()) - .doesNotContain("event-history:") - .doesNotContain("event-file-store:") - .doesNotContain("vehicle-state:") - .doesNotContain("vehicle-stat:") - .doesNotContain("springdoc:") - .doesNotContain("VEHICLE_IDENTITY_STORE") - .doesNotContain("KAFKA_ENABLED"); - } - - private static Properties applicationProperties() { - YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean(); - factory.setResources(new ClassPathResource("application.yml")); - - Properties properties = factory.getObject(); - - assertThat(properties).isNotNull(); - return properties; - } - - private static String applicationYaml() throws IOException { - return new String(new ClassPathResource("application.yml").getInputStream().readAllBytes(), - StandardCharsets.UTF_8); - } -} diff --git a/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttPortainerComposeTest.java b/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttPortainerComposeTest.java deleted file mode 100644 index a67c2d66..00000000 --- a/modules/apps/yutong-mqtt-app/src/test/java/com/lingniu/ingest/yutongmqttapp/YutongMqttPortainerComposeTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.lingniu.ingest.yutongmqttapp; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class YutongMqttPortainerComposeTest { - - @Test - void portainerComposeUsesYutongMqttApplicationEnvironmentNames() throws IOException { - String compose = Files.readString(repositoryRoot().resolve("deploy/portainer/docker-compose.yml")); - - assertThat(compose) - .contains("KAFKA_TOPIC_YUTONG_MQTT_EVENT:") - .contains("KAFKA_TOPIC_YUTONG_MQTT_RAW:") - .contains("KAFKA_TOPIC_YUTONG_MQTT_DLQ:") - .contains("YUTONG_MQTT_ENABLED: ${YUTONG_MQTT_ENABLED:-false}") - .contains("YUTONG_MQTT_AUTO_STARTUP: ${YUTONG_MQTT_AUTO_STARTUP:-true}") - .contains("YUTONG_MQTT_URI: ${YUTONG_MQTT_URI:-}") - .contains("YUTONG_MQTT_TOPIC: \"${YUTONG_MQTT_TOPIC:-#}\"") - .contains("YUTONG_MQTT_KEEP_ALIVE_SECONDS: ${YUTONG_MQTT_KEEP_ALIVE_SECONDS:-20}") - .contains("YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS: ${YUTONG_MQTT_CONNECTION_TIMEOUT_SECONDS:-10}") - .contains("YUTONG_MQTT_TLS_CA_PEM: ${YUTONG_MQTT_TLS_CA_PEM:-}") - .contains("YUTONG_MQTT_TLS_CLIENT_PEM: ${YUTONG_MQTT_TLS_CLIENT_PEM:-}") - .contains("YUTONG_MQTT_TLS_CLIENT_KEY: ${YUTONG_MQTT_TLS_CLIENT_KEY:-}") - .contains("YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED: ${YUTONG_MQTT_TLS_HOSTNAME_VERIFICATION_ENABLED:-true}") - .doesNotContain("YUTONG_MQTT_URI: ${YUTONG_MQTT_URI:-tcp://127.0.0.1:1883}") - .doesNotContain("KAFKA_TOPIC_PARTNER_EVENT") - .doesNotContain("KAFKA_TOPIC_PARTNER_RAW") - .doesNotContain("KAFKA_TOPIC_PARTNER_DLQ") - .doesNotContain("\n MQTT_ENABLED:") - .doesNotContain("\n MQTT_URI:") - .doesNotContain("\n MQTT_TOPIC:"); - } - - private static Path repositoryRoot() { - Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath(); - while (current != null) { - if (Files.exists(current.resolve("deploy/portainer/docker-compose.yml"))) { - return current; - } - current = current.getParent(); - } - throw new IllegalStateException("repository root not found"); - } -} diff --git a/modules/core/ingest-api/pom.xml b/modules/core/ingest-api/pom.xml deleted file mode 100644 index 88cf89fb..00000000 --- a/modules/core/ingest-api/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - ingest-api - ingest-api - SPI、sealed 领域事件、注解定义。零 Spring 依赖。 - - - - org.slf4j - slf4j-api - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/ProtocolId.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/ProtocolId.java deleted file mode 100644 index 8814ac33..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/ProtocolId.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.lingniu.ingest.api; - -/** - * 协议标识,作为所有 SPI / 注解 / 路由 key 的统一枚举。 - * 新增协议需要在此处登记,避免散落的字符串常量。 - */ -public enum ProtocolId { - UNKNOWN, - GB32960, - JT808, - JT1078, - JSATL12, - MQTT_YUTONG -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/AsyncBatch.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/AsyncBatch.java deleted file mode 100644 index 6cea65df..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/AsyncBatch.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.lingniu.ingest.api.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 批量异步处理:框架把多次同类调用聚合成 {@code List} 交给方法。 - * - *

处理方法签名需接收 {@code List},返回 {@code List} 或单个 - * {@code VehicleEvent}。Dispatcher 不直接调用目标方法,而是交给 - * {@code AsyncBatchExecutor} 按 size/waitMs 聚合后调用。 - * - *

注意:当调用方需要给每条事件补不同的 rawArchiveUri 等逐条元数据时,执行器会按单条 flush, - * 以保证事件与原始帧一一对应。 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface AsyncBatch { - - /** 达到该批量大小后立即 flush。 */ - int size() default 1000; - - /** 第一条消息入队后最多等待毫秒数,避免低流量车辆长期不出批。 */ - long waitMs() default 500; - - /** 每个 Handler 方法对应的批处理 worker 数。 */ - int poolSize() default 2; -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/EventEmit.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/EventEmit.java deleted file mode 100644 index 48c81478..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/EventEmit.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.lingniu.ingest.api.annotation; - -import com.lingniu.ingest.api.event.VehicleEvent; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 声明当前 Handler 方法产出的事件类型。用于文档化和静态校验。 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface EventEmit { - Class[] value(); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/IdempotentKey.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/IdempotentKey.java deleted file mode 100644 index 24badc4d..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/IdempotentKey.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.lingniu.ingest.api.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 声明幂等键,由 Dedup 拦截器使用。支持 SpEL 表达式,上下文根对象为 Handler 参数。 - * - *

示例:{@code @IdempotentKey("#msg.vin + ':' + #msg.seq + ':' + #msg.eventTime")} - * - *

当前内置去重主要基于 RawFrame sourceMeta/raw bytes;该注解保留给更细粒度 Handler 级去重。 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface IdempotentKey { - String value(); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/MessageMapping.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/MessageMapping.java deleted file mode 100644 index aa53a873..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/MessageMapping.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.lingniu.ingest.api.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 方法级路由注解:声明此方法处理哪些协议消息。 - * - *

{@link #command} 和 {@link #infoType} 的含义由各协议自行解释,Dispatcher 只负责匹配: - *

    - *
  • GB/T 32960:{@code command} = 0x01 车辆登入 / 0x02 实时上报 / ...;{@code infoType} = 信息体 ID - *
  • JT/T 808:{@code command} = 消息 ID(0x0100 / 0x0200 / ...);{@code infoType} 留空 - *
  • MQTT:{@code command} 可为 topic hash 或忽略 - *
- * - *

同一个方法可以声明多个 command/infoType。Dispatcher 会按协议、command、infoType 精确路由; - * 32960 实时上报解析后通常由 0x02/0x03 主命令加信息体 ID 决定落到哪个 Handler。 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface MessageMapping { - - /** 协议主命令或消息 ID,空数组表示该维度不限制。 */ - int[] command() default {}; - - /** 协议子类型,GB32960 中对应信息体 ID;空数组表示该维度不限制。 */ - int[] infoType() default {}; - - /** 仅用于日志/Swagger/排障展示,不参与路由。 */ - String desc() default ""; -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/ProtocolHandler.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/ProtocolHandler.java deleted file mode 100644 index 6e308444..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/ProtocolHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.lingniu.ingest.api.annotation; - -import com.lingniu.ingest.api.ProtocolId; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 标记一个类为协议 Handler Bean,由 {@code AnnotationHandlerProcessor} 自动扫描注册。 - * - *

典型用法: - *

{@code
- * @ProtocolHandler(protocol = ProtocolId.GB32960)
- * public class Gb32960RealtimeHandler {
- *     @MessageMapping(command = 0x02)
- *     public VehicleEvent.Realtime handle(Gb32960Message msg) { ... }
- * }
- * }
- */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.TYPE) -public @interface ProtocolHandler { - - ProtocolId protocol(); - - /** 可选:子协议版本(如 32960 的 2011 / 2017)。 */ - String version() default ""; -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/RateLimited.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/RateLimited.java deleted file mode 100644 index 77855f91..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/RateLimited.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lingniu.ingest.api.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 单 VIN 速率限制声明。 - * - *

当前内置限流器在 Pipeline before 阶段按 VIN 中止处理,超限帧不会进入 Handler 和下游存储。 - * 如果需要把超限帧写入 DLQ,应在拦截器或入口层显式增加对应 sink。 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface RateLimited { - - /** 单 VIN 每秒最大消息数。 */ - int perVin() default 50; - - /** 全局 QPS 上限;<=0 表示不设限。 */ - int global() default -1; -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/TraceSpan.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/TraceSpan.java deleted file mode 100644 index 1dbbb521..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/annotation/TraceSpan.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.lingniu.ingest.api.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * 为 Handler 方法标记轻量 traceId 诊断名称。 - */ -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.METHOD) -public @interface TraceSpan { - String value(); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeBatchIngestor.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeBatchIngestor.java deleted file mode 100644 index 03aaafe7..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeBatchIngestor.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -import java.util.List; - -/** - * Optional extension for consumers that can persist a Kafka poll batch in one - * storage operation. - */ -public interface EnvelopeBatchIngestor extends EnvelopeIngestor { - - List tryIngestAll(List kafkaValues); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessor.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessor.java deleted file mode 100644 index f5b51abb..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessor.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -public final class EnvelopeConsumerProcessor { - - private static final Set DEAD_LETTER_STATUSES = EnumSet.of( - EnvelopeIngestResult.Status.SKIPPED, - EnvelopeIngestResult.Status.INVALID_ENVELOPE, - EnvelopeIngestResult.Status.FAILED - ); - - private final String service; - private final EnvelopeIngestor ingestor; - private final EnvelopeDeadLetterSink deadLetterSink; - - public EnvelopeConsumerProcessor(String service, EnvelopeIngestor ingestor, EnvelopeDeadLetterSink deadLetterSink) { - if (ingestor == null) { - throw new IllegalArgumentException("ingestor must not be null"); - } - if (deadLetterSink == null) { - throw new IllegalArgumentException("deadLetterSink must not be null"); - } - this.service = service == null || service.isBlank() ? "unknown" : service; - this.ingestor = ingestor; - this.deadLetterSink = deadLetterSink; - } - - public EnvelopeIngestResult process(EnvelopeConsumerRecord record) { - if (record == null) { - throw new IllegalArgumentException("record must not be null"); - } - EnvelopeIngestResult result = processAll(List.of(record)).getFirst(); - return result; - } - - public List processAll(List records) { - if (records == null) { - throw new IllegalArgumentException("records must not be null"); - } - if (records.isEmpty()) { - return List.of(); - } - List payloads = new ArrayList<>(records.size()); - for (EnvelopeConsumerRecord record : records) { - if (record == null) { - throw new IllegalArgumentException("record must not be null"); - } - payloads.add(record.payload()); - } - List results = ingestor.tryIngestAll(payloads); - if (results.size() != records.size()) { - throw new IllegalStateException("ingestor result count does not match record count"); - } - for (int i = 0; i < records.size(); i++) { - publishDeadLetterIfNeeded(records.get(i), results.get(i)); - } - return results; - } - - private void publishDeadLetterIfNeeded(EnvelopeConsumerRecord record, EnvelopeIngestResult result) { - // 派生消费者不在这里抛出业务异常给 Kafka worker;坏消息统一进入 DLQ, - // worker 看到 process 正常返回后才能提交 offset,避免同一坏消息无限阻塞消费组。 - if (DEAD_LETTER_STATUSES.contains(result.status())) { - deadLetterSink.publish(toDeadLetter(record, result)); - } - } - - public List processBatch(List records) { - if (records == null || records.isEmpty()) { - return List.of(); - } - if (!(ingestor instanceof EnvelopeBatchIngestor batchIngestor)) { - return records.stream().map(this::process).toList(); - } - List payloads = records.stream() - .map(EnvelopeConsumerRecord::payload) - .toList(); - List results = batchIngestor.tryIngestAll(payloads); - if (results.size() != records.size()) { - throw new IllegalStateException("batch ingestor returned " + results.size() - + " results for " + records.size() + " records"); - } - List copy = new ArrayList<>(results.size()); - for (int i = 0; i < records.size(); i++) { - EnvelopeIngestResult result = results.get(i); - if (DEAD_LETTER_STATUSES.contains(result.status())) { - deadLetterSink.publish(toDeadLetter(records.get(i), result)); - } - copy.add(result); - } - return List.copyOf(copy); - } - - private EnvelopeDeadLetterRecord toDeadLetter(EnvelopeConsumerRecord record, EnvelopeIngestResult result) { - return new EnvelopeDeadLetterRecord( - service, - record.topic(), - record.partition(), - record.offset(), - record.key(), - result.status(), - result.eventId(), - result.vin(), - result.message(), - record.payload(), - Instant.now()); - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerRecord.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerRecord.java deleted file mode 100644 index 40bb948e..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerRecord.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -public record EnvelopeConsumerRecord( - String topic, - int partition, - long offset, - String key, - byte[] payload -) { - public EnvelopeConsumerRecord { - topic = topic == null ? "" : topic; - key = key == null ? "" : key; - payload = payload == null ? new byte[0] : payload.clone(); - } - - @Override - public byte[] payload() { - return payload.clone(); - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterRecord.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterRecord.java deleted file mode 100644 index 45cb7901..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterRecord.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -import java.time.Instant; - -public record EnvelopeDeadLetterRecord( - String service, - String topic, - int partition, - long offset, - String key, - EnvelopeIngestResult.Status status, - String eventId, - String vin, - String message, - byte[] payload, - Instant createdAt -) { - public EnvelopeDeadLetterRecord { - service = service == null ? "" : service; - topic = topic == null ? "" : topic; - key = key == null ? "" : key; - if (status == null) { - throw new IllegalArgumentException("status must not be null"); - } - eventId = eventId == null ? "" : eventId; - vin = vin == null ? "" : vin; - message = message == null ? "" : message; - payload = payload == null ? new byte[0] : payload.clone(); - createdAt = createdAt == null ? Instant.now() : createdAt; - } - - @Override - public byte[] payload() { - return payload.clone(); - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterSink.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterSink.java deleted file mode 100644 index 5de699af..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeDeadLetterSink.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -@FunctionalInterface -public interface EnvelopeDeadLetterSink { - void publish(EnvelopeDeadLetterRecord record); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestResult.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestResult.java deleted file mode 100644 index c1a7f43b..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestResult.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -/** - * Non-throwing result for Kafka envelope consumers. It lets production - * listeners isolate bad records without blocking the partition. - */ -public record EnvelopeIngestResult(Status status, String eventId, String vin, String message) { - - public enum Status { - STORED, - PROCESSED, - SKIPPED, - INVALID_ENVELOPE, - FAILED - } - - public EnvelopeIngestResult { - if (status == null) { - throw new IllegalArgumentException("status must not be null"); - } - eventId = eventId == null ? "" : eventId; - vin = vin == null ? "" : vin; - message = message == null ? "" : message; - } - - public static EnvelopeIngestResult stored(String eventId, String vin) { - return new EnvelopeIngestResult(Status.STORED, eventId, vin, ""); - } - - public static EnvelopeIngestResult processed(String eventId, String vin) { - return new EnvelopeIngestResult(Status.PROCESSED, eventId, vin, ""); - } - - public static EnvelopeIngestResult skipped(String eventId, String vin, String message) { - return new EnvelopeIngestResult(Status.SKIPPED, eventId, vin, message); - } - - public static EnvelopeIngestResult invalid(String message) { - return new EnvelopeIngestResult(Status.INVALID_ENVELOPE, "", "", message); - } - - public static EnvelopeIngestResult failed(String eventId, String vin, String message) { - return new EnvelopeIngestResult(Status.FAILED, eventId, vin, message); - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestor.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestor.java deleted file mode 100644 index 2d200465..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/consumer/EnvelopeIngestor.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -import java.util.ArrayList; -import java.util.List; - -@FunctionalInterface -public interface EnvelopeIngestor { - EnvelopeIngestResult tryIngest(byte[] kafkaValue); - - default List tryIngestAll(List kafkaValues) { - if (kafkaValues == null || kafkaValues.isEmpty()) { - return List.of(); - } - List results = new ArrayList<>(kafkaValues.size()); - for (byte[] kafkaValue : kafkaValues) { - results.add(tryIngest(kafkaValue)); - } - return results; - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/AlarmPayload.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/AlarmPayload.java deleted file mode 100644 index 48c9e6bf..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/AlarmPayload.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.lingniu.ingest.api.event; - -import java.util.List; -import java.util.Set; - -/** - * 报警事件载荷,跨协议归一。 - * - * @param level 报警最高等级 - * @param alarmTypeCode 协议原始 alarm type 编码(按协议自定义),可用于联查协议规范 - * @param alarmTypeName 报警类型名称,如 {@code GB32960_ALARM} - * @param faultCodes 故障代码列表(协议私有编码字符串化,便于下游去重/聚合) - * @param activeBits 结构化的通用报警位集合(2016 版:0~15;2025 版:0~27),对照 GB/T 32960.3 表 24 - * @param longitude 经度(可选,若事件伴随位置) - * @param latitude 纬度(可选) - * @param safetyCategory 内部安全分类。氢气泄露独立成类,不与普通故障混用 - * @param hydrogenLeakDetected 是否检测到氢气泄露 - * @param hydrogenLeakLevel 氢气泄露等级 - * @param hydrogenLeakActionRequired 是否需要立即处置 - */ -public record AlarmPayload( - AlarmLevel level, - int alarmTypeCode, - String alarmTypeName, - List faultCodes, - Set activeBits, - Double longitude, - Double latitude, - SafetyCategory safetyCategory, - boolean hydrogenLeakDetected, - HydrogenLeakLevel hydrogenLeakLevel, - boolean hydrogenLeakActionRequired -) { - /** - * 报警等级(归一化的跨协议分类)。 - * - *

    - *
  • {@link #INFO}:提示级(对应 GB/T 32960.3 0/无故障) - *
  • {@link #MINOR}:1 级 —— 不影响车辆正常行驶 - *
  • {@link #MAJOR}:2 级 —— 影响车辆性能,需驾驶员限制行驶 - *
  • {@link #CRITICAL}:3 级(驾驶员应立即停车)或 4 级(热事件最高级) - *
- */ - public enum AlarmLevel { INFO, MINOR, MAJOR, CRITICAL } - - /** - * 面向运营统计的内部安全分类。 - */ - public enum SafetyCategory { - GENERAL, - TANK_PRESSURE, - TANK_TEMPERATURE, - HYDROGEN_LEAK - } - - /** - * 氢气泄露内部等级。明确泄露信号一律视为 CRITICAL。 - */ - public enum HydrogenLeakLevel { - NONE, - WARNING, - CRITICAL, - UNKNOWN - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/LocationPayload.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/LocationPayload.java deleted file mode 100644 index f786fde9..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/LocationPayload.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lingniu.ingest.api.event; - -/** 位置事件载荷。坐标已统一转换为 WGS84 十进制度。 */ -public record LocationPayload( - double longitude, - double latitude, - double altitudeM, - double speedKmh, - double directionDeg, - long alarmFlag, - long statusFlag, - Double totalMileageKm -) { - public LocationPayload(double longitude, - double latitude, - double altitudeM, - double speedKmh, - double directionDeg, - long alarmFlag, - long statusFlag) { - this(longitude, latitude, altitudeM, speedKmh, directionDeg, alarmFlag, statusFlag, null); - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RawArchiveKeys.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RawArchiveKeys.java deleted file mode 100644 index 014098d9..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RawArchiveKeys.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.lingniu.ingest.api.event; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Map; - -/** - * Shared raw-archive key conventions used by Dispatcher, archive sink, and query services. - * - *

32960 生产链路里,RAW .bin 文件本体按 {@code 日期/协议/VIN/eventId.bin} 落在 archive 根目录, - * TDengine raw_frames 或兼容索引只保存 {@code archive://...} 引用。这个类集中维护 key 规则, - * 避免接收端、落盘端、snapshot 查询端对目录分区的理解不一致。

- */ -public final class RawArchiveKeys { - - public static final String META_EVENT_ID = "rawArchiveEventId"; - public static final String META_KEY = "rawArchiveKey"; - public static final String META_URI = "rawArchiveUri"; - public static final String META_PARSED_JSON = "rawArchiveParsedJson"; - - private static final DateTimeFormatter DATE_KEY = - DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.of("Asia/Shanghai")); - - private RawArchiveKeys() { - } - - public static String key(Instant ingestTime, ProtocolId source, String vin, String eventId) { - // 分区日期使用 ingestTime 的东八区自然日,和磁盘目录保持一致;eventTime 只代表车端上报时间。 - String dateKey = DATE_KEY.format(ingestTime == null ? Instant.EPOCH : ingestTime); - String safeVin = vin == null || vin.isBlank() ? "unknown-vin" : vin; - String safeEventId = eventId == null || eventId.isBlank() - ? Long.toHexString(System.nanoTime()) : eventId; - String safeSource = source == null ? "UNKNOWN" : source.name(); - return dateKey + "/" + safeSource + "/" + safeVin + "/" + safeEventId + ".bin"; - } - - public static String key(VehicleEvent.RawArchive raw) { - String key = metadataValue(raw.metadata(), META_KEY); - if (!key.isBlank()) { - return key; - } - return key(raw.ingestTime(), raw.source(), raw.vin(), raw.eventId()); - } - - public static String logicalUri(String key) { - return key == null || key.isBlank() ? "" : "archive://" + key; - } - - private static String metadataValue(Map metadata, String key) { - if (metadata == null || key == null) { - return ""; - } - String value = metadata.get(key); - return value == null ? "" : value; - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RealtimePayload.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RealtimePayload.java deleted file mode 100644 index 76ceefa1..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/RealtimePayload.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.lingniu.ingest.api.event; - -/** - * 实时数据载荷(扁平 record,字段用 {@code Double} / {@code Integer} 允许 null,表达"未上报")。 - * - *

不再复刻旧服务 {@code VehicleDataActual} 的 273 字段巨型类 —— 协议特有细节通过 - * {@link VehicleEvent#metadata()} 传递。此处只保留跨协议通用字段。 - */ -public record RealtimePayload( - // ===== 动力 & 能源 ===== - Double speedKmh, - Double totalMileageKm, - Double batterySoc, - Double batteryVoltageV, - Double batteryCurrentA, - - // ===== 燃料电池 / 氢能 ===== - Double fcVoltageV, - Double fcCurrentA, - Double fcTempC, - Double hydrogenRemainingKg, - Double hydrogenHighPressureMpa, - Double hydrogenLowPressureMpa, - - // ===== 车辆状态 ===== - VehicleState vehicleState, - ChargingState chargingState, - RunningMode runningMode, - Integer gearLevel, - Double acceleratorPedal, - Double brakePedal, - - // ===== 位置(冗余一份便于单主题消费)===== - Double longitude, - Double latitude, - Double altitudeM, - Double directionDeg, - - // ===== 环境 ===== - Double ambientTempC, - Double coolantTempC -) { - - public enum VehicleState { STARTED, SHUTDOWN, OTHER, INVALID } - - public enum ChargingState { UNCHARGED, PARKED_CHARGING, DRIVING_CHARGING, CHARGED, OTHER, INVALID } - - public enum RunningMode { ELECTRIC, HYBRID, FUEL, OTHER, INVALID } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryFieldValue.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryFieldValue.java deleted file mode 100644 index d101ad25..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryFieldValue.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.lingniu.ingest.api.event; - -/** - * One normalized telemetry field value. - * - *

Protocol mappers use stable internal field keys so Kafka, TDengine, Redis, - * and statistics do not depend on protocol-specific names. - * - *

{@code value} 统一用字符串承载,真实类型由 {@code valueType} 表达。这样 Kafka - * protobuf、CSV 导出、JSON 字段和前端展示可以共用同一份字段结构;数值精度/格式化 - * 由查询层或前端按 {@code valueType + unit} 决定。 - */ -public record TelemetryFieldValue( - String key, - ValueType valueType, - String value, - String unit, - Quality quality, - String sourcePath -) { - - public TelemetryFieldValue { - if (key == null || key.isBlank()) { - throw new IllegalArgumentException("key must not be blank"); - } - if (valueType == null) { - throw new IllegalArgumentException("valueType must not be null"); - } - value = value == null ? "" : value; - unit = unit == null ? "" : unit; - // 默认 GOOD,只有解析器明确知道异常/无效/缺失时才降级,避免调用方到处补质量字段。 - quality = quality == null ? Quality.GOOD : quality; - sourcePath = sourcePath == null ? "" : sourcePath; - } - - public static TelemetryFieldValue stringValue(String key, String value, String unit, String sourcePath) { - return new TelemetryFieldValue(key, ValueType.STRING, value, unit, Quality.GOOD, sourcePath); - } - - public static TelemetryFieldValue doubleValue(String key, double value, String unit, String sourcePath) { - return new TelemetryFieldValue(key, ValueType.DOUBLE, Double.toString(value), unit, Quality.GOOD, sourcePath); - } - - public static TelemetryFieldValue longValue(String key, long value, String unit, String sourcePath) { - return new TelemetryFieldValue(key, ValueType.LONG, Long.toString(value), unit, Quality.GOOD, sourcePath); - } - - public static TelemetryFieldValue booleanValue(String key, boolean value, String unit, String sourcePath) { - return new TelemetryFieldValue(key, ValueType.BOOLEAN, Boolean.toString(value), unit, Quality.GOOD, sourcePath); - } - - public static TelemetryFieldValue instantValue(String key, String value, String unit, String sourcePath) { - return new TelemetryFieldValue(key, ValueType.INSTANT, value, unit, Quality.GOOD, sourcePath); - } - - public static TelemetryFieldValue jsonValue(String key, String value, String unit, String sourcePath) { - return new TelemetryFieldValue(key, ValueType.JSON, value, unit, Quality.GOOD, sourcePath); - } - - public static TelemetryFieldValue missing(String key, String unit, String sourcePath) { - return new TelemetryFieldValue(key, ValueType.STRING, "", unit, Quality.MISSING, sourcePath); - } - - public enum ValueType { - STRING, - DOUBLE, - LONG, - BOOLEAN, - INSTANT, - JSON - } - - public enum Quality { - GOOD, - ESTIMATED, - INVALID, - MISSING - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryMetadataValues.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryMetadataValues.java deleted file mode 100644 index 48f85bba..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetryMetadataValues.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.lingniu.ingest.api.event; - -import java.util.Locale; - -/** - * Stable string formatting for metadata values shared across protocol mappers. - */ -public final class TelemetryMetadataValues { - - private TelemetryMetadataValues() { - } - - public static String doubleValue(double value) { - if (!Double.isFinite(value)) { - return ""; - } - String formatted = String.format(Locale.ROOT, "%.6f", value); - return formatted.indexOf('.') < 0 - ? formatted - : formatted.replaceAll("0+$", "").replaceAll("\\.$", ""); - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetrySnapshot.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetrySnapshot.java deleted file mode 100644 index 2035eb55..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/TelemetrySnapshot.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.lingniu.ingest.api.event; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -/** - * Normalized full-field telemetry snapshot for one parsed vehicle event. - * - *

This is the shared contract for Kafka, TDengine history, Redis hot state, - * and statistics. It intentionally lives in {@code ingest-api} and has no - * Spring, Kafka, Redis, or TDengine dependency. - */ -public record TelemetrySnapshot( - String eventId, - String vin, - ProtocolId protocol, - String eventType, - Instant eventTime, - Instant ingestTime, - String rawArchiveUri, - Map metadata, - List fields -) { - - public TelemetrySnapshot { - if (eventId == null || eventId.isBlank()) { - throw new IllegalArgumentException("eventId must not be blank"); - } - if (vin == null || vin.isBlank()) { - throw new IllegalArgumentException("vin must not be blank"); - } - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - if (eventType == null || eventType.isBlank()) { - throw new IllegalArgumentException("eventType must not be blank"); - } - if (eventTime == null) { - throw new IllegalArgumentException("eventTime must not be null"); - } - if (ingestTime == null) { - throw new IllegalArgumentException("ingestTime must not be null"); - } - rawArchiveUri = rawArchiveUri == null ? "" : rawArchiveUri; - metadata = Map.copyOf(metadata == null ? Map.of() : metadata); - fields = List.copyOf(fields == null ? List.of() : fields); - // snapshot/fields 查询按 key 投影和导出,重复 key 会导致前端列和 CSV 表头不稳定,构造期直接拒绝。 - validateUniqueKeys(fields); - } - - public Optional field(String key) { - if (key == null || key.isBlank()) { - return Optional.empty(); - } - // fields 保持插入顺序,单字段查询走线性查找;批量查询应使用 fieldsByKey 建索引。 - for (TelemetryFieldValue field : fields) { - if (field.key().equals(key)) { - return Optional.of(field); - } - } - return Optional.empty(); - } - - public Map fieldsByKey() { - Map index = new LinkedHashMap<>(); - for (TelemetryFieldValue field : fields) { - index.put(field.key(), field); - } - // LinkedHashMap 保留原始字段顺序,CSV 导出和 Swagger 示例能稳定复现解析器顺序。 - return Map.copyOf(index); - } - - public static Builder builder(String eventId, - String vin, - ProtocolId protocol, - String eventType, - Instant eventTime, - Instant ingestTime) { - return new Builder(eventId, vin, protocol, eventType, eventTime, ingestTime); - } - - private static void validateUniqueKeys(List fields) { - Map seen = new LinkedHashMap<>(); - for (TelemetryFieldValue field : fields) { - if (field == null) { - throw new IllegalArgumentException("field must not be null"); - } - TelemetryFieldValue previous = seen.put(field.key(), field); - if (previous != null) { - throw new IllegalArgumentException("duplicate telemetry field key: " + field.key()); - } - } - } - - public static final class Builder { - private final String eventId; - private final String vin; - private final ProtocolId protocol; - private final String eventType; - private final Instant eventTime; - private final Instant ingestTime; - private String rawArchiveUri = ""; - private Map metadata = Map.of(); - private final List fields = new ArrayList<>(); - - private Builder(String eventId, - String vin, - ProtocolId protocol, - String eventType, - Instant eventTime, - Instant ingestTime) { - this.eventId = eventId; - this.vin = vin; - this.protocol = protocol; - this.eventType = eventType; - this.eventTime = eventTime; - this.ingestTime = ingestTime; - } - - public Builder rawArchiveUri(String rawArchiveUri) { - this.rawArchiveUri = rawArchiveUri; - return this; - } - - public Builder metadata(Map metadata) { - this.metadata = metadata == null ? Map.of() : Map.copyOf(metadata); - return this; - } - - public Builder addField(TelemetryFieldValue field) { - this.fields.add(field); - return this; - } - - public TelemetrySnapshot build() { - return new TelemetrySnapshot( - eventId, vin, protocol, eventType, eventTime, ingestTime, - rawArchiveUri, metadata, fields); - } - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEvent.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEvent.java deleted file mode 100644 index d4f4e90a..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEvent.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.lingniu.ingest.api.event; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.Map; - -/** - * 领域事件根类型。所有协议归一到这里。 - * - *

使用 sealed + record 表达有界闭合的事件集合,下游消费方可用 {@code switch} 模式匹配穷尽处理。 - * - *

字段约定: - *

    - *
  • {@link #vin} 作为 Kafka 分区 key,保证单车顺序 - *
  • {@link #eventTime} 设备上报时间(而非服务器接收时间) - *
  • {@link #ingestTime} 服务器接收时间,用于延迟监控 - *
  • {@link #traceId} W3C traceparent 上下文 - *
- */ -public sealed interface VehicleEvent - permits VehicleEvent.Realtime, - VehicleEvent.Location, - VehicleEvent.Alarm, - VehicleEvent.Login, - VehicleEvent.Logout, - VehicleEvent.Heartbeat, - VehicleEvent.MediaMeta, - VehicleEvent.Passthrough, - VehicleEvent.RawArchive { - - String eventId(); - String vin(); - ProtocolId source(); - Instant eventTime(); - Instant ingestTime(); - String traceId(); - - /** 通用元数据:协议版本、终端手机号、网关 IP 等。 */ - Map metadata(); - - // ===== 具体事件类型 ===== - - /** 整车实时数据(32960 实时上报 / MQTT 宇通 / JT808 位置扩展等都归一到这里)。 */ - record Realtime( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - RealtimePayload payload - ) implements VehicleEvent {} - - /** 纯位置事件(808 的 0x0200 等映射到此)。 */ - record Location( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - LocationPayload payload - ) implements VehicleEvent {} - - /** 报警事件。 */ - record Alarm( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - AlarmPayload payload - ) implements VehicleEvent {} - - /** 设备登入 / 车辆登入。 */ - record Login( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - String iccid, - String protocolVersion - ) implements VehicleEvent {} - - /** 设备登出。 */ - record Logout( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata - ) implements VehicleEvent {} - - /** 心跳 / 链路保活。 */ - record Heartbeat( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata - ) implements VehicleEvent {} - - /** 多媒体元数据(大文件本体走对象存储,通过 archiveRef 引用)。 */ - record MediaMeta( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - String mediaId, - String mediaType, - long sizeBytes, - String archiveRef - ) implements VehicleEvent {} - - /** 透传 / 自定义扩展消息。 */ - record Passthrough( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - int passthroughType, - byte[] data - ) implements VehicleEvent {} - - /** - * 原始报文归档事件:每条成功解码的入站帧由 Dispatcher 产出一条,携带原始字节和归档索引信息。 - * 32960 历史全字段查询依赖它对应的 {@code archive://...} 文件能够被回读。 - * - *

当前 {@code KafkaEventSink} 会发送本类型的索引信息,RAW bytes 由 archive 落盘保存; - * 因此启用 raw 落盘/转发实现时,必须同时保证 {@link RawArchiveKeys} 的 key 规则一致。 - * - * @param command 协议主命令码(如 32960 0x02/0x03 等),冗余在事件里便于按命令分片归档 - * @param infoType 协议子类型(可为 0),同上 - * @param rawBytes 原始入站字节,不可为 null - * @param parsedJson 已解析结构化 JSON。只用于 RAW 明细查询,不应复制到位置/字段等派生表。 - */ - record RawArchive( - String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - int command, - int infoType, - byte[] rawBytes, - String parsedJson - ) implements VehicleEvent { - public RawArchive(String eventId, - String vin, - ProtocolId source, - Instant eventTime, - Instant ingestTime, - String traceId, - Map metadata, - int command, - int infoType, - byte[] rawBytes) { - this(eventId, vin, source, eventTime, ingestTime, traceId, metadata, - command, infoType, rawBytes, ""); - } - - public RawArchive { - parsedJson = parsedJson == null ? "" : parsedJson; - } - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapper.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapper.java deleted file mode 100644 index d4392d43..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapper.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.lingniu.ingest.api.event; - -import java.util.Map; -import java.util.Optional; - -/** - * Converts current {@link VehicleEvent} variants into normalized full-field - * telemetry snapshots. - * - *

Protocol-specific mappers can add richer fields later, but all downstream - * modules should consume {@link TelemetrySnapshot} instead of protocol objects. - */ -public final class VehicleEventTelemetrySnapshotMapper { - - private VehicleEventTelemetrySnapshotMapper() { - } - - public static Optional toSnapshot(VehicleEvent event) { - if (event == null || event instanceof VehicleEvent.RawArchive) { - return Optional.empty(); - } - TelemetrySnapshot.Builder builder = TelemetrySnapshot.builder( - event.eventId(), - event.vin(), - event.source(), - eventType(event), - event.eventTime(), - event.ingestTime()) - .metadata(event.metadata()) - .rawArchiveUri(rawArchiveUri(event)); - - switch (event) { - case VehicleEvent.Realtime realtime -> addRealtimeFields(builder, realtime.payload()); - case VehicleEvent.Location location -> addLocationFields(builder, location.payload()); - case VehicleEvent.Alarm alarm -> addAlarmFields(builder, alarm.payload()); - case VehicleEvent.Login login -> { - addString(builder, "iccid", login.iccid(), "", "event.login.iccid"); - addString(builder, "protocol_version", login.protocolVersion(), "", "event.login.protocolVersion"); - } - case VehicleEvent.Logout ignored -> { - } - case VehicleEvent.Heartbeat ignored -> { - } - case VehicleEvent.MediaMeta media -> { - addString(builder, "media_id", media.mediaId(), "", "event.media.mediaId"); - addString(builder, "media_type", media.mediaType(), "", "event.media.mediaType"); - builder.addField(TelemetryFieldValue.longValue( - "media_size_bytes", media.sizeBytes(), "bytes", "event.media.sizeBytes")); - addString(builder, "media_archive_ref", media.archiveRef(), "", "event.media.archiveRef"); - } - case VehicleEvent.Passthrough passthrough -> { - builder.addField(TelemetryFieldValue.longValue( - "passthrough_type", passthrough.passthroughType(), "", "event.passthrough.type")); - builder.addField(TelemetryFieldValue.longValue( - "passthrough_size_bytes", - passthrough.data() == null ? 0 : passthrough.data().length, - "bytes", - "event.passthrough.data")); - } - case VehicleEvent.RawArchive ignored -> { - return Optional.empty(); - } - } - return Optional.of(builder.build()); - } - - private static void addRealtimeFields(TelemetrySnapshot.Builder builder, RealtimePayload p) { - if (p == null) return; - addDouble(builder, "speed_kmh", p.speedKmh(), "km/h", "event.realtime.speedKmh"); - addDouble(builder, "total_mileage_km", p.totalMileageKm(), "km", "event.realtime.totalMileageKm"); - addDouble(builder, "battery_soc", p.batterySoc(), "%", "event.realtime.batterySoc"); - addDouble(builder, "battery_voltage_v", p.batteryVoltageV(), "V", "event.realtime.batteryVoltageV"); - addDouble(builder, "battery_current_a", p.batteryCurrentA(), "A", "event.realtime.batteryCurrentA"); - addDouble(builder, "fc_voltage_v", p.fcVoltageV(), "V", "event.realtime.fcVoltageV"); - addDouble(builder, "fc_current_a", p.fcCurrentA(), "A", "event.realtime.fcCurrentA"); - addDouble(builder, "fc_temp_c", p.fcTempC(), "C", "event.realtime.fcTempC"); - addDouble(builder, "hydrogen_remaining_kg", p.hydrogenRemainingKg(), "kg", "event.realtime.hydrogenRemainingKg"); - addDouble(builder, "hydrogen_high_pressure_mpa", p.hydrogenHighPressureMpa(), "MPa", "event.realtime.hydrogenHighPressureMpa"); - addDouble(builder, "hydrogen_low_pressure_mpa", p.hydrogenLowPressureMpa(), "MPa", "event.realtime.hydrogenLowPressureMpa"); - addEnum(builder, "vehicle_state", p.vehicleState(), "", "event.realtime.vehicleState"); - addEnum(builder, "charging_state", p.chargingState(), "", "event.realtime.chargingState"); - addEnum(builder, "running_mode", p.runningMode(), "", "event.realtime.runningMode"); - addLong(builder, "gear_level", p.gearLevel(), "", "event.realtime.gearLevel"); - addDouble(builder, "accelerator_pedal", p.acceleratorPedal(), "%", "event.realtime.acceleratorPedal"); - addDouble(builder, "brake_pedal", p.brakePedal(), "%", "event.realtime.brakePedal"); - addDouble(builder, "longitude", p.longitude(), "deg", "event.realtime.longitude"); - addDouble(builder, "latitude", p.latitude(), "deg", "event.realtime.latitude"); - addDouble(builder, "altitude_m", p.altitudeM(), "m", "event.realtime.altitudeM"); - addDouble(builder, "direction_deg", p.directionDeg(), "deg", "event.realtime.directionDeg"); - addDouble(builder, "ambient_temp_c", p.ambientTempC(), "C", "event.realtime.ambientTempC"); - addDouble(builder, "coolant_temp_c", p.coolantTempC(), "C", "event.realtime.coolantTempC"); - } - - private static void addLocationFields(TelemetrySnapshot.Builder builder, LocationPayload p) { - if (p == null) return; - builder.addField(TelemetryFieldValue.doubleValue("longitude", p.longitude(), "deg", "event.location.longitude")); - builder.addField(TelemetryFieldValue.doubleValue("latitude", p.latitude(), "deg", "event.location.latitude")); - builder.addField(TelemetryFieldValue.doubleValue("altitude_m", p.altitudeM(), "m", "event.location.altitudeM")); - builder.addField(TelemetryFieldValue.doubleValue("speed_kmh", p.speedKmh(), "km/h", "event.location.speedKmh")); - addDouble(builder, "total_mileage_km", p.totalMileageKm(), "km", "event.location.totalMileageKm"); - builder.addField(TelemetryFieldValue.doubleValue("direction_deg", p.directionDeg(), "deg", "event.location.directionDeg")); - builder.addField(TelemetryFieldValue.longValue("location_alarm_flag", p.alarmFlag(), "", "event.location.alarmFlag")); - builder.addField(TelemetryFieldValue.longValue("location_status_raw", p.statusFlag(), "", "event.location.statusFlag")); - } - - private static void addAlarmFields(TelemetrySnapshot.Builder builder, AlarmPayload p) { - if (p == null) return; - addEnum(builder, "alarm_level", p.level(), "", "event.alarm.level"); - builder.addField(TelemetryFieldValue.longValue("alarm_type_code", p.alarmTypeCode(), "", "event.alarm.alarmTypeCode")); - addString(builder, "alarm_type_name", p.alarmTypeName(), "", "event.alarm.alarmTypeName"); - if (p.faultCodes() != null && !p.faultCodes().isEmpty()) { - builder.addField(TelemetryFieldValue.stringValue( - "fault_codes", String.join(",", p.faultCodes()), "", "event.alarm.faultCodes")); - } - if (p.activeBits() != null && !p.activeBits().isEmpty()) { - builder.addField(TelemetryFieldValue.stringValue( - "active_alarm_bits", String.join(",", p.activeBits()), "", "event.alarm.activeBits")); - } - addDouble(builder, "longitude", p.longitude(), "deg", "event.alarm.longitude"); - addDouble(builder, "latitude", p.latitude(), "deg", "event.alarm.latitude"); - addEnum(builder, "safety_category", p.safetyCategory(), "", "event.alarm.safetyCategory"); - builder.addField(TelemetryFieldValue.booleanValue( - "hydrogen_leak_detected", p.hydrogenLeakDetected(), "", "event.alarm.hydrogenLeakDetected")); - addEnum(builder, "hydrogen_leak_level", p.hydrogenLeakLevel(), "", "event.alarm.hydrogenLeakLevel"); - builder.addField(TelemetryFieldValue.booleanValue( - "hydrogen_leak_action_required", - p.hydrogenLeakActionRequired(), - "", - "event.alarm.hydrogenLeakActionRequired")); - } - - private static String eventType(VehicleEvent event) { - if (event instanceof VehicleEvent.Realtime) return "REALTIME"; - if (event instanceof VehicleEvent.Location) return "LOCATION"; - if (event instanceof VehicleEvent.Alarm) return "ALARM"; - if (event instanceof VehicleEvent.Login) return "LOGIN"; - if (event instanceof VehicleEvent.Logout) return "LOGOUT"; - if (event instanceof VehicleEvent.Heartbeat) return "HEARTBEAT"; - if (event instanceof VehicleEvent.MediaMeta) return "MEDIA_META"; - if (event instanceof VehicleEvent.Passthrough) return "PASSTHROUGH"; - return "UNKNOWN"; - } - - private static String rawArchiveUri(VehicleEvent event) { - Map metadata = event.metadata(); - if (metadata == null) { - return mediaArchiveRef(event); - } - String uri = metadata.getOrDefault(RawArchiveKeys.META_URI, ""); - if (uri != null && !uri.isBlank()) { - return uri; - } - String key = metadata.getOrDefault(RawArchiveKeys.META_KEY, ""); - if (key != null && !key.isBlank()) { - return RawArchiveKeys.logicalUri(key); - } - return mediaArchiveRef(event); - } - - private static String mediaArchiveRef(VehicleEvent event) { - if (event instanceof VehicleEvent.MediaMeta media && media.archiveRef() != null) { - return media.archiveRef(); - } - return ""; - } - - private static void addDouble(TelemetrySnapshot.Builder builder, - String key, - Double value, - String unit, - String sourcePath) { - if (value != null) { - builder.addField(TelemetryFieldValue.doubleValue(key, value, unit, sourcePath)); - } - } - - private static void addLong(TelemetrySnapshot.Builder builder, - String key, - Integer value, - String unit, - String sourcePath) { - if (value != null) { - builder.addField(TelemetryFieldValue.longValue(key, value, unit, sourcePath)); - } - } - - private static void addString(TelemetrySnapshot.Builder builder, - String key, - String value, - String unit, - String sourcePath) { - if (value != null && !value.isBlank()) { - builder.addField(TelemetryFieldValue.stringValue(key, value, unit, sourcePath)); - } - } - - private static void addEnum(TelemetrySnapshot.Builder builder, - String key, - Enum value, - String unit, - String sourcePath) { - if (value != null) { - builder.addField(TelemetryFieldValue.stringValue(key, value.name(), unit, sourcePath)); - } - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/package-info.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/package-info.java deleted file mode 100644 index c93d2a45..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/package-info.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * lingniu-vehicle-ingest 公共 API 模块。零 Spring 依赖,只定义 SPI、sealed 领域事件、注解。 - * - *

分包: - *

    - *
  • {@code annotation} - Handler 注解体系 - *
  • {@code event} - sealed {@code VehicleEvent} + payload records - *
  • {@code pipeline} - RawFrame / IngestContext / IngestInterceptor - *
  • {@code sink} - EventSink SPI - *
  • {@code spi} - ProtocolPlugin / FrameDecoder / FrameEncoder / EventMapper - *
- */ -package com.lingniu.ingest.api; diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestContext.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestContext.java deleted file mode 100644 index 6b54d829..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestContext.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.lingniu.ingest.api.pipeline; - -import java.util.HashMap; -import java.util.Map; - -/** - * 一次消息处理的上下文,贯穿整个拦截链与 Handler。线程不共享,不需要同步。 - * - *

attributes 用于在入口、拦截器、Dispatcher 之间传递轻量元数据,例如 traceId、归档 URI、 - * 鉴权结果等;不要放大对象或长期缓存,避免高频上报时放大内存占用。 - */ -public final class IngestContext { - - private final String traceId; - private final Map attributes = new HashMap<>(8); - private volatile boolean aborted; - private volatile String abortReason; - - public IngestContext(String traceId) { - this.traceId = traceId; - } - - public String traceId() { - return traceId; - } - - public T attr(String key) { - @SuppressWarnings("unchecked") - T v = (T) attributes.get(key); - return v; - } - - public void attr(String key, Object value) { - attributes.put(key, value); - } - - public void abort(String reason) { - // reason 会进入日志/诊断信息,保持短小、机器可读,方便定位是去重、限流还是鉴权失败。 - this.aborted = true; - this.abortReason = reason; - } - - public boolean aborted() { - return aborted; - } - - public String abortReason() { - return abortReason; - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestInterceptor.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestInterceptor.java deleted file mode 100644 index e0574fa8..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/IngestInterceptor.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.api.pipeline; - -import com.lingniu.ingest.api.event.VehicleEvent; - -/** - * 拦截器 SPI。内置实现:Auth / Dedup / RateLimit / CoordTransform / Tracing。 - * 实现 {@link org.springframework.core.Ordered}(或使用 {@code @Order})控制顺序。 - */ -public interface IngestInterceptor { - - /** 解码后、分发到 Handler 之前触发。返回 false 表示终止后续处理。 */ - default boolean before(RawFrame frame, IngestContext ctx) { - return true; - } - - /** Handler 成功产出事件后触发,允许修饰事件或取消投递。 */ - default void after(VehicleEvent event, IngestContext ctx) {} - - /** 处理链任一环节抛出异常时触发。 */ - default void onError(Throwable error, IngestContext ctx) {} -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/RawFrame.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/RawFrame.java deleted file mode 100644 index d292ce19..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/pipeline/RawFrame.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.lingniu.ingest.api.pipeline; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.Map; - -/** - * 进入 Pipeline 的原始帧描述。 - * - * @param protocolId 来源协议 - * @param command 协议主命令 / 消息 ID(由各协议自行定义) - * @param infoType 可选子类型(如 32960 的信息体 ID) - * @param payload 已经被 {@link com.lingniu.ingest.api.spi.FrameDecoder} 解析出的协议对象 - * @param rawBytes 原始字节,用于冷存与排障(可能为 null,按配置开关) - * @param sourceMeta 来源元数据:peer ip、端口、session id、topic 等 - * @param receivedAt 服务器接收时刻 - * - *

RawFrame 是所有协议进入统一 Pipeline 的边界对象。32960 TCP 入口会把 VIN、seq、 - * rawArchiveUri 等关键索引放进 sourceMeta,后续去重、快照和历史查询都依赖这些字段保持稳定。 - */ -public record RawFrame( - ProtocolId protocolId, - int command, - int infoType, - Object payload, - byte[] rawBytes, - Map sourceMeta, - Instant receivedAt -) {} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/sink/EventSink.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/sink/EventSink.java deleted file mode 100644 index dcc13451..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/sink/EventSink.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.lingniu.ingest.api.sink; - -import com.lingniu.ingest.api.event.VehicleEvent; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * 事件出口 SPI。Kafka、本地归档、指标打点都是一种 Sink。 - * - *

实现应当线程安全、非阻塞(内部异步 IO)。由 Disruptor EventBus 扇出调用。 - */ -public interface EventSink { - - String name(); - - CompletableFuture publish(VehicleEvent event); - - default CompletableFuture publishBatch(List batch) { - CompletableFuture[] all = batch.stream().map(this::publish).toArray(CompletableFuture[]::new); - return CompletableFuture.allOf(all); - } - - /** 是否接受此事件类型。默认全部接受。 */ - default boolean accepts(VehicleEvent event) { - return true; - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/DecodeException.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/DecodeException.java deleted file mode 100644 index 0c49d960..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/DecodeException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.lingniu.ingest.api.spi; - -/** 解码阶段异常,指示数据体本身违反协议。应被 Dispatcher 路由到 DLQ。 */ -public class DecodeException extends RuntimeException { - public DecodeException(String message) { - super(message); - } - - public DecodeException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/EventMapper.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/EventMapper.java deleted file mode 100644 index ecceef3c..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/EventMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.lingniu.ingest.api.spi; - -import com.lingniu.ingest.api.event.VehicleEvent; - -import java.util.List; - -/** - * 协议消息 → 领域事件的映射器(Adapter 模式)。 - * - *

实现类负责把"我这个协议特有的 Java 对象"翻译成统一的 {@link VehicleEvent}。 - * 允许一条协议消息展开为多条领域事件(例如 32960 的一次实时上报同时产出 Realtime + Location)。 - */ -@FunctionalInterface -public interface EventMapper { - List toEvents(I message); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameDecoder.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameDecoder.java deleted file mode 100644 index 9aa9eb90..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameDecoder.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.lingniu.ingest.api.spi; - -import java.nio.ByteBuffer; - -/** - * 协议帧解码器:字节流 → 协议消息对象。 - * - *

实现类应是无状态的(拆包/粘包由上游 Netty 的帧解码器处理),此处只负责一条完整帧的解析。 - */ -@FunctionalInterface -public interface FrameDecoder { - I decode(ByteBuffer buffer) throws DecodeException; -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameEncoder.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameEncoder.java deleted file mode 100644 index ded9040d..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/FrameEncoder.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.lingniu.ingest.api.spi; - -import java.nio.ByteBuffer; - -/** 下行帧编码器。无下行的协议返回 null 即可。 */ -@FunctionalInterface -public interface FrameEncoder { - ByteBuffer encode(O message); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/MessageHandler.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/MessageHandler.java deleted file mode 100644 index 13b5278d..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/MessageHandler.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.lingniu.ingest.api.spi; - -import com.lingniu.ingest.api.event.VehicleEvent; - -import java.util.List; - -/** - * 编程式 Handler 接口。推荐使用 {@code @ProtocolHandler + @MessageMapping} 注解方式代替。 - * 此接口保留是为了 SPI 场景下的脚本式扩展(例如动态加载的 groovy handler)。 - */ -public interface MessageHandler { - - boolean supports(I message); - - List handle(I message); -} diff --git a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/ProtocolPlugin.java b/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/ProtocolPlugin.java deleted file mode 100644 index 49947910..00000000 --- a/modules/core/ingest-api/src/main/java/com/lingniu/ingest/api/spi/ProtocolPlugin.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.lingniu.ingest.api.spi; - -import com.lingniu.ingest.api.ProtocolId; - -import java.util.List; - -/** - * 协议插件 SPI。每个协议模块提供一个实现,由 {@code ProtocolPluginRegistry} 通过 ServiceLoader - * 或 Spring Bean 方式发现并装配。 - * - * @param 协议解码后的消息类型 - * @param 下行消息类型(无下行的协议用 {@link Void}) - */ -public interface ProtocolPlugin { - - ProtocolId id(); - - FrameDecoder decoder(); - - FrameEncoder encoder(); - - EventMapper eventMapper(); - - /** - * 插件自带的协议特定 Handler 列表(可选)。 - * 通常推荐使用 {@code @ProtocolHandler} + Spring Bean 的方式注册,此处返回空列表。 - */ - default List> builtinHandlers() { - return List.of(); - } -} diff --git a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessorTest.java b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessorTest.java deleted file mode 100644 index 80f142f2..00000000 --- a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/consumer/EnvelopeConsumerProcessorTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.lingniu.ingest.api.consumer; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class EnvelopeConsumerProcessorTest { - - @Test - void invalidEnvelopePublishesDeadLetterRecordWithKafkaPosition() { - CapturingDeadLetterSink deadLetters = new CapturingDeadLetterSink(); - EnvelopeConsumerProcessor processor = new EnvelopeConsumerProcessor( - "event-history", ignored -> EnvelopeIngestResult.invalid("VehicleEnvelope bytes are invalid"), deadLetters); - - EnvelopeIngestResult result = processor.process( - new EnvelopeConsumerRecord("vehicle.realtime", 2, 42L, "VIN001", new byte[]{0x01, 0x02})); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.INVALID_ENVELOPE); - assertThat(deadLetters.records).singleElement().satisfies(record -> { - assertThat(record.service()).isEqualTo("event-history"); - assertThat(record.topic()).isEqualTo("vehicle.realtime"); - assertThat(record.partition()).isEqualTo(2); - assertThat(record.offset()).isEqualTo(42L); - assertThat(record.key()).isEqualTo("VIN001"); - assertThat(record.status()).isEqualTo(EnvelopeIngestResult.Status.INVALID_ENVELOPE); - assertThat(record.message()).contains("VehicleEnvelope"); - assertThat(record.payload()).containsExactly(0x01, 0x02); - }); - } - - @Test - void processedEnvelopeDoesNotPublishDeadLetter() { - CapturingDeadLetterSink deadLetters = new CapturingDeadLetterSink(); - EnvelopeConsumerProcessor processor = new EnvelopeConsumerProcessor( - "vehicle-state", ignored -> EnvelopeIngestResult.processed("event-1", "VIN001"), deadLetters); - - EnvelopeIngestResult result = processor.process( - new EnvelopeConsumerRecord("vehicle.realtime", 0, 7L, "VIN001", new byte[]{0x0a})); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.PROCESSED); - assertThat(deadLetters.records).isEmpty(); - } - - private static final class CapturingDeadLetterSink implements EnvelopeDeadLetterSink { - private final List records = new ArrayList<>(); - - @Override - public void publish(EnvelopeDeadLetterRecord record) { - records.add(record); - } - } -} diff --git a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/RawArchiveKeysTest.java b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/RawArchiveKeysTest.java deleted file mode 100644 index a8e92eab..00000000 --- a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/RawArchiveKeysTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.lingniu.ingest.api.event; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class RawArchiveKeysTest { - - @Test - void keyDateUsesShanghaiBusinessDayInsteadOfUtcDay() { - String key = RawArchiveKeys.key( - Instant.parse("2026-06-22T16:30:00Z"), - ProtocolId.GB32960, - "VIN001", - "event-1"); - - assertThat(key).isEqualTo("2026/06/23/GB32960/VIN001/event-1.bin"); - } - - @Test - void keyForRawArchiveUsesPrecomputedMetadataKey() { - VehicleEvent.RawArchive raw = new VehicleEvent.RawArchive( - "event-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-22T16:30:00Z"), - Instant.parse("2026-06-22T16:30:00Z"), - "trace-1", - Map.of(RawArchiveKeys.META_KEY, "precomputed/key.bin"), - 0x02, - 0, - new byte[]{0x23, 0x23}); - - assertThat(RawArchiveKeys.key(raw)).isEqualTo("precomputed/key.bin"); - } -} diff --git a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryFieldValueTest.java b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryFieldValueTest.java deleted file mode 100644 index e38170ec..00000000 --- a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryFieldValueTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.lingniu.ingest.api.event; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class TelemetryFieldValueTest { - - @Test - void createsTypedDoubleFieldWithStableMetadata() { - TelemetryFieldValue field = TelemetryFieldValue.doubleValue( - "total_mileage_km", 12345.6, "km", "gb32960.vehicle.totalMileageKm"); - - assertThat(field.key()).isEqualTo("total_mileage_km"); - assertThat(field.valueType()).isEqualTo(TelemetryFieldValue.ValueType.DOUBLE); - assertThat(field.value()).isEqualTo("12345.6"); - assertThat(field.unit()).isEqualTo("km"); - assertThat(field.quality()).isEqualTo(TelemetryFieldValue.Quality.GOOD); - assertThat(field.sourcePath()).isEqualTo("gb32960.vehicle.totalMileageKm"); - } - - @Test - void rejectsBlankFieldKey() { - assertThatThrownBy(() -> TelemetryFieldValue.stringValue(" ", "STARTED", "", "gb32960.vehicle.state")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("key"); - } - - @Test - void marksMissingValueExplicitly() { - TelemetryFieldValue field = TelemetryFieldValue.missing( - "hydrogen_remaining_kg", "kg", "gb32960.vendor.hydrogenRemaining"); - - assertThat(field.valueType()).isEqualTo(TelemetryFieldValue.ValueType.STRING); - assertThat(field.value()).isEmpty(); - assertThat(field.quality()).isEqualTo(TelemetryFieldValue.Quality.MISSING); - } -} diff --git a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryMetadataValuesTest.java b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryMetadataValuesTest.java deleted file mode 100644 index d19abfc6..00000000 --- a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetryMetadataValuesTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lingniu.ingest.api.event; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class TelemetryMetadataValuesTest { - - @Test - void formatsDoubleWithoutFloatingPointNoise() { - assertThat(TelemetryMetadataValues.doubleValue(123456.70000000001)).isEqualTo("123456.7"); - assertThat(TelemetryMetadataValues.doubleValue(1234.5)).isEqualTo("1234.5"); - assertThat(TelemetryMetadataValues.doubleValue(1234.0)).isEqualTo("1234"); - assertThat(TelemetryMetadataValues.doubleValue(1.23456789)).isEqualTo("1.234568"); - } - - @Test - void rejectsNonFiniteDoubleValuesAsBlankMetadata() { - assertThat(TelemetryMetadataValues.doubleValue(Double.NaN)).isEmpty(); - assertThat(TelemetryMetadataValues.doubleValue(Double.POSITIVE_INFINITY)).isEmpty(); - assertThat(TelemetryMetadataValues.doubleValue(Double.NEGATIVE_INFINITY)).isEmpty(); - } -} diff --git a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetrySnapshotTest.java b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetrySnapshotTest.java deleted file mode 100644 index 3b22ccf7..00000000 --- a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/TelemetrySnapshotTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.lingniu.ingest.api.event; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class TelemetrySnapshotTest { - - @Test - void indexesFieldsByInternalKey() { - TelemetrySnapshot snapshot = new TelemetrySnapshot( - "event-1", - "VIN001", - ProtocolId.GB32960, - "REALTIME", - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "archive://raw/event-1.bin", - Map.of("protocolVersion", "V2016"), - List.of( - TelemetryFieldValue.doubleValue("total_mileage_km", 120.5, "km", "gb32960.vehicle.totalMileageKm"), - TelemetryFieldValue.booleanValue("hydrogen_leak_detected", true, "", "gb32960.alarm.HYDROGEN_LEAK") - ) - ); - - assertThat(snapshot.field("total_mileage_km")).isPresent(); - assertThat(snapshot.field("total_mileage_km").orElseThrow().value()).isEqualTo("120.5"); - assertThat(snapshot.field("hydrogen_leak_detected").orElseThrow().value()).isEqualTo("true"); - assertThat(snapshot.field("unknown")).isEmpty(); - assertThat(snapshot.metadata()).containsEntry("protocolVersion", "V2016"); - } - - @Test - void rejectsDuplicateFieldKeys() { - assertThatThrownBy(() -> new TelemetrySnapshot( - "event-1", - "VIN001", - ProtocolId.GB32960, - "REALTIME", - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "", - Map.of(), - List.of( - TelemetryFieldValue.doubleValue("speed_kmh", 1.0, "km/h", "a"), - TelemetryFieldValue.doubleValue("speed_kmh", 2.0, "km/h", "b") - ) - )) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("duplicate"); - } -} diff --git a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapperTest.java b/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapperTest.java deleted file mode 100644 index ab3caeba..00000000 --- a/modules/core/ingest-api/src/test/java/com/lingniu/ingest/api/event/VehicleEventTelemetrySnapshotMapperTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.lingniu.ingest.api.event; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleEventTelemetrySnapshotMapperTest { - - @Test - void mapsRealtimeEventToInternalTelemetryFields() { - VehicleEvent.Realtime event = new VehicleEvent.Realtime( - "event-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-1", - Map.of("protocolVersion", "V2016", "rawArchiveUri", "archive://raw/event-1.bin"), - new RealtimePayload( - 80.5, - 12345.6, - 55.0, - 610.0, - -20.5, - 220.0, - 100.0, - 65.0, - 8.2, - 35.0, - 1.1, - RealtimePayload.VehicleState.STARTED, - RealtimePayload.ChargingState.UNCHARGED, - RealtimePayload.RunningMode.FUEL, - 3, - 20.0, - 0.0, - 113.12, - 23.45, - 10.0, - 180.0, - 30.0, - 70.0 - ) - ); - - TelemetrySnapshot snapshot = VehicleEventTelemetrySnapshotMapper.toSnapshot(event).orElseThrow(); - - assertThat(snapshot.eventId()).isEqualTo("event-1"); - assertThat(snapshot.rawArchiveUri()).isEqualTo("archive://raw/event-1.bin"); - assertThat(snapshot.field("speed_kmh").orElseThrow().value()).isEqualTo("80.5"); - assertThat(snapshot.field("total_mileage_km").orElseThrow().value()).isEqualTo("12345.6"); - assertThat(snapshot.field("vehicle_state").orElseThrow().value()).isEqualTo("STARTED"); - assertThat(snapshot.field("hydrogen_high_pressure_mpa").orElseThrow().value()).isEqualTo("35.0"); - assertThat(snapshot.field("longitude").orElseThrow().value()).isEqualTo("113.12"); - } - - @Test - void mapsHydrogenLeakAlarmToInternalSafetyFields() { - VehicleEvent.Alarm event = new VehicleEvent.Alarm( - "event-2", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-2", - Map.of("protocolVersion", "V2016"), - new AlarmPayload( - AlarmPayload.AlarmLevel.CRITICAL, - 1, - "GB32960_ALARM", - java.util.List.of("OTH-1"), - java.util.Set.of("HYDROGEN_LEAK"), - 113.12, - 23.45, - AlarmPayload.SafetyCategory.HYDROGEN_LEAK, - true, - AlarmPayload.HydrogenLeakLevel.CRITICAL, - true - ) - ); - - TelemetrySnapshot snapshot = VehicleEventTelemetrySnapshotMapper.toSnapshot(event).orElseThrow(); - - assertThat(snapshot.eventType()).isEqualTo("ALARM"); - assertThat(snapshot.field("hydrogen_leak_detected").orElseThrow().value()).isEqualTo("true"); - assertThat(snapshot.field("hydrogen_leak_level").orElseThrow().value()).isEqualTo("CRITICAL"); - assertThat(snapshot.field("hydrogen_leak_action_required").orElseThrow().value()).isEqualTo("true"); - assertThat(snapshot.field("safety_category").orElseThrow().value()).isEqualTo("HYDROGEN_LEAK"); - } - - @Test - void mapsLocationMileageToInternalTelemetryField() { - VehicleEvent.Location event = new VehicleEvent.Location( - "event-location-1", - "VIN001", - ProtocolId.JT808, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-location-1", - Map.of("messageId", "0x200"), - new LocationPayload(113.12, 23.45, 10.0, 60.0, 90.0, 0, 1, 1234.5) - ); - - TelemetrySnapshot snapshot = VehicleEventTelemetrySnapshotMapper.toSnapshot(event).orElseThrow(); - - assertThat(snapshot.field("total_mileage_km").orElseThrow().value()).isEqualTo("1234.5"); - assertThat(snapshot.field("total_mileage_km").orElseThrow().sourcePath()) - .isEqualTo("event.location.totalMileageKm"); - } - - @Test - void mediaMetaUsesArchiveRefAsQueryableRawArchiveUriFallback() { - VehicleEvent.MediaMeta event = new VehicleEvent.MediaMeta( - "media-1", - "VIN001", - ProtocolId.JT1078, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-4", - Map.of("channel", "1"), - "media-001", - "jt1078-video-h264-channel-1", - 1024, - "file:///archive/jt1078/2026/06/22/VIN001/media-001.rtp" - ); - - TelemetrySnapshot snapshot = VehicleEventTelemetrySnapshotMapper.toSnapshot(event).orElseThrow(); - - assertThat(snapshot.eventType()).isEqualTo("MEDIA_META"); - assertThat(snapshot.rawArchiveUri()) - .isEqualTo("file:///archive/jt1078/2026/06/22/VIN001/media-001.rtp"); - assertThat(snapshot.field("media_archive_ref").orElseThrow().value()) - .isEqualTo("file:///archive/jt1078/2026/06/22/VIN001/media-001.rtp"); - } - - @Test - void derivesRawArchiveUriFromRawArchiveKeyMetadata() { - VehicleEvent.Heartbeat event = new VehicleEvent.Heartbeat( - "heartbeat-1", - "VIN001", - ProtocolId.JT808, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-5", - Map.of("rawArchiveKey", "2026/06/22/JT808/VIN001/raw-1.bin") - ); - - TelemetrySnapshot snapshot = VehicleEventTelemetrySnapshotMapper.toSnapshot(event).orElseThrow(); - - assertThat(snapshot.rawArchiveUri()) - .isEqualTo("archive://2026/06/22/JT808/VIN001/raw-1.bin"); - } - - @Test - void skipsRawArchiveBecauseItIsStoredByArchiveSink() { - VehicleEvent.RawArchive raw = new VehicleEvent.RawArchive( - "raw-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-3", - Map.of(), - 0x02, - 0, - new byte[]{0x23, 0x23} - ); - - assertThat(VehicleEventTelemetrySnapshotMapper.toSnapshot(raw)).isEmpty(); - } -} diff --git a/modules/core/ingest-codec-common/pom.xml b/modules/core/ingest-codec-common/pom.xml deleted file mode 100644 index 80d9e6a3..00000000 --- a/modules/core/ingest-codec-common/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - ingest-codec-common - ingest-codec-common - BCD / CRC / BCC / bit utils 等公共编解码工具。 - - - - io.netty - netty-buffer - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BccChecksum.java b/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BccChecksum.java deleted file mode 100644 index 5dc49274..00000000 --- a/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BccChecksum.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.lingniu.ingest.codec; - -/** BCC(异或校验)。GB/T 32960 与 JT/T 808 都使用此方式做帧尾校验。 */ -public final class BccChecksum { - - private BccChecksum() {} - - public static byte compute(byte[] data, int offset, int length) { - byte sum = 0; - for (int i = offset; i < offset + length; i++) { - sum ^= data[i]; - } - return sum; - } - - public static boolean verify(byte[] data, int offset, int length, byte expected) { - return compute(data, offset, length) == expected; - } -} diff --git a/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BcdCodec.java b/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BcdCodec.java deleted file mode 100644 index 32fdddbd..00000000 --- a/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BcdCodec.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.lingniu.ingest.codec; - -/** BCD (Binary-Coded Decimal) 编解码。JT/T 808 的手机号、时间戳常用。 */ -public final class BcdCodec { - - private BcdCodec() {} - - public static byte[] encode(String decimal) { - int len = (decimal.length() + 1) / 2; - byte[] out = new byte[len]; - int idx = decimal.length() % 2; - if (idx == 1) { - out[0] = (byte) Character.digit(decimal.charAt(0), 10); - } - for (int i = idx; i < decimal.length(); i += 2) { - int hi = Character.digit(decimal.charAt(i), 10); - int lo = Character.digit(decimal.charAt(i + 1), 10); - out[(i + idx) / 2] = (byte) ((hi << 4) | lo); - } - return out; - } - - public static String decode(byte[] bcd) { - StringBuilder sb = new StringBuilder(bcd.length * 2); - for (byte b : bcd) { - sb.append((b >> 4) & 0x0F).append(b & 0x0F); - } - return sb.toString(); - } -} diff --git a/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BitUtils.java b/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BitUtils.java deleted file mode 100644 index cf3034fd..00000000 --- a/modules/core/ingest-codec-common/src/main/java/com/lingniu/ingest/codec/BitUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lingniu.ingest.codec; - -/** 位运算工具:从字节数组读写无符号整数。 */ -public final class BitUtils { - - private BitUtils() {} - - public static int readUint8(byte[] buf, int offset) { - return buf[offset] & 0xFF; - } - - public static int readUint16BE(byte[] buf, int offset) { - return ((buf[offset] & 0xFF) << 8) | (buf[offset + 1] & 0xFF); - } - - public static long readUint32BE(byte[] buf, int offset) { - return ((long) (buf[offset] & 0xFF) << 24) - | ((long) (buf[offset + 1] & 0xFF) << 16) - | ((long) (buf[offset + 2] & 0xFF) << 8) - | (buf[offset + 3] & 0xFF); - } - - public static void writeUint16BE(byte[] buf, int offset, int value) { - buf[offset] = (byte) ((value >> 8) & 0xFF); - buf[offset + 1] = (byte) (value & 0xFF); - } - - public static void writeUint32BE(byte[] buf, int offset, long value) { - buf[offset] = (byte) ((value >> 24) & 0xFF); - buf[offset + 1] = (byte) ((value >> 16) & 0xFF); - buf[offset + 2] = (byte) ((value >> 8) & 0xFF); - buf[offset + 3] = (byte) (value & 0xFF); - } -} diff --git a/modules/core/ingest-codec-common/src/test/java/com/lingniu/ingest/codec/BcdCodecTest.java b/modules/core/ingest-codec-common/src/test/java/com/lingniu/ingest/codec/BcdCodecTest.java deleted file mode 100644 index 7ee9f94f..00000000 --- a/modules/core/ingest-codec-common/src/test/java/com/lingniu/ingest/codec/BcdCodecTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.lingniu.ingest.codec; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class BcdCodecTest { - - @Test - void roundTripEvenLength() { - // 偶数位直接等值 - byte[] bcd = BcdCodec.encode("1380013800"); - assertThat(BcdCodec.decode(bcd)).isEqualTo("1380013800"); - } - - @Test - void roundTripOddLengthPadsLeadingZero() { - // 奇数位会在高位补 0 - byte[] bcd = BcdCodec.encode("13800138000"); - assertThat(BcdCodec.decode(bcd)).isEqualTo("013800138000"); - } - - @Test - void checksumMatches() { - byte[] data = {0x01, 0x02, 0x03, 0x04}; - assertThat(BccChecksum.compute(data, 0, data.length)).isEqualTo((byte) 0x04); - } -} diff --git a/modules/core/ingest-core/pom.xml b/modules/core/ingest-core/pom.xml deleted file mode 100644 index 00dbde8f..00000000 --- a/modules/core/ingest-core/pom.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - ingest-core - ingest-core - Dispatcher / Interceptor Chain / Disruptor Event Bus / 注解扫描与自动注册。 - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - ingest-codec-common - - - com.lingniu.ingest - ingest-facts - - - org.springframework.boot - spring-boot-starter - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - org.springframework.boot - spring-boot-autoconfigure - - - org.springframework.boot - spring-boot-configuration-processor - true - - - com.lmax - disruptor - - - com.github.ben-manes.caffeine - caffeine - - - io.github.resilience4j - resilience4j-ratelimiter - - - io.github.resilience4j - resilience4j-circuitbreaker - - - io.netty - netty-all - - - org.springframework.boot - spring-boot-starter-test - test - - - diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/AsyncBatchExecutor.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/AsyncBatchExecutor.java deleted file mode 100644 index 0399e506..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/AsyncBatchExecutor.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.lingniu.ingest.core.concurrency; - -import com.lingniu.ingest.api.annotation.AsyncBatch; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.core.dispatcher.HandlerDefinition; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.UnaryOperator; - -/** - * {@link AsyncBatch} 注解的执行器:把 Handler 方法从"单条调用"变成"批量调用"。 - * - *

每个 Handler 方法对应一个 {@link Batcher},背后是一个固定容量的 {@link BlockingQueue} - * 和 {@link AsyncBatch#poolSize()} 条守护虚拟线程。批量触发条件: - *

    - *
  • 累积 {@link AsyncBatch#size()} 条立即 flush - *
  • 从第一条入队起等待 {@link AsyncBatch#waitMs()} 毫秒后强制 flush - *
- * - *

目标 Handler 方法签名约定: - *

{@code
- *   @MessageMapping(...)
- *   @AsyncBatch(size = 4000, waitMs = 1000, poolSize = 2)
- *   public List onBatch(List batch) { ... }
- * }
- * - *

flush 产出的事件由构造器注入的 {@code eventPublisher} 异步投递(通常是 - * {@link DisruptorEventBus#publish}),不阻塞 Netty EventLoop。 - */ -public final class AsyncBatchExecutor implements AutoCloseable { - - private static final Logger log = LoggerFactory.getLogger(AsyncBatchExecutor.class); - - private final Consumer eventPublisher; - private final ConcurrentMap batchers = new ConcurrentHashMap<>(); - - public AsyncBatchExecutor(Consumer eventPublisher) { - this.eventPublisher = eventPublisher; - } - - /** 供 Dispatcher 调用:把单条消息交给目标 Handler 的 batcher。 */ - public void submit(HandlerDefinition def, Object message) { - Batcher b = batchers.computeIfAbsent(def.method(), m -> new Batcher(def, eventPublisher)); - b.offer(new BatchItem(message, UnaryOperator.identity(), false)); - } - - public void submit(HandlerDefinition def, Object message, UnaryOperator eventTransformer) { - Batcher b = batchers.computeIfAbsent(def.method(), m -> new Batcher(def, eventPublisher)); - b.offer(new BatchItem(message, eventTransformer == null ? UnaryOperator.identity() : eventTransformer, true)); - } - - @Override - public void close() { - batchers.values().forEach(Batcher::close); - batchers.clear(); - log.info("AsyncBatchExecutor closed"); - } - - // ===== internals ===== - - private static final class Batcher implements AutoCloseable { - private final HandlerDefinition def; - private final Consumer publisher; - private final int batchSize; - private final long waitMs; - private final BlockingQueue queue; - private final Thread[] workers; - private volatile boolean running = true; - - Batcher(HandlerDefinition def, Consumer publisher) { - AsyncBatch cfg = def.asyncBatch(); - this.def = def; - this.publisher = publisher; - this.batchSize = Math.max(1, cfg.size()); - this.waitMs = Math.max(1, cfg.waitMs()); - this.queue = new ArrayBlockingQueue<>(Math.max(batchSize * 4, 16)); - int pool = Math.max(1, cfg.poolSize()); - this.workers = new Thread[pool]; - for (int i = 0; i < pool; i++) { - Thread t = Thread.ofVirtual() - .name("batcher-" + def.method().getName() + "-" + i) - .unstarted(this::loop); - workers[i] = t; - t.start(); - } - log.info("batcher started method={} size={} waitMs={} pool={}", - def.method().getName(), batchSize, waitMs, pool); - } - - void offer(BatchItem item) { - try { - if (!queue.offer(item, 100, TimeUnit.MILLISECONDS)) { - log.warn("batcher queue full, dropping item for {}", def.method().getName()); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private void loop() { - while (running) { - try { - BatchItem first = queue.poll(200, TimeUnit.MILLISECONDS); - if (first == null) continue; - List buf = new ArrayList<>(batchSize); - buf.add(first); - long deadlineNanos = System.nanoTime() + waitMs * 1_000_000L; - while (buf.size() < batchSize) { - long remain = deadlineNanos - System.nanoTime(); - if (remain <= 0) break; - BatchItem next = queue.poll(remain, TimeUnit.NANOSECONDS); - if (next == null) break; - buf.add(next); - } - flush(buf); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return; - } catch (Exception e) { - log.error("batcher loop error method={}", def.method().getName(), e); - } - } - } - - private void flush(List buf) { - if (requiresPerItemTransform(buf)) { - // rawArchiveUri/rawSize 等元数据是每帧唯一的。为了不把第一条帧的元数据错误复制给整批事件, - // 一旦存在逐条 transformer,就按单条调用 Handler,再逐条发布。 - for (BatchItem item : buf) { - flushTransformedItem(item); - } - return; - } - - publishEvents(invoke(buf.stream().map(BatchItem::message).toList()), UnaryOperator.identity()); - } - - private boolean requiresPerItemTransform(List buf) { - for (BatchItem item : buf) { - if (item.transformRequired()) { - return true; - } - } - return false; - } - - private void flushTransformedItem(BatchItem item) { - publishEvents(invoke(List.of(item.message())), item.eventTransformer()); - } - - @SuppressWarnings("unchecked") - private List invoke(List messages) { - List events; - try { - // Handler 仍然只暴露一个批量签名;是否批满或超时由 Batcher 统一处理。 - Object result = def.method().invoke(def.bean(), messages); - events = switch (result) { - case null -> List.of(); - case List list -> (List) list; - case VehicleEvent e -> List.of(e); - default -> throw new IllegalStateException( - "@AsyncBatch method must return List: " + def.method()); - }; - } catch (InvocationTargetException e) { - log.error("batcher invoke failed method={}", def.method().getName(), e.getCause()); - return List.of(); - } catch (IllegalAccessException e) { - log.error("batcher access denied method={}", def.method().getName(), e); - return List.of(); - } - return events; - } - - private void publishEvents(List events, UnaryOperator eventTransformer) { - for (VehicleEvent e : events) { - try { - publisher.accept(eventTransformer.apply(e)); - } catch (Exception ex) { - log.warn("batcher publish failed eventId={}", e.eventId(), ex); - } - } - } - - @Override - public void close() { - running = false; - for (Thread t : workers) t.interrupt(); - } - } - - private record BatchItem(Object message, - UnaryOperator eventTransformer, - boolean transformRequired) { - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/DisruptorEventBus.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/DisruptorEventBus.java deleted file mode 100644 index 8e084cd6..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/DisruptorEventBus.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.lingniu.ingest.core.concurrency; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; -import com.lmax.disruptor.BlockingWaitStrategy; -import com.lmax.disruptor.BusySpinWaitStrategy; -import com.lmax.disruptor.EventHandler; -import com.lmax.disruptor.SleepingWaitStrategy; -import com.lmax.disruptor.WaitStrategy; -import com.lmax.disruptor.YieldingWaitStrategy; -import com.lmax.disruptor.dsl.Disruptor; -import com.lmax.disruptor.dsl.ProducerType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicLong; - -/** - * 基于 Disruptor 的事件总线:Dispatcher 投递事件 → RingBuffer → Sink 扇出。 - * - *

使用虚拟线程作为 Sink 消费线程,避免阻塞 IO 占用平台线程。 - * 单 VIN 有序性由上游 Netty + 分区投递保证,RingBuffer 不做 per-vin 排序。 - */ -public final class DisruptorEventBus implements AutoCloseable { - - private static final Logger log = LoggerFactory.getLogger(DisruptorEventBus.class); - - private final Disruptor disruptor; - private final List sinks; - private final ExecutorService awaitExecutor; - private final AtomicLong published = new AtomicLong(); - private final AtomicLong failed = new AtomicLong(); - - public DisruptorEventBus(int ringBufferSize, String waitStrategyName, List sinks) { - this.sinks = List.copyOf(sinks); - this.awaitExecutor = Executors.newVirtualThreadPerTaskExecutor(); - ThreadFactory tf = Thread.ofVirtual().name("ingest-bus-", 0).factory(); - this.disruptor = new Disruptor<>( - VehicleEventSlot::new, - ringBufferSize, - tf, - ProducerType.MULTI, - waitStrategy(waitStrategyName)); - - EventHandler[] handlers = this.sinks.stream() - .map(this::toHandler) - .toArray(EventHandler[]::new); - // 每个 sink 一个独立 handler,事件按扇出模式同时写 Kafka、archive、历史索引等目标。 - disruptor.handleEventsWith(handlers); - disruptor.start(); - log.info("DisruptorEventBus started ringBuffer={} wait={} sinks={}", - ringBufferSize, waitStrategyName, this.sinks.size()); - } - - public void publish(VehicleEvent event) { - disruptor.getRingBuffer().publishEvent((slot, seq, e) -> slot.event = e, event); - published.incrementAndGet(); - } - - public CompletableFuture publishAndAwait(VehicleEvent event, String requiredSinkName) { - String requiredSink = Objects.requireNonNull(requiredSinkName, "requiredSinkName"); - published.incrementAndGet(); - List acceptingSinks = sinks.stream() - .filter(sink -> sink.accepts(event)) - .toList(); - List requiredSinks = acceptingSinks.stream() - .filter(sink -> requiredSink.equals(sink.name())) - .toList(); - if (requiredSinks.isEmpty()) { - return CompletableFuture.failedFuture(new IllegalStateException( - "required sink '" + requiredSink + "' did not accept event " + event.eventId())); - } - - List> requiredFutures = new ArrayList<>(); - for (EventSink sink : acceptingSinks) { - CompletableFuture future = CompletableFuture - .supplyAsync(() -> publishToSinkAndTrack(sink, event), awaitExecutor) - .thenCompose(f -> f); - if (requiredSink.equals(sink.name())) { - requiredFutures.add(future); - } - } - return CompletableFuture.allOf(requiredFutures.toArray(CompletableFuture[]::new)); - } - - @Override - public void close() { - disruptor.shutdown(); - awaitExecutor.shutdown(); - log.info("DisruptorEventBus stopped published={} failed={}", published.get(), failed.get()); - } - - private EventHandler toHandler(EventSink sink) { - return (slot, seq, endOfBatch) -> { - VehicleEvent e = slot.event; - if (e == null || !sink.accepts(e)) return; - try { - publishToSinkAndTrack(sink, e); - } catch (Throwable t) { - failed.incrementAndGet(); - log.warn("sink {} publish failed", sink.name(), t); - } - }; - } - - private CompletableFuture publishToSinkAndTrack(EventSink sink, VehicleEvent event) { - try { - return sink.publish(event).whenComplete((ignored, ex) -> { - if (ex == null) return; - // sink 异步失败只计数和打日志;await 调用方仍会收到异常并决定是否 ACK。 - failed.incrementAndGet(); - log.warn("sink {} publish failed", sink.name(), ex); - }); - } catch (Throwable t) { - failed.incrementAndGet(); - log.warn("sink {} publish failed", sink.name(), t); - return CompletableFuture.failedFuture(t); - } - } - - private static WaitStrategy waitStrategy(String name) { - return switch (name == null ? "yielding" : name.toLowerCase()) { - case "blocking" -> new BlockingWaitStrategy(); - case "sleeping" -> new SleepingWaitStrategy(); - case "busy-spin" -> new BusySpinWaitStrategy(); - default -> new YieldingWaitStrategy(); - }; - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/VehicleEventSlot.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/VehicleEventSlot.java deleted file mode 100644 index 5a3b0ec2..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/concurrency/VehicleEventSlot.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.lingniu.ingest.core.concurrency; - -import com.lingniu.ingest.api.event.VehicleEvent; - -/** Disruptor RingBuffer 槽位:持有可变引用,避免每次发布都分配新对象。 */ -public final class VehicleEventSlot { - public VehicleEvent event; - - public void clear() { - this.event = null; - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreAutoConfiguration.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreAutoConfiguration.java deleted file mode 100644 index debe4687..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreAutoConfiguration.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.lingniu.ingest.core.config; - -import com.lingniu.ingest.api.pipeline.IngestInterceptor; -import com.lingniu.ingest.api.sink.EventSink; -import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor; -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.dispatcher.AnnotationHandlerBeanPostProcessor; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.core.dispatcher.HandlerInvoker; -import com.lingniu.ingest.core.dispatcher.HandlerRegistry; -import com.lingniu.ingest.core.pipeline.InterceptorChain; -import com.lingniu.ingest.core.pipeline.builtin.DedupInterceptor; -import com.lingniu.ingest.core.pipeline.builtin.RateLimitInterceptor; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -import java.util.List; - -/** - * ingest-core 的自动装配入口。所有 Bean 都是 {@code @ConditionalOnMissingBean},便于下游替换。 - * - *

生产链路顺序:入口收到 {@code RawFrame} → {@link InterceptorChain} 做准入 → - * {@link Dispatcher} 定位协议 Handler → Handler 产出 {@code VehicleEvent} → - * {@link DisruptorEventBus} 扇出到 Archive、Kafka、历史索引等 {@code EventSink}。 - */ -@AutoConfiguration -@EnableConfigurationProperties(IngestCoreProperties.class) -public class IngestCoreAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public HandlerRegistry handlerRegistry() { - return new HandlerRegistry(); - } - - @Bean - @ConditionalOnMissingBean - public HandlerInvoker handlerInvoker() { - return new HandlerInvoker(); - } - - @Bean - public static AnnotationHandlerBeanPostProcessor annotationHandlerBeanPostProcessor() { - return new AnnotationHandlerBeanPostProcessor(); - } - - @Bean - @ConditionalOnProperty(prefix = "lingniu.ingest.pipeline.dedup", name = "enabled", havingValue = "true", matchIfMissing = true) - public DedupInterceptor dedupInterceptor(IngestCoreProperties props) { - // 去重尽量放在限流前,重复包不应消耗后续每 VIN QPS 配额。 - return new DedupInterceptor(props.getDedup().getCacheSize(), props.getDedup().getTtlSeconds()); - } - - @Bean - public RateLimitInterceptor rateLimitInterceptor(IngestCoreProperties props) { - return new RateLimitInterceptor(props.getRateLimit().getPerVinQps(), props.getRateLimit().getMaxVins()); - } - - @Bean - @ConditionalOnMissingBean - public InterceptorChain interceptorChain(List interceptors) { - return new InterceptorChain(interceptors); - } - - @Bean(destroyMethod = "close") - @ConditionalOnMissingBean - public DisruptorEventBus disruptorEventBus(IngestCoreProperties props, List sinks) { - return new DisruptorEventBus( - props.getDisruptor().getRingBufferSize(), - props.getDisruptor().getWaitStrategy(), - sinks); - } - - @Bean(destroyMethod = "close") - @ConditionalOnMissingBean - public AsyncBatchExecutor asyncBatchExecutor(DisruptorEventBus eventBus) { - return new AsyncBatchExecutor(eventBus::publish); - } - - @Bean - @ConditionalOnMissingBean - public Dispatcher dispatcher(HandlerRegistry registry, - InterceptorChain chain, - HandlerInvoker invoker, - DisruptorEventBus eventBus, - AsyncBatchExecutor batchExecutor) { - return new Dispatcher(registry, chain, invoker, eventBus, batchExecutor); - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreProperties.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreProperties.java deleted file mode 100644 index dfec3e99..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/config/IngestCoreProperties.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.lingniu.ingest.core.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "lingniu.ingest.pipeline") -public class IngestCoreProperties { - - private Disruptor disruptor = new Disruptor(); - private Dedup dedup = new Dedup(); - private RateLimit rateLimit = new RateLimit(); - - public Disruptor getDisruptor() { return disruptor; } - public void setDisruptor(Disruptor disruptor) { this.disruptor = disruptor; } - - public Dedup getDedup() { return dedup; } - public void setDedup(Dedup dedup) { this.dedup = dedup; } - - public RateLimit getRateLimit() { return rateLimit; } - public void setRateLimit(RateLimit rateLimit) { this.rateLimit = rateLimit; } - - public static class Disruptor { - /** 事件总线 ring buffer 大小;生产环境应保持 2 的幂,避免 Disruptor 初始化失败。 */ - private int ringBufferSize = 131072; - /** waiting 策略直接影响延迟和 CPU 占用,默认 yielding 偏低延迟。 */ - private String waitStrategy = "yielding"; - private String producerType = "multi"; - - public int getRingBufferSize() { return ringBufferSize; } - public void setRingBufferSize(int ringBufferSize) { this.ringBufferSize = ringBufferSize; } - public String getWaitStrategy() { return waitStrategy; } - public void setWaitStrategy(String waitStrategy) { this.waitStrategy = waitStrategy; } - public String getProducerType() { return producerType; } - public void setProducerType(String producerType) { this.producerType = producerType; } - } - - public static class Dedup { - private boolean enabled = true; - /** 单实例缓存的幂等键数量上限,按 VIN 数和重发窗口估算。 */ - private int cacheSize = 200000; - /** 幂等键保留窗口;过短会放过重发包,过长会增加内存压力。 */ - private long ttlSeconds = 600; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public int getCacheSize() { return cacheSize; } - public void setCacheSize(int cacheSize) { this.cacheSize = cacheSize; } - public long getTtlSeconds() { return ttlSeconds; } - public void setTtlSeconds(long ttlSeconds) { this.ttlSeconds = ttlSeconds; } - } - - public static class RateLimit { - /** 每 VIN 每秒进入 Handler 的最大帧数,默认按 32960 高频实时上报预留。 */ - private int perVinQps = 50; - /** VIN limiter 缓存上限;覆盖活跃车辆数即可,淘汰后会重新创建 limiter。 */ - private int maxVins = 100000; - - public int getPerVinQps() { return perVinQps; } - public void setPerVinQps(int perVinQps) { this.perVinQps = perVinQps; } - public int getMaxVins() { return maxVins; } - public void setMaxVins(int maxVins) { this.maxVins = maxVins; } - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/AnnotationHandlerBeanPostProcessor.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/AnnotationHandlerBeanPostProcessor.java deleted file mode 100644 index 0812558e..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/AnnotationHandlerBeanPostProcessor.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.lingniu.ingest.api.annotation.AsyncBatch; -import com.lingniu.ingest.api.annotation.IdempotentKey; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.annotation.RateLimited; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.core.annotation.AnnotatedElementUtils; - -import java.lang.reflect.Method; - -/** - * Spring BeanPostProcessor:扫描带 {@link ProtocolHandler} 注解的 Bean, - * 把每个带 {@link MessageMapping} 的方法注册到 {@link HandlerRegistry}。 - * - *

替代旧代码里遍布的 {@code if (msgId == 0x0100) ... else if (msgId == 0x0102) ...} 风格。 - */ -public class AnnotationHandlerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { - - private static final Logger log = LoggerFactory.getLogger(AnnotationHandlerBeanPostProcessor.class); - - private HandlerRegistry registry; - private BeanFactory beanFactory; - - public AnnotationHandlerBeanPostProcessor() { - } - - public AnnotationHandlerBeanPostProcessor(HandlerRegistry registry) { - this.registry = registry; - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - Class type = bean.getClass(); - ProtocolHandler classAnno = AnnotatedElementUtils.findMergedAnnotation(type, ProtocolHandler.class); - if (classAnno == null) return bean; - - for (Method method : type.getMethods()) { - MessageMapping mapping = AnnotatedElementUtils.findMergedAnnotation(method, MessageMapping.class); - if (mapping == null) continue; - - Class paramType = method.getParameterCount() > 0 ? method.getParameterTypes()[0] : Object.class; - int[] commands = mapping.command().length == 0 ? new int[]{0} : mapping.command(); - int[] infoTypes = mapping.infoType().length == 0 ? new int[]{0} : mapping.infoType(); - - // 这些注解只声明策略,实际限流、幂等、异步批处理由 Dispatcher/Interceptor 层解释执行。 - RateLimited rl = AnnotatedElementUtils.findMergedAnnotation(method, RateLimited.class); - IdempotentKey ik = AnnotatedElementUtils.findMergedAnnotation(method, IdempotentKey.class); - AsyncBatch ab = AnnotatedElementUtils.findMergedAnnotation(method, AsyncBatch.class); - - for (int cmd : commands) { - for (int info : infoTypes) { - HandlerDefinition def = new HandlerDefinition( - classAnno.protocol(), cmd, info, mapping.desc(), - bean, method, paramType, rl, ik, ab); - registry().register(def); - log.info("registered handler {} protocol={} command=0x{} info=0x{}", - type.getSimpleName() + "#" + method.getName(), - classAnno.protocol(), Integer.toHexString(cmd), Integer.toHexString(info)); - } - } - } - return bean; - } - - private HandlerRegistry registry() { - if (registry == null) { - if (beanFactory == null) { - throw new IllegalStateException("BeanFactory must be set before registering protocol handlers"); - } - registry = beanFactory.getBean(HandlerRegistry.class); - } - return registry; - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/Dispatcher.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/Dispatcher.java deleted file mode 100644 index e066ee67..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/Dispatcher.java +++ /dev/null @@ -1,304 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.lingniu.ingest.api.event.RawArchiveKeys; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor; -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.pipeline.InterceptorChain; -import com.lingniu.ingest.facts.RawFrameIdentity; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -/** - * 核心分发器:RawFrame → 拦截链 → Handler → 事件 → Disruptor EventBus。 - * - *

所有 Inbound Adapter(Netty / MQTT / PushClient)都调用 {@link #dispatch(RawFrame)}, - * 协议差异在这里被彻底抹平。 - * - *

对于带 {@code @AsyncBatch} 的 Handler,Dispatcher 不直接反射调用,而是把消息交给 - * {@link AsyncBatchExecutor} 进行聚合 → 批量调用 → 异步发布事件。 - */ -public final class Dispatcher { - - private static final Logger log = LoggerFactory.getLogger(Dispatcher.class); - private static final AtomicLong RAW_ARCHIVE_SEQUENCE = new AtomicLong(); - private static final String REQUIRED_DURABLE_SINK = "kafka"; - private static final ObjectMapper RAW_ARCHIVE_JSON = JsonMapper.builder() - .findAndAddModules() - .build(); - - private final HandlerRegistry registry; - private final InterceptorChain interceptors; - private final HandlerInvoker invoker; - private final DisruptorEventBus eventBus; - private final AsyncBatchExecutor batchExecutor; - - public Dispatcher(HandlerRegistry registry, - InterceptorChain interceptors, - HandlerInvoker invoker, - DisruptorEventBus eventBus, - AsyncBatchExecutor batchExecutor) { - this.registry = registry; - this.interceptors = interceptors; - this.invoker = invoker; - this.eventBus = eventBus; - this.batchExecutor = batchExecutor; - } - - public void dispatch(RawFrame frame) { - IngestContext ctx = new IngestContext(UUID.randomUUID().toString()); - try { - DispatchPlan plan = mapFrameToEvents(frame, ctx, false); - for (VehicleEvent e : plan.events()) { - eventBus.publish(e); - } - if (plan.failure() != null) { - throw plan.failure(); - } - } catch (Throwable t) { - log.error("dispatch failure traceId={}", ctx.traceId(), t); - interceptors.onError(t, ctx); - } - } - - public CompletableFuture dispatchAndAwait(RawFrame frame) { - IngestContext ctx = new IngestContext(UUID.randomUUID().toString()); - try { - DispatchPlan plan = mapFrameToEvents(frame, ctx, true); - if (plan.events().isEmpty() && plan.failure() == null) { - return CompletableFuture.failedFuture(new IllegalStateException( - "awaited dispatch produced no events; required sink '" + REQUIRED_DURABLE_SINK + "' was not reached")); - } - CompletableFuture[] futures = plan.events().stream() - .map(event -> eventBus.publishAndAwait(event, REQUIRED_DURABLE_SINK)) - .toArray(CompletableFuture[]::new); - CompletableFuture boundary = CompletableFuture.allOf(futures); - if (plan.failure() == null) { - return boundary; - } - return boundary.handle((ignored, publishFailure) -> { - log.error("dispatch failure traceId={}", ctx.traceId(), plan.failure()); - interceptors.onError(plan.failure(), ctx); - if (publishFailure != null) { - throw new CompletionException(publishFailure); - } - throw new CompletionException(plan.failure()); - }); - } catch (Throwable t) { - log.error("dispatch failure traceId={}", ctx.traceId(), t); - interceptors.onError(t, ctx); - return CompletableFuture.failedFuture(t); - } - } - - public CompletableFuture dispatchAndAwait(RawFrame frame, Duration timeout) { - CompletableFuture future = dispatchAndAwait(frame); - if (timeout == null || timeout.isZero() || timeout.isNegative()) { - return future; - } - return future.orTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS); - } - - private DispatchPlan mapFrameToEvents(RawFrame frame, IngestContext ctx, boolean awaitDurability) { - List out = new ArrayList<>(); - // 在 interceptor 之前发 RawArchive,保证原始字节被无条件落盘(dedup/rate-limit - // 不会过滤它),满足"原始可回放"目标。archive 的写盘靠下游 Sink 消费。 - RawArchiveLookup rawArchive = appendRawArchive(frame, ctx, out); - - try { - if (!interceptors.before(frame, ctx)) { - log.debug("frame aborted: {}", ctx.abortReason()); - return new DispatchPlan(out, null); - } - - List handlers = registry.resolve( - frame.protocolId(), frame.command(), frame.infoType()); - if (handlers.isEmpty()) { - log.debug("no handler for {} cmd=0x{} info=0x{}", - frame.protocolId(), Integer.toHexString(frame.command()), - Integer.toHexString(frame.infoType())); - return new DispatchPlan(out, null); - } - - for (HandlerDefinition def : handlers) { - if (def.asyncBatch() != null) { - if (awaitDurability) { - throw new AwaitedAsyncBatchUnsupportedException( - "@AsyncBatch handler cannot be used with dispatchAndAwait until batch completion is awaited: " - + def.method()); - } - batchExecutor.submit(def, frame.payload(), event -> enrichWithRawArchive(event, rawArchive)); - continue; - } - List events = invoker.invoke(def, frame, ctx); - for (VehicleEvent e : events) { - e = enrichWithRawArchive(e, rawArchive); - interceptors.after(e, ctx); - out.add(e); - } - } - return new DispatchPlan(out, null); - } catch (Throwable t) { - if (t instanceof AwaitedAsyncBatchUnsupportedException) { - return new DispatchPlan(List.of(), t); - } - return new DispatchPlan(out, t); - } - } - - /** - * 从 {@link RawFrame} 构造一条 {@link VehicleEvent.RawArchive} 发到 EventBus。 - * 仅在 {@code rawBytes} 非空时发——有些入站适配器(未来)可能只传解析后的对象。 - * - *

VIN 取自 sourceMeta 里的 {@code vin} key(由各入站适配器负责填充);缺失时 - * 留空字符串,archive sink 会用 "unknown-vin" 占位保证 key 可解析。 - */ - private RawArchiveLookup appendRawArchive(RawFrame frame, IngestContext ctx, List out) { - byte[] bytes = frame.rawBytes(); - if (bytes == null || bytes.length == 0) return RawArchiveLookup.empty(); - - Map meta = frame.sourceMeta() == null - ? Map.of() - : new LinkedHashMap<>(frame.sourceMeta()); - String parsedJson = parsedJson(frame.payload(), meta.remove(RawArchiveKeys.META_PARSED_JSON)); - String vin = meta.getOrDefault("vin", ""); - String phone = meta.getOrDefault("phone", ""); - Instant ingestTime = frame.receivedAt() != null ? frame.receivedAt() : Instant.now(); - String eventId = nextRawArchiveEventId(ingestTime); - String key = RawArchiveKeys.key(ingestTime, frame.protocolId(), vin, eventId); - RawFrameIdentity identity = RawFrameIdentity.derive( - frame.protocolId(), vin, phone, eventId, frame.command(), frame.infoType(), ingestTime, bytes); - Map archiveMeta = addRawArchiveMetadata(meta, eventId, key, identity.frameId()); - - VehicleEvent.RawArchive raw = new VehicleEvent.RawArchive( - eventId, - vin, - frame.protocolId(), - ingestTime, - ingestTime, - ctx.traceId(), - archiveMeta, - frame.command(), - frame.infoType(), - bytes, - parsedJson); - out.add(raw); - return new RawArchiveLookup(eventId, key, RawArchiveKeys.logicalUri(key), identity.frameId()); - } - - private static String parsedJson(Object payload, String explicitParsedJson) { - if (explicitParsedJson != null && !explicitParsedJson.isBlank()) { - return explicitParsedJson; - } - if (payload == null) { - return "{}"; - } - try { - return RAW_ARCHIVE_JSON.writeValueAsString(payload); - } catch (JsonProcessingException ex) { - return "{\"serializationError\":\"" + escapeJson(ex.getMessage()) + "\",\"payloadType\":\"" - + escapeJson(payload.getClass().getName()) + "\"}"; - } - } - - private static String escapeJson(String value) { - if (value == null) { - return ""; - } - return value.replace("\\", "\\\\").replace("\"", "\\\""); - } - - private static String nextRawArchiveEventId(Instant ingestTime) { - // 文件名需要可排序且单进程内唯一:毫秒时间放大到微秒量级,再用 AtomicLong 递增兜底。 - long base = (ingestTime == null ? Instant.now() : ingestTime).toEpochMilli() * 1000L; - long next = RAW_ARCHIVE_SEQUENCE.updateAndGet(previous -> Math.max(previous + 1, base)); - return Long.toString(next); - } - - private static Map addRawArchiveMetadata(Map metadata, - String eventId, - String key, - String frameId) { - Map out = new LinkedHashMap<>(metadata == null ? Map.of() : metadata); - out.putIfAbsent(RawArchiveKeys.META_EVENT_ID, eventId); - out.putIfAbsent(RawArchiveKeys.META_KEY, key); - out.putIfAbsent(RawArchiveKeys.META_URI, RawArchiveKeys.logicalUri(key)); - if (frameId != null && !frameId.isBlank()) { - out.putIfAbsent("rawFrameId", frameId); - out.putIfAbsent("frame_id", frameId); - } - return Map.copyOf(out); - } - - private static VehicleEvent enrichWithRawArchive(VehicleEvent event, RawArchiveLookup rawArchive) { - if (event == null || rawArchive.isEmpty() || event instanceof VehicleEvent.RawArchive) { - return event; - } - // 派生事件携带 rawArchiveUri,便于后续服务从 Kafka 事件回查原始报文。 - Map metadata = addRawArchiveMetadata( - event.metadata(), rawArchive.eventId(), rawArchive.key(), rawArchive.frameId()); - return switch (event) { - case VehicleEvent.Realtime e -> new VehicleEvent.Realtime( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata, e.payload()); - case VehicleEvent.Location e -> new VehicleEvent.Location( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata, e.payload()); - case VehicleEvent.Alarm e -> new VehicleEvent.Alarm( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata, e.payload()); - case VehicleEvent.Login e -> new VehicleEvent.Login( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata, e.iccid(), e.protocolVersion()); - case VehicleEvent.Logout e -> new VehicleEvent.Logout( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata); - case VehicleEvent.Heartbeat e -> new VehicleEvent.Heartbeat( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata); - case VehicleEvent.MediaMeta e -> new VehicleEvent.MediaMeta( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata, e.mediaId(), e.mediaType(), e.sizeBytes(), e.archiveRef()); - case VehicleEvent.Passthrough e -> new VehicleEvent.Passthrough( - e.eventId(), e.vin(), e.source(), e.eventTime(), e.ingestTime(), - e.traceId(), metadata, e.passthroughType(), e.data()); - case VehicleEvent.RawArchive e -> e; - }; - } - - private record RawArchiveLookup(String eventId, String key, String uri, String frameId) { - private static RawArchiveLookup empty() { - return new RawArchiveLookup("", "", "", ""); - } - - private boolean isEmpty() { - return key == null || key.isBlank(); - } - } - - private record DispatchPlan(List events, Throwable failure) { - } - - private static final class AwaitedAsyncBatchUnsupportedException extends IllegalStateException { - private AwaitedAsyncBatchUnsupportedException(String message) { - super(message); - } - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerDefinition.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerDefinition.java deleted file mode 100644 index 71bd179a..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerDefinition.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.AsyncBatch; -import com.lingniu.ingest.api.annotation.IdempotentKey; -import com.lingniu.ingest.api.annotation.RateLimited; - -import java.lang.reflect.Method; - -/** - * 一个被注解扫描到的 Handler 方法的静态描述。不可变。 - */ -public record HandlerDefinition( - ProtocolId protocol, - int command, - int infoType, - String desc, - Object bean, - Method method, - Class parameterType, - RateLimited rateLimited, - IdempotentKey idempotentKey, - AsyncBatch asyncBatch -) { - public boolean matches(ProtocolId p, int cmd, int info) { - if (p != protocol) return false; - if (command != 0 && command != cmd) return false; - if (infoType != 0 && infoType != info) return false; - return true; - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerInvoker.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerInvoker.java deleted file mode 100644 index f6ec962c..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerInvoker.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.RawFrame; - -import java.lang.reflect.InvocationTargetException; -import java.util.Collections; -import java.util.List; - -/** - * 反射调用 Handler。单独抽出是为了后续可插拔:未来可替换为 MethodHandle 或字节码生成。 - */ -public class HandlerInvoker { - - @SuppressWarnings("unchecked") - public List invoke(HandlerDefinition def, RawFrame frame, IngestContext ctx) { - try { - Object result = def.method().invoke(def.bean(), frame.payload()); - return switch (result) { - case null -> Collections.emptyList(); - case VehicleEvent e -> List.of(e); - // Handler 可以一次产出多个领域事件,例如 GB32960 0x02 同时产出 Realtime/Location/Alarm。 - case List list -> (List) list; - default -> throw new IllegalStateException( - "Handler return type must be VehicleEvent or List: " + def.method()); - }; - } catch (InvocationTargetException e) { - throw new RuntimeException("Handler threw exception: " + def.method(), e.getCause()); - } catch (IllegalAccessException e) { - throw new RuntimeException("Handler not accessible: " + def.method(), e); - } - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerRegistry.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerRegistry.java deleted file mode 100644 index 90a7d158..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/dispatcher/HandlerRegistry.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.lingniu.ingest.api.ProtocolId; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Handler 注册中心。按 {@code (ProtocolId, command)} 索引,O(1) 查找;同 key 可注册多个 Handler,由 - * {@link HandlerDefinition#matches} 进一步精确匹配 {@code infoType}。 - */ -public final class HandlerRegistry { - - private final Map> byRoute = new ConcurrentHashMap<>(); - private final List wildcardHandlers = Collections.synchronizedList(new ArrayList<>()); - - public void register(HandlerDefinition def) { - if (def.command() == 0) { - wildcardHandlers.add(def); - return; - } - byRoute.computeIfAbsent(new RoutingKey(def.protocol(), def.command()), - k -> Collections.synchronizedList(new ArrayList<>())).add(def); - } - - public List resolve(ProtocolId protocol, int command, int infoType) { - List exact = byRoute.get(new RoutingKey(protocol, command)); - List result = new ArrayList<>(); - if (exact != null) { - for (HandlerDefinition d : exact) { - if (d.matches(protocol, command, infoType)) result.add(d); - } - } - if (!result.isEmpty()) { - return result; - } - for (HandlerDefinition d : wildcardHandlers) { - if (d.matches(protocol, command, infoType)) result.add(d); - } - return result; - } - - public int size() { - return byRoute.values().stream().mapToInt(List::size).sum() + wildcardHandlers.size(); - } - - private record RoutingKey(ProtocolId protocol, int command) {} -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/InterceptorChain.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/InterceptorChain.java deleted file mode 100644 index f3109787..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/InterceptorChain.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.lingniu.ingest.core.pipeline; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.IngestInterceptor; -import com.lingniu.ingest.api.pipeline.RawFrame; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; - -import java.util.ArrayList; -import java.util.List; - -/** - * 顺序执行的拦截器链,通过 Spring {@code @Order} 或 {@code Ordered} 排序。 - * - *

{@link #before(RawFrame, IngestContext)} 是接入链路的准入闸门:任何拦截器返回 - * {@code false} 或调用 {@link IngestContext#abort(String)} 都会停止后续协议解析/Handler 调用。 - * {@link #after(VehicleEvent, IngestContext)} 与 {@link #onError(Throwable, IngestContext)} - * 则保持广播语义,让指标、审计等横切逻辑都能观察到同一条处理结果。 - */ -public final class InterceptorChain { - - private final List interceptors; - - public InterceptorChain(List interceptors) { - List sorted = new ArrayList<>(interceptors); - AnnotationAwareOrderComparator.sort(sorted); - this.interceptors = List.copyOf(sorted); - } - - public boolean before(RawFrame frame, IngestContext ctx) { - for (IngestInterceptor i : interceptors) { - // before 阶段短路是有意的:去重、限流失败后不应继续消耗解析和存储资源。 - if (!i.before(frame, ctx) || ctx.aborted()) { - return false; - } - } - return true; - } - - public void after(VehicleEvent event, IngestContext ctx) { - for (IngestInterceptor i : interceptors) { - i.after(event, ctx); - } - } - - public void onError(Throwable error, IngestContext ctx) { - for (IngestInterceptor i : interceptors) { - i.onError(error, ctx); - } - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptor.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptor.java deleted file mode 100644 index 7c4f72f4..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptor.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.lingniu.ingest.core.pipeline.builtin; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.IngestInterceptor; -import com.lingniu.ingest.api.pipeline.RawFrame; -import org.springframework.core.Ordered; - -import java.util.Arrays; -import java.time.Duration; -import java.util.Map; - -/** - * 基于 Caffeine 的本地幂等去重。 - * - *

Key 构造:{@code protocolId + vehicle identity + command + seq/fingerprint}。vehicle identity - * 优先使用已解析 VIN;VIN 未解析时使用 phone/deviceId/terminalId/plate,避免 JT808 多终端在 - * {@code vin=unknown} 时同流水号互相去重。如果上游已经在 {@link RawFrame#sourceMeta()} 里带了 - * {@code seq},优先用真实流水号;否则退化为 raw bytes fingerprint,避免设备重连或 TCP 重发导致 - * 同一帧重复进入 Archive/Kafka/历史索引。 - * - *

当前缓存是进程内的,只能保证单实例去重。32960 若做多副本水平扩容,需要把这个位置替换成 - * Redis/集中式幂等键,否则不同实例仍可能各自接收一次相同原始包。 - */ -public class DedupInterceptor implements IngestInterceptor, Ordered { - - private final Cache seen; - - public DedupInterceptor(int maxSize, long ttlSeconds) { - this.seen = Caffeine.newBuilder() - .maximumSize(maxSize) - .expireAfterWrite(Duration.ofSeconds(ttlSeconds)) - .build(); - } - - @Override - public boolean before(RawFrame frame, IngestContext ctx) { - Map meta = frame.sourceMeta() == null ? Map.of() : frame.sourceMeta(); - String identity = identityKey(frame, meta); - String seq = meta.get("seq"); - if (seq == null || seq.isBlank()) { - // 某些入口没有硬件流水号,使用原始字节哈希作为保底键,至少能挡住同连接内的重复帧。 - seq = rawFingerprint(frame); - } - String key = frame.protocolId() + ":" + identity + ":" + frame.command() + ":" + seq; - if (seen.asMap().putIfAbsent(key, Boolean.TRUE) != null) { - ctx.abort("duplicate:" + key); - return false; - } - return true; - } - - @Override - public int getOrder() { - return 100; - } - - private static String identityKey(RawFrame frame, Map meta) { - String vin = meta.getOrDefault("vin", "unknown").trim(); - if (!vin.isBlank() && !"unknown".equalsIgnoreCase(vin)) { - return "vin:" + vin; - } - for (String key : new String[]{"phone", "deviceId", "terminalId", "plate"}) { - String value = meta.get(key); - if (value != null && !value.isBlank()) { - return key + ":" + value.trim(); - } - } - return "raw:" + rawFingerprint(frame); - } - - private static String rawFingerprint(RawFrame frame) { - byte[] rawBytes = frame.rawBytes(); - if (rawBytes != null && rawBytes.length > 0) { - return "raw:" + Integer.toHexString(Arrays.hashCode(rawBytes)); - } - return "receivedAt:" + frame.receivedAt(); - } -} diff --git a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptor.java b/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptor.java deleted file mode 100644 index 3a7da48d..00000000 --- a/modules/core/ingest-core/src/main/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptor.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.lingniu.ingest.core.pipeline.builtin; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.IngestInterceptor; -import com.lingniu.ingest.api.pipeline.RawFrame; -import io.github.resilience4j.ratelimiter.RateLimiter; -import io.github.resilience4j.ratelimiter.RateLimiterConfig; -import org.springframework.core.Ordered; - -import java.time.Duration; - -/** - * 单 VIN 速率限制。每个 VIN 一个独立的 Resilience4j RateLimiter,由 Caffeine 按 LRU 管理。 - * - *

这里限的是进入业务 Handler 前的原始帧,不区分命令类型;32960 生产侧如需允许登入/登出穿透, - * 应在此处扩展白名单或改为按命令配置,而不是在存储层丢弃。 - */ -public class RateLimitInterceptor implements IngestInterceptor, Ordered { - - private final Cache limiters; - private final RateLimiterConfig defaultConfig; - - public RateLimitInterceptor(int perVinQps, int maxVins) { - this.defaultConfig = RateLimiterConfig.custom() - .limitForPeriod(perVinQps) - .limitRefreshPeriod(Duration.ofSeconds(1)) - .timeoutDuration(Duration.ZERO) - .build(); - this.limiters = Caffeine.newBuilder() - .maximumSize(maxVins) - .expireAfterAccess(Duration.ofMinutes(10)) - .build(); - } - - @Override - public boolean before(RawFrame frame, IngestContext ctx) { - String limiterKey = limiterKey(frame); - // 车辆粒度隔离:VIN 可用时按 VIN;JT808 等未映射 VIN 的协议按 vehicleKey/phone 兜底, - // 避免所有 unknown 终端挤在同一个限流桶里。 - RateLimiter rl = limiters.get(limiterKey, k -> RateLimiter.of("vehicle-" + k, defaultConfig)); - if (!rl.acquirePermission()) { - ctx.abort("rate-limited:" + limiterKey); - return false; - } - return true; - } - - private static String limiterKey(RawFrame frame) { - String vin = meta(frame, "vin"); - if (isKnown(vin)) { - return "vin:" + vin; - } - String vehicleKey = meta(frame, "vehicleKey"); - if (isKnown(vehicleKey)) { - return "vehicleKey:" + vehicleKey; - } - String phone = meta(frame, "phone"); - if (isKnown(phone)) { - return "phone:" + phone; - } - return "unknown"; - } - - private static String meta(RawFrame frame, String key) { - if (frame == null || frame.sourceMeta() == null) { - return ""; - } - return frame.sourceMeta().getOrDefault(key, "").trim(); - } - - private static boolean isKnown(String value) { - return value != null && !value.isBlank() && !"unknown".equalsIgnoreCase(value.trim()); - } - - @Override - public int getOrder() { - return 200; - } -} diff --git a/modules/core/ingest-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/core/ingest-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index c5adbd90..00000000 --- a/modules/core/ingest-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.core.config.IngestCoreAutoConfiguration diff --git a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/concurrency/DisruptorEventBusAwaitTest.java b/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/concurrency/DisruptorEventBusAwaitTest.java deleted file mode 100644 index fbbaea9e..00000000 --- a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/concurrency/DisruptorEventBusAwaitTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.lingniu.ingest.core.concurrency; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class DisruptorEventBusAwaitTest { - - @Test - void publishAndAwaitDoesNotInvokeSinkPublishOnCallerThread() throws Exception { - long callerThreadId = Thread.currentThread().threadId(); - CapturingSink sink = new CapturingSink("kafka"); - - try (DisruptorEventBus bus = new DisruptorEventBus(1024, "blocking", java.util.List.of(sink))) { - bus.publishAndAwait(event(), "kafka").get(3, TimeUnit.SECONDS); - } - - assertThat(sink.publishThreadId.get()).isNotEqualTo(callerThreadId); - } - - @Test - void publishAndAwaitDoesNotInvokeOptionalSinksWhenRequiredSinkIsAbsent() throws Exception { - CapturingSink optionalSink = new CapturingSink("raw-archive"); - - try (DisruptorEventBus bus = new DisruptorEventBus(1024, "blocking", List.of(optionalSink))) { - assertThatThrownBy(() -> bus.publishAndAwait(event(), "kafka").join()) - .isInstanceOf(CompletionException.class) - .hasRootCauseInstanceOf(IllegalStateException.class) - .hasMessageContaining("kafka"); - - assertThat(optionalSink.published.await(200, TimeUnit.MILLISECONDS)).isFalse(); - } - } - - @Test - void publishFansOutEveryEventToEveryAcceptingSink() throws Exception { - int count = 1000; - CountingSink rawArchive = new CountingSink("raw-archive", count); - CountingSink kafka = new CountingSink("kafka", count); - - try (DisruptorEventBus bus = new DisruptorEventBus(1024, "blocking", List.of(rawArchive, kafka))) { - for (int i = 0; i < count; i++) { - bus.publish(event("heartbeat-" + i)); - } - - assertThat(rawArchive.received.await(3, TimeUnit.SECONDS)).isTrue(); - assertThat(kafka.received.await(3, TimeUnit.SECONDS)).isTrue(); - } - - assertThat(rawArchive.count.get()).isEqualTo(count); - assertThat(kafka.count.get()).isEqualTo(count); - } - - private static VehicleEvent.Heartbeat event() { - return event("heartbeat-1"); - } - - private static VehicleEvent.Heartbeat event(String eventId) { - Instant now = Instant.parse("2026-06-23T10:00:00Z"); - return new VehicleEvent.Heartbeat( - eventId, - "VIN001", - ProtocolId.GB32960, - now, - now, - "trace-1", - Map.of()); - } - - private static final class CapturingSink implements EventSink { - private final String name; - private final AtomicLong publishThreadId = new AtomicLong(-1); - private final CountDownLatch published = new CountDownLatch(1); - - private CapturingSink(String name) { - this.name = name; - } - - @Override - public String name() { - return name; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - publishThreadId.set(Thread.currentThread().threadId()); - published.countDown(); - return CompletableFuture.completedFuture(null); - } - } - - private static final class CountingSink implements EventSink { - private final String name; - private final AtomicLong count = new AtomicLong(); - private final CountDownLatch received; - - private CountingSink(String name, int expected) { - this.name = name; - this.received = new CountDownLatch(expected); - } - - @Override - public String name() { - return name; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - count.incrementAndGet(); - received.countDown(); - return CompletableFuture.completedFuture(null); - } - } -} diff --git a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/config/IngestCoreAutoConfigurationTest.java b/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/config/IngestCoreAutoConfigurationTest.java deleted file mode 100644 index 3bf4c9cb..00000000 --- a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/config/IngestCoreAutoConfigurationTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lingniu.ingest.core.config; - -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; - -import static org.assertj.core.api.Assertions.assertThat; - -class IngestCoreAutoConfigurationTest { - - @Test - void beanPostProcessorFactoryMethodIsStaticToAvoidEarlyConfigurationInstantiation() throws Exception { - Method method = IngestCoreAutoConfiguration.class.getDeclaredMethod( - "annotationHandlerBeanPostProcessor"); - - assertThat(Modifier.isStatic(method.getModifiers())).isTrue(); - assertThat(method.getParameterCount()).isZero(); - } -} diff --git a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherDurableAckBoundaryTest.java b/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherDurableAckBoundaryTest.java deleted file mode 100644 index 0512c463..00000000 --- a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherDurableAckBoundaryTest.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.AsyncBatch; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.IngestInterceptor; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.api.sink.EventSink; -import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor; -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.pipeline.InterceptorChain; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Method; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class DispatcherDurableAckBoundaryTest { - - @Test - void dispatchAndAwaitFailsWhenNoKafkaSinkAcceptsEvent() throws Exception { - EventSink rawArchiveSink = new NamedSink("raw-archive", true); - - try (Harness harness = new Harness(registryWithHeartbeatHandler(), List.of(rawArchiveSink), List.of())) { - CompletionStage future = harness.dispatcher.dispatchAndAwait(frame(0x07, "payload")); - - assertThatThrownBy(() -> future.toCompletableFuture().join()) - .isInstanceOf(CompletionException.class) - .hasRootCauseInstanceOf(IllegalStateException.class) - .hasMessageContaining("kafka"); - } - } - - @Test - void dispatchAndAwaitNotifiesInterceptorWhenPlanCapturesFailure() throws Exception { - CapturingInterceptor interceptor = new CapturingInterceptor(); - - try (Harness harness = new Harness(registryWithThrowingHandler(), List.of(new NamedSink("kafka", true)), - List.of(interceptor))) { - CompletionStage future = harness.dispatcher.dispatchAndAwait(frame(0x08, "payload")); - - assertThatThrownBy(() -> future.toCompletableFuture().join()) - .isInstanceOf(CompletionException.class) - .hasRootCauseInstanceOf(IllegalStateException.class) - .rootCause() - .hasMessageContaining("handler failed"); - assertThat(interceptor.error.get()) - .isInstanceOf(RuntimeException.class) - .hasMessageContaining("Handler threw exception"); - } - } - - @Test - void dispatchAndAwaitFailsFastForAsyncBatchHandler() throws Exception { - NamedSink kafka = new NamedSink("kafka", true); - try (Harness harness = new Harness(registryWithAsyncBatchHandler(), List.of(kafka), List.of())) { - CompletionStage future = harness.dispatcher.dispatchAndAwait( - frame(0x09, "payload", new byte[]{0x23, 0x23, 0x09})); - - assertThatThrownBy(() -> future.toCompletableFuture().join()) - .isInstanceOf(CompletionException.class) - .hasRootCauseInstanceOf(IllegalStateException.class) - .hasMessageContaining("@AsyncBatch"); - assertThat(kafka.published.get()).isZero(); - } - } - - private static HandlerRegistry registryWithHeartbeatHandler() throws NoSuchMethodException { - HeartbeatHandler bean = new HeartbeatHandler(); - Method method = HeartbeatHandler.class.getDeclaredMethod("onHeartbeat", Object.class); - return registry(bean, method, 0x07, method.getAnnotation(AsyncBatch.class)); - } - - private static HandlerRegistry registryWithThrowingHandler() throws NoSuchMethodException { - ThrowingHandler bean = new ThrowingHandler(); - Method method = ThrowingHandler.class.getDeclaredMethod("onFrame", Object.class); - return registry(bean, method, 0x08, method.getAnnotation(AsyncBatch.class)); - } - - private static HandlerRegistry registryWithAsyncBatchHandler() throws NoSuchMethodException { - AsyncHandler bean = new AsyncHandler(); - Method method = AsyncHandler.class.getDeclaredMethod("onBatch", List.class); - return registry(bean, method, 0x09, method.getAnnotation(AsyncBatch.class)); - } - - private static HandlerRegistry registry(Object bean, Method method, int command, AsyncBatch asyncBatch) { - HandlerRegistry registry = new HandlerRegistry(); - registry.register(new HandlerDefinition( - ProtocolId.GB32960, - command, - 0, - method.getName(), - bean, - method, - Object.class, - null, - null, - asyncBatch)); - return registry; - } - - private static RawFrame frame(int command, Object payload) { - return frame(command, payload, null); - } - - private static RawFrame frame(int command, Object payload, byte[] rawBytes) { - return new RawFrame( - ProtocolId.GB32960, - command, - 0, - payload, - rawBytes, - Map.of("vin", "VIN001"), - Instant.parse("2026-06-23T10:00:00Z")); - } - - private static VehicleEvent.Heartbeat event(String id) { - Instant now = Instant.parse("2026-06-23T10:00:00Z"); - return new VehicleEvent.Heartbeat(id, "VIN001", ProtocolId.GB32960, now, now, "trace-" + id, Map.of()); - } - - private static final class Harness implements AutoCloseable { - private final DisruptorEventBus eventBus; - private final AsyncBatchExecutor batchExecutor; - private final Dispatcher dispatcher; - - private Harness(HandlerRegistry registry, List sinks, List interceptors) { - this.eventBus = new DisruptorEventBus(1024, "blocking", sinks); - this.batchExecutor = new AsyncBatchExecutor(eventBus::publish); - this.dispatcher = new Dispatcher( - registry, - new InterceptorChain(interceptors), - new HandlerInvoker(), - eventBus, - batchExecutor); - } - - @Override - public void close() { - batchExecutor.close(); - eventBus.close(); - } - } - - private static final class NamedSink implements EventSink { - private final String name; - private final boolean accepts; - private final AtomicInteger published = new AtomicInteger(); - - private NamedSink(String name, boolean accepts) { - this.name = name; - this.accepts = accepts; - } - - @Override - public String name() { - return name; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - published.incrementAndGet(); - return CompletableFuture.completedFuture(null); - } - - @Override - public boolean accepts(VehicleEvent event) { - return accepts; - } - } - - private static final class CapturingInterceptor implements IngestInterceptor { - private final AtomicReference error = new AtomicReference<>(); - - @Override - public void onError(Throwable error, IngestContext ctx) { - this.error.set(error); - } - } - - @ProtocolHandler(protocol = ProtocolId.GB32960) - static final class HeartbeatHandler { - @MessageMapping(command = 0x07) - VehicleEvent.Heartbeat onHeartbeat(Object ignored) { - return event("heartbeat-1"); - } - } - - @ProtocolHandler(protocol = ProtocolId.GB32960) - static final class ThrowingHandler { - @MessageMapping(command = 0x08) - VehicleEvent.Heartbeat onFrame(Object ignored) { - throw new IllegalStateException("handler failed"); - } - } - - @ProtocolHandler(protocol = ProtocolId.GB32960) - public static final class AsyncHandler { - @MessageMapping(command = 0x09) - @AsyncBatch(size = 2, waitMs = 10, poolSize = 1) - public List onBatch(List ignored) { - return List.of(event("async-1")); - } - } -} diff --git a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherRawArchiveMetadataTest.java b/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherRawArchiveMetadataTest.java deleted file mode 100644 index 1b446ccd..00000000 --- a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/DispatcherRawArchiveMetadataTest.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.AsyncBatch; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.event.LocationPayload; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.api.sink.EventSink; -import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor; -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.pipeline.InterceptorChain; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Method; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import static org.assertj.core.api.Assertions.assertThat; - -class DispatcherRawArchiveMetadataTest { - - @Test - void enrichesBusinessEventsWithRawArchiveLookupMetadata() throws Exception { - CollectingSink sink = new CollectingSink(); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - try { - Dispatcher dispatcher = new Dispatcher( - registryWithLocationHandler(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - - dispatcher.dispatch(new RawFrame( - ProtocolId.JT808, - 0x0200, - 0, - new SamplePayload("payload-0200", 7), - new byte[]{0x7e, 0x01, 0x7e}, - Map.of("vin", "VIN001", "peer", "127.0.0.1:10001"), - Instant.parse("2026-06-22T08:00:00Z"))); - - VehicleEvent.RawArchive raw = awaitEvent(sink, VehicleEvent.RawArchive.class); - VehicleEvent.Location location = awaitEvent(sink, VehicleEvent.Location.class); - - String rawArchiveKey = raw.metadata().get("rawArchiveKey"); - String rawFrameId = raw.metadata().get("rawFrameId"); - assertThat(rawArchiveKey).isNotBlank(); - assertThat(rawFrameId).startsWith("rf_"); - assertThat(raw.parsedJson()).contains("\"name\":\"payload-0200\""); - assertThat(raw.metadata()).doesNotContainKey("rawArchiveParsedJson"); - assertThat(raw.eventId()).containsOnlyDigits(); - assertThat(rawArchiveKey).endsWith("/" + raw.eventId() + ".bin"); - assertThat(raw.metadata()).containsEntry("frame_id", rawFrameId); - assertThat(location.metadata()) - .containsEntry("rawArchiveEventId", raw.eventId()) - .containsEntry("rawArchiveKey", rawArchiveKey) - .containsEntry("rawArchiveUri", "archive://" + rawArchiveKey) - .containsEntry("rawFrameId", rawFrameId) - .containsEntry("frame_id", rawFrameId); - } finally { - batchExecutor.close(); - eventBus.close(); - } - } - - @Test - void enrichesAsyncBatchEventsWithTheirRawArchiveLookupMetadata() throws Exception { - CollectingSink sink = new CollectingSink(); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - try { - Dispatcher dispatcher = new Dispatcher( - registryWithAsyncLocationHandler(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - - dispatcher.dispatch(new RawFrame( - ProtocolId.JT808, - 0x0201, - 0, - "payload-1", - new byte[]{0x7e, 0x02, 0x7e}, - Map.of("vin", "VIN002"), - Instant.parse("2026-06-22T08:01:00Z"))); - - VehicleEvent.RawArchive raw = awaitEvent(sink, VehicleEvent.RawArchive.class); - VehicleEvent.Location location = awaitLocationEvent(sink, "async-location-1"); - - String rawArchiveKey = raw.metadata().get("rawArchiveKey"); - String rawFrameId = raw.metadata().get("rawFrameId"); - assertThat(rawFrameId).startsWith("rf_"); - assertThat(location.metadata()) - .containsEntry("rawArchiveEventId", raw.eventId()) - .containsEntry("rawArchiveKey", rawArchiveKey) - .containsEntry("rawArchiveUri", "archive://" + rawArchiveKey) - .containsEntry("rawFrameId", rawFrameId) - .containsEntry("frame_id", rawFrameId); - } finally { - batchExecutor.close(); - eventBus.close(); - } - } - - private static HandlerRegistry registryWithLocationHandler() throws NoSuchMethodException { - LocationHandler bean = new LocationHandler(); - Method method = LocationHandler.class.getDeclaredMethod("onLocation", SamplePayload.class); - HandlerRegistry registry = new HandlerRegistry(); - registry.register(new HandlerDefinition( - ProtocolId.JT808, - 0x0200, - 0, - "test-location", - bean, - method, - Object.class, - null, - null, - null)); - return registry; - } - - private static HandlerRegistry registryWithAsyncLocationHandler() throws NoSuchMethodException { - AsyncLocationHandler bean = new AsyncLocationHandler(); - Method method = AsyncLocationHandler.class.getDeclaredMethod("onLocationBatch", List.class); - HandlerRegistry registry = new HandlerRegistry(); - registry.register(new HandlerDefinition( - ProtocolId.JT808, - 0x0201, - 0, - "test-async-location", - bean, - method, - Object.class, - null, - null, - method.getAnnotation(AsyncBatch.class))); - return registry; - } - - private static T awaitEvent(CollectingSink sink, Class type) throws InterruptedException { - long deadline = System.currentTimeMillis() + 3000; - while (System.currentTimeMillis() < deadline) { - synchronized (sink.events) { - for (VehicleEvent event : sink.events) { - if (type.isInstance(event)) { - return type.cast(event); - } - } - } - Thread.sleep(25); - } - throw new AssertionError("event not published: " + type.getSimpleName() + ", actual=" + sink.events); - } - - private static VehicleEvent.Location awaitLocationEvent(CollectingSink sink, String eventId) throws InterruptedException { - long deadline = System.currentTimeMillis() + 3000; - while (System.currentTimeMillis() < deadline) { - synchronized (sink.events) { - for (VehicleEvent event : sink.events) { - if (event instanceof VehicleEvent.Location location && location.eventId().equals(eventId)) { - return location; - } - } - } - Thread.sleep(25); - } - throw new AssertionError("location event not published: " + eventId + ", actual=" + sink.events); - } - - @ProtocolHandler(protocol = ProtocolId.JT808) - static final class LocationHandler { - @MessageMapping(command = 0x0200) - VehicleEvent.Location onLocation(SamplePayload ignored) { - return new VehicleEvent.Location( - "location-1", - "VIN001", - ProtocolId.JT808, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-location", - Map.of(), - new LocationPayload(113.12, 23.45, 10.0, 60.0, 90.0, 0, 1)); - } - } - - record SamplePayload(String name, int value) { - } - - @ProtocolHandler(protocol = ProtocolId.JT808) - public static final class AsyncLocationHandler { - @MessageMapping(command = 0x0201) - @AsyncBatch(size = 2, waitMs = 10, poolSize = 1) - public List onLocationBatch(List batch) { - return List.of(new VehicleEvent.Location( - "async-location-1", - "VIN002", - ProtocolId.JT808, - Instant.parse("2026-06-22T08:01:00Z"), - Instant.parse("2026-06-22T08:01:01Z"), - "trace-async-location", - Map.of(), - new LocationPayload(113.12, 23.45, 10.0, 60.0, 90.0, 0, 1))); - } - } - - private static final class CollectingSink implements EventSink { - private final List events = new ArrayList<>(); - - @Override - public String name() { - return "collecting"; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - synchronized (events) { - events.add(event); - } - return CompletableFuture.completedFuture(null); - } - } -} diff --git a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/HandlerRegistryTest.java b/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/HandlerRegistryTest.java deleted file mode 100644 index 1eaa8f72..00000000 --- a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/dispatcher/HandlerRegistryTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.lingniu.ingest.core.dispatcher; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Method; - -import static org.assertj.core.api.Assertions.assertThat; - -class HandlerRegistryTest { - - @Test - void exactCommandMatchTakesPrecedenceOverWildcardHandlers() throws Exception { - HandlerRegistry registry = new HandlerRegistry(); - Object bean = new SampleHandlers(); - Method exact = SampleHandlers.class.getMethod("exact", Object.class); - Method wildcard = SampleHandlers.class.getMethod("wildcard", Object.class); - HandlerDefinition exactDef = definition(0x0200, "exact", bean, exact); - HandlerDefinition wildcardDef = definition(0, "wildcard", bean, wildcard); - - registry.register(exactDef); - registry.register(wildcardDef); - - assertThat(registry.resolve(ProtocolId.JT808, 0x0200, 0)) - .containsExactly(exactDef); - assertThat(registry.resolve(ProtocolId.JT808, 0x0500, 0)) - .containsExactly(wildcardDef); - } - - private static HandlerDefinition definition(int command, String desc, Object bean, Method method) { - return new HandlerDefinition( - ProtocolId.JT808, - command, - 0, - desc, - bean, - method, - Object.class, - null, - null, - null); - } - - public static final class SampleHandlers { - public void exact(Object ignored) { - } - - public void wildcard(Object ignored) { - } - } -} diff --git a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptorTest.java b/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptorTest.java deleted file mode 100644 index 85e33e23..00000000 --- a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/DedupInterceptorTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.lingniu.ingest.core.pipeline.builtin; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.RawFrame; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class DedupInterceptorTest { - - @Test - void usesRawFingerprintWhenSequenceIsMissing() { - DedupInterceptor interceptor = new DedupInterceptor(100, 60); - IngestContext ctx = new IngestContext("trace-1"); - - RawFrame first = frame(new byte[]{0x23, 0x23, 0x02, 0x01}); - RawFrame differentPayload = frame(new byte[]{0x23, 0x23, 0x02, 0x02}); - RawFrame duplicate = frame(new byte[]{0x23, 0x23, 0x02, 0x01}); - - assertThat(interceptor.before(first, ctx)).isTrue(); - assertThat(interceptor.before(differentPayload, ctx)).isTrue(); - assertThat(interceptor.before(duplicate, ctx)).isFalse(); - assertThat(ctx.aborted()).isTrue(); - } - - @Test - void doesNotDeduplicateDifferentJt808PhonesWhenVinIsUnknownAndSequenceMatches() { - DedupInterceptor interceptor = new DedupInterceptor(100, 60); - - RawFrame firstPhone = jt808Frame("13079960001", "1000"); - RawFrame secondPhone = jt808Frame("13079960002", "1000"); - RawFrame duplicateFirstPhone = jt808Frame("13079960001", "1000"); - - assertThat(interceptor.before(firstPhone, new IngestContext("trace-1"))).isTrue(); - assertThat(interceptor.before(secondPhone, new IngestContext("trace-2"))).isTrue(); - IngestContext duplicateContext = new IngestContext("trace-3"); - assertThat(interceptor.before(duplicateFirstPhone, duplicateContext)).isFalse(); - assertThat(duplicateContext.abortReason()).contains("duplicate"); - } - - private static RawFrame frame(byte[] rawBytes) { - return new RawFrame( - ProtocolId.GB32960, - 0x02, - 0, - new Object(), - rawBytes, - Map.of("vin", "TESTSTATVIN000001"), - Instant.parse("2026-06-22T13:00:00Z")); - } - - private static RawFrame jt808Frame(String phone, String seq) { - return new RawFrame( - ProtocolId.JT808, - 0x0200, - 0, - new Object(), - new byte[]{0x02, 0x00, 0x00, 0x1c}, - Map.of("vin", "unknown", "phone", phone, "seq", seq), - Instant.parse("2026-06-22T13:00:00Z")); - } -} diff --git a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptorTest.java b/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptorTest.java deleted file mode 100644 index 45859a3e..00000000 --- a/modules/core/ingest-core/src/test/java/com/lingniu/ingest/core/pipeline/builtin/RateLimitInterceptorTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.lingniu.ingest.core.pipeline.builtin; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.RawFrame; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class RateLimitInterceptorTest { - - @Test - void unknownJt808VinUsesPhoneAsIndependentLimiterKey() { - RateLimitInterceptor interceptor = new RateLimitInterceptor(1, 100); - IngestContext first = new IngestContext("trace-1"); - IngestContext second = new IngestContext("trace-2"); - - boolean firstAllowed = interceptor.before(frame("unknown", "13079970001"), first); - boolean secondAllowed = interceptor.before(frame("unknown", "13079970002"), second); - - assertThat(firstAllowed).isTrue(); - assertThat(secondAllowed).isTrue(); - assertThat(first.aborted()).isFalse(); - assertThat(second.aborted()).isFalse(); - } - - @Test - void sameJt808PhoneStillSharesLimiterWhenVinIsUnknown() { - RateLimitInterceptor interceptor = new RateLimitInterceptor(1, 100); - IngestContext first = new IngestContext("trace-1"); - IngestContext second = new IngestContext("trace-2"); - - boolean firstAllowed = interceptor.before(frame("unknown", "13079970001"), first); - boolean secondAllowed = interceptor.before(frame("unknown", "13079970001"), second); - - assertThat(firstAllowed).isTrue(); - assertThat(secondAllowed).isFalse(); - assertThat(second.aborted()).isTrue(); - assertThat(second.abortReason()).isEqualTo("rate-limited:phone:13079970001"); - } - - private static RawFrame frame(String vin, String phone) { - return new RawFrame( - ProtocolId.JT808, - 0x0200, - 0, - new Object(), - new byte[]{0x01}, - Map.of("vin", vin, "phone", phone), - Instant.parse("2026-06-29T00:00:00Z")); - } -} diff --git a/modules/core/ingest-facts/pom.xml b/modules/core/ingest-facts/pom.xml deleted file mode 100644 index a8d6f220..00000000 --- a/modules/core/ingest-facts/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - ingest-facts - ingest-facts - Protocol-neutral raw frame and decoded fact model for vehicle ingestion. - - - - com.lingniu.ingest - ingest-api - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/DecodedFact.java b/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/DecodedFact.java deleted file mode 100644 index f155d039..00000000 --- a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/DecodedFact.java +++ /dev/null @@ -1,215 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.Map; - -public sealed interface DecodedFact - permits DecodedFact.Location, - DecodedFact.Realtime, - DecodedFact.Alarm, - DecodedFact.Session, - DecodedFact.Register, - DecodedFact.Extension { - - String factId(); - String frameId(); - ProtocolId protocol(); - String vehicleKey(); - String vin(); - String phone(); - Instant eventTime(); - Instant receivedAt(); - String rawUri(); - Map metadata(); - - record Location( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - Double longitude, - Double latitude, - Double altitudeM, - Double speedKmh, - Double directionDeg, - Long alarmFlag, - Long statusFlag, - Double totalMileageKm - ) implements DecodedFact { - public Location { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - metadata = BaseFields.metadata(metadata); - } - } - - record Realtime( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - Map fields - ) implements DecodedFact { - public Realtime { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - record Alarm( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - String alarmLevel, - Long alarmCode, - String alarmName, - Map fields - ) implements DecodedFact { - public Alarm { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - alarmLevel = BaseFields.clean(alarmLevel); - alarmName = BaseFields.clean(alarmName); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - record Session( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - String sessionType, - Integer resultCode, - Map fields - ) implements DecodedFact { - public Session { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - sessionType = BaseFields.clean(sessionType); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - record Register( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - Map registrationFields - ) implements DecodedFact { - public Register { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - metadata = BaseFields.metadata(metadata); - registrationFields = registrationFields == null ? Map.of() : Map.copyOf(registrationFields); - } - } - - record Extension( - String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - Instant eventTime, - Instant receivedAt, - String rawUri, - Map metadata, - String extensionType, - Map fields - ) implements DecodedFact { - public Extension { - BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri); - vin = BaseFields.clean(vin); - phone = BaseFields.clean(phone); - extensionType = BaseFields.clean(extensionType); - metadata = BaseFields.metadata(metadata); - fields = fields == null ? Map.of() : Map.copyOf(fields); - } - } - - final class BaseFields { - private BaseFields() { - } - - static void validate(String factId, - String frameId, - ProtocolId protocol, - String vehicleKey, - Instant eventTime, - Instant receivedAt, - String rawUri) { - required(factId, "factId"); - required(frameId, "frameId"); - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - required(vehicleKey, "vehicleKey"); - if (eventTime == null) { - throw new IllegalArgumentException("eventTime must not be null"); - } - if (receivedAt == null) { - throw new IllegalArgumentException("receivedAt must not be null"); - } - required(rawUri, "rawUri"); - } - - static String clean(String value) { - return value == null ? "" : value.trim(); - } - - static Map metadata(Map metadata) { - return metadata == null ? Map.of() : Map.copyOf(metadata); - } - - private static void required(String value, String field) { - if (clean(value).isBlank()) { - throw new IllegalArgumentException(field + " must not be blank"); - } - } - } -} diff --git a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/FactIds.java b/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/FactIds.java deleted file mode 100644 index aa1cdfdd..00000000 --- a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/FactIds.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.HexFormat; - -public final class FactIds { - - private FactIds() { - } - - public static String rawFrameId(ProtocolId protocol, - String vehicleKey, - int messageId, - int subType, - Instant receivedAt, - String checksum) { - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - String input = protocol.name() + "|" - + clean(vehicleKey) + "|" - + messageId + "|" - + subType + "|" - + (receivedAt == null ? "" : receivedAt.toString()) + "|" - + clean(checksum); - return "rf_" + sha256(input).substring(0, 32); - } - - public static String decodedFactId(String frameId, String kind, int ordinal) { - return "df_" + clean(frameId) + "_" + clean(kind) + "_" + ordinal; - } - - private static String clean(String value) { - return value == null ? "" : value.trim(); - } - - private static String sha256(String value) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(digest.digest(value.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is unavailable", e); - } - } -} diff --git a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/ParseStatus.java b/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/ParseStatus.java deleted file mode 100644 index 1855f7f1..00000000 --- a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/ParseStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.lingniu.ingest.facts; - -public enum ParseStatus { - NOT_PARSED, - SUCCEEDED, - FAILED -} diff --git a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameFact.java b/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameFact.java deleted file mode 100644 index 17298499..00000000 --- a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameFact.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.Map; - -public record RawFrameFact( - String frameId, - ProtocolId protocol, - String vehicleKey, - String vin, - String phone, - int messageId, - int subType, - Instant eventTime, - Instant receivedAt, - String peer, - String rawUri, - String checksum, - long rawSizeBytes, - ParseStatus parseStatus, - String parseError, - Map metadata -) { - public RawFrameFact { - frameId = required(frameId, "frameId"); - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - vehicleKey = required(vehicleKey, "vehicleKey"); - receivedAt = required(receivedAt, "receivedAt"); - eventTime = eventTime == null ? receivedAt : eventTime; - rawUri = required(rawUri, "rawUri"); - checksum = required(checksum, "checksum"); - if (rawSizeBytes < 0) { - throw new IllegalArgumentException("rawSizeBytes must not be negative"); - } - parseStatus = parseStatus == null ? ParseStatus.NOT_PARSED : parseStatus; - vin = clean(vin); - phone = clean(phone); - peer = clean(peer); - parseError = clean(parseError); - metadata = metadata == null ? Map.of() : Map.copyOf(metadata); - } - - private static String required(String value, String field) { - String cleaned = clean(value); - if (cleaned.isBlank()) { - throw new IllegalArgumentException(field + " must not be blank"); - } - return cleaned; - } - - private static Instant required(Instant value, String field) { - if (value == null) { - throw new IllegalArgumentException(field + " must not be null"); - } - return value; - } - - private static String clean(String value) { - return value == null ? "" : value.trim(); - } -} diff --git a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameIdentity.java b/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameIdentity.java deleted file mode 100644 index 58b9bfc5..00000000 --- a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/RawFrameIdentity.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.HexFormat; - -public record RawFrameIdentity( - String checksum, - String vehicleKey, - String frameId -) { - public RawFrameIdentity { - checksum = checksum == null ? "" : checksum; - vehicleKey = vehicleKey == null ? "" : vehicleKey; - frameId = frameId == null ? "" : frameId; - } - - public static RawFrameIdentity derive(ProtocolId protocol, - String vin, - String phone, - String rawIdentitySeed, - int messageId, - int subType, - Instant receivedAt, - byte[] rawBytes) { - String checksum = "sha256:" + sha256(rawBytes); - String vehicleKey = VehicleKey.derive(protocol, vin, phone, rawIdentitySeed); - String frameId = FactIds.rawFrameId(protocol, vehicleKey, messageId, subType, receivedAt, checksum); - return new RawFrameIdentity(checksum, vehicleKey, frameId); - } - - private static String sha256(byte[] value) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(digest.digest(value == null ? new byte[0] : value)); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is unavailable", e); - } - } -} diff --git a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/VehicleKey.java b/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/VehicleKey.java deleted file mode 100644 index ae93a68f..00000000 --- a/modules/core/ingest-facts/src/main/java/com/lingniu/ingest/facts/VehicleKey.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HexFormat; - -public final class VehicleKey { - - private VehicleKey() { - } - - public static String derive(ProtocolId protocol, String vin, String phone, String rawIdentitySeed) { - if (protocol == null) { - throw new IllegalArgumentException("protocol must not be null"); - } - String normalizedVin = clean(vin); - if (!normalizedVin.isBlank()) { - return normalizedVin; - } - String normalizedPhone = clean(phone); - if (protocol == ProtocolId.JT808 && !normalizedPhone.isBlank()) { - return "jt808:" + normalizedPhone; - } - String seed = clean(rawIdentitySeed); - if (seed.isBlank()) { - seed = protocol.name(); - } - return "unknown:" + protocol.name() + ":" + sha256(seed).substring(0, 16); - } - - private static String clean(String value) { - return value == null ? "" : value.trim(); - } - - private static String sha256(String value) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - return HexFormat.of().formatHex(digest.digest(value.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is unavailable", e); - } - } -} diff --git a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/DecodedFactTest.java b/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/DecodedFactTest.java deleted file mode 100644 index 06f71891..00000000 --- a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/DecodedFactTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class DecodedFactTest { - - @Test - void locationFactCarriesRawReferenceAndMetadata() { - DecodedFact.Location location = new DecodedFact.Location( - "fact-1", - "frame-1", - ProtocolId.JT808, - "jt808:013912345678", - "", - "013912345678", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-29T00:00:01Z"), - "archive://jt808/frame-1.bin", - Map.of("messageId", "0x0200"), - 120.123456, - 30.123456, - 10.0, - 42.0, - 180.0, - 0L, - 2L, - 1000.5); - - assertThat(location.factId()).isEqualTo("fact-1"); - assertThat(location.rawUri()).isEqualTo("archive://jt808/frame-1.bin"); - assertThat(location.metadata()).containsEntry("messageId", "0x0200"); - } -} diff --git a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/FactIdsTest.java b/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/FactIdsTest.java deleted file mode 100644 index cf5ffa29..00000000 --- a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/FactIdsTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class FactIdsTest { - - @Test - void rawFrameIdIsStableForSameInputs() { - String first = FactIds.rawFrameId( - ProtocolId.GB32960, - "VIN001", - 2, - 7, - Instant.parse("2026-06-29T00:00:00Z"), - "sha256:abc"); - String second = FactIds.rawFrameId( - ProtocolId.GB32960, - "VIN001", - 2, - 7, - Instant.parse("2026-06-29T00:00:00Z"), - "sha256:abc"); - - assertThat(first).isEqualTo(second).startsWith("rf_"); - } - - @Test - void decodedFactIdIncludesKind() { - String id = FactIds.decodedFactId("rf_123", "location", 0); - - assertThat(id).isEqualTo("df_rf_123_location_0"); - } -} diff --git a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameFactTest.java b/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameFactTest.java deleted file mode 100644 index 74299833..00000000 --- a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameFactTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class RawFrameFactTest { - - @Test - void normalizesOptionalStringsAndMetadata() { - RawFrameFact fact = new RawFrameFact( - "frame-1", - ProtocolId.JT808, - "jt808:013912345678", - null, - "013912345678", - 0x0200, - 0, - null, - Instant.parse("2026-06-29T00:00:00Z"), - null, - "archive://2026/06/29/JT808/jt808-013912345678/frame-1.bin", - "sha256:abc", - 128, - ParseStatus.SUCCEEDED, - null, - Map.of("messageName", "LOCATION")); - - assertThat(fact.vin()).isEmpty(); - assertThat(fact.eventTime()).isEqualTo(fact.receivedAt()); - assertThat(fact.peer()).isEmpty(); - assertThat(fact.parseError()).isEmpty(); - assertThat(fact.metadata()).containsEntry("messageName", "LOCATION"); - } - - @Test - void rejectsMissingRequiredFields() { - assertThatThrownBy(() -> new RawFrameFact( - "", - ProtocolId.GB32960, - "VIN001", - "VIN001", - "", - 2, - 0, - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-29T00:00:00Z"), - "", - "archive://x", - "sha256:abc", - 1, - ParseStatus.SUCCEEDED, - "", - Map.of())) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("frameId"); - } -} diff --git a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameIdentityTest.java b/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameIdentityTest.java deleted file mode 100644 index c0a59f1d..00000000 --- a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/RawFrameIdentityTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class RawFrameIdentityTest { - - @Test - void derivesChecksumVehicleKeyAndFrameIdTogether() { - RawFrameIdentity identity = RawFrameIdentity.derive( - ProtocolId.JT808, - "", - "13307795425", - "1782900000000000", - 0x0200, - 0, - Instant.parse("2026-07-01T10:00:00Z"), - new byte[]{0x7e, 0x02, 0x00, 0x7e}); - - assertThat(identity.checksum()).startsWith("sha256:"); - assertThat(identity.vehicleKey()).isEqualTo("jt808:13307795425"); - assertThat(identity.frameId()) - .isEqualTo(FactIds.rawFrameId( - ProtocolId.JT808, - "jt808:13307795425", - 0x0200, - 0, - Instant.parse("2026-07-01T10:00:00Z"), - identity.checksum())) - .startsWith("rf_"); - } -} diff --git a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/VehicleKeyTest.java b/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/VehicleKeyTest.java deleted file mode 100644 index 43820676..00000000 --- a/modules/core/ingest-facts/src/test/java/com/lingniu/ingest/facts/VehicleKeyTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.lingniu.ingest.facts; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class VehicleKeyTest { - - @Test - void usesVinForGb32960() { - assertThat(VehicleKey.derive(ProtocolId.GB32960, " LB9A32A20P0LS1257 ", "", "abc")) - .isEqualTo("LB9A32A20P0LS1257"); - } - - @Test - void usesResolvedVinForJt808WhenPresent() { - assertThat(VehicleKey.derive(ProtocolId.JT808, "VIN001", "013912345678", "abc")) - .isEqualTo("VIN001"); - } - - @Test - void usesPhoneForJt808WhenVinIsMissing() { - assertThat(VehicleKey.derive(ProtocolId.JT808, "", "013912345678", "abc")) - .isEqualTo("jt808:013912345678"); - } - - @Test - void usesStableUnknownHashWhenVehicleIdentifiersAreMissing() { - assertThat(VehicleKey.derive(ProtocolId.JT808, "", "", "abc")) - .isEqualTo(VehicleKey.derive(ProtocolId.JT808, null, null, "abc")) - .startsWith("unknown:JT808:"); - } - - @Test - void rejectsMissingProtocol() { - assertThatThrownBy(() -> VehicleKey.derive(null, "VIN001", "", "abc")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("protocol"); - } -} diff --git a/modules/core/observability/pom.xml b/modules/core/observability/pom.xml deleted file mode 100644 index e5e14c6e..00000000 --- a/modules/core/observability/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - observability - observability - Micrometer + Prometheus + 业务指标封装(事件计数、Dispatcher 耗时、DLQ 计数等)。 - - - - com.lingniu.ingest - ingest-api - - - org.springframework.boot - spring-boot-starter-actuator - - - io.micrometer - micrometer-registry-prometheus - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/core/observability/src/main/java/com/lingniu/ingest/observability/IngestMetrics.java b/modules/core/observability/src/main/java/com/lingniu/ingest/observability/IngestMetrics.java deleted file mode 100644 index c9fa09b9..00000000 --- a/modules/core/observability/src/main/java/com/lingniu/ingest/observability/IngestMetrics.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.lingniu.ingest.observability; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.IngestInterceptor; -import com.lingniu.ingest.api.pipeline.RawFrame; -import io.micrometer.core.instrument.Counter; -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.Timer; -import org.springframework.core.Ordered; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * 指标拦截器:自动以 {@link IngestInterceptor} 形式接入拦截链,无侵入采集核心流量指标。 - * - *

暴露指标: - *

    - *
  • {@code ingest_frames_total{protocol}} - 入站帧计数 - *
  • {@code ingest_events_total{protocol,type}} - 事件产出计数 - *
  • {@code ingest_errors_total{protocol}} - 异常计数 - *
  • {@code ingest_frame_duration_seconds{protocol}} - 单帧处理耗时 - *
- */ -public class IngestMetrics implements IngestInterceptor, Ordered { - - private static final String ATTR_TIMER_SAMPLE = "metrics.timer.sample"; - private static final String ATTR_PROTOCOL = "metrics.protocol"; - - private final MeterRegistry registry; - private final ConcurrentMap frameCounters = new ConcurrentHashMap<>(); - private final ConcurrentMap errorCounters = new ConcurrentHashMap<>(); - private final ConcurrentMap frameTimers = new ConcurrentHashMap<>(); - private final ConcurrentMap eventCounters = new ConcurrentHashMap<>(); - - public IngestMetrics(MeterRegistry registry) { - this.registry = registry; - } - - @Override - public boolean before(RawFrame frame, IngestContext ctx) { - ProtocolId p = frame.protocolId() == null ? ProtocolId.UNKNOWN : frame.protocolId(); - ctx.attr(ATTR_PROTOCOL, p); - frameCounters.computeIfAbsent(p, k -> - Counter.builder("ingest_frames_total").tag("protocol", k.name()).register(registry)).increment(); - Timer.Sample sample = Timer.start(registry); - ctx.attr(ATTR_TIMER_SAMPLE, sample); - return true; - } - - @Override - public void after(VehicleEvent event, IngestContext ctx) { - String key = event.source().name() + ":" + event.getClass().getSimpleName(); - eventCounters.computeIfAbsent(key, k -> { - String[] parts = k.split(":"); - return Counter.builder("ingest_events_total") - .tags(Tags.of("protocol", parts[0], "type", parts[1])) - .register(registry); - }).increment(); - - Timer.Sample s = ctx.attr(ATTR_TIMER_SAMPLE); - if (s != null) { - stopTimer(ctx, protocolTag(ctx)); - } - } - - @Override - public void onError(Throwable error, IngestContext ctx) { - String protocol = protocolTag(ctx); - errorCounters.computeIfAbsent(protocol, p -> - Counter.builder("ingest_errors_total").tag("protocol", p).register(registry)).increment(); - stopTimer(ctx, protocol); - } - - @Override - public int getOrder() { - return 10; // 尽早介入,晚于 Tracing(order=0) - } - - private void stopTimer(IngestContext ctx, String protocol) { - if (ctx == null) { - return; - } - Timer.Sample sample = ctx.attr(ATTR_TIMER_SAMPLE); - if (sample == null) { - return; - } - Timer timer = frameTimers.computeIfAbsent(protocol, p -> - Timer.builder("ingest_frame_duration_seconds").tag("protocol", p).register(registry)); - sample.stop(timer); - ctx.attr(ATTR_TIMER_SAMPLE, null); - } - - private static String protocolTag(IngestContext ctx) { - if (ctx == null) { - return "unknown"; - } - ProtocolId protocol = ctx.attr(ATTR_PROTOCOL); - return protocol == null || protocol == ProtocolId.UNKNOWN ? "unknown" : protocol.name(); - } -} diff --git a/modules/core/observability/src/main/java/com/lingniu/ingest/observability/ObservabilityAutoConfiguration.java b/modules/core/observability/src/main/java/com/lingniu/ingest/observability/ObservabilityAutoConfiguration.java deleted file mode 100644 index a6a5f63f..00000000 --- a/modules/core/observability/src/main/java/com/lingniu/ingest/observability/ObservabilityAutoConfiguration.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.lingniu.ingest.observability; - -import io.micrometer.core.instrument.MeterRegistry; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; - -@AutoConfiguration -@ConditionalOnBean(MeterRegistry.class) -public class ObservabilityAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public IngestMetrics ingestMetrics(MeterRegistry registry) { - return new IngestMetrics(registry); - } -} diff --git a/modules/core/observability/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/core/observability/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index b3367068..00000000 --- a/modules/core/observability/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.observability.ObservabilityAutoConfiguration diff --git a/modules/core/observability/src/test/java/com/lingniu/ingest/observability/IngestMetricsTest.java b/modules/core/observability/src/test/java/com/lingniu/ingest/observability/IngestMetricsTest.java deleted file mode 100644 index cb44aa7d..00000000 --- a/modules/core/observability/src/test/java/com/lingniu/ingest/observability/IngestMetricsTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.lingniu.ingest.observability; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.RawFrame; -import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class IngestMetricsTest { - - @Test - void countsErrorsAgainstTheFrameProtocolCapturedInBefore() { - SimpleMeterRegistry registry = new SimpleMeterRegistry(); - IngestMetrics metrics = new IngestMetrics(registry); - IngestContext context = new IngestContext("trace-1"); - - metrics.before(frame(ProtocolId.JT808), context); - metrics.onError(new IllegalStateException("boom"), context); - - assertThat(registry.counter("ingest_errors_total", "protocol", "JT808").count()) - .isEqualTo(1.0); - assertThat(registry.find("ingest_errors_total").tag("protocol", "unknown").counter()) - .isNull(); - } - - @Test - void recordsFrameDurationWhenProcessingFailsAfterBefore() { - SimpleMeterRegistry registry = new SimpleMeterRegistry(); - IngestMetrics metrics = new IngestMetrics(registry); - IngestContext context = new IngestContext("trace-2"); - - metrics.before(frame(ProtocolId.MQTT_YUTONG), context); - metrics.onError(new RuntimeException("boom"), context); - - assertThat(registry.timer("ingest_frame_duration_seconds", "protocol", "MQTT_YUTONG").count()) - .isEqualTo(1); - } - - @Test - void recordsFrameDurationAgainstTheFrameProtocolCapturedInBefore() { - SimpleMeterRegistry registry = new SimpleMeterRegistry(); - IngestMetrics metrics = new IngestMetrics(registry); - IngestContext context = new IngestContext("trace-4"); - - metrics.before(frame(ProtocolId.JT808), context); - metrics.after(new VehicleEvent.Heartbeat( - "event-1", - "VIN001", - ProtocolId.UNKNOWN, - Instant.parse("2026-07-01T00:00:01Z"), - Instant.parse("2026-07-01T00:00:02Z"), - "trace-4", - Map.of()), context); - - assertThat(registry.timer("ingest_frame_duration_seconds", "protocol", "JT808").count()) - .isEqualTo(1); - assertThat(registry.find("ingest_frame_duration_seconds").tag("protocol", "unknown").timer()) - .isNull(); - } - - @Test - void countsErrorsAsUnknownWhenThereIsNoFrameProtocolContext() { - SimpleMeterRegistry registry = new SimpleMeterRegistry(); - IngestMetrics metrics = new IngestMetrics(registry); - - metrics.onError(new IllegalStateException("boom"), new IngestContext("trace-3")); - - assertThat(registry.counter("ingest_errors_total", "protocol", "unknown").count()) - .isEqualTo(1.0); - } - - private static RawFrame frame(ProtocolId protocolId) { - return new RawFrame( - protocolId, - 0x0200, - 0, - null, - new byte[]{0x01}, - Map.of(), - Instant.parse("2026-07-01T00:00:00Z")); - } -} diff --git a/modules/core/session-core/pom.xml b/modules/core/session-core/pom.xml deleted file mode 100644 index d02dbe36..00000000 --- a/modules/core/session-core/pom.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - session-core - session-core - 设备会话 + 鉴权 + Token + Redis SessionStore。 - - - - com.lingniu.ingest - ingest-api - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-autoconfigure - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.mockito - mockito-core - test - - - org.springframework.boot - spring-boot-test - test - - - diff --git a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/CommandDispatcher.java b/modules/core/session-core/src/main/java/com/lingniu/ingest/session/CommandDispatcher.java deleted file mode 100644 index 30958922..00000000 --- a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/CommandDispatcher.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lingniu.ingest.session; - -import java.time.Duration; -import java.util.concurrent.CompletableFuture; - -/** - * 下行命令调度器 SPI。 - * - *

用于 command-gateway 把 HTTP 请求转为设备命令,等待设备应答。真正的实现需要与协议 - * 模块(如 jt808)的 Netty 通道绑定:按 sessionId 找到 Channel → 发送命令字节 → 用 - * {@code CompletableFuture + 序列号 map} 实现同步请求/应答。 - * - *

当前阶段提供接口 + 占位实现,具体 Netty 绑定在 command-gateway / protocol-jt808 - * 的下一批次落地。 - */ -public interface CommandDispatcher { - - /** 异步下发命令,不等应答。 */ - CompletableFuture notify(String sessionId, Object command); - - /** 同步下发命令并等待应答。 */ - CompletableFuture request(String sessionId, Object command, Class responseType, Duration timeout); -} diff --git a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/DeviceSession.java b/modules/core/session-core/src/main/java/com/lingniu/ingest/session/DeviceSession.java deleted file mode 100644 index b2261608..00000000 --- a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/DeviceSession.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.lingniu.ingest.session; - -import com.lingniu.ingest.api.ProtocolId; - -import java.time.Instant; -import java.util.Map; - -/** - * 设备会话领域模型。不可变;更新通过 {@link SessionStore#update} 原子替换。 - * - * @param sessionId 会话 ID(通常由 Netty channel id + 终端号生成) - * @param protocol 协议 ID - * @param vin 车架号(注册/鉴权后补齐,注册前为 null) - * @param phone 终端手机号 / 设备号 - * @param token 鉴权 Token(平台下发) - * @param peerAddress 对端 IP:port - * @param registeredAt 注册时间 - * @param lastSeenAt 最后活跃时间 - * @param attributes 协议特定属性(协议版本、型号等) - */ -public record DeviceSession( - String sessionId, - ProtocolId protocol, - String vin, - String phone, - String token, - String peerAddress, - Instant registeredAt, - Instant lastSeenAt, - Map attributes -) { - public DeviceSession touch(Instant now) { - return new DeviceSession(sessionId, protocol, vin, phone, token, peerAddress, - registeredAt, now, attributes); - } - - public DeviceSession withVin(String vin) { - return new DeviceSession(sessionId, protocol, vin, phone, token, peerAddress, - registeredAt, lastSeenAt, attributes); - } - - public DeviceSession withToken(String token) { - return new DeviceSession(sessionId, protocol, vin, phone, token, peerAddress, - registeredAt, lastSeenAt, attributes); - } -} diff --git a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/NoopCommandDispatcher.java b/modules/core/session-core/src/main/java/com/lingniu/ingest/session/NoopCommandDispatcher.java deleted file mode 100644 index 6539dabe..00000000 --- a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/NoopCommandDispatcher.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.lingniu.ingest.session; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.util.concurrent.CompletableFuture; - -/** - * 占位命令调度器:返回 {@link UnsupportedOperationException}, - * 待 command-gateway 与协议模块的下行通道打通后替换为真实实现。 - */ -public final class NoopCommandDispatcher implements CommandDispatcher { - - private static final Logger log = LoggerFactory.getLogger(NoopCommandDispatcher.class); - - @Override - public CompletableFuture notify(String sessionId, Object command) { - log.warn("[noop] notify sessionId={} command={} dropped", sessionId, command); - return CompletableFuture.failedFuture(new UnsupportedOperationException( - "CommandDispatcher not yet implemented; configure a real one in protocol module.")); - } - - @Override - public CompletableFuture request(String sessionId, Object command, Class responseType, Duration timeout) { - log.warn("[noop] request sessionId={} command={} responseType={} dropped", - sessionId, command, responseType.getSimpleName()); - return CompletableFuture.failedFuture(new UnsupportedOperationException( - "CommandDispatcher not yet implemented; configure a real one in protocol module.")); - } -} diff --git a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/RedisSessionStore.java b/modules/core/session-core/src/main/java/com/lingniu/ingest/session/RedisSessionStore.java deleted file mode 100644 index 90a86457..00000000 --- a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/RedisSessionStore.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.lingniu.ingest.session; - -import com.lingniu.ingest.api.ProtocolId; -import org.springframework.data.redis.core.StringRedisTemplate; - -import java.time.Duration; -import java.time.Instant; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.UnaryOperator; - -/** - * Redis-backed session store for multi-instance command routing metadata. - * - *

The Netty Channel itself stays local to the protocol instance. Redis keeps - * the stable terminal indexes so HTTP callers can resolve sessionId / VIN / - * phone consistently across restarts and replicas. - */ -public final class RedisSessionStore implements SessionStore { - - private static final String PREFIX = "vehicle:session:"; - private static final String ID_SET_KEY = PREFIX + "index:ids"; - - private final StringRedisTemplate redis; - private final Duration ttl; - - public RedisSessionStore(StringRedisTemplate redis, Duration ttl) { - this.redis = redis; - this.ttl = ttl; - } - - @Override - public void put(DeviceSession session) { - // 同一个 sessionId 重新写入时先清理旧 VIN/phone 索引,避免旧外部号继续指向新会话。 - findBySessionId(session.sessionId()).ifPresent(old -> deleteIndexes(old)); - redis.opsForHash().putAll(sessionKey(session.sessionId()), toHash(session)); - redis.expire(sessionKey(session.sessionId()), ttl); - if (hasText(session.vin())) { - redis.opsForValue().set(vinIndexKey(session.vin()), session.sessionId(), ttl); - } - if (hasText(session.phone())) { - redis.opsForValue().set(phoneIndexKey(session.phone()), session.sessionId(), ttl); - } - redis.opsForSet().add(ID_SET_KEY, session.sessionId()); - redis.expire(ID_SET_KEY, ttl); - } - - @Override - public Optional findBySessionId(String sessionId) { - if (!hasText(sessionId)) { - return Optional.empty(); - } - Map values = redis.opsForHash().entries(sessionKey(sessionId)); - if (values == null || values.isEmpty()) { - return Optional.empty(); - } - redis.expire(sessionKey(sessionId), ttl); - return Optional.of(fromHash(values)); - } - - @Override - public Optional findByVin(String vin) { - if (!hasText(vin)) { - return Optional.empty(); - } - String sessionId = redis.opsForValue().get(vinIndexKey(vin)); - Optional session = findBySessionId(sessionId); - if (session.isEmpty() && hasText(sessionId)) { - // 二级索引可能比主 hash 晚过期;发现悬挂索引时立即清理。 - redis.delete(vinIndexKey(vin)); - } - return session; - } - - @Override - public Optional findByPhone(String phone) { - if (!hasText(phone)) { - return Optional.empty(); - } - String sessionId = redis.opsForValue().get(phoneIndexKey(phone)); - Optional session = findBySessionId(sessionId); - if (session.isEmpty() && hasText(sessionId)) { - // 二级索引可能比主 hash 晚过期;发现悬挂索引时立即清理。 - redis.delete(phoneIndexKey(phone)); - } - return session; - } - - @Override - public synchronized Optional update(String sessionId, UnaryOperator updater) { - Optional current = findBySessionId(sessionId); - if (current.isEmpty()) { - return Optional.empty(); - } - DeviceSession next = updater.apply(current.orElseThrow()); - put(next); - return Optional.of(next); - } - - @Override - public void remove(String sessionId) { - Optional current = findBySessionId(sessionId); - redis.delete(sessionKey(sessionId)); - current.ifPresent(this::deleteIndexes); - redis.opsForSet().remove(ID_SET_KEY, sessionId); - } - - @Override - public int size() { - Long size = redis.opsForSet().size(ID_SET_KEY); - if (size == null || size <= 0) { - return 0; - } - return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : size.intValue(); - } - - private void deleteIndexes(DeviceSession session) { - if (hasText(session.vin())) { - redis.delete(vinIndexKey(session.vin())); - } - if (hasText(session.phone())) { - redis.delete(phoneIndexKey(session.phone())); - } - } - - private static Map toHash(DeviceSession session) { - Map values = new LinkedHashMap<>(); - // attr.* 保留协议侧附加字段,后续扩展不需要改 Redis schema。 - put(values, "sessionId", session.sessionId()); - put(values, "protocol", session.protocol() == null ? null : session.protocol().name()); - put(values, "vin", session.vin()); - put(values, "phone", session.phone()); - put(values, "token", session.token()); - put(values, "peerAddress", session.peerAddress()); - put(values, "registeredAt", session.registeredAt() == null ? null : session.registeredAt().toString()); - put(values, "lastSeenAt", session.lastSeenAt() == null ? null : session.lastSeenAt().toString()); - if (session.attributes() != null) { - session.attributes().forEach((key, value) -> put(values, "attr." + key, value)); - } - return values; - } - - private static DeviceSession fromHash(Map values) { - Map attributes = new LinkedHashMap<>(); - values.forEach((key, value) -> { - String k = String.valueOf(key); - if (k.startsWith("attr.") && value != null) { - attributes.put(k.substring("attr.".length()), String.valueOf(value)); - } - }); - return new DeviceSession( - value(values, "sessionId"), - ProtocolId.valueOf(value(values, "protocol")), - value(values, "vin"), - value(values, "phone"), - value(values, "token"), - value(values, "peerAddress"), - Instant.parse(value(values, "registeredAt")), - Instant.parse(value(values, "lastSeenAt")), - Map.copyOf(attributes)); - } - - private static void put(Map values, String key, String value) { - if (value != null) { - values.put(key, value); - } - } - - private static String value(Map values, String key) { - Object value = values.get(key); - return value == null ? null : String.valueOf(value); - } - - private static String sessionKey(String sessionId) { - return PREFIX + sessionId; - } - - private static String vinIndexKey(String vin) { - return PREFIX + "index:vin:" + vin; - } - - private static String phoneIndexKey(String phone) { - return PREFIX + "index:phone:" + phone; - } - - private static boolean hasText(String value) { - return value != null && !value.isBlank(); - } -} diff --git a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionProperties.java b/modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionProperties.java deleted file mode 100644 index d44f1abb..00000000 --- a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionProperties.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.lingniu.ingest.session; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.time.Duration; -import java.util.Locale; - -@ConfigurationProperties(prefix = "lingniu.ingest.session") -public class SessionProperties { - - private String store = "redis"; - - private Duration ttl = Duration.ofMinutes(30); - - public String getStore() { - return store; - } - - public void setStore(String store) { - String value = store == null || store.isBlank() ? "redis" : store.trim().toLowerCase(Locale.ROOT); - if (!"redis".equals(value)) { - throw new IllegalStateException("session.store supports only redis; configured value: " + store); - } - this.store = value; - } - - public Duration getTtl() { - return ttl; - } - - public void setTtl(Duration ttl) { - this.ttl = ttl; - } - -} diff --git a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionStore.java b/modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionStore.java deleted file mode 100644 index 38811206..00000000 --- a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/SessionStore.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lingniu.ingest.session; - -import java.util.Optional; -import java.util.function.UnaryOperator; - -/** - * 设备会话存储 SPI。生产自动配置只创建 Redis 实现;测试可显式提供轻量实现。 - */ -public interface SessionStore { - - void put(DeviceSession session); - - Optional findBySessionId(String sessionId); - - Optional findByVin(String vin); - - Optional findByPhone(String phone); - - /** 原子更新:读 → 修改 → 写。返回更新后的会话;若原会话不存在返回 empty。 */ - Optional update(String sessionId, UnaryOperator updater); - - void remove(String sessionId); - - int size(); -} diff --git a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/config/SessionCoreAutoConfiguration.java b/modules/core/session-core/src/main/java/com/lingniu/ingest/session/config/SessionCoreAutoConfiguration.java deleted file mode 100644 index 29f9497e..00000000 --- a/modules/core/session-core/src/main/java/com/lingniu/ingest/session/config/SessionCoreAutoConfiguration.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.lingniu.ingest.session.config; - -import com.lingniu.ingest.session.CommandDispatcher; -import com.lingniu.ingest.session.NoopCommandDispatcher; -import com.lingniu.ingest.session.RedisSessionStore; -import com.lingniu.ingest.session.SessionProperties; -import com.lingniu.ingest.session.SessionStore; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.core.StringRedisTemplate; - -@AutoConfiguration(after = RedisAutoConfiguration.class) -@EnableConfigurationProperties(SessionProperties.class) -public class SessionCoreAutoConfiguration { - - @Bean - @ConditionalOnBean(StringRedisTemplate.class) - @ConditionalOnProperty(prefix = "lingniu.ingest.session", name = "store", havingValue = "redis", - matchIfMissing = true) - @ConditionalOnMissingBean - public SessionStore redisSessionStore(StringRedisTemplate redis, SessionProperties properties) { - // Redis 只保存会话元数据索引;真实 Netty Channel 仍在各协议进程本地内存中。 - return new RedisSessionStore(redis, properties.getTtl()); - } - - @Bean - @ConditionalOnMissingBean - public CommandDispatcher commandDispatcher() { - // 没有协议模块提供真实下行实现时使用 Noop,避免仅接收/查询部署因为缺 Bean 启动失败。 - return new NoopCommandDispatcher(); - } -} diff --git a/modules/core/session-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/core/session-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 0f53ea0f..00000000 --- a/modules/core/session-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.session.config.SessionCoreAutoConfiguration diff --git a/modules/core/session-core/src/test/java/com/lingniu/ingest/session/RedisSessionStoreTest.java b/modules/core/session-core/src/test/java/com/lingniu/ingest/session/RedisSessionStoreTest.java deleted file mode 100644 index 1b4df081..00000000 --- a/modules/core/session-core/src/test/java/com/lingniu/ingest/session/RedisSessionStoreTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.lingniu.ingest.session; - -import com.lingniu.ingest.api.ProtocolId; -import org.junit.jupiter.api.Test; -import org.springframework.data.redis.core.HashOperations; -import org.springframework.data.redis.core.SetOperations; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.core.ValueOperations; - -import java.time.Duration; -import java.time.Instant; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class RedisSessionStoreTest { - - @Test - void putStoresSessionHashAndThreeIndexesWithTtl() { - RedisFixture fixture = new RedisFixture(); - RedisSessionStore store = new RedisSessionStore(fixture.redis, Duration.ofMinutes(45)); - DeviceSession session = sampleSession("sid-1", "VIN001", "13800138000"); - - store.put(session); - - verify(fixture.hashOps).putAll(eq("vehicle:session:sid-1"), any(Map.class)); - verify(fixture.valueOps).set("vehicle:session:index:vin:VIN001", "sid-1", Duration.ofMinutes(45)); - verify(fixture.valueOps).set("vehicle:session:index:phone:13800138000", "sid-1", Duration.ofMinutes(45)); - verify(fixture.setOps).add("vehicle:session:index:ids", "sid-1"); - verify(fixture.redis).expire("vehicle:session:sid-1", Duration.ofMinutes(45)); - verify(fixture.redis).expire("vehicle:session:index:ids", Duration.ofMinutes(45)); - } - - @Test - void findsBySessionIdVinAndPhoneFromRedisIndexes() { - RedisFixture fixture = new RedisFixture(); - RedisSessionStore store = new RedisSessionStore(fixture.redis, Duration.ofMinutes(30)); - Map stored = storedSession("sid-1", "VIN001", "13800138000"); - when(fixture.hashOps.entries("vehicle:session:sid-1")).thenReturn(stored); - when(fixture.valueOps.get("vehicle:session:index:vin:VIN001")).thenReturn("sid-1"); - when(fixture.valueOps.get("vehicle:session:index:phone:13800138000")).thenReturn("sid-1"); - - Optional byId = store.findBySessionId("sid-1"); - Optional byVin = store.findByVin("VIN001"); - Optional byPhone = store.findByPhone("13800138000"); - - assertThat(byId).isPresent(); - assertThat(byVin).contains(byId.orElseThrow()); - assertThat(byPhone).contains(byId.orElseThrow()); - assertThat(byId.orElseThrow().attributes()).containsEntry("identitySource", "BINDING"); - verify(fixture.redis, atLeastOnce()).expire("vehicle:session:sid-1", Duration.ofMinutes(30)); - } - - @Test - void updateRewritesIndexesWhenVinChangesAndRemoveDeletesAllKeys() { - RedisFixture fixture = new RedisFixture(); - RedisSessionStore store = new RedisSessionStore(fixture.redis, Duration.ofMinutes(30)); - when(fixture.hashOps.entries("vehicle:session:sid-1")) - .thenReturn(storedSession("sid-1", "VIN001", "13800138000")) - .thenReturn(storedSession("sid-1", "VIN001", "13800138000")) - .thenReturn(storedSession("sid-1", "VIN002", "13800138000")); - - Optional updated = store.update("sid-1", session -> session.withVin("VIN002")); - store.remove("sid-1"); - - assertThat(updated).isPresent(); - verify(fixture.redis).delete("vehicle:session:index:vin:VIN001"); - verify(fixture.valueOps).set("vehicle:session:index:vin:VIN002", "sid-1", Duration.ofMinutes(30)); - verify(fixture.redis).delete("vehicle:session:sid-1"); - verify(fixture.redis).delete("vehicle:session:index:vin:VIN002"); - verify(fixture.redis, atLeastOnce()).delete("vehicle:session:index:phone:13800138000"); - verify(fixture.setOps).remove("vehicle:session:index:ids", "sid-1"); - } - - @Test - void sizeReturnsRedisIdSetCardinality() { - RedisFixture fixture = new RedisFixture(); - RedisSessionStore store = new RedisSessionStore(fixture.redis, Duration.ofMinutes(30)); - when(fixture.setOps.size("vehicle:session:index:ids")).thenReturn(12L); - - assertThat(store.size()).isEqualTo(12); - } - - private static DeviceSession sampleSession(String sid, String vin, String phone) { - return new DeviceSession( - sid, - ProtocolId.JT808, - vin, - phone, - "token-1", - "127.0.0.1:9000", - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:01:00Z"), - Map.of("identitySource", "BINDING")); - } - - private static Map storedSession(String sid, String vin, String phone) { - Map values = new LinkedHashMap<>(); - values.put("sessionId", sid); - values.put("protocol", "JT808"); - values.put("vin", vin); - values.put("phone", phone); - values.put("token", "token-1"); - values.put("peerAddress", "127.0.0.1:9000"); - values.put("registeredAt", "2026-06-22T08:00:00Z"); - values.put("lastSeenAt", "2026-06-22T08:01:00Z"); - values.put("attr.identitySource", "BINDING"); - return values; - } - - private static final class RedisFixture { - private final StringRedisTemplate redis = mock(StringRedisTemplate.class); - @SuppressWarnings("unchecked") - private final HashOperations hashOps = mock(HashOperations.class); - @SuppressWarnings("unchecked") - private final ValueOperations valueOps = mock(ValueOperations.class); - @SuppressWarnings("unchecked") - private final SetOperations setOps = mock(SetOperations.class); - - private RedisFixture() { - when(redis.opsForHash()).thenReturn(hashOps); - when(redis.opsForValue()).thenReturn(valueOps); - when(redis.opsForSet()).thenReturn(setOps); - when(hashOps.entries("vehicle:session:sid-1")).thenReturn(Map.of()); - when(setOps.members("vehicle:session:index:ids")).thenReturn(Set.of()); - } - } -} diff --git a/modules/core/session-core/src/test/java/com/lingniu/ingest/session/config/SessionCoreAutoConfigurationTest.java b/modules/core/session-core/src/test/java/com/lingniu/ingest/session/config/SessionCoreAutoConfigurationTest.java deleted file mode 100644 index 2d13d90d..00000000 --- a/modules/core/session-core/src/test/java/com/lingniu/ingest/session/config/SessionCoreAutoConfigurationTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.lingniu.ingest.session.config; - -import com.lingniu.ingest.session.RedisSessionStore; -import com.lingniu.ingest.session.SessionStore; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.data.redis.core.StringRedisTemplate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; - -class SessionCoreAutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SessionCoreAutoConfiguration.class)); - - @Test - void doesNotCreateImplicitInMemorySessionStoreByDefault() { - contextRunner.run(context -> { - assertThat(context).doesNotHaveBean(SessionStore.class); - }); - } - - @Test - void doesNotPublishInMemorySessionStoreImplementation() { - assertThatThrownBy(() -> Class.forName("com.lingniu.ingest.session.InMemorySessionStore")) - .isInstanceOf(ClassNotFoundException.class); - } - - @Test - void usesRedisSessionStoreWhenEnabledAndRedisTemplateExists() { - contextRunner - .withBean(StringRedisTemplate.class, () -> mock(StringRedisTemplate.class)) - .withPropertyValues( - "lingniu.ingest.session.store=redis", - "lingniu.ingest.session.ttl=45m") - .run(context -> { - assertThat(context).hasSingleBean(SessionStore.class); - assertThat(context).hasSingleBean(RedisSessionStore.class); - }); - } - - @Test - void usesRedisSessionStoreByDefaultWhenRedisTemplateExists() { - contextRunner - .withBean(StringRedisTemplate.class, () -> mock(StringRedisTemplate.class)) - .run(context -> { - assertThat(context).hasSingleBean(SessionStore.class); - assertThat(context).hasSingleBean(RedisSessionStore.class); - }); - } - - @Test - void waitsForRedisAutoConfigurationBeforeCheckingRedisTemplateBean() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - SessionCoreAutoConfiguration.class, - RedisAutoConfiguration.class)) - .run(context -> { - assertThat(context).hasSingleBean(StringRedisTemplate.class); - assertThat(context).hasSingleBean(SessionStore.class); - assertThat(context).hasSingleBean(RedisSessionStore.class); - }); - } - - @Test - void rejectsMemorySessionStoreModeWithRedisOnlyRule() { - contextRunner - .withPropertyValues("lingniu.ingest.session.store=memory") - .run(context -> assertThat(context.getStartupFailure()) - .hasRootCauseInstanceOf(IllegalStateException.class) - .hasStackTraceContaining("session.store supports only redis") - .hasStackTraceContaining("memory")); - } - - @Test - void sessionPropertiesDoNotExposeLegacyStoreEnum() { - assertThatThrownBy(() -> Class.forName("com.lingniu.ingest.session.SessionProperties$Store")) - .isInstanceOf(ClassNotFoundException.class); - } -} diff --git a/modules/core/vehicle-identity/pom.xml b/modules/core/vehicle-identity/pom.xml deleted file mode 100644 index 2c6b9ad2..00000000 --- a/modules/core/vehicle-identity/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - vehicle-identity - vehicle-identity - 跨协议车辆身份解析与外部标识绑定。 - - - - com.lingniu.ingest - ingest-api - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-autoconfigure - - - org.springframework - spring-jdbc - - - com.fasterxml.jackson.core - jackson-databind - - - com.mysql - mysql-connector-j - runtime - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.springframework.boot - spring-boot-test - test - - - com.h2database - h2 - test - - - diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/MySqlVehicleIdentityService.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/MySqlVehicleIdentityService.java deleted file mode 100644 index 90336fec..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/MySqlVehicleIdentityService.java +++ /dev/null @@ -1,232 +0,0 @@ -package com.lingniu.ingest.identity; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.identity.config.VehicleIdentityProperties; -import org.springframework.jdbc.core.ConnectionCallback; -import org.springframework.jdbc.core.JdbcTemplate; - -import javax.sql.DataSource; -import java.sql.DatabaseMetaData; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -public final class MySqlVehicleIdentityService implements VehicleIdentityResolver, VehicleIdentityRegistry { - - private final JdbcTemplate jdbc; - private final ObjectMapper mapper; - private final String tableName; - - public MySqlVehicleIdentityService(DataSource dataSource, - VehicleIdentityProperties.Mysql properties, - ObjectMapper mapper) { - this.jdbc = new JdbcTemplate(dataSource); - this.mapper = mapper == null ? new ObjectMapper() : mapper; - VehicleIdentityProperties.Mysql mysql = properties == null ? new VehicleIdentityProperties.Mysql() : properties; - this.tableName = safeTableName(mysql.getTableName()); - if (mysql.isInitializeSchema()) { - initializeSchema(); - } - } - - @Override - public void bind(VehicleIdentityBinding binding) { - String metadataJson = metadataJson(binding.metadata()); - jdbc.update(""" - INSERT INTO %s - (protocol, vin, phone, device_id, plate, province, city, maker, device_type, plate_color, metadata_json, - created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) - ON DUPLICATE KEY UPDATE - vin = VALUES(vin), - device_id = VALUES(device_id), - province = VALUES(province), - city = VALUES(city), - maker = VALUES(maker), - device_type = VALUES(device_type), - plate_color = VALUES(plate_color), - metadata_json = VALUES(metadata_json), - updated_at = CURRENT_TIMESTAMP - """.formatted(tableName), - protocol(binding.protocol()), binding.vin(), binding.phone(), binding.deviceId(), binding.plate(), - binding.metadata().getOrDefault("province", ""), - binding.metadata().getOrDefault("city", ""), - binding.metadata().getOrDefault("maker", ""), - binding.metadata().getOrDefault("deviceType", ""), - binding.metadata().getOrDefault("plateColor", ""), - metadataJson); - } - - @Override - public VehicleIdentity resolve(VehicleIdentityLookup lookup) { - if (lookup == null) { - return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); - } - if (!lookup.vin().isBlank()) { - return new VehicleIdentity(lookup.vin(), true, VehicleIdentitySource.EXPLICIT_VIN); - } - VehicleIdentity byPhone = resolveBy("phone", lookup.protocol(), lookup.phone(), VehicleIdentitySource.BOUND_PHONE); - if (byPhone != null) { - return byPhone; - } - VehicleIdentity byDevice = resolveBy("device_id", lookup.protocol(), lookup.deviceId(), VehicleIdentitySource.BOUND_DEVICE_ID); - if (byDevice != null) { - return byDevice; - } - VehicleIdentity byPlate = resolveBy("plate", lookup.protocol(), lookup.plate(), VehicleIdentitySource.BOUND_PLATE); - if (byPlate != null) { - return byPlate; - } - 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 new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); - } - - private VehicleIdentity resolveBy(String column, ProtocolId protocol, String externalId, VehicleIdentitySource source) { - if (externalId == null || externalId.isBlank()) { - return null; - } - String safeColumn = switch (column) { - case "phone", "device_id", "plate" -> column; - default -> throw new IllegalArgumentException("unsupported identity lookup column: " + column); - }; - List matches = jdbc.query(""" - SELECT vin - FROM %s - WHERE protocol = ? AND %s = ? - ORDER BY updated_at DESC - LIMIT 1 - """.formatted(tableName, safeColumn), - (rs, rowNum) -> identity(rs, source), - protocol(protocol), externalId.trim()); - return matches.isEmpty() ? null : matches.getFirst(); - } - - private VehicleIdentity identity(ResultSet rs, VehicleIdentitySource source) throws SQLException { - return new VehicleIdentity(rs.getString("vin"), true, source); - } - - private void initializeSchema() { - jdbc.execute(""" - CREATE TABLE IF NOT EXISTS %s ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - protocol VARCHAR(32) NOT NULL, - vin VARCHAR(32) NOT NULL, - phone VARCHAR(64) NOT NULL DEFAULT '', - device_id VARCHAR(64) NOT NULL DEFAULT '', - plate VARCHAR(64) NOT NULL DEFAULT '', - province VARCHAR(32) NOT NULL DEFAULT '', - city VARCHAR(32) NOT NULL DEFAULT '', - maker VARCHAR(64) NOT NULL DEFAULT '', - device_type VARCHAR(128) NOT NULL DEFAULT '', - plate_color VARCHAR(32) NOT NULL DEFAULT '', - metadata_json TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE KEY uk_vehicle_identity_protocol_phone (protocol, phone), - KEY idx_vehicle_identity_protocol_device (protocol, device_id), - KEY idx_vehicle_identity_protocol_plate (protocol, plate) - ) - """.formatted(tableName)); - ensureColumn("phone", "phone VARCHAR(64) NOT NULL DEFAULT ''"); - ensureColumn("device_id", "device_id VARCHAR(64) NOT NULL DEFAULT ''"); - ensureColumn("plate", "plate VARCHAR(64) NOT NULL DEFAULT ''"); - ensureColumn("province", "province VARCHAR(32) NOT NULL DEFAULT ''"); - ensureColumn("city", "city VARCHAR(32) NOT NULL DEFAULT ''"); - ensureColumn("maker", "maker VARCHAR(64) NOT NULL DEFAULT ''"); - ensureColumn("device_type", "device_type VARCHAR(128) NOT NULL DEFAULT ''"); - ensureColumn("plate_color", "plate_color VARCHAR(32) NOT NULL DEFAULT ''"); - ensureColumn("metadata_json", "metadata_json TEXT"); - ensureColumn("created_at", "created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"); - ensureColumn("updated_at", "updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"); - ensureIndex("idx_vehicle_identity_protocol_device", "CREATE INDEX idx_vehicle_identity_protocol_device ON %s (protocol, device_id)"); - ensureIndex("idx_vehicle_identity_protocol_plate", "CREATE INDEX idx_vehicle_identity_protocol_plate ON %s (protocol, plate)"); - } - - private void ensureColumn(String columnName, String definition) { - if (!columnExists(columnName)) { - jdbc.execute("ALTER TABLE " + tableName + " ADD COLUMN " + definition); - } - } - - private boolean columnExists(String columnName) { - return Boolean.TRUE.equals(jdbc.execute((ConnectionCallback) connection -> { - DatabaseMetaData metaData = connection.getMetaData(); - for (String table : nameVariants(tableName)) { - for (String column : nameVariants(columnName)) { - try (ResultSet rs = metaData.getColumns(null, null, table, column)) { - if (rs.next()) { - return true; - } - } - } - } - return false; - })); - } - - private void ensureIndex(String indexName, String ddlTemplate) { - if (!indexExists(indexName)) { - jdbc.execute(ddlTemplate.formatted(tableName)); - } - } - - private boolean indexExists(String indexName) { - return Boolean.TRUE.equals(jdbc.execute((ConnectionCallback) connection -> { - DatabaseMetaData metaData = connection.getMetaData(); - for (String table : nameVariants(tableName)) { - try (ResultSet rs = metaData.getIndexInfo(null, null, table, false, false)) { - while (rs.next()) { - String existing = rs.getString("INDEX_NAME"); - if (existing != null && existing.equalsIgnoreCase(indexName)) { - return true; - } - } - } - } - return false; - })); - } - - private static String[] nameVariants(String name) { - return new String[]{ - name, - name.toLowerCase(Locale.ROOT), - name.toUpperCase(Locale.ROOT) - }; - } - - private String metadataJson(Map metadata) { - if (metadata == null || metadata.isEmpty()) { - return "{}"; - } - try { - return mapper.writeValueAsString(metadata); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("vehicle identity metadata serialize failed", e); - } - } - - private static String protocol(ProtocolId protocol) { - return protocol == null ? "UNKNOWN" : protocol.name(); - } - - private static String safeTableName(String value) { - String name = value == null || value.isBlank() ? "vehicle_identity_bindings" : value.trim(); - if (!name.matches("[A-Za-z0-9_]+")) { - throw new IllegalArgumentException("invalid vehicle identity mysql table name: " + value); - } - return name.toLowerCase(Locale.ROOT); - } -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentity.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentity.java deleted file mode 100644 index 06449c86..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentity.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.lingniu.ingest.identity; - -public record VehicleIdentity( - String vin, - boolean resolved, - VehicleIdentitySource source -) { - public VehicleIdentity { - vin = normalize(vin); - source = source == null ? VehicleIdentitySource.UNKNOWN : source; - } - - private static String normalize(String value) { - return value == null ? "" : value.trim(); - } -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityBinding.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityBinding.java deleted file mode 100644 index 3a31a26a..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityBinding.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.lingniu.ingest.identity; - -import com.lingniu.ingest.api.ProtocolId; - -import java.util.Map; - -public record VehicleIdentityBinding( - ProtocolId protocol, - String vin, - String phone, - String deviceId, - String plate, - Map metadata -) { - public VehicleIdentityBinding(ProtocolId protocol, String vin, String phone, String deviceId, String plate) { - this(protocol, vin, phone, deviceId, plate, Map.of()); - } - - public VehicleIdentityBinding { - vin = normalize(vin); - phone = normalize(phone); - deviceId = normalize(deviceId); - plate = normalize(plate); - metadata = metadata == null ? Map.of() : Map.copyOf(metadata); - if (vin.isBlank()) { - throw new IllegalArgumentException("vin must not be blank"); - } - } - - public boolean hasResolvedVin() { - return !"unknown".equalsIgnoreCase(vin); - } - - private static String normalize(String value) { - return value == null ? "" : value.trim(); - } -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityLookup.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityLookup.java deleted file mode 100644 index 4d9ea38c..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityLookup.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.lingniu.ingest.identity; - -import com.lingniu.ingest.api.ProtocolId; - -public record VehicleIdentityLookup( - ProtocolId protocol, - String vin, - String phone, - String deviceId, - String plate -) { - public VehicleIdentityLookup { - vin = normalize(vin); - phone = normalize(phone); - deviceId = normalize(deviceId); - plate = normalize(plate); - } - - private static String normalize(String value) { - return value == null ? "" : value.trim(); - } -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityRegistry.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityRegistry.java deleted file mode 100644 index d15808c5..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityRegistry.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.lingniu.ingest.identity; - -public interface VehicleIdentityRegistry { - - void bind(VehicleIdentityBinding binding); - - default void register(VehicleRegistrationBinding registration) { - } -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityResolver.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityResolver.java deleted file mode 100644 index 00228696..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentityResolver.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.lingniu.ingest.identity; - -public interface VehicleIdentityResolver { - - VehicleIdentity resolve(VehicleIdentityLookup lookup); -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentitySource.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentitySource.java deleted file mode 100644 index b35cfa69..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleIdentitySource.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.lingniu.ingest.identity; - -public enum VehicleIdentitySource { - EXPLICIT_VIN, - REGISTERED, - BOUND_PHONE, - BOUND_DEVICE_ID, - BOUND_PLATE, - FALLBACK_DEVICE_ID, - FALLBACK_PHONE, - FALLBACK_PLATE, - UNKNOWN -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleRegistrationBinding.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleRegistrationBinding.java deleted file mode 100644 index 4761c61b..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/VehicleRegistrationBinding.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.identity; - -import com.lingniu.ingest.api.ProtocolId; - -public record VehicleRegistrationBinding( - ProtocolId protocol, - String vin, - String phone, - String deviceId, - String plate, - Integer province, - Integer city, - String maker, - String deviceType, - Integer plateColor -) { - public VehicleRegistrationBinding { - vin = normalize(vin); - phone = normalize(phone); - deviceId = normalize(deviceId); - plate = normalize(plate); - maker = normalize(maker); - deviceType = normalize(deviceType); - if (phone.isBlank() && deviceId.isBlank() && plate.isBlank()) { - throw new IllegalArgumentException("at least one registration identifier must not be blank"); - } - } - - public boolean hasResolvedVin() { - return !vin.isBlank() && !"unknown".equalsIgnoreCase(vin); - } - - private static String normalize(String value) { - return value == null ? "" : value.trim(); - } -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfiguration.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfiguration.java deleted file mode 100644 index c47f50c0..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfiguration.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.lingniu.ingest.identity.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.identity.MySqlVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityRegistry; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.jdbc.datasource.DriverManagerDataSource; - -import javax.sql.DataSource; -import java.util.Locale; - -@AutoConfiguration -@EnableConfigurationProperties(VehicleIdentityProperties.class) -public class VehicleIdentityAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public ObjectMapper vehicleIdentityObjectMapper() { - return new ObjectMapper(); - } - - @Bean - @ConditionalOnMissingBean({VehicleIdentityResolver.class, VehicleIdentityRegistry.class}) - public MySqlVehicleIdentityService mysqlVehicleIdentityService(VehicleIdentityProperties properties, - ObjectMapper objectMapper) { - requireMysqlStore(properties.getStore()); - VehicleIdentityProperties.Mysql mysql = properties.getMysql(); - DataSource dataSource = new DriverManagerDataSource( - mysql.getJdbcUrl(), mysql.getUsername(), mysql.getPassword()); - return new MySqlVehicleIdentityService(dataSource, mysql, objectMapper); - } - - private static void requireMysqlStore(String value) { - String store = value == null || value.isBlank() ? "mysql" : value.trim().toLowerCase(Locale.ROOT); - if (!"mysql".equals(store)) { - throw new IllegalStateException("identity.store supports only mysql; configured value: " + value); - } - } -} diff --git a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityProperties.java b/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityProperties.java deleted file mode 100644 index c47f7cc5..00000000 --- a/modules/core/vehicle-identity/src/main/java/com/lingniu/ingest/identity/config/VehicleIdentityProperties.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lingniu.ingest.identity.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "lingniu.ingest.identity") -public class VehicleIdentityProperties { - - private String store = "mysql"; - private Mysql mysql = new Mysql(); - - public String getStore() { return store; } - public void setStore(String store) { this.store = store; } - public Mysql getMysql() { return mysql; } - public void setMysql(Mysql mysql) { this.mysql = mysql; } - - public static class Mysql { - private String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/lingniu_vehicle?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"; - private String username = "root"; - private String password = ""; - private String tableName = "vehicle_identity_bindings"; - private boolean initializeSchema = true; - - public String getJdbcUrl() { return jdbcUrl; } - public void setJdbcUrl(String jdbcUrl) { this.jdbcUrl = jdbcUrl; } - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - public String getPassword() { return password; } - public void setPassword(String password) { this.password = password; } - public String getTableName() { return tableName; } - public void setTableName(String tableName) { this.tableName = tableName; } - public boolean isInitializeSchema() { return initializeSchema; } - public void setInitializeSchema(boolean initializeSchema) { this.initializeSchema = initializeSchema; } - } -} diff --git a/modules/core/vehicle-identity/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/core/vehicle-identity/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 2ad78acc..00000000 --- a/modules/core/vehicle-identity/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration diff --git a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceJdbcTest.java b/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceJdbcTest.java deleted file mode 100644 index 87eb5cfb..00000000 --- a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceJdbcTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.lingniu.ingest.identity; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.identity.config.VehicleIdentityProperties; -import org.h2.jdbcx.JdbcDataSource; -import org.junit.jupiter.api.Test; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import static org.assertj.core.api.Assertions.assertThat; - -class MySqlVehicleIdentityServiceJdbcTest { - - @Test - void plateVinBindingIsQueryableThroughRealJdbc() throws Exception { - JdbcDataSource dataSource = new JdbcDataSource(); - dataSource.setURL("jdbc:h2:mem:" + UUID.randomUUID() - + ";MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1"); - - VehicleIdentityProperties.Mysql mysql = new VehicleIdentityProperties.Mysql(); - mysql.setTableName("vehicle_identity_binding"); - MySqlVehicleIdentityService service = new MySqlVehicleIdentityService( - dataSource, - mysql, - new ObjectMapper()); - - service.bind(new VehicleIdentityBinding( - ProtocolId.JT808, - "LNVIN00000061001", - "13079961001", - "dev61001", - "粤B61001", - Map.of( - "province", "44", - "city", "4401", - "maker", "maker-a", - "deviceType", "type-a", - "plateColor", "1"))); - - try (Connection connection = dataSource.getConnection(); - Statement statement = connection.createStatement()) { - ResultSet binding = statement.executeQuery(""" - SELECT vin, phone, device_id, plate, province, city, maker, device_type, plate_color - FROM vehicle_identity_binding - WHERE protocol = 'JT808' AND phone = '13079961001' - """); - assertThat(binding.next()).isTrue(); - assertThat(binding.getString("vin")).isEqualTo("LNVIN00000061001"); - assertThat(binding.getString("phone")).isEqualTo("13079961001"); - assertThat(binding.getString("device_id")).isEqualTo("dev61001"); - assertThat(binding.getString("plate")).isEqualTo("粤B61001"); - assertThat(binding.getString("province")).isEqualTo("44"); - assertThat(binding.getString("city")).isEqualTo("4401"); - assertThat(binding.getString("maker")).isEqualTo("maker-a"); - assertThat(binding.getString("device_type")).isEqualTo("type-a"); - assertThat(binding.getString("plate_color")).isEqualTo("1"); - - List columns = new ArrayList<>(); - ResultSet bindingColumns = connection.getMetaData() - .getColumns(null, null, "vehicle_identity_binding", null); - while (bindingColumns.next()) { - columns.add(bindingColumns.getString("COLUMN_NAME")); - } - assertThat(columns).contains( - "protocol", - "phone", - "device_id", - "plate", - "vin", - "metadata_json"); - } - - assertThat(service.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", "13079961001", "", "")).vin()) - .isEqualTo("LNVIN00000061001"); - assertThat(service.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", "", "dev61001", "")).vin()) - .isEqualTo("LNVIN00000061001"); - assertThat(service.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", "", "", "粤B61001")).vin()) - .isEqualTo("LNVIN00000061001"); - } -} diff --git a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceTest.java b/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceTest.java deleted file mode 100644 index 1088a3f3..00000000 --- a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/MySqlVehicleIdentityServiceTest.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.lingniu.ingest.identity; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.identity.config.VehicleIdentityProperties; -import org.junit.jupiter.api.Test; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.DriverManagerDataSource; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class MySqlVehicleIdentityServiceTest { - - @Test - void upsertsRegistrationBindingAndResolvesByExternalIdentifiers() { - DriverManagerDataSource dataSource = new DriverManagerDataSource( - "jdbc:h2:mem:mysql-identity;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1", "sa", ""); - VehicleIdentityProperties.Mysql mysql = new VehicleIdentityProperties.Mysql(); - mysql.setTableName("vehicle_identity_bindings"); - MySqlVehicleIdentityService service = new MySqlVehicleIdentityService( - dataSource, - mysql, - new ObjectMapper()); - - service.bind(new VehicleIdentityBinding( - ProtocolId.JT808, - "JT808AABBCCDDEE1", - "13079963289", - "9963289", - "沪A00113F", - Map.of( - "province", "31", - "city", "113", - "maker", "70112", - "deviceType", "SEG-9888G", - "plateColor", "2"))); - - assertThat(service.resolve(new VehicleIdentityLookup(ProtocolId.JT808, "", "13079963289", "", ""))) - .extracting(VehicleIdentity::vin, VehicleIdentity::resolved, VehicleIdentity::source) - .containsExactly("JT808AABBCCDDEE1", true, VehicleIdentitySource.BOUND_PHONE); - assertThat(service.resolve(new VehicleIdentityLookup(ProtocolId.JT808, "", "", "9963289", ""))) - .extracting(VehicleIdentity::vin, VehicleIdentity::resolved, VehicleIdentity::source) - .containsExactly("JT808AABBCCDDEE1", true, VehicleIdentitySource.BOUND_DEVICE_ID); - assertThat(service.resolve(new VehicleIdentityLookup(ProtocolId.JT808, "", "", "", "沪A00113F"))) - .extracting(VehicleIdentity::vin, VehicleIdentity::resolved, VehicleIdentity::source) - .containsExactly("JT808AABBCCDDEE1", true, VehicleIdentitySource.BOUND_PLATE); - - JdbcTemplate jdbc = new JdbcTemplate(dataSource); - Map row = jdbc.queryForMap(""" - SELECT vin, phone, device_id, plate, province, city, maker, device_type, plate_color - FROM vehicle_identity_bindings - WHERE protocol = 'JT808' AND phone = '13079963289' - """); - assertThat(row) - .containsEntry("vin", "JT808AABBCCDDEE1") - .containsEntry("phone", "13079963289") - .containsEntry("device_id", "9963289") - .containsEntry("plate", "沪A00113F") - .containsEntry("province", "31") - .containsEntry("city", "113") - .containsEntry("maker", "70112") - .containsEntry("device_type", "SEG-9888G") - .containsEntry("plate_color", "2"); - } - - @Test - void initializesMissingColumnsOnExistingLegacyTable() { - DriverManagerDataSource dataSource = new DriverManagerDataSource( - "jdbc:h2:mem:mysql-identity-legacy;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1", "sa", ""); - JdbcTemplate jdbc = new JdbcTemplate(dataSource); - jdbc.execute(""" - CREATE TABLE vehicle_identity_binding ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - protocol VARCHAR(32) NOT NULL, - vin VARCHAR(32) NOT NULL, - legacy_key VARCHAR(64) NOT NULL DEFAULT '' - ) - """); - VehicleIdentityProperties.Mysql mysql = new VehicleIdentityProperties.Mysql(); - mysql.setTableName("vehicle_identity_binding"); - - MySqlVehicleIdentityService service = new MySqlVehicleIdentityService( - dataSource, - mysql, - new ObjectMapper()); - service.bind(new VehicleIdentityBinding( - ProtocolId.JT808, - "JT808LEGACY00001", - "13079963289", - "9963289", - "沪A00113F", - Map.of("maker", "70112", "deviceType", "SEG-9888G"))); - - Map row = jdbc.queryForMap(""" - SELECT vin, maker, device_type, metadata_json - FROM vehicle_identity_binding - WHERE protocol = 'JT808' AND phone = '13079963289' - """); - assertThat(row) - .containsEntry("vin", "JT808LEGACY00001") - .containsEntry("maker", "70112") - .containsEntry("device_type", "SEG-9888G"); - assertThat((String) row.get("metadata_json")).contains("SEG-9888G"); - } - - @Test - void upsertsByCompositeProtocolPhonePlatePrimaryKey() { - DriverManagerDataSource dataSource = new DriverManagerDataSource( - "jdbc:h2:mem:mysql-identity-composite;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1", "sa", ""); - JdbcTemplate jdbc = new JdbcTemplate(dataSource); - jdbc.execute(""" - CREATE TABLE vehicle_identity_binding ( - protocol VARCHAR(32) NOT NULL DEFAULT 'ALL', - phone VARCHAR(128) NOT NULL DEFAULT '', - plate VARCHAR(128) NOT NULL DEFAULT '', - vin VARCHAR(64) NOT NULL, - province VARCHAR(32) NOT NULL DEFAULT '', - city VARCHAR(32) NOT NULL DEFAULT '', - maker VARCHAR(64) NOT NULL DEFAULT '', - device_type VARCHAR(128) NOT NULL DEFAULT '', - plate_color VARCHAR(32) NOT NULL DEFAULT '', - metadata_json TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - device_id VARCHAR(64) NOT NULL DEFAULT '', - PRIMARY KEY (protocol, phone, plate) - ) - """); - VehicleIdentityProperties.Mysql mysql = new VehicleIdentityProperties.Mysql(); - mysql.setTableName("vehicle_identity_binding"); - MySqlVehicleIdentityService service = new MySqlVehicleIdentityService( - dataSource, - mysql, - new ObjectMapper()); - service.bind(new VehicleIdentityBinding(ProtocolId.JT808, - "VIN-A", "13079963289", "DEV-A", "沪A00113F")); - - service.bind(new VehicleIdentityBinding(ProtocolId.JT808, - "VIN-B", "13079963289", "DEV-B", "沪A00114F")); - service.bind(new VehicleIdentityBinding(ProtocolId.JT808, - "VIN-B2", "13079963289", "DEV-B2", "沪A00114F")); - - Integer rows = jdbc.queryForObject(""" - SELECT COUNT(*) - FROM vehicle_identity_binding - WHERE protocol = 'JT808' AND phone = '13079963289' - """, Integer.class); - assertThat(rows).isEqualTo(2); - assertThat(jdbc.queryForObject(""" - SELECT vin - FROM vehicle_identity_binding - WHERE protocol = 'JT808' AND phone = '13079963289' AND plate = '沪A00114F' - """, String.class)).isEqualTo("VIN-B2"); - } -} diff --git a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfigurationTest.java b/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfigurationTest.java deleted file mode 100644 index ca673fa4..00000000 --- a/modules/core/vehicle-identity/src/test/java/com/lingniu/ingest/identity/config/VehicleIdentityAutoConfigurationTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.lingniu.ingest.identity.config; - -import com.lingniu.ingest.identity.VehicleIdentityRegistry; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleIdentityAutoConfigurationTest { - - private final ApplicationContextRunner runner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(VehicleIdentityAutoConfiguration.class)); - - @Test - void createsMysqlIdentityServiceByDefault() { - runner - .withPropertyValues( - "lingniu.ingest.identity.mysql.jdbc-url=jdbc:h2:mem:auto-identity-default;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1", - "lingniu.ingest.identity.mysql.username=sa", - "lingniu.ingest.identity.mysql.password=", - "lingniu.ingest.identity.mysql.table-name=vehicle_identity_bindings") - .run(context -> { - assertThat(context).hasSingleBean(VehicleIdentityResolver.class); - assertThat(context).hasSingleBean(VehicleIdentityRegistry.class); - assertThat(context.getBean(VehicleIdentityResolver.class)) - .isSameAs(context.getBean(VehicleIdentityRegistry.class)); - assertThat(context.getBean(VehicleIdentityResolver.class).getClass().getSimpleName()) - .isEqualTo("MySqlVehicleIdentityService"); - }); - } - - @Test - void createsMysqlIdentityServiceWhenConfigured() { - runner - .withPropertyValues( - "lingniu.ingest.identity.store=mysql", - "lingniu.ingest.identity.mysql.jdbc-url=jdbc:h2:mem:auto-identity;MODE=MySQL;DATABASE_TO_UPPER=false;DB_CLOSE_DELAY=-1", - "lingniu.ingest.identity.mysql.username=sa", - "lingniu.ingest.identity.mysql.password=", - "lingniu.ingest.identity.mysql.table-name=vehicle_identity_bindings") - .run(context -> { - assertThat(context).hasSingleBean(VehicleIdentityResolver.class); - assertThat(context).hasSingleBean(VehicleIdentityRegistry.class); - assertThat(context.getBean(VehicleIdentityResolver.class)) - .isSameAs(context.getBean(VehicleIdentityRegistry.class)); - assertThat(context.getBean(VehicleIdentityResolver.class).getClass().getSimpleName()) - .isEqualTo("MySqlVehicleIdentityService"); - }); - } - - @Test - void rejectsUnsupportedStoreMode() { - runner - .withPropertyValues("lingniu.ingest.identity.store=file") - .run(context -> assertThat(context.getStartupFailure()) - .hasRootCauseInstanceOf(IllegalStateException.class) - .hasMessageContaining("identity.store supports only mysql") - .hasMessageContaining("file")); - } - - @Test - void rejectsSqliteStoreModeWithSameMysqlOnlyRule() { - runner - .withPropertyValues("lingniu.ingest.identity.store=sqlite") - .run(context -> assertThat(context.getStartupFailure()) - .hasRootCauseInstanceOf(IllegalStateException.class) - .hasMessageContaining("identity.store supports only mysql") - .hasMessageContaining("sqlite")); - } - - @Test - void moduleDoesNotKeepAlternativeStoreImplementations() { - Path sourceRoot = Path.of("src/main/java/com/lingniu/ingest/identity"); - - assertThat(Files.exists(sourceRoot.resolve("InMemoryVehicleIdentityService.java"))) - .as("identity store is mysql-only; in-memory implementation must not be shipped") - .isFalse(); - } -} diff --git a/modules/inbound/inbound-mqtt/pom.xml b/modules/inbound/inbound-mqtt/pom.xml deleted file mode 100644 index d5efad2d..00000000 --- a/modules/inbound/inbound-mqtt/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - inbound-mqtt - inbound-mqtt - MQTT 接入模块(支持多 endpoint,宇通等),Eclipse Paho MQTT Client + 双向 TLS。 - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - vehicle-identity - - - com.lingniu.ingest - vehicle-identity-test-support - test - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.eclipse.paho - org.eclipse.paho.client.mqttv3 - - - com.fasterxml.jackson.core - jackson-databind - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttEndpointManager.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttEndpointManager.java deleted file mode 100644 index efe435bf..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttEndpointManager.java +++ /dev/null @@ -1,380 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.client; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.inbound.mqtt.config.MqttInboundProperties; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry; -import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttCallbackExtended; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -/** - * MQTT 多 endpoint 生命周期管理。 - * - *

为每个 endpoint 创建一个 Paho 异步客户端 → 连接 → 订阅 → 注册回调 → - * 回调里解析 payload → {@link Dispatcher#dispatch(RawFrame)}。 - * - *

MQTT 回调不做业务处理,虚拟线程由 Dispatcher 侧的 Disruptor 承接。 - */ -public final class MqttEndpointManager implements AutoCloseable { - - private static final Logger log = LoggerFactory.getLogger(MqttEndpointManager.class); - - private final MqttInboundProperties props; - private final MqttProfileRegistry profileRegistry; - private final VehicleIdentityResolver identityResolver; - private final Dispatcher dispatcher; - private final List clients = new ArrayList<>(); - private boolean started; - - public MqttEndpointManager(MqttInboundProperties props, - MqttProfileRegistry profileRegistry, - VehicleIdentityResolver identityResolver, - Dispatcher dispatcher) { - this.props = props; - this.profileRegistry = profileRegistry; - if (identityResolver == null) { - throw new IllegalArgumentException("identityResolver must not be null"); - } - this.identityResolver = identityResolver; - this.dispatcher = dispatcher; - } - - public synchronized void start() { - if (started) { - return; - } - started = true; - if (props.getEndpoints().isEmpty()) { - log.warn("mqtt inbound enabled but no endpoints configured"); - return; - } - log.info("mqtt inbound starting endpoints={}", props.getEndpoints().size()); - for (MqttInboundProperties.Endpoint ep : props.getEndpoints()) { - try { - // 多 endpoint 互不影响:单个连接初始化失败会派发 operationalError,但不会阻止其他 endpoint。 - log.info("mqtt endpoint [{}] initializing uri={} topic={} profile={} qos={} cleanSession={} tlsHostnameVerification={}", - ep.getName(), ep.getUri(), ep.getTopic(), ep.getProfile(), ep.getQos(), ep.isCleanSession(), - ep.getTls() == null || ep.getTls().isHostnameVerificationEnabled()); - MqttAsyncClient client = buildClient(ep); - clients.add(client); - connectAndSubscribe(client, ep); - } catch (Exception e) { - log.error("mqtt endpoint [{}] init failed, skipped", ep.getName(), e); - publishOperationalError(ep, "init", e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()); - } - } - } - - private MqttAsyncClient buildClient(MqttInboundProperties.Endpoint ep) throws MqttException { - String clientId = ep.getClientId() == null || ep.getClientId().isBlank() - ? "lingniu-" + UUID.randomUUID() : ep.getClientId(); - return new MqttAsyncClient(pahoServerUri(ep.getUri()), clientId, new MemoryPersistence()); - } - - private void connectAndSubscribe(MqttAsyncClient client, MqttInboundProperties.Endpoint ep) throws MqttException { - client.setCallback(new MqttCallbackExtended() { - @Override - public void connectComplete(boolean reconnect, String serverURI) { - if (!reconnect) { - return; - } - try { - client.subscribe(ep.getTopic(), mapQos(ep.getQos())).waitForCompletion(timeoutMs(ep)); - log.info("mqtt endpoint [{}] re-subscribed topic={} qos={}", ep.getName(), ep.getTopic(), ep.getQos()); - } catch (Exception e) { - log.error("mqtt endpoint [{}] re-subscribe failed topic={}", ep.getName(), ep.getTopic(), e); - publishOperationalError(ep, "subscribe", - e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()); - } - } - - @Override - public void connectionLost(Throwable cause) { - log.warn("mqtt endpoint [{}] connection lost", ep.getName(), cause); - } - - @Override - public void messageArrived(String topic, MqttMessage message) { - log.info("mqtt endpoint [{}] received topic={} bytes={}", ep.getName(), topic, message.getPayload().length); - onMessage(ep, topic, message.getPayload()); - } - - @Override - public void deliveryComplete(IMqttDeliveryToken token) { - // inbound only - } - }); - - MqttConnectOptions options = connectOptions(ep); - client.connect(options).waitForCompletion(timeoutMs(ep)); - log.info("mqtt endpoint [{}] connected serverUri={} clientId={}", - ep.getName(), client.getServerURI(), client.getClientId()); - client.subscribe(ep.getTopic(), mapQos(ep.getQos())).waitForCompletion(timeoutMs(ep)); - log.info("mqtt endpoint [{}] subscribed topic={} qos={}", ep.getName(), ep.getTopic(), ep.getQos()); - } - - private void onMessage(MqttInboundProperties.Endpoint ep, String topic, byte[] payload) { - try { - MqttPayload parsed = profileRegistry.parse(ep.getName(), ep.getProfile(), topic, payload); - String externalDeviceId = firstNonBlank(parsed.deviceId(), parsed.vin()); - IdentityResolution identity = resolveIdentity(parsed); - parsed = parsed.withIdentity(identity.identity(), identity.errorMessage()); - Map meta = new HashMap<>(4); - // metadata 同时保留外部设备号和内部 VIN,便于排查绑定表错误导致的车辆归属问题。 - meta.put("vin", identity.identity().vin()); - meta.put("externalVin", parsed.externalVin() == null ? "" : parsed.externalVin()); - meta.put("phone", parsed.phone() == null ? "" : parsed.phone()); - meta.put("mqttDeviceId", externalDeviceId); - meta.put("plateNo", parsed.plateNo() == null ? "" : parsed.plateNo()); - meta.put("identityResolved", Boolean.toString(identity.identity().resolved())); - meta.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - meta.put("identityError", "true"); - meta.put("identityErrorMessage", identity.errorMessage()); - } - meta.put("endpoint", ep.getName()); - meta.put("topic", topic); - meta.put("profile", parsed.profile() == null ? "" : parsed.profile()); - if (parsed.data() != null && parsed.data().path("_parseError").asBoolean(false)) { - meta.put("parseError", "true"); - meta.put("parseErrorMessage", parsed.data().path("_parseErrorMessage").asText("")); - meta.put("unsupportedProfile", - Boolean.toString(parsed.data().path("_unsupportedProfile").asBoolean(false))); - } - RawFrame rf = new RawFrame( - ProtocolId.MQTT_YUTONG, 0, 0, - parsed, payload, meta, Instant.now()); - dispatcher.dispatch(rf); - } catch (Exception e) { - log.warn("mqtt endpoint [{}] message handling failed topic={} len={}", - ep.getName(), topic, payload.length, e); - // 单条消息异常也转成 RawFrame 错误事件,避免 MQTT 回调线程吞掉现场。 - publishMessageError(ep, topic, payload, e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()); - } - } - - private IdentityResolution resolveIdentity(MqttPayload payload) { - String externalDeviceId = firstNonBlank(payload.deviceId(), payload.vin()); - String externalVin = payload.externalVin() == null ? "" : payload.externalVin(); - String phone = payload.phone() == null ? "" : payload.phone(); - String plateNo = payload.plateNo() == null ? "" : payload.plateNo(); - try { - VehicleIdentity bound = identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.MQTT_YUTONG, - externalVin, - phone, - externalDeviceId, - plateNo)); - if (bound.resolved() || !looksLikeVin(externalDeviceId)) { - return new IdentityResolution(bound, null); - } - return new IdentityResolution(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.MQTT_YUTONG, - externalDeviceId, - phone, - externalDeviceId, - plateNo)), null); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private static String firstNonBlank(String... values) { - for (String value : values) { - if (value != null && !value.isBlank()) { - return value; - } - } - return ""; - } - - private void publishOperationalError(MqttInboundProperties.Endpoint ep, String phase, String reason) { - // 连接/订阅级别错误也进入 Dispatcher,这样监控和历史查询能看到接入端本身的问题。 - String endpointName = ep == null ? "" : ep.getName(); - String profile = ep == null ? "" : ep.getProfile(); - String topic = ep == null ? "" : ep.getTopic(); - String json = "{\"operationalError\":true" - + ",\"endpoint\":\"" + escape(endpointName) + "\"" - + ",\"profile\":\"" + escape(profile) + "\"" - + ",\"topic\":\"" + escape(topic) + "\"" - + ",\"phase\":\"" + escape(phase) + "\"" - + ",\"reason\":\"" + escape(reason) + "\"}"; - byte[] raw = json.getBytes(StandardCharsets.UTF_8); - MqttPayload payload = MqttProfileRegistry.operationalErrorPayload( - endpointName, profile, topic, raw, phase, reason); - Map meta = new HashMap<>(); - meta.put("vin", "unknown"); - meta.put("identityResolved", "false"); - meta.put("identitySource", "UNKNOWN"); - meta.put("endpoint", endpointName == null ? "" : endpointName); - meta.put("topic", topic == null ? "" : topic); - meta.put("profile", profile == null ? "" : profile); - meta.put("operationalError", "true"); - meta.put("phase", phase == null ? "" : phase); - meta.put("reason", reason == null ? "" : reason); - dispatcher.dispatch(new RawFrame( - ProtocolId.MQTT_YUTONG, - 0, - 0, - payload, - raw, - meta, - Instant.now())); - } - - private void publishMessageError(MqttInboundProperties.Endpoint ep, String topic, byte[] raw, String reason) { - String endpointName = ep == null ? "" : ep.getName(); - String profile = ep == null ? "" : ep.getProfile(); - byte[] payloadBytes = raw == null ? new byte[0] : raw; - MqttPayload payload = MqttProfileRegistry.operationalErrorPayload( - endpointName, - profile, - topic, - payloadBytes, - "message", - reason); - Map meta = new HashMap<>(); - meta.put("vin", "unknown"); - meta.put("identityResolved", "false"); - meta.put("identitySource", "UNKNOWN"); - meta.put("endpoint", endpointName == null ? "" : endpointName); - meta.put("topic", topic == null ? "" : topic); - meta.put("profile", profile == null ? "" : profile); - meta.put("operationalError", "true"); - meta.put("phase", "message"); - meta.put("reason", reason == null ? "" : reason); - dispatcher.dispatch(new RawFrame( - ProtocolId.MQTT_YUTONG, - 0, - 0, - payload, - payloadBytes, - meta, - Instant.now())); - } - - private static String escape(String value) { - if (value == null) { - return ""; - } - return value.replace("\\", "\\\\").replace("\"", "\\\""); - } - - private static MqttConnectOptions connectOptions(MqttInboundProperties.Endpoint ep) { - MqttConnectOptions options = new MqttConnectOptions(); - options.setAutomaticReconnect(true); - options.setCleanSession(ep.isCleanSession()); - if (ep.getKeepAliveSeconds() > 0) { - options.setKeepAliveInterval(ep.getKeepAliveSeconds()); - } - if (ep.getConnectionTimeoutSeconds() > 0) { - options.setConnectionTimeout(ep.getConnectionTimeoutSeconds()); - } - if (hasText(ep.getUsername())) { - options.setUserName(ep.getUsername()); - options.setPassword(ep.getPassword() == null ? new char[0] : ep.getPassword().toCharArray()); - } - if (isSslUri(ep.getUri()) && hasCustomTls(ep.getTls())) { - options.setSocketFactory(MqttTlsSupport.socketFactory(ep.getTls())); - if (ep.getTls() != null && !ep.getTls().isHostnameVerificationEnabled()) { - options.setSSLHostnameVerifier((hostname, session) -> true); - } - } - return options; - } - - private static String pahoServerUri(String value) { - URI uri = URI.create(value); - String scheme = uri.getScheme(); - String pahoScheme = switch (scheme == null ? "" : scheme.toLowerCase()) { - case "ssl", "tls", "mqtts" -> "ssl"; - default -> "tcp"; - }; - int port = uri.getPort() > 0 ? uri.getPort() : defaultPort(scheme); - return pahoScheme + "://" + uri.getHost() + ":" + port; - } - - private static int mapQos(int level) { - return switch (level) { - case 0 -> 0; - case 2 -> 2; - default -> 1; - }; - } - - private static long timeoutMs(MqttInboundProperties.Endpoint ep) { - int seconds = ep.getConnectionTimeoutSeconds() > 0 ? ep.getConnectionTimeoutSeconds() : 10; - return TimeUnit.SECONDS.toMillis(seconds); - } - - private static int defaultPort(String scheme) { - if (scheme == null) return 1883; - return switch (scheme.toLowerCase()) { - case "ssl", "tls", "mqtts" -> 8883; - default -> 1883; - }; - } - - private static boolean looksLikeVin(String value) { - return value != null && value.trim().length() == 17; - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) {} - - private static boolean hasCustomTls(MqttInboundProperties.Tls tls) { - return tls != null && (hasText(tls.getCaPem()) - || (hasText(tls.getClientPem()) && hasText(tls.getClientKey()))); - } - - private static boolean isSslUri(String value) { - String scheme = URI.create(value).getScheme(); - return scheme != null && switch (scheme.toLowerCase()) { - case "ssl", "tls", "mqtts" -> true; - default -> false; - }; - } - - private static boolean hasText(String value) { - return value != null && !value.isBlank(); - } - - @Override - public void close() { - for (MqttAsyncClient c : clients) { - try { - if (c.isConnected()) { - c.disconnectForcibly(1000, 1000, false); - } - c.close(true); - } catch (Exception ignored) { - // best-effort - } - } - log.info("mqtt endpoint manager stopped, clients={}", clients.size()); - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttTlsSupport.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttTlsSupport.java deleted file mode 100644 index 2983c7a9..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/client/MqttTlsSupport.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.client; - -import com.lingniu.ingest.inbound.mqtt.config.MqttInboundProperties; - -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLEngine; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLParameters; -import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509ExtendedTrustManager; -import javax.net.ssl.X509TrustManager; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Collection; -import java.util.List; - -public final class MqttTlsSupport { - - private MqttTlsSupport() {} - - public static SSLSocketFactory socketFactory(MqttInboundProperties.Tls tls) { - try { - KeyManager[] keyManagers = null; - TrustManager[] trustManagers = null; - if (tls != null && hasText(tls.getCaPem())) { - trustManagers = trustManagerFactory(Path.of(tls.getCaPem())).getTrustManagers(); - if (!tls.isHostnameVerificationEnabled()) { - trustManagers = withoutHostnameVerification(trustManagers); - } - } - if (tls != null && hasText(tls.getClientPem()) && hasText(tls.getClientKey())) { - keyManagers = keyManagerFactory(Path.of(tls.getClientPem()), Path.of(tls.getClientKey())).getKeyManagers(); - } - SSLContext context = SSLContext.getInstance("TLS"); - context.init(keyManagers, trustManagers, null); - SSLSocketFactory factory = context.getSocketFactory(); - if (tls != null && !tls.isHostnameVerificationEnabled()) { - return new HostnameVerificationDisablingSocketFactory(factory); - } - return factory; - } catch (Exception e) { - throw new IllegalStateException("mqtt tls config build failed", e); - } - } - - private static TrustManagerFactory trustManagerFactory(Path caPem) throws Exception { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null, null); - int i = 0; - for (Certificate cert : readCertificates(caPem)) { - trustStore.setCertificateEntry("ca-" + i++, cert); - } - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(trustStore); - return tmf; - } - - private static KeyManagerFactory keyManagerFactory(Path clientPem, Path clientKey) throws Exception { - List chain = readCertificates(clientPem); - PrivateKey key = readPrivateKey(clientKey); - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - keyStore.load(null, null); - char[] password = new char[0]; - keyStore.setKeyEntry("mqtt-client", key, password, chain.toArray(Certificate[]::new)); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - kmf.init(keyStore, password); - return kmf; - } - - private static List readCertificates(Path pem) throws Exception { - String text = Files.readString(pem, StandardCharsets.UTF_8); - List certificates = new ArrayList<>(); - CertificateFactory factory = CertificateFactory.getInstance("X.509"); - for (String block : pemBlocks(text, "CERTIFICATE")) { - try (ByteArrayInputStream in = new ByteArrayInputStream(Base64.getMimeDecoder().decode(block))) { - certificates.add(factory.generateCertificate(in)); - } - } - if (certificates.isEmpty()) { - throw new IllegalArgumentException("no certificate found in " + pem); - } - return certificates; - } - - private static PrivateKey readPrivateKey(Path pem) throws Exception { - String text = Files.readString(pem, StandardCharsets.UTF_8); - List blocks = pemBlocks(text, "PRIVATE KEY"); - if (blocks.isEmpty()) { - throw new IllegalArgumentException("no PKCS#8 private key found in " + pem); - } - byte[] der = Base64.getMimeDecoder().decode(blocks.getFirst()); - PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(der); - return KeyFactory.getInstance("RSA").generatePrivate(spec); - } - - private static List pemBlocks(String text, String type) { - String begin = "-----BEGIN " + type + "-----"; - String end = "-----END " + type + "-----"; - List blocks = new ArrayList<>(); - int pos = 0; - while (true) { - int start = text.indexOf(begin, pos); - if (start < 0) break; - int contentStart = start + begin.length(); - int stop = text.indexOf(end, contentStart); - if (stop < 0) break; - blocks.add(text.substring(contentStart, stop).replaceAll("\\s+", "")); - pos = stop + end.length(); - } - return blocks; - } - - private static boolean hasText(String value) { - return value != null && !value.isBlank(); - } - - private static TrustManager[] withoutHostnameVerification(TrustManager[] managers) { - TrustManager[] wrapped = new TrustManager[managers.length]; - for (int i = 0; i < managers.length; i++) { - TrustManager manager = managers[i]; - if (manager instanceof X509TrustManager x509) { - wrapped[i] = new ChainOnlyTrustManager(x509); - } else { - wrapped[i] = manager; - } - } - return wrapped; - } - - private static final class ChainOnlyTrustManager extends X509ExtendedTrustManager { - private final X509TrustManager delegate; - - private ChainOnlyTrustManager(X509TrustManager delegate) { - this.delegate = delegate; - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) - throws CertificateException { - if (delegate instanceof X509ExtendedTrustManager extended) { - extended.checkClientTrusted(chain, authType, socket); - } else { - delegate.checkClientTrusted(chain, authType); - } - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) - throws CertificateException { - delegate.checkServerTrusted(chain, authType); - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine engine) - throws CertificateException { - if (delegate instanceof X509ExtendedTrustManager extended) { - extended.checkClientTrusted(chain, authType, engine); - } else { - delegate.checkClientTrusted(chain, authType); - } - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine engine) - throws CertificateException { - delegate.checkServerTrusted(chain, authType); - } - - @Override - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - delegate.checkClientTrusted(chain, authType); - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - delegate.checkServerTrusted(chain, authType); - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return delegate.getAcceptedIssuers(); - } - } - - private static final class HostnameVerificationDisablingSocketFactory extends SSLSocketFactory { - private final SSLSocketFactory delegate; - - private HostnameVerificationDisablingSocketFactory(SSLSocketFactory delegate) { - this.delegate = delegate; - } - - @Override - public String[] getDefaultCipherSuites() { - return delegate.getDefaultCipherSuites(); - } - - @Override - public String[] getSupportedCipherSuites() { - return delegate.getSupportedCipherSuites(); - } - - @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException { - return prepare(delegate.createSocket(socket, host, port, autoClose)); - } - - @Override - public Socket createSocket() throws IOException { - return prepare(delegate.createSocket()); - } - - @Override - public Socket createSocket(String host, int port) throws IOException { - return prepare(delegate.createSocket(host, port)); - } - - @Override - public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { - return prepare(delegate.createSocket(host, port, localHost, localPort)); - } - - @Override - public Socket createSocket(InetAddress host, int port) throws IOException { - return prepare(delegate.createSocket(host, port)); - } - - @Override - public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { - return prepare(delegate.createSocket(address, port, localAddress, localPort)); - } - - private static Socket prepare(Socket socket) { - if (socket instanceof SSLSocket sslSocket) { - SSLParameters parameters = sslSocket.getSSLParameters(); - parameters.setEndpointIdentificationAlgorithm(null); - sslSocket.setSSLParameters(parameters); - } - return socket; - } - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfiguration.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfiguration.java deleted file mode 100644 index ff1f5f87..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfiguration.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.inbound.mqtt.client.MqttEndpointManager; -import com.lingniu.ingest.inbound.mqtt.handler.MqttRealtimeHandler; -import com.lingniu.ingest.inbound.mqtt.mapper.YutongEventMapper; -import com.lingniu.ingest.inbound.mqtt.parser.MqttPayloadParser; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfile; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry; -import com.lingniu.ingest.inbound.mqtt.profile.YutongMqttProfile; -import org.springframework.boot.ApplicationRunner; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Lazy; - -import java.util.List; - -@AutoConfiguration -@EnableConfigurationProperties(MqttInboundProperties.class) -@Conditional(MqttInboundEnabledCondition.class) -public class MqttInboundAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public ObjectMapper mqttObjectMapper() { - return new ObjectMapper(); - } - - @Bean - @ConditionalOnMissingBean - public MqttPayloadParser mqttPayloadParser(ObjectMapper objectMapper) { - return new MqttPayloadParser(objectMapper); - } - - @Bean - @ConditionalOnMissingBean - public YutongEventMapper yutongEventMapper(VehicleIdentityResolver identityResolver) { - return new YutongEventMapper(identityResolver); - } - - @Bean - @ConditionalOnMissingBean - public YutongMqttProfile yutongMqttProfile(MqttPayloadParser parser, YutongEventMapper mapper) { - return new YutongMqttProfile(parser, mapper); - } - - @Bean - @ConditionalOnMissingBean - public MqttProfileRegistry mqttProfileRegistry(List profiles) { - return new MqttProfileRegistry(profiles); - } - - @Bean - @ConditionalOnMissingBean - public MqttRealtimeHandler mqttRealtimeHandler(MqttProfileRegistry profileRegistry) { - return new MqttRealtimeHandler(profileRegistry); - } - - @Bean(destroyMethod = "close") - @Lazy(false) - @ConditionalOnMissingBean - public MqttEndpointManager mqttEndpointManager(MqttInboundProperties props, - MqttProfileRegistry profileRegistry, - VehicleIdentityResolver identityResolver, - Dispatcher dispatcher) { - return new MqttEndpointManager(props, profileRegistry, identityResolver, dispatcher); - } - - @Bean - @ConditionalOnProperty(prefix = "lingniu.ingest.mqtt", - name = "auto-startup", - havingValue = "true", - matchIfMissing = true) - public ApplicationRunner mqttEndpointStartupRunner(MqttEndpointManager manager) { - return args -> manager.start(); - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledCondition.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledCondition.java deleted file mode 100644 index 12366dad..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledCondition.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.config; - -import org.springframework.context.annotation.Condition; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.env.Environment; -import org.springframework.core.type.AnnotatedTypeMetadata; - -final class MqttInboundEnabledCondition implements Condition { - - @Override - public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { - Environment env = context.getEnvironment(); - Boolean direct = parseBoolean(env.getProperty("lingniu.ingest.mqtt.enabled")); - if (direct != null) { - return direct; - } - return isTrue(env.getProperty("YUTONG_MQTT_ENABLED")); - } - - private static boolean isTrue(String value) { - Boolean parsed = parseBoolean(value); - return parsed != null && parsed; - } - - private static Boolean parseBoolean(String value) { - if (value == null || value.isBlank() || value.contains("${")) { - return null; - } - String normalized = value.trim().toLowerCase(); - if ("true".equals(normalized) || "1".equals(normalized) || "yes".equals(normalized) - || "on".equals(normalized)) { - return true; - } - if ("false".equals(normalized) || "0".equals(normalized) || "no".equals(normalized) - || "off".equals(normalized)) { - return false; - } - return null; - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundProperties.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundProperties.java deleted file mode 100644 index 5aeced23..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundProperties.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.ArrayList; -import java.util.List; - -/** - * MQTT 接入配置。支持多 endpoint(每个 endpoint 可对应一个车厂 / 一个 broker)。 - * - *

这是 JSON/MQTT 厂商入口,不参与 GB32960 TCP 32960 端口接入;如果同一辆车同时存在 - * MQTT 和 32960 数据,需要在下游按 protocol/source 做归因。 - */ -@ConfigurationProperties(prefix = "lingniu.ingest.mqtt") -public class MqttInboundProperties { - - /** 总开关。关闭时不会创建任何 MQTT 客户端连接。 */ - private boolean enabled = false; - /** 是否随 Spring Boot 应用启动立即连接 broker。测试装配时可关闭,生产默认开启。 */ - private boolean autoStartup = true; - /** 多 broker/多 topic 配置;每个 endpoint 独立重连、订阅并转成内部 VehicleEvent。 */ - private List endpoints = new ArrayList<>(); - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public boolean isAutoStartup() { return autoStartup; } - public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } - public List getEndpoints() { return endpoints; } - public void setEndpoints(List endpoints) { this.endpoints = endpoints; } - - public static class Endpoint { - /** 逻辑名称,用于指标与日志。 */ - private String name; - /** 对端 URI。形如 ssl://host:port 或 tcp://host:port。 */ - private String uri; - /** 订阅 topic,支持 + / # 通配符。 */ - private String topic; - /** QoS 0/1/2。 */ - private int qos = 1; - private String clientId; - private String username; - private String password; - /** false 表示保留 broker 会话,重连后可继续接收离线期间的 QoS1/2 消息。 */ - private boolean cleanSession = false; - private int keepAliveSeconds = 20; - private int connectionTimeoutSeconds = 10; - /** JSON 字段适配器 profile。默认 yutong。未来可支持 haipote / others。 */ - private String profile = "yutong"; - private Tls tls = new Tls(); - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getUri() { return uri; } - public void setUri(String uri) { this.uri = uri; } - public String getTopic() { return topic; } - public void setTopic(String topic) { this.topic = topic; } - public int getQos() { return qos; } - public void setQos(int qos) { this.qos = qos; } - public String getClientId() { return clientId; } - public void setClientId(String clientId) { this.clientId = clientId; } - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - public String getPassword() { return password; } - public void setPassword(String password) { this.password = password; } - public boolean isCleanSession() { return cleanSession; } - public void setCleanSession(boolean cleanSession) { this.cleanSession = cleanSession; } - public int getKeepAliveSeconds() { return keepAliveSeconds; } - public void setKeepAliveSeconds(int keepAliveSeconds) { this.keepAliveSeconds = keepAliveSeconds; } - public int getConnectionTimeoutSeconds() { return connectionTimeoutSeconds; } - public void setConnectionTimeoutSeconds(int connectionTimeoutSeconds) { this.connectionTimeoutSeconds = connectionTimeoutSeconds; } - public String getProfile() { return profile; } - public void setProfile(String profile) { this.profile = profile; } - public Tls getTls() { return tls; } - public void setTls(Tls tls) { this.tls = tls; } - } - - public static class Tls { - private String caPem; - private String clientPem; - private String clientKey; - private boolean hostnameVerificationEnabled = true; - public String getCaPem() { return caPem; } - public void setCaPem(String caPem) { this.caPem = caPem; } - public String getClientPem() { return clientPem; } - public void setClientPem(String clientPem) { this.clientPem = clientPem; } - public String getClientKey() { return clientKey; } - public void setClientKey(String clientKey) { this.clientKey = clientKey; } - public boolean isHostnameVerificationEnabled() { return hostnameVerificationEnabled; } - public void setHostnameVerificationEnabled(boolean hostnameVerificationEnabled) { - this.hostnameVerificationEnabled = hostnameVerificationEnabled; - } - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/handler/MqttRealtimeHandler.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/handler/MqttRealtimeHandler.java deleted file mode 100644 index 75b978f8..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/handler/MqttRealtimeHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.EventEmit; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.annotation.RateLimited; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry; - -import java.util.List; - -/** - * MQTT 接入 Handler。 - * - *

MQTT 没有"命令字"概念,使用默认的 wildcard 匹配({@code command} 为空)。 - * 同一 Dispatcher 仍能正确路由,因为协议 ID 做了隔离。 - */ -@ProtocolHandler(protocol = ProtocolId.MQTT_YUTONG) -public class MqttRealtimeHandler { - - private final MqttProfileRegistry profileRegistry; - - public MqttRealtimeHandler(MqttProfileRegistry profileRegistry) { - this.profileRegistry = profileRegistry; - } - - @MessageMapping(desc = "MQTT profile 实时数据") - @RateLimited(perVin = 20) - @EventEmit({VehicleEvent.Realtime.class, VehicleEvent.Location.class, VehicleEvent.Passthrough.class}) - public List onMqtt(MqttPayload payload) { - return profileRegistry.toEvents(payload); - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java deleted file mode 100644 index 01b9e1ef..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/mapper/YutongEventMapper.java +++ /dev/null @@ -1,260 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.mapper; - -import com.fasterxml.jackson.databind.JsonNode; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.LocationPayload; -import com.lingniu.ingest.api.event.RealtimePayload; -import com.lingniu.ingest.api.event.TelemetryMetadataValues; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.spi.EventMapper; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * 宇通 MQTT payload → 领域事件。 - * - *

字段名取自旧项目的 {@code ReceptionDataVO}(大写 + 下划线),对字段映射一致性做出显式断言, - * 便于与旧服务双跑对账。 - */ -public final class YutongEventMapper implements EventMapper { - - private final VehicleIdentityResolver identityResolver; - - public YutongEventMapper(VehicleIdentityResolver identityResolver) { - if (identityResolver == null) { - throw new IllegalArgumentException("identityResolver must not be null"); - } - this.identityResolver = identityResolver; - } - - @Override - public List toEvents(MqttPayload payload) { - JsonNode d = payload.data(); - if (d == null || !d.isObject()) return List.of(); - - Instant ingestTime = Instant.now(); - String externalDeviceId = firstNonBlank(payload.deviceId(), payload.vin()); - IdentityResolution identity = resolveIdentity(payload); - String vin = identity.vin(); - Map meta = metadata(payload, externalDeviceId, vin, identity); - - if (d.path("_parseError").asBoolean(false)) { - return List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin == null || vin.isBlank() ? "unknown" : vin, - ProtocolId.MQTT_YUTONG, - payload.deviceTime(), - ingestTime, - null, - withMeta(meta, - "parseError", "true", - "parseErrorMessage", d.path("_parseErrorMessage").asText("")), - 0, - rawBytes(d.path("_rawBase64").asText("")))); - } - - Double speed = optDouble(d, "METER_SPEED"); - Double mileage = optDouble(d, "TOTAL_MILEAGE"); - Double soc = optDouble(d, "BATTERY_CAPACITY_SOC"); - Double fcVoltage = optDouble(d, "VOLTAGE_OF_FC"); - Double fcCurrent = optDouble(d, "CURRENT_OF_FC"); - Double fcTemp = optDouble(d, "TEMPT_OF_FC"); - Double leftH2 = optDouble(d, "LEFT_HYDROGEN"); - Double lowH2Pressure = optDouble(d, "HYDROGEN_LOW_PRESSURE"); - Double longitude = optCoord(d, "LONGITUDE"); - Double latitude = optCoord(d, "LATITUDE"); - Double direction = optDouble(d, "GPSDirection"); - Double acc = optDouble(d, "ACC_PEDAL_APT"); - Double brake = optDouble(d, "BRAKEPEDALAPT"); - Integer gear = optInt(d, "ON_GEAR"); - - RealtimePayload realtime = new RealtimePayload( - speed, mileage, soc, null, null, - fcVoltage, fcCurrent, fcTemp, leftH2, null, lowH2Pressure, - null, null, null, gear, acc, brake, - longitude, latitude, null, direction, - null, null); - - List out = new ArrayList<>(2); - out.add(new VehicleEvent.Realtime( - UUID.randomUUID().toString(), - vin, ProtocolId.MQTT_YUTONG, payload.deviceTime(), ingestTime, - null, meta, realtime)); - - if (longitude != null && latitude != null) { - LocationPayload loc = new LocationPayload( - longitude, latitude, 0.0, - speed == null ? 0.0 : speed, - direction == null ? 0.0 : direction, - 0, 0); - Map locationMeta = mileage == null - ? meta - : withMeta(meta, "total_mileage_km", TelemetryMetadataValues.doubleValue(mileage)); - out.add(new VehicleEvent.Location( - UUID.randomUUID().toString(), - vin, ProtocolId.MQTT_YUTONG, payload.deviceTime(), ingestTime, - null, locationMeta, loc)); - } - return out; - } - - private IdentityResolution resolveIdentity(MqttPayload payload) { - if (payload.hasIdentitySnapshot()) { - VehicleIdentitySource source = VehicleIdentitySource.UNKNOWN; - try { - source = VehicleIdentitySource.valueOf(payload.identitySource()); - } catch (IllegalArgumentException ignored) { - // 保守兜底,外部化的 payload 如果带了未知 source 不应影响接收主链路。 - } - return new IdentityResolution( - new VehicleIdentity(payload.resolvedVin(), payload.identityResolved(), source), - payload.identityErrorMessage().isBlank() ? null : payload.identityErrorMessage()); - } - String externalDeviceId = firstNonBlank(payload.deviceId(), payload.vin()); - String externalVin = payload.externalVin() == null ? "" : payload.externalVin(); - String phone = payload.phone() == null ? "" : payload.phone(); - String plateNo = payload.plateNo() == null ? "" : payload.plateNo(); - try { - if (externalDeviceId == null - || externalDeviceId.isBlank() - || "unknown".equalsIgnoreCase(externalDeviceId.trim())) { - if (!externalVin.isBlank() || !phone.isBlank() || !plateNo.isBlank()) { - return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.MQTT_YUTONG, - externalVin, - phone, - "", - plateNo))); - } - return IdentityResolution.ok(new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN)); - } - VehicleIdentity bound = identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.MQTT_YUTONG, - externalVin, - phone, - externalDeviceId, - plateNo)); - if (bound.resolved() || !looksLikeVin(externalDeviceId)) { - return IdentityResolution.ok(bound); - } - return IdentityResolution.ok(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.MQTT_YUTONG, - externalDeviceId, - phone, - externalDeviceId, - plateNo))); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private static String firstNonBlank(String... values) { - for (String value : values) { - if (value != null && !value.isBlank()) { - return value; - } - } - return ""; - } - - private static boolean looksLikeVin(String value) { - if (value == null || value.isBlank()) { - return false; - } - String trimmed = value.trim(); - return trimmed.length() == 17; - } - - private static Double optDouble(JsonNode n, String field) { - JsonNode v = n.get(field); - if (v == null || v.isNull() || !v.isNumber()) return null; - return v.asDouble(); - } - - private static Integer optInt(JsonNode n, String field) { - JsonNode v = n.get(field); - if (v == null || v.isNull() || !v.isNumber()) return null; - return v.asInt(); - } - - /** 宇通坐标约定:1e6 整数 或 已经是小数。同时过滤无效值 999999 / 0。 */ - private static Double optCoord(JsonNode n, String field) { - Double v = optDouble(n, field); - if (v == null) return null; - if (v.intValue() == 999_999 || v == 0.0) return null; - if (Math.abs(v) > 1000) return v / 1_000_000.0; - return v; - } - - private static Map withMeta(Map meta, String key, String value) { - java.util.HashMap copy = new java.util.HashMap<>(meta); - copy.put(key, value == null ? "" : value); - return Map.copyOf(copy); - } - - private static Map metadata(MqttPayload payload, - String externalDeviceId, - String vin, - IdentityResolution identity) { - java.util.HashMap meta = new java.util.HashMap<>(); - meta.put("endpoint", payload.endpointName() == null ? "" : payload.endpointName()); - meta.put("topic", payload.topic() == null ? "" : payload.topic()); - meta.put("profile", payload.profile() == null ? "" : payload.profile()); - meta.put("externalVin", payload.externalVin() == null ? "" : payload.externalVin()); - meta.put("phone", payload.phone() == null ? "" : payload.phone()); - meta.put("mqttDeviceId", externalDeviceId == null ? "" : externalDeviceId); - meta.put("plateNo", payload.plateNo() == null ? "" : payload.plateNo()); - meta.put("vin", vin == null || vin.isBlank() ? "unknown" : vin); - meta.put("identityResolved", Boolean.toString(identity.identity().resolved())); - meta.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - meta.put("identityError", "true"); - meta.put("identityErrorMessage", identity.errorMessage()); - } - return Map.copyOf(meta); - } - - private static Map withMeta(Map meta, - String firstKey, - String firstValue, - String secondKey, - String secondValue) { - java.util.HashMap copy = new java.util.HashMap<>(meta); - copy.put(firstKey, firstValue == null ? "" : firstValue); - copy.put(secondKey, secondValue == null ? "" : secondValue); - return Map.copyOf(copy); - } - - private static byte[] rawBytes(String base64) { - if (base64 == null || base64.isBlank()) { - return new byte[0]; - } - try { - return Base64.getDecoder().decode(base64); - } catch (IllegalArgumentException e) { - return new byte[0]; - } - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) { - private static IdentityResolution ok(VehicleIdentity identity) { - return new IdentityResolution(identity, null); - } - - private String vin() { - return identity.vin(); - } - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/model/MqttPayload.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/model/MqttPayload.java deleted file mode 100644 index ba454c64..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/model/MqttPayload.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.model; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.JsonNode; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentitySource; - -import java.time.Instant; - -/** - * MQTT 解析后的统一消息体:{@code Dispatcher} 收到的 RawFrame payload 就是这个。 - * - * @param endpointName 来源 endpoint 逻辑名 - * @param profile 字段映射 profile(如 yutong / haipote) - * @param topic 原始 topic - * @param vin VIN(解析自 payload 的 device / vin 字段,保留兼容旧调用) - * @param externalVin 外部 VIN 字段 - * @param phone 外部手机号 / SIM 字段 - * @param deviceId 外部设备号 / 终端 ID / IMEI 字段 - * @param plateNo 外部车牌字段 - * @param resolvedVin 已解析的内部 VIN,供 RAW 和业务事件复用同一次身份查询 - * @param deviceTime 设备上报时刻 - * @param data 原始 JSON 树,供 Mapper 按 profile 提取字段 - */ -public record MqttPayload( - String endpointName, - String profile, - String topic, - String vin, - String externalVin, - String phone, - String deviceId, - String plateNo, - @JsonIgnore String resolvedVin, - @JsonIgnore boolean identityResolved, - @JsonIgnore String identitySource, - @JsonIgnore String identityErrorMessage, - Instant deviceTime, - JsonNode data -) { - public MqttPayload { - resolvedVin = resolvedVin == null ? "" : resolvedVin.trim(); - identitySource = identitySource == null ? "" : identitySource.trim(); - identityErrorMessage = identityErrorMessage == null ? "" : identityErrorMessage.trim(); - } - - public MqttPayload(String endpointName, - String profile, - String topic, - String vin, - String externalVin, - String phone, - String deviceId, - String plateNo, - Instant deviceTime, - JsonNode data) { - this(endpointName, profile, topic, vin, externalVin, phone, deviceId, plateNo, - "", false, "", "", deviceTime, data); - } - - public MqttPayload(String endpointName, - String profile, - String topic, - String vin, - Instant deviceTime, - JsonNode data) { - this(endpointName, profile, topic, vin, "", "", vin, "", deviceTime, data); - } - - public MqttPayload withIdentity(VehicleIdentity identity, String errorMessage) { - VehicleIdentity safe = identity == null - ? new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN) - : identity; - return new MqttPayload(endpointName, profile, topic, vin, externalVin, phone, deviceId, plateNo, - safe.vin(), safe.resolved(), safe.source().name(), errorMessage, deviceTime, data); - } - - @JsonIgnore - public boolean hasIdentitySnapshot() { - return !identitySource.isBlank(); - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/parser/MqttPayloadParser.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/parser/MqttPayloadParser.java deleted file mode 100644 index bc9bae9e..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/parser/MqttPayloadParser.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.parser; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.spi.DecodeException; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; - -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; - -/** - * 宇通 MQTT JSON 消息基础解析器(对标旧 {@code VehicleDataReceptionVO} + {@code ReceptionDataVO})。 - * - *

约定宇通 MQTT 的 payload JSON 形如: - *

{@code
- * {
- *   "id": "xxx",
- *   "device": "LTEST000000000001",   // VIN
- *   "code": "C001",
- *   "time": "20260413100000",        // yyyyMMddHHmmss
- *   "data": { "METER_SPEED": 52, "LATITUDE": 39916527, ... }
- * }
- * }
- * - *

其它厂商只需新增 {@code MqttProfile} 实现,公共 MQTT endpoint 生命周期不需要修改。 - */ -public final class MqttPayloadParser { - - private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); - private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai"); - - private final ObjectMapper mapper; - - public MqttPayloadParser(ObjectMapper mapper) { - this.mapper = mapper; - } - - public MqttPayload parse(String endpointName, String profile, String topic, byte[] bytes) { - try { - JsonNode root = mapper.readTree(bytes); - String externalVin = text(root, "vin", "VIN"); - String deviceId = text(root, "device", "deviceId", "terminalId", "imei", "IMEI"); - String phone = text(root, "sim", "phone", "mobile"); - String plateNo = text(root, "plateNo", "carNo", "plate"); - String vin = !externalVin.isBlank() ? externalVin : deviceId; - Instant deviceTime = parseTime(text(root, "time")); - JsonNode data = root.has("data") ? root.get("data") : root; - return new MqttPayload(endpointName, profile, topic, - vin, externalVin, phone, deviceId, plateNo, deviceTime, data); - } catch (Exception e) { - throw new DecodeException("mqtt payload parse failed: " + e.getMessage(), e); - } - } - - private static String text(JsonNode root, String... keys) { - for (String k : keys) { - JsonNode n = root.get(k); - if (n != null && !n.isNull()) return n.asText(); - } - return ""; - } - - private static Instant parseTime(String raw) { - if (raw == null || raw.isEmpty()) return Instant.now(); - try { - return LocalDateTime.parse(raw, TIME_FMT).atZone(ZONE).toInstant(); - } catch (Exception e) { - return Instant.now(); - } - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfile.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfile.java deleted file mode 100644 index 45ddeb85..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfile.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.profile; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; - -import java.util.List; - -/** - * MQTT 厂商 profile 边界。 - * - *

endpoint 只关心 profile 名称,具体 payload 字段结构、VIN/时间提取和事件映射都收敛在 profile 内。 - */ -public interface MqttProfile { - - String name(); - - MqttPayload parse(String endpointName, String topic, byte[] bytes); - - List toEvents(MqttPayload payload); -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfileRegistry.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfileRegistry.java deleted file mode 100644 index e1521b23..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/MqttProfileRegistry.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.profile; - -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.spi.DecodeException; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; - -import java.time.Instant; -import java.util.Base64; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; - -public final class MqttProfileRegistry { - - private final Map profiles; - - public MqttProfileRegistry(Collection profiles) { - Map indexed = new LinkedHashMap<>(); - for (MqttProfile profile : profiles) { - String key = normalize(profile.name()); - if (key.isEmpty()) { - throw new IllegalArgumentException("mqtt profile name must not be blank"); - } - MqttProfile old = indexed.putIfAbsent(key, profile); - if (old != null) { - throw new IllegalArgumentException("duplicated mqtt profile: " + key); - } - } - this.profiles = Map.copyOf(indexed); - } - - public MqttPayload parse(String endpointName, String profileName, String topic, byte[] bytes) { - MqttProfile profile; - try { - profile = resolve(profileName); - } catch (DecodeException e) { - return parseErrorPayload(endpointName, profileName, topic, bytes, e.getMessage(), true); - } - try { - return profile.parse(endpointName, topic, bytes); - } catch (DecodeException e) { - return parseErrorPayload(endpointName, profile.name(), topic, bytes, e.getMessage(), false); - } - } - - public List toEvents(MqttPayload payload) { - if (payload != null && payload.data() != null - && payload.data().path("_operationalError").asBoolean(false)) { - return List.of(operationalErrorEvent(payload)); - } - if (payload != null && payload.data() != null - && payload.data().path("_unsupportedProfile").asBoolean(false)) { - return List.of(parseErrorEvent(payload)); - } - return resolve(payload.profile()).toEvents(payload); - } - - public MqttProfile resolve(String profileName) { - String key = normalize(profileName); - MqttProfile profile = profiles.get(key); - if (profile == null) { - throw new DecodeException("unsupported mqtt profile: " + profileName); - } - return profile; - } - - private static String normalize(String raw) { - return raw == null ? "" : raw.trim().toLowerCase(Locale.ROOT); - } - - private static MqttPayload parseErrorPayload(String endpointName, - String profileName, - String topic, - byte[] bytes, - String message, - boolean unsupportedProfile) { - ObjectNode data = JsonNodeFactory.instance.objectNode(); - data.put("_parseError", true); - data.put("_parseErrorMessage", message == null ? "" : message); - data.put("_unsupportedProfile", unsupportedProfile); - data.put("_rawBase64", Base64.getEncoder().encodeToString(bytes == null ? new byte[0] : bytes)); - return new MqttPayload(endpointName, normalize(profileName), topic, "unknown", Instant.now(), data); - } - - public static MqttPayload operationalErrorPayload(String endpointName, - String profileName, - String topic, - byte[] bytes, - String phase, - String reason) { - ObjectNode data = JsonNodeFactory.instance.objectNode(); - data.put("_operationalError", true); - data.put("_phase", phase == null ? "" : phase); - data.put("_reason", reason == null ? "" : reason); - data.put("_rawBase64", Base64.getEncoder().encodeToString(bytes == null ? new byte[0] : bytes)); - return new MqttPayload(endpointName, normalize(profileName), topic, "unknown", Instant.now(), data); - } - - private static VehicleEvent.Passthrough parseErrorEvent(MqttPayload payload) { - Map metadata = new HashMap<>(); - metadata.put("endpoint", payload.endpointName() == null ? "" : payload.endpointName()); - metadata.put("topic", payload.topic() == null ? "" : payload.topic()); - metadata.put("profile", payload.profile() == null ? "" : payload.profile()); - metadata.put("parseError", "true"); - metadata.put("unsupportedProfile", Boolean.toString(payload.data().path("_unsupportedProfile").asBoolean(false))); - metadata.put("parseErrorMessage", payload.data().path("_parseErrorMessage").asText("")); - addUnknownIdentity(metadata); - return new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - "unknown", - ProtocolId.MQTT_YUTONG, - payload.deviceTime(), - Instant.now(), - null, - Map.copyOf(metadata), - 0, - rawBytes(payload.data().path("_rawBase64").asText(""))); - } - - private static VehicleEvent.Passthrough operationalErrorEvent(MqttPayload payload) { - Map metadata = new HashMap<>(); - metadata.put("endpoint", payload.endpointName() == null ? "" : payload.endpointName()); - metadata.put("topic", payload.topic() == null ? "" : payload.topic()); - metadata.put("profile", payload.profile() == null ? "" : payload.profile()); - metadata.put("operationalError", "true"); - metadata.put("phase", payload.data().path("_phase").asText("")); - metadata.put("reason", payload.data().path("_reason").asText("")); - addUnknownIdentity(metadata); - return new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - "unknown", - ProtocolId.MQTT_YUTONG, - payload.deviceTime(), - Instant.now(), - null, - Map.copyOf(metadata), - 0, - rawBytes(payload.data().path("_rawBase64").asText(""))); - } - - private static void addUnknownIdentity(Map metadata) { - metadata.put("vin", "unknown"); - metadata.put("identityResolved", "false"); - metadata.put("identitySource", "UNKNOWN"); - } - - private static byte[] rawBytes(String base64) { - if (base64 == null || base64.isBlank()) { - return new byte[0]; - } - try { - return Base64.getDecoder().decode(base64); - } catch (IllegalArgumentException e) { - return new byte[0]; - } - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/YutongMqttProfile.java b/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/YutongMqttProfile.java deleted file mode 100644 index 426f2efb..00000000 --- a/modules/inbound/inbound-mqtt/src/main/java/com/lingniu/ingest/inbound/mqtt/profile/YutongMqttProfile.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.profile; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.inbound.mqtt.mapper.YutongEventMapper; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; -import com.lingniu.ingest.inbound.mqtt.parser.MqttPayloadParser; - -import java.util.List; - -public final class YutongMqttProfile implements MqttProfile { - - public static final String NAME = "yutong"; - - private final MqttPayloadParser parser; - private final YutongEventMapper mapper; - - public YutongMqttProfile(MqttPayloadParser parser, YutongEventMapper mapper) { - this.parser = parser; - this.mapper = mapper; - } - - @Override - public String name() { - return NAME; - } - - @Override - public MqttPayload parse(String endpointName, String topic, byte[] bytes) { - return parser.parse(endpointName, NAME, topic, bytes); - } - - @Override - public List toEvents(MqttPayload payload) { - return mapper.toEvents(payload); - } -} diff --git a/modules/inbound/inbound-mqtt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/inbound/inbound-mqtt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index c8aa81c4..00000000 --- a/modules/inbound/inbound-mqtt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.inbound.mqtt.config.MqttInboundAutoConfiguration diff --git a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttEndpointManagerTest.java b/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttEndpointManagerTest.java deleted file mode 100644 index e4bfcb10..00000000 --- a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttEndpointManagerTest.java +++ /dev/null @@ -1,473 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; -import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor; -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.core.dispatcher.HandlerDefinition; -import com.lingniu.ingest.core.dispatcher.HandlerInvoker; -import com.lingniu.ingest.core.dispatcher.HandlerRegistry; -import com.lingniu.ingest.core.pipeline.InterceptorChain; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.inbound.mqtt.client.MqttEndpointManager; -import com.lingniu.ingest.inbound.mqtt.config.MqttInboundProperties; -import com.lingniu.ingest.inbound.mqtt.handler.MqttRealtimeHandler; -import com.lingniu.ingest.inbound.mqtt.mapper.YutongEventMapper; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; -import com.lingniu.ingest.inbound.mqtt.parser.MqttPayloadParser; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry; -import com.lingniu.ingest.inbound.mqtt.profile.YutongMqttProfile; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class MqttEndpointManagerTest { - - @Test - void inboundMessageRawArchiveUsesResolvedVehicleIdentity() throws Exception { - MqttInboundProperties.Endpoint endpoint = new MqttInboundProperties.Endpoint(); - endpoint.setName("endpoint-a"); - endpoint.setTopic("/ytforward/#"); - endpoint.setProfile("yutong"); - byte[] payload = """ - {"device":"YT-DEVICE-001","time":"20260622150000","data":{"METER_SPEED":31,"TOTAL_MILEAGE":1024}} - """.trim().getBytes(StandardCharsets.UTF_8); - InMemoryVehicleIdentityService identities = new InMemoryVehicleIdentityService(); - identities.bind(new VehicleIdentityBinding( - ProtocolId.MQTT_YUTONG, - "LNVIN000000MQTT01", - "", - "YT-DEVICE-001", - "")); - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - MqttProfileRegistry profileRegistry = new MqttProfileRegistry(List.of( - new YutongMqttProfile( - new MqttPayloadParser(new ObjectMapper()), - new YutongEventMapper(identities)))); - Dispatcher dispatcher = new Dispatcher( - registry(profileRegistry), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - MqttEndpointManager manager = new MqttEndpointManager( - new MqttInboundProperties(), profileRegistry, identities, dispatcher); - - invokeOnMessage(manager, endpoint, "/ytforward/vehicle/YT-DEVICE-001", payload); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.vin()).isEqualTo("LNVIN000000MQTT01"); - assertThat(archive.metadata()) - .containsEntry("vin", "LNVIN000000MQTT01") - .containsEntry("mqttDeviceId", "YT-DEVICE-001") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_DEVICE_ID"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void inboundMessageReusesResolvedIdentityForRawArchiveAndMappedEvents() throws Exception { - MqttInboundProperties.Endpoint endpoint = new MqttInboundProperties.Endpoint(); - endpoint.setName("endpoint-a"); - endpoint.setTopic("/ytforward/#"); - endpoint.setProfile("yutong"); - byte[] payload = """ - {"device":"YT-DEVICE-001","time":"20260622150000","data":{"METER_SPEED":31,"TOTAL_MILEAGE":1024}} - """.trim().getBytes(StandardCharsets.UTF_8); - CountingIdentityResolver identities = new CountingIdentityResolver(new VehicleIdentity( - "LNVIN000000MQTT01", true, VehicleIdentitySource.BOUND_DEVICE_ID)); - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - MqttProfileRegistry profileRegistry = new MqttProfileRegistry(List.of( - new YutongMqttProfile( - new MqttPayloadParser(new ObjectMapper()), - new YutongEventMapper(identities)))); - Dispatcher dispatcher = new Dispatcher( - registry(profileRegistry), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - MqttEndpointManager manager = new MqttEndpointManager( - new MqttInboundProperties(), profileRegistry, identities, dispatcher); - - invokeOnMessage(manager, endpoint, "/ytforward/vehicle/YT-DEVICE-001", payload); - - assertThat(sink.await()).isTrue(); - assertThat(identities.calls()).isEqualTo(1); - assertThat(sink.events) - .extracting(VehicleEvent::vin) - .contains("LNVIN000000MQTT01"); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.parsedJson()) - .contains("\"deviceId\":\"YT-DEVICE-001\"") - .doesNotContain("resolvedVin") - .doesNotContain("identityResolved") - .doesNotContain("identitySource") - .doesNotContain("identityErrorMessage"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void malformedInboundMessageArchivesParseFailureMetadataAndPassthrough() throws Exception { - MqttInboundProperties.Endpoint endpoint = new MqttInboundProperties.Endpoint(); - endpoint.setName("endpoint-a"); - endpoint.setTopic("/ytforward/#"); - endpoint.setProfile("yutong"); - byte[] badPayload = "{bad-json".getBytes(StandardCharsets.UTF_8); - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - MqttProfileRegistry profileRegistry = new MqttProfileRegistry(List.of( - new YutongMqttProfile( - new MqttPayloadParser(new ObjectMapper()), - new YutongEventMapper(new InMemoryVehicleIdentityService())))); - Dispatcher dispatcher = new Dispatcher( - registry(profileRegistry), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - MqttEndpointManager manager = new MqttEndpointManager( - new MqttInboundProperties(), profileRegistry, new InMemoryVehicleIdentityService(), dispatcher); - - invokeOnMessage(manager, endpoint, "/ytforward/bad", badPayload); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(archive.rawBytes()).containsExactly(badPayload); - assertThat(archive.metadata()) - .containsEntry("endpoint", "endpoint-a") - .containsEntry("topic", "/ytforward/bad") - .containsEntry("profile", "yutong") - .containsEntry("parseError", "true") - .containsEntry("vin", "unknown"); - assertThat(archive.metadata().get("parseErrorMessage")) - .contains("mqtt payload parse failed"); - }); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(passthrough.data()).containsExactly(badPayload); - assertThat(passthrough.metadata()) - .containsEntry("parseError", "true") - .containsEntry("endpoint", "endpoint-a") - .containsEntry("profile", "yutong"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void endpointInitializationFailureIsArchivedAndDispatchedAsPassthrough() throws Exception { - MqttInboundProperties props = new MqttInboundProperties(); - MqttInboundProperties.Endpoint endpoint = new MqttInboundProperties.Endpoint(); - endpoint.setName("bad-endpoint"); - endpoint.setUri("://bad-uri"); - endpoint.setTopic("/vehicles/#"); - endpoint.setProfile("yutong"); - props.setEndpoints(List.of(endpoint)); - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - MqttProfileRegistry profileRegistry = new MqttProfileRegistry(List.of()); - Dispatcher dispatcher = new Dispatcher( - registry(profileRegistry), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - MqttEndpointManager manager = new MqttEndpointManager( - props, profileRegistry, new InMemoryVehicleIdentityService(), dispatcher); - - manager.start(); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(archive.metadata()) - .containsEntry("endpoint", "bad-endpoint") - .containsEntry("profile", "yutong") - .containsEntry("operationalError", "true") - .containsEntry("phase", "init") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(passthrough.metadata()) - .containsEntry("operationalError", "true") - .containsEntry("endpoint", "bad-endpoint") - .containsEntry("profile", "yutong") - .containsEntry("phase", "init"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void profileRuntimeFailureIsArchivedAndDispatchedAsPassthrough() throws Exception { - MqttInboundProperties.Endpoint endpoint = new MqttInboundProperties.Endpoint(); - endpoint.setName("endpoint-a"); - endpoint.setTopic("/vehicles/#"); - endpoint.setProfile("broken"); - byte[] payload = "{\"device\":\"YT-DEVICE-001\"}".getBytes(StandardCharsets.UTF_8); - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - MqttProfileRegistry profileRegistry = new MqttProfileRegistry(List.of(new BrokenProfile())); - Dispatcher dispatcher = new Dispatcher( - registry(profileRegistry), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - MqttEndpointManager manager = new MqttEndpointManager( - new MqttInboundProperties(), profileRegistry, new InMemoryVehicleIdentityService(), dispatcher); - - invokeOnMessage(manager, endpoint, "/vehicles/broken", payload); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(archive.rawBytes()).containsExactly(payload); - assertThat(archive.metadata()) - .containsEntry("endpoint", "endpoint-a") - .containsEntry("topic", "/vehicles/broken") - .containsEntry("profile", "broken") - .containsEntry("operationalError", "true") - .containsEntry("phase", "message") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - assertThat(archive.metadata().get("reason")).contains("profile blew up"); - }); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(passthrough.data()).containsExactly(payload); - assertThat(passthrough.metadata()) - .containsEntry("operationalError", "true") - .containsEntry("endpoint", "endpoint-a") - .containsEntry("profile", "broken") - .containsEntry("phase", "message"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void rejectsMissingIdentityResolver() throws Exception { - MqttProfileRegistry profileRegistry = new MqttProfileRegistry(List.of()); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of()); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - registry(profileRegistry), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - - assertThatThrownBy(() -> new MqttEndpointManager( - new MqttInboundProperties(), profileRegistry, null, dispatcher)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("identityResolver"); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void identityResolverFailureStillArchivesAndDispatchesRealtimeWithUnknownIdentity() throws Exception { - MqttInboundProperties.Endpoint endpoint = new MqttInboundProperties.Endpoint(); - endpoint.setName("endpoint-a"); - endpoint.setTopic("/ytforward/#"); - endpoint.setProfile("yutong"); - byte[] payload = """ - {"device":"YT-DEVICE-001","time":"20260622150000","data":{"METER_SPEED":31,"TOTAL_MILEAGE":1024}} - """.trim().getBytes(StandardCharsets.UTF_8); - var failingIdentity = (com.lingniu.ingest.identity.VehicleIdentityResolver) lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }; - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - MqttProfileRegistry profileRegistry = new MqttProfileRegistry(List.of( - new YutongMqttProfile( - new MqttPayloadParser(new ObjectMapper()), - new YutongEventMapper(failingIdentity)))); - Dispatcher dispatcher = new Dispatcher( - registry(profileRegistry), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - MqttEndpointManager manager = new MqttEndpointManager( - new MqttInboundProperties(), profileRegistry, failingIdentity, dispatcher); - - invokeOnMessage(manager, endpoint, "/ytforward/vehicle/YT-DEVICE-001", payload); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(archive.vin()).isEqualTo("unknown"); - assertThat(archive.rawBytes()).containsExactly(payload); - assertThat(archive.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("mqttDeviceId", "YT-DEVICE-001") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable"); - }); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Realtime.class); - assertThat(event.vin()).isEqualTo("unknown"); - assertThat(event.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("mqttDeviceId", "YT-DEVICE-001") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - private static HandlerRegistry registry(MqttProfileRegistry profileRegistry) throws NoSuchMethodException { - HandlerRegistry registry = new HandlerRegistry(); - MqttRealtimeHandler handler = new MqttRealtimeHandler(profileRegistry); - registry.register(new HandlerDefinition( - ProtocolId.MQTT_YUTONG, - 0, - 0, - "test mqtt", - handler, - MqttRealtimeHandler.class.getMethod("onMqtt", MqttPayload.class), - MqttPayload.class, - null, - null, - null)); - return registry; - } - - private static void invokeOnMessage(MqttEndpointManager manager, - MqttInboundProperties.Endpoint endpoint, - String topic, - byte[] payload) throws Exception { - Method method = MqttEndpointManager.class.getDeclaredMethod( - "onMessage", MqttInboundProperties.Endpoint.class, String.class, byte[].class); - method.setAccessible(true); - method.invoke(manager, endpoint, topic, payload); - } - - private static final class RecordingSink implements EventSink { - private final List events = new CopyOnWriteArrayList<>(); - private final CountDownLatch latch; - - private RecordingSink(int expectedEvents) { - this.latch = new CountDownLatch(expectedEvents); - } - - @Override - public String name() { - return "recording"; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - events.add(event); - latch.countDown(); - return CompletableFuture.completedFuture(null); - } - - private boolean await() throws InterruptedException { - return latch.await(3, TimeUnit.SECONDS); - } - } - - private static final class CountingIdentityResolver implements VehicleIdentityResolver { - private final VehicleIdentity identity; - private final AtomicInteger calls = new AtomicInteger(); - - private CountingIdentityResolver(VehicleIdentity identity) { - this.identity = identity; - } - - @Override - public VehicleIdentity resolve(VehicleIdentityLookup lookup) { - calls.incrementAndGet(); - return identity; - } - - private int calls() { - return calls.get(); - } - } - - private static final class BrokenProfile implements com.lingniu.ingest.inbound.mqtt.profile.MqttProfile { - @Override - public String name() { - return "broken"; - } - - @Override - public MqttPayload parse(String endpointName, String topic, byte[] bytes) { - throw new IllegalStateException("profile blew up"); - } - - @Override - public List toEvents(MqttPayload payload) { - return List.of(); - } - } -} diff --git a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttProfileRegistryTest.java b/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttProfileRegistryTest.java deleted file mode 100644 index c6d8add3..00000000 --- a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttProfileRegistryTest.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.inbound.mqtt.mapper.YutongEventMapper; -import com.lingniu.ingest.inbound.mqtt.parser.MqttPayloadParser; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry; -import com.lingniu.ingest.inbound.mqtt.profile.YutongMqttProfile; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class MqttProfileRegistryTest { - - private final MqttProfileRegistry registry = new MqttProfileRegistry(List.of( - new YutongMqttProfile(new MqttPayloadParser(new ObjectMapper()), - new YutongEventMapper(new InMemoryVehicleIdentityService())))); - - @Test - void resolvesProfileCaseInsensitiveAndRoutesParseAndMapping() { - byte[] payload = """ - { - "device": "LTEST000000000001", - "time": "20260413100000", - "data": { - "METER_SPEED": 52.3, - "TOTAL_MILEAGE": 123456.7, - "BATTERY_CAPACITY_SOC": 70, - "LONGITUDE": 116397128, - "LATITUDE": 39916527 - } - } - """.getBytes(StandardCharsets.UTF_8); - - var parsed = registry.parse("endpoint-a", "YuTong", "/ytforward/shln/dev1", payload); - - assertThat(parsed.profile()).isEqualTo("yutong"); - assertThat(parsed.vin()).isEqualTo("LTEST000000000001"); - assertThat(registry.toEvents(parsed)) - .extracting(VehicleEvent::vin) - .containsOnly("LTEST000000000001"); - } - - @Test - void parserKeepsCommonExternalIdentityFieldsForColdStorageAndMapping() { - byte[] payload = """ - { - "vin": "LVIN0000000000001", - "terminalId": "terminal-001", - "sim": "13800138000", - "plateNo": "粤B12345", - "time": "20260413100000", - "data": { "METER_SPEED": 12.3 } - } - """.getBytes(StandardCharsets.UTF_8); - - var parsed = registry.parse("endpoint-a", "yutong", "/ytforward/shln/terminal-001", payload); - - assertThat(parsed.externalVin()).isEqualTo("LVIN0000000000001"); - assertThat(parsed.deviceId()).isEqualTo("terminal-001"); - assertThat(parsed.phone()).isEqualTo("13800138000"); - assertThat(parsed.plateNo()).isEqualTo("粤B12345"); - } - - @Test - void unsupportedProfileBecomesPassthroughEventForOperationalTraceability() { - byte[] payload = "{\"device\":\"D1\"}".getBytes(StandardCharsets.UTF_8); - - var parsed = registry.parse("endpoint-a", "unknown", "/x", payload); - - assertThat(parsed.profile()).isEqualTo("unknown"); - assertThat(parsed.data().path("_parseError").asBoolean()).isTrue(); - assertThat(parsed.data().path("_unsupportedProfile").asBoolean()).isTrue(); - assertThat(registry.toEvents(parsed)).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.data()).containsExactly(payload); - assertThat(passthrough.metadata()) - .containsEntry("parseError", "true") - .containsEntry("unsupportedProfile", "true") - .containsEntry("profile", "unknown") - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } - - @Test - void malformedPayloadBecomesPassthroughEventForOperationalTraceability() { - byte[] payload = "{bad-json".getBytes(StandardCharsets.UTF_8); - - var parsed = registry.parse("endpoint-a", "yutong", "/ytforward/bad", payload); - - assertThat(parsed.vin()).isEqualTo("unknown"); - assertThat(parsed.data().path("_parseError").asBoolean()).isTrue(); - assertThat(registry.toEvents(parsed)).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(com.lingniu.ingest.api.ProtocolId.MQTT_YUTONG); - assertThat(passthrough.data()).containsExactly(payload); - assertThat(passthrough.metadata()) - .containsEntry("parseError", "true") - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } - - @Test - void operationalPayloadBecomesPassthroughEventWithFailureMetadata() { - byte[] payload = """ - {"operationalError":true,"endpoint":"endpoint-a","phase":"init","reason":"bad uri"} - """.getBytes(StandardCharsets.UTF_8); - - var parsed = MqttProfileRegistry.operationalErrorPayload( - "endpoint-a", "yutong", "/operational", payload, "init", "bad uri"); - - assertThat(registry.toEvents(parsed)).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(com.lingniu.ingest.api.ProtocolId.MQTT_YUTONG); - assertThat(passthrough.vin()).isEqualTo("unknown"); - assertThat(passthrough.data()).containsExactly(payload); - assertThat(passthrough.metadata()) - .containsEntry("operationalError", "true") - .containsEntry("endpoint", "endpoint-a") - .containsEntry("profile", "yutong") - .containsEntry("phase", "init") - .containsEntry("reason", "bad uri") - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } -} diff --git a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttTlsSupportTest.java b/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttTlsSupportTest.java deleted file mode 100644 index 1e9ab16e..00000000 --- a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/MqttTlsSupportTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt; - -import com.lingniu.ingest.inbound.mqtt.client.MqttTlsSupport; -import com.lingniu.ingest.inbound.mqtt.config.MqttInboundProperties; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import javax.net.ssl.SSLSocketFactory; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class MqttTlsSupportTest { - - @TempDir - Path tempDir; - - @Test - void buildsTrustManagerFromCaPem() throws Exception { - Path ca = tempDir.resolve("ca.pem"); - Files.writeString(ca, CERT); - MqttInboundProperties.Tls tls = new MqttInboundProperties.Tls(); - tls.setCaPem(ca.toString()); - - SSLSocketFactory factory = MqttTlsSupport.socketFactory(tls); - - assertThat(factory).isNotNull(); - } - - @Test - void buildsMutualTlsFromClientCertificateAndKey() throws Exception { - Path ca = tempDir.resolve("ca.pem"); - Path cert = tempDir.resolve("client.pem"); - Path key = tempDir.resolve("client.key"); - Files.writeString(ca, CERT); - Files.writeString(cert, CERT); - Files.writeString(key, KEY); - MqttInboundProperties.Tls tls = new MqttInboundProperties.Tls(); - tls.setCaPem(ca.toString()); - tls.setClientPem(cert.toString()); - tls.setClientKey(key.toString()); - - SSLSocketFactory factory = MqttTlsSupport.socketFactory(tls); - - assertThat(factory).isNotNull(); - } - - private static final String CERT = """ - -----BEGIN CERTIFICATE----- - MIIC/zCCAeegAwIBAgIUPg+j3PAaXVWYykrese0Ssb/blk4wDQYJKoZIhvcNAQEL - BQAwDzENMAsGA1UEAwwEdGVzdDAeFw0yNjA2MjIwNTA0MzNaFw0yNjA2MjMwNTA0 - MzNaMA8xDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK - AoIBAQChuMSubmmNY13b4PUGBoUO+EIy074ZfbRMPHCGJhfSnZVWdETBflOe3kNy - ouejxN2eT3Xoqf7slqTPySJo8hZQjukORNtudMPWQkiBrvaWPCttlYtCxpxhxRw0 - VnIo/jYP2Fmwt2buHV//Kn039amDDr3auZWVXEIGLcafVpd/3v7c+1NLbMPR/uK8 - 62Yk6UG1rlvOYBIttNp2Y9fGZLkMLA5fE3xsPbg8VeWuN9/xjPtZGZ5qUYHaQn3Z - DzmZ8U9q5TGCbcpoVEIooRSb2lIILAqw2FRAR7ashCcJf6QzPVdhoZ8QIovJoViB - nzuFP0I4VYp9fT1pRmBYjcZtIsSfAgMBAAGjUzBRMB0GA1UdDgQWBBSzG5+aGFJr - qgnVlOmtdz8iGzU5RjAfBgNVHSMEGDAWgBSzG5+aGFJrqgnVlOmtdz8iGzU5RjAP - BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQB7/6Q1e+acJobo/wnW - 9DiGzNaownFRs4yz8EgIYVcc7iROjBrVFrcORJAcFrg1uh0Q4rFcslMmEqFM8ryV - IJ1/L6PcQUp4QlKGbbc9LarhalEdFxhX8IAIp2QCNaG8h9/WczZ5/TczwODfy1tW - BFoLG19/dXd5HXLqxiLIpez2f2a/bbUkTu1Yj9QQUFUX2r6SO6LOWmZQsfboq040 - oeJhDzhL218ze1t7qhBk4rbju8NLFwtdhWZ8M7JBzlj5PfACBewd0eosFtb1Tz3N - eAZn4M/GkzBh4t1M55Jt+GwYBD2A7trDRUCUQxtJ1QVzr2gbIB/FfoGvP0qnE4gk - R3PW - -----END CERTIFICATE----- - """; - - private static final String KEY = """ - -----BEGIN PRIVATE KEY----- - MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChuMSubmmNY13b - 4PUGBoUO+EIy074ZfbRMPHCGJhfSnZVWdETBflOe3kNyouejxN2eT3Xoqf7slqTP - ySJo8hZQjukORNtudMPWQkiBrvaWPCttlYtCxpxhxRw0VnIo/jYP2Fmwt2buHV// - Kn039amDDr3auZWVXEIGLcafVpd/3v7c+1NLbMPR/uK862Yk6UG1rlvOYBIttNp2 - Y9fGZLkMLA5fE3xsPbg8VeWuN9/xjPtZGZ5qUYHaQn3ZDzmZ8U9q5TGCbcpoVEIo - oRSb2lIILAqw2FRAR7ashCcJf6QzPVdhoZ8QIovJoViBnzuFP0I4VYp9fT1pRmBY - jcZtIsSfAgMBAAECggEAQu+cbIwjoRM3NnpqQAezzAniMHJmlNtsJD/B3SxoINL7 - jDCMgr/cMX3SUeDuWmDxz4QZC+dMrbT+W0hnNyO4K7iy6qaCYjnvEsAVjaOSyYT2 - /qDuZoGZGXiBn4IGN0RcsPs9yEBo2HaNFKqL8Hz8H9QarayxpoPsie0pcCrhgtlr - fsVN2AMhbEU15SpD0nkjWkqG51m1EY36seSzAwQod2UasD+eNdZ89a2o849cfuA3 - 1F1TyghVIm0RmAdC1yWuud8YY4xvtpfy1+3RjShW0jgA0nVHfP2UgYfBAJ8+TFGy - +hVCgnnt/eMQ1fCszwLkq5spRcqNr4oOVcm+nR718QKBgQDSvEXaaQgqhARfyN/c - /1qFgOBT34a7pEJXotEC6aDV3YxivAt2jDB4TC0NVS5IGP9NYm4oi0Fiw1v0V5eZ - n8nmq0gkfBjW1y37U4mP74QyKY9rwyjyjjsYqr/aTS8e2ZY4QWmWxm9y5a3Q0Ifu - UwzLD5eqiXNLNvvNZoggo+dVsQKBgQDEdWAntCdTZEYO3fp4/ygCJisPeFgy/0Gw - GsgHCkWR4ZyzmBmFSvf3Es3MmcH3QgkfIROCOMaZ0YcGCLzr/RMdYkgpa9DvhJnG - WZDtumxQAW3G3moAYu2r2wXfOjgsMBDlRBH0mBWTBjpovbWBs1NMFfxsetQYY7zG - aQGLDPJDTwKBgQCD6QQUrlBFRLP0PSocDN9d2AkTl0SgKja44prQputdU7vvhePr - Bd/FPXGp+drpmHQevXFVAa4hI0ZpEXc822+naynSZLerq7AFtQnTxkrKl4dGHjiA - dBV74E4NWOkY93x3pEJy9a2Hj0uY/R9JSEUmypDWWAmKWFWQAhFN1SsWUQKBgD7e - xTfPimpAg78MQLTqCvatGkioHamsUGw4Fd1S5zKpPcmnmjsy46nZBa09Y3pqUpr4 - rdKVstDU4d4He9YVtkFIC4nd7A5KpB962EuLxk/QNT5YPRoEjsTZocZvTjyt4SpN - n2VkKjtT2etdErIAHl8SBib9I9TuTiI8xnamXP03AoGATMdUyehOBqG3+YlS31c8 - z9vcIn2egPOjFCWK64FQIuwpmVU5sx6aYIRDb3/jxYqYCaMRcnMSSp/Mrn2yBnui - oRry31MaC/aTSumn/r6WMz7FEYrbsfr9//bYr+tiTrtimWAe4yTwrqFve1Bw2bOQ - P16PTZ2/SMJDmiLR1MIkCns= - -----END PRIVATE KEY----- - """; -} diff --git a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/YutongEventMapperTest.java b/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/YutongEventMapperTest.java deleted file mode 100644 index a2aa3702..00000000 --- a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/YutongEventMapperTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.inbound.mqtt.mapper.YutongEventMapper; -import com.lingniu.ingest.inbound.mqtt.model.MqttPayload; -import com.lingniu.ingest.inbound.mqtt.parser.MqttPayloadParser; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class YutongEventMapperTest { - - private final ObjectMapper objectMapper = new ObjectMapper(); - private final MqttPayloadParser parser = new MqttPayloadParser(objectMapper); - private final YutongEventMapper mapper = new YutongEventMapper(new InMemoryVehicleIdentityService()); - - @Test - void rejectsMissingIdentityResolver() { - assertThatThrownBy(() -> new YutongEventMapper(null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("identityResolver"); - } - - @Test - void parsesYutongSamplePayload() { - String json = """ - { - "id": "abc", - "device": "LTEST000000000001", - "code": "C1", - "time": "20260413100000", - "data": { - "METER_SPEED": 52.3, - "TOTAL_MILEAGE": 123456.7, - "BATTERY_CAPACITY_SOC": 70, - "LONGITUDE": 116397128, - "LATITUDE": 39916527, - "GPSDirection": 90, - "LEFT_HYDROGEN": 5.5, - "VOLTAGE_OF_FC": 600.0, - "CURRENT_OF_FC": 120.0, - "TEMPT_OF_FC": 65.0, - "ON_GEAR": 3, - "ACC_PEDAL_APT": 25, - "BRAKEPEDALAPT": 0 - } - } - """; - - MqttPayload payload = parser.parse("yutong", "yutong", "/ytforward/shln/dev1", json.getBytes()); - assertThat(payload.vin()).isEqualTo("LTEST000000000001"); - - List events = mapper.toEvents(payload); - assertThat(events).hasSize(2); - - VehicleEvent.Realtime rt = (VehicleEvent.Realtime) events.stream() - .filter(e -> e instanceof VehicleEvent.Realtime).findFirst().orElseThrow(); - assertThat(rt.source()).isEqualTo(ProtocolId.MQTT_YUTONG); - assertThat(rt.vin()).isEqualTo("LTEST000000000001"); - assertThat(rt.payload().speedKmh()).isEqualTo(52.3); - assertThat(rt.payload().batterySoc()).isEqualTo(70.0); - assertThat(rt.payload().longitude()).isEqualTo(116.397128, org.assertj.core.data.Offset.offset(0.000001)); - assertThat(rt.payload().latitude()).isEqualTo(39.916527, org.assertj.core.data.Offset.offset(0.000001)); - assertThat(rt.payload().hydrogenRemainingKg()).isEqualTo(5.5); - - VehicleEvent.Location loc = (VehicleEvent.Location) events.stream() - .filter(e -> e instanceof VehicleEvent.Location).findFirst().orElseThrow(); - assertThat(loc.payload().longitude()).isEqualTo(116.397128, org.assertj.core.data.Offset.offset(0.000001)); - assertThat(loc.metadata()).containsEntry("total_mileage_km", "123456.7"); - } - - @Test - void resolvesVinThroughSharedIdentityServiceWhenDeviceIsAnExternalId() throws Exception { - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.MQTT_YUTONG, - "VIN-INTERNAL-001", - "", - "mqtt-device-001", - "")); - YutongEventMapper mapperWithIdentity = new YutongEventMapper(identity); - - MqttPayload payload = new MqttPayload( - "yutong", - "yutong", - "/ytforward/shln/mqtt-device-001", - "mqtt-device-001", - java.time.Instant.parse("2026-06-22T08:00:00Z"), - objectMapper.readTree(""" - { - "METER_SPEED": 18.5, - "TOTAL_MILEAGE": 99.1 - } - """)); - - List events = mapperWithIdentity.toEvents(payload); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event.vin()).isEqualTo("VIN-INTERNAL-001"); - assertThat(event.metadata()) - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_DEVICE_ID") - .containsEntry("mqttDeviceId", "mqtt-device-001"); - }); - } - - @Test - void resolvesVinThroughSharedIdentityServiceWhenPayloadUsesTerminalId() { - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.MQTT_YUTONG, - "VIN-INTERNAL-TID", - "", - "terminal-001", - "")); - YutongEventMapper mapperWithIdentity = new YutongEventMapper(identity); - String json = """ - { - "terminalId": "terminal-001", - "time": "20260413100000", - "data": { - "METER_SPEED": 18.5, - "TOTAL_MILEAGE": 99.1 - } - } - """; - - MqttPayload payload = parser.parse( - "yutong", "yutong", "/ytforward/shln/terminal-001", json.getBytes()); - List events = mapperWithIdentity.toEvents(payload); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event.vin()).isEqualTo("VIN-INTERNAL-TID"); - assertThat(event.metadata()) - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_DEVICE_ID") - .containsEntry("mqttDeviceId", "terminal-001"); - }); - } - - @Test - void prefersBoundDeviceIdentityOverVinLengthGuess() throws Exception { - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.MQTT_YUTONG, - "VIN-INTERNAL-017", - "", - "DEVICEID000000000", - "")); - YutongEventMapper mapperWithIdentity = new YutongEventMapper(identity); - - MqttPayload payload = new MqttPayload( - "yutong", - "yutong", - "/ytforward/shln/DEVICEID000000000", - "DEVICEID000000000", - java.time.Instant.parse("2026-06-22T08:00:00Z"), - objectMapper.readTree("{\"METER_SPEED\":18.5}")); - - List events = mapperWithIdentity.toEvents(payload); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event.vin()).isEqualTo("VIN-INTERNAL-017"); - assertThat(event.metadata()).containsEntry("identitySource", "BOUND_DEVICE_ID"); - }); - } - - @Test - void identityResolverFailureStillProducesRealtimeEventWithUnknownVin() throws Exception { - YutongEventMapper mapperWithFailingIdentity = new YutongEventMapper(lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }); - MqttPayload payload = new MqttPayload( - "yutong", - "yutong", - "/ytforward/shln/mqtt-device-001", - "mqtt-device-001", - java.time.Instant.parse("2026-06-22T08:00:00Z"), - objectMapper.readTree(""" - { - "METER_SPEED": 18.5, - "TOTAL_MILEAGE": 99.1 - } - """)); - - List events = mapperWithFailingIdentity.toEvents(payload); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Realtime.class); - assertThat(event.vin()).isEqualTo("unknown"); - assertThat(event.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("mqttDeviceId", "mqtt-device-001") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable"); - }); - } - - @Test - void parseErrorPayloadKeepsDiagnosticMessageOnPassthroughEvent() throws Exception { - MqttPayload payload = new MqttPayload( - "endpoint-a", - "yutong", - "/ytforward/shln/bad-device", - "unknown", - java.time.Instant.parse("2026-06-22T08:00:00Z"), - objectMapper.readTree(""" - { - "_parseError": true, - "_parseErrorMessage": "Unexpected character at byte 1", - "_rawBase64": "e2JhZC1qc29u" - } - """)); - - List events = mapper.toEvents(payload); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.data()).containsExactly("{bad-json".getBytes(java.nio.charset.StandardCharsets.UTF_8)); - assertThat(event.metadata()) - .containsEntry("parseError", "true") - .containsEntry("parseErrorMessage", "Unexpected character at byte 1"); - }); - } -} diff --git a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfigurationTest.java b/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfigurationTest.java deleted file mode 100644 index da622ffc..00000000 --- a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundAutoConfigurationTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.config; - -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.inbound.mqtt.profile.MqttProfileRegistry; -import org.junit.jupiter.api.Test; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.StandardEnvironment; - -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class MqttInboundAutoConfigurationTest { - - @Test - void doesNotCreateEndpointFromLegacySingleEndpointEnvironmentKeys() { - MqttInboundProperties props = new MqttInboundProperties(); - StandardEnvironment environment = new StandardEnvironment(); - environment.getPropertySources().addFirst(new MapPropertySource("test", Map.of( - "MQTT_URI", "tcp://127.0.0.1:1883", - "MQTT_TOPIC", "/legacy/#", - "YUTONG_MQTT_ENDPOINT_NAME", "legacy-yutong"))); - - new MqttInboundAutoConfiguration().mqttEndpointManager( - props, - new MqttProfileRegistry(List.of()), - lookup -> new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - new Dispatcher(null, null, null, null, null)); - - assertThat(props.getEndpoints()).isEmpty(); - assertThat(environment.getProperty("MQTT_URI")).isEqualTo("tcp://127.0.0.1:1883"); - } -} diff --git a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledConditionTest.java b/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledConditionTest.java deleted file mode 100644 index 2379fe69..00000000 --- a/modules/inbound/inbound-mqtt/src/test/java/com/lingniu/ingest/inbound/mqtt/config/MqttInboundEnabledConditionTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.lingniu.ingest.inbound.mqtt.config; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.core.io.ResourceLoader; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class MqttInboundEnabledConditionTest { - - private final MqttInboundEnabledCondition condition = new MqttInboundEnabledCondition(); - - @Test - void ignoresLegacyGenericMqttEnabledEnvironmentAlias() { - assertThat(condition.matches(context(Map.of("MQTT_ENABLED", "true")), null)).isFalse(); - } - - @Test - void acceptsYutongMqttEnabledEnvironmentAlias() { - assertThat(condition.matches(context(Map.of("YUTONG_MQTT_ENABLED", "true")), null)).isTrue(); - } - - @Test - void directPropertyOverridesEnvironmentAlias() { - assertThat(condition.matches(context(Map.of( - "lingniu.ingest.mqtt.enabled", "false", - "YUTONG_MQTT_ENABLED", "true")), null)).isFalse(); - } - - private static ConditionContext context(Map properties) { - StandardEnvironment environment = new StandardEnvironment(); - environment.getPropertySources().addFirst(new MapPropertySource("test", properties)); - return new TestConditionContext(environment); - } - - private record TestConditionContext(StandardEnvironment environment) implements ConditionContext { - @Override - public BeanDefinitionRegistry getRegistry() { - return null; - } - - @Override - public ConfigurableListableBeanFactory getBeanFactory() { - return null; - } - - @Override - public StandardEnvironment getEnvironment() { - return environment; - } - - @Override - public ResourceLoader getResourceLoader() { - return null; - } - - @Override - public ClassLoader getClassLoader() { - return null; - } - } -} diff --git a/modules/protocols/protocol-gb32960/pom.xml b/modules/protocols/protocol-gb32960/pom.xml deleted file mode 100644 index 9d4aa350..00000000 --- a/modules/protocols/protocol-gb32960/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - protocol-gb32960 - protocol-gb32960 - GB/T 32960 新能源车国标协议接入(Netty Server,默认 TCP 9000)。 - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - ingest-codec-common - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-configuration-processor - true - - - io.netty - netty-all - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.springframework.boot - spring-boot-test - test - - - diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960PlatformAuthorizer.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960PlatformAuthorizer.java deleted file mode 100644 index 43d880db..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960PlatformAuthorizer.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.auth; - -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - -/** - * 平台登入(0x05)账号白名单校验器。 - * - *

与 {@link Gb32960VinAuthorizer} 区别: - *

    - *
  • 车辆登入靠 VIN 白名单(无密码) - *
  • 平台登入有 12B 用户名 + 20B 密码 + 可选 IP 白名单 - *
- * - *

平台账号不仅用于 0x05 鉴权,也会绑定到 channel attribute,后续 0x02/0x03 上报会用它选择 - * vendor profile。账号配错时,可能表现为 0x30+ 厂商字段全部落 Raw。 - * - *

匹配规则: - *

    - *
  1. 配置列表为空 → 返回 {@link Result#ALLOW_NO_POLICY}(兼容老行为,放行但标记未鉴权) - *
  2. 用户名不存在 → {@link Result#DENY_UNKNOWN_USER} - *
  3. 密码不匹配 → {@link Result#DENY_BAD_PASSWORD} - *
  4. IP 白名单非空且 peer IP 不在其中 → {@link Result#DENY_IP_NOT_ALLOWED} - *
  5. 全部通过 → {@link Result#ALLOW} - *
- */ -public final class Gb32960PlatformAuthorizer { - - private static final Logger log = LoggerFactory.getLogger(Gb32960PlatformAuthorizer.class); - - private final Map byUsername; - - public Gb32960PlatformAuthorizer(List platforms) { - Map map = new HashMap<>(); - for (Gb32960Properties.Auth.Platform p : platforms) { - if (p.getUsername() == null || p.getUsername().isBlank()) continue; - map.put(p.getUsername().trim(), p); - } - this.byUsername = Collections.unmodifiableMap(map); - log.info("[gb32960] platform authorizer loaded {} platform(s)", this.byUsername.size()); - } - - /** - * 鉴权一次平台登入请求。 - * - * @param username 终端发来的用户名 - * @param password 终端发来的密码 - * @param peerIp 连接对端 IP(纯 IPv4/IPv6,不含端口) - */ - public Result authenticate(String username, String password, String peerIp) { - if (byUsername.isEmpty()) return Result.ALLOW_NO_POLICY; - Gb32960Properties.Auth.Platform entry = byUsername.get(username == null ? "" : username.trim()); - if (entry == null) return Result.DENY_UNKNOWN_USER; - if (!Objects.equals(entry.getPassword(), password)) return Result.DENY_BAD_PASSWORD; - Set allowed = entry.getAllowedIps() == null - ? Set.of() - : entry.getAllowedIps().stream() - .filter(Objects::nonNull) - .flatMap(value -> Arrays.stream(value.split(","))) - .map(String::trim) - .filter(ip -> !ip.isBlank()) - .collect(java.util.stream.Collectors.toSet()); - if (!allowed.isEmpty() && (peerIp == null || !allowed.contains(peerIp))) { - return Result.DENY_IP_NOT_ALLOWED; - } - return Result.ALLOW; - } - - public int size() { - return byUsername.size(); - } - - public boolean isEnforcing() { - return !byUsername.isEmpty(); - } - - /** 鉴权结果枚举,调用方根据结果选择应答标志和日志级别。 */ - public enum Result { - /** 匹配成功。 */ - ALLOW(true), - /** 配置列表为空,放行但标记未鉴权。 */ - ALLOW_NO_POLICY(true), - /** 用户名不存在。 */ - DENY_UNKNOWN_USER(false), - /** 密码错误。 */ - DENY_BAD_PASSWORD(false), - /** IP 白名单拒绝。 */ - DENY_IP_NOT_ALLOWED(false); - - private final boolean accepted; - Result(boolean accepted) { this.accepted = accepted; } - public boolean accepted() { return accepted; } - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960VinAuthorizer.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960VinAuthorizer.java deleted file mode 100644 index 5c26b817..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/auth/Gb32960VinAuthorizer.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.auth; - -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -/** - * GB/T 32960 VIN 白名单校验器。 - * - *

线程安全:内部持有 {@code AtomicReference},支持运行时热替换白名单 - * (未来可扩展为从 Nacos / 数据库定时刷新)。 - * - *

该校验只决定是否允许设备继续进入接收链路,不负责车辆主数据注册。生产环境若打开白名单, - * 需要确保新增车辆先同步到这里,否则终端会收到 VIN_NOT_EXIST 应答并断开。 - */ -public final class Gb32960VinAuthorizer { - - private static final Logger log = LoggerFactory.getLogger(Gb32960VinAuthorizer.class); - - private final boolean enabled; - private final boolean caseSensitive; - private final AtomicReference> whitelist; - - public Gb32960VinAuthorizer(Gb32960Properties.Auth cfg) { - this.enabled = cfg.isEnabled(); - this.caseSensitive = cfg.isCaseSensitive(); - this.whitelist = new AtomicReference<>(cfg.normalizedWhitelist()); - log.info("[gb32960] VIN authorizer enabled={} caseSensitive={} whitelistSize={}", - enabled, caseSensitive, this.whitelist.get().size()); - } - - /** - * 鉴权。 - * - * @return true 通过;false 拒绝(调用方应发送 0x04 VIN_NOT_EXIST 应答并关闭连接) - */ - public boolean isAllowed(String vin) { - if (!enabled) return true; - if (vin == null || vin.isBlank()) return false; - // 默认大小写不敏感,以容忍部分平台把 VIN 小写转发;规范 VIN 本身仍应为大写。 - String key = caseSensitive ? vin.trim() : vin.trim().toUpperCase(); - return whitelist.get().contains(key); - } - - public boolean isEnforcing() { - return enabled; - } - - public int whitelistSize() { - return whitelist.get().size(); - } - - /** 运行时热更新。接入 Nacos/DB 监听后调用。 */ - public void replaceWhitelist(Set newVins) { - Set norm = Collections.newSetFromMap(new java.util.concurrent.ConcurrentHashMap<>(newVins.size())); - for (String v : newVins) { - if (v == null || v.isBlank()) continue; - norm.add(caseSensitive ? v.trim() : v.trim().toUpperCase()); - } - this.whitelist.set(norm); - log.info("[gb32960] VIN whitelist replaced size={}", norm.size()); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java deleted file mode 100644 index b9809441..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParser.java +++ /dev/null @@ -1,239 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.api.spi.DecodeException; -import com.lingniu.ingest.protocol.gb32960.codec.profile.Gb32960ProfileRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionSelector; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlockType; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 实时/补发上报数据单元循环解析器。 - * - *

数据单元布局(表 7):{@code timestamp(6B) | (typeFlag(1B) + infoBody)* | [0xFF 签名信息]}。 - * 其中时间戳由 {@link Gb32960MessageDecoder} 负责剥离,本 parser 只处理信息体循环。 - * - *

支持的信息类型标志(表 9): - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
20162025含义
0x010x01整车数据
0x020x02驱动电机数据
0x030x03燃料电池发动机及车载氢系统数据
0x040x04发动机数据
0x050x05车辆位置数据(2025 版增加坐标系字段)
0x06 极值0x06 报警 
0x07 报警0x07 动力蓄电池最小并联单元电压 
0x08 储能电压0x08 动力蓄电池温度 
0x09 储能温度 
0x30燃料电池电堆
0x31超级电容器
0x32超级电容器极值
0xFF签名数据开始标识(2025 版新增)
- * - *

处理策略: - *

    - *
  • 遇到 {@code 0xFF}(2025 版签名起始标识):停止循环,剩余字节作为签名原始字节返回 - *
  • 遇到未注册的类型:作为单个 {@link InfoBlock.Raw} 包装剩余字节并终止循环,避免丢失整帧 - *
- */ -public final class Gb32960BodyParser { - - private static final Logger log = LoggerFactory.getLogger(Gb32960BodyParser.class); - - /** 2025 版实时上报尾部签名标识(表 9)。 */ - private static final int SIGNATURE_MARKER = 0xFF; - - private final Gb32960ProfileRegistry profileRegistry; - private final VendorExtensionSelector selector; - - /** - * 单块解析异常时是否兜底为 {@link InfoBlock.Raw} 后继续。由 - * {@link com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties.Parse#isLenientBlockFailure()} - * 注入;默认 true。实际行为在 - * {@link #parse(Gb32960ParserContext, ByteBuffer)} 主循环里实现。 - */ - private boolean lenientBlockFailure = true; - - public void setLenientBlockFailure(boolean lenientBlockFailure) { - this.lenientBlockFailure = lenientBlockFailure; - } - - /** - * 单 registry 构造(向后兼容旧测试)。等价于 default profile + 无 vendor 扩展。 - */ - public Gb32960BodyParser(InfoBlockParserRegistry registry) { - this.profileRegistry = new Gb32960ProfileRegistry( - new java.util.ArrayList<>(), // ignored, defaultRegistry overridden via wrapper below - new com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionCatalog(java.util.Map.of()), - java.util.Set.of()) { - @Override public InfoBlockParserRegistry forProfile(String name) { return registry; } - @Override public InfoBlockParserRegistry defaultRegistry() { return registry; } - }; - this.selector = VendorExtensionSelector.NONE; - } - - /** - * 标准构造:profile 注册表 + vendor 选择器。运行期由 - * {@link Gb32960MessageDecoder} 调用 {@link #parse(Gb32960ParserContext, ByteBuffer)} - * 携带上下文,按 selector 路由到具体 profile 的 registry。 - */ - public Gb32960BodyParser(Gb32960ProfileRegistry profileRegistry, VendorExtensionSelector selector) { - this.profileRegistry = profileRegistry; - this.selector = selector; - } - - /** 旧入口:无上下文 → 走默认 profile。保留用于不需要 vendor 路由的单测。 */ - public Gb32960MessageDecoder.BodyParseResult parse(ProtocolVersion version, ByteBuffer body) { - return parse(Gb32960ParserContext.minimal(version), body); - } - - /** 主入口:根据上下文按 selector 选 vendor profile,然后照旧解析循环。 */ - public Gb32960MessageDecoder.BodyParseResult parse(Gb32960ParserContext ctx, ByteBuffer body) { - ProtocolVersion version = ctx.version(); - // selector 只决定“标准 parser 集合上是否叠加某个 vendor 扩展集合”;标准 typeCode 始终可解析。 - InfoBlockParserRegistry registry = profileRegistry.forProfile(selector.select(ctx)); - List blocks = new ArrayList<>(8); - byte[] signature = null; - // 捕获 info-block 区间的起止位置,用于未知块出现时打 dump 帮助手动定位漂移点。 - // body 由 Gb32960MessageDecoder 通过 ByteBuffer.wrap(frame, bodyStart+6, dataLength-6) 构造, - // arrayOffset()=0,position() = bodyStart+6,limit() = bodyStart + dataLength。 - final int infoBlocksStart = body.position(); - final int infoBlocksEnd = body.limit(); - - while (body.hasRemaining()) { - int typeStartPos = body.position(); - int typeCode = body.get() & 0xFF; - - if (typeCode == SIGNATURE_MARKER && version == ProtocolVersion.V2025) { - // 剩余全部字节即为签名内容(表 8) - signature = new byte[body.remaining()]; - body.get(signature); - break; - } - - InfoBlockParser parser = registry.find(version, typeCode); - if (parser == null) { - // lenient:未知类型无法安全跳过,吞剩余字节作为 Raw 后终止。 - // 真实 typeCode 透传到 Raw 字段,便于在日志/JSON 里直接看到漂移落点。 - // 如果该未知块实际是 TLV,应优先新增专用 TLV parser,让主循环可以继续解析后续块。 - int remaining = body.remaining(); - byte[] rest = new byte[remaining]; - body.get(rest); - log.warn("[gb32960] unknown info block typeCode=0x{} version={} bodyPos={} parsedBlocks={} remaining={} preview={}", - Integer.toHexString(typeCode), version, typeStartPos, - blocks.size(), remaining, hexPreview(rest, 16)); - // 一次性把整段 info-block 区间的原始字节打出来,并在 typeStartPos 处插入标记, - // 帮助人工对比规范字段长度、定位是哪个早期块对齐错。 - if (body.hasArray()) { - log.warn("[gb32960] body dump (info-block region) infoBlocksStart={} infoBlocksEnd={} typeStartPos={} totalLen={} alreadyParsed={}\n{}", - infoBlocksStart, infoBlocksEnd, typeStartPos, - infoBlocksEnd - infoBlocksStart, blocks.size(), - hexDumpWithMarker(body.array(), infoBlocksStart, infoBlocksEnd, typeStartPos)); - } - blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, rest)); - break; - } - - int fixedLen = parser.fixedLength(); - int posBefore = body.position(); - - // 预检:固定长度块在剩余字节不足时,lenient 模式兜 Raw 后 break;严格模式保持旧行为抛异常。 - if (fixedLen >= 0 && body.remaining() < fixedLen) { - if (!lenientBlockFailure) { - throw new DecodeException( - "info block 0x" + Integer.toHexString(typeCode) - + " needs " + fixedLen + " bytes but got " + body.remaining()); - } - // 固定长度块尾部缺字节时不猜测字段;保留剩余 bytes,方便后续用 RAW 回放复盘。 - int remaining = body.remaining(); - byte[] tail = new byte[remaining]; - body.get(tail); - log.warn("[gb32960] truncated block typeCode=0x{} declaredFixedLen={} remaining={} — wrapping tail as Raw", - Integer.toHexString(typeCode), fixedLen, remaining); - blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail)); - break; - } - - InfoBlock block; - try { - block = parser.parse(body); - } catch (DecodeException | java.nio.BufferUnderflowException | IndexOutOfBoundsException e) { - if (!lenientBlockFailure) { - if (e instanceof DecodeException de) throw de; - throw new DecodeException( - "parser " + parser.getClass().getSimpleName() + " failed: " + e.getMessage(), e); - } - // 回滚到 parser.parse 入口,按块类型分支处理。 - body.position(posBefore); - if (fixedLen >= 0) { - // 固定长度块:按 fixedLen 截取 → 兜 Raw → continue 循环 - byte[] corrupt = new byte[fixedLen]; - body.get(corrupt); - log.warn("[gb32960] block parse failed typeCode=0x{} parser={} fixedLen={} — isolated as Raw, continuing", - Integer.toHexString(typeCode), parser.getClass().getSimpleName(), fixedLen, e); - blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, corrupt)); - continue; - } - // 变长块:无法安全找下一块边界 → 剩余整段兜 Raw + break - int remaining = body.remaining(); - byte[] tail = new byte[remaining]; - body.get(tail); - log.warn("[gb32960] variable-length block parse failed typeCode=0x{} parser={} remaining={} — wrapping remainder as Raw, stopping loop", - Integer.toHexString(typeCode), parser.getClass().getSimpleName(), remaining, e); - blocks.add(new InfoBlock.Raw(typeCode, InfoBlockType.RAW, tail)); - break; - } - - int consumed = body.position() - posBefore; - if (fixedLen >= 0 && consumed != fixedLen) { - // parser 契约违反(声明 fixedLen=X 但实际读了 Y),保持抛异常以暴露 parser bug, - // 不走 lenient 兜底,避免静默误解析长期误判。 - throw new DecodeException( - "parser " + parser.getClass().getSimpleName() - + " consumed " + consumed - + " bytes but declared " + fixedLen); - } - if (log.isDebugEnabled()) { - log.debug("[gb32960] block typeCode=0x{} parser={} pos {}→{} consumed={} declaredFixed={}", - Integer.toHexString(typeCode), parser.getClass().getSimpleName(), - typeStartPos, body.position(), consumed, fixedLen); - } - blocks.add(block); - } - - return new Gb32960MessageDecoder.BodyParseResult(blocks, signature); - } - - private static String hexPreview(byte[] bytes, int max) { - int n = Math.min(bytes.length, max); - StringBuilder sb = new StringBuilder(n * 2); - for (int i = 0; i < n; i++) sb.append(String.format("%02x", bytes[i])); - if (bytes.length > max) sb.append(".."); - return sb.toString(); - } - - /** - * 16 字节/行的 hex dump,遇到 typeStartPos 时在该字节后插入 {@code |<< - *
  • 0x01 车辆登入(表 6) - *
  • 0x04 车辆登出(表 28) - *
  • 0x05 平台登入(表 29) - *
  • 0x06 平台登出(表 30) - *
  • 0x07 心跳(空) - *
  • 0x08 终端校时(空) - *
  • 0x09 激活(表 B.3) - *
  • 0x0A 激活应答(表 B.4) - *
  • 0x0B 密钥交换(表 31) - * - * - *

    其它命令(查询/设置/控制/自定义)保留为 {@link CommandBody.RawCommand}。 - */ -public final class Gb32960CommandParser { - - private static final ZoneId ZONE_GMT8 = ZoneId.of("Asia/Shanghai"); - - public CommandBody parse(CommandType command, ByteBuffer buf) { - return switch (command) { - case VEHICLE_LOGIN -> parseVehicleLogin(buf); - case VEHICLE_LOGOUT -> parseVehicleLogout(buf); - case PLATFORM_LOGIN -> parsePlatformLogin(buf); - case PLATFORM_LOGOUT -> parsePlatformLogout(buf); - case HEARTBEAT -> new CommandBody.Heartbeat(); - case TIME_CALIBRATION -> new CommandBody.TimeCalibration(); - case ACTIVATION -> parseActivation(buf); - case ACTIVATION_RESPONSE -> parseActivationResponse(buf); - case KEY_EXCHANGE -> parseKeyExchange(buf); - case QUERY -> parseQueryParams(buf); - case SET_PARAMS -> parseSetParams(buf); - case TERMINAL_CONTROL -> parseTerminalControl(buf); - default -> captureRaw(command, buf); - }; - } - - /** 表 6:车辆登入。 */ - private CommandBody.VehicleLogin parseVehicleLogin(ByteBuffer buf) { - Instant time = readTime(buf); - int serialNo = buf.getShort() & 0xFFFF; - String iccid = readAscii(buf, 20); - // 2025 版新增:电池管理系统数 n + 各系统包数 + 编码列表 - int batterySystemCount = buf.hasRemaining() ? (buf.get() & 0xFF) : 0; - List packCounts = new ArrayList<>(); - int totalPacks = 0; - for (int i = 0; i < batterySystemCount && buf.hasRemaining(); i++) { - int c = buf.get() & 0xFF; - packCounts.add(c); - totalPacks += c; - } - List codes = new ArrayList<>(totalPacks); - for (int i = 0; i < totalPacks && buf.remaining() >= 24; i++) { - codes.add(readAscii(buf, 24)); - } - return new CommandBody.VehicleLogin(time, serialNo, iccid, batterySystemCount, packCounts, codes); - } - - private CommandBody.VehicleLogout parseVehicleLogout(ByteBuffer buf) { - return new CommandBody.VehicleLogout(readTime(buf), buf.getShort() & 0xFFFF); - } - - private CommandBody.PlatformLogin parsePlatformLogin(ByteBuffer buf) { - Instant time = readTime(buf); - int serial = buf.getShort() & 0xFFFF; - String user = readAscii(buf, 12); - // 标准密码字段为 20B,但现场存在扩展到 32B 的平台登入包;这里按剩余长度-1 读取, - // 最后一字节仍保留给加密规则,兼容两种形态。 - int passwordLength = Math.max(0, buf.remaining() - 1); - String pwd = readAscii(buf, passwordLength); - EncryptType enc = EncryptType.of(buf.get() & 0xFF); - return new CommandBody.PlatformLogin(time, serial, user, pwd, enc); - } - - private CommandBody.PlatformLogout parsePlatformLogout(ByteBuffer buf) { - return new CommandBody.PlatformLogout(readTime(buf), buf.getShort() & 0xFFFF); - } - - private CommandBody.Activation parseActivation(ByteBuffer buf) { - Instant time = readTime(buf); - String chipId = readAscii(buf, 16); - int keyLen = buf.getShort() & 0xFFFF; - byte[] pubKey = new byte[Math.min(keyLen, buf.remaining())]; - buf.get(pubKey); - String vin = readAscii(buf, 17); - byte[] signature = new byte[buf.remaining()]; - buf.get(signature); - return new CommandBody.Activation(time, chipId, pubKey, vin, signature); - } - - private CommandBody.ActivationResponse parseActivationResponse(ByteBuffer buf) { - return new CommandBody.ActivationResponse(buf.get() & 0xFF, buf.get() & 0xFF); - } - - private CommandBody.KeyExchange parseKeyExchange(ByteBuffer buf) { - EncryptType keyType = EncryptType.of(buf.get() & 0xFF); - int keyLen = buf.getShort() & 0xFFFF; - byte[] key = new byte[Math.min(keyLen, buf.remaining() - 12)]; - buf.get(key); - Instant activateAt = readTime(buf); - Instant expireAt = readTime(buf); - return new CommandBody.KeyExchange(keyType, key, activateAt, expireAt); - } - - private CommandBody.RawCommand captureRaw(CommandType command, ByteBuffer buf) { - byte[] bytes = new byte[buf.remaining()]; - buf.get(bytes); - return new CommandBody.RawCommand(command.code(), bytes); - } - - // ========================================================================= - // 表 B.5-B.13 下行命令 - // ========================================================================= - - /** - * 参数查询 0x80(表 B.5):{@code time(6) + count(1) + paramId(count)}。 - * 如果 count > 0,则是查询命令;如果 count == 0 可能是终端应答(表 B.6), - * 应答带参数项列表(表 B.7),paramValue 长度根据 paramId 查表 B.8 决定。 - */ - private CommandBody parseQueryParams(ByteBuffer buf) { - Instant time = readTime(buf); - int count = buf.get() & 0xFF; - - // 判定是查询命令还是应答:命令方向下 count 后紧跟 paramId 列表(每项 1 字节); - // 应答方向下 count 后是完整的 paramId + paramValue 列表(表 B.7)。 - // 通过剩余字节数判断:命令形态 remaining == count,应答形态 remaining > count - if (buf.remaining() == count) { - List ids = new ArrayList<>(count); - for (int i = 0; i < count; i++) ids.add(buf.get() & 0xFF); - return new CommandBody.QueryParams(time, ids); - } - // 作为应答处理 - Map params = readParamItems(buf, count); - return new CommandBody.QueryParamsResponse(time, params); - } - - /** 参数设置 0x81(表 B.9):{@code time(6) + count(1) + paramItems}。 */ - private CommandBody.SetParams parseSetParams(ByteBuffer buf) { - Instant time = readTime(buf); - int count = buf.get() & 0xFF; - Map params = readParamItems(buf, count); - return new CommandBody.SetParams(time, params); - } - - /** 读取 N 个参数项(表 B.7):{@code paramId(1) + paramValue(按表 B.8 解释)}。 */ - private Map readParamItems(ByteBuffer buf, int count) { - Map out = new HashMap<>(count); - for (int i = 0; i < count && buf.hasRemaining(); i++) { - int paramId = buf.get() & 0xFF; - int len = paramValueLength(paramId, buf); - if (len < 0 || len > buf.remaining()) { - // 长度未知或越界:吞剩余字节作为该参数值后终止 - byte[] rest = new byte[buf.remaining()]; - buf.get(rest); - out.put(paramId, rest); - break; - } - byte[] value = new byte[len]; - buf.get(value); - out.put(paramId, value); - } - return out; - } - - /** - * 按表 B.8 返回参数值长度。 - * - *

    对于变长参数(0x05 域名 / 0x0E 公共平台域名),前一个参数(0x04/0x0D)给出长度; - * 这里简化处理:变长参数使用 {@code buf.get() & 0xFF + 1} 预读 + 取值。 - */ - private int paramValueLength(int paramId, ByteBuffer buf) { - return switch (paramId) { - case 0x01 -> 2; // 本地存储时间周期 WORD - case 0x02 -> 1; // 正常信息上报时间周期 - case 0x03 -> 1; // 报警信息上报时间周期 - case 0x04 -> 1; // 平台域名长度 M - case 0x05 -> { - // 平台域名 M 字节,M 由前一个参数的值给出。 - // 安全起见这里一次吞掉剩余字节(实际场景参数 0x04 和 0x05 通常一起下发) - yield buf.remaining(); - } - case 0x06 -> 2; // 平台端口 WORD - case 0x07, 0x08 -> 5; // 硬件/固件版本 5 字节 - case 0x09 -> 1; // 心跳发送周期 - case 0x0A -> 2; // 终端应答超时时间 - case 0x0B -> 2; // 平台应答超时时间 - case 0x0C -> 1; // 登入失败后下次登入间隔 - case 0x0D -> 1; // 公共平台域名长度 N - case 0x0E -> buf.remaining(); // 公共平台域名 - case 0x0F -> 2; // 公共平台端口 - case 0x10 -> 1; // 是否抽样监测中 - default -> -1; // 未知参数:无法决定长度 - }; - } - - /** 车载终端控制 0x82(表 B.10)。 */ - private CommandBody.TerminalControl parseTerminalControl(ByteBuffer buf) { - Instant time = readTime(buf); - int cmdId = buf.get() & 0xFF; - CommandBody.ControlCommand commandId = CommandBody.ControlCommand.of(cmdId); - CommandBody.ControlPayload payload = switch (commandId) { - case REMOTE_UPGRADE -> parseRemoteUpgrade(buf); - case ALARM_COMMAND -> parseAlarmCommand(buf); - case SHUTDOWN, RESET, FACTORY_RESET, DISCONNECT, SAMPLING_LINK_ON -> new CommandBody.ControlPayload.None(); - case UNKNOWN -> captureRawPayload(buf); - }; - return new CommandBody.TerminalControl(time, commandId, payload); - } - - /** - * 表 B.12 远程升级参数。字段之间用半角分号 {@code ;} 分隔。 - * - *

    字段顺序:URL;apn;dialUser;dialPwd;serverAddress;serverPort;manufacturerCode;hwVer;fwVer;timeoutMin - */ - private CommandBody.ControlPayload.RemoteUpgrade parseRemoteUpgrade(ByteBuffer buf) { - byte[] all = new byte[buf.remaining()]; - buf.get(all); - String s = new String(all, Charset.forName("GBK")); - String[] parts = s.split(";", -1); - String url = parts.length > 0 ? parts[0] : ""; - String apn = parts.length > 1 ? parts[1] : ""; - String user = parts.length > 2 ? parts[2] : ""; - String pwd = parts.length > 3 ? parts[3] : ""; - byte[] addr = parts.length > 4 ? parts[4].getBytes(StandardCharsets.US_ASCII) : new byte[0]; - int port = parts.length > 5 ? parseIntSafe(parts[5]) : 0; - String manu = parts.length > 6 ? parts[6] : ""; - String hw = parts.length > 7 ? parts[7] : ""; - String fw = parts.length > 8 ? parts[8] : ""; - int timeoutMin = parts.length > 9 ? parseIntSafe(parts[9]) : 0; - return new CommandBody.ControlPayload.RemoteUpgrade( - apn, user, pwd, addr, port, manu, hw, fw, url, timeoutMin); - } - - /** 表 B.13 报警/预警命令。 */ - private CommandBody.ControlPayload.AlarmCommand parseAlarmCommand(ByteBuffer buf) { - int warningLevel = buf.hasRemaining() ? (buf.get() & 0xFF) : 0; - byte[] info = new byte[buf.remaining()]; - buf.get(info); - return new CommandBody.ControlPayload.AlarmCommand(warningLevel, info); - } - - private CommandBody.ControlPayload.RawPayload captureRawPayload(ByteBuffer buf) { - byte[] bytes = new byte[buf.remaining()]; - buf.get(bytes); - return new CommandBody.ControlPayload.RawPayload(bytes); - } - - private static int parseIntSafe(String s) { - try { return Integer.parseInt(s.trim()); } catch (Exception e) { return 0; } - } - - /** 表 5:时间定义 YY MM DD HH MM SS,GMT+8。 */ - private Instant readTime(ByteBuffer buf) { - if (buf.remaining() < 6) { - throw new DecodeException("time field requires 6 bytes, got " + buf.remaining()); - } - int year = 2000 + (buf.get() & 0xFF); - int month = buf.get() & 0xFF; - int day = buf.get() & 0xFF; - int hour = buf.get() & 0xFF; - int minute = buf.get() & 0xFF; - int second = buf.get() & 0xFF; - try { - return LocalDateTime.of(year, month, day, hour, minute, second).atZone(ZONE_GMT8).toInstant(); - } catch (Exception e) { - throw new DecodeException("invalid time: " + year + "-" + month + "-" + day - + " " + hour + ":" + minute + ":" + second, e); - } - } - - private String readAscii(ByteBuffer buf, int len) { - int safeLen = Math.min(len, buf.remaining()); - byte[] b = new byte[safeLen]; - buf.get(b); - return new String(b, StandardCharsets.US_ASCII).trim(); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoder.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoder.java deleted file mode 100644 index 62fca57b..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoder.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import com.lingniu.ingest.codec.BccChecksum; - -import java.util.List; - -/** - * Netty 层的帧分帧器:从字节流里提取一条完整的 32960 帧。 - * - *

    支持两种起始符:{@code 0x23 0x23 (##)} 2016 版,{@code 0x24 0x24 ($$)} 2025 版。 - * 只负责拆包粘包,不做业务解析。具体字段解析交给 {@link Gb32960MessageDecoder}。 - * - *

    开启 DEBUG 日志可以看到每次读到的字节、每条产出的完整帧以及因长度/起始符不匹配被丢弃的字节数。 - */ -public class Gb32960FrameDecoder extends ByteToMessageDecoder { - - private static final Logger log = LoggerFactory.getLogger(Gb32960FrameDecoder.class); - - private static final int HEADER_LEN = 24; // 起始(2) + cmd(1) + rsp(1) + vin(17) + enc(1) + len(2) - private static final int MAX_FRAME = 8 * 1024; - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - if (log.isDebugEnabled() && in.readableBytes() > 0) { - int peek = Math.min(in.readableBytes(), 64); - log.debug("[gb32960] frame-decoder rx bytes={} head={}", - in.readableBytes(), - ByteBufUtil.hexDump(in, in.readerIndex(), peek)); - } - while (in.readableBytes() >= HEADER_LEN) { - int startIdx = findStart(in); - if (startIdx < 0 || startIdx > in.writerIndex() - HEADER_LEN) { - int drop = Math.max(0, in.readableBytes() - 1); - if (drop > 0) { - log.debug("[gb32960] frame-decoder skip {} bytes (no start symbol)", drop); - in.skipBytes(drop); - } - return; - } - if (startIdx != in.readerIndex()) { - int drop = startIdx - in.readerIndex(); - log.debug("[gb32960] frame-decoder skip {} leading bytes before start", drop); - in.skipBytes(drop); - } - int dataLen = in.getUnsignedShort(in.readerIndex() + 22); - int frameLen = frameLength(in, in.readerIndex(), dataLen); - if (frameLen > MAX_FRAME) { - log.warn("[gb32960] frame-decoder oversized frame len={} peer={}, skip start", - frameLen, ctx.channel().remoteAddress()); - in.skipBytes(2); - continue; - } - if (in.readableBytes() < frameLen) return; - - byte[] frame = new byte[frameLen]; - in.readBytes(frame); - if (log.isDebugEnabled()) { - log.debug("[gb32960] frame-decoder emit len={} start={} peer={}", - frameLen, - String.format("%02x%02x", frame[0], frame[1]), - ctx.channel().remoteAddress()); - } - out.add(frame); - } - } - - private static int frameLength(ByteBuf in, int readerIndex, int dataLen) { - int standardLen = HEADER_LEN + dataLen + 1; - int command = in.getUnsignedByte(readerIndex + 2); - if (command != 0x05 || dataLen != 41) { - return standardLen; - } - // 现场平台登入包存在"声明 41B、实际 53B"的兼容形态;只有扩展长度 BCC 成立时才按 53B 分帧, - // 否则回退标准长度,避免误吞下一条帧。 - int extendedLen = HEADER_LEN + 53 + 1; - if (in.readableBytes() < extendedLen) { - return standardLen; - } - byte[] extended = new byte[extendedLen]; - in.getBytes(readerIndex, extended); - if (BccChecksum.verify(extended, 2, extended.length - 3, extended[extended.length - 1])) { - return extendedLen; - } - return standardLen; - } - - /** 查找下一个合法起始位置:两字节连续 0x23 0x23 或 0x24 0x24。 */ - private static int findStart(ByteBuf in) { - int limit = in.writerIndex() - 1; - for (int i = in.readerIndex(); i < limit; i++) { - byte b = in.getByte(i); - if ((b == 0x23 || b == 0x24) && in.getByte(i + 1) == b) { - return i; - } - } - return -1; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameEncoder.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameEncoder.java deleted file mode 100644 index 64eaae75..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameEncoder.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.EncryptType; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; - -/** - * GB/T 32960.3 下行帧编码器。主要用于应答场景:平台收到终端上行命令后发送应答。 - * - *

    应答规则(§6.3.2):服务端发送应答时,变更应答标志、保留报文时间、删除其余报文内容、 - * 重新计算校验位。 - * - *

    该类只做协议编码,不写 Channel。是否立即关闭连接由 {@code Gb32960AckService} - * 根据鉴权结果决定。 - */ -public final class Gb32960FrameEncoder { - - private static final ZoneId ZONE_GMT8 = ZoneId.of("Asia/Shanghai"); - - private Gb32960FrameEncoder() {} - - /** - * 构造简易应答帧:复用原命令的 command/vin/encryptType,修改应答标志, - * 可选携带数据单元(通常只带 6 字节采集时间)。 - * - * @param version 协议版本(决定起始符) - * @param command 命令标识(与原命令相同) - * @param responseFlag 应答标志:{@link ResponseFlag#SUCCESS}/{@link ResponseFlag#VIN_NOT_EXIST} 等 - * @param vin 17 字节 VIN - * @param collectTime 数据采集时间(保留原帧时间,若无则传 {@code Instant.now()}) - * @param extraBody 附加数据(可为 null) - */ - public static byte[] buildResponse(ProtocolVersion version, - CommandType command, - ResponseFlag responseFlag, - String vin, - Instant collectTime, - byte[] extraBody) { - return buildResponse(version, command, responseFlag, vinPadded(vin), collectTime, extraBody); - } - - /** - * 同 {@link #buildResponse(ProtocolVersion, CommandType, ResponseFlag, String, Instant, byte[])}, - * 但直接接收 17 字节原始 VIN。用于回执时原样回显请求帧的 VIN 字节,避免 - * 平台登入这类 VIN 全 0 的场景被我们用空格重写后导致对端判定 VIN 不匹配。 - */ - public static byte[] buildResponse(ProtocolVersion version, - CommandType command, - ResponseFlag responseFlag, - byte[] vin17, - Instant collectTime, - byte[] extraBody) { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - // 数据采集时间 6 字节(表 5) - if (collectTime != null) { - LocalDateTime ldt = LocalDateTime.ofInstant(collectTime, ZONE_GMT8); - body.write(ldt.getYear() - 2000); - body.write(ldt.getMonthValue()); - body.write(ldt.getDayOfMonth()); - body.write(ldt.getHour()); - body.write(ldt.getMinute()); - body.write(ldt.getSecond()); - } - if (extraBody != null && extraBody.length > 0) { - body.write(extraBody, 0, extraBody.length); - } - - byte[] bodyBytes = body.toByteArray(); - int dataLength = bodyBytes.length; - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - // 起始符 - if (version == ProtocolVersion.V2025) { - out.write(0x24); out.write(0x24); - } else { - out.write(0x23); out.write(0x23); - } - out.write(command.code()); - out.write(responseFlag.code()); - // VIN(17 字节,由调用方给出;平台登入等场景直接回显请求的原始字节) - byte[] vinBytes = vin17 != null && vin17.length == 17 ? vin17 : vinPadded(null); - out.write(vinBytes, 0, 17); - // 加密方式:应答不加密 - out.write(EncryptType.UNENCRYPTED.code()); - // 数据单元长度 WORD 大端 - out.write((dataLength >> 8) & 0xFF); - out.write(dataLength & 0xFF); - out.write(bodyBytes, 0, bodyBytes.length); - // BCC:从 cmd(offset 2) 到数据单元结尾 - byte[] almost = out.toByteArray(); - byte bcc = BccChecksum.compute(almost, 2, almost.length - 2); - out.write(bcc & 0xFF); - return out.toByteArray(); - } - - private static byte[] vinPadded(String vin) { - byte[] out = new byte[17]; - // 填充空格 - java.util.Arrays.fill(out, (byte) ' '); - if (vin == null) return out; - byte[] src = vin.getBytes(StandardCharsets.US_ASCII); - int len = Math.min(src.length, 17); - System.arraycopy(src, 0, out, 0, len); - return out; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java deleted file mode 100644 index ecc2884b..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java +++ /dev/null @@ -1,180 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.api.spi.DecodeException; -import com.lingniu.ingest.api.spi.FrameDecoder; -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.protocol.gb32960.model.CommandBody; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.EncryptType; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Header; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.Collections; -import java.util.List; - -/** - * 完整帧 → {@link Gb32960Message} 的解码器。 - * - *

    支持 GB/T 32960.3 两个版本: - *

      - *
    • {@code 0x23 0x23 (##)} → 2016 版({@link ProtocolVersion#V2016}) - *
    • {@code 0x24 0x24 ($$)} → 2025 版({@link ProtocolVersion#V2025}) - *
    - * - *

    完整数据包结构(表 2 / 表 B.1): - *

    - *   offset 0:  起始符 (2B)
    - *   offset 2:  命令标识 (1B)
    - *   offset 3:  应答标志 (1B)
    - *   offset 4:  唯一识别码 VIN (17B ASCII)
    - *   offset 21: 数据单元加密方式 (1B)
    - *   offset 22: 数据单元长度 (2B WORD 大端)
    - *   offset 24: 数据单元 (dataLength 字节)
    - *   last byte: 校验码 (1B BCC)
    - * 
    - * - *

    BCC 校验范围:从命令单元的第一个字节开始(offset 2),同后一字节异或, - * 直到校验码前一字节为止。注意:当数据单元存在加密时,应先加密后校验,解析时先校验后解密。 - * - *

    上游 Netty 的 frame handler 已经保证 {@code buffer} 内是一条完整帧。 - */ -public final class Gb32960MessageDecoder implements FrameDecoder { - - /** 最小帧长:2(起始符)+1(cmd)+1(rsp)+17(vin)+1(enc)+2(len)+0(body)+1(bcc)。 */ - private static final int MIN_FRAME = 25; - private static final ZoneId ZONE_GMT8 = ZoneId.of("Asia/Shanghai"); - - private final Gb32960BodyParser bodyParser; - private final Gb32960CommandParser commandParser; - - public Gb32960MessageDecoder(Gb32960BodyParser bodyParser) { - this(bodyParser, new Gb32960CommandParser()); - } - - public Gb32960MessageDecoder(Gb32960BodyParser bodyParser, Gb32960CommandParser commandParser) { - this.bodyParser = bodyParser; - this.commandParser = commandParser; - } - - @Override - public Gb32960Message decode(ByteBuffer buffer) { - return decode(buffer, null); - } - - /** - * 带 platformAccount 提示的解码。Handler 在已经完成 0x05 PLATFORM_LOGIN 鉴权后通过 - * channel attribute 取到账号名再传进来;以便 BodyParser 内部的 vendor profile selector - * 路由。其他命令传 {@code null} 即可。 - */ - public Gb32960Message decode(ByteBuffer buffer, String platformAccount) { - int frameLen = buffer.remaining(); - if (frameLen < MIN_FRAME) { - throw new DecodeException("frame too short: " + frameLen); - } - - byte[] frame = new byte[frameLen]; - buffer.get(frame); - - ProtocolVersion version = detectVersion(frame); - - // BCC 校验:从 cmd(offset 2) 开始到 BCC 前一字节 - byte expectedBcc = frame[frame.length - 1]; - if (!BccChecksum.verify(frame, 2, frame.length - 3, expectedBcc)) { - throw new DecodeException("BCC mismatch"); - } - - int commandCode = frame[2] & 0xFF; - int responseCode = frame[3] & 0xFF; - String vin = new String(frame, 4, 17, StandardCharsets.US_ASCII).trim(); - int encryptCode = frame[21] & 0xFF; - int dataLength = ((frame[22] & 0xFF) << 8) | (frame[23] & 0xFF); - int actualDataLength = frame.length - 25; - - int bodyStart = 24; - if (bodyStart + dataLength + 1 != frame.length - && !isExtendedPlatformLogin(commandCode, dataLength, actualDataLength)) { - throw new DecodeException("declared body length " + dataLength - + " does not match frame remainder " + (frame.length - bodyStart - 1)); - } - - CommandType cmdType = CommandType.of(commandCode); - ResponseFlag rspFlag = ResponseFlag.of(responseCode); - EncryptType encType = EncryptType.of(encryptCode); - - // 实时 / 补发上报:解析信息体(和 2025 版尾部签名) - if (cmdType == CommandType.REALTIME_REPORT || cmdType == CommandType.RESEND_REPORT) { - if (dataLength < 6) throw new DecodeException("report body missing timestamp"); - Instant eventTime = parseTimestamp(frame, bodyStart); - ByteBuffer body = ByteBuffer.wrap(frame, bodyStart + 6, dataLength - 6); - Gb32960ParserContext ctx = new Gb32960ParserContext(version, vin, platformAccount); - BodyParseResult result = bodyParser.parse(ctx, body); - Gb32960Header header = new Gb32960Header( - version, cmdType, rspFlag, vin, encType, dataLength, eventTime); - return new Gb32960Message(header, result.blocks(), null, result.signature()); - } - - // 其他命令:解析 command body - // 车辆/平台登入登出 body 首 6B 为采集时间(表 5),预解析并写入 header.eventTime, - // 以便 handler 构造应答帧时按 §6.3.2 保留原报文时间。 - Instant eventTime = null; - if (hasLeadingTimestamp(cmdType) && dataLength >= 6) { - eventTime = parseTimestamp(frame, bodyStart); - } - Gb32960Header headerStub = new Gb32960Header( - version, cmdType, rspFlag, vin, encType, dataLength, eventTime); - int commandBodyLength = isExtendedPlatformLogin(commandCode, dataLength, actualDataLength) - ? actualDataLength - : dataLength; - ByteBuffer body = ByteBuffer.wrap(frame, bodyStart, commandBodyLength); - CommandBody cmdBody = commandParser.parse(cmdType, body); - return new Gb32960Message(headerStub, Collections.emptyList(), cmdBody, null); - } - - private static boolean isExtendedPlatformLogin(int commandCode, int declaredDataLength, int actualDataLength) { - // 与 FrameDecoder 的兼容规则保持一致:部分平台登录帧声明长度 41B,但真实账号/密码体为 53B。 - return commandCode == 0x05 && declaredDataLength == 41 && actualDataLength == 53; - } - - private static ProtocolVersion detectVersion(byte[] frame) { - if (frame[0] == 0x23 && frame[1] == 0x23) return ProtocolVersion.V2016; - if (frame[0] == 0x24 && frame[1] == 0x24) return ProtocolVersion.V2025; - throw new DecodeException("unknown start symbol: " - + String.format("%02x %02x", frame[0] & 0xFF, frame[1] & 0xFF)); - } - - private static boolean hasLeadingTimestamp(CommandType cmd) { - return cmd == CommandType.VEHICLE_LOGIN - || cmd == CommandType.VEHICLE_LOGOUT - || cmd == CommandType.PLATFORM_LOGIN - || cmd == CommandType.PLATFORM_LOGOUT; - } - - /** 时间定义(表 5):YY MM DD HH MM SS,各 1 字节,GMT+8。 */ - private static Instant parseTimestamp(byte[] frame, int offset) { - int year = 2000 + (frame[offset] & 0xFF); - int month = frame[offset + 1] & 0xFF; - int day = frame[offset + 2] & 0xFF; - int hour = frame[offset + 3] & 0xFF; - int minute = frame[offset + 4] & 0xFF; - int second = frame[offset + 5] & 0xFF; - try { - return LocalDateTime.of(year, month, day, hour, minute, second) - .atZone(ZONE_GMT8) - .toInstant(); - } catch (Exception e) { - throw new DecodeException("invalid timestamp " + year + "-" + month + "-" + day - + " " + hour + ":" + minute + ":" + second, e); - } - } - - /** 实时上报 body 解析结果:信息体列表 + 可选签名字节。 */ - public record BodyParseResult(List blocks, byte[] signature) {} -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960ParserContext.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960ParserContext.java deleted file mode 100644 index addcd7e2..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960ParserContext.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -/** - * Body parser 调用上下文:携带选 profile 时需要的三元组。 - * - *

    由 {@link Gb32960MessageDecoder} 在解析帧时构造(vin / version 来自帧字节, - * platformAccount 由 handler 通过 channel attribute 传入)。 - * - * @param version 协议版本(来自帧起始符) - * @param vin 17 字节 VIN(trim 后),平台登入 0x05 等帧 VIN 全 0 时为空串 - * @param platformAccount 平台登入用户名;非平台对接场景为 null - * - *

    广东燃料电池扩展等厂商字段通常不是靠 VIN 就能可靠判断,上游平台账号是更稳定的路由线索。 - */ -public record Gb32960ParserContext(ProtocolVersion version, String vin, String platformAccount) { - - /** 占位上下文:版本不可缺,vin/account 允许为 null,便于内部构造和单测。 */ - public static Gb32960ParserContext minimal(ProtocolVersion version) { - return new Gb32960ParserContext(version, null, null); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParser.java deleted file mode 100644 index e8bc5790..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParser.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 单一信息体解析器 SPI。 - * - *

    替代旧代码里 2500 行 {@code informationReporting()} 巨方法。每个 - * {@code (ProtocolVersion, typeCode)} 组合对应一个独立实现。同一个协议 typeCode - * 在 2016 / 2025 可能语义不同(如 0x07),因此需要两个 Parser 分别声明 version。 - * - *

    实现约定: - *

      - *
    • {@link #parse(ByteBuffer)} 被调用时 buffer 的 position 已指向信息体第一个字节(不含 1 字节类型前缀) - *
    • 解析完成后 position 应恰好移动到信息体末尾 - *
    • 实现应无状态,可被单例复用 - *
    - */ -public interface InfoBlockParser { - - /** 本 Parser 适用的协议版本({@link ProtocolVersion#V2016} 或 {@link ProtocolVersion#V2025})。 */ - ProtocolVersion version(); - - /** 本 Parser 对应的协议信息类型标志字节(表 9)。 */ - int typeCode(); - - /** 信息体固定长度;变长类型返回 -1 并自行在 parse 中读取长度字段。 */ - int fixedLength(); - - InfoBlock parse(ByteBuffer buffer); -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParserRegistry.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParserRegistry.java deleted file mode 100644 index 563b96b6..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/InfoBlockParserRegistry.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 双键策略注册中心:按 {@code (ProtocolVersion, typeCode)} 索引解析器。 - * - *

    同一 typeCode 在 2016 / 2025 版本可能语义不同(如 0x07 在 2016 是报警,在 2025 是 - * 动力蓄电池最小并联单元电压),必须按版本分派。 - */ -public final class InfoBlockParserRegistry { - - private final Map parsers = new HashMap<>(); - - public InfoBlockParserRegistry(List list) { - for (InfoBlockParser p : list) { - // 后注册覆盖前注册:vendor profile 可以用同一 typeCode 替换默认解析器。 - parsers.put(new Key(p.version(), p.typeCode()), p); - } - } - - public InfoBlockParser find(ProtocolVersion version, int typeCode) { - return parsers.get(new Key(version, typeCode)); - } - - public int size() { - return parsers.size(); - } - - private record Key(ProtocolVersion version, int typeCode) {} -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/ValueDecoder.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/ValueDecoder.java deleted file mode 100644 index 4a7b39bf..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/ValueDecoder.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import java.nio.ByteBuffer; - -/** - * 规范异常值处理辅助:GB/T 32960.3 约定 {@code 0xFE}/{@code 0xFFFE}/{@code 0xFFFFFFFE} 为异常, - * {@code 0xFF}/{@code 0xFFFF}/{@code 0xFFFFFFFF} 为无效。本类把字段读取 + 异常检查封装起来, - * 异常或无效时返回 {@code null},让 record 字段保持 {@link Integer}/{@link Double} 装箱类型。 - */ -public final class ValueDecoder { - - private ValueDecoder() {} - - // ------------ 1 字节无符号 ------------ - - public static Integer u8(ByteBuffer buf) { - int v = buf.get() & 0xFF; - if (v == 0xFE || v == 0xFF) return null; - return v; - } - - /** 原始值,不做异常处理(状态位等不能以 null 表达时用此方法)。 */ - public static int u8raw(ByteBuffer buf) { - return buf.get() & 0xFF; - } - - // ------------ 2 字节无符号大端 ------------ - - public static Integer u16(ByteBuffer buf) { - int v = buf.getShort() & 0xFFFF; - if (v == 0xFFFE || v == 0xFFFF) return null; - return v; - } - - public static int u16raw(ByteBuffer buf) { - // 仅用于长度、数量、bitmask 等协议控制字段;业务测量值应优先走 u16/scaledU16。 - return buf.getShort() & 0xFFFF; - } - - // ------------ 4 字节无符号大端 ------------ - - public static Long u32(ByteBuffer buf) { - long v = buf.getInt() & 0xFFFFFFFFL; - if (v == 0xFFFFFFFEL || v == 0xFFFFFFFFL) return null; - return v; - } - - public static long u32raw(ByteBuffer buf) { - // 仅用于故障码、标志位等需要保留全部 bit 的字段。 - return buf.getInt() & 0xFFFFFFFFL; - } - - // ------------ 业务标度辅助 ------------ - - /** 读 u16,scale 后返回(例如 0.1 转换)。异常/无效返回 null。 */ - public static Double scaledU16(ByteBuffer buf, double scale) { - Integer raw = u16(buf); - return raw == null ? null : raw * scale; - } - - /** 读 u16 + 偏移 + scale。例:读总电流 -> scaledU16WithOffset(buf, 0.1, -1000) */ - public static Double scaledU16WithOffset(ByteBuffer buf, double scale, double offset) { - Integer raw = u16(buf); - return raw == null ? null : raw * scale + offset; - } - - public static Double scaledU32(ByteBuffer buf, double scale) { - Long raw = u32(buf); - return raw == null ? null : raw * scale; - } - - public static Double scaledU32WithOffset(ByteBuffer buf, double scale, double offset) { - Long raw = u32(buf); - return raw == null ? null : raw * scale + offset; - } - - /** 温度字段:原始值 - 40 ℃,范围 -40~+210。 */ - public static Integer tempOffset40(ByteBuffer buf) { - Integer raw = u8(buf); - return raw == null ? null : raw - 40; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/AlarmV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/AlarmV2016BlockParser.java deleted file mode 100644 index 60f719db..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/AlarmV2016BlockParser.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -// ProtocolVersion still used by version() override below - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 报警数据 (0x07) —— GB/T 32960.3-2016 附录 B 表 B.17。变长。 - * maxAlarmLevel(1) + generalAlarmFlag(4) + 4 组故障列表(count(1)+entries(4*N))。 - * - *

    2025 版报警数据 typeCode 改为 0x06 且尾部多 N5 个 (flagBit,level) —— 见 v2025 包。 - */ -public final class AlarmV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x07; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer maxLevel = ValueDecoder.u8(buf); - long generalFlag = ValueDecoder.u32raw(buf); - List battery = readFaultList(buf); - List motor = readFaultList(buf); - List engine = readFaultList(buf); - List other = readFaultList(buf); - return new InfoBlock.Gb32960V2016.Alarm( - maxLevel, generalFlag, battery, motor, engine, other); - } - - static List readFaultList(ByteBuffer buf) { - int count = buf.get() & 0xFF; - List out = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - // 故障码按 4 字节无符号值处理,避免高位为 1 时变成负数。 - out.add(buf.getInt() & 0xFFFFFFFFL); - } - return out; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/DriveMotorV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/DriveMotorV2016BlockParser.java deleted file mode 100644 index 9980b4a2..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/DriveMotorV2016BlockParser.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 驱动电机数据 (0x02) —— GB/T 32960.3-2016 表 B.16。变长。 - * 1 字节电机个数 + N × 12 字节电机: - * serialNo(1) state(1) ctrlTempC(1) rpm(2,offset -20000) - * torque(2,offset -20000,0.1 N·m) motorTempC(1) - * ctrlVoltageV(2,0.1V) ctrlCurrentA(2,0.1A,offset -1000)。 - */ -public final class DriveMotorV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x02; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int count = buf.get() & 0xFF; - List motors = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - // rpm/torque 在标准里是带偏移的无符号数,先按原始无符号读,再统一减偏移。 - Integer serialNo = ValueDecoder.u8(buf); - Integer state = ValueDecoder.u8(buf); - Integer ctrlTempC = ValueDecoder.tempOffset40(buf); - Integer rawRpm = ValueDecoder.u16(buf); - Integer rpm = rawRpm == null ? null : rawRpm - 20_000; - Integer rawTq = ValueDecoder.u16(buf); - Double torque = rawTq == null ? null : (rawTq - 20_000) * 0.1; - Integer motorTempC = ValueDecoder.tempOffset40(buf); - Double ctrlVoltage = ValueDecoder.scaledU16(buf, 0.1); - Double ctrlCurrent = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); - motors.add(new InfoBlock.Gb32960V2016.DriveMotor.Motor( - serialNo, state, ctrlTempC, rpm, torque, motorTempC, ctrlVoltage, ctrlCurrent)); - } - return new InfoBlock.Gb32960V2016.DriveMotor(motors); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/EngineV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/EngineV2016BlockParser.java deleted file mode 100644 index 580857ae..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/EngineV2016BlockParser.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 发动机数据 (0x04) —— GB/T 32960.3-2016 表 20。固定 5 字节: - * engineState(1) + crankshaftRpm(2) + fuelConsumptionRate(2,0.01 L/100km)。 - * 停车充电过程和增程式车辆纯电模式无需传输。 - */ -public final class EngineV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x04; } - @Override public int fixedLength() { return 5; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer engineState = ValueDecoder.u8(buf); - Integer crankshaftRpm = ValueDecoder.u16(buf); - Double fuelRate = ValueDecoder.scaledU16(buf, 0.01); - return new InfoBlock.Gb32960V2016.Engine(engineState, crankshaftRpm, fuelRate); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/ExtremeValueV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/ExtremeValueV2016BlockParser.java deleted file mode 100644 index d3c2df23..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/ExtremeValueV2016BlockParser.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 动力蓄电池极值数据 (0x06) —— GB/T 32960.3-2016 表 B.16。固定 14 字节。 - * 2025 版 0x06 语义已变更为报警数据。 - */ -public final class ExtremeValueV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x06; } - @Override public int fixedLength() { return 14; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - // 极值块只给出子系统号/单体号/探针号及对应值,不包含完整单体数组。 - Integer maxVSub = ValueDecoder.u8(buf); - Integer maxVCell = ValueDecoder.u8(buf); - Double maxV = ValueDecoder.scaledU16(buf, 0.001); - Integer minVSub = ValueDecoder.u8(buf); - Integer minVCell = ValueDecoder.u8(buf); - Double minV = ValueDecoder.scaledU16(buf, 0.001); - Integer maxTSub = ValueDecoder.u8(buf); - Integer maxTProbe = ValueDecoder.u8(buf); - Integer maxT = ValueDecoder.tempOffset40(buf); - Integer minTSub = ValueDecoder.u8(buf); - Integer minTProbe = ValueDecoder.u8(buf); - Integer minT = ValueDecoder.tempOffset40(buf); - return new InfoBlock.Gb32960V2016.Extreme( - maxVSub, maxVCell, maxV, - minVSub, minVCell, minV, - maxTSub, maxTProbe, maxT, - minTSub, minTProbe, minT); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/FuelCellV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/FuelCellV2016BlockParser.java deleted file mode 100644 index b47fa280..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/FuelCellV2016BlockParser.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 燃料电池数据 (0x03) —— GB/T 32960.3-2016 表 B.13。变长。 - * fcVoltageV(2) + fcCurrentA(2) + hydrogenConsumption(2,0.01 kg/100km) - * + probeCount N(2) + probes[N](1B,-40 offset) + 后续氢系统 10 字节固定段。 - */ -public final class FuelCellV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x03; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Double fcVoltage = ValueDecoder.scaledU16(buf, 0.1); - Double fcCurrent = ValueDecoder.scaledU16(buf, 0.1); - Double consumption = ValueDecoder.scaledU16(buf, 0.01); - int probeCount = ValueDecoder.u16raw(buf); - - // 防御:probeCount 异常时回退为空列表避免吞 buffer - List probes = new ArrayList<>(); - int tailFixed = 2 + 1 + 2 + 1 + 2 + 1 + 1; // 尾部固定 10 字节 - // probeCount 来自车端报文,错误值会导致尾部固定段错位;这里先确保尾部还能完整解析。 - if (probeCount >= 0 && probeCount <= buf.remaining() - tailFixed) { - for (int i = 0; i < probeCount; i++) { - Integer t = ValueDecoder.tempOffset40(buf); - if (t != null) probes.add(t); - } - } - Double maxHydrogenTempC = ValueDecoder.scaledU16WithOffset(buf, 0.1, -40.0); - Integer maxHydrogenTempProbeId = ValueDecoder.u8(buf); - Double maxHydrogenConcentration = ValueDecoder.scaledU16(buf, 0.000001); - Integer maxHydrogenConcentrationProbeId = ValueDecoder.u8(buf); - Double maxHydrogenPressure = ValueDecoder.scaledU16(buf, 0.1); - Integer maxHydrogenPressureProbeId = ValueDecoder.u8(buf); - Integer hvDcDcStatus = ValueDecoder.u8(buf); - - return new InfoBlock.Gb32960V2016.FuelCell( - fcVoltage, fcCurrent, consumption, probes, - maxHydrogenTempC, maxHydrogenTempProbeId, - maxHydrogenConcentration, maxHydrogenConcentrationProbeId, - maxHydrogenPressure, maxHydrogenPressureProbeId, - hvDcDcStatus); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/PositionV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/PositionV2016BlockParser.java deleted file mode 100644 index f69fbf46..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/PositionV2016BlockParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 车辆位置数据 (0x05) —— GB/T 32960.3-2016 表 22。固定 9 字节: - * 状态位(1) + 经度(4,1e-6 度) + 纬度(4,1e-6 度)。 - * - *

    状态位 bit0 0=有效定位 / bit1 0=北纬 1=南纬 / bit2 0=东经 1=西经。 - */ -public final class PositionV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x05; } - @Override public int fixedLength() { return 9; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int statusFlag = buf.get() & 0xFF; - long lonRaw = buf.getInt() & 0xFFFFFFFFL; - long latRaw = buf.getInt() & 0xFFFFFFFFL; - double longitude = lonRaw / 1_000_000.0; - double latitude = latRaw / 1_000_000.0; - // 标准用状态位表达南纬/西经;原始经纬度本身始终按正数传输。 - if ((statusFlag & 0b100) != 0) longitude = -longitude; - if ((statusFlag & 0b010) != 0) latitude = -latitude; - return new InfoBlock.Gb32960V2016.Position(statusFlag, longitude, latitude); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/TemperatureV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/TemperatureV2016BlockParser.java deleted file mode 100644 index 939ef2a4..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/TemperatureV2016BlockParser.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 可充电储能装置温度数据 (0x09) —— GB/T 32960.3-2016 表 B.20。变长。 - * subSystemCount(1) + 每子系统 [batteryIndex(1) + probeCount(2) + probes(probeCount*1,-40 offset)]。 - * - *

    2025 版已删除该 typeCode(动力蓄电池温度改用 0x08)。 - */ -public final class TemperatureV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x09; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int subCount = buf.get() & 0xFF; - int maxT = Integer.MIN_VALUE; - int minT = Integer.MAX_VALUE; - for (int i = 0; i < subCount; i++) { - buf.get(); // batteryIndex - int probes = buf.getShort() & 0xFFFF; - // 与电压块类似,这里暂不保存每个探针,只输出可查询的最高/最低温摘要。 - for (int p = 0; p < probes; p++) { - int t = (buf.get() & 0xFF) - 40; - if (t > maxT) maxT = t; - if (t < minT) minT = t; - } - } - if (maxT == Integer.MIN_VALUE) maxT = 0; - if (minT == Integer.MAX_VALUE) minT = 0; - return new InfoBlock.Gb32960V2016.Temperature(subCount, maxT, minT); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VehicleV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VehicleV2016BlockParser.java deleted file mode 100644 index a0df6f7c..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VehicleV2016BlockParser.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 整车数据 (0x01) —— GB/T 32960.3-2016 表 B.10。固定 20 字节: - * vehicleState(1) + chargingState(1) + runningMode(1) + speedKmh(2) - * + totalMileageKm(4) + totalVoltageV(2) + totalCurrentA(2) - * + socPercent(1) + dcDcStatus(1) + gearRaw(1) + insulationKohm(2) - * + accelPedal(1) + brakePedal(1)。 - * - *

    该块是实时事件和 snapshot 的核心公共字段来源,字段名变更会影响前端常用查询。 - */ -public final class VehicleV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x01; } - @Override public int fixedLength() { return 20; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer vehicleState = ValueDecoder.u8(buf); - Integer chargingState = ValueDecoder.u8(buf); - Integer runningMode = ValueDecoder.u8(buf); - Double speedKmh = ValueDecoder.scaledU16(buf, 0.1); - Double mileageKm = ValueDecoder.scaledU32(buf, 0.1); - Double totalVoltageV = ValueDecoder.scaledU16(buf, 0.1); - Double totalCurrentA = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); - Integer soc = ValueDecoder.u8(buf); - Integer dcDcStatus = ValueDecoder.u8(buf); - Integer gearRaw = ValueDecoder.u8raw(buf); - Integer insulation = ValueDecoder.u16(buf); - Integer accelPedal = ValueDecoder.u8(buf); - Integer brakePedal = ValueDecoder.u8(buf); - return new InfoBlock.Gb32960V2016.Vehicle(vehicleState, chargingState, runningMode, - speedKmh, mileageKm, totalVoltageV, totalCurrentA, soc, dcDcStatus, gearRaw, - insulation, accelPedal, brakePedal); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VoltageV2016BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VoltageV2016BlockParser.java deleted file mode 100644 index 1aa5f9ba..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2016/VoltageV2016BlockParser.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2016; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 可充电储能装置电压数据 (0x08) —— GB/T 32960.3-2016 表 B.18。变长。 - * subSystemCount(1) + 每子系统 [batteryIndex(1) + voltage(2,0.1V) + current(2,0.1A,offset -1000) - * + cellCount(2) + frameCellStart(2) + frameCellCount(1) + cellVoltages(frameCellCount*2,0.001V)]。 - * - *

    PoC 汇总:保留子系统数 + 总电压 + 单体最高/最低值。 - * 2025 版 0x08 语义已变更为动力蓄电池温度。 - */ -public final class VoltageV2016BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x08; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int subCount = buf.get() & 0xFF; - double totalV = 0; - double maxCell = Double.MIN_VALUE; - double minCell = Double.MAX_VALUE; - for (int i = 0; i < subCount; i++) { - buf.get(); // batteryIndex - double voltage = (buf.getShort() & 0xFFFF) * 0.1; - totalV += voltage; - buf.getShort(); // current (skip) - int cellCount = buf.getShort() & 0xFFFF; - buf.getShort(); // frameCellStart - int frameCellCount = buf.get() & 0xFF; - // 报文可能只上传本帧范围内的单体电压;聚合层只保存最高/最低/总压摘要。 - int actual = Math.min(frameCellCount, cellCount); - for (int c = 0; c < actual; c++) { - double cellV = (buf.getShort() & 0xFFFF) * 0.001; - if (cellV > maxCell) maxCell = cellV; - if (cellV < minCell) minCell = cellV; - } - // 如果 frameCellCount 大于 cellCount,剩余字节仍要消费掉,避免影响后续子系统解析。 - for (int c = actual; c < frameCellCount; c++) buf.getShort(); - } - if (maxCell == Double.MIN_VALUE) maxCell = 0; - if (minCell == Double.MAX_VALUE) minCell = 0; - return new InfoBlock.Gb32960V2016.Voltage(subCount, maxCell, minCell, totalV); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/AlarmV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/AlarmV2025BlockParser.java deleted file mode 100644 index 0817f17b..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/AlarmV2025BlockParser.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 报警数据 (0x06) —— GB/T 32960.3-2025 §7.2.4.9 表 23。变长。 - * 前半部分与 2016 版相同,尾部新增"通用报警故障总数(1) + N5×(flagBit(1)+level(1))"。 - */ -public final class AlarmV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x06; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer maxLevel = ValueDecoder.u8(buf); - long generalFlag = ValueDecoder.u32raw(buf); - List battery = readFaultList(buf); - List motor = readFaultList(buf); - List engine = readFaultList(buf); - List other = readFaultList(buf); - - int generalAlarmCount = buf.hasRemaining() ? (buf.get() & 0xFF) : 0; - List levels = new ArrayList<>(generalAlarmCount); - // 2025 新增通用报警等级列表;尾部不完整时保留已读条目,避免整帧丢弃。 - for (int i = 0; i < generalAlarmCount && buf.remaining() >= 2; i++) { - int bit = buf.get() & 0xFF; - int level = buf.get() & 0xFF; - levels.add(new InfoBlock.Gb32960V2025.Alarm.GeneralAlarmEntry(bit, level)); - } - - return new InfoBlock.Gb32960V2025.Alarm( - maxLevel, generalFlag, battery, motor, engine, other, levels); - } - - private static List readFaultList(ByteBuffer buf) { - int count = buf.get() & 0xFF; - List out = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - // 故障码按 4 字节无符号值处理,避免高位为 1 时变成负数。 - out.add(buf.getInt() & 0xFFFFFFFFL); - } - return out; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/BatteryTemperatureV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/BatteryTemperatureV2025BlockParser.java deleted file mode 100644 index eda41c16..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/BatteryTemperatureV2025BlockParser.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 动力蓄电池温度数据 (0x08) —— GB/T 32960.3-2025 §7.2.4.3。变长。 - * 2016 版 0x08 为可充电储能装置电压,由 v2016 包处理。 - */ -public final class BatteryTemperatureV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x08; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int subCount = buf.get() & 0xFF; - List packs = new ArrayList<>(subCount); - for (int i = 0; i < subCount; i++) { - int packNo = buf.get() & 0xFF; - int probeCount = buf.getShort() & 0xFFFF; - List temps = new ArrayList<>(probeCount); - // probeCount 是车端声明值;实际数组按剩余字节裁剪,防止坏包读穿。 - int safeCount = Math.min(probeCount, buf.remaining()); - for (int k = 0; k < safeCount; k++) { - temps.add((buf.get() & 0xFF) - 40); - } - packs.add(new InfoBlock.Gb32960V2025.BatteryTemperature.Pack(packNo, probeCount, temps)); - } - return new InfoBlock.Gb32960V2025.BatteryTemperature(subCount, packs); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/DriveMotorV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/DriveMotorV2025BlockParser.java deleted file mode 100644 index 16c13ec8..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/DriveMotorV2025BlockParser.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 驱动电机数据 (0x02) —— GB/T 32960.3-2025 表 16。变长。 - * 1 字节电机个数 + N × 14 字节电机: - * serialNo(1) state(1) ctrlTempC(1) rpm(2,offset -32000) - * torque(4,offset -20000,0.1 N·m) motorTempC(1) - * ctrlVoltageV(2,0.1V) ctrlCurrentA(2,0.1A,offset -1000)。 - * - *

    相比 2016 版:rpm 偏移变为 -32000;torque 由 2 字节扩展为 4 字节。 - */ -public final class DriveMotorV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x02; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int count = buf.get() & 0xFF; - List motors = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - // 2025 版 torque 扩为 4 字节,不能沿用 2016 的 u16 解析。 - Integer serialNo = ValueDecoder.u8(buf); - Integer state = ValueDecoder.u8(buf); - Integer ctrlTempC = ValueDecoder.tempOffset40(buf); - Integer rawRpm = ValueDecoder.u16(buf); - Integer rpm = rawRpm == null ? null : rawRpm - 32_000; - Long rawTq = ValueDecoder.u32(buf); - Double torque = rawTq == null ? null : (rawTq - 20_000L) * 0.1; - Integer motorTempC = ValueDecoder.tempOffset40(buf); - Double ctrlVoltage = ValueDecoder.scaledU16(buf, 0.1); - Double ctrlCurrent = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); - motors.add(new InfoBlock.Gb32960V2025.DriveMotor.Motor( - serialNo, state, ctrlTempC, rpm, torque, motorTempC, ctrlVoltage, ctrlCurrent)); - } - return new InfoBlock.Gb32960V2025.DriveMotor(motors); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/EngineV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/EngineV2025BlockParser.java deleted file mode 100644 index 48661477..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/EngineV2025BlockParser.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 发动机数据 (0x04) —— GB/T 32960.3-2025。固定 5 字节。 - * 字段与 2016 版完全一致;保留独立类只为遵守"双版本不共用"原则。 - */ -public final class EngineV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x04; } - @Override public int fixedLength() { return 5; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer engineState = ValueDecoder.u8(buf); - Integer crankshaftRpm = ValueDecoder.u16(buf); - Double fuelRate = ValueDecoder.scaledU16(buf, 0.01); - return new InfoBlock.Gb32960V2025.Engine(engineState, crankshaftRpm, fuelRate); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellStackV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellStackV2025BlockParser.java deleted file mode 100644 index b7bba34f..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellStackV2025BlockParser.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 燃料电池电堆数据 (0x30) —— GB/T 32960.3-2025 §7.2.4.6 表 18/19。变长。 - * - *

    V2016 的同 typeCode 厂商扩展由 v2016 包独立处理。 - */ -public final class FuelCellStackV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x30; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int stackCount = buf.get() & 0xFF; - List stacks = new ArrayList<>(stackCount); - for (int i = 0; i < stackCount; i++) { - int stackNo = buf.get() & 0xFF; - Double voltage = ValueDecoder.scaledU16(buf, 0.1); - Double current = ValueDecoder.scaledU16(buf, 0.1); - Double h2InletPressure = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); - Double airInletPressure = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); - Integer airInletTemp = ValueDecoder.tempOffset40(buf); - int probeCount = buf.getShort() & 0xFFFF; - List temps = new ArrayList<>(probeCount); - // 坏包 probeCount 过大时只消费剩余可用字节;声明数量仍保存在 stack 对象外层上下文中。 - int safeCount = Math.min(probeCount, buf.remaining()); - for (int k = 0; k < safeCount; k++) { - temps.add((buf.get() & 0xFF) - 40); - } - stacks.add(new InfoBlock.Gb32960V2025.FuelCellStack.Stack( - stackNo, voltage, current, h2InletPressure, airInletPressure, airInletTemp, temps)); - } - return new InfoBlock.Gb32960V2025.FuelCellStack(stackCount, stacks); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellV2025BlockParser.java deleted file mode 100644 index 72edbd76..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/FuelCellV2025BlockParser.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 燃料电池发动机及车载氢系统数据 (0x03) —— GB/T 32960.3-2025 §7.2.4.5 表 17。固定 13 字节。 - */ -public final class FuelCellV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x03; } - @Override public int fixedLength() { return 13; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Double maxTempC = ValueDecoder.scaledU16WithOffset(buf, 0.1, -40.0); - Integer maxTempProbeId = ValueDecoder.u8(buf); - Double maxConcentration = ValueDecoder.scaledU16(buf, 0.000001); - Integer maxConcentrationProbeId = ValueDecoder.u8(buf); - Double maxPressure = ValueDecoder.scaledU16(buf, 0.1); - Integer maxPressureProbeId = ValueDecoder.u8(buf); - Integer hvDcDcStatus = ValueDecoder.u8(buf); - Integer remainingH2Percent = ValueDecoder.u8(buf); - Integer hvDcDcControllerTempC = ValueDecoder.tempOffset40(buf); - return new InfoBlock.Gb32960V2025.FuelCell( - maxTempC, maxTempProbeId, - maxConcentration, maxConcentrationProbeId, - maxPressure, maxPressureProbeId, - hvDcDcStatus, remainingH2Percent, hvDcDcControllerTempC); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/MinParallelVoltageV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/MinParallelVoltageV2025BlockParser.java deleted file mode 100644 index 54f9a638..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/MinParallelVoltageV2025BlockParser.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 动力蓄电池最小并联单元电压数据 (0x07) —— GB/T 32960.3-2025 §7.2.4.2。变长。 - * 2016 版 0x07 为报警,由 v2016 包处理。 - */ -public final class MinParallelVoltageV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x07; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int subCount = buf.get() & 0xFF; - List packs = new ArrayList<>(subCount); - for (int i = 0; i < subCount; i++) { - int packNo = buf.get() & 0xFF; - Double voltage = ValueDecoder.scaledU16(buf, 0.1); - Double current = ValueDecoder.scaledU16WithOffset(buf, 0.1, -3000.0); - int totalUnits = buf.getShort() & 0xFFFF; - List frameVoltages = new ArrayList<>(totalUnits); - // 每个最小并联单元电压占 2 字节;不足时只读完整单元,避免半个值污染后续解析。 - int safeCount = Math.min(totalUnits, buf.remaining() / 2); - for (int k = 0; k < safeCount; k++) { - frameVoltages.add((buf.getShort() & 0xFFFF) * 0.001); - } - packs.add(new InfoBlock.Gb32960V2025.MinParallelVoltage.Pack( - packNo, voltage, current, totalUnits, frameVoltages)); - } - return new InfoBlock.Gb32960V2025.MinParallelVoltage(subCount, packs); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/PositionV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/PositionV2025BlockParser.java deleted file mode 100644 index d6a65c84..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/PositionV2025BlockParser.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 车辆位置数据 (0x05) —— GB/T 32960.3-2025。固定 10 字节, - * 比 2016 版多一个坐标系字节: - * 状态位(1) + 坐标系(1) + 经度(4) + 纬度(4)。 - * - *

    坐标系:0x01=WGS84 / 0x02=GCJ-02 / 0x03=其他。 - */ -public final class PositionV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x05; } - @Override public int fixedLength() { return 10; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int statusFlag = buf.get() & 0xFF; - int coordSystem = buf.get() & 0xFF; - long lonRaw = buf.getInt() & 0xFFFFFFFFL; - long latRaw = buf.getInt() & 0xFFFFFFFFL; - double longitude = lonRaw / 1_000_000.0; - double latitude = latRaw / 1_000_000.0; - if ((statusFlag & 0b100) != 0) longitude = -longitude; - if ((statusFlag & 0b010) != 0) latitude = -latitude; - return new InfoBlock.Gb32960V2025.Position(statusFlag, coordSystem, longitude, latitude); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorExtremeV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorExtremeV2025BlockParser.java deleted file mode 100644 index 919e6c56..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorExtremeV2025BlockParser.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 超级电容器极值数据 (0x32) —— GB/T 32960.3-2025 §7.2.4.11 表 26。固定 18 字节。 - */ -public final class SuperCapacitorExtremeV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x32; } - @Override public int fixedLength() { return 18; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - // 2025 超级电容极值的单体/探针编号为 2 字节,区别于 2016 蓄电池极值的 1 字节编号。 - Integer maxVSys = ValueDecoder.u8(buf); - Integer maxVCell = ValueDecoder.u16(buf); - Double maxV = ValueDecoder.scaledU16(buf, 0.001); - Integer minVSys = ValueDecoder.u8(buf); - Integer minVCell = ValueDecoder.u16(buf); - Double minV = ValueDecoder.scaledU16(buf, 0.001); - Integer maxTSys = ValueDecoder.u8(buf); - Integer maxTProbe = ValueDecoder.u16(buf); - Integer maxT = ValueDecoder.tempOffset40(buf); - Integer minTSys = ValueDecoder.u8(buf); - Integer minTProbe = ValueDecoder.u16(buf); - Integer minT = ValueDecoder.tempOffset40(buf); - return new InfoBlock.Gb32960V2025.SuperCapacitorExtreme( - maxVSys, maxVCell, maxV, - minVSys, minVCell, minV, - maxTSys, maxTProbe, maxT, - minTSys, minTProbe, minT); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorV2025BlockParser.java deleted file mode 100644 index 96655812..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/SuperCapacitorV2025BlockParser.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 超级电容器数据 (0x31) —— GB/T 32960.3-2025 §7.2.4.10 表 25。变长。 - */ -public final class SuperCapacitorV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x31; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int systemNo = buf.get() & 0xFF; - Double totalVoltage = ValueDecoder.scaledU16(buf, 0.1); - Double totalCurrent = ValueDecoder.scaledU16WithOffset(buf, 0.1, -3000.0); - int cellCount = buf.getShort() & 0xFFFF; - List cells = new ArrayList<>(cellCount); - // 电压数组按 2 字节一个值裁剪,避免声明数量异常时读穿温度段。 - int safeCellCount = Math.min(cellCount, buf.remaining() / 2); - for (int i = 0; i < safeCellCount; i++) { - cells.add((buf.getShort() & 0xFFFF) * 0.001); - } - int probeCount = buf.hasRemaining() ? (buf.getShort() & 0xFFFF) : 0; - List temps = new ArrayList<>(probeCount); - // 温度探针每个 1 字节,按剩余字节裁剪。 - int safeProbeCount = Math.min(probeCount, buf.remaining()); - for (int i = 0; i < safeProbeCount; i++) { - temps.add((buf.get() & 0xFF) - 40); - } - return new InfoBlock.Gb32960V2025.SuperCapacitor(systemNo, totalVoltage, totalCurrent, cells, temps); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/VehicleV2025BlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/VehicleV2025BlockParser.java deleted file mode 100644 index e6ee6a40..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/v2025/VehicleV2025BlockParser.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.v2025; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 整车数据 (0x01) —— GB/T 32960.3-2025。固定 18 字节。 - * 相比 2016 版删除加速踏板和制动踏板。 - * - *

    为保持查询接口稳定,后续转换层会把 2016/2025 两版整车字段映射到同一组逻辑字段。 - */ -public final class VehicleV2025BlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2025; } - @Override public int typeCode() { return 0x01; } - @Override public int fixedLength() { return 18; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer vehicleState = ValueDecoder.u8(buf); - Integer chargingState = ValueDecoder.u8(buf); - Integer runningMode = ValueDecoder.u8(buf); - Double speedKmh = ValueDecoder.scaledU16(buf, 0.1); - Double mileageKm = ValueDecoder.scaledU32(buf, 0.1); - Double totalVoltageV = ValueDecoder.scaledU16(buf, 0.1); - Double totalCurrentA = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); - Integer soc = ValueDecoder.u8(buf); - Integer dcDcStatus = ValueDecoder.u8(buf); - Integer gearRaw = ValueDecoder.u8raw(buf); - Integer insulation = ValueDecoder.u16(buf); - return new InfoBlock.Gb32960V2025.Vehicle(vehicleState, chargingState, runningMode, - speedKmh, mileageKm, totalVoltageV, totalCurrentA, soc, dcDcStatus, gearRaw, - insulation); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAirConditionerBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAirConditionerBlockParser.java deleted file mode 100644 index 67a822d9..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAirConditionerBlockParser.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 广东燃料电池规范 §7.2.3.7 表 18:空调数据。typeCode {@code 0x33}。 - * - *

    固定 5 字节: - *

    - *   status(1)                        0 关闭 / 1 启动
    - *   powerKw(2, offset -5, 1 kw, range -5~+30)
    - *   compressorInputVoltageV(2, 0.1V, 0~1000V)
    - * 
    - */ -public final class GdFcAirConditionerBlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x33; } - @Override public int fixedLength() { return 5; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer status = ValueDecoder.u8raw(buf); - int rawPower = buf.getShort() & 0xFFFF; - Integer power = (rawPower == 0xFFFE || rawPower == 0xFFFF) ? null : rawPower - 5; - Double inputV = ValueDecoder.scaledU16(buf, 0.1); - return new InfoBlock.GuangdongFc.AirConditioner(status, power, inputV); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAuxiliaryBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAuxiliaryBlockParser.java deleted file mode 100644 index 919a6ee9..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcAuxiliaryBlockParser.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 广东燃料电池规范 §7.2.3.5 表 15/16:辅助系统数据。typeCode {@code 0x31}。变长。 - * - *
    - *   subSystemCount(1)
    - *   For each:
    - *     airCompressorMotorVoltageV(2, 0.1V, 0~1000V)
    - *     airCompressorPowerKw(2, offset -5, 1 kw, range -5~+30)
    - *     hydrogenPumpMotorVoltageV(2, 0.2V, 0~50V)
    - *     hydrogenPumpPowerKw(2, offset -5, 1 kw)
    - *     waterPumpVoltageV(2, 0.1V)
    - *     ptcVoltageV(2, 0.1V)
    - *     ptcPowerKw(2, offset -5, 1 kw)
    - *     lowVoltageBatteryVoltageV(2, 0.1V, 0~32V)
    - * 
    - * - * 每子系统 16 字节。 - */ -public final class GdFcAuxiliaryBlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x31; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int count = buf.get() & 0xFF; - List subs = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - Double airCompV = ValueDecoder.scaledU16(buf, 0.1); - Integer airCompP = readPowerOffsetMinus5(buf); - Double h2PumpV = ValueDecoder.scaledU16(buf, 0.2); - Integer h2PumpP = readPowerOffsetMinus5(buf); - Double waterPumpV = ValueDecoder.scaledU16(buf, 0.1); - Double ptcV = ValueDecoder.scaledU16(buf, 0.1); - Integer ptcP = readPowerOffsetMinus5(buf); - Double lvBattV = ValueDecoder.scaledU16(buf, 0.1); - subs.add(new InfoBlock.GuangdongFc.Auxiliary.Subsystem( - airCompV, airCompP, h2PumpV, h2PumpP, waterPumpV, ptcV, ptcP, lvBattV)); - } - return new InfoBlock.GuangdongFc.Auxiliary(count, subs); - } - - /** 功率字段:raw(2B) - 5,0xFFFE/0xFFFF 异常/无效。 */ - private static Integer readPowerOffsetMinus5(ByteBuffer buf) { - int raw = buf.getShort() & 0xFFFF; - if (raw == 0xFFFE || raw == 0xFFFF) return null; - return raw - 5; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDcDcBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDcDcBlockParser.java deleted file mode 100644 index 7d088197..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDcDcBlockParser.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 广东燃料电池规范 §7.2.3.6 表 17:DC/DC(燃料电池系统)数据。typeCode {@code 0x32}。 - * - *

    固定 9 字节: - *

    - *   inputVoltageV(2, 0.1V, 0~400V)
    - *   inputCurrentA(2, 0.1A, 0~600A)
    - *   outputVoltageV(2, 0.1V, 0~700V)
    - *   outputCurrentA(2, 0.1A, 0~600A)
    - *   controllerTempC(1, -40 offset)
    - * 
    - * - *

    只有 vendor profile 命中 {@code guangdong-fc} 时才会解析到该结构;否则同一 typeCode 会落 Raw。 - */ -public final class GdFcDcDcBlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x32; } - @Override public int fixedLength() { return 9; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Double inV = ValueDecoder.scaledU16(buf, 0.1); - Double inC = ValueDecoder.scaledU16(buf, 0.1); - Double outV = ValueDecoder.scaledU16(buf, 0.1); - Double outC = ValueDecoder.scaledU16(buf, 0.1); - Integer ctrlTemp = ValueDecoder.tempOffset40(buf); - return new InfoBlock.GuangdongFc.DcDc(inV, inC, outV, outC, ctrlTemp); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDemoExtensionBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDemoExtensionBlockParser.java deleted file mode 100644 index 8457d615..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcDemoExtensionBlockParser.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 广东燃料电池规范 §7.2.3.14 表 27:燃料电池汽车示范应用数据扩展。typeCode {@code 0x80}。 - * - *

    结构(typeCode 由外层消费): - *

    - *   dataLength (2B WORD) = 9     表示后续扩展数据长度
    - *   stackTempC (1B, -40 offset, -40~+210°C)
    - *   airCompressorVoltageV (2B, 0.1V, 0~2000V)
    - *   airCompressorCurrentA (2B, 0.1A, -1000 offset, -1000~+1000A)
    - *   hydrogenPumpVoltageV (2B, 0.1V, 0~2000V)
    - *   hydrogenPumpCurrentA (2B, 0.1A, -1000 offset, -1000~+1000A)
    - * 
    - * - * 含 length 字段共 11 字节 body(不含外层 1B typeCode)。 - */ -public final class GdFcDemoExtensionBlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x80; } - @Override public int fixedLength() { return 11; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int declaredLen = buf.getShort() & 0xFFFF; - // declaredLen 通常 = 9,但即便不是也按 9 字节固定布局解,避免无谓的失败。 - Integer stackTemp = ValueDecoder.tempOffset40(buf); - Double acV = ValueDecoder.scaledU16(buf, 0.1); - Double acC = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); - Double h2V = ValueDecoder.scaledU16(buf, 0.1); - Double h2C = ValueDecoder.scaledU16WithOffset(buf, 0.1, -1000.0); - // 防御:若 declaredLen 与实际不一致,仅在 DEBUG 留痕。 - if (declaredLen != 9 && org.slf4j.LoggerFactory.getLogger(GdFcDemoExtensionBlockParser.class).isDebugEnabled()) { - org.slf4j.LoggerFactory.getLogger(GdFcDemoExtensionBlockParser.class) - .debug("[gb32960/gd-fc/0x80] declaredLen={} (expected 9), parsed by fixed layout", declaredLen); - } - return new InfoBlock.GuangdongFc.DemoExtension(stackTemp, acV, acC, h2V, h2C); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcStackBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcStackBlockParser.java deleted file mode 100644 index 1e6d4c43..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcStackBlockParser.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -/** - * 广东燃料电池汽车示范应用城市群综合监管平台 §7.2.3.4 表 13/14:燃料电池电堆数据。 - * - *

    typeCode {@code 0x30},工程上观察到对端在 GB/T 32960.3-2016 帧({@code 2323} - * 起始符)里下发该块。本规范在 GB/T 32960.3-2016 标准里属于 {@code 0x30~0x7F} 预留区段, - * 因此只有当对端被 vendor profile 选中后才启用此 parser。 - * - *

    结构: - *

    - *   stackCount (1B)
    - *   For each stack:
    - *     engineWorkState (1B)                         00 关闭 / 01 打开 / FE 异常 / FF 无效
    - *     stackWaterOutletTempC (1B, -40 offset)
    - *     hydrogenInletPressureKpa (2B WORD, -100 offset, 0.1 kPa)
    - *     airInletPressureKpa (2B WORD, -100 offset, 0.1 kPa)
    - *     airInletTempC (1B, -40 offset)
    - *     maxCellVoltageId (2B WORD)
    - *     minCellVoltageId (2B WORD)
    - *     maxCellVoltageV (2B WORD, 0.001 V)
    - *     minCellVoltageV (2B WORD, 0.001 V)
    - *     avgCellVoltageV (2B WORD, 0.001 V)
    - *     cellCount (2B WORD)
    - *     frameCellStart (2B WORD)
    - *     frameCellCount (1B)
    - *     frameCellVoltagesV (2 * frameCellCount, 0.001 V)
    - * 
    - */ -public final class GdFcStackBlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x30; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int stackCount = buf.get() & 0xFF; - List stacks = new ArrayList<>(stackCount); - for (int i = 0; i < stackCount; i++) { - Integer workState = ValueDecoder.u8raw(buf); - Integer waterTemp = ValueDecoder.tempOffset40(buf); - Double h2InletP = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); - Double airInletP = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); - Integer airInletTemp = ValueDecoder.tempOffset40(buf); - Integer maxId = ValueDecoder.u16(buf); - Integer minId = ValueDecoder.u16(buf); - Double maxV = ValueDecoder.scaledU16(buf, 0.001); - Double minV = ValueDecoder.scaledU16(buf, 0.001); - Double avgV = ValueDecoder.scaledU16(buf, 0.001); - Integer cellCount = ValueDecoder.u16(buf); - Integer frameStart = ValueDecoder.u16(buf); - int frameCells = buf.get() & 0xFF; - List frameVoltages = new ArrayList<>(frameCells); - // 对端会把单体电压按 frameCellStart/frameCellCount 分帧上送;这里只解析当前帧片段, - // 完整电压数组由查询层按同一 vin+eventTime 合并,避免入站侧持有跨帧状态。 - int safe = Math.min(frameCells, buf.remaining() / 2); - for (int k = 0; k < safe; k++) { - frameVoltages.add((buf.getShort() & 0xFFFF) * 0.001); - } - stacks.add(new InfoBlock.GuangdongFc.Stack.Item( - workState, waterTemp, h2InletP, airInletP, airInletTemp, - maxId, minId, maxV, minV, avgV, - cellCount, frameStart, frameCells, frameVoltages)); - } - return new InfoBlock.GuangdongFc.Stack(stackCount, stacks); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVehicleInfoBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVehicleInfoBlockParser.java deleted file mode 100644 index f2df20b3..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVehicleInfoBlockParser.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.ValueDecoder; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 广东燃料电池规范 §7.2.3.8 表 19:车辆信息(补充)。typeCode {@code 0x34}。 - * - *

    固定 7 字节: - *

    - *   collisionAlarm(1)                0 无 / 1 有
    - *   ambientTempC(2, -40 offset, 1°C)
    - *   ambientPressureKpa(2, -100 offset, 0.1 kPa)
    - *   hydrogenMassKg(2, 0.1 kg)
    - * 
    - */ -public final class GdFcVehicleInfoBlockParser implements InfoBlockParser { - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x34; } - @Override public int fixedLength() { return 7; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - Integer collision = ValueDecoder.u8raw(buf); - int rawTemp = buf.getShort() & 0xFFFF; - // 规范保留值按缺失处理,避免 snapshot 中出现 65494/65495 这种不可读温度。 - Integer tempC = (rawTemp == 0xFFFE || rawTemp == 0xFFFF) ? null : rawTemp - 40; - Double pressure = ValueDecoder.scaledU16WithOffset(buf, 0.1, -100.0); - Double h2Mass = ValueDecoder.scaledU16(buf, 0.1); - return new InfoBlock.GuangdongFc.VehicleInfo(collision, tempC, pressure, h2Mass); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParser.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParser.java deleted file mode 100644 index 16e22ced..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParser.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; - -import java.nio.ByteBuffer; - -/** - * 广东燃料电池 peer 的厂商私有 TLV 块兜底 parser。 - * - *

    同一 peer 在广东规范 v1.0 表 8 列出的 0x30~0x34 / 0x80 之外,**还会下发**一些 - * 在该规范里完全未定义的扩展 typeCode(线上首先观察到 {@code 0x83},未来不排除有 - * 0x84 等)。这些块经验上是 **TLV 格式**: - * - *

    - *   typeCode (1B, 由 BodyParser 外层消费)
    - *   length   (2B WORD, 大端)
    - *   payload  (length 字节)
    - * 
    - * - *

    本 parser 严格按 length 消费字节,贪心读到 buffer 末尾,保证 BodyParser - * 主循环在该块之后仍能继续解析后面的标准/扩展块。 - * - *

    typeCode 通过构造器注入,未来若发现新的 vendor typeCode,只需在 catalog 里多注册 - * 一个 {@code new GdFcVendorTlvBlockParser(0x84)} 即可,无需新写 parser 类。 - * - *

    length 字段宽度为 2 字节是从 {@code 0x83} 实际字节序列推断而来(0x0024=36 与 - * payload 实际长度匹配);如未来对端的某个 typeCode 用 1B length 而非 2B,将出现 - * 字节漂移并被 BodyParser 的 hex dump 立刻暴露,可针对性新增独立 parser。 - */ -public final class GdFcVendorTlvBlockParser implements InfoBlockParser { - - private final int typeCode; - - public GdFcVendorTlvBlockParser(int typeCode) { - if (typeCode < 0 || typeCode > 0xFF) { - throw new IllegalArgumentException("typeCode out of range: " + typeCode); - } - this.typeCode = typeCode; - } - - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return typeCode; } - @Override public int fixedLength() { return -1; } - - @Override - public InfoBlock parse(ByteBuffer buf) { - int len = buf.getShort() & 0xFFFF; - // 防御:若 length 大于剩余可读字节,截短到剩余长度(避免 BufferUnderflowException) - // declaredLength 仍保留原值,后续可以在 API/日志里看出对端长度声明是否异常。 - int safe = Math.min(len, buf.remaining()); - byte[] payload = new byte[safe]; - buf.get(payload); - return new InfoBlock.GuangdongFc.VendorTlv(typeCode, len, payload); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/Gb32960ProfileRegistry.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/Gb32960ProfileRegistry.java deleted file mode 100644 index 881029ef..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/Gb32960ProfileRegistry.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.profile; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * 命名 profile → {@link InfoBlockParserRegistry} 映射。 - * - *

    构造时接收: - *

      - *
    • {@code defaultParsers} —— 标准 GB/T 32960 parser 集合(v2016 + v2025) - *
    • {@code catalog} —— vendor 扩展套件目录 - *
    • {@code enabledExtensionNames} —— 在配置文件里被引用过的 vendor 套件名集合 - *
    - * - * 构造内部为 default profile 和每个 enabled 扩展套件分别构建一个 registry: - * 套件 registry = default parsers + catalog 里该套件的 parsers。运行期通过 - * {@link #forProfile(String)} 按名字查找;查不到或传 null 时返回 default。 - */ -public class Gb32960ProfileRegistry { - - /** 约定的默认 profile 名(不会出现在配置里,仅作为 forProfile(null) 的 fallback 标识)。 */ - public static final String DEFAULT_PROFILE = "__default__"; - - private final InfoBlockParserRegistry defaultRegistry; - private final Map registriesByProfile; - - public Gb32960ProfileRegistry(List defaultParsers, - VendorExtensionCatalog catalog, - Set enabledExtensionNames) { - this.defaultRegistry = new InfoBlockParserRegistry(defaultParsers); - Map map = new HashMap<>(); - for (String name : enabledExtensionNames) { - List vendorParsers = catalog.parsersFor(name); - if (vendorParsers.isEmpty()) { - throw new IllegalStateException( - "vendor extension '" + name + "' referenced in config but not present in catalog. " - + "Available: " + catalog.names()); - } - List combined = new ArrayList<>(defaultParsers.size() + vendorParsers.size()); - combined.addAll(defaultParsers); - // vendor parser 只新增扩展 typeCode,不应覆盖标准 parser;若未来需覆盖,必须显式设计优先级。 - combined.addAll(vendorParsers); - map.put(name, new InfoBlockParserRegistry(combined)); - } - this.registriesByProfile = Map.copyOf(map); - } - - /** 按 profile 名取 registry;null 或未注册的名字返回默认 registry。 */ - public InfoBlockParserRegistry forProfile(String profileName) { - if (profileName == null) return defaultRegistry; - InfoBlockParserRegistry r = registriesByProfile.get(profileName); - return r != null ? r : defaultRegistry; - } - - public InfoBlockParserRegistry defaultRegistry() { - return defaultRegistry; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelector.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelector.java deleted file mode 100644 index 83561ed9..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelector.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.profile; - -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960ParserContext; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 配置驱动的 vendor 扩展选择器。按 {@link Gb32960Properties#getVendorExtensions()} 配置的 - * 顺序自上而下首匹(first-match-wins),命中的 entry 的 {@code name} 即返回值。 - * - *

    命中字段:{@code platformAccount} / {@code vin} / {@code vinPrefix}(任一命中即整条 - * entry 命中)。比较为 case-insensitive。 - * - *

    结果按 {@code (account, vin)} 缓存以避免每帧重复扫描;约定 cache key 不含 version - * 因为版本不参与匹配。Cache 用 {@link Optional} 包裹以同时缓存 "no match"。 - */ -public final class RuleBasedVendorExtensionSelector implements VendorExtensionSelector { - - private final List rules; - private final ConcurrentHashMap> cache = new ConcurrentHashMap<>(); - - public RuleBasedVendorExtensionSelector(List entries, - Set validNames) { - List compiled = new ArrayList<>(entries.size()); - for (Gb32960Properties.VendorExtension e : entries) { - if (e.getName() == null || e.getName().isBlank()) { - throw new IllegalStateException("vendor extension entry missing 'name'"); - } - if (!validNames.contains(e.getName())) { - throw new IllegalStateException( - "vendor extension '" + e.getName() + "' not registered in catalog. " - + "Available: " + validNames); - } - compiled.add(CompiledRule.from(e)); - } - this.rules = List.copyOf(compiled); - } - - @Override - public String select(Gb32960ParserContext ctx) { - if (rules.isEmpty()) return null; - CacheKey key = new CacheKey(normalize(ctx.platformAccount()), normalize(ctx.vin())); - Optional cached = cache.get(key); - if (cached != null) return cached.orElse(null); - // 32960 高频上报通常同一连接/同一 VIN 连续上报,缓存可避免每帧扫描所有规则。 - String hit = scan(key); - cache.put(key, Optional.ofNullable(hit)); - return hit; - } - - private String scan(CacheKey key) { - for (CompiledRule r : rules) { - if (r.matches(key.account, key.vin)) return r.name; - } - return null; - } - - private static String normalize(String s) { - return s == null || s.isEmpty() ? null : s.toUpperCase(Locale.ROOT); - } - - /** 暴露给测试和 actuator 端点(如果以后加)。 */ - public int cacheSize() { return cache.size(); } - - private record CacheKey(String account, String vin) {} - - /** 预编译后的单条规则:所有列表已 normalize 成大写 set。 */ - private static final class CompiledRule { - final String name; - final Set accounts; - final Set vins; - final List vinPrefixes; - - CompiledRule(String name, Set accounts, Set vins, List vinPrefixes) { - this.name = name; - this.accounts = accounts; - this.vins = vins; - this.vinPrefixes = vinPrefixes; - } - - static CompiledRule from(Gb32960Properties.VendorExtension e) { - Gb32960Properties.VendorExtension.Match m = e.getMatch(); - return new CompiledRule( - e.getName(), - Set.copyOf(toUpper(m.getPlatformAccounts())), - Set.copyOf(toUpper(m.getVins())), - List.copyOf(toUpper(m.getVinPrefixes()))); - } - - boolean matches(String account, String vin) { - if (account != null && accounts.contains(account)) return true; - if (vin != null && vins.contains(vin)) return true; - if (vin != null) { - for (String prefix : vinPrefixes) { - if (vin.startsWith(prefix)) return true; - } - } - return false; - } - - private static List toUpper(List list) { - if (list == null || list.isEmpty()) return List.of(); - List out = new ArrayList<>(list.size()); - for (String s : list) { - if (s == null || s.isBlank()) continue; - out.add(s.trim().toUpperCase(Locale.ROOT)); - } - return out; - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionCatalog.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionCatalog.java deleted file mode 100644 index 3d89a9da..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionCatalog.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.profile; - -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * Vendor 扩展套件目录:从套件名映射到该套件包含的 {@link InfoBlockParser} 列表。 - * - *

    Catalog 在启动时由 {@code Gb32960AutoConfiguration} 一次性构建并注入到 - * {@link Gb32960ProfileRegistry},运行期不可变。每个 entry 代表一个**已实现的**厂商扩展 - * 协议(如广东燃料电池汽车示范规范),其 parser 集合会在选中时**追加**到默认标准 parser - * 集合之上,组成完整的 profile registry。 - * - *

    当前内置 key: - *

      - *
    • {@code guangdong-fc} —— 广东燃料电池汽车示范规范 0x30~0x34、0x80 - *
    - */ -public final class VendorExtensionCatalog { - - private final Map> extensions; - - public VendorExtensionCatalog(Map> extensions) { - // 只冻结顶层 map;parser 实例本身按无状态单例使用,不应在运行期修改。 - this.extensions = Map.copyOf(extensions); - } - - /** 已注册的全部 vendor 扩展名集合。 */ - public java.util.Set names() { - return extensions.keySet(); - } - - /** 取指定 vendor 扩展套件的 parser 列表;不存在返回空列表。 */ - public List parsersFor(String name) { - List list = extensions.get(name); - return list == null ? Collections.emptyList() : list; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionSelector.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionSelector.java deleted file mode 100644 index cbfd408d..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/profile/VendorExtensionSelector.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.profile; - -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960ParserContext; - -/** - * 给定上下文返回应用的 vendor 扩展套件名({@code null} 表示走默认 profile)。 - * - *

    实现需要线程安全且 O(1) 期望复杂度(建议内部缓存)。 - * 命中结果会直接影响 0x30+ 等厂商字段是否解析成结构化字段,还是退化为 Raw。 - */ -public interface VendorExtensionSelector { - - /** - * @return 命中的扩展套件名(与 {@code Gb32960ProfileRegistry} 注册名一致), - * 未命中返回 {@code null}。 - */ - String select(Gb32960ParserContext ctx); - - /** 永远返回 null 的实现,用于不启用任何 vendor 扩展的默认场景。 */ - VendorExtensionSelector NONE = ctx -> null; -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java deleted file mode 100644 index 513b3f51..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.config; - -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960PlatformAuthorizer; -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960VinAuthorizer; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960CommandParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.AlarmV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.DriveMotorV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.EngineV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.ExtremeValueV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.FuelCellV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.TemperatureV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VoltageV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.AlarmV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.BatteryTemperatureV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.DriveMotorV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.EngineV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.FuelCellStackV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.FuelCellV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.MinParallelVoltageV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.PositionV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.SuperCapacitorExtremeV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.SuperCapacitorV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.VehicleV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcAirConditionerBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcAuxiliaryBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcDcDcBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcDemoExtensionBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcStackBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcVehicleInfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcVendorTlvBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.profile.Gb32960ProfileRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.profile.RuleBasedVendorExtensionSelector; -import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionCatalog; -import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionSelector; -import com.lingniu.ingest.protocol.gb32960.handler.Gb32960RealtimeHandler; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960AccessService; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960AckService; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ConnectionDiagnostics; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960FrameDiagnostics; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer; -import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -import java.util.List; - -/** - * GB/T 32960.3 协议接入模块的自动装配。 - * - *

    每个 Parser Bean 都会被 {@link InfoBlockParserRegistry} 按 - * {@code (ProtocolVersion, typeCode)} 注册。2016/2025 可同时加载。 - * - *

    只有 {@code lingniu.ingest.gb32960.enabled=true} 时才会装配 32960 协议解析组件。 - * TCP 监听由 {@code lingniu.ingest.gb32960.server.enabled} 单独控制。 - */ -@AutoConfiguration -@EnableConfigurationProperties(Gb32960Properties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.gb32960", name = "enabled", havingValue = "true") -public class Gb32960AutoConfiguration { - - // ================= 2016 版信息体 Parser(com...parser.v2016 包) ================= - - @Bean public InfoBlockParser gb32960V2016Vehicle() { return new VehicleV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016DriveMotor() { return new DriveMotorV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016FuelCell() { return new FuelCellV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016Engine() { return new EngineV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016Position() { return new PositionV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016Extreme() { return new ExtremeValueV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016Alarm() { return new AlarmV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016Voltage() { return new VoltageV2016BlockParser(); } - @Bean public InfoBlockParser gb32960V2016Temperature() { return new TemperatureV2016BlockParser(); } - // 注意:0x30~0x7F 在 GB/T 32960.3-2016 附录 B 表 B.3 是"预留"区段,没有任何字段定义。 - // peer 在 V2016 帧里发 0x30/0x31/0x32 是越界使用,强行用 2025 字段布局解读会得到非物理值。 - // 故 V2016 不注册任何 0x30+ parser;遇到时由 Gb32960BodyParser 走 Raw 兜底并打 WARN。 - - // ================= 2025 版信息体 Parser(com...parser.v2025 包) ================= - - @Bean public InfoBlockParser gb32960V2025Vehicle() { return new VehicleV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025DriveMotor() { return new DriveMotorV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025FuelCell() { return new FuelCellV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025Engine() { return new EngineV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025Position() { return new PositionV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025Alarm() { return new AlarmV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025MinParallelVoltage() { return new MinParallelVoltageV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025BatteryTemp() { return new BatteryTemperatureV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025FuelCellStack() { return new FuelCellStackV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025SuperCap() { return new SuperCapacitorV2025BlockParser(); } - @Bean public InfoBlockParser gb32960V2025SuperCapExtreme() { return new SuperCapacitorExtremeV2025BlockParser(); } - - // ================= Vendor 扩展套件目录 ================= - - /** - * 内置 vendor 扩展套件目录。当前注册: - *

      - *
    • {@code guangdong-fc} —— 广东燃料电池汽车示范规范 0x30~0x34、0x80 - *
    - * 未来新增其他厂商扩展时直接在这里加 entry。 - */ - @Bean - @ConditionalOnMissingBean - public VendorExtensionCatalog gb32960VendorExtensionCatalog() { - return new VendorExtensionCatalog(java.util.Map.of( - "guangdong-fc", List.of( - new GdFcStackBlockParser(), - new GdFcAuxiliaryBlockParser(), - new GdFcDcDcBlockParser(), - new GdFcAirConditionerBlockParser(), - new GdFcVehicleInfoBlockParser(), - new GdFcDemoExtensionBlockParser(), - // 0x83 厂商私有 TLV 兜底(同 peer 在广东规范之外又下发的未公开扩展) - // 这里只注册已经在线上见过的 typeCode;新增 typeCode 应先确认长度字段格式。 - new GdFcVendorTlvBlockParser(0x83)))); - } - - // ================= Core components ================= - - /** - * Profile 注册表:把全部已注入的标准 parser 作为 default profile, - * 同时按配置 {@code lingniu.ingest.gb32960.vendor-extensions} 里被引用的 vendor 套件名 - * 分别构建一份 "default + vendor" 的 registry。 - */ - @Bean - @ConditionalOnMissingBean - public Gb32960ProfileRegistry gb32960ProfileRegistry(List parsers, - VendorExtensionCatalog catalog, - Gb32960Properties props) { - java.util.Set enabled = new java.util.LinkedHashSet<>(); - for (Gb32960Properties.VendorExtension e : props.getVendorExtensions()) { - if (e.getName() != null && !e.getName().isBlank()) enabled.add(e.getName()); - } - // enabled 只来自配置里实际引用过的扩展,避免无关 vendor parser 进入默认解析路径。 - return new Gb32960ProfileRegistry(parsers, catalog, enabled); - } - - /** Vendor 扩展选择器:按配置 list 顺序首匹。 */ - @Bean - @ConditionalOnMissingBean - public VendorExtensionSelector gb32960VendorExtensionSelector(Gb32960Properties props, - VendorExtensionCatalog catalog) { - if (props.getVendorExtensions() == null || props.getVendorExtensions().isEmpty()) { - // 没有 vendor 配置时强制 NONE,0x30+ 在 2016 帧里会 Raw 兜底,防止误套扩展解析。 - return VendorExtensionSelector.NONE; - } - return new RuleBasedVendorExtensionSelector(props.getVendorExtensions(), catalog.names()); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960BodyParser gb32960BodyParser(Gb32960ProfileRegistry profileRegistry, - VendorExtensionSelector selector, - Gb32960Properties props) { - Gb32960BodyParser parser = new Gb32960BodyParser(profileRegistry, selector); - parser.setLenientBlockFailure(props.getParse().isLenientBlockFailure()); - return parser; - } - - @Bean - @ConditionalOnMissingBean - public Gb32960CommandParser gb32960CommandParser() { - return new Gb32960CommandParser(); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960MessageDecoder gb32960MessageDecoder(Gb32960BodyParser bodyParser, - Gb32960CommandParser commandParser) { - return new Gb32960MessageDecoder(bodyParser, commandParser); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960EventMapper gb32960EventMapper() { - return new Gb32960EventMapper(); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960RealtimeHandler gb32960RealtimeHandler(Gb32960EventMapper mapper) { - return new Gb32960RealtimeHandler(mapper); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960VinAuthorizer gb32960VinAuthorizer(Gb32960Properties props) { - return new Gb32960VinAuthorizer(props.getAuth()); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960PlatformAuthorizer gb32960PlatformAuthorizer(Gb32960Properties props) { - return new Gb32960PlatformAuthorizer(props.getAuth().getPlatforms()); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960AccessService gb32960AccessService(Gb32960VinAuthorizer authorizer, - Gb32960PlatformAuthorizer platformAuthorizer) { - return new Gb32960AccessService(authorizer, platformAuthorizer); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960AckService gb32960AckService() { - return new Gb32960AckService(); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960FrameDiagnostics gb32960FrameDiagnostics(Gb32960Properties props) { - return new Gb32960FrameDiagnostics( - props.getDiagnostics().getMaxLoggedFrameKeysPerChannel()); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960ConnectionDiagnostics gb32960ConnectionDiagnostics() { - return new Gb32960ConnectionDiagnostics(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "lingniu.ingest.gb32960.server", name = "enabled", - havingValue = "true", matchIfMissing = true) - public Gb32960NettyServer gb32960NettyServer(Gb32960Properties props, - Gb32960MessageDecoder decoder, - Dispatcher dispatcher, - Gb32960AccessService accessService, - Gb32960AckService ackService, - Gb32960FrameDiagnostics diagnostics, - Gb32960ConnectionDiagnostics connectionDiagnostics) { - return new Gb32960NettyServer( - props.getPort(), - props.getWorkerThreads(), - props.getIdleReadSeconds(), - decoder, - dispatcher, - accessService, - ackService, - diagnostics, - connectionDiagnostics, - props.getTls()); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960Properties.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960Properties.java deleted file mode 100644 index 4ea74189..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960Properties.java +++ /dev/null @@ -1,277 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -@ConfigurationProperties(prefix = "lingniu.ingest.gb32960") -public class Gb32960Properties { - - /** 是否装配 32960 协议解析组件。TCP 监听由 {@link #server} 单独控制。 */ - private boolean enabled = false; - /** 32960 TCP 监听开关。默认跟随协议启用后启动,查询类应用可单独关闭。 */ - private Server server = new Server(); - /** 32960 TCP 监听端口;当前生产验证线使用 32960。 */ - private int port = 9000; - /** Netty worker 线程数;0 表示按 Netty/CPU 默认策略分配。 */ - private int workerThreads = 0; - - /** - * 读空闲告警阈值(秒)。连接在该时长内没有任何上行帧则打一条 WARN 日志,方便排查 - * "对端登入成功但不推数据"之类的静默场景。仅告警不断链;0 或负数禁用。 - */ - private int idleReadSeconds = 60; - - /** VIN 白名单认证。关闭时任何 VIN 都能登入。 */ - private Auth auth = new Auth(); - - /** TLS 双向认证。 */ - private Tls tls = new Tls(); - - /** - * 报文解析行为配置。 - * - *

    默认 {@code lenientBlockFailure=true}:单个信息块解析异常时兜成 - * {@link com.lingniu.ingest.protocol.gb32960.model.InfoBlock.Raw} 继续解析, - * 不再整帧放弃。关闭后恢复旧行为(任意异常直接抛 {@code DecodeException} - * 放弃整帧),仅在灰度回滚时使用。 - */ - private Parse parse = new Parse(); - - /** - * 高频入站诊断配置。用于限制每个连接保存的"首次见到帧形态"去重 key 数量, - * 避免异常设备制造大量不同 Raw 组合时造成 channel attribute 无界增长。 - */ - private Diagnostics diagnostics = new Diagnostics(); - - /** - * 厂商扩展协议(vendor extensions)路由配置。 - * - *

    每个 entry 声明一个**已被实现**的 vendor 扩展套件名({@code name})以及该套件 - * 的命中规则({@link VendorExtension.Match})。命中规则由 - * {@code RuleBasedVendorExtensionSelector} 在收到帧时按上下文(platformAccount / - * vin / vin-prefix)逐条按 list 顺序首匹,命中后用对应套件的 parser 集合替换默认的 - * 标准解析器集合(仍包含 0x01..0x09 标准块)。 - * - *

    套件名必须与 {@code VendorExtensionCatalog} 里注册的 key 一致; - * 当前内置:{@code guangdong-fc}(广东燃料电池汽车示范规范 0x30~0x34 + 0x80)。 - * - *

    未匹配任何规则时回退到默认 profile,仅保留 GB/T 32960 标准块。 - */ - private List vendorExtensions = new ArrayList<>(); - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public Server getServer() { return server; } - public void setServer(Server server) { this.server = server; } - public int getPort() { return port; } - public void setPort(int port) { this.port = port; } - public int getWorkerThreads() { return workerThreads; } - public void setWorkerThreads(int workerThreads) { this.workerThreads = workerThreads; } - public int getIdleReadSeconds() { return idleReadSeconds; } - public void setIdleReadSeconds(int idleReadSeconds) { this.idleReadSeconds = idleReadSeconds; } - public Auth getAuth() { return auth; } - public void setAuth(Auth auth) { this.auth = auth; } - public Tls getTls() { return tls; } - public void setTls(Tls tls) { this.tls = tls; } - public Parse getParse() { return parse; } - public void setParse(Parse parse) { this.parse = parse; } - public Diagnostics getDiagnostics() { return diagnostics; } - public void setDiagnostics(Diagnostics diagnostics) { this.diagnostics = diagnostics; } - public List getVendorExtensions() { return vendorExtensions; } - public void setVendorExtensions(List vendorExtensions) { - this.vendorExtensions = vendorExtensions; - } - - public static class Server { - private boolean enabled = true; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - } - - /** - * VIN 白名单认证。 - * - *

    GB/T 32960 协议没有设备账号密码,身份由报文头的 17 字节 VIN + 数据单元里的 ICCID - * 决定。此处通过内存白名单实现最小可用的接入控制: - *

      - *
    • {@code enabled=false}(默认):接受任意 VIN 接入,兼容未注册的测试终端 - *
    • {@code enabled=true}: - *
        - *
      • 车辆登入 0x01:VIN 不在白名单时返回 {@code 0x04 VIN_NOT_EXIST} 应答,关闭连接 - *
      • 实时上报 0x02 / 补发 0x03 / 登出 0x04 / 心跳 0x07:VIN 不在白名单时丢弃,关闭连接 - *
      - *
    - */ - public static class Auth { - private boolean enabled = false; - /** 是否大小写敏感比较 VIN。GB 16735 要求 VIN 大写,默认大小写不敏感以容错。 */ - private boolean caseSensitive = false; - private List whitelist = new ArrayList<>(); - - /** - * 平台登入 (0x05) 账号列表。GB/T 32960.3 表 29 规定平台登入携带 - * 12B 用户名 + 20B 密码 + 1B 加密规则。此处维护允许登入的账号集合, - * 收到 0x05 时比对 username / password: - *
      - *
    • 匹配且(可选)IP 在 {@code allowedIps} 内 → 返回 {@code SUCCESS} 并产出登入事件 - *
    • 不匹配 → 返回 {@code 0x02 OTHER_ERROR} 并关闭连接 - *
    - * 列表为空时默认放行所有平台登入(兼容旧行为)。 - */ - private List platforms = new ArrayList<>(); - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public boolean isCaseSensitive() { return caseSensitive; } - public void setCaseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; } - public List getWhitelist() { return whitelist; } - public void setWhitelist(List whitelist) { this.whitelist = whitelist; } - public List getPlatforms() { return platforms; } - public void setPlatforms(List platforms) { this.platforms = platforms; } - - /** 转换为规范化 Set 供 O(1) 查找。 */ - public Set normalizedWhitelist() { - Set out = new LinkedHashSet<>(whitelist.size()); - for (String v : whitelist) { - if (v == null || v.isBlank()) continue; - out.add(caseSensitive ? v.trim() : v.trim().toUpperCase()); - } - return out; - } - - /** - * 平台账号定义。对应 GB/T 32960.3 表 29 的字段。 - * - * @param username 12 字节以内的用户名 - * @param password 20 字节以内的密码(明文配置;推荐通过 env var 注入避免入库代码) - * @param allowedIps 可选:允许的客户端 IP 列表,为空不做 IP 限制 - * @param description 可选:平台描述(仅日志用) - */ - public static class Platform { - private String username; - private String password; - private List allowedIps = new ArrayList<>(); - private String description; - - public String getUsername() { return username; } - public void setUsername(String username) { this.username = username; } - public String getPassword() { return password; } - public void setPassword(String password) { this.password = password; } - public List getAllowedIps() { return allowedIps; } - public void setAllowedIps(List allowedIps) { this.allowedIps = allowedIps; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - } - } - - /** - * TLS 双向认证。 - * - *

    启用后 Netty pipeline 前置 {@code SslHandler},服务端加载自身证书+私钥, - * 强制要求客户端提供证书({@code NEED_CLIENT_AUTH})并使用 CA 证书验证。 - */ - public static class Tls { - private boolean enabled = false; - /** 服务端证书 PEM 路径。文件路径或 classpath 资源。 */ - private String certPath; - /** 服务端私钥 PEM 路径(PKCS#8 未加密)。 */ - private String keyPath; - /** 受信任的 CA 证书 PEM 路径。用于验证客户端证书。 */ - private String trustCertPath; - /** 是否要求客户端证书。默认 true(双向);false 则变成单向 TLS。 */ - private boolean requireClientAuth = true; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public String getCertPath() { return certPath; } - public void setCertPath(String certPath) { this.certPath = certPath; } - public String getKeyPath() { return keyPath; } - public void setKeyPath(String keyPath) { this.keyPath = keyPath; } - public String getTrustCertPath() { return trustCertPath; } - public void setTrustCertPath(String trustCertPath) { this.trustCertPath = trustCertPath; } - public boolean isRequireClientAuth() { return requireClientAuth; } - public void setRequireClientAuth(boolean requireClientAuth) { this.requireClientAuth = requireClientAuth; } - } - - /** - * 报文解析容错策略。 - */ - public static class Parse { - /** - * 单块解析异常时是否兜底为 {@link com.lingniu.ingest.protocol.gb32960.model.InfoBlock.Raw} - * 继续解析。默认开启。 - * - *

      - *
    • {@code true}(默认):parser 抛 DecodeException / BufferUnderflowException / - * IndexOutOfBoundsException 时,固定长度块按 fixedLen 截取 Raw 后 continue; - * 变长块或剩余字节不足时,剩余全部兜成 Raw 后 break 循环。 - *
    • {@code false}:保留旧行为,任意异常直接抛出 DecodeException 放弃整帧。 - *
    - */ - private boolean lenientBlockFailure = true; - - public boolean isLenientBlockFailure() { return lenientBlockFailure; } - public void setLenientBlockFailure(boolean lenientBlockFailure) { - this.lenientBlockFailure = lenientBlockFailure; - } - } - - /** - * 入站诊断限流/限内存策略。 - */ - public static class Diagnostics { - /** 每个 TCP channel 最多保存的首次帧诊断 key 数量。 */ - private int maxLoggedFrameKeysPerChannel = 128; - - public int getMaxLoggedFrameKeysPerChannel() { - return maxLoggedFrameKeysPerChannel; - } - - public void setMaxLoggedFrameKeysPerChannel(int maxLoggedFrameKeysPerChannel) { - this.maxLoggedFrameKeysPerChannel = maxLoggedFrameKeysPerChannel; - } - } - - /** - * 单条 vendor 扩展路由配置。{@code name} 必须与 - * {@code VendorExtensionCatalog} 注册的 key 一致。 - */ - public static class VendorExtension { - /** 扩展套件名,必须在 catalog 中已注册(如 {@code guangdong-fc})。 */ - private String name; - /** 命中规则。任一字段命中即视为整条 entry 命中。 */ - private Match match = new Match(); - - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public Match getMatch() { return match; } - public void setMatch(Match match) { this.match = match; } - - /** - * 命中规则。所有字段为 OR 关系:任意一个命中即匹配。 - * 字段内部为 case-insensitive 比较;字段为空 list 视为不参与匹配(不会主动命中)。 - */ - public static class Match { - /** 平台登入用户名精确匹配列表(来自 0x05 PlatformLogin 的 username 字段)。 */ - private List platformAccounts = new ArrayList<>(); - /** VIN 精确匹配列表。 */ - private List vins = new ArrayList<>(); - /** VIN 前缀匹配列表(startsWith)。 */ - private List vinPrefixes = new ArrayList<>(); - - public List getPlatformAccounts() { return platformAccounts; } - public void setPlatformAccounts(List platformAccounts) { - this.platformAccounts = platformAccounts; - } - public List getVins() { return vins; } - public void setVins(List vins) { this.vins = vins; } - public List getVinPrefixes() { return vinPrefixes; } - public void setVinPrefixes(List vinPrefixes) { this.vinPrefixes = vinPrefixes; } - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java deleted file mode 100644 index b0480d15..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.EventEmit; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.annotation.RateLimited; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; - -import java.util.List; - -/** - * GB/T 32960 协议 Handler:Dispatcher 路由到此的所有 0x01~0x07 命令都委托给 - * {@link Gb32960EventMapper} 转成 {@link VehicleEvent}。 - * - *

    这是 GB32960 协议在通用 Dispatcher 管线里的**唯一入口** Bean——移除将导致所有 - * GB32960 帧产出的事件丢失。构造器注入 {@link Gb32960EventMapper},全部业务逻辑限制 - * 在纯函数里,不触碰数据库 / 不做 IO。产出的事件由 Dispatcher 统一投递到 Disruptor; - * 下游 sink 再决定写 raw archive、TDengine 历史库或转发 Kafka。 - * - *

    当前 32960 专用 snapshot 查询不依赖 Realtime/Location 事件表,而是通过 raw archive URI - * 回读原始 .bin 并即时解码合并,避免不同车辆或不同子包之间做大范围 join。 - */ -@ProtocolHandler(protocol = ProtocolId.GB32960) -public class Gb32960RealtimeHandler { - - private final Gb32960EventMapper mapper; - - public Gb32960RealtimeHandler(Gb32960EventMapper mapper) { - this.mapper = mapper; - } - - /** 实时 + 补发上报。 */ - @MessageMapping(command = {0x02, 0x03}, desc = "实时/补发上报") - @RateLimited(perVin = 50) - @EventEmit({VehicleEvent.Realtime.class, VehicleEvent.Location.class}) - public List onReport(Gb32960Message msg) { - // 这里保留 Realtime/Location 事件供实时消费者使用;历史全字段查询走 raw archive 解码。 - return mapper.toEvents(msg); - } - - /** 车辆登入 / 登出 / 心跳:统一映射为 session 事件。 */ - @MessageMapping(command = {0x01, 0x04, 0x07}, desc = "登入/登出/心跳") - @EventEmit({VehicleEvent.Login.class, VehicleEvent.Logout.class, VehicleEvent.Heartbeat.class}) - public List onSession(Gb32960Message msg) { - return mapper.toEvents(msg); - } - - /** - * 平台登入 / 登出(0x05 / 0x06):外部下级平台握手,产出 - * {@link VehicleEvent.Login} / {@link VehicleEvent.Logout}, - * vin 字段形如 {@code platform:},metadata 带 {@code kind=platform}, - * 下游消费者可据此区分车辆会话与平台会话。 - */ - @MessageMapping(command = {0x05, 0x06}, desc = "平台登入/登出") - @EventEmit({VehicleEvent.Login.class, VehicleEvent.Logout.class}) - public List onPlatform(Gb32960Message msg) { - return mapper.toEvents(msg); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessService.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessService.java deleted file mode 100644 index ed9097ea..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessService.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960PlatformAuthorizer; -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960VinAuthorizer; -import io.netty.channel.Channel; -import io.netty.util.AttributeKey; - -import java.net.InetSocketAddress; - -/** - * Access boundary for GB/T 32960 inbound channels. - * - *

    Keeps authentication and channel-scoped platform identity out of the Netty - * handler so the hot path can stay small and testable. - * - *

    平台登入成功后,username 会绑定到 Netty channel attribute。后续实时上报帧没有账号字段, - * 只能通过同一连接上的 attribute 找回平台账号,用于 Hyundai/广东扩展等 vendor profile 选择。 - */ -public final class Gb32960AccessService { - - public static final AttributeKey PLATFORM_ACCOUNT_ATTR = - AttributeKey.valueOf("gb32960.platformAccount"); - - private final Gb32960VinAuthorizer vinAuthorizer; - private final Gb32960PlatformAuthorizer platformAuthorizer; - - public Gb32960AccessService(Gb32960VinAuthorizer vinAuthorizer, - Gb32960PlatformAuthorizer platformAuthorizer) { - this.vinAuthorizer = vinAuthorizer; - this.platformAuthorizer = platformAuthorizer; - } - - public boolean isVinAllowed(String vin) { - return vinAuthorizer.isAllowed(vin); - } - - public boolean isVinWhitelistEnforcing() { - return vinAuthorizer.isEnforcing(); - } - - public int vinWhitelistSize() { - return vinAuthorizer.whitelistSize(); - } - - public boolean isPlatformAuthEnforcing() { - return platformAuthorizer.isEnforcing(); - } - - public int platformPolicySize() { - return platformAuthorizer.size(); - } - - public Gb32960PlatformAuthorizer.Result authenticatePlatformLogin(String username, - String password, - Channel channel) { - // 鉴权策略可以按来源 IP 做限制,因此这里把远端地址从 Netty channel 中抽出。 - return platformAuthorizer.authenticate(username, password, peerIp(channel)); - } - - public void bindPlatformAccount(Channel channel, String username) { - // 只绑定到当前连接,断线重连后必须重新平台登入,避免跨连接复用旧账号。 - channel.attr(PLATFORM_ACCOUNT_ATTR).set(username); - } - - public String platformAccount(Channel channel) { - return channel.hasAttr(PLATFORM_ACCOUNT_ATTR) - ? channel.attr(PLATFORM_ACCOUNT_ATTR).get() - : null; - } - - private static String peerIp(Channel channel) { - if (channel.remoteAddress() instanceof InetSocketAddress i) { - return i.getAddress().getHostAddress(); - } - return null; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckService.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckService.java deleted file mode 100644 index 8ac4918d..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckService.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960FrameEncoder; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; -import java.time.Instant; - -/** - * Builds and writes GB/T 32960 response frames. - * - *

    ACK 需要尽量复用请求帧里的原始 17 字节 VIN。平台登入这类命令可能没有真实 VIN, - * 如果把 trim 后的字符串重新 padding,部分上级平台会认为应答 VIN 与请求不一致。 - */ -public final class Gb32960AckService { - - private static final Logger log = LoggerFactory.getLogger(Gb32960AckService.class); - - public void writeResponse(ChannelHandlerContext ctx, - ProtocolVersion version, - CommandType command, - ResponseFlag responseFlag, - byte[] rawVin, - Instant eventTime, - byte[] data, - String tag) { - writeResponse(ctx.channel(), version, command, responseFlag, rawVin, eventTime, data, tag); - } - - public void writeResponse(Channel channel, - ProtocolVersion version, - CommandType command, - ResponseFlag responseFlag, - byte[] rawVin, - Instant eventTime, - byte[] data, - String tag) { - // rawVin 版本用于严格回显请求帧 VIN 字节,优先给入站 handler 使用。 - byte[] ack = Gb32960FrameEncoder.buildResponse( - version, command, responseFlag, rawVin, eventTime, data); - write(channel, ack, tag); - } - - public void writeAndClose(ChannelHandlerContext ctx, - ProtocolVersion version, - CommandType command, - ResponseFlag responseFlag, - String vin, - Instant eventTime, - byte[] data, - String tag) { - byte[] ack = Gb32960FrameEncoder.buildResponse( - version, command, responseFlag, vin, eventTime, data); - ctx.writeAndFlush(Unpooled.wrappedBuffer(ack)).addListener(f -> ctx.close()); - } - - public void writeAndClose(ChannelHandlerContext ctx, - ProtocolVersion version, - CommandType command, - ResponseFlag responseFlag, - byte[] rawVin, - Instant eventTime, - byte[] data, - String tag) { - byte[] ack = Gb32960FrameEncoder.buildResponse( - version, command, responseFlag, rawVin, eventTime, data); - String hex = hex(ack); - ctx.writeAndFlush(Unpooled.wrappedBuffer(ack)).addListener(f -> { - if (f.isSuccess()) { - log.info("[gb32960] {} flushed peer={} len={} hex={}", - tag, addr(ctx.channel()), ack.length, hex); - } else { - log.warn("[gb32960] {} flush FAILED peer={} len={} hex={}", - tag, addr(ctx.channel()), ack.length, hex, f.cause()); - } - ctx.close(); - }); - } - - private static void write(Channel channel, byte[] ack, String tag) { - String hex = hex(ack); - boolean highFrequency = tag.startsWith("report-ack-") || tag.startsWith("heartbeat-ack"); - channel.writeAndFlush(Unpooled.wrappedBuffer(ack)).addListener(f -> { - if (f.isSuccess()) { - if (highFrequency) { - // 实时上报 ACK 频率高,默认降到 DEBUG,避免生产日志被正常心跳/上报刷满。 - if (log.isDebugEnabled()) { - log.debug("[gb32960] {} flushed peer={} len={} hex={}", - tag, addr(channel), ack.length, hex); - } - } else { - log.info("[gb32960] {} flushed peer={} len={} hex={}", - tag, addr(channel), ack.length, hex); - } - } else { - log.warn("[gb32960] {} flush FAILED peer={} len={} hex={}", - tag, addr(channel), ack.length, hex, f.cause()); - } - }); - } - - private static String hex(byte[] bytes) { - StringBuilder sb = new StringBuilder(bytes.length * 2); - for (byte b : bytes) sb.append(String.format("%02x", b)); - return sb.toString(); - } - - private static String addr(Channel channel) { - if (channel.remoteAddress() instanceof InetSocketAddress i) { - return i.getAddress().getHostAddress() + ":" + i.getPort(); - } - return "unknown"; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java deleted file mode 100644 index 3f28adb9..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java +++ /dev/null @@ -1,510 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960PlatformAuthorizer; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.model.CommandBody; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.timeout.IdleState; -import io.netty.handler.timeout.IdleStateEvent; -import io.netty.util.AttributeKey; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.concurrent.CompletableFuture; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Netty 入站处理器:字节 → Gb32960Message → 认证 → RawFrame → Dispatcher。 - * - *

    这是 32960 生产链路的入口边界。它只做协议层职责:解码、鉴权、应答、组装 - * {@link RawFrame} 并投递 Dispatcher;原始包归档、TDengine 历史索引、Kafka 转发都在 - * 下游 sink 中完成,避免 Netty EventLoop 被存储 IO 阻塞。 - * - *

    认证逻辑: - *

      - *
    • {@link Gb32960VinAuthorizer#isEnforcing()} = false:放行所有 VIN - *
    • {@link Gb32960VinAuthorizer#isEnforcing()} = true: - *
        - *
      • 车辆登入 {@code 0x01}:VIN 在白名单则返回成功应答(保留原帧时间)并派发; - * 不在则返回 {@code 0x04 VIN_NOT_EXIST} 应答并关闭连接 - *
      • 其他命令:VIN 在白名单则派发;不在则 DEBUG 日志后关闭连接 - *
      - *
    - * 注意应答帧由 {@link Gb32960AckService} 构造并写回 Channel。 - */ -public class Gb32960ChannelHandler extends SimpleChannelInboundHandler { - - private static final Logger log = LoggerFactory.getLogger(Gb32960ChannelHandler.class); - - /** - * 0x05 PLATFORM_LOGIN 鉴权通过后写入;后续 0x02/0x03 实时上报帧从 channel attribute 取出 - * 作为 {@code Gb32960ParserContext.platformAccount},供 vendor profile selector 路由。 - * 普通车端直连场景(0x01 VEHICLE_LOGIN)此 attribute 始终为 null。 - */ - public static final AttributeKey PLATFORM_ACCOUNT_ATTR = - Gb32960AccessService.PLATFORM_ACCOUNT_ATTR; - - private static final AttributeKey> DURABLE_ACK_CHAIN_ATTR = - AttributeKey.valueOf("gb32960.durableAckChain"); - - private final Gb32960MessageDecoder decoder; - private final Dispatcher dispatcher; - private final Gb32960AccessService accessService; - private final Gb32960AckService ackService; - private final Gb32960FrameDiagnostics diagnostics; - private final Gb32960ConnectionDiagnostics connectionDiagnostics; - - public Gb32960ChannelHandler(Gb32960MessageDecoder decoder, - Dispatcher dispatcher, - Gb32960AccessService accessService, - Gb32960AckService ackService, - Gb32960FrameDiagnostics diagnostics, - Gb32960ConnectionDiagnostics connectionDiagnostics) { - this.decoder = decoder; - this.dispatcher = dispatcher; - this.accessService = accessService; - this.ackService = ackService; - this.diagnostics = diagnostics; - this.connectionDiagnostics = connectionDiagnostics; - } - - @Override - public void channelActive(ChannelHandlerContext ctx) { - connectionDiagnostics.opened(sessionId(ctx), addr(ctx)); - log.info("[gb32960] connection opened peer={}", addr(ctx)); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - connectionDiagnostics.closed(sessionId(ctx)); - log.info("[gb32960] connection closed peer={}", addr(ctx)); - } - - /** - * 读空闲告警:仅记录日志,不断链。用于排查"对端登入成功后长时间不推数据"场景。 - * 由 pipeline 中的 {@link io.netty.handler.timeout.IdleStateHandler}(可选)触发。 - */ - @Override - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { - if (evt instanceof IdleStateEvent idle && idle.state() == IdleState.READER_IDLE) { - connectionDiagnostics.readIdle(sessionId(ctx)); - log.warn("[gb32960] read idle peer={} noInboundFrameFor>=thresholdSeconds (see idleReadSeconds)", - addr(ctx)); - } - super.userEventTriggered(ctx, evt); - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, byte[] frame) { - Gb32960Message msg; - try { - String platformAccount = accessService.platformAccount(ctx.channel()); - msg = decoder.decode(ByteBuffer.wrap(frame), platformAccount); - } catch (Exception e) { - // 解码失败只丢当前帧,不主动断开连接。真实设备偶发脏字节时,FrameDecoder 会继续寻找下一帧头。 - log.warn("[gb32960] decode failed peer={} len={}", addr(ctx), frame.length, e); - dispatchMalformed(ctx, frame, e.getMessage()); - return; - } - - String vin = msg.header().vin(); - CommandType cmd = msg.header().command(); - connectionDiagnostics.received(sessionId(ctx), cmd, vin); - // 原样回显请求帧的 17 字节 VIN(frame[4..20]),避免平台登入 VIN 全 0 - // 被 vinPadded 重写为空格导致对端判定 VIN 不匹配。 - byte[] rawVin = new byte[17]; - System.arraycopy(frame, 4, rawVin, 0, 17); - - // 平台登入/登出(0x05/0x06)没有 VIN,不进 VIN 白名单校验 - boolean isPlatformCommand = cmd == CommandType.PLATFORM_LOGIN || cmd == CommandType.PLATFORM_LOGOUT; - if (!isPlatformCommand && !accessService.isVinAllowed(vin)) { - handleUnauthorized(ctx, msg); - return; - } - - // 鉴权通过:先完成命令侧状态变更/日志,再统一 dispatch 到通用管线。 - // 需要成功 ACK 的已接收帧,等待配置的 Kafka dispatch 边界完成后再回 ACK。 - // 平台登入鉴权失败会短路 return,不进 dispatch。 - switch (cmd) { - case VEHICLE_LOGIN -> logVehicleLogin(ctx, msg); - case VEHICLE_LOGOUT -> logVehicleLogout(ctx, msg); - case PLATFORM_LOGIN -> { - if (!handlePlatformLogin(ctx, msg, rawVin)) return; - } - case PLATFORM_LOGOUT -> logPlatformLogout(ctx); - case REALTIME_REPORT, RESEND_REPORT, HEARTBEAT -> logReportOrHeartbeat(ctx, msg); - case TIME_CALIBRATION -> { } - default -> logOtherFrame(ctx, msg); - } - - RawFrame rf = rawFrame(ctx, msg, frame); - if (!requiresDurableAck(cmd)) { - dispatcher.dispatch(rf); - return; - } - - enqueueDurableAck(ctx, msg, rawVin, vin, cmd, dispatcher.dispatchAndAwait(rf)); - } - - private void enqueueDurableAck(ChannelHandlerContext ctx, - Gb32960Message msg, - byte[] rawVin, - String vin, - CommandType cmd, - CompletableFuture dispatchFuture) { - CompletableFuture previous = ctx.channel().attr(DURABLE_ACK_CHAIN_ATTR).get(); - if (previous == null) { - previous = CompletableFuture.completedFuture(null); - } - - CompletableFuture next = previous - .handle((ignored, previousFailure) -> null) - .thenCompose(ignored -> dispatchFuture.handle((ok, failure) -> failure)) - .thenCompose(failure -> runOnEventLoop(ctx, - () -> handleDurableAckResult(ctx, msg, rawVin, vin, cmd, failure))); - ctx.channel().attr(DURABLE_ACK_CHAIN_ATTR).set(next); - } - - private CompletableFuture runOnEventLoop(ChannelHandlerContext ctx, Runnable task) { - CompletableFuture done = new CompletableFuture<>(); - Runnable wrapped = () -> { - try { - task.run(); - done.complete(null); - } catch (Throwable t) { - done.completeExceptionally(t); - } - }; - if (ctx.executor().inEventLoop()) { - wrapped.run(); - } else { - ctx.executor().execute(wrapped); - } - return done; - } - - private void handleDurableAckResult(ChannelHandlerContext ctx, - Gb32960Message msg, - byte[] rawVin, - String vin, - CommandType cmd, - Throwable failure) { - if (!ctx.channel().isActive()) { - return; - } - if (failure != null) { - log.warn("[gb32960] dispatch failed before ack peer={} vin={} cmd=0x{}", - addr(ctx), vin, Integer.toHexString(cmd.code()), failure); - ctx.close(); - return; - } - writeAckForCommand(ctx, msg, rawVin); - } - - private RawFrame rawFrame(ChannelHandlerContext ctx, Gb32960Message msg, byte[] frame) { - Map sourceMeta = new HashMap<>(4); - sourceMeta.put("vin", msg.header().vin()); - sourceMeta.put("peer", addr(ctx)); - String platformAccount = accessService.platformAccount(ctx.channel()); - if (platformAccount != null && !platformAccount.isBlank()) { - sourceMeta.put("platformAccount", platformAccount); - } - // RawFrame.rawBytes 是后续 raw archive 和按 rawArchiveUri 回查的源头;不要在这里裁剪或重编码。 - return new RawFrame( - ProtocolId.GB32960, - msg.header().command().code(), - 0, - msg, - frame, - sourceMeta, - Instant.now()); - } - - private void dispatchMalformed(ChannelHandlerContext ctx, byte[] frame, String errorMessage) { - Map sourceMeta = new HashMap<>(4); - sourceMeta.put("vin", "unknown"); - sourceMeta.put("peer", addr(ctx)); - sourceMeta.put("parseError", "true"); - sourceMeta.put("parseErrorMessage", errorMessage == null ? "" : errorMessage); - dispatcher.dispatch(new RawFrame( - ProtocolId.GB32960, - 0, - 0, - frame, - frame == null ? new byte[0] : frame, - sourceMeta, - Instant.now())); - } - - private static boolean requiresDurableAck(CommandType cmd) { - return switch (cmd) { - case VEHICLE_LOGIN, - VEHICLE_LOGOUT, - PLATFORM_LOGIN, - PLATFORM_LOGOUT, - REALTIME_REPORT, - RESEND_REPORT, - HEARTBEAT, - TIME_CALIBRATION -> true; - default -> false; - }; - } - - private void writeAckForCommand(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - switch (msg.header().command()) { - case VEHICLE_LOGIN -> writeVehicleLoginAck(ctx, msg, rawVin); - case VEHICLE_LOGOUT -> writeVehicleLogoutAck(ctx, msg, rawVin); - case PLATFORM_LOGIN -> writePlatformLoginAck(ctx, msg, rawVin); - case PLATFORM_LOGOUT -> writePlatformLogoutAck(ctx, msg, rawVin); - case REALTIME_REPORT, RESEND_REPORT, HEARTBEAT -> writeReportOrHeartbeatAck(ctx, msg, rawVin); - case TIME_CALIBRATION -> writeTimeCalibrationAck(ctx, msg, rawVin); - default -> { } - } - } - - /** 0x01 车辆登入:按 §6.3.2 带原采集时间回成功 ACK + INFO 日志。 */ - private void logVehicleLogin(ChannelHandlerContext ctx, Gb32960Message msg) { - log.info("[gb32960] vehicle login peer={} vin={} protocolVersion={}", - addr(ctx), msg.header().vin(), msg.header().protocolVersion()); - } - - private void writeVehicleLoginAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - ackService.writeResponse( - ctx, - msg.header().protocolVersion(), - CommandType.VEHICLE_LOGIN, - ResponseFlag.SUCCESS, - rawVin, - eventOrNow(msg), - null, - "vehicle-login-ack"); - } - - /** 0x04 车辆登出:INFO 日志 + 带原采集时间回成功 ACK。 */ - private void logVehicleLogout(ChannelHandlerContext ctx, Gb32960Message msg) { - log.info("[gb32960] vehicle logout peer={} vin={}", addr(ctx), msg.header().vin()); - } - - private void writeVehicleLogoutAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - ackService.writeResponse(ctx, - msg.header().protocolVersion(), - CommandType.VEHICLE_LOGOUT, - ResponseFlag.SUCCESS, - rawVin, - eventOrNow(msg), - null, - "vehicle-logout-ack"); - } - - /** - * 0x05 平台登入(表 29):含 12B username + 20B password + 加密规则。 - * - *
      - *
    • 消息体解析缺失:关闭连接,返回 false 短路 - *
    • 鉴权失败:写 {@code 0x02 OTHER_ERROR} NACK + 关闭连接,返回 false 短路 - *
    • 鉴权通过:把 username 钉到 channel attribute(供后续 vendor profile 路由),INFO 日志, - * 返回 true 继续 dispatch;成功 ACK 等 dispatch 持久化边界完成后再写 - *
    - * - * @return true 表示鉴权通过应继续 dispatch;false 表示调用方应 short-circuit return - */ - private boolean handlePlatformLogin(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - if (!(msg.commandBody() instanceof CommandBody.PlatformLogin pl)) { - log.warn("[gb32960] platform login peer={} body unparsed, closing", addr(ctx)); - ctx.close(); - return false; - } - Gb32960PlatformAuthorizer.Result result = accessService.authenticatePlatformLogin( - pl.username(), pl.password(), ctx.channel()); - if (!result.accepted()) { - log.warn("[gb32960] platform login REJECTED peer={} username={} reason={}", - addr(ctx), pl.username(), result); - ackService.writeAndClose( - ctx, - msg.header().protocolVersion(), - CommandType.PLATFORM_LOGIN, - ResponseFlag.OTHER_ERROR, - rawVin, - eventOrNow(msg), - null, - "platform-login-nack"); - return false; - } - log.info("[gb32960] platform login peer={} username={} encryptRule={} serial={} time={} policy={}", - addr(ctx), pl.username(), pl.encryptRule(), pl.serialNo(), pl.loginTime(), result); - // 把 username 钉到 channel attribute,供后续 0x02/0x03 帧的 vendor profile 路由 - accessService.bindPlatformAccount(ctx.channel(), pl.username()); - connectionDiagnostics.platformLoginAccepted(sessionId(ctx), pl.username()); - return true; - } - - private void writePlatformLoginAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - ackService.writeResponse( - ctx, - msg.header().protocolVersion(), - CommandType.PLATFORM_LOGIN, - ResponseFlag.SUCCESS, - rawVin, - eventOrNow(msg), - null, - "platform-login-ack"); - } - - /** 0x06 平台登出:INFO 日志 + 带原采集时间回成功 ACK。 */ - private void logPlatformLogout(ChannelHandlerContext ctx) { - log.info("[gb32960] platform logout peer={}", addr(ctx)); - } - - private void writePlatformLogoutAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - ackService.writeResponse(ctx, - msg.header().protocolVersion(), - CommandType.PLATFORM_LOGOUT, - ResponseFlag.SUCCESS, - rawVin, - eventOrNow(msg), - null, - "platform-logout-ack"); - } - - /** - * 0x02/0x03/0x07 实时 / 补发 / 心跳:记录帧诊断;成功 ACK 在配置的 Kafka dispatch 边界后写回。 - * 部分车端实现严格按规范,没收到应答会停止后续上报或重连。ack-tag 按 cmd.code 区分便于日志筛选。 - * - *

    注意:应答成功表示当前配置的 Kafka sink 已完成该帧映射事件的 dispatch 边界。 - * 生产排查历史数据问题时,应继续检查 raw archive sink、Kafka topic 和 history app 的 - * TDengine raw_frames 写入日志。 - * - *

    日志策略(按 (channel, vin, rawTypeSignature) 去重,避免高频帧刷屏): - *

      - *
    • 0x02 / 0x03 首次见到某 (vin, normal) → INFO 一条,含 peer/vin/platformAccount/version/parsedBlocks。 - *
    • 0x02 / 0x03 首次见到某 (vin, rawTypes=…) → WARN 一条,列出 raw typeCode。提示对端可能没走预期 - * vendor profile(例:peer 是广东燃料电池但 platformAccount 没匹配上 - * {@code vendor-extensions.platform-accounts} → 0x30+ 落 Raw)。 - *
    • 同 (vin, signature) 后续帧 → DEBUG(默认看不到;troubleshoot 时打开 Gb32960ChannelHandler 的 DEBUG)。 - *
    • 0x07 心跳:DEBUG(量大且无信息体)。 - *
    - * 帧内字段全量 JSON 仍保留 DEBUG。 - */ - private void logReportOrHeartbeat(ChannelHandlerContext ctx, Gb32960Message msg) { - CommandType cmd = msg.header().command(); - String platformAccount = accessService.platformAccount(ctx.channel()); - if (cmd == CommandType.HEARTBEAT) { - if (log.isDebugEnabled()) { - log.debug("[gb32960] HEARTBEAT peer={} vin={} platformAccount={}", - addr(ctx), msg.header().vin(), platformAccount); - } - return; - } - List rawTypes = Gb32960FrameDiagnostics.collectRawTypeHex(msg.infoBlocks()); - int parsedBlocks = msg.infoBlocks().size() - rawTypes.size(); - String vin = msg.header().vin(); - boolean firstSeen = diagnostics.markFirstSeen(ctx.channel(), vin, rawTypes); - if (firstSeen) { - if (rawTypes.isEmpty()) { - log.info("[gb32960] {} (first frame for vin) peer={} vin={} platformAccount={} version={} parsedBlocks={}", - cmd.name(), addr(ctx), vin, platformAccount, - msg.header().protocolVersion(), parsedBlocks); - } else { - log.warn("[gb32960] {} peer={} vin={} platformAccount={} version={} parsedBlocks={} rawTypes={} — vendor profile likely not selected for this peer", - cmd.name(), addr(ctx), vin, platformAccount, - msg.header().protocolVersion(), parsedBlocks, rawTypes); - } - } - if (log.isDebugEnabled()) { - log.debug("[gb32960] frame peer={} vin={} cmd=0x{} parsedBlocks={} rawTypes={} json={}", - addr(ctx), vin, Integer.toHexString(cmd.code()), - parsedBlocks, rawTypes, Gb32960FrameDiagnostics.toJson(msg)); - } - } - - private void writeReportOrHeartbeatAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - CommandType cmd = msg.header().command(); - ackService.writeResponse(ctx, - msg.header().protocolVersion(), - cmd, - ResponseFlag.SUCCESS, - rawVin, - eventOrNow(msg), - null, - "report-ack-0x" + Integer.toHexString(cmd.code())); - } - - /** 0x08 终端校时:平台应答 data 段为平台当前时间 6B(用 Instant.now())。 */ - private void writeTimeCalibrationAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) { - ackService.writeResponse(ctx, - msg.header().protocolVersion(), - CommandType.TIME_CALIBRATION, - ResponseFlag.SUCCESS, - rawVin, - Instant.now(), - null, - "time-calibration-ack"); - } - - /** 其它未单独处理的命令(如激活 0x09、密钥交换 0x0A):仅 DEBUG 日志,仍会进 dispatcher。 */ - private void logOtherFrame(ChannelHandlerContext ctx, Gb32960Message msg) { - if (log.isDebugEnabled()) { - log.debug("[gb32960] frame peer={} vin={} cmd=0x{} infoBlocks={} json={}", - addr(ctx), msg.header().vin(), Integer.toHexString(msg.header().command().code()), - msg.infoBlocks().size(), Gb32960FrameDiagnostics.toJson(msg)); - } - } - - /** 优先使用帧内采集时间;为 null 时回落到 {@link Instant#now()}。 */ - private static Instant eventOrNow(Gb32960Message msg) { - return msg.header().eventTime() != null ? msg.header().eventTime() : Instant.now(); - } - - /** VIN 未授权:对登入命令下发 VIN_NOT_EXIST 应答;其他命令静默关闭。 */ - private void handleUnauthorized(ChannelHandlerContext ctx, Gb32960Message msg) { - String vin = msg.header().vin(); - CommandType cmd = msg.header().command(); - if (cmd == CommandType.VEHICLE_LOGIN) { - // 规范要求登入失败要回 ACK,让终端能明确知道 VIN 不存在,而不是当作网络超时重试。 - ackService.writeAndClose( - ctx, - msg.header().protocolVersion(), - CommandType.VEHICLE_LOGIN, - ResponseFlag.VIN_NOT_EXIST, - vin, - msg.header().eventTime() != null ? msg.header().eventTime() : Instant.now(), - null, - "vehicle-login-nack"); - log.info("[gb32960] reject login peer={} vin={} (not in whitelist)", addr(ctx), vin); - } else { - log.debug("[gb32960] drop frame peer={} vin={} cmd=0x{} (not in whitelist)", - addr(ctx), vin, Integer.toHexString(cmd.code())); - ctx.close(); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - log.warn("[gb32960] channel error peer={}", addr(ctx), cause); - ctx.close(); - } - - private static String addr(ChannelHandlerContext ctx) { - if (ctx.channel().remoteAddress() instanceof InetSocketAddress i) { - return i.getAddress().getHostAddress() + ":" + i.getPort(); - } - return "unknown"; - } - - private static String sessionId(ChannelHandlerContext ctx) { - return ctx.channel().id().asLongText(); - } - -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnostics.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnostics.java deleted file mode 100644 index a7be8bd5..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnostics.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.protocol.gb32960.model.CommandType; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -public class Gb32960ConnectionDiagnostics { - - private final Clock clock; - private final ConcurrentMap activeSessions = new ConcurrentHashMap<>(); - - public Gb32960ConnectionDiagnostics() { - this(Clock.systemUTC()); - } - - Gb32960ConnectionDiagnostics(Clock clock) { - this.clock = clock; - } - - public void opened(String sessionId, String peer) { - Instant now = clock.instant(); - activeSessions.put(sessionId, new MutableSession(sessionId, peer, now)); - } - - public void received(String sessionId, CommandType command, String vin) { - activeSessions.computeIfPresent(sessionId, (ignored, session) -> { - session.lastInboundAt = clock.instant(); - session.lastCommandCode = command.code(); - session.lastCommand = command.name(); - session.vin = vin == null ? "" : vin; - return session; - }); - } - - public void platformLoginAccepted(String sessionId, String platformAccount) { - activeSessions.computeIfPresent(sessionId, (ignored, session) -> { - session.platformAccount = platformAccount == null ? "" : platformAccount; - return session; - }); - } - - public void readIdle(String sessionId) { - activeSessions.computeIfPresent(sessionId, (ignored, session) -> { - session.readIdleCount++; - return session; - }); - } - - public void closed(String sessionId) { - activeSessions.remove(sessionId); - } - - public List activeSessions() { - Instant now = clock.instant(); - return activeSessions.values().stream() - .map(session -> session.snapshot(now)) - .sorted(Comparator.comparing(SessionSnapshot::connectedAt)) - .toList(); - } - - public record SessionSnapshot( - String sessionId, - String peer, - Instant connectedAt, - Instant lastInboundAt, - long idleSeconds, - int lastCommandCode, - String lastCommand, - String vin, - String platformAccount, - long readIdleCount) { - } - - private static final class MutableSession { - private final String sessionId; - private final String peer; - private final Instant connectedAt; - private Instant lastInboundAt; - private int lastCommandCode; - private String lastCommand = ""; - private String vin = ""; - private String platformAccount = ""; - private long readIdleCount; - - private MutableSession(String sessionId, String peer, Instant connectedAt) { - this.sessionId = sessionId; - this.peer = peer; - this.connectedAt = connectedAt; - this.lastInboundAt = connectedAt; - } - - private SessionSnapshot snapshot(Instant now) { - long idleSeconds = Math.max(0, Duration.between(lastInboundAt, now).toSeconds()); - return new SessionSnapshot( - sessionId, - peer, - connectedAt, - lastInboundAt, - idleSeconds, - lastCommandCode, - lastCommand, - vin, - platformAccount, - readIdleCount); - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnostics.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnostics.java deleted file mode 100644 index d4d67363..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnostics.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import io.netty.channel.Channel; -import io.netty.util.AttributeKey; - -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Channel-scoped diagnostics for high-frequency GB/T 32960 reports. - * - *

    每个 TCP channel 只记录有限个首次出现的 (vin, rawTypeSignature),用于发现厂商 profile - * 没命中、字段解析退化为 Raw 的问题。它不参与业务判断,也不会影响 raw archive 和历史查询。 - */ -public final class Gb32960FrameDiagnostics { - - private static final AttributeKey> LOGGED_FRAME_KEYS_ATTR = - AttributeKey.valueOf("gb32960.loggedFrameKeys"); - - private static final ObjectMapper DEBUG_JSON = new ObjectMapper() - .registerModule(new JavaTimeModule()) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - - private final int maxKeysPerChannel; - - public Gb32960FrameDiagnostics() { - this(128); - } - - public Gb32960FrameDiagnostics(int maxKeysPerChannel) { - this.maxKeysPerChannel = Math.max(1, maxKeysPerChannel); - } - - public boolean markFirstSeen(Channel channel, String vin, List rawTypes) { - String signature = rawTypes == null || rawTypes.isEmpty() ? "NORMAL" : "RAW:" + rawTypes; - return markFirstSeenKey(channel, vin + "|" + signature); - } - - public int seenKeyCount(Channel channel) { - Set seen = channel.attr(LOGGED_FRAME_KEYS_ATTR).get(); - return seen == null ? 0 : seen.size(); - } - - public static List collectRawTypeHex(List blocks) { - List raws = new ArrayList<>(0); - for (InfoBlock b : blocks) { - if (b instanceof InfoBlock.Raw r) { - raws.add(String.format("0x%02x", r.typeCode())); - } - } - return raws; - } - - public static String toJson(Gb32960Message msg) { - try { - return DEBUG_JSON.writeValueAsString(msg); - } catch (Exception e) { - return ""; - } - } - - private boolean markFirstSeenKey(Channel channel, String key) { - Set seen = channel.attr(LOGGED_FRAME_KEYS_ATTR).get(); - if (seen == null) { - seen = new LinkedHashSet<>(); - channel.attr(LOGGED_FRAME_KEYS_ATTR).set(seen); - } - boolean added = seen.add(key); - if (added) trimToLimit(seen); - return added; - } - - private void trimToLimit(Set seen) { - while (seen.size() > maxKeysPerChannel) { - // LinkedHashSet 保持插入顺序,超限时淘汰最早的签名,避免异常设备导致 attribute 无界增长。 - String first = seen.iterator().next(); - seen.remove(first); - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960NettyServer.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960NettyServer.java deleted file mode 100644 index 49e67501..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960NettyServer.java +++ /dev/null @@ -1,181 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960FrameDecoder; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.ssl.ClientAuth; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.timeout.IdleStateHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; - -import java.io.File; -import java.io.InputStream; -import java.net.InetSocketAddress; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.concurrent.ThreadFactory; - -/** - * GB/T 32960 Netty Server 启动器。 - * - *

    EventLoop 只做解码与投递 {@link Dispatcher},业务处理由 Disruptor 侧的虚拟线程承担。 - * 端口是否启动由 {@code lingniu.ingest.gb32960.enabled} 控制,生产 32960 线应确认该开关、 - * 端口、防火墙和上游平台账号同时匹配。 - * - *

    支持可选 TLS:{@link Gb32960Properties.Tls#isEnabled()} = true 时,pipeline 前置 - * {@code SslHandler},加载服务端证书 + 私钥 + 受信 CA,默认要求客户端证书(双向 TLS)。 - */ -public class Gb32960NettyServer implements InitializingBean, DisposableBean { - - private static final Logger log = LoggerFactory.getLogger(Gb32960NettyServer.class); - - private final int port; - private final int workerThreads; - private final int idleReadSeconds; - private final Gb32960MessageDecoder messageDecoder; - private final Dispatcher dispatcher; - private final Gb32960AccessService accessService; - private final Gb32960AckService ackService; - private final Gb32960FrameDiagnostics diagnostics; - private final Gb32960ConnectionDiagnostics connectionDiagnostics; - private final Gb32960Properties.Tls tlsConfig; - - private EventLoopGroup boss; - private EventLoopGroup workers; - private Channel serverChannel; - private SslContext sslContext; - - public Gb32960NettyServer(int port, int workerThreads, int idleReadSeconds, - Gb32960MessageDecoder messageDecoder, - Dispatcher dispatcher, - Gb32960AccessService accessService, - Gb32960AckService ackService, - Gb32960FrameDiagnostics diagnostics, - Gb32960ConnectionDiagnostics connectionDiagnostics, - Gb32960Properties.Tls tlsConfig) { - this.port = port; - this.workerThreads = workerThreads; - this.idleReadSeconds = idleReadSeconds; - this.messageDecoder = messageDecoder; - this.dispatcher = dispatcher; - this.accessService = accessService; - this.ackService = ackService; - this.diagnostics = diagnostics; - this.connectionDiagnostics = connectionDiagnostics; - this.tlsConfig = tlsConfig; - } - - @Override - public void afterPropertiesSet() throws Exception { - if (tlsConfig != null && tlsConfig.isEnabled()) { - this.sslContext = buildSslContext(tlsConfig); - log.info("[gb32960] TLS enabled cert={} clientAuth={}", - tlsConfig.getCertPath(), - tlsConfig.isRequireClientAuth() ? "REQUIRED" : "OPTIONAL"); - } - - ThreadFactory bossTf = runnable -> new Thread(runnable, "gb32960-boss"); - ThreadFactory wkTf = runnable -> new Thread(runnable, "gb32960-worker"); - this.boss = new NioEventLoopGroup(1, bossTf); - this.workers = new NioEventLoopGroup(workerThreads == 0 - ? Runtime.getRuntime().availableProcessors() * 2 : workerThreads, wkTf); - - ServerBootstrap b = new ServerBootstrap(); - b.group(boss, workers) - .channel(NioServerSocketChannel.class) - .option(ChannelOption.SO_BACKLOG, 1024) - .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.SO_KEEPALIVE, true) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) { - // Pipeline 顺序不能调换:TLS 先解密,FrameDecoder 再做拆包,ChannelHandler 最后做业务应答和派发。 - if (sslContext != null) { - ch.pipeline().addLast("ssl", sslContext.newHandler(ch.alloc())); - } - if (idleReadSeconds > 0) { - // 只监听读空闲:readerIdle=N,writerIdle=0,allIdle=0。 - // 触发后由 Gb32960ChannelHandler#userEventTriggered 仅打告警日志,不断链。 - ch.pipeline().addLast("idle", - new IdleStateHandler(idleReadSeconds, 0, 0)); - } - ch.pipeline() - .addLast("frame", new Gb32960FrameDecoder()) - .addLast("dispatch", - new Gb32960ChannelHandler(messageDecoder, dispatcher, - accessService, ackService, diagnostics, - connectionDiagnostics)); - } - }); - this.serverChannel = b.bind(port).sync().channel(); - log.info("[gb32960] Netty server listening on :{} (tls={}, vinWhitelist={}, platformAuth={}, idleReadSeconds={})", - getBoundPort(), - sslContext != null, - accessService.isVinWhitelistEnforcing() ? accessService.vinWhitelistSize() : "DISABLED", - accessService.isPlatformAuthEnforcing() ? accessService.platformPolicySize() : "DISABLED", - idleReadSeconds > 0 ? idleReadSeconds : "DISABLED"); - } - - /** 实际绑定端口(支持 port=0 随机分配后查询)。 */ - public int getBoundPort() { - if (serverChannel != null && serverChannel.localAddress() instanceof InetSocketAddress a) { - return a.getPort(); - } - return port; - } - - private static SslContext buildSslContext(Gb32960Properties.Tls cfg) throws Exception { - if (cfg.getCertPath() == null || cfg.getKeyPath() == null) { - throw new IllegalStateException("TLS enabled but cert-path/key-path not configured"); - } - SslContextBuilder builder = SslContextBuilder.forServer( - openPem(cfg.getCertPath()), - openPem(cfg.getKeyPath())); - if (cfg.getTrustCertPath() != null && !cfg.getTrustCertPath().isBlank()) { - try (InputStream trust = openPem(cfg.getTrustCertPath())) { - builder.trustManager(trust); - } - } - builder.clientAuth(cfg.isRequireClientAuth() ? ClientAuth.REQUIRE : ClientAuth.OPTIONAL); - return builder.build(); - } - - /** 读取 PEM 文件,支持 file:// 绝对路径或相对路径。 */ - private static InputStream openPem(String path) throws Exception { - String stripped = path.startsWith("file://") ? path.substring("file://".length()) : path; - File f = new File(stripped); - if (!f.isAbsolute()) { - f = Paths.get(System.getProperty("user.dir"), stripped).toFile(); - } - if (!f.exists()) { - throw new IllegalStateException("TLS PEM not found: " + f.getAbsolutePath()); - } - return Files.newInputStream(f.toPath()); - } - - @Override - public void destroy() { - try { - if (serverChannel != null) serverChannel.close().sync(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - if (boss != null) boss.shutdownGracefully(); - if (workers != null) workers.shutdownGracefully(); - log.info("[gb32960] Netty server stopped"); - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java deleted file mode 100644 index f0ea087f..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapper.java +++ /dev/null @@ -1,344 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.mapper; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.AlarmPayload; -import com.lingniu.ingest.api.event.LocationPayload; -import com.lingniu.ingest.api.event.RealtimePayload; -import com.lingniu.ingest.api.event.TelemetryMetadataValues; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.spi.EventMapper; -import com.lingniu.ingest.protocol.gb32960.model.CommandBody; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.GeneralAlarmFlagBit; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Collectors; - -/** - * 32960 → 领域事件 Adapter。 - * - *

    实时/补发上报产出 {@code Realtime + Location}(可选)+ {@code Alarm}(可选); - * 车辆登入/登出/心跳产出对应的会话事件。 - * - *

    该 Mapper 只生成统一领域事件,字段是面向实时总线的精简视图。32960 的完整包字段、 - * 厂商扩展字段和 snapshot 合并字段由历史查询服务从 raw archive 重新解码,不在这里展开。 - */ -public final class Gb32960EventMapper implements EventMapper { - - @Override - public List toEvents(Gb32960Message message) { - var header = message.header(); - Instant eventTime = header.eventTime() != null ? header.eventTime() : Instant.now(); - Instant ingestTime = Instant.now(); - - List out = new ArrayList<>(3); - Map baseMeta = Map.of( - "command", header.command().name(), - "protocolVersion", header.protocolVersion().name()); - - CommandType cmd = header.command(); - if (cmd == CommandType.REALTIME_REPORT || cmd == CommandType.RESEND_REPORT) { - // 补发包与实时包进入同一套实时事件模型,差异保留在 metadata.command 里供下游判断。 - buildRealtime(message, header, baseMeta, eventTime, ingestTime, out); - return out; - } - if (cmd == CommandType.VEHICLE_LOGIN) { - String iccid = ""; - if (message.commandBody() instanceof CommandBody.VehicleLogin vl) iccid = vl.iccid(); - out.add(new VehicleEvent.Login( - UUID.randomUUID().toString(), - header.vin(), ProtocolId.GB32960, eventTime, ingestTime, - null, baseMeta, iccid, header.protocolVersion().name())); - } else if (cmd == CommandType.VEHICLE_LOGOUT) { - out.add(new VehicleEvent.Logout( - UUID.randomUUID().toString(), - header.vin(), ProtocolId.GB32960, eventTime, ingestTime, - null, baseMeta)); - } else if (cmd == CommandType.HEARTBEAT) { - out.add(new VehicleEvent.Heartbeat( - UUID.randomUUID().toString(), - header.vin(), ProtocolId.GB32960, eventTime, ingestTime, - null, baseMeta)); - } else if (cmd == CommandType.PLATFORM_LOGIN) { - // 平台登入 0x05:映射为 VehicleEvent.Login + metadata.kind=platform。 - // 使用 "platform:" + username 作为 vin 字段,既能送到 vehicle.session topic - // 下游消费者又能靠 metadata.kind 区分车辆/平台登入。 - if (message.commandBody() instanceof CommandBody.PlatformLogin pl) { - Map meta = new java.util.HashMap<>(baseMeta); - meta.put("kind", "platform"); - meta.put("username", pl.username() == null ? "" : pl.username()); - meta.put("encryptRule", pl.encryptRule() == null ? "" : pl.encryptRule().name()); - meta.put("serial", String.valueOf(pl.serialNo())); - out.add(new VehicleEvent.Login( - UUID.randomUUID().toString(), - "platform:" + (pl.username() == null ? "" : pl.username()), - ProtocolId.GB32960, - pl.loginTime() != null ? pl.loginTime() : eventTime, - ingestTime, - null, meta, "", header.protocolVersion().name())); - } - } else if (cmd == CommandType.PLATFORM_LOGOUT) { - if (message.commandBody() instanceof CommandBody.PlatformLogout plo) { - Map meta = new java.util.HashMap<>(baseMeta); - meta.put("kind", "platform"); - meta.put("serial", String.valueOf(plo.serialNo())); - out.add(new VehicleEvent.Logout( - UUID.randomUUID().toString(), - "platform:unknown", - ProtocolId.GB32960, - plo.logoutTime() != null ? plo.logoutTime() : eventTime, - ingestTime, - null, meta)); - } - } - return out; - } - - private void buildRealtime(Gb32960Message message, - com.lingniu.ingest.protocol.gb32960.model.Gb32960Header header, - Map meta, - Instant eventTime, Instant ingestTime, - List out) { - // 整车数据:先查 V2016,再 V2025;每帧只会命中其一,extracted 出来变成统一 - // 视图(VehicleView)供后面 payload 构造使用 - VehicleView v = extractVehicleView(message); - // 位置数据:同样两版本都查 - PositionView p = extractPositionView(message); - // 燃料电池:V2016 字段更全(带电压/电流),V2025 字段更少 - InfoBlock.Gb32960V2016.FuelCell fc16 = - message.findBlock(InfoBlock.Gb32960V2016.FuelCell.class).orElse(null); - InfoBlock.Gb32960V2025.FuelCell fc25 = - message.findBlock(InfoBlock.Gb32960V2025.FuelCell.class).orElse(null); - // 报警 - AlarmView alarm = extractAlarmView(message); - - if (v != null) { - Double fcVoltageV = fc16 != null ? fc16.fcVoltageV() : null; - Double fcCurrentA = fc16 != null ? fc16.fcCurrentA() : null; - Double fcTempC = fc16 != null ? fc16.maxHydrogenSystemTempC() - : (fc25 != null ? fc25.maxHydrogenSystemTempC() : null); - Double hydrogenHighPressure = fc16 != null ? fc16.maxHydrogenPressureMpa() - : (fc25 != null ? fc25.maxHydrogenPressureMpa() : null); - Double hydrogenRemaining = fc25 != null && fc25.remainingHydrogenPercent() != null - ? fc25.remainingHydrogenPercent().doubleValue() : null; - - RealtimePayload payload = new RealtimePayload( - v.speedKmh, - v.totalMileageKm, - v.socPercent != null ? v.socPercent.doubleValue() : null, - v.totalVoltageV, - v.totalCurrentA, - fcVoltageV, - fcCurrentA, - fcTempC, - hydrogenRemaining, - hydrogenHighPressure, - null, - mapVehicleState(v.vehicleState), - mapChargingState(v.chargingState), - mapRunningMode(v.runningMode), - extractGearLevel(v.gearRaw), - v.acceleratorPedal != null ? v.acceleratorPedal.doubleValue() : null, - v.brakePedal != null ? v.brakePedal.doubleValue() : null, - p != null ? p.longitude : null, - p != null ? p.latitude : null, - null, - null, - null, - null); - out.add(new VehicleEvent.Realtime( - UUID.randomUUID().toString(), - header.vin(), ProtocolId.GB32960, eventTime, ingestTime, - null, meta, payload)); - } - if (p != null) { - // 位置事件只从 POSITION 子包生成。没有 POSITION 时不从整车数据推断经纬度,避免制造假定位。 - LocationPayload loc = new LocationPayload( - p.longitude, p.latitude, 0.0, - v != null && v.speedKmh != null ? v.speedKmh : 0.0, - 0.0, 0, p.statusFlag); - Map locationMeta = v != null && v.totalMileageKm != null - ? withMeta(meta, "total_mileage_km", TelemetryMetadataValues.doubleValue(v.totalMileageKm)) - : meta; - out.add(new VehicleEvent.Location( - UUID.randomUUID().toString(), - header.vin(), ProtocolId.GB32960, eventTime, ingestTime, - null, locationMeta, loc)); - } - if (alarm != null && shouldEmitAlarm(alarm)) { - // 只在存在告警等级或氢安全关键位时发 Alarm,减少正常高频帧对告警消费者的噪声。 - List faultCodes = new ArrayList<>(); - addFaultCodes(faultCodes, "BAT", alarm.batteryFaults); - addFaultCodes(faultCodes, "MOT", alarm.motorFaults); - addFaultCodes(faultCodes, "ENG", alarm.engineFaults); - addFaultCodes(faultCodes, "OTH", alarm.otherFaults); - Set activeBitEnums = GeneralAlarmFlagBit.parse(alarm.generalAlarmFlag); - Set activeBits = activeBitEnums.stream() - .map(Enum::name) - .collect(Collectors.toCollection(java.util.LinkedHashSet::new)); - boolean hydrogenLeakDetected = activeBitEnums.contains(GeneralAlarmFlagBit.HYDROGEN_LEAK); - AlarmPayload ap = new AlarmPayload( - mapAlarmLevel(alarm.maxLevel, hydrogenLeakDetected), - (int) (alarm.generalAlarmFlag & 0xFFFF), - "GB32960_ALARM", - faultCodes, - activeBits, - p != null ? p.longitude : null, - p != null ? p.latitude : null, - safetyCategory(activeBitEnums), - hydrogenLeakDetected, - hydrogenLeakDetected - ? AlarmPayload.HydrogenLeakLevel.CRITICAL - : AlarmPayload.HydrogenLeakLevel.NONE, - hydrogenLeakDetected); - out.add(new VehicleEvent.Alarm( - UUID.randomUUID().toString(), - header.vin(), ProtocolId.GB32960, eventTime, ingestTime, - null, meta, ap)); - } - } - - /** 跨版本的整车数据视图,把 V2016 的踏板字段(V2025 没有)放在一起。 */ - private record VehicleView( - Integer vehicleState, Integer chargingState, Integer runningMode, - Double speedKmh, Double totalMileageKm, Double totalVoltageV, Double totalCurrentA, - Integer socPercent, Integer gearRaw, - Integer acceleratorPedal, Integer brakePedal) {} - - private static VehicleView extractVehicleView(Gb32960Message m) { - var v16 = m.findBlock(InfoBlock.Gb32960V2016.Vehicle.class).orElse(null); - if (v16 != null) { - return new VehicleView(v16.vehicleState(), v16.chargingState(), v16.runningMode(), - v16.speedKmh(), v16.totalMileageKm(), v16.totalVoltageV(), v16.totalCurrentA(), - v16.socPercent(), v16.gearRaw(), - v16.acceleratorPedal(), v16.brakePedal()); - } - var v25 = m.findBlock(InfoBlock.Gb32960V2025.Vehicle.class).orElse(null); - if (v25 != null) { - return new VehicleView(v25.vehicleState(), v25.chargingState(), v25.runningMode(), - v25.speedKmh(), v25.totalMileageKm(), v25.totalVoltageV(), v25.totalCurrentA(), - v25.socPercent(), v25.gearRaw(), - null, null); - } - return null; - } - - /** 跨版本的位置数据视图。 */ - private record PositionView(int statusFlag, double longitude, double latitude) {} - - private static PositionView extractPositionView(Gb32960Message m) { - var p16 = m.findBlock(InfoBlock.Gb32960V2016.Position.class).orElse(null); - if (p16 != null) return new PositionView(p16.statusFlag(), p16.longitude(), p16.latitude()); - var p25 = m.findBlock(InfoBlock.Gb32960V2025.Position.class).orElse(null); - if (p25 != null) return new PositionView(p25.statusFlag(), p25.longitude(), p25.latitude()); - return null; - } - - /** 跨版本的报警视图(仅暴露公共字段,2025 的 generalAlarmLevels 暂未上送下游)。 */ - private record AlarmView(Integer maxLevel, long generalAlarmFlag, - List batteryFaults, List motorFaults, - List engineFaults, List otherFaults) {} - - private static AlarmView extractAlarmView(Gb32960Message m) { - var a16 = m.findBlock(InfoBlock.Gb32960V2016.Alarm.class).orElse(null); - if (a16 != null) { - return new AlarmView(a16.maxLevel(), a16.generalAlarmFlag(), - a16.batteryFaults(), a16.motorFaults(), a16.engineFaults(), a16.otherFaults()); - } - var a25 = m.findBlock(InfoBlock.Gb32960V2025.Alarm.class).orElse(null); - if (a25 != null) { - return new AlarmView(a25.maxLevel(), a25.generalAlarmFlag(), - a25.batteryFaults(), a25.motorFaults(), a25.engineFaults(), a25.otherFaults()); - } - return null; - } - - /** 挡位字节按附录 A.1 解析:低 4 位为挡位值 0000-1111。 */ - private static Integer extractGearLevel(Integer gearRaw) { - if (gearRaw == null) return null; - if ((gearRaw & 0x80) != 0) return null; // bit7: 挡位无效 - return gearRaw & 0x0F; - } - - private static RealtimePayload.VehicleState mapVehicleState(Integer code) { - if (code == null) return null; - return switch (code) { - case 0x01 -> RealtimePayload.VehicleState.STARTED; - case 0x02 -> RealtimePayload.VehicleState.SHUTDOWN; - case 0x03 -> RealtimePayload.VehicleState.OTHER; - default -> RealtimePayload.VehicleState.INVALID; - }; - } - - private static RealtimePayload.ChargingState mapChargingState(Integer code) { - if (code == null) return null; - return switch (code) { - case 0x01 -> RealtimePayload.ChargingState.PARKED_CHARGING; - case 0x02 -> RealtimePayload.ChargingState.DRIVING_CHARGING; - case 0x03 -> RealtimePayload.ChargingState.UNCHARGED; - case 0x04 -> RealtimePayload.ChargingState.CHARGED; - default -> RealtimePayload.ChargingState.INVALID; - }; - } - - private static RealtimePayload.RunningMode mapRunningMode(Integer code) { - if (code == null) return null; - return switch (code) { - case 0x01 -> RealtimePayload.RunningMode.ELECTRIC; - case 0x02 -> RealtimePayload.RunningMode.HYBRID; - case 0x03 -> RealtimePayload.RunningMode.FUEL; - default -> RealtimePayload.RunningMode.INVALID; - }; - } - - private static boolean shouldEmitAlarm(AlarmView alarm) { - if (alarm.maxLevel != null && alarm.maxLevel > 0) return true; - return GeneralAlarmFlagBit.HYDROGEN_LEAK.isSet(alarm.generalAlarmFlag) - || GeneralAlarmFlagBit.HYDROGEN_PRESSURE_ABNORMAL.isSet(alarm.generalAlarmFlag) - || GeneralAlarmFlagBit.HYDROGEN_TEMP_ABNORMAL.isSet(alarm.generalAlarmFlag); - } - - private static AlarmPayload.AlarmLevel mapAlarmLevel(Integer code, boolean hydrogenLeakDetected) { - if (hydrogenLeakDetected) return AlarmPayload.AlarmLevel.CRITICAL; - if (code == null) return AlarmPayload.AlarmLevel.INFO; - return switch (code) { - case 1 -> AlarmPayload.AlarmLevel.MINOR; - case 2 -> AlarmPayload.AlarmLevel.MAJOR; - case 3, 4 -> AlarmPayload.AlarmLevel.CRITICAL; - default -> AlarmPayload.AlarmLevel.INFO; - }; - } - - private static AlarmPayload.SafetyCategory safetyCategory(Set activeBits) { - if (activeBits.contains(GeneralAlarmFlagBit.HYDROGEN_LEAK)) { - return AlarmPayload.SafetyCategory.HYDROGEN_LEAK; - } - if (activeBits.contains(GeneralAlarmFlagBit.HYDROGEN_PRESSURE_ABNORMAL)) { - return AlarmPayload.SafetyCategory.TANK_PRESSURE; - } - if (activeBits.contains(GeneralAlarmFlagBit.HYDROGEN_TEMP_ABNORMAL)) { - return AlarmPayload.SafetyCategory.TANK_TEMPERATURE; - } - return AlarmPayload.SafetyCategory.GENERAL; - } - - private static void addFaultCodes(List out, String prefix, List codes) { - if (codes == null || codes.isEmpty()) return; - for (Long c : codes) out.add(prefix + "-" + Long.toHexString(c)); - } - - private static Map withMeta(Map meta, String key, String value) { - java.util.HashMap copy = new java.util.HashMap<>(meta); - copy.put(key, value == null ? "" : value); - return Map.copyOf(copy); - } - -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandBody.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandBody.java deleted file mode 100644 index ca80804f..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandBody.java +++ /dev/null @@ -1,225 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -import java.time.Instant; -import java.util.List; -import java.util.Map; - -/** - * 命令数据单元(非实时上报)。sealed 集合;每个子类型对应一条 {@link CommandType}。 - * - *

    实时/补发上报的数据单元不走这里,而是产出 {@link InfoBlock} 列表。 - * - *

    上行类型(由车载终端发起):VehicleLogin/VehicleLogout/PlatformLogin/PlatformLogout/ - * Heartbeat/TimeCalibration/Activation/KeyExchange。 - * - *

    下行类型(由平台发起):QueryParams/SetParams/TerminalControl/ActivationResponse。 - * 参见 GB/T 32960.3-2025 附录 B 表 B.5~B.13。 - */ -public sealed interface CommandBody - permits CommandBody.VehicleLogin, - CommandBody.VehicleLogout, - CommandBody.PlatformLogin, - CommandBody.PlatformLogout, - CommandBody.Heartbeat, - CommandBody.TimeCalibration, - CommandBody.Activation, - CommandBody.ActivationResponse, - CommandBody.KeyExchange, - CommandBody.QueryParams, - CommandBody.QueryParamsResponse, - CommandBody.SetParams, - CommandBody.TerminalControl, - CommandBody.RawCommand { - - /** - * 车辆登入 0x01(表 6)。 - * - * @param loginTime 数据采集时间(表 5) - * @param serialNo 登入流水号 1~65531,按天循环累加 - * @param iccid 20 字节 SIM 卡 ICCID,终端从 SIM 卡获取 - * @param batterySystemCount 电池管理系统数 n (0~20,2025 版新增) - * @param batteryPackCounts 每个电池管理系统对应的动力蓄电池包个数 - * @param batteryCodes 动力蓄电池包编码列表(GB/T 34014,24 字节/个) - */ - record VehicleLogin( - Instant loginTime, - int serialNo, - String iccid, - int batterySystemCount, - List batteryPackCounts, - List batteryCodes - ) implements CommandBody {} - - /** 车辆登出 0x04(表 28)。 */ - record VehicleLogout(Instant logoutTime, int serialNo) implements CommandBody {} - - /** 平台登入 0x05(表 29)。 */ - record PlatformLogin( - Instant loginTime, - int serialNo, - String username, - String password, - EncryptType encryptRule - ) implements CommandBody {} - - /** 平台登出 0x06(表 30)。 */ - record PlatformLogout(Instant logoutTime, int serialNo) implements CommandBody {} - - /** 心跳 0x07:数据单元为空(B.3.5.10)。 */ - record Heartbeat() implements CommandBody {} - - /** 终端校时 0x08:信息标志及信息体为空(B.3.5.11)。 */ - record TimeCalibration() implements CommandBody {} - - /** 激活 0x09(表 B.3)。 */ - record Activation( - Instant collectTime, - String chipId, - byte[] publicKey, - String vin, - byte[] signature - ) implements CommandBody {} - - /** 激活结果应答 0x0A(表 B.4)。 */ - record ActivationResponse(int status, int info) implements CommandBody {} - - /** - * 数据单元加密密钥交换 0x0B(表 31)。 - * - * @param keyType 密钥类型({@link EncryptType}) - * @param key 密钥字节 - * @param activateAt 启用时间 - * @param expireAt 失效时间 - */ - record KeyExchange( - EncryptType keyType, - byte[] key, - Instant activateAt, - Instant expireAt - ) implements CommandBody {} - - // ======================================================================== - // 下行命令 0x80 / 0x81 / 0x82 —— 附录 B 表 B.5 ~ B.13 - // ======================================================================== - - /** - * 查询命令 0x80(表 B.5)。平台向车载终端发送,请求查询指定参数。 - * - * @param queryTime 参数查询时间 - * @param paramIds 待查询的参数 ID 列表(表 B.8:0x01-0x10,2025 版新增 0x0D-0x10) - */ - record QueryParams(Instant queryTime, List paramIds) implements CommandBody {} - - /** - * 参数查询返回 0x80 应答(表 B.6/B.7/B.8)。车载终端响应平台。 - * - * @param respondTime 返回查询参数时间 - * @param params 参数项键值对(key = paramId,value = bytes,需按表 B.8 解释) - */ - record QueryParamsResponse(Instant respondTime, Map params) implements CommandBody {} - - /** - * 设置命令 0x81(表 B.9)。平台向车载终端发送,修改车载终端参数。 - * - *

    参数 ID 范围见表 B.8,参数值定义见表 B.8 各字段说明。 - * 其中 0x07 硬件版本、0x08 固件版本为只读,不在 set 命令中出现。 - * - * @param setTime 参数设置时间 - * @param params 参数项键值对 - */ - record SetParams(Instant setTime, Map params) implements CommandBody {} - - /** - * 车载终端控制命令 0x82(表 B.10)。 - * - * @param time 时间 - * @param commandId 控制命令({@link ControlCommand}) - * @param payload 根据不同控制命令的负载 - */ - record TerminalControl( - Instant time, - ControlCommand commandId, - ControlPayload payload - ) implements CommandBody {} - - /** 控制命令枚举(表 B.11)。 */ - enum ControlCommand { - /** 0x01:远程升级,参数见表 B.12。 */ - REMOTE_UPGRADE(0x01), - /** 0x02:车载终端关机。 */ - SHUTDOWN(0x02), - /** 0x03:车载终端复位。 */ - RESET(0x03), - /** 0x04:车载终端恢复出厂设置。 */ - FACTORY_RESET(0x04), - /** 0x05:断开数据通信链路。 */ - DISCONNECT(0x05), - /** 0x06:车载终端报警/预警,参数见表 B.13。 */ - ALARM_COMMAND(0x06), - /** 0x07:开启抽样监测链路。 */ - SAMPLING_LINK_ON(0x07), - /** 未知命令 / 用户自定义 0x80-0xFE。 */ - UNKNOWN(-1); - - private final int code; - ControlCommand(int code) { this.code = code; } - public int code() { return code; } - - public static ControlCommand of(int code) { - for (ControlCommand c : values()) if (c.code == code) return c; - return UNKNOWN; - } - } - - /** 控制命令的负载。sealed:每种控制命令对应一种 payload 类型。 */ - sealed interface ControlPayload - permits ControlPayload.None, ControlPayload.RemoteUpgrade, ControlPayload.AlarmCommand, ControlPayload.RawPayload { - - /** 无参数(SHUTDOWN / RESET / FACTORY_RESET / DISCONNECT / SAMPLING_LINK_ON)。 */ - record None() implements ControlPayload {} - - /** - * 远程升级参数(表 B.12)。 - * - * @param apn 拨号点名称 - * @param dialUser 拨号用户名 - * @param dialPassword 拨号密码 - * @param serverAddress 升级服务器地址(IP 或域名,IPv4 时前 2 字节为 0) - * @param serverPort 升级服务器端口 - * @param manufacturerCode 车载终端制造商 ID(4 字节英文大写字母或数字) - * @param hardwareVersion 硬件版本(5 字节) - * @param firmwareVersion 固件版本(5 字节) - * @param upgradeUrl 升级 URL 地址(建议 FTP) - * @param connectTimeoutMinutes 连接到升级服务器时限(0~60000 分钟) - */ - record RemoteUpgrade( - String apn, - String dialUser, - String dialPassword, - byte[] serverAddress, - int serverPort, - String manufacturerCode, - String hardwareVersion, - String firmwareVersion, - String upgradeUrl, - int connectTimeoutMinutes - ) implements ControlPayload {} - - /** - * 报警/预警命令(表 B.13)。 - * - * @param warningLevel 警告等级:0x00 无报警 / 0x01-0x04 1-4 级报警 / 0xFF 无效 - * @param info 预留字节,可变长 - */ - record AlarmCommand(int warningLevel, byte[] info) implements ControlPayload {} - - /** 未解析的自定义 payload(0x80-0xFE 用户自定义等)。 */ - record RawPayload(byte[] bytes) implements ControlPayload {} - } - - /** - * 未解析或厂商自定义的命令体(0xC0~0xFE 等)。 - * 保留原始字节以便下游冷存分析。 - */ - record RawCommand(int commandCode, byte[] bytes) implements CommandBody {} -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandType.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandType.java deleted file mode 100644 index 0bcc3fb5..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/CommandType.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -/** - * 命令标识。GB/T 32960.3-2025 表 3 和附录 B 表 B.2 "命令标识定义"。 - * - *

    命令标识是发起方的唯一标识,区分数据传输的方向与种类。 - * - *

    方向约定: - *

      - *
    • 上行:车载终端 → 远程服务与管理平台 - *
    • 下行:远程服务与管理平台 → 车载终端 - *
    • 上行/下行:双向使用 - *
    - */ -public enum CommandType { - /** 0x01:车辆登入(上行)—— 见表 6。 */ - VEHICLE_LOGIN(0x01), - /** 0x02:实时信息上报(上行)—— 见表 7。 */ - REALTIME_REPORT(0x02), - /** 0x03:补发信息上报(上行)—— 数据格式与实时上报一致,用于通信链路恢复后补发 7d 内存储的数据。 */ - RESEND_REPORT(0x03), - /** 0x04:车辆登出(上行)—— 见表 28。 */ - VEHICLE_LOGOUT(0x04), - /** 0x05:平台登入(上行)—— 见表 29;2025 版附录 B.2 中 0x05/0x06 改为"平台传输数据占用,自定义"。 */ - PLATFORM_LOGIN(0x05), - /** 0x06:平台登出(上行)—— 见表 30。 */ - PLATFORM_LOGOUT(0x06), - /** 0x07:心跳(上行)—— 数据单元为空,见 B.3.5.10。 */ - HEARTBEAT(0x07), - /** 0x08:终端校时(上行)—— 信息标志及信息体为空,见 B.3.5.11。 */ - TIME_CALIBRATION(0x08), - /** 0x09:激活(上行)—— 见表 B.3。 */ - ACTIVATION(0x09), - /** 0x0A:激活应答(下行)—— 见表 B.4。 */ - ACTIVATION_RESPONSE(0x0A), - /** 0x0B:数据单元加密密钥交换(上行/下行)—— 见表 31。 */ - KEY_EXCHANGE(0x0B), - /** 0x80:查询命令(下行)—— 见表 B.5 ~ B.8。 */ - QUERY(0x80), - /** 0x81:设置命令(下行)—— 见表 B.9。 */ - SET_PARAMS(0x81), - /** 0x82:车载终端控制命令(下行)—— 见表 B.10 ~ B.13。 */ - TERMINAL_CONTROL(0x82); - - private final int code; - - CommandType(int code) { - this.code = code; - } - - public int code() { - return code; - } - - /** 按协议编码查找命令类型;未识别时抛异常以便 DLQ 路由。 */ - public static CommandType of(int code) { - for (CommandType c : values()) if (c.code == code) return c; - throw new IllegalArgumentException("unknown gb32960 command: 0x" + Integer.toHexString(code)); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/EncryptType.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/EncryptType.java deleted file mode 100644 index 769a0b5f..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/EncryptType.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -/** - * 数据单元加密方式。GB/T 32960.3-2025 表 2 "数据单元加密方式"。 - * - *

    当数据单元存在加密时,应先加密后校验,解析端先校验后解密。 - */ -public enum EncryptType { - /** 0x01:数据不加密。 */ - UNENCRYPTED(0x01), - /** 0x02:RSA 算法加密。 */ - RSA(0x02), - /** 0x03:AES(高级加密标准)算法加密。 */ - AES(0x03), - /** 0x04:SM2 算法加密(2025 版新增)。 */ - SM2(0x04), - /** 0x05:SM4 算法加密(2025 版新增)。 */ - SM4(0x05), - /** 0xFE:异常。 */ - ABNORMAL(0xFE), - /** 0xFF:无效。 */ - INVALID(0xFF); - - private final int code; - - EncryptType(int code) { - this.code = code; - } - - public int code() { - return code; - } - - public static EncryptType of(int code) { - for (EncryptType e : values()) if (e.code == code) return e; - return INVALID; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Header.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Header.java deleted file mode 100644 index 765e2880..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Header.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -import java.time.Instant; - -/** - * GB/T 32960.3 数据包报文头(含起始符/命令单元/VIN/加密方式/数据长度),见表 2 / 表 B.1。 - * - *

    完整数据包结构: - *

    - *   起始符 (2B)            ## (0x23 0x23) 或 $$ (0x24 0x24)
    - *   命令标识 (1B)          CommandType
    - *   应答标志 (1B)          ResponseFlag
    - *   唯一识别码 (17B STRING) VIN 或自定义编码
    - *   数据单元加密方式 (1B)   EncryptType
    - *   数据单元长度 (2B WORD)  0 ~ 65531
    - *   数据单元 (NB)          见 §7
    - *   校验码 (1B BCC)        对命令单元到数据单元结尾异或
    - * 
    - * - * @param protocolVersion 协议版本,由起始符推断 - * @param command 命令标识(表 3 / B.2) - * @param responseFlag 应答标志(表 4) - * @param vin 唯一识别码(17 字节 ASCII) - * @param encryptType 数据单元加密方式(表 2) - * @param dataLength 数据单元长度(字节) - * @param eventTime 数据采集时间。仅实时/补发上报携带,其它命令为 null - */ -public record Gb32960Header( - ProtocolVersion protocolVersion, - CommandType command, - ResponseFlag responseFlag, - String vin, - EncryptType encryptType, - int dataLength, - Instant eventTime -) {} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Message.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Message.java deleted file mode 100644 index 39ace8d5..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/Gb32960Message.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -/** - * 完整解析后的 32960 消息。一条消息要么是实时/补发上报(携带 {@link InfoBlock} 列表), - * 要么是登入/登出/平台/心跳/校时/激活/密钥交换等命令(携带 {@link CommandBody}), - * 二者互斥。 - * - * @param header 报文头 - * @param infoBlocks 实时/补发上报的信息体列表;其他命令为空列表 - * @param commandBody 非实时上报的命令数据单元;实时/补发上报为 null - * @param signature 实时上报尾部的签名原始字节(2025 版新增,2016 版为 null) - */ -public record Gb32960Message( - Gb32960Header header, - List infoBlocks, - CommandBody commandBody, - byte[] signature -) { - - /** 便捷构造:只含信息体(实时/补发上报,2016 版常用)。 */ - public Gb32960Message(Gb32960Header header, List infoBlocks) { - this(header, infoBlocks == null ? Collections.emptyList() : infoBlocks, null, null); - } - - /** 便捷构造:非实时命令。 */ - public Gb32960Message(Gb32960Header header, CommandBody commandBody) { - this(header, Collections.emptyList(), commandBody, null); - } - - public boolean isReport() { - return header.command() == CommandType.REALTIME_REPORT - || header.command() == CommandType.RESEND_REPORT; - } - - @SuppressWarnings("unchecked") - public Optional findBlock(Class type) { - return (Optional) infoBlocks.stream().filter(type::isInstance).findFirst(); - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/GeneralAlarmFlagBit.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/GeneralAlarmFlagBit.java deleted file mode 100644 index 0843becd..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/GeneralAlarmFlagBit.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -import java.util.ArrayList; -import java.util.EnumSet; -import java.util.List; -import java.util.Set; - -/** - * 通用报警标志位定义(GB/T 32960.3-2025 表 24)。4 字节 32 位位图中的每个位对应一种报警, - * 2025 版将 2016 版 bit0-15 扩展到 bit0-27,新增了热事件、氢系统、超级电容等位定义。 - * - *

    "1" 表示报警,"0" 表示正常;标志维持到报警条件解除。 - */ -public enum GeneralAlarmFlagBit { - /** bit0:温度差异报警 */ - TEMP_DIFFERENCE(0), - /** bit1:电池高温报警 */ - BATTERY_HIGH_TEMP(1), - /** bit2:车载储能装置类型过压报警 */ - STORAGE_OVER_VOLTAGE(2), - /** bit3:车载储能装置类型欠压报警 */ - STORAGE_UNDER_VOLTAGE(3), - /** bit4:SOC 低报警 */ - SOC_LOW(4), - /** bit5:最小并联单元过压报警 */ - MIN_PARALLEL_UNIT_OVER_VOLTAGE(5), - /** bit6:最小并联单元欠压报警 */ - MIN_PARALLEL_UNIT_UNDER_VOLTAGE(6), - /** bit7:SOC 过高报警 */ - SOC_HIGH(7), - /** bit8:SOC 跳变报警 */ - SOC_JUMP(8), - /** bit9:可充电储能系统不匹配报警 */ - STORAGE_MISMATCH(9), - /** bit10:最小并联单元一致性差报警 */ - MIN_PARALLEL_UNIT_INCONSISTENT(10), - /** bit11:绝缘电阻失效报警 */ - INSULATION_FAILURE(11), - /** bit12:DC-DC 温度报警 */ - DCDC_TEMP(12), - /** bit13:制动系统报警 */ - BRAKE_SYSTEM(13), - /** bit14:DC-DC 状态报警 */ - DCDC_STATUS(14), - /** bit15:驱动电机控制器温度报警 */ - MOTOR_CONTROLLER_TEMP(15), - /** bit16:高压互锁状态报警 */ - HV_INTERLOCK(16), - /** bit17:驱动电机温度报警 */ - MOTOR_TEMP(17), - /** bit18:车载储能装置类型过充报警 */ - STORAGE_OVER_CHARGE(18), - /** bit19:驱动电机超速报警(2025 版新增) */ - MOTOR_OVER_SPEED(19), - /** bit20:驱动电机过流报警(2025 版新增) */ - MOTOR_OVER_CURRENT(20), - /** bit21:超级电容过温报警(2025 版新增) */ - SUPER_CAP_OVER_TEMP(21), - /** bit22:超级电容过压报警(2025 版新增) */ - SUPER_CAP_OVER_VOLTAGE(22), - /** bit23:可充电储能装置热事件报警(2025 版新增,出现即为 4 级故障) */ - STORAGE_THERMAL_EVENT(23), - /** bit24:氢气泄漏异常报警(2025 版新增) */ - HYDROGEN_LEAK(24), - /** bit25:车载氢系统压力异常报警(2025 版新增) */ - HYDROGEN_PRESSURE_ABNORMAL(25), - /** bit26:车载氢系统温度异常报警(2025 版新增) */ - HYDROGEN_TEMP_ABNORMAL(26), - /** bit27:燃料电池电堆超温报警(2025 版新增) */ - FUEL_CELL_STACK_OVER_TEMP(27); - - private final int bitIndex; - - GeneralAlarmFlagBit(int bitIndex) { - this.bitIndex = bitIndex; - } - - public int bitIndex() { - return bitIndex; - } - - /** 当前位是否在 flag 中置位。 */ - public boolean isSet(long flag) { - return (flag & (1L << bitIndex)) != 0; - } - - /** 从 32 位标志解出所有置位的报警。 */ - public static Set parse(long flag) { - EnumSet out = EnumSet.noneOf(GeneralAlarmFlagBit.class); - for (GeneralAlarmFlagBit bit : values()) { - if (bit.isSet(flag)) out.add(bit); - } - return out; - } - - /** 作为可读 fault code 列表(形如 {@code SOC_LOW, MOTOR_TEMP})。 */ - public static List asCodeList(long flag) { - List out = new ArrayList<>(); - for (GeneralAlarmFlagBit bit : values()) { - if (bit.isSet(flag)) out.add(bit.name()); - } - return out; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java deleted file mode 100644 index e775eb26..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlock.java +++ /dev/null @@ -1,463 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -import java.util.List; - -/** - * 已解析的信息体基接口。按 {@code <协议+版本>.<数据类型>} 严格分组: - * - *

      - *
    • {@link Gb32960V2016} —— GB/T 32960.3-2016 附录 B 表 B.10~B.20 - *
    • {@link Gb32960V2025} —— GB/T 32960.3-2025 §7.2.4 表 10~27 - *
    • {@link GuangdongFc} —— 广东燃料电池汽车示范应用城市群综合监管平台规范 v1.0 - *
    • {@link Raw} —— 未实现 parser 的原始字节兜底 - *
    - * - *

    哪怕同一个数据类型在两个版本字段一致(如 Engine 5 字节固定布局),也按规则 - * 复制成两份 record,绝不跨版本共用 record 类型,以便消费方在 instanceof / switch - * 时能严格区分数据来源。 - * - *

    异常值约定: - *

      - *
    • 单字节:{@code 0xFE} 异常 / {@code 0xFF} 无效 - *
    • 双字节:{@code 0xFFFE} 异常 / {@code 0xFFFF} 无效 - *
    • 四字节:{@code 0xFFFFFFFE} 异常 / {@code 0xFFFFFFFF} 无效 - *
    - * 对外 record 使用 {@link Double} / {@link Integer} 等装箱类型,异常或无效时设为 - * {@code null}。 - */ -public sealed interface InfoBlock - permits InfoBlock.Gb32960V2016, - InfoBlock.Gb32960V2025, - InfoBlock.GuangdongFc, - InfoBlock.Raw { - - InfoBlockType type(); - - // ======================================================================== - // GB/T 32960.3-2016 - // ======================================================================== - - /** GB/T 32960.3-2016 附录 B 定义的全部信息体类型。 */ - sealed interface Gb32960V2016 extends InfoBlock - permits Gb32960V2016.Vehicle, - Gb32960V2016.DriveMotor, - Gb32960V2016.FuelCell, - Gb32960V2016.Engine, - Gb32960V2016.Position, - Gb32960V2016.Extreme, - Gb32960V2016.Alarm, - Gb32960V2016.Voltage, - Gb32960V2016.Temperature { - - /** 0x01 整车数据。固定 20 字节(含加速/制动踏板)。 */ - record Vehicle( - Integer vehicleState, - Integer chargingState, - Integer runningMode, - Double speedKmh, - Double totalMileageKm, - Double totalVoltageV, - Double totalCurrentA, - Integer socPercent, - Integer dcDcStatus, - Integer gearRaw, - Integer insulationResistanceKohm, - Integer acceleratorPedal, - Integer brakePedal - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.VEHICLE; } - } - - /** 0x02 驱动电机数据。变长。每电机 12 字节(rpm 偏移 20000,torque 2 字节偏移 20000)。 */ - record DriveMotor(List motors) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.DRIVE_MOTOR; } - - public record Motor( - Integer serialNo, - Integer state, - Integer controllerTempC, - Integer rpm, - Double torqueNm, - Integer motorTempC, - Double controllerInputVoltageV, - Double controllerDcCurrentA - ) {} - } - - /** 0x03 燃料电池数据(2016 版字段顺序,含 N×探针温度 1B)。变长。 */ - record FuelCell( - Double fcVoltageV, - Double fcCurrentA, - Double hydrogenConsumptionKgPer100km, - List probeTempC, - Double maxHydrogenSystemTempC, - Integer maxHydrogenSystemTempProbeId, - Double maxHydrogenConcentrationPercent, - Integer maxHydrogenConcentrationProbeId, - Double maxHydrogenPressureMpa, - Integer maxHydrogenPressureProbeId, - Integer hvDcDcStatus - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.FUEL_CELL_V2016; } - } - - /** 0x04 发动机数据。固定 5 字节。 */ - record Engine( - Integer engineState, - Integer crankshaftRpm, - Double fuelConsumptionRate - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.ENGINE; } - } - - /** 0x05 车辆位置数据。固定 9 字节。 */ - record Position( - int statusFlag, - double longitude, - double latitude - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.POSITION_V2016; } - } - - /** 0x06 动力蓄电池极值数据。固定 14 字节。 */ - record Extreme( - Integer maxCellVoltageSubSysNo, - Integer maxCellVoltageCellId, - Double maxCellVoltageV, - Integer minCellVoltageSubSysNo, - Integer minCellVoltageCellId, - Double minCellVoltageV, - Integer maxTempSubSysNo, - Integer maxTempProbeId, - Integer maxTempC, - Integer minTempSubSysNo, - Integer minTempProbeId, - Integer minTempC - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.EXTREME_V2016; } - } - - /** 0x07 报警数据。变长(4 个故障代码列表)。 */ - record Alarm( - Integer maxLevel, - long generalAlarmFlag, - List batteryFaults, - List motorFaults, - List engineFaults, - List otherFaults - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.ALARM_V2016; } - } - - /** 0x08 可充电储能装置电压数据。变长。PoC 仅保留汇总。 */ - record Voltage( - int subSystemCount, - double maxCellVoltageV, - double minCellVoltageV, - double totalVoltageV - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.VOLTAGE_V2016; } - } - - /** 0x09 可充电储能装置温度数据。变长。 */ - record Temperature( - int subSystemCount, - int maxTempC, - int minTempC - ) implements Gb32960V2016 { - @Override public InfoBlockType type() { return InfoBlockType.TEMPERATURE_V2016; } - } - } - - // ======================================================================== - // GB/T 32960.3-2025 - // ======================================================================== - - /** GB/T 32960.3-2025 §7.2.4 定义的全部信息体类型。 */ - sealed interface Gb32960V2025 extends InfoBlock - permits Gb32960V2025.Vehicle, - Gb32960V2025.DriveMotor, - Gb32960V2025.FuelCell, - Gb32960V2025.Engine, - Gb32960V2025.Position, - Gb32960V2025.Alarm, - Gb32960V2025.MinParallelVoltage, - Gb32960V2025.BatteryTemperature, - Gb32960V2025.FuelCellStack, - Gb32960V2025.SuperCapacitor, - Gb32960V2025.SuperCapacitorExtreme { - - /** 0x01 整车数据。固定 18 字节(删除加速/制动踏板)。 */ - record Vehicle( - Integer vehicleState, - Integer chargingState, - Integer runningMode, - Double speedKmh, - Double totalMileageKm, - Double totalVoltageV, - Double totalCurrentA, - Integer socPercent, - Integer dcDcStatus, - Integer gearRaw, - Integer insulationResistanceKohm - ) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.VEHICLE; } - } - - /** 0x02 驱动电机数据。变长。每电机 14 字节(rpm 偏移 32000,torque 4 字节偏移 20000)。 */ - record DriveMotor(List motors) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.DRIVE_MOTOR; } - - public record Motor( - Integer serialNo, - Integer state, - Integer controllerTempC, - Integer rpm, - Double torqueNm, - Integer motorTempC, - Double controllerInputVoltageV, - Double controllerDcCurrentA - ) {} - } - - /** 0x03 燃料电池发动机及车载氢系统数据。固定 13 字节。 */ - record FuelCell( - Double maxHydrogenSystemTempC, - Integer maxHydrogenSystemTempProbeId, - Double maxHydrogenConcentrationPercent, - Integer maxHydrogenConcentrationProbeId, - Double maxHydrogenPressureMpa, - Integer maxHydrogenPressureProbeId, - Integer hvDcDcStatus, - Integer remainingHydrogenPercent, - Integer hvDcDcControllerTempC - ) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.FUEL_CELL_V2025; } - } - - /** 0x04 发动机数据。固定 5 字节。字段同 2016 版。 */ - record Engine( - Integer engineState, - Integer crankshaftRpm, - Double fuelConsumptionRate - ) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.ENGINE; } - } - - /** 0x05 车辆位置数据。固定 10 字节(多坐标系字节)。 */ - record Position( - int statusFlag, - int coordSystem, - double longitude, - double latitude - ) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.POSITION_V2025; } - } - - /** 0x06 报警数据。变长(4 个故障代码列表 + 通用报警故障等级列表)。 */ - record Alarm( - Integer maxLevel, - long generalAlarmFlag, - List batteryFaults, - List motorFaults, - List engineFaults, - List otherFaults, - List generalAlarmLevels - ) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.ALARM_V2025; } - - /** 通用报警故障等级列表项(2 字节):bit 对应表 24 位序号,level 为故障等级。 */ - public record GeneralAlarmEntry(int bit, int level) {} - } - - /** 0x07 动力蓄电池最小并联单元电压数据。变长。 */ - record MinParallelVoltage(int subSystemCount, List packs) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.MIN_PARALLEL_VOLTAGE_V2025; } - - /** totalUnits 是车端声明数量;frameVoltages 是本帧实际解析出的完整电压值。 */ - public record Pack( - int packNo, - Double packVoltageV, - Double packCurrentA, - int totalUnits, - List frameVoltages - ) {} - } - - /** 0x08 动力蓄电池温度数据。变长。 */ - record BatteryTemperature(int subSystemCount, List packs) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.BATTERY_TEMPERATURE_V2025; } - - /** probeCount 是车端声明数量;probeTempsC 是本帧实际解析出的温度值。 */ - public record Pack(int packNo, int probeCount, List probeTempsC) {} - } - - /** 0x30 燃料电池电堆数据。变长(2025 标准)。 */ - record FuelCellStack(int stackCount, List stacks) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.FUEL_CELL_STACK; } - - public record Stack( - int stackNo, - Double voltageV, - Double currentA, - // 标准字段名写 pressure,协议比例按 kPa 命名,避免与 2016 Mpa 字段混淆。 - Double hydrogenInletPressureKpa, - Double airInletPressureKpa, - Integer airInletTempC, - List coolantOutletProbeTempsC - ) {} - } - - /** 0x31 超级电容器数据。变长。 */ - record SuperCapacitor( - int systemNo, - Double totalVoltageV, - Double totalCurrentA, - List cellVoltages, - List probeTempsC - ) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.SUPER_CAPACITOR; } - } - - /** 0x32 超级电容器极值数据。固定 18 字节。 */ - record SuperCapacitorExtreme( - Integer maxVoltageSystemNo, - Integer maxVoltageCellId, - Double maxCellVoltageV, - Integer minVoltageSystemNo, - Integer minVoltageCellId, - Double minCellVoltageV, - Integer maxTempSystemNo, - Integer maxTempProbeId, - Integer maxTempC, - Integer minTempSystemNo, - Integer minTempProbeId, - Integer minTempC - ) implements Gb32960V2025 { - @Override public InfoBlockType type() { return InfoBlockType.SUPER_CAPACITOR_EXTREME; } - } - } - - // ======================================================================== - // 广东燃料电池汽车示范应用城市群综合监管平台规范 v1.0(基于 GB/T 32960.3-2016 帧头) - // ======================================================================== - - /** 广东燃料电池汽车示范应用规范定义的扩展信息体(typeCode 0x30~0x34、0x80)+ 同 peer 的厂商私有 TLV。 */ - sealed interface GuangdongFc extends InfoBlock - permits GuangdongFc.Stack, - GuangdongFc.Auxiliary, - GuangdongFc.DcDc, - GuangdongFc.AirConditioner, - GuangdongFc.VehicleInfo, - GuangdongFc.DemoExtension, - GuangdongFc.VendorTlv { - - /** 0x30 燃料电池电堆数据(§7.2.3.4 表 13/14)。变长。 */ - record Stack(int stackCount, List stacks) implements GuangdongFc { - @Override public InfoBlockType type() { return InfoBlockType.GD_FC_STACK; } - - public record Item( - Integer engineWorkState, - Integer stackWaterOutletTempC, - Double hydrogenInletPressureKpa, - Double airInletPressureKpa, - Integer airInletTempC, - Integer maxCellVoltageId, - Integer minCellVoltageId, - Double maxCellVoltageV, - Double minCellVoltageV, - Double avgCellVoltageV, - Integer cellCount, - Integer frameCellStart, - Integer frameCellCount, - List frameCellVoltagesV - ) {} - } - - /** 0x31 辅助系统数据(§7.2.3.5 表 15/16)。变长。 */ - record Auxiliary(int subSystemCount, List subsystems) implements GuangdongFc { - @Override public InfoBlockType type() { return InfoBlockType.GD_FC_AUXILIARY; } - - public record Subsystem( - Double airCompressorMotorVoltageV, - Integer airCompressorPowerKw, - Double hydrogenPumpMotorVoltageV, - Integer hydrogenPumpPowerKw, - Double waterPumpVoltageV, - Double ptcVoltageV, - Integer ptcPowerKw, - Double lowVoltageBatteryVoltageV - ) {} - } - - /** 0x32 DC/DC(燃料电池系统)数据(§7.2.3.6 表 17)。固定 9 字节。 */ - record DcDc( - Double inputVoltageV, - Double inputCurrentA, - Double outputVoltageV, - Double outputCurrentA, - Integer controllerTempC - ) implements GuangdongFc { - @Override public InfoBlockType type() { return InfoBlockType.GD_FC_DCDC; } - } - - /** 0x33 空调数据(§7.2.3.7 表 18)。固定 5 字节。 */ - record AirConditioner( - Integer status, - Integer powerKw, - Double compressorInputVoltageV - ) implements GuangdongFc { - @Override public InfoBlockType type() { return InfoBlockType.GD_FC_AIR_CONDITIONER; } - } - - /** 0x34 车辆信息数据(§7.2.3.8 表 19)。固定 7 字节。 */ - record VehicleInfo( - Integer collisionAlarm, - Integer ambientTempC, - Double ambientPressureKpa, - Double hydrogenMassKg - ) implements GuangdongFc { - @Override public InfoBlockType type() { return InfoBlockType.GD_FC_VEHICLE_INFO; } - } - - /** 0x80 燃料电池汽车示范应用数据扩展(§7.2.3.14 表 27)。 */ - record DemoExtension( - Integer stackTempC, - Double airCompressorVoltageV, - Double airCompressorCurrentA, - Double hydrogenPumpVoltageV, - Double hydrogenPumpCurrentA - ) implements GuangdongFc { - @Override public InfoBlockType type() { return InfoBlockType.GD_FC_DEMO_EXTENSION; } - } - - /** - * 厂商私有 TLV 块兜底容器。当对端在广东规范之外又下发了一个未公开的 typeCode - * (目前线上观察到 {@code 0x83}),但该块明显是 TLV 格式(type(1) + length(2) + - * value(N))时,本 record 保留原始字节直到对端提供正式协议文档为止。 - * - *

    关键在于对应的 {@code GdFcVendorTlvBlockParser} 严格按 length 字段消费字节, - * 因此 BodyParser 主循环在该块之后**仍能继续解析其它信息体**,不会被吞光。 - * - * @param typeCode 触发的 typeCode(保留以便诊断/分类) - * @param declaredLength TLV 的 length 字段值(payload 字节数) - * @param payload 原始 payload 字节,长度 = declaredLength - */ - record VendorTlv(int typeCode, int declaredLength, byte[] payload) implements GuangdongFc { - @Override public InfoBlockType type() { return InfoBlockType.GD_FC_VENDOR_TLV; } - } - } - - // ======================================================================== - // Raw / Unknown - // ======================================================================== - - /** - * 尚未实现 Parser 的原始信息体(含自定义 0x80~0xFE),保留字节透传到冷存。 - * - * @param typeCode 触发兜底时的真实信息体类型字节,便于诊断未知段或确认 parser 漂移落点 - * @param type 枚举形态,目前固定 {@link InfoBlockType#RAW}(保留字段为后续真正分类预留) - * @param bytes 自该未知 type 字节之后到 body 结尾的剩余原始字节 - */ - record Raw(int typeCode, InfoBlockType type, byte[] bytes) implements InfoBlock {} -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java deleted file mode 100644 index 1fef0255..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/InfoBlockType.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -/** - * 信息体语义类型(逻辑分类)。不与协议 typeCode 一一对应。 - * - *

    因为 2016 和 2025 的 typeCode 0x06/0x07/0x08/0x09 语义不同,本枚举按逻辑含义 - * 分类,而不是按 typeCode。真正的协议 typeCode 路由由 {@link com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser#typeCode()} - * 和 {@link com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry} 负责。 - */ -public enum InfoBlockType { - /** 整车数据(表 10)。 */ - VEHICLE, - /** 驱动电机数据(表 15/16)。 */ - DRIVE_MOTOR, - /** 燃料电池数据(2016 版,带电压/电流/温度探针列表)。 */ - FUEL_CELL_V2016, - /** 燃料电池发动机及车载氢系统数据(2025 版,表 17)。 */ - FUEL_CELL_V2025, - /** 发动机数据(表 20)。 */ - ENGINE, - /** 车辆位置数据(2016 版,表 B.15)。 */ - POSITION_V2016, - /** 车辆位置数据(2025 版,表 21,带坐标系字段)。 */ - POSITION_V2025, - /** 动力蓄电池极值数据(2016 版,仅 2016 存在)。 */ - EXTREME_V2016, - /** 报警数据(2016 版)。 */ - ALARM_V2016, - /** 报警数据(2025 版,表 23,新增通用报警故障等级列表)。 */ - ALARM_V2025, - /** 可充电储能装置电压数据(2016 版)。 */ - VOLTAGE_V2016, - /** 可充电储能装置温度数据(2016 版)。 */ - TEMPERATURE_V2016, - /** 动力蓄电池最小并联单元电压数据(2025 版,表 11/12)。 */ - MIN_PARALLEL_VOLTAGE_V2025, - /** 动力蓄电池温度数据(2025 版,表 13/14)。 */ - BATTERY_TEMPERATURE_V2025, - /** 燃料电池电堆数据(2025 版,表 18/19)。 */ - FUEL_CELL_STACK, - /** 超级电容器数据(2025 版,表 25)。 */ - SUPER_CAPACITOR, - /** 超级电容器极值数据(2025 版,表 26)。 */ - SUPER_CAPACITOR_EXTREME, - /** 自定义数据 0x80~0xFE(表 27)。 */ - USER_DEFINED, - /** 广东燃料电池规范 0x30 燃料电池电堆数据(表 14)。 */ - GD_FC_STACK, - /** 广东燃料电池规范 0x31 辅助系统数据(表 16)。 */ - GD_FC_AUXILIARY, - /** 广东燃料电池规范 0x32 DC/DC 数据(表 17)。 */ - GD_FC_DCDC, - /** 广东燃料电池规范 0x33 空调数据(表 18)。 */ - GD_FC_AIR_CONDITIONER, - /** 广东燃料电池规范 0x34 车辆信息数据(表 19)。 */ - GD_FC_VEHICLE_INFO, - /** 广东燃料电池规范 0x80 燃料电池汽车示范应用数据扩展(表 27)。 */ - GD_FC_DEMO_EXTENSION, - /** 同 peer 的厂商私有 TLV 兜底(如 0x83),等待对端文档后升级为正式 parser。 */ - GD_FC_VENDOR_TLV, - /** 原始/未解析。 */ - RAW -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ProtocolVersion.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ProtocolVersion.java deleted file mode 100644 index 12a304b8..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ProtocolVersion.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -/** - * GB/T 32960.3 协议版本。 - * - *

    通过数据包起始符区分: - *

      - *
    • {@code ## (0x23 0x23)} → {@link #V2016} 对应 GB/T 32960.3-2016 - *
    • {@code $$ (0x24 0x24)} → {@link #V2025} 对应 GB/T 32960.3-2025 - *
    - * - *

    两版核心差异(GB/T 32960.3-2025 前言): - *

      - *
    • 信息类型 0x06/0x07/0x08 语义重构: - * - * - * - * - * - * - *
      类型20162025
      0x06动力蓄电池极值报警数据
      0x07报警数据动力蓄电池最小并联单元电压
      0x08可充电储能装置电压动力蓄电池温度
      0x09可充电储能装置温度(已删除,合入 0x08)
      - *
    • 新增 0x30 燃料电池电堆 / 0x31 超级电容 / 0x32 超级电容极值 - *
    • 0x05 位置数据新增 1 字节坐标系(表 21) - *
    • 0x03 燃料电池数据字段重构:拆分为"燃料电池发动机及车载氢系统" - * + 独立 0x30"燃料电池电堆"(表 17/18/19) - *
    • 驱动电机 0x02 转速偏移由 {@code 20000} 改为 {@code 32000},转矩由 2 字节扩展为 4 字节 - *
    • 实时上报数据单元尾部增加签名信息(以类型 {@code 0xFF} 标识,表 8) - *
    • 加密方式新增 {@code 0x04 SM2} / {@code 0x05 SM4} - *
    • 命令标识新增 {@code 0x07 心跳 / 0x08 校时 / 0x09 激活 / 0x0A 激活应答 / 0x0B 密钥交换} - *
    - */ -public enum ProtocolVersion { - /** GB/T 32960.3-2016。起始符 {@code 0x23 0x23 (##)}。 */ - V2016, - /** GB/T 32960.3-2025。起始符 {@code 0x24 0x24 ($$)}。 */ - V2025 -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ResponseFlag.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ResponseFlag.java deleted file mode 100644 index e58845a1..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/ResponseFlag.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -/** - * 应答标志。GB/T 32960.3-2025 表 4 "应答标志定义"。 - * - *

    命令的主动发起方应答标志为 {@code 0xFE}({@link #COMMAND}),表示此包为命令包; - * 当应答标志不是 {@code 0xFE} 时,此包表示为应答包。服务端发送应答时,应变更应答标志, - * 保留报文时间,删除其余报文内容,并重新计算校验位。 - */ -public enum ResponseFlag { - /** 0x01:成功 —— 接收到的信息正确。 */ - SUCCESS(0x01), - /** 0x02:其他错误 —— 其他收到的信息存在格式及内容错误。 */ - OTHER_ERROR(0x02), - /** 0x03:VIN 重复。 */ - VIN_DUPLICATED(0x03), - /** 0x04:VIN 不存在(2025 版新增)。 */ - VIN_NOT_EXIST(0x04), - /** 0x05:验签错误(2025 版新增)。 */ - SIGNATURE_ERROR(0x05), - /** 0x06:数据结构错误(2025 版新增)。 */ - STRUCTURE_ERROR(0x06), - /** 0x07:解密错误(2025 版新增)。 */ - DECRYPT_ERROR(0x07), - /** 0xFE:命令 —— 表示数据包为命令包,而非应答包。 */ - COMMAND(0xFE); - - private final int code; - - ResponseFlag(int code) { - this.code = code; - } - - public int code() { - return code; - } - - public boolean isCommand() { - return this == COMMAND; - } - - public static ResponseFlag of(int code) { - for (ResponseFlag r : values()) if (r.code == code) return r; - return OTHER_ERROR; - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/SignatureInfo.java b/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/SignatureInfo.java deleted file mode 100644 index 2b961ed0..00000000 --- a/modules/protocols/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/model/SignatureInfo.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.model; - -import java.nio.ByteBuffer; - -/** - * 实时信息的车端数字签名(GB/T 32960.3-2025 表 8)。 - * - *

    签名信息出现在实时上报数据单元的末尾,紧跟在信息体循环后,由 {@code 0xFF} - * 作为"签名数据开始标识"引出(见表 9)。签名范围:从数据采集时间的第一字节到签名 - * 信息前一个字节的数字签名。 - * - *

    表 8 字段: - *

    - *   signType(1)    签名类型:0x01 SM2 / 0x02 RSA / 0x03 ECC,其他预留
    - *   rLen(2 WORD)   签名 R 值长度
    - *   r(rLen)        签名 R 值
    - *   sLen(2 WORD)   签名 S 值长度
    - *   s(sLen)        签名 S 值
    - * 
    - */ -public record SignatureInfo(SignType signType, byte[] r, byte[] s) { - - public enum SignType { - /** 0x01:SM2 国密签名。 */ - SM2(0x01), - /** 0x02:RSA 签名。 */ - RSA(0x02), - /** 0x03:ECC 签名。 */ - ECC(0x03), - /** 其他预留。 */ - UNKNOWN(0xFF); - - private final int code; - SignType(int code) { this.code = code; } - public int code() { return code; } - - public static SignType of(int code) { - for (SignType t : values()) if (t.code == code) return t; - return UNKNOWN; - } - } - - /** - * 尝试解析签名字节。非法数据时返回 null 并保留原始字节由冷存分析。 - * - * @param bytes 签名原始字节(不含前缀的 0xFF 签名开始标识) - */ - public static SignatureInfo tryParse(byte[] bytes) { - if (bytes == null || bytes.length < 5) return null; - ByteBuffer buf = ByteBuffer.wrap(bytes); - try { - SignType type = SignType.of(buf.get() & 0xFF); - int rLen = buf.getShort() & 0xFFFF; - if (rLen > buf.remaining() - 2) return null; - byte[] r = new byte[rLen]; - buf.get(r); - int sLen = buf.getShort() & 0xFFFF; - if (sLen > buf.remaining()) return null; - byte[] s = new byte[sLen]; - buf.get(s); - return new SignatureInfo(type, r, s); - } catch (Exception e) { - return null; - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/protocols/protocol-gb32960/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 2a10e523..00000000 --- a/modules/protocols/protocol-gb32960/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.protocol.gb32960.config.Gb32960AutoConfiguration diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParserIsolationTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParserIsolationTest.java deleted file mode 100644 index 4e4ec2d8..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960BodyParserIsolationTest.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.api.spi.DecodeException; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlockType; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * 验证 {@link Gb32960BodyParser} 单块异常隔离行为。 - * - *

    三个隔离场景: - *

      - *
    1. 固定长度块 parser 抛异常 → 失败块兜 Raw(按 fixedLen 截取)+ 后续块继续解析 - *
    2. 固定长度块剩余字节不足(截断帧) → 兜 Raw(剩余) + break 循环 - *
    3. 变长块 parser 抛异常 → 兜 Raw(从失败块起剩余全部) + break 循环 - *
    - * - *

    外加一个严格模式回退测试:{@code lenientBlockFailure=false} 时异常应抛 DecodeException。 - */ -class Gb32960BodyParserIsolationTest { - - /** 模拟一个固定长度的 Position parser,声明 fixedLength=9,parse 时主动抛异常。 */ - private static final InfoBlockParser EXPLODING_FIXED_LEN_POSITION = new InfoBlockParser() { - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x05; } - @Override public int fixedLength() { return 9; } - @Override public InfoBlock parse(ByteBuffer buffer) { - buffer.get(); - buffer.get(); - throw new DecodeException("simulated parser failure in Position"); - } - }; - - /** 模拟一个变长 Alarm parser(fixedLength=-1),消费若干字节后抛。 */ - private static final InfoBlockParser EXPLODING_VAR_LEN_ALARM = new InfoBlockParser() { - @Override public ProtocolVersion version() { return ProtocolVersion.V2016; } - @Override public int typeCode() { return 0x07; } - @Override public int fixedLength() { return -1; } - @Override public InfoBlock parse(ByteBuffer buffer) { - buffer.get(); - throw new DecodeException("simulated parser failure in Alarm list-length read"); - } - }; - - @Test - void fixedLengthBlockFailure_isIsolated_subsequentBlocksStillParsed() { - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - EXPLODING_FIXED_LEN_POSITION)); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); // 1 + 20 = 21B - writePositionTypeAnd9ByteBody(os); // 1 + 9 = 10B(parser 会爆) - writeValidVehicle(os); // 1 + 20 = 21B - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - var result = parser.parse(ProtocolVersion.V2016, body); - - assertThat(result.blocks()).hasSize(3); - assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> { - assertThat(raw.typeCode()).isEqualTo(0x05); - assertThat(raw.type()).isEqualTo(InfoBlockType.RAW); - assertThat(raw.bytes()).hasSize(9); - }); - assertThat(result.blocks().get(2)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(body.hasRemaining()).isFalse(); - } - - @Test - void truncatedFixedLengthBlock_isWrappedAsRaw_loopTerminates() { - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - new PositionV2016BlockParser())); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); - os.write(0x05); - os.write(0); - os.write(0); - os.write(0); - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - var result = parser.parse(ProtocolVersion.V2016, body); - - assertThat(result.blocks()).hasSize(2); - assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> { - assertThat(raw.typeCode()).isEqualTo(0x05); - assertThat(raw.bytes()).hasSize(3); - }); - } - - @Test - void variableLengthBlockFailure_swallowsRemainderAsRaw_loopBreaks() { - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - EXPLODING_VAR_LEN_ALARM)); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); - os.write(0x07); - for (int i = 0; i < 10; i++) os.write(0xAA); - writeValidVehicle(os); - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - var result = parser.parse(ProtocolVersion.V2016, body); - - assertThat(result.blocks()).hasSize(2); - assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - assertThat(result.blocks().get(1)).isInstanceOfSatisfying(InfoBlock.Raw.class, raw -> { - assertThat(raw.typeCode()).isEqualTo(0x07); - assertThat(raw.bytes()).hasSize(31); - }); - } - - @Test - void strictMode_throwsOnAnyBlockFailure() { - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - EXPLODING_FIXED_LEN_POSITION)); - - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - parser.setLenientBlockFailure(false); - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeValidVehicle(os); - writePositionTypeAnd9ByteBody(os); - ByteBuffer body = ByteBuffer.wrap(os.toByteArray()); - - assertThatThrownBy(() -> parser.parse(ProtocolVersion.V2016, body)) - .isInstanceOf(DecodeException.class); - } - - private static void writeValidVehicle(ByteArrayOutputStream os) { - os.write(0x01); - os.write(0x01); - os.write(0x01); - os.write(0x01); - os.write(0); os.write(0); - os.write(0); os.write(0); os.write(0); os.write(0); - os.write(0); os.write(0); - os.write(0); os.write(0); - os.write(50); - os.write(0x01); - os.write(0); - os.write(0); os.write(0); - os.write(0); - os.write(0); - } - - private static void writePositionTypeAnd9ByteBody(ByteArrayOutputStream os) { - os.write(0x05); - for (int i = 0; i < 9; i++) os.write(0); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderGoldenTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderGoldenTest.java deleted file mode 100644 index 91b92f66..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderGoldenTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.AlarmV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.DriveMotorV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.EngineV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.ExtremeValueV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.FuelCellV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.TemperatureV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VoltageV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 黄金样本集回放测试:遍历 {@code src/test/resources/samples/gb32960/*.hex}, - * 逐帧解码并断言命令类型来自 {@link com.lingniu.ingest.protocol.gb32960.model.CommandType} 有效值。 - * - *

    样本文件目前为空,该测试会优雅地跳过。加入样本后会自动变为 N 条动态用例。 - */ -class Gb32960DecoderGoldenTest { - - private final Gb32960MessageDecoder decoder = new Gb32960MessageDecoder( - new Gb32960BodyParser(new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - new PositionV2016BlockParser(), - new DriveMotorV2016BlockParser(), - new FuelCellV2016BlockParser(), - new EngineV2016BlockParser(), - new ExtremeValueV2016BlockParser(), - new AlarmV2016BlockParser(), - new VoltageV2016BlockParser(), - new TemperatureV2016BlockParser())))); - - @TestFactory - Collection replaySamples() throws URISyntaxException, IOException { - var url = getClass().getClassLoader().getResource("samples/gb32960"); - if (url == null) return List.of(); - Path dir = Paths.get(url.toURI()); - try (Stream s = Files.list(dir)) { - return s.filter(p -> p.toString().endsWith(".hex")) - .sorted() - .map(this::toTest) - .collect(Collectors.toList()); - } - } - - private DynamicTest toTest(Path sample) { - return DynamicTest.dynamicTest(sample.getFileName().toString(), () -> { - byte[] frame = readHex(sample); - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame)); - assertThat(msg.header().vin()).hasSize(17); - assertThat(msg.header().command()).isNotNull(); - // 749 字节真实生产帧(realtime_002/003/010/200):peer 在 V2016 帧里下发了 - // 0x30/0x31/0x32 等 typeCode,但这些在 GB/T 32960.3-2016 附录 B 表 B.3 是"预留"区, - // 没有任何字段定义。因此**期望产生 Raw 兜底块**——这是符合规范的正确行为。 - // 若未来对端切换到 V2025 (2424 起始) 或者迁移到 0x80~0xFE 用户自定义区,可再调整断言。 - String name = sample.getFileName().toString(); - boolean isRealProductionFrame = frame.length == 774; - if (isRealProductionFrame) { - assertThat(msg.findBlock(InfoBlock.Raw.class)) - .as("[%s] 749字节真实生产帧应有 Raw 兜底块(peer 越界使用 0x30+ 预留 typeCode)", name) - .isPresent(); - } - // 总电流必须落在协议规定的 -1000~+1000 A 区间,防回归到 -3000 偏移 - msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class).ifPresent(v -> { - if (v.totalCurrentA() != null) { - assertThat(v.totalCurrentA()) - .as("[%s] vehicle.totalCurrentA 越界,疑似偏移常量回归", name) - .isBetween(-1000.0, 1000.0); - } - }); - }); - } - - private static byte[] readHex(Path path) throws IOException { - StringBuilder sb = new StringBuilder(); - for (String line : Files.readAllLines(path)) { - String trimmed = line.trim(); - if (trimmed.isEmpty() || trimmed.startsWith("#")) continue; - sb.append(trimmed); - } - String hex = sb.toString(); - int len = hex.length() / 2; - byte[] out = new byte[len]; - for (int i = 0; i < len; i++) { - out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); - } - return out; - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderTest.java deleted file mode 100644 index 37609f4a..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960DecoderTest.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.model.CommandBody; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 构造一条合成 32960 实时上报帧,跑通 Frame 解码 → Body 解析 → InfoBlock 的全链路。 - * - *

    样本无需外部文件:本测试既验证解码正确性,也作为 {@code samples/} 黄金样本的期望值参考实现。 - */ -public class Gb32960DecoderTest { - - @Test - void decodesSyntheticRealtimeReport() { - byte[] frame = buildRealtimeFrame("LTEST000000000001"); - - InfoBlockParserRegistry registry = new InfoBlockParserRegistry( - List.of(new VehicleV2016BlockParser(), new PositionV2016BlockParser())); - Gb32960BodyParser bodyParser = new Gb32960BodyParser(registry); - Gb32960MessageDecoder decoder = new Gb32960MessageDecoder(bodyParser); - - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame)); - - assertThat(msg.header().command()).isEqualTo(CommandType.REALTIME_REPORT); - assertThat(msg.header().vin()).isEqualTo("LTEST000000000001"); - assertThat(msg.header().eventTime()).isNotNull(); - - InfoBlock.Gb32960V2016.Vehicle v = msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class).orElseThrow(); - assertThat(v.socPercent()).isEqualTo(70); - assertThat(v.speedKmh()).isEqualTo(52.3, org.assertj.core.data.Offset.offset(0.01)); - assertThat(v.gearRaw()).isEqualTo(0x0F); - - InfoBlock.Gb32960V2016.Position p = msg.findBlock(InfoBlock.Gb32960V2016.Position.class).orElseThrow(); - assertThat(p.longitude()).isEqualTo(116.397128, org.assertj.core.data.Offset.offset(0.000001)); - assertThat(p.latitude()).isEqualTo(39.916527, org.assertj.core.data.Offset.offset(0.000001)); - } - - @Test - void decodesPlatformLoginWithMd5HexPasswordLongerThanDeclaredGbField() { - byte[] frame = buildPlatformLoginFrameWithExtendedPassword( - "Hyundai", - "f2e3445d7cda409fb4f278f6fb890734"); - - Gb32960MessageDecoder decoder = new Gb32960MessageDecoder(new Gb32960BodyParser( - new InfoBlockParserRegistry(List.of()))); - - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame)); - - assertThat(msg.header().command()).isEqualTo(CommandType.PLATFORM_LOGIN); - assertThat(msg.commandBody()).isInstanceOf(CommandBody.PlatformLogin.class); - CommandBody.PlatformLogin login = (CommandBody.PlatformLogin) msg.commandBody(); - assertThat(login.username()).isEqualTo("Hyundai"); - assertThat(login.password()).isEqualTo("f2e3445d7cda409fb4f278f6fb890734"); - } - - /** - * 构造一条合法的 32960 实时上报帧: - * header + 6B 时间戳(2024-01-02 03:04:05) + 0x01 整车 + 0x05 位置 + BCC - */ - public static byte[] buildRealtimeFrame(String vin) { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - // 时间戳 - body.write(24); body.write(1); body.write(2); body.write(3); body.write(4); body.write(5); - - // 0x01 整车 20 字节 - body.write(0x01); - body.write(0x01); // vehicle state = 启动 - body.write(0x03); // charging = 未充电 - body.write(0x01); // 纯电 - writeU16(body, 523); // 车速 52.3 km/h - writeU32(body, 1234567); // 里程 123456.7 km - writeU16(body, 6000); // 600.0 V - writeU16(body, 1100); // 110.0 A (110 - (1000-1000) = 10) 实际计算:1100*0.1 - 1000 = -890;此处只为结构测试 - body.write(70); // SOC - body.write(0x01); - body.write(0x0F); - writeU16(body, 500); - body.write(30); - body.write(0); - - // 0x05 位置 9 字节 - body.write(0x05); - body.write(0x00); // 有效 + 北纬 + 东经 - writeU32(body, 116_397_128L); - writeU32(body, 39_916_527L); - - byte[] bodyBytes = body.toByteArray(); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x02); // 实时上报 - out.write(0xFE); // 应答 / 命令 - byte[] vinBytes = vin.getBytes(StandardCharsets.US_ASCII); - out.write(vinBytes, 0, 17); - out.write(0x01); // 不加密 - writeU16(out, bodyBytes.length); - out.write(bodyBytes, 0, bodyBytes.length); - - byte[] almost = out.toByteArray(); - byte bcc = BccChecksum.compute(almost, 2, almost.length - 2); - out.write(bcc & 0xFF); - return out.toByteArray(); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } - - private static byte[] buildPlatformLoginFrameWithExtendedPassword(String username, String password) { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - body.write(26); body.write(6); body.write(22); body.write(20); body.write(40); body.write(45); - writeU16(body, 1); - writePaddedAscii(body, username, 12); - writePaddedAscii(body, password, password.length()); - body.write(0x01); - - byte[] bodyBytes = body.toByteArray(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x05); - out.write(0xFE); - for (int i = 0; i < 17; i++) out.write(0); - out.write(0x01); - writeU16(out, 41); - out.write(bodyBytes, 0, bodyBytes.length); - byte[] almost = out.toByteArray(); - out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF); - return out.toByteArray(); - } - - private static void writePaddedAscii(ByteArrayOutputStream os, String value, int len) { - byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - int copy = Math.min(bytes.length, len); - os.write(bytes, 0, copy); - for (int i = copy; i < len; i++) os.write(0); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoderTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoderTest.java deleted file mode 100644 index 70a3c0b0..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FrameDecoderTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.codec.BccChecksum; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960FrameDecoderTest { - - @Test - void keepsPlatformLoginWithMd5HexPasswordAsOneFrame() { - byte[] frame = buildPlatformLoginFrame("Hyundai", "f2e3445d7cda409fb4f278f6fb890734"); - EmbeddedChannel channel = new EmbeddedChannel(new Gb32960FrameDecoder()); - - assertThat(channel.writeInbound(frame)).isTrue(); - - byte[] decoded = channel.readInbound(); - assertThat(decoded).isEqualTo(frame); - assertThat((Object) channel.readInbound()).isNull(); - } - - private static byte[] buildPlatformLoginFrame(String username, String password) { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - body.write(26); body.write(6); body.write(22); body.write(20); body.write(40); body.write(45); - writeU16(body, 1); - writePaddedAscii(body, username, 12); - writePaddedAscii(body, password, password.length()); - body.write(0x01); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x05); - out.write(0xFE); - for (int i = 0; i < 17; i++) out.write(0); - out.write(0x01); - writeU16(out, 41); - byte[] bodyBytes = body.toByteArray(); - out.write(bodyBytes, 0, bodyBytes.length); - byte[] almost = out.toByteArray(); - out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF); - return out.toByteArray(); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writePaddedAscii(ByteArrayOutputStream os, String value, int len) { - byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - int copy = Math.min(bytes.length, len); - os.write(bytes, 0, copy); - for (int i = copy; i < len; i++) os.write(0); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FullBlocksTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FullBlocksTest.java deleted file mode 100644 index 1620957b..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960FullBlocksTest.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.AlarmV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.DriveMotorV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.TemperatureV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VoltageV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 验证新增的变长信息体 Parser(0x02 驱动电机 / 0x07 报警 / 0x08 电压 / 0x09 温度) - * 与主解码器协同工作。 - */ -class Gb32960FullBlocksTest { - - private final Gb32960MessageDecoder decoder = new Gb32960MessageDecoder( - new Gb32960BodyParser(new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - new PositionV2016BlockParser(), - new DriveMotorV2016BlockParser(), - new AlarmV2016BlockParser(), - new VoltageV2016BlockParser(), - new TemperatureV2016BlockParser())))); - - @Test - void parsesDriveMotorBlock() { - byte[] frame = buildFrame(os -> { - os.write(0x02); // drive motor - os.write(1); // 1 motor - os.write(1); // serial - os.write(0x01); // state - os.write(100); // controllerTemp = 60 - writeU16(os, 23_000); // rpm = 3000 - writeU16(os, 21_000); // torque raw, actual = 100 Nm - os.write(90); // motorTemp = 50 - writeU16(os, 5400); // voltage 540.0V - writeU16(os, 11000); // current raw → 100 A - }); - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame)); - InfoBlock.Gb32960V2016.DriveMotor dm = msg.findBlock(InfoBlock.Gb32960V2016.DriveMotor.class).orElseThrow(); - assertThat(dm.motors()).hasSize(1); - var m = dm.motors().get(0); - assertThat(m.rpm()).isEqualTo(3000); - assertThat(m.torqueNm()).isEqualTo(100.0); - assertThat(m.controllerInputVoltageV()).isEqualTo(540.0); - } - - @Test - void parsesAlarmBlock() { - byte[] frame = buildFrame(os -> { - os.write(0x07); // alarm - os.write(2); // max level - writeU32(os, 0x0000_0003L);// general flag - os.write(1); writeU32(os, 0xDEAD_BEEFL); // battery faults 1 - os.write(0); // motor faults 0 - os.write(0); // engine faults 0 - os.write(1); writeU32(os, 0xCAFE_BABEL); // other faults 1 - }); - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame)); - InfoBlock.Gb32960V2016.Alarm a = msg.findBlock(InfoBlock.Gb32960V2016.Alarm.class).orElseThrow(); - assertThat(a.maxLevel()).isEqualTo(2); - assertThat(a.batteryFaults()).hasSize(1); - assertThat(a.otherFaults()).hasSize(1); - } - - @Test - void parsesVoltageBlock() { - byte[] frame = buildFrame(os -> { - os.write(0x08); // voltage - os.write(1); // 1 subsystem - os.write(1); // battery index - writeU16(os, 3800); // 380.0 V - writeU16(os, 11_000); // current raw - writeU16(os, 96); // total cells - writeU16(os, 1); // start - os.write(3); // frame cells - writeU16(os, 3500); // 3.5V - writeU16(os, 3600); // 3.6V - writeU16(os, 3300); // 3.3V - }); - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame)); - InfoBlock.Gb32960V2016.Voltage v = msg.findBlock(InfoBlock.Gb32960V2016.Voltage.class).orElseThrow(); - assertThat(v.subSystemCount()).isEqualTo(1); - assertThat(v.maxCellVoltageV()).isEqualTo(3.6, org.assertj.core.data.Offset.offset(0.001)); - assertThat(v.minCellVoltageV()).isEqualTo(3.3, org.assertj.core.data.Offset.offset(0.001)); - } - - @Test - void parsesTemperatureBlock() { - byte[] frame = buildFrame(os -> { - os.write(0x09); // temperature - os.write(1); // subsystems - os.write(1); // battery index - writeU16(os, 4); // probes - os.write(60); // actual 20 - os.write(65); // actual 25 - os.write(70); // actual 30 - os.write(55); // actual 15 - }); - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame)); - InfoBlock.Gb32960V2016.Temperature t = msg.findBlock(InfoBlock.Gb32960V2016.Temperature.class).orElseThrow(); - assertThat(t.maxTempC()).isEqualTo(30); - assertThat(t.minTempC()).isEqualTo(15); - } - - // ===== frame builder helpers ===== - - private static byte[] buildFrame(java.util.function.Consumer bodyWriter) { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - // 时间戳 - body.write(24); body.write(1); body.write(2); body.write(3); body.write(4); body.write(5); - bodyWriter.accept(body); - - byte[] bodyBytes = body.toByteArray(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x02); // cmd: realtime - out.write(0xFE); // response flag - byte[] vin = "LTEST000000000010".getBytes(StandardCharsets.US_ASCII); - out.write(vin, 0, 17); - out.write(0x01); // not encrypted - writeU16(out, bodyBytes.length); - out.write(bodyBytes, 0, bodyBytes.length); - byte[] almost = out.toByteArray(); - byte bcc = BccChecksum.compute(almost, 2, almost.length - 2); - out.write(bcc & 0xFF); - return out.toByteArray(); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/GuangdongFcEndToEndTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/GuangdongFcEndToEndTest.java deleted file mode 100644 index 2a3a959e..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/GuangdongFcEndToEndTest.java +++ /dev/null @@ -1,296 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec; - -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.AlarmV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.DriveMotorV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.TemperatureV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VoltageV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcAirConditionerBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcAuxiliaryBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcDcDcBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcDemoExtensionBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcStackBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcVehicleInfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.profile.Gb32960ProfileRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.profile.RuleBasedVendorExtensionSelector; -import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionCatalog; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960ChannelHandler; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.within; - -/** - * 端到端集成测试:构造一帧带广东燃料电池规范扩展块的 V2016 帧,验证 - * vendor profile selector 路由 + GuangdongFc 系列 parser 全链路解码。 - * - *

    覆盖: - *

      - *
    • 9 个 GB/T 32960 标准块(0x01~0x09)原生解析 - *
    • 0x30 GuangdongFc.Stack(含 1 个电堆、4 个单体电压样本) - *
    • 0x32 GuangdongFc.DcDc - *
    • 0x33 GuangdongFc.AirConditioner - *
    • 0x34 GuangdongFc.VehicleInfo - *
    • 0x80 GuangdongFc.DemoExtension - *
    • VendorExtensionSelector 按 platformAccount=lingniu 命中 - *
    - * - *

    0x31 Auxiliary 没有放进帧里(变长且字段相对琐碎),单独由 unit test 覆盖。 - */ -class GuangdongFcEndToEndTest { - - private final Gb32960MessageDecoder decoder = buildDecoder(); - - @Test - void parsesRealtimeFrameWithGuangdongFcExtensions() { - byte[] frame = buildFrame(); - // 用 platformAccount=lingniu 触发 vendor profile - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame), "lingniu"); - - // ---- 标准块 ---- - var v = msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class).orElseThrow(); - assertThat(v.socPercent()).isEqualTo(85); - - var pos = msg.findBlock(InfoBlock.Gb32960V2016.Position.class).orElseThrow(); - assertThat(pos.longitude()).isCloseTo(120.848158, within(1e-6)); - - // ---- vendor 块:0x30 Stack ---- - var stack = msg.findBlock(InfoBlock.GuangdongFc.Stack.class).orElseThrow(); - assertThat(stack.stackCount()).isEqualTo(1); - var s0 = stack.stacks().get(0); - assertThat(s0.engineWorkState()).isEqualTo(1); - assertThat(s0.stackWaterOutletTempC()).isEqualTo(50); // 90-40 - assertThat(s0.maxCellVoltageV()).isCloseTo(0.745, within(1e-6)); // 745 mV - assertThat(s0.minCellVoltageV()).isCloseTo(0.730, within(1e-6)); - assertThat(s0.avgCellVoltageV()).isCloseTo(0.738, within(1e-6)); - assertThat(s0.cellCount()).isEqualTo(400); - assertThat(s0.frameCellCount()).isEqualTo(4); - assertThat(s0.frameCellVoltagesV()).hasSize(4) - .allSatisfy(c -> assertThat(c).isBetween(0.7, 0.8)); - - // ---- vendor 块:0x32 DcDc ---- - var dcdc = msg.findBlock(InfoBlock.GuangdongFc.DcDc.class).orElseThrow(); - assertThat(dcdc.inputVoltageV()).isCloseTo(300.0, within(1e-6)); - assertThat(dcdc.outputCurrentA()).isCloseTo(110.0, within(1e-6)); - - // ---- vendor 块:0x33 AirConditioner ---- - var ac = msg.findBlock(InfoBlock.GuangdongFc.AirConditioner.class).orElseThrow(); - assertThat(ac.status()).isEqualTo(1); - assertThat(ac.powerKw()).isEqualTo(15); - - // ---- vendor 块:0x34 VehicleInfo ---- - var info = msg.findBlock(InfoBlock.GuangdongFc.VehicleInfo.class).orElseThrow(); - assertThat(info.collisionAlarm()).isEqualTo(0); - assertThat(info.ambientTempC()).isEqualTo(30); - assertThat(info.hydrogenMassKg()).isCloseTo(8.0, within(1e-6)); - - // ---- vendor 块:0x80 DemoExtension ---- - var demo = msg.findBlock(InfoBlock.GuangdongFc.DemoExtension.class).orElseThrow(); - assertThat(demo.stackTempC()).isEqualTo(50); - assertThat(demo.airCompressorVoltageV()).isCloseTo(372.0, within(1e-6)); - - // 不应有 Raw 兜底:所有块都被结构化解析 - assertThat(msg.findBlock(InfoBlock.Raw.class)).isEmpty(); - } - - @Test - void unmatchedAccountFallsBackToDefaultAndProducesRawForVendorBlocks() { - byte[] frame = buildFrame(); - // 不传 account → selector 返回 null → default profile(无 vendor parser) - Gb32960Message msg = decoder.decode(ByteBuffer.wrap(frame), null); - - // 标准块仍能解析 - assertThat(msg.findBlock(InfoBlock.Gb32960V2016.Vehicle.class)).isPresent(); - // 但 0x30 应触发 unknown → Raw 兜底 - assertThat(msg.findBlock(InfoBlock.Raw.class)).isPresent(); - assertThat(msg.findBlock(InfoBlock.GuangdongFc.Stack.class)).isEmpty(); - } - - // ==================================================================== - // 帧构造:标准块 0x01/0x05/0x07/0x08/0x09 + vendor 块 0x30/0x32/0x33/0x34/0x80 - // 不带 0x02/0x04/0x06/0x31,纯粹为了控制帧长和测试焦点。 - // ==================================================================== - private static byte[] buildFrame() { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - // 时间戳 6B - body.write(24); body.write(4); body.write(15); body.write(11); body.write(0); body.write(0); - - // 0x01 整车数据 20B - body.write(0x01); - body.write(0x01); // vehicleState 启动 - body.write(0x03); // chargingState 未充电 - body.write(0x02); // runningMode 混动 - writeU16(body, 0); // 速度 - writeU32(body, 100_000L); // 累计里程 = 10000 km - writeU16(body, 5605); // totalVoltage 560.5 V - writeU16(body, 10000); // totalCurrent 0 A (after -1000 offset) - body.write(85); // SOC - body.write(0x01); // DC-DC 工作 - body.write(0x00); // 挡位 - writeU16(body, 10000); // 绝缘电阻 - body.write(0x00); // accelPedal - body.write(0x00); // brakePedal - - // 0x05 位置数据 9B - body.write(0x05); - body.write(0x00); // 状态:有效定位 - writeU32(body, 120_848_158L); // 经度 120.848158 - writeU32(body, 31_497_236L); // 纬度 31.497236 - - // 0x07 报警数据 10B:level=0 + flag=0 + 4 个空 fault list - body.write(0x07); - body.write(0x00); // maxLevel - writeU32(body, 0); // generalFlag - body.write(0x00); // batteryFaultCount - body.write(0x00); // motorFaultCount - body.write(0x00); // engineFaultCount - body.write(0x00); // otherFaultCount - - // 0x08 储能电压:1 sub × 0 cell(保持简洁) - body.write(0x08); - body.write(0x01); // subCount - body.write(0x01); // batteryIndex - writeU16(body, 5605); // voltage - writeU16(body, 10000); // current - writeU16(body, 0); // cellCount - writeU16(body, 1); // frameCellStart - body.write(0x00); // frameCellCount = 0 - - // 0x09 储能温度:1 sub × 0 probe - body.write(0x09); - body.write(0x01); // subCount - body.write(0x01); // batteryIndex - writeU16(body, 0); // probeCount = 0 - - // 0x30 GuangdongFc.Stack:1 stack + 4 单体电压 - body.write(0x30); - body.write(0x01); // stackCount - body.write(0x01); // engineWorkState 打开 - body.write(90); // 水温 90-40 = 50°C - writeU16(body, 1850); // h2 入口压力 raw=1850 → 85 kPa - writeU16(body, 1500); // air 入口压力 raw=1500 → 50 kPa - body.write(70); // air 入口温度 70-40 = 30°C - writeU16(body, 12); // maxCellId - writeU16(body, 88); // minCellId - writeU16(body, 745); // maxCellV 745 mV - writeU16(body, 730); // minCellV 730 mV - writeU16(body, 738); // avgCellV 738 mV - writeU16(body, 400); // cellCount - writeU16(body, 1); // frameCellStart - body.write(0x04); // frameCellCount = 4 - writeU16(body, 745); - writeU16(body, 740); - writeU16(body, 735); - writeU16(body, 730); - - // 0x32 GuangdongFc.DcDc 9B - body.write(0x32); - writeU16(body, 3000); // inputV 300.0 - writeU16(body, 1200); // inputC 120.0 - writeU16(body, 5600); // outputV 560.0 - writeU16(body, 1100); // outputC 110.0 - body.write(105); // ctrlTemp 105-40 = 65°C - - // 0x33 GuangdongFc.AirConditioner 5B - body.write(0x33); - body.write(0x01); // status 启动 - writeU16(body, 20); // power 20-5 = 15 kw - writeU16(body, 5400); // compressorInputV 540.0 - - // 0x34 GuangdongFc.VehicleInfo 7B - body.write(0x34); - body.write(0x00); // collision 无 - writeU16(body, 70); // ambientTemp 70-40 = 30°C - writeU16(body, 1100); // ambientPressure raw=1100 → 10.0 kPa - writeU16(body, 80); // h2Mass 80*0.1 = 8.0 kg - - // 0x80 GuangdongFc.DemoExtension 12B(含 2B 内层 length=9) - body.write(0x80); - writeU16(body, 9); // declared length - body.write(90); // stackTemp 50°C - writeU16(body, 3720); // ac voltage 372.0 - writeU16(body, 10000); // ac current 0 A - writeU16(body, 13); // h2 pump voltage 1.3 - writeU16(body, 10000); // h2 pump current 0 A - - byte[] bodyBytes = body.toByteArray(); - - // 帧封装 - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x02); // cmd realtime - out.write(0xFE); // 命令包 - byte[] vin = "LNVFC000000000001".getBytes(StandardCharsets.US_ASCII); - out.write(vin, 0, 17); - out.write(0x01); // 不加密 - writeU16(out, bodyBytes.length); - out.write(bodyBytes, 0, bodyBytes.length); - byte[] almost = out.toByteArray(); - byte bcc = BccChecksum.compute(almost, 2, almost.length - 2); - out.write(bcc & 0xFF); - return out.toByteArray(); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } - - // ==================================================================== - // Decoder 装配:手动构造一个挂着 guangdong-fc 路由的 BodyParser - // ==================================================================== - private static Gb32960MessageDecoder buildDecoder() { - var standardParsers = List.of( - (com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser) new VehicleV2016BlockParser(), - new DriveMotorV2016BlockParser(), - new PositionV2016BlockParser(), - new AlarmV2016BlockParser(), - new VoltageV2016BlockParser(), - new TemperatureV2016BlockParser()); - var catalog = new VendorExtensionCatalog(java.util.Map.of( - "guangdong-fc", List.of( - new GdFcStackBlockParser(), - new GdFcAuxiliaryBlockParser(), - new GdFcDcDcBlockParser(), - new GdFcAirConditionerBlockParser(), - new GdFcVehicleInfoBlockParser(), - new GdFcDemoExtensionBlockParser()))); - var profileRegistry = new Gb32960ProfileRegistry( - standardParsers, catalog, Set.of("guangdong-fc")); - - // selector:lingniu 账号 → guangdong-fc - var entry = new Gb32960Properties.VendorExtension(); - entry.setName("guangdong-fc"); - var match = new Gb32960Properties.VendorExtension.Match(); - match.setPlatformAccounts(List.of("lingniu")); - entry.setMatch(match); - var selector = new RuleBasedVendorExtensionSelector( - List.of(entry), Set.of("guangdong-fc")); - - var bodyParser = new Gb32960BodyParser(profileRegistry, selector); - // PLATFORM_ACCOUNT_ATTR 仅供 handler 使用,这里不需要 - return new Gb32960MessageDecoder(bodyParser); - } - - // suppress unused-import warning for the channel handler attr key - @SuppressWarnings("unused") - private static final Object KEEP_ATTR_REF = Gb32960ChannelHandler.PLATFORM_ACCOUNT_ATTR; -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcParserTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcParserTest.java deleted file mode 100644 index 0aedda7f..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcParserTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.within; - -/** - * 单元测试:广东燃料电池规范 fixed-length parser(0x32 / 0x33 / 0x34 / 0x80)逐字段还原。 - * 0x30/0x31 是变长,集成层会在 BodyParser 全链路测试里覆盖。 - */ -class GdFcParserTest { - - @Test - void dcDcBlockParsesAllFields() { - // inV=300.0 inC=120.0 outV=560.0 outC=110.5 ctrlTemp=65°C - ByteBuffer buf = ByteBuffer.wrap(new byte[]{ - 0x0B, (byte) 0xB8, // 0x0BB8 = 3000 → 300.0 V - 0x04, (byte) 0xB0, // 0x04B0 = 1200 → 120.0 A - 0x15, (byte) 0xE0, // 0x15E0 = 5600 → 560.0 V - 0x04, 0x52, // 0x0452 = 1106 → 110.6 A - 0x69 // 0x69 = 105 - 40 = 65 °C - }); - InfoBlock.GuangdongFc.DcDc b = - (InfoBlock.GuangdongFc.DcDc) new GdFcDcDcBlockParser().parse(buf); - assertThat(b.inputVoltageV()).isCloseTo(300.0, within(1e-6)); - assertThat(b.inputCurrentA()).isCloseTo(120.0, within(1e-6)); - assertThat(b.outputVoltageV()).isCloseTo(560.0, within(1e-6)); - assertThat(b.outputCurrentA()).isCloseTo(110.6, within(1e-6)); - assertThat(b.controllerTempC()).isEqualTo(65); - } - - @Test - void airConditionerParsesPowerOffsetAndStatus() { - // status=01 power raw=20 (= 15 kw after -5 offset) inputV=540.0 - ByteBuffer buf = ByteBuffer.wrap(new byte[]{ - 0x01, - 0x00, 0x14, // 20 - 5 = 15 kw - 0x15, (byte) 0x18 // 0x1518 = 5400 → 540.0 V - }); - InfoBlock.GuangdongFc.AirConditioner b = - (InfoBlock.GuangdongFc.AirConditioner) new GdFcAirConditionerBlockParser().parse(buf); - assertThat(b.status()).isEqualTo(1); - assertThat(b.powerKw()).isEqualTo(15); - assertThat(b.compressorInputVoltageV()).isCloseTo(540.0, within(1e-6)); - } - - @Test - void vehicleInfoParsesAmbientFields() { - // collision=00 ambientTemp raw=70 (=30°C) ambientPressure raw=1100 (10kPa) h2Mass raw=80 (8.0 kg) - ByteBuffer buf = ByteBuffer.wrap(new byte[]{ - 0x00, - 0x00, 0x46, // 70 - 40 = 30°C - 0x04, 0x4C, // 0x044C = 1100 → 110*0.1 - 100 = 10.0 kPa - 0x00, 0x50 // 0x0050 = 80 → 8.0 kg - }); - InfoBlock.GuangdongFc.VehicleInfo b = - (InfoBlock.GuangdongFc.VehicleInfo) new GdFcVehicleInfoBlockParser().parse(buf); - assertThat(b.collisionAlarm()).isEqualTo(0); - assertThat(b.ambientTempC()).isEqualTo(30); - assertThat(b.ambientPressureKpa()).isCloseTo(10.0, within(1e-6)); - assertThat(b.hydrogenMassKg()).isCloseTo(8.0, within(1e-6)); - } - - @Test - void demoExtensionParsesInnerLengthAndFields() { - // length=9, stackTemp=50, acV=372.0, acC=0, h2V=1.3V, h2C=0 - ByteBuffer buf = ByteBuffer.wrap(new byte[]{ - 0x00, 0x09, // declared length - 0x5A, // 90 - 40 = 50°C - 0x0E, (byte) 0x88, // 0x0E88 = 3720 → 372.0V - 0x27, 0x10, // 0x2710 = 10000 → 0 A (after -1000) - 0x00, 0x0D, // 0x000D = 13 → 1.3V - 0x27, 0x10 // 0 A - }); - InfoBlock.GuangdongFc.DemoExtension b = - (InfoBlock.GuangdongFc.DemoExtension) new GdFcDemoExtensionBlockParser().parse(buf); - assertThat(b.stackTempC()).isEqualTo(50); - assertThat(b.airCompressorVoltageV()).isCloseTo(372.0, within(1e-6)); - assertThat(b.airCompressorCurrentA()).isCloseTo(0.0, within(1e-6)); - assertThat(b.hydrogenPumpVoltageV()).isCloseTo(1.3, within(1e-6)); - assertThat(b.hydrogenPumpCurrentA()).isCloseTo(0.0, within(1e-6)); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParserTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParserTest.java deleted file mode 100644 index e8d93891..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/parser/vendor/guangdong/GdFcVendorTlvBlockParserTest.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong; - -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 验证 {@link GdFcVendorTlvBlockParser} 严格按 length 消费字节,不会贪心吞光后续信息体。 - * - *

    这是关键回归 case:用户问"加 0x83 兜底会不会把后面其它块也吃掉?"——通过构造 - * 一个 {@code 0x83 TLV + 0x01 Vehicle} 串联的 body,断言 BodyParser 解出**两个块**而非一个。 - */ -class GdFcVendorTlvBlockParserTest { - - @Test - void parsesPayloadOfDeclaredLengthExactly() { - // 0x83 typeCode 已被外层消费,buffer 从 length 字段开始 - byte[] data = bytes( - 0x00, 0x05, // length = 5 - 0xAA, 0xBB, 0xCC, 0xDD, 0xEE); // 5 bytes payload - ByteBuffer buf = ByteBuffer.wrap(data); - - InfoBlock.GuangdongFc.VendorTlv tlv = - (InfoBlock.GuangdongFc.VendorTlv) new GdFcVendorTlvBlockParser(0x83).parse(buf); - - assertThat(tlv.typeCode()).isEqualTo(0x83); - assertThat(tlv.declaredLength()).isEqualTo(5); - assertThat(tlv.payload()).containsExactly(0xAA, 0xBB, 0xCC, 0xDD, 0xEE); - assertThat(buf.remaining()).as("应正好消费 2+5=7 字节").isZero(); - } - - @Test - void doesNotSwallowSubsequentBlocksWhenChainedInBodyParser() { - // 构造 body:0x83 TLV(length=4) + 0x01 Vehicle(20B 标准布局) - ByteArrayOutputStream body = new ByteArrayOutputStream(); - // 0x83 TLV:3+4 = 7 字节 - body.write(0x83); - body.write(0x00); body.write(0x04); // length = 4 - body.write(0x11); body.write(0x22); body.write(0x33); body.write(0x44); - // 0x01 Vehicle: typeCode + 20 字节 body - body.write(0x01); - body.write(0x01); // vehicleState - body.write(0x03); // chargingState - body.write(0x02); // runningMode - write16(body, 0); // speed - write32(body, 100_000L); // mileage - write16(body, 5605); // totalVoltage - write16(body, 10000); // totalCurrent - body.write(85); // SOC - body.write(0x01); // dcDcStatus - body.write(0x00); // gear - write16(body, 10000); // insulation - body.write(0x00); // accelPedal - body.write(0x00); // brakePedal - - // 装配 body parser:注册 0x83 vendor TLV + 0x01 标准 Vehicle - InfoBlockParserRegistry registry = new InfoBlockParserRegistry(List.of( - new GdFcVendorTlvBlockParser(0x83), - new VehicleV2016BlockParser())); - Gb32960BodyParser parser = new Gb32960BodyParser(registry); - - ByteBuffer buf = ByteBuffer.wrap(body.toByteArray()); - Gb32960MessageDecoder.BodyParseResult result = parser.parse(ProtocolVersion.V2016, buf); - - // 关键断言:两个块都解析出来了,0x01 没被 0x83 吞掉 - assertThat(result.blocks()).hasSize(2); - assertThat(result.blocks().get(0)).isInstanceOf(InfoBlock.GuangdongFc.VendorTlv.class); - assertThat(result.blocks().get(1)).isInstanceOf(InfoBlock.Gb32960V2016.Vehicle.class); - - InfoBlock.GuangdongFc.VendorTlv tlv = (InfoBlock.GuangdongFc.VendorTlv) result.blocks().get(0); - assertThat(tlv.declaredLength()).isEqualTo(4); - assertThat(tlv.payload()).containsExactly(0x11, 0x22, 0x33, 0x44); - - InfoBlock.Gb32960V2016.Vehicle v = (InfoBlock.Gb32960V2016.Vehicle) result.blocks().get(1); - assertThat(v.socPercent()).isEqualTo(85); - assertThat(v.totalVoltageV()).isEqualTo(560.5); - } - - @Test - void truncatesPayloadIfDeclaredLengthExceedsRemaining() { - // 防御场景:length 字段声称 100 字节但 buffer 只剩 3 字节 - byte[] data = bytes(0x00, 0x64, 0x01, 0x02, 0x03); - ByteBuffer buf = ByteBuffer.wrap(data); - InfoBlock.GuangdongFc.VendorTlv tlv = - (InfoBlock.GuangdongFc.VendorTlv) new GdFcVendorTlvBlockParser(0x83).parse(buf); - assertThat(tlv.declaredLength()).isEqualTo(100); - assertThat(tlv.payload()).hasSize(3).containsExactly(0x01, 0x02, 0x03); - } - - private static byte[] bytes(int... values) { - byte[] out = new byte[values.length]; - for (int i = 0; i < values.length; i++) out[i] = (byte) values[i]; - return out; - } - - private static void write16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void write32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelectorTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelectorTest.java deleted file mode 100644 index c4e4ddff..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/codec/profile/RuleBasedVendorExtensionSelectorTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.codec.profile; - -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960ParserContext; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import org.junit.jupiter.api.Test; - -import java.util.List; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class RuleBasedVendorExtensionSelectorTest { - - @Test - void platformAccountMatchHits() { - var ext = entry("guangdong-fc", List.of("lingniu"), List.of(), List.of()); - var selector = new RuleBasedVendorExtensionSelector(List.of(ext), Set.of("guangdong-fc")); - var ctx = new Gb32960ParserContext(ProtocolVersion.V2016, "LNV1234567890ABCD", "lingniu"); - assertThat(selector.select(ctx)).isEqualTo("guangdong-fc"); - } - - @Test - void vinExactMatchHits() { - var ext = entry("guangdong-fc", List.of(), List.of("LNV1234567890ABCD"), List.of()); - var selector = new RuleBasedVendorExtensionSelector(List.of(ext), Set.of("guangdong-fc")); - var ctx = new Gb32960ParserContext(ProtocolVersion.V2016, "lnv1234567890abcd", null); - assertThat(selector.select(ctx)) - .as("VIN match should be case-insensitive") - .isEqualTo("guangdong-fc"); - } - - @Test - void vinPrefixMatchHits() { - var ext = entry("guangdong-fc", List.of(), List.of(), List.of("LNVFC", "LZG")); - var selector = new RuleBasedVendorExtensionSelector(List.of(ext), Set.of("guangdong-fc")); - var ctx = new Gb32960ParserContext(ProtocolVersion.V2016, "LNVFC0001", null); - assertThat(selector.select(ctx)).isEqualTo("guangdong-fc"); - } - - @Test - void noMatchReturnsNull() { - var ext = entry("guangdong-fc", List.of("other"), List.of(), List.of()); - var selector = new RuleBasedVendorExtensionSelector(List.of(ext), Set.of("guangdong-fc")); - var ctx = new Gb32960ParserContext(ProtocolVersion.V2016, "ZZZ", "lingniu"); - assertThat(selector.select(ctx)).isNull(); - } - - @Test - void firstMatchWinsTopDown() { - var first = entry("guangdong-fc", List.of("lingniu"), List.of(), List.of()); - var second = entry("guangdong-fc", List.of(), List.of(), List.of("LNV")); - var selector = new RuleBasedVendorExtensionSelector( - List.of(first, second), Set.of("guangdong-fc")); - // 即便两条都能命中,应返回第一条匹配 entry 的 name(这里凑巧一样) - var ctx = new Gb32960ParserContext(ProtocolVersion.V2016, "LNV1", "lingniu"); - assertThat(selector.select(ctx)).isEqualTo("guangdong-fc"); - } - - @Test - void emptyConfigReturnsNullWithoutScanning() { - var selector = new RuleBasedVendorExtensionSelector(List.of(), Set.of("guangdong-fc")); - var ctx = new Gb32960ParserContext(ProtocolVersion.V2016, "ANY", "ANY"); - assertThat(selector.select(ctx)).isNull(); - } - - @Test - void unknownExtensionNameRejectedAtConstruction() { - var ext = entry("not-in-catalog", List.of("x"), List.of(), List.of()); - assertThatThrownBy(() -> - new RuleBasedVendorExtensionSelector(List.of(ext), Set.of("guangdong-fc"))) - .isInstanceOf(IllegalStateException.class) - .hasMessageContaining("not-in-catalog"); - } - - @Test - void resultIsCachedPerAccountVinTuple() { - var ext = entry("guangdong-fc", List.of("lingniu"), List.of(), List.of()); - var selector = new RuleBasedVendorExtensionSelector(List.of(ext), Set.of("guangdong-fc")); - var ctx1 = new Gb32960ParserContext(ProtocolVersion.V2016, "VIN1", "lingniu"); - var ctx2 = new Gb32960ParserContext(ProtocolVersion.V2016, "VIN1", "lingniu"); - selector.select(ctx1); - selector.select(ctx2); - // 同一个 (account, vin) 应只产生一个 cache 条目 - assertThat(selector.cacheSize()).isEqualTo(1); - - var ctx3 = new Gb32960ParserContext(ProtocolVersion.V2016, "VIN2", "lingniu"); - selector.select(ctx3); - assertThat(selector.cacheSize()).isEqualTo(2); - } - - private static Gb32960Properties.VendorExtension entry(String name, - List accounts, - List vins, - List vinPrefixes) { - var e = new Gb32960Properties.VendorExtension(); - e.setName(name); - var m = new Gb32960Properties.VendorExtension.Match(); - m.setPlatformAccounts(accounts); - m.setVins(vins); - m.setVinPrefixes(vinPrefixes); - e.setMatch(m); - return e; - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfigurationTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfigurationTest.java deleted file mode 100644 index 69bf3899..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfigurationTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.config; - -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.inbound.Gb32960NettyServer; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960AutoConfigurationTest { - - private final ApplicationContextRunner runner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(Gb32960AutoConfiguration.class)) - .withBean(Dispatcher.class, () -> new Dispatcher(null, null, null, null, null)) - .withPropertyValues( - "lingniu.ingest.gb32960.enabled=true", - "lingniu.ingest.gb32960.port=0"); - - @Test - void createsDecoderWithoutTcpServerWhenServerDisabled() { - runner.withPropertyValues("lingniu.ingest.gb32960.server.enabled=false") - .run(context -> { - assertThat(context).hasSingleBean(Gb32960MessageDecoder.class); - assertThat(context).doesNotHaveBean(Gb32960NettyServer.class); - }); - } - - @Test - void createsTcpServerByDefaultWhenProtocolEnabled() { - runner.run(context -> { - assertThat(context).hasSingleBean(Gb32960MessageDecoder.class); - assertThat(context).hasSingleBean(Gb32960NettyServer.class); - }); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandlerTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandlerTest.java deleted file mode 100644 index b46862d3..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandlerTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.handler; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper; -import com.lingniu.ingest.protocol.gb32960.model.CommandBody; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.EncryptType; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Header; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * 保障 {@link Gb32960RealtimeHandler} 正确委托给 {@link Gb32960EventMapper}, - * 不吞掉任何需要上送下游的事件。 - */ -class Gb32960RealtimeHandlerTest { - - private final Gb32960RealtimeHandler handler = new Gb32960RealtimeHandler(new Gb32960EventMapper()); - - @Test - void onPlatform_login_emitsLoginEventWithKindPlatform() { - Gb32960Message msg = buildPlatformLoginMessage("lingniu"); - - List events = handler.onPlatform(msg); - - assertThat(events).hasSize(1); - VehicleEvent first = events.get(0); - assertThat(first).isInstanceOf(VehicleEvent.Login.class); - assertThat(first.vin()).isEqualTo("platform:lingniu"); - assertThat(first.metadata()) - .containsEntry("kind", "platform") - .containsEntry("username", "lingniu"); - } - - @Test - void onPlatform_logout_emitsLogoutEventWithKindPlatform() { - Gb32960Message msg = new Gb32960Message( - new Gb32960Header( - ProtocolVersion.V2016, - CommandType.PLATFORM_LOGOUT, - ResponseFlag.COMMAND, - "00000000000000000", - EncryptType.UNENCRYPTED, - 0, - null), - new CommandBody.PlatformLogout(Instant.parse("2026-04-20T10:00:00Z"), 1)); - - List events = handler.onPlatform(msg); - - assertThat(events).hasSize(1); - assertThat(events.get(0)).isInstanceOf(VehicleEvent.Logout.class); - assertThat(events.get(0).metadata()).containsEntry("kind", "platform"); - } - - private static Gb32960Message buildPlatformLoginMessage(String username) { - return new Gb32960Message( - new Gb32960Header( - ProtocolVersion.V2016, - CommandType.PLATFORM_LOGIN, - ResponseFlag.COMMAND, - "00000000000000000", - EncryptType.UNENCRYPTED, - 0, - null), - new CommandBody.PlatformLogin( - Instant.parse("2026-04-20T10:00:00Z"), - 1, - username, - "secret", - EncryptType.UNENCRYPTED)); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessServiceTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessServiceTest.java deleted file mode 100644 index d6453115..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AccessServiceTest.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960PlatformAuthorizer; -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960VinAuthorizer; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.net.InetSocketAddress; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960AccessServiceTest { - - @Test - void bindPlatformAccount_makesAccountAvailableForLaterFrames() { - Gb32960AccessService service = newService(false); - EmbeddedChannel channel = new EmbeddedChannel(); - - service.bindPlatformAccount(channel, "lingniu"); - - assertThat(service.platformAccount(channel)).isEqualTo("lingniu"); - } - - @Test - void authenticatePlatformLogin_rejectsUnknownPeerIpWhenPolicyRestrictsIt() { - Gb32960Properties.Auth auth = new Gb32960Properties.Auth(); - Gb32960Properties.Auth.Platform platform = new Gb32960Properties.Auth.Platform(); - platform.setUsername("lingniu"); - platform.setPassword("secret"); - platform.setAllowedIps(List.of("10.0.0.1")); - auth.setPlatforms(List.of(platform)); - - Gb32960AccessService service = new Gb32960AccessService( - new Gb32960VinAuthorizer(auth), - new Gb32960PlatformAuthorizer(auth.getPlatforms())); - EmbeddedChannel channel = new EmbeddedChannel(); - channel.connect(new InetSocketAddress("10.0.0.2", 9001)); - - assertThat(service.authenticatePlatformLogin("lingniu", "secret", channel) - .accepted()).isFalse(); - } - - @Test - void authenticatePlatformLogin_ignoresBlankAllowedIps() { - Gb32960Properties.Auth auth = new Gb32960Properties.Auth(); - Gb32960Properties.Auth.Platform platform = new Gb32960Properties.Auth.Platform(); - platform.setUsername("lingniu"); - platform.setPassword("secret"); - platform.setAllowedIps(List.of("", " ", "\t")); - auth.setPlatforms(List.of(platform)); - - Gb32960AccessService service = new Gb32960AccessService( - new Gb32960VinAuthorizer(auth), - new Gb32960PlatformAuthorizer(auth.getPlatforms())); - EmbeddedChannel channel = new EmbeddedChannel(); - channel.connect(new InetSocketAddress("115.29.187.205", 9001)); - - assertThat(service.authenticatePlatformLogin("lingniu", "secret", channel) - .accepted()).isTrue(); - } - - @Test - void authenticatePlatformLogin_acceptsCommaSeparatedAllowedIps() { - Gb32960Properties.Auth auth = new Gb32960Properties.Auth(); - Gb32960Properties.Auth.Platform platform = new Gb32960Properties.Auth.Platform(); - platform.setUsername("lingniu"); - platform.setPassword("secret"); - platform.setAllowedIps(List.of("115.29.187.205,127.0.0.1")); - auth.setPlatforms(List.of(platform)); - - Gb32960PlatformAuthorizer authorizer = new Gb32960PlatformAuthorizer(auth.getPlatforms()); - - assertThat(authorizer.authenticate("lingniu", "secret", "127.0.0.1") - .accepted()).isTrue(); - } - - private static Gb32960AccessService newService(boolean vinAuthEnabled) { - Gb32960Properties.Auth auth = new Gb32960Properties.Auth(); - auth.setEnabled(vinAuthEnabled); - return new Gb32960AccessService( - new Gb32960VinAuthorizer(auth), - new Gb32960PlatformAuthorizer(auth.getPlatforms())); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckServiceTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckServiceTest.java deleted file mode 100644 index 4f914119..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960AckServiceTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag; -import io.netty.buffer.ByteBuf; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960AckServiceTest { - - @Test - void writeResponse_writesGb32960AckFrameToChannel() { - Gb32960AckService service = new Gb32960AckService(); - EmbeddedChannel channel = new EmbeddedChannel(); - byte[] rawVin = "LTEST000000000001".getBytes(StandardCharsets.US_ASCII); - - service.writeResponse( - channel, - ProtocolVersion.V2016, - CommandType.VEHICLE_LOGIN, - ResponseFlag.SUCCESS, - rawVin, - Instant.parse("2026-04-20T10:00:00Z"), - null, - "vehicle-login-ack"); - - ByteBuf out = channel.readOutbound(); - assertThat(out).isNotNull(); - byte[] bytes = new byte[out.readableBytes()]; - out.readBytes(bytes); - assertThat(bytes[0]).isEqualTo((byte) 0x23); - assertThat(bytes[1]).isEqualTo((byte) 0x23); - assertThat(bytes[2]).isEqualTo((byte) CommandType.VEHICLE_LOGIN.code()); - assertThat(bytes[3]).isEqualTo((byte) ResponseFlag.SUCCESS.code()); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandlerAckBoundaryTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandlerAckBoundaryTest.java deleted file mode 100644 index 4e9cb5d9..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandlerAckBoundaryTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor; -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.core.dispatcher.HandlerInvoker; -import com.lingniu.ingest.core.dispatcher.HandlerRegistry; -import com.lingniu.ingest.core.pipeline.InterceptorChain; -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960PlatformAuthorizer; -import com.lingniu.ingest.protocol.gb32960.auth.Gb32960VinAuthorizer; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960DecoderTest; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.config.Gb32960Properties; -import io.netty.buffer.ByteBuf; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960ChannelHandlerAckBoundaryTest { - - @Test - void realtimeReportAckWaitsForDispatchFutureCompletion() { - try (DispatchHarness dispatch = new DispatchHarness()) { - EmbeddedChannel channel = new EmbeddedChannel(handlerWith(dispatch.dispatcher())); - - assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse(); - assertThat((Object) channel.readOutbound()).isNull(); - - dispatch.awaitPublishCount(1); - dispatch.completeDurability(); - - ByteBuf ack = awaitOutbound(channel); - assertThat(ack).isNotNull(); - assertThat(ack.getUnsignedByte(2)).isEqualTo((short) 0x02); - assertThat(ack.getUnsignedByte(3)).isEqualTo((short) 0x01); - ack.release(); - channel.finishAndReleaseAll(); - } - } - - @Test - void realtimeReportAckIsNotWrittenWhenDispatchFutureFails() { - try (DispatchHarness dispatch = new DispatchHarness()) { - EmbeddedChannel channel = new EmbeddedChannel(handlerWith(dispatch.dispatcher())); - - assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse(); - - dispatch.awaitPublishCount(1); - dispatch.failDurability(); - - assertThat((Object) channel.readOutbound()).isNull(); - assertThat(awaitInactive(channel)).isTrue(); - channel.finishAndReleaseAll(); - } - } - - @Test - void durableAcksAreWrittenInInboundOrderWhenLaterDispatchCompletesFirst() { - try (DispatchHarness dispatch = new DispatchHarness()) { - EmbeddedChannel channel = new EmbeddedChannel(handlerWith(dispatch.dispatcher())); - - assertThat(channel.writeInbound(Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000001"))).isFalse(); - assertThat(channel.writeInbound(buildHeartbeatFrame("LTEST000000000001"))).isFalse(); - assertThat((Object) channel.readOutbound()).isNull(); - - dispatch.awaitPublishCount(2); - dispatch.completeDurability(1); - channel.runPendingTasks(); - assertThat((Object) channel.readOutbound()).isNull(); - - dispatch.completeDurability(0); - - ByteBuf firstAck = awaitOutbound(channel); - ByteBuf secondAck = awaitOutbound(channel); - assertThat(firstAck).isNotNull(); - assertThat(secondAck).isNotNull(); - assertThat(firstAck.getUnsignedByte(2)).isEqualTo((short) 0x02); - assertThat(secondAck.getUnsignedByte(2)).isEqualTo((short) 0x07); - firstAck.release(); - secondAck.release(); - channel.finishAndReleaseAll(); - } - } - - private static Gb32960ChannelHandler handlerWith(Dispatcher dispatcher) { - return new Gb32960ChannelHandler( - decoder(), - dispatcher, - accessService(), - new Gb32960AckService(), - new Gb32960FrameDiagnostics(16), - new Gb32960ConnectionDiagnostics()); - } - - private static Gb32960MessageDecoder decoder() { - return new Gb32960MessageDecoder(new Gb32960BodyParser(new InfoBlockParserRegistry(List.of( - new VehicleV2016BlockParser(), - new PositionV2016BlockParser())))); - } - - private static Gb32960AccessService accessService() { - Gb32960Properties.Auth auth = new Gb32960Properties.Auth(); - auth.setEnabled(false); - return new Gb32960AccessService( - new Gb32960VinAuthorizer(auth), - new Gb32960PlatformAuthorizer(auth.getPlatforms())); - } - - private static byte[] buildHeartbeatFrame(String vin) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); - out.write(0x23); - out.write(0x07); - out.write(0xFE); - byte[] vinBytes = vin.getBytes(StandardCharsets.US_ASCII); - out.write(vinBytes, 0, 17); - out.write(0x01); - out.write(0x00); - out.write(0x00); - - byte[] almost = out.toByteArray(); - out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF); - return out.toByteArray(); - } - - private static ByteBuf awaitOutbound(EmbeddedChannel channel) { - long deadline = System.currentTimeMillis() + 3000; - while (System.currentTimeMillis() < deadline) { - channel.runPendingTasks(); - ByteBuf outbound = channel.readOutbound(); - if (outbound != null) { - return outbound; - } - Thread.onSpinWait(); - } - return null; - } - - private static boolean awaitInactive(EmbeddedChannel channel) { - long deadline = System.currentTimeMillis() + 3000; - while (System.currentTimeMillis() < deadline) { - channel.runPendingTasks(); - if (!channel.isActive()) { - return true; - } - Thread.onSpinWait(); - } - return false; - } - - private static final class DispatchHarness implements AutoCloseable { - private final ControlledSink sink = new ControlledSink(); - private final DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - private final AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - private final Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - - Dispatcher dispatcher() { - return dispatcher; - } - - void completeDurability() { - completeDurability(0); - } - - void completeDurability(int index) { - sink.future(index).complete(null); - } - - void failDurability() { - sink.future(0).completeExceptionally(new IllegalStateException("durability failed")); - } - - void awaitPublishCount(int count) { - long deadline = System.currentTimeMillis() + 3000; - while (System.currentTimeMillis() < deadline) { - synchronized (sink.futures) { - if (sink.futures.size() >= count) { - return; - } - } - Thread.onSpinWait(); - } - throw new AssertionError("expected " + count + " sink publishes, actual=" + sink.futures.size()); - } - - @Override - public void close() { - batchExecutor.close(); - eventBus.close(); - } - } - - private static final class ControlledSink implements EventSink { - private final List> futures = new ArrayList<>(); - - @Override - public String name() { - return "kafka"; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - CompletableFuture future = new CompletableFuture<>(); - synchronized (futures) { - futures.add(future); - } - return future; - } - - private CompletableFuture future(int index) { - synchronized (futures) { - return futures.get(index); - } - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnosticsTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnosticsTest.java deleted file mode 100644 index cc68c65c..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ConnectionDiagnosticsTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import org.junit.jupiter.api.Test; - -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneOffset; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960ConnectionDiagnosticsTest { - - private static final Instant NOW = Instant.parse("2026-06-29T10:00:00Z"); - - @Test - void tracksActiveSessionCommandPlatformAccountAndReadIdleCount() { - MutableClock clock = new MutableClock(NOW); - Gb32960ConnectionDiagnostics diagnostics = new Gb32960ConnectionDiagnostics(clock); - - diagnostics.opened("session-1", "127.0.0.1:65469"); - clock.advanceSeconds(5); - diagnostics.received("session-1", CommandType.PLATFORM_LOGIN, ""); - diagnostics.platformLoginAccepted("session-1", "Hyundai"); - clock.advanceSeconds(30); - diagnostics.readIdle("session-1"); - - assertThat(diagnostics.activeSessions()).singleElement().satisfies(session -> { - assertThat(session.sessionId()).isEqualTo("session-1"); - assertThat(session.peer()).isEqualTo("127.0.0.1:65469"); - assertThat(session.connectedAt()).isEqualTo(NOW); - assertThat(session.lastInboundAt()).isEqualTo(NOW.plusSeconds(5)); - assertThat(session.idleSeconds()).isEqualTo(30); - assertThat(session.lastCommandCode()).isEqualTo(0x05); - assertThat(session.lastCommand()).isEqualTo("PLATFORM_LOGIN"); - assertThat(session.vin()).isEmpty(); - assertThat(session.platformAccount()).isEqualTo("Hyundai"); - assertThat(session.readIdleCount()).isEqualTo(1); - }); - } - - @Test - void removesSessionWhenChannelCloses() { - Gb32960ConnectionDiagnostics diagnostics = new Gb32960ConnectionDiagnostics(Clock.fixed(NOW, ZoneOffset.UTC)); - - diagnostics.opened("session-1", "127.0.0.1:65469"); - diagnostics.closed("session-1"); - - assertThat(diagnostics.activeSessions()).isEmpty(); - } - - private static final class MutableClock extends Clock { - private Instant instant; - - private MutableClock(Instant instant) { - this.instant = instant; - } - - void advanceSeconds(long seconds) { - instant = instant.plusSeconds(seconds); - } - - @Override - public ZoneOffset getZone() { - return ZoneOffset.UTC; - } - - @Override - public Clock withZone(java.time.ZoneId zone) { - return this; - } - - @Override - public Instant instant() { - return instant; - } - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnosticsTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnosticsTest.java deleted file mode 100644 index 254adf4d..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960FrameDiagnosticsTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.inbound; - -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlockType; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960FrameDiagnosticsTest { - - @Test - void shouldLogFirstSeenRawSignatureOnlyOncePerChannel() { - Gb32960FrameDiagnostics diagnostics = new Gb32960FrameDiagnostics(8); - EmbeddedChannel channel = new EmbeddedChannel(); - - List rawTypes = List.of("0x30", "0x83"); - - assertThat(diagnostics.markFirstSeen(channel, "VIN001", rawTypes)).isTrue(); - assertThat(diagnostics.markFirstSeen(channel, "VIN001", rawTypes)).isFalse(); - } - - @Test - void markFirstSeen_keepsBoundedKeysPerChannel() { - Gb32960FrameDiagnostics diagnostics = new Gb32960FrameDiagnostics(2); - EmbeddedChannel channel = new EmbeddedChannel(); - - assertThat(diagnostics.markFirstSeen(channel, "VIN001", List.of("0x30"))).isTrue(); - assertThat(diagnostics.markFirstSeen(channel, "VIN001", List.of("0x31"))).isTrue(); - assertThat(diagnostics.markFirstSeen(channel, "VIN001", List.of("0x32"))).isTrue(); - - assertThat(diagnostics.seenKeyCount(channel)).isLessThanOrEqualTo(2); - } - - @Test - void collectRawTypeHex_returnsOnlyRawBlocks() { - List rawTypes = Gb32960FrameDiagnostics.collectRawTypeHex(List.of( - new InfoBlock.Raw(0x30, InfoBlockType.RAW, new byte[] {1}), - new InfoBlock.Raw(0x83, InfoBlockType.RAW, new byte[] {2}))); - - assertThat(rawTypes).containsExactly("0x30", "0x83"); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapperTest.java b/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapperTest.java deleted file mode 100644 index 8f4e1d15..00000000 --- a/modules/protocols/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/mapper/Gb32960EventMapperTest.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.lingniu.ingest.protocol.gb32960.mapper; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.AlarmPayload; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960DecoderTest; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.model.CommandType; -import com.lingniu.ingest.protocol.gb32960.model.EncryptType; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Header; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.GeneralAlarmFlagBit; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion; -import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960EventMapperTest { - - @Test - void realtimeReportProducesRealtimeAndLocationEvents() { - byte[] frame = Gb32960DecoderTest.buildRealtimeFrame("LTEST000000000002"); - - Gb32960BodyParser body = new Gb32960BodyParser(new InfoBlockParserRegistry( - List.of(new VehicleV2016BlockParser(), new PositionV2016BlockParser()))); - Gb32960Message msg = new Gb32960MessageDecoder(body).decode(ByteBuffer.wrap(frame)); - - List events = new Gb32960EventMapper().toEvents(msg); - - assertThat(events).hasSize(2); - assertThat(events).anyMatch(e -> e instanceof VehicleEvent.Realtime); - assertThat(events).anyMatch(e -> e instanceof VehicleEvent.Location); - assertThat(events).allMatch(e -> e.source() == ProtocolId.GB32960); - assertThat(events).allMatch(e -> e.vin().equals("LTEST000000000002")); - VehicleEvent.Location location = events.stream() - .filter(VehicleEvent.Location.class::isInstance) - .map(VehicleEvent.Location.class::cast) - .findFirst() - .orElseThrow(); - assertThat(location.metadata()).containsEntry("total_mileage_km", "123456.7"); - } - - @Test - void hydrogenLeakAlarmBitProducesCriticalSafetyAlarmEvent() { - long hydrogenLeakFlag = 1L << GeneralAlarmFlagBit.HYDROGEN_LEAK.bitIndex(); - Gb32960Message msg = new Gb32960Message( - new Gb32960Header( - ProtocolVersion.V2025, - CommandType.REALTIME_REPORT, - ResponseFlag.COMMAND, - "LTEST000000000003", - EncryptType.UNENCRYPTED, - 0, - Instant.parse("2026-06-22T01:00:00Z")), - List.of(new InfoBlock.Gb32960V2025.Alarm( - 0, - hydrogenLeakFlag, - List.of(), - List.of(), - List.of(), - List.of(), - List.of()))); - - List events = new Gb32960EventMapper().toEvents(msg); - - VehicleEvent.Alarm alarm = events.stream() - .filter(VehicleEvent.Alarm.class::isInstance) - .map(VehicleEvent.Alarm.class::cast) - .findFirst() - .orElseThrow(); - assertThat(alarm.payload().level()).isEqualTo(AlarmPayload.AlarmLevel.CRITICAL); - assertThat(alarm.payload().safetyCategory()).isEqualTo(AlarmPayload.SafetyCategory.HYDROGEN_LEAK); - assertThat(alarm.payload().hydrogenLeakDetected()).isTrue(); - assertThat(alarm.payload().hydrogenLeakLevel()).isEqualTo(AlarmPayload.HydrogenLeakLevel.CRITICAL); - assertThat(alarm.payload().hydrogenLeakActionRequired()).isTrue(); - assertThat(alarm.payload().activeBits()).contains("HYDROGEN_LEAK"); - } -} diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/README.md b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/README.md deleted file mode 100644 index 1b3628c4..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# GB/T 32960 黄金样本集 - -> 本目录用于回放测试与双跑对账。所有样本从旧服务 `lingniu-vehicle-data-reception` 线上抓包后脱敏(VIN 替换为 `LTEST<序号>XXXXXXXX`)。 - -## 文件命名 - -``` -<命令类型>_<场景>_<序号>.hex -``` - -命令类型取自 `CommandType` 枚举: -- `vehicle_login` (0x01) -- `realtime_report` (0x02) -- `resend_report` (0x03) -- `vehicle_logout` (0x04) -- `heartbeat` (0x07) - -## 文件格式 - -每个 `.hex` 文件是一行紧凑十六进制,字节之间**无分隔符**。 -行首允许以 `#` 开头写注释,测试代码忽略注释行与空行。 - -示例 `realtime_report_basic_001.hex`: -``` -# 2017-05-21 10:00:00 宇通 YT01,SOC 70%,车速 52.3 km/h -2323020000010203040506070809101112131415160100352016051510000001010012350000037102710000A064010505050A0005010A00102003E8... -``` - -## 新增样本步骤 - -1. 从生产抓包工具(tcpdump / rg-samples)导出一帧完整字节(含 `0x23 0x23` 起始和 BCC 尾) -2. VIN 替换为测试段:`LTEST0000<9位序号>` -3. 放入本目录,文件名按规范 -4. 在 `Gb32960DecoderGoldenTest` 中添加断言(期望 VIN / 命令 / 关键字段) -5. 运行 `mvn -pl protocol-gb32960 test` - -## 验证目标 - -- 100% 解析一致性(新服务解析结果 === 旧服务解析结果) -- BCC 校验通过 -- 事件映射字段单位一致(车速 km/h、里程 km、经纬度十进制度) diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_001.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_001.hex deleted file mode 100644 index f827bd45..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_001.hex +++ /dev/null @@ -1 +0,0 @@ -232307fe4c54455354303030303030303030303131010000a2 diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_002.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_002.hex deleted file mode 100644 index d9711570..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/heartbeat_002.hex +++ /dev/null @@ -1 +0,0 @@ -232307fe4c54455354303030303030303030303231010000a1 diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_001.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_001.hex deleted file mode 100644 index 5d175a1f..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_001.hex +++ /dev/null @@ -1 +0,0 @@ -232302fe4c544553543030303030303030303030310102101a040d0c213901010302037a000531c2157825fb44012e27103400020101025575304eca6b157a288b030c82050a00aa0002686c02940200000100630101050006c4f414015eedea06018c0f0701310ebf01014a01054907000000000000000000080101157825fb00900001900eea0eea0eea0ee60ee60ee60ef00eef0ef10eeb0eec0eeb0eed0eee0eee0eeb0eed0eea0ed30ed20ed20ed30ed50ed40ecc0ecd0ecc0ece0ece0ece0ed40ed40ed40ed30ed10ed00ed20ed10ed00ece0ece0ecf0ed40ed40ed40eda0eda0edb0ebf0ed40ed30ed60ed60ed50ed40ed50ede0edf0ede0ede0eee0eed0eec0ee80ee80ee50ef00eef0eef0ee80ee90eea0ee40ee70ee80eeb0eeb0eea0eee0eef0eef0ef60ef60ef50ef80ef70ef50ef60ef50ef60eef0ef00ef10ef60ef80ef90efa0ef70ef90efa0ef80ef80efc0efc0efc0ef50ef50ef50ef20ef30ef40ef50ef50ef40ef50ef50ef70ef40ef40ef50ef80ef80ef70ef70ef50ef40ef70ef70ef70efa0efd0efc0ef70ef80ef70ef80ef60ef50f050f070f070ef00eed0ef009010100084a4a4a4a494a4a493001026c08fcffffff0003003a02ee02e402e5000000000031010c800006ffffffff0c80ffffffff0088320c8004c2159002c35333ffffffffff34ffffffffff001c8000096c0c802742ffffffff8300250019000e0b002300020000000000014f00ffffffffffff00000282ffffffff1ffe1fedff1fa0 diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_002.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_002.hex deleted file mode 100644 index 8eb90545..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_002.hex +++ /dev/null @@ -1 +0,0 @@ -232302fe4c544553543030303030303030303030320102ed1a040d0c213a01010302013600036758162826284c012e2710010002010101485bf84de44b165526ec030da9017400d200026a6b023a0200000100b101010402ffff0015050107373d6c01d2e6880601020f8701520efe010141010540070000000000000000000801011628262800900001900f860f870f860f6d0f6c0f6c0f870f860f870f6d0f700f6e0f7e0f7d0f710f640f630f650f6e0f800f7e0f260f250f250f810f810f820f620f660f670f810f820f830f670f680f670f780f770f770f670f660f670f640f630f620f1a0f1c0f1c0f500f500f4d0f690f6b0f690f540f530f5a0f420f440f440f6e0f6e0f6d0f460f450f450f720f6c0f6b0f650f640f670f500f4f0f4c0f150f140f150f780f760f780efe0eff0eff0f7b0f7c0f7d0f640f640f640f6e0f6d0f6e0f360f390f390f5c0f5e0f5c0f650f660f660f7e0f800f800f640f620f640f810f800f800f300f330f2f0f800f820f830f670f660f680f820f840f820f650f670f660f820f840f830f680f660f660f820f810f810f680f660f660f720f720f710f680f670f67090101000841414141404040403001026b092eff00ff000100730ca80c800ca4006c00016c0ca40ca80ca30ca30ca30ca30ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca40ca431010dc00005ffffffff0dc0ffffffff008a320dc000ff15e7009e4833ffffffffff34ffffffffff002f8000096b0dc02715000d000e83002402040100002700080000000000c900000000ffffffffffffffffffffffff1ffe15c8631f18 diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_003.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_003.hex deleted file mode 100644 index b038497c..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_003.hex +++ /dev/null @@ -1 +0,0 @@ -232302fe4c544553543030303030303030303030330102ed1a040d0c2200010203010000000418d914ed27103a020007d0000002010104454e204e204814be2710030006000000b400024a4902bc02000001006701000400ffff0012050006be89500161217106013d0e91012c0e860101470105460700000000000000000008010114ed271000900001900e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e8a0e870e870e880e880e880e880e880e880e880e880e880e870e860e860e880e880e8a0e880e880e880e880e880e880e880e880e880e880e880e880e910e900e900e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e880e88090101000847474747464746463001004907c6ff00ff00000039000000000004006c00016c00000004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400000004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040005000500050005000500050005000531010eb00005ffffffff0eb0ffffffff0081320eb01b3b14e613234733ffffffffff34ffffffffff001d800009490eb02710000d000e83002402040100002700080000000000d500000000ffffffffffffffffffffffff1ffe167c021f7b diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_010.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_010.hex deleted file mode 100644 index 43eab7b3..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_010.hex +++ /dev/null @@ -1 +0,0 @@ -232302fe4c544553543030303030303030303031300102ed1a040d0c220501020301006400049992163027df4f012e27101e00020101015952da51365215f4279f030005000000000002625f02a80200000100a801000400ffff0000050106c3d5cf016240a406010b0f90015b0f4001014a01054907000000000000000000080101163027df00900001900f8c0f8d0f8d0f8d0f8e0f8e0f8f0f8f0f8e0f8f0f900f900f8f0f900f7a0f7c0f800f800f7f0f7c0f7c0f7c0f7e0f7e0f810f7f0f7f0f7e0f820f830f500f500f500f840f850f840f840f820f850f850f820f820f730f750f730f690f6a0f680f620f5f0f600f500f500f500f630f5a0f6d0f6c0f6c0f6d0f500f500f500f6b0f6b0f6c0f6a0f5d0f5f0f5c0f5f0f610f5c0f5e0f5a0f5c0f5c0f610f630f630f640f510f520f540f500f500f500f610f550f500f400f430f410f500f510f500f4e0f4f0f530f500f500f4f0f550f550f560f550f570f560f540f570f560f510f540f530f540f560f5a0f5a0f590f570f7e0f7e0f810f580f4e0f530f570f550f510f500f480f4d0f870f7e0f7e0f7c0f7e0f7f0f7c0f7b0f810f7d0f7e0f7f09010100084a4a4a4a494949493001005f07daff00ff00000038000000000004006c00016c0000000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400000004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000400040004000500050005000500050005000500053101003c0005ffffffff003cffffffff008a32003c00000000ffff4f33ffffffffff34ffffffffff002c8000095f003c2710000d000e830024020401000027000800000000000000000000ffffffffffffffffffffffff1ffe17ae631ff9 diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_020.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_020.hex deleted file mode 100644 index d07198d2..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_020.hex +++ /dev/null @@ -1 +0,0 @@ -232302fe4c544553543030303030303030303032330101d61a040d0c22070101030203010004c1ee17fc261337012e271032000201010157705a4fe26e17f2260c0309d50bba021a00027f6e022b030000010106010105000733a7d401d832920601840eda011c0ebf01044b0101480700000000000000000008010117fc261300a20001a20ecc0eca0ec30ecb0ec90ec70eca0ec90ec20ec20ec40ec20ec70eca0eca0ec90ece0ecd0ec80ecd0eca0ecc0ec90ec80ece0ec70ec50ebf0ed40ed20ed40ed30ed40ed30ed50ed40ed30ed50ed20ed50ed50ed30ecc0ecb0ec80ecf0ec90ed00ec70ecd0ec70ec50ec70ece0eca0eca0ec90eca0ed10ec90ed00ecf0eca0ed10ed30ed10ecf0ed20ed00ed20ed40ed50ed20ed20ed60ed20ed30ed20ed50ed50ed70ed40ed10ed40ed30ed20ed30ed20ecc0ed00ed10ecc0ed30ed10ed00ecb0ec90ecd0ec80ed10ec50ec80ed20ec10ec90ec50ec90ec30ec50ed40ed40ed40ed40ed40ed40ed60ed50ed30ed50ed50ed50ed50ed40ed50ed40ed30ed20ed20ed30ed60ed50eda0ed00ed70ed80ed60ed70ed50ed60ece0ed50ed40ed60ed40ed10ed30ed20ed40ed40ed40ecf0ed80ed60ed50ed60ed70ed60ed60ed40ed50ed70eda0901010020484a4a4b4b4b4b4a484b4b4b4b4b4b4a484a4b4b4b4b4b4a484a4b4b4b4b4b4acc diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_100.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_100.hex deleted file mode 100644 index 25de83e9..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_100.hex +++ /dev/null @@ -1 +0,0 @@ -232302fe4c544553543030303030303030303130370101ae1a040d0c39263001016908fc09c4410003007403200320032001b000c9c803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e8cb diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_200.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_200.hex deleted file mode 100644 index 0069f5b0..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/realtime_200.hex +++ /dev/null @@ -1 +0,0 @@ -232302fe4c544553543030303030303030303231310102ed1a040d0c3a120101030200000002533c150727203e01002710000002010104464e204e204814fc2710030004000200be00025353023a01000001010201010402ffff00130500072eb49401de012e06013f0ea7012a0e9301014001033f070000000000000000000801011507272000900001900ea60ea60ea60ea60ea60ea60ea50ea60ea60ea60ea60ea60ea60ea50e960e990e960e980e980e990e970e990e970e960e980e980e990e980e950e970e950e940e950e960e970e940e990e960e960e950e950e930e960e9b0e9c0e9c0e990e9b0e940e950e930e980e9a0e9a0e940e950ea50ea50ea50ea50ea50ea60ea70ea60ea50ea50ea50e9d0e9e0e9e0e9e0e990e970e970e990e980e9a0e9d0e9a0e9c0e9a0e990e9a0e9a0e9b0e9c0e9c0e980e950e980e990e990e990e980e970e970e980e970e970e990e990e9a0e990e990e990e9a0e980e960e960e990e970e970e970e970e980e950e930e9b0e980e9a0e990e9a0e990e940e940e950e940e950e950e990e990e9b0e9a0e9b0e9b0e960e950e960e950e950e950e950e940e99090101000840403f403f3f3f3f300102530708ff00ff00000037000000000000006c00016c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000031010e880005ffffffff0e88ffffffff008a320e880002150e00014733ffffffffff34ffffffffff0044800009530e882710000d000e830024020401000027000800000000005401000000ffffffffffffffffffffffff1ffe1d7b011fdd diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_001.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_001.hex deleted file mode 100644 index bf30e283..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_001.hex +++ /dev/null @@ -1 +0,0 @@ -232303fe4c544553543030303030303030303032320102101a040d0d0b0a010203010000000532ad156d271c45011007d0006502010103514e204e205c003f27100300070000000000025e5c03b601000001014d0100050106c3aa530160540f0601010ee301120ede01014801054707000000000000000000080101156d271c00900001900ee30ee30ee30ee00ee20ee20ee20ee10ee20ee30ee20ee20ee20ee10ee30ee10ee10ede0ee10ee10ee10ee00ee10ee10ee10ee00ee00ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee10ee1090101000848484848474848473001005c0816ffffff0000003a000a0000000100000000003101003c0005ffffffff003cffffffff008b32003c00000000ffff5233ffffffffff34ffffffffff004c8000095c003c2710ffffffff8300250019000e0b002300020000000000000000ffffffffffff0000000affffffff1ffe1fedff0089 diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_005.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_005.hex deleted file mode 100644 index 55197f67..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/resend_005.hex +++ /dev/null @@ -1 +0,0 @@ -232303fe4c544553543030303030303030303230390101ae1a040d0c391b3001005307ee09c4410037000100000000000001b000c9c803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e803e8be diff --git a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/vehicle_login_001.hex b/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/vehicle_login_001.hex deleted file mode 100644 index 0583697c..00000000 --- a/modules/protocols/protocol-gb32960/src/test/resources/samples/gb32960/vehicle_login_001.hex +++ /dev/null @@ -1 +0,0 @@ -232301fe4c5445535430303030303030303030313901001e1a040d0d0b1a000c38393836303332313435323039303735363530390100bd diff --git a/modules/protocols/protocol-jsatl12/pom.xml b/modules/protocols/protocol-jsatl12/pom.xml deleted file mode 100644 index b922c0e2..00000000 --- a/modules/protocols/protocol-jsatl12/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - protocol-jsatl12 - protocol-jsatl12 - - 苏标(JSATL12)主动安全报警附件上传协议。独立 TCP 端口(默认 7612), - 与 JT808 使用相同的转义规则,但数据帧带 01cd 魔数前缀,用于大文件分块上传。 - - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - protocol-jt808 - - - com.lingniu.ingest - vehicle-identity - - - com.lingniu.ingest - vehicle-identity-test-support - test - - - com.lingniu.ingest - sink-archive - - - org.springframework.boot - spring-boot-starter - - - io.netty - netty-all - - - com.github.ben-manes.caffeine - caffeine - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoder.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoder.java deleted file mode 100644 index ddf7cd5a..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoder.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; - -import java.util.List; - -/** - * 苏标报警附件协议分帧。 - * - *

    两种帧并存: - *

      - *
    1. 标准 JT/T 808 帧:以 {@code 0x7e} 为分界,承载 0x1210 / 0x1211 / 0x9212 等信令 - *
    2. 数据帧:以 {@code 0x30 0x31 0x63 0x64}(即 ASCII "01cd")为魔数,后续 4 字节大端长度 + 数据 - *
    - * - *

    本 decoder 合并处理:根据第一个字节决定分帧策略,输出带类型标记的 {@link Jsatl12RawFrame}。 - */ -public class Jsatl12FrameDecoder extends ByteToMessageDecoder { - - private static final byte[] DATA_MAGIC = {0x30, 0x31, 0x63, 0x64}; - private static final int DEFAULT_MAX_FRAME = 64 * 1024; - private final int maxFrameLength; - - public Jsatl12FrameDecoder() { - this(DEFAULT_MAX_FRAME); - } - - public Jsatl12FrameDecoder(int maxFrameLength) { - this.maxFrameLength = maxFrameLength <= 0 ? DEFAULT_MAX_FRAME : maxFrameLength; - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - while (in.readableBytes() >= 1) { - byte first = in.getByte(in.readerIndex()); - if (first == 0x7e) { - if (!decodeJtFrame(in, out)) return; - } else if (first == DATA_MAGIC[0]) { - if (!decodeDataFrame(in, out)) return; - } else { - in.skipBytes(1); - } - } - } - - private boolean decodeJtFrame(ByteBuf in, List out) { - if (in.readableBytes() < 2) return false; - int endIdx = in.indexOf(in.readerIndex() + 1, in.writerIndex(), (byte) 0x7e); - if (endIdx < 0) { - if (in.readableBytes() > maxFrameLength) { - byte[] malformed = new byte[in.readableBytes()]; - in.readBytes(malformed); - out.add(new Jsatl12RawFrame(Jsatl12RawFrame.Type.MALFORMED, malformed)); - return true; - } - return false; - } - int len = endIdx - (in.readerIndex() + 1); - if (len <= 0) { - in.skipBytes(1); - return true; - } - byte[] raw = new byte[len]; - in.skipBytes(1); - in.readBytes(raw); - in.skipBytes(1); - out.add(new Jsatl12RawFrame(Jsatl12RawFrame.Type.JT_MESSAGE, raw)); - return true; - } - - private boolean decodeDataFrame(ByteBuf in, List out) { - if (in.readableBytes() < DATA_MAGIC.length + 4) return false; - for (int i = 0; i < DATA_MAGIC.length; i++) { - if (in.getByte(in.readerIndex() + i) != DATA_MAGIC[i]) { - in.skipBytes(1); - return true; - } - } - if (in.readableBytes() < 62) return false; - // DataPacket 布局:4B magic + 50B 文件名 + 4B offset + 4B length + data - long dataLen = in.getUnsignedInt(in.readerIndex() + 58); - if (dataLen > maxFrameLength) { - int malformedLen = in.readableBytes(); - long declaredTotal = 62L + dataLen; - if (declaredTotal <= Integer.MAX_VALUE && in.readableBytes() >= declaredTotal) { - malformedLen = (int) declaredTotal; - } - byte[] malformed = new byte[malformedLen]; - in.readBytes(malformed); - out.add(new Jsatl12RawFrame(Jsatl12RawFrame.Type.MALFORMED, malformed)); - return true; - } - int totalFrameLen = 62 + (int) dataLen; - if (in.readableBytes() < totalFrameLen) return false; - byte[] frame = new byte[totalFrameLen]; - in.readBytes(frame); - out.add(new Jsatl12RawFrame(Jsatl12RawFrame.Type.DATA_CHUNK, frame)); - return true; - } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12RawFrame.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12RawFrame.java deleted file mode 100644 index 48444827..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12RawFrame.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.codec; - -/** - * JSATL12 原始帧:区分信令帧、数据帧与无法继续解析但需要审计的畸形帧。 - */ -public record Jsatl12RawFrame(Type type, byte[] bytes) { - public enum Type { JT_MESSAGE, DATA_CHUNK, MALFORMED } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12AutoConfiguration.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12AutoConfiguration.java deleted file mode 100644 index b1792e78..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12AutoConfiguration.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.config; - -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.protocol.jsatl12.handler.Jsatl12ArchiveEventHandler; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12AttachmentTracker; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12JtMessageBridge; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12NettyServer; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.config.SinkArchiveAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -@AutoConfiguration(after = {SinkArchiveAutoConfiguration.class, Jt808AutoConfiguration.class}) -@EnableConfigurationProperties(Jsatl12Properties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.jsatl12", name = "enabled", havingValue = "true") -@ConditionalOnBean({ArchiveStore.class, Jt808MessageDecoder.class}) -public class Jsatl12AutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public Jsatl12AttachmentTracker jsatl12AttachmentTracker() { - return new Jsatl12AttachmentTracker(); - } - - @Bean - @ConditionalOnMissingBean - public Jsatl12JtMessageBridge jsatl12JtMessageBridge(Jt808MessageDecoder decoder, - VehicleIdentityResolver identityResolver, - Jsatl12AttachmentTracker attachmentTracker, - Dispatcher dispatcher) { - return new Jsatl12JtMessageBridge(decoder, identityResolver, attachmentTracker, dispatcher::dispatch); - } - - @Bean - @ConditionalOnMissingBean - public Jsatl12ArchiveEventHandler jsatl12ArchiveEventHandler() { - return new Jsatl12ArchiveEventHandler(); - } - - @Bean - @ConditionalOnMissingBean - public Jsatl12NettyServer jsatl12NettyServer(Jsatl12Properties props, - ArchiveStore archive, - Jsatl12JtMessageBridge jtMessageBridge, - Jsatl12AttachmentTracker attachmentTracker, - VehicleIdentityResolver identityResolver, - Dispatcher dispatcher) { - return new Jsatl12NettyServer(props.getPort(), props.getWorkerThreads(), - props.getMaxFrameLength(), archive, jtMessageBridge, attachmentTracker, identityResolver, dispatcher::dispatch); - } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12Properties.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12Properties.java deleted file mode 100644 index b31a65de..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/config/Jsatl12Properties.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** JSATL12 主动安全附件接入配置。 */ -@ConfigurationProperties(prefix = "lingniu.ingest.jsatl12") -public class Jsatl12Properties { - private boolean enabled = false; - private int port = 7612; - private int workerThreads = 0; - private int maxFrameLength = 64 * 1024; - /** 附件落盘根目录,支持 file:// / s3:// / oss:// 形式,由 sink-archive 解析。 */ - private String fileStore = "file:///var/lingniu/alarm-files"; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public int getPort() { return port; } - public void setPort(int port) { this.port = port; } - public int getWorkerThreads() { return workerThreads; } - public void setWorkerThreads(int workerThreads) { this.workerThreads = workerThreads; } - public int getMaxFrameLength() { return maxFrameLength; } - public void setMaxFrameLength(int maxFrameLength) { this.maxFrameLength = maxFrameLength; } - public String getFileStore() { return fileStore; } - public void setFileStore(String fileStore) { this.fileStore = fileStore; } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandler.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandler.java deleted file mode 100644 index 7f3d79e4..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandler.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.EventEmit; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12ArchiveFailure; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12ArchivedChunk; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12MalformedFrame; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12MalformedJtMessage; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12MessageId; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -@ProtocolHandler(protocol = ProtocolId.JSATL12) -public final class Jsatl12ArchiveEventHandler { - - @MessageMapping(command = Jsatl12MessageId.DATA_CHUNK_ARCHIVED, desc = "主动安全附件数据块已归档") - @EventEmit(VehicleEvent.MediaMeta.class) - public List onDataChunkArchived(Jsatl12ArchivedChunk chunk) { - Instant eventTime = chunk.archivedAt() == null ? Instant.now() : chunk.archivedAt(); - String peer = chunk.peer() == null || chunk.peer().isBlank() ? "unknown" : chunk.peer(); - return List.of(new VehicleEvent.MediaMeta( - UUID.randomUUID().toString(), - nullToUnknown(chunk.vin()), - ProtocolId.JSATL12, - eventTime, - Instant.now(), - null, - baseMeta( - "vin", nullToUnknown(chunk.vin()), - "phone", nullToEmpty(chunk.phone()), - "identityResolved", Boolean.toString(chunk.identityResolved()), - "identitySource", nullToEmpty(chunk.identitySource()), - "peer", peer, - "archiveKey", nullToEmpty(chunk.archiveKey()), - "archiveRef", nullToEmpty(chunk.archiveRef()), - "fileName", nullToEmpty(chunk.fileName()), - "fileOffset", Long.toString(Math.max(0, chunk.fileOffset())), - "declaredChunkSizeBytes", Long.toString(Math.max(0, chunk.declaredSizeBytes())), - "chunkSizeBytes", Long.toString(Math.max(0, chunk.sizeBytes()))), - "jsatl12-" + peer + "-" + eventTime.toEpochMilli(), - "jsatl12-data-chunk", - Math.max(0, chunk.sizeBytes()), - nullToEmpty(chunk.archiveRef()))); - } - - @MessageMapping(command = Jsatl12MessageId.MALFORMED_JT_MESSAGE, desc = "主动安全 JT 消息解析失败兜底") - @EventEmit(VehicleEvent.Passthrough.class) - public List onMalformedJtMessage(Jsatl12MalformedJtMessage malformed) { - Instant eventTime = malformed.receivedAt() == null ? Instant.now() : malformed.receivedAt(); - String peer = malformed.peer() == null || malformed.peer().isBlank() ? "unknown" : malformed.peer(); - return List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - "unknown", - ProtocolId.JSATL12, - eventTime, - Instant.now(), - null, - baseMeta( - "peer", peer, - "source", "jsatl12-jt-message", - "parseError", "true", - "parseErrorMessage", nullToEmpty(malformed.errorMessage())), - Jsatl12MessageId.MALFORMED_JT_MESSAGE, - malformed.rawBytes() == null ? new byte[0] : malformed.rawBytes())); - } - - @MessageMapping(command = Jsatl12MessageId.DATA_CHUNK_ARCHIVE_FAILED, desc = "主动安全附件数据块归档失败兜底") - @EventEmit(VehicleEvent.Passthrough.class) - public List onArchiveFailure(Jsatl12ArchiveFailure failure) { - Instant eventTime = failure.failedAt() == null ? Instant.now() : failure.failedAt(); - String peer = failure.peer() == null || failure.peer().isBlank() ? "unknown" : failure.peer(); - return List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - "unknown", - ProtocolId.JSATL12, - eventTime, - Instant.now(), - null, - baseMeta( - "peer", peer, - "source", "jsatl12-data-chunk", - "archiveError", "true", - "archiveErrorMessage", nullToEmpty(failure.errorMessage()), - "archiveKey", nullToEmpty(failure.archiveKey()), - "chunkSizeBytes", Long.toString(Math.max(0, failure.sizeBytes()))), - Jsatl12MessageId.DATA_CHUNK_ARCHIVE_FAILED, - failure.rawBytes() == null ? new byte[0] : failure.rawBytes())); - } - - @MessageMapping(command = Jsatl12MessageId.MALFORMED_FRAME, desc = "主动安全附件帧边界异常兜底") - @EventEmit(VehicleEvent.Passthrough.class) - public List onMalformedFrame(Jsatl12MalformedFrame malformed) { - Instant eventTime = malformed.receivedAt() == null ? Instant.now() : malformed.receivedAt(); - String peer = malformed.peer() == null || malformed.peer().isBlank() ? "unknown" : malformed.peer(); - return List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - "unknown", - ProtocolId.JSATL12, - eventTime, - Instant.now(), - null, - baseMeta( - "peer", peer, - "source", "jsatl12-frame", - "frameError", "true", - "frameErrorMessage", nullToEmpty(malformed.errorMessage()), - "parseError", "true", - "parseErrorMessage", nullToEmpty(malformed.errorMessage())), - Jsatl12MessageId.MALFORMED_FRAME, - malformed.rawBytes() == null ? new byte[0] : malformed.rawBytes())); - } - - private static Map baseMeta(String... kv) { - java.util.HashMap out = new java.util.HashMap<>(); - out.put("vin", "unknown"); - out.put("identityResolved", "false"); - out.put("identitySource", "UNKNOWN"); - for (int i = 0; i + 1 < kv.length; i += 2) { - out.put(kv[i], kv[i + 1] == null ? "" : kv[i + 1]); - } - return Map.copyOf(out); - } - - private static String nullToEmpty(String value) { - return value == null ? "" : value; - } - - private static String nullToUnknown(String value) { - return value == null || value.isBlank() ? "unknown" : value; - } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchiveFailure.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchiveFailure.java deleted file mode 100644 index d2271739..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchiveFailure.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import java.time.Instant; - -public record Jsatl12ArchiveFailure( - byte[] rawBytes, - String archiveKey, - long sizeBytes, - String peer, - String errorMessage, - Instant failedAt -) {} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchivedChunk.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchivedChunk.java deleted file mode 100644 index c3db11f2..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ArchivedChunk.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import java.time.Instant; - -public record Jsatl12ArchivedChunk( - String archiveRef, - String archiveKey, - String fileName, - long fileOffset, - long declaredSizeBytes, - long sizeBytes, - String peer, - Instant archivedAt, - String phone, - String vin, - boolean identityResolved, - String identitySource -) { - public Jsatl12ArchivedChunk { - phone = phone == null ? "" : phone.trim(); - vin = vin == null || vin.isBlank() ? "unknown" : vin.trim(); - identitySource = identitySource == null || identitySource.isBlank() ? "UNKNOWN" : identitySource.trim(); - } - - public Jsatl12ArchivedChunk(String archiveRef, - String archiveKey, - String fileName, - long fileOffset, - long declaredSizeBytes, - long sizeBytes, - String peer, - Instant archivedAt) { - this(archiveRef, archiveKey, fileName, fileOffset, declaredSizeBytes, sizeBytes, peer, archivedAt, - "", "unknown", false, "UNKNOWN"); - } - - public Jsatl12ArchivedChunk(String archiveRef, - String archiveKey, - long sizeBytes, - String peer, - Instant archivedAt) { - this(archiveRef, archiveKey, "", 0, sizeBytes, sizeBytes, peer, archivedAt, - "", "unknown", false, "UNKNOWN"); - } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTracker.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTracker.java deleted file mode 100644 index 27565499..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTracker.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public final class Jsatl12AttachmentTracker { - - private final Map> filesByPhone = new HashMap<>(); - - public synchronized void registerAttachmentList(String phone, byte[] body) { - String key = normalizePhone(phone); - byte[] raw = body == null ? new byte[0] : body; - if (raw.length < 57) { - throw new IllegalArgumentException("jsatl12 T1210 body too short: " + raw.length); - } - int total = raw[56] & 0xFF; - int pos = 57; - Map files = filesByPhone.computeIfAbsent(key, ignored -> new HashMap<>()); - for (int i = 0; i < total && pos < raw.length; i++) { - int nameLen = raw[pos++] & 0xFF; - if (pos + nameLen + 4 > raw.length) { - throw new IllegalArgumentException("jsatl12 T1210 item truncated at index " + i); - } - String name = new String(raw, pos, nameLen, StandardCharsets.UTF_8).trim(); - pos += nameLen; - long size = Jsatl12DataPacket.readDword(raw, pos); - pos += 4; - files.put(name, new FileState(name, 0, size)); - } - } - - public synchronized Jsatl12DataPacket recordDataPacket(String phone, byte[] rawPacket) { - Jsatl12DataPacket packet = Jsatl12DataPacket.parse(rawPacket); - String key = normalizePhone(phone); - Map files = filesByPhone.computeIfAbsent(key, ignored -> new HashMap<>()); - FileState state = files.computeIfAbsent(packet.name(), - ignored -> new FileState(packet.name(), 0, packet.offset() + packet.length())); - state.ranges.add(new Range(packet.offset(), packet.offset() + packet.length())); - return packet; - } - - public synchronized Jsatl12UploadCompletionAck completeUpload(String phone, byte[] body) { - T1212 completion = parseT1212(body); - String key = normalizePhone(phone); - Map files = filesByPhone.computeIfAbsent(key, ignored -> new HashMap<>()); - FileState state = files.computeIfAbsent(completion.name(), - ignored -> new FileState(completion.name(), completion.type(), completion.size())); - mergeUnknownFileInto(key, state); - state.type = completion.type(); - state.size = completion.size(); - - List missing = missingRanges(state); - if (missing.isEmpty()) { - files.remove(completion.name()); - if (files.isEmpty()) { - filesByPhone.remove(key); - } - return new Jsatl12UploadCompletionAck(completion.name(), completion.type(), 0, List.of()); - } - return new Jsatl12UploadCompletionAck(completion.name(), completion.type(), 1, missing); - } - - private void mergeUnknownFileInto(String ownerKey, FileState target) { - if ("unknown".equals(ownerKey)) { - return; - } - Map unknownFiles = filesByPhone.get("unknown"); - if (unknownFiles == null) { - return; - } - FileState unknown = unknownFiles.remove(target.name); - if (unknown == null) { - return; - } - target.ranges.addAll(unknown.ranges); - if (unknownFiles.isEmpty()) { - filesByPhone.remove("unknown"); - } - } - - public synchronized Set knownFiles(String phone) { - Map files = filesByPhone.get(normalizePhone(phone)); - if (files == null) { - return Set.of(); - } - return Set.copyOf(new HashSet<>(files.keySet())); - } - - public synchronized String ownerPhone(String fileName) { - String name = fileName == null ? "" : fileName.trim(); - if (name.isBlank()) { - return ""; - } - for (Map.Entry> entry : filesByPhone.entrySet()) { - if (!"unknown".equals(entry.getKey()) && entry.getValue().containsKey(name)) { - return entry.getKey(); - } - } - return ""; - } - - private static List missingRanges(FileState state) { - List ranges = new ArrayList<>(state.ranges); - ranges.sort((a, b) -> Long.compare(a.start, b.start)); - List missing = new ArrayList<>(); - long cursor = 0; - for (Range range : ranges) { - if (range.end <= cursor) { - continue; - } - if (range.start > cursor) { - missing.add(new Jsatl12UploadCompletionAck.Range(cursor, range.start - cursor)); - } - cursor = Math.max(cursor, range.end); - if (cursor >= state.size) { - break; - } - } - if (cursor < state.size) { - missing.add(new Jsatl12UploadCompletionAck.Range(cursor, state.size - cursor)); - } - return List.copyOf(missing); - } - - private static T1212 parseT1212(byte[] body) { - byte[] raw = body == null ? new byte[0] : body; - if (raw.length < 6) { - throw new IllegalArgumentException("jsatl12 T1212 body too short: " + raw.length); - } - int nameLen = raw[0] & 0xFF; - if (raw.length < 1 + nameLen + 1 + 4) { - throw new IllegalArgumentException("jsatl12 T1212 body truncated"); - } - String name = new String(raw, 1, nameLen, StandardCharsets.UTF_8).trim(); - int type = raw[1 + nameLen] & 0xFF; - long size = Jsatl12DataPacket.readDword(raw, 2 + nameLen); - return new T1212(name, type, size); - } - - private static String normalizePhone(String phone) { - return phone == null || phone.isBlank() ? "unknown" : phone; - } - - private static final class FileState { - private final String name; - private final List ranges = new ArrayList<>(); - private int type; - private long size; - - private FileState(String name, int type, long size) { - this.name = name; - this.type = type; - this.size = Math.max(0, size); - } - } - - private record Range(long start, long end) {} - - private record T1212(String name, int type, long size) {} -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandler.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandler.java deleted file mode 100644 index 1768ce1b..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandler.java +++ /dev/null @@ -1,249 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.protocol.jsatl12.codec.Jsatl12RawFrame; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Consumer; - -/** - * JSATL12 入站处理器。 - * - *

    当前实现:把 DATA_CHUNK 帧写入 {@link ArchiveStore} 后派发统一事件引用,文件名按日期 + 对端 IP + - * 时间戳生成;JT_MESSAGE 帧复用 JT808 decoder 后进入统一 Dispatcher。 - * - *

    路径模板:{@code yyyy/MM/dd//.bin}。 - */ -public class Jsatl12ChannelHandler extends SimpleChannelInboundHandler { - - private static final Logger log = LoggerFactory.getLogger(Jsatl12ChannelHandler.class); - private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.systemDefault()); - - /** 为每个 Channel 持久化一个文件 key,保证同一次传输追加到同一个文件。 */ - private static final Cache KEYS = Caffeine.newBuilder() - .expireAfterAccess(Duration.ofMinutes(30)) - .maximumSize(10_000) - .build(); - - private final ArchiveStore archive; - private final Jsatl12JtMessageBridge jtMessageBridge; - private final Jsatl12AttachmentTracker attachmentTracker; - private final VehicleIdentityResolver identityResolver; - private final Consumer dataChunkDispatcher; - - public Jsatl12ChannelHandler(ArchiveStore archive, Jsatl12JtMessageBridge jtMessageBridge) { - this(archive, jtMessageBridge, new Jsatl12AttachmentTracker(), ignored -> {}); - } - - public Jsatl12ChannelHandler(ArchiveStore archive, - Jsatl12JtMessageBridge jtMessageBridge, - Consumer dataChunkDispatcher) { - this(archive, jtMessageBridge, new Jsatl12AttachmentTracker(), null, dataChunkDispatcher); - } - - public Jsatl12ChannelHandler(ArchiveStore archive, - Jsatl12JtMessageBridge jtMessageBridge, - Jsatl12AttachmentTracker attachmentTracker, - Consumer dataChunkDispatcher) { - this(archive, jtMessageBridge, attachmentTracker, null, dataChunkDispatcher); - } - - public Jsatl12ChannelHandler(ArchiveStore archive, - Jsatl12JtMessageBridge jtMessageBridge, - Jsatl12AttachmentTracker attachmentTracker, - VehicleIdentityResolver identityResolver, - Consumer dataChunkDispatcher) { - this.archive = archive; - this.jtMessageBridge = jtMessageBridge; - this.attachmentTracker = attachmentTracker == null ? new Jsatl12AttachmentTracker() : attachmentTracker; - this.identityResolver = identityResolver; - this.dataChunkDispatcher = dataChunkDispatcher; - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, Jsatl12RawFrame frame) { - switch (frame.type()) { - case JT_MESSAGE -> { - jtMessageBridge.dispatch(frame.bytes(), addr(ctx)) - .ifPresent(response -> ctx.writeAndFlush(io.netty.buffer.Unpooled.wrappedBuffer(response))); - } - case MALFORMED -> { - byte[] raw = frame.bytes() == null ? new byte[0] : frame.bytes(); - Instant now = Instant.now(); - String peer = addr(ctx); - String errorMessage = "malformed jsatl12 frame"; - Jsatl12MalformedFrame malformed = new Jsatl12MalformedFrame(raw, peer, errorMessage, now); - dataChunkDispatcher.accept(new RawFrame( - ProtocolId.JSATL12, - Jsatl12MessageId.MALFORMED_FRAME, - 0, - malformed, - raw, - unresolvedMeta( - "source", "jsatl12-frame", - "peer", peer, - "frameError", "true", - "frameErrorMessage", errorMessage, - "frameSizeBytes", Integer.toString(raw.length)), - now)); - } - case DATA_CHUNK -> { - String key = KEYS.get(ctx, k -> { - String day = DAY_FMT.format(Instant.now()); - return "jsatl12/" + day + "/" + addr(ctx).replace(':', '_') - + "/" + Instant.now().toEpochMilli() + ".bin"; - }); - try { - byte[] all = frame.bytes(); - Jsatl12DataPacket packet = attachmentTracker.recordDataPacket(null, all); - byte[] payload = packet.data(); - int payloadLen = payload.length; - if (payloadLen > 0) { - String phone = attachmentTracker.ownerPhone(packet.name()); - IdentityResolution identity = resolveIdentity(phone); - String archiveRef = archive.append(key, payload); - Instant now = Instant.now(); - Jsatl12ArchivedChunk archived = new Jsatl12ArchivedChunk( - archiveRef, - key, - packet.name(), - packet.offset(), - packet.length(), - payloadLen, - addr(ctx), - now, - phone, - identity.identity().vin(), - identity.identity().resolved(), - identity.identity().source().name()); - dataChunkDispatcher.accept(new RawFrame( - ProtocolId.JSATL12, - Jsatl12MessageId.DATA_CHUNK_ARCHIVED, - 0, - archived, - null, - identityMeta(identity, - "phone", phone, - "source", "jsatl12-data-chunk", - "peer", addr(ctx), - "fileName", packet.name(), - "fileOffset", Long.toString(packet.offset()), - "declaredChunkSizeBytes", Long.toString(packet.length()), - "archiveRef", archiveRef, - "archiveKey", key, - "chunkSizeBytes", Integer.toString(payloadLen)), - now)); - } - } catch (IOException | RuntimeException e) { - log.warn("[jsatl12] write chunk failed key={}", key, e); - byte[] all = frame.bytes(); - int payloadLen = Math.max(0, all.length - 62); - Instant now = Instant.now(); - Jsatl12ArchiveFailure failure = new Jsatl12ArchiveFailure( - all, - key, - payloadLen, - addr(ctx), - e.getMessage() == null ? "" : e.getMessage(), - now); - dataChunkDispatcher.accept(new RawFrame( - ProtocolId.JSATL12, - Jsatl12MessageId.DATA_CHUNK_ARCHIVE_FAILED, - 0, - failure, - all, - unresolvedMeta( - "source", "jsatl12-data-chunk", - "peer", addr(ctx), - "archiveKey", key, - "archiveError", "true", - "archiveErrorMessage", failure.errorMessage(), - "chunkSizeBytes", Long.toString(payloadLen)), - now)); - } - } - } - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - String key = KEYS.getIfPresent(ctx); - if (key != null) { - log.info("[jsatl12] channel closed, archived key={}", key); - KEYS.invalidate(ctx); - } - } - - private static Map unresolvedMeta(String... kv) { - HashMap out = new HashMap<>(); - out.put("vin", "unknown"); - out.put("identityResolved", "false"); - out.put("identitySource", "UNKNOWN"); - for (int i = 0; i + 1 < kv.length; i += 2) { - out.put(kv[i], kv[i + 1] == null ? "" : kv[i + 1]); - } - return Map.copyOf(out); - } - - private IdentityResolution resolveIdentity(String phone) { - if (identityResolver != null) { - try { - return new IdentityResolution( - identityResolver.resolve(new VehicleIdentityLookup(ProtocolId.JT808, "", phone, "", "")), - null); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - if (phone != null && !phone.isBlank()) { - return new IdentityResolution(new VehicleIdentity(phone, false, VehicleIdentitySource.FALLBACK_PHONE), null); - } - return new IdentityResolution(new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), null); - } - - private static Map identityMeta(IdentityResolution resolution, String... kv) { - VehicleIdentity identity = resolution.identity(); - HashMap out = new HashMap<>(); - out.put("vin", identity.vin()); - out.put("identityResolved", Boolean.toString(identity.resolved())); - out.put("identitySource", identity.source().name()); - if (resolution.errorMessage() != null) { - out.put("identityError", "true"); - out.put("identityErrorMessage", resolution.errorMessage()); - } - for (int i = 0; i + 1 < kv.length; i += 2) { - out.put(kv[i], kv[i + 1] == null ? "" : kv[i + 1]); - } - return Map.copyOf(out); - } - - private static String addr(ChannelHandlerContext ctx) { - if (ctx.channel().remoteAddress() instanceof InetSocketAddress i) { - return i.getAddress().getHostAddress() + ":" + i.getPort(); - } - return "unknown"; - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) {} -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12DataPacket.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12DataPacket.java deleted file mode 100644 index ba76c156..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12DataPacket.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -public record Jsatl12DataPacket(String name, long offset, long length, byte[] data) { - - private static final int HEADER_LENGTH = 62; - - public Jsatl12DataPacket { - name = name == null ? "" : name.trim(); - data = data == null ? new byte[0] : Arrays.copyOf(data, data.length); - if (offset < 0) { - throw new IllegalArgumentException("jsatl12 data packet offset must not be negative"); - } - if (length < 0) { - throw new IllegalArgumentException("jsatl12 data packet length must not be negative"); - } - if (length != data.length) { - throw new IllegalArgumentException("jsatl12 data packet length " + length - + " does not match data length " + data.length); - } - } - - @Override - public byte[] data() { - return Arrays.copyOf(data, data.length); - } - - public static Jsatl12DataPacket parse(byte[] bytes) { - byte[] raw = bytes == null ? new byte[0] : bytes; - if (raw.length < HEADER_LENGTH) { - throw new IllegalArgumentException("jsatl12 data packet too short: " + raw.length); - } - if (raw[0] != 0x30 || raw[1] != 0x31 || raw[2] != 0x63 || raw[3] != 0x64) { - throw new IllegalArgumentException("jsatl12 data packet magic mismatch"); - } - String name = readFixedString(raw, 4, 50); - long offset = readDword(raw, 54); - long length = readDword(raw, 58); - if (length > Integer.MAX_VALUE) { - throw new IllegalArgumentException("jsatl12 data packet length too large: " + length); - } - if (raw.length < HEADER_LENGTH + (int) length) { - throw new IllegalArgumentException("jsatl12 data packet body truncated: " + raw.length); - } - byte[] data = Arrays.copyOfRange(raw, HEADER_LENGTH, HEADER_LENGTH + (int) length); - return new Jsatl12DataPacket(name, offset, length, data); - } - - private static String readFixedString(byte[] raw, int offset, int length) { - int end = offset; - int max = Math.min(raw.length, offset + length); - while (end < max && raw[end] != 0) { - end++; - } - return new String(raw, offset, end - offset, StandardCharsets.UTF_8).trim(); - } - - static long readDword(byte[] raw, int offset) { - return ((raw[offset] & 0xFFL) << 24) - | ((raw[offset + 1] & 0xFFL) << 16) - | ((raw[offset + 2] & 0xFFL) << 8) - | (raw[offset + 3] & 0xFFL); - } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridge.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridge.java deleted file mode 100644 index 60bb4797..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridge.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.protocol.jt808.codec.Jt808Escape; -import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameEncoder; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; - -public final class Jsatl12JtMessageBridge { - - private final Jt808MessageDecoder decoder; - private final VehicleIdentityResolver identityResolver; - private final Jsatl12AttachmentTracker attachmentTracker; - private final Consumer dispatcher; - - public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder, Consumer dispatcher) { - this(decoder, (VehicleIdentityResolver) null, dispatcher); - } - - public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder, - VehicleIdentityResolver identityResolver, - Consumer dispatcher) { - this(decoder, identityResolver, new Jsatl12AttachmentTracker(), dispatcher); - } - - public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder, - Jsatl12AttachmentTracker attachmentTracker, - Consumer dispatcher) { - this(decoder, null, attachmentTracker, dispatcher); - } - - public Jsatl12JtMessageBridge(Jt808MessageDecoder decoder, - VehicleIdentityResolver identityResolver, - Jsatl12AttachmentTracker attachmentTracker, - Consumer dispatcher) { - this.decoder = decoder; - this.identityResolver = identityResolver; - this.attachmentTracker = attachmentTracker == null ? new Jsatl12AttachmentTracker() : attachmentTracker; - this.dispatcher = dispatcher; - } - - public Optional dispatch(byte[] jtMessageBytes, String peer) { - Instant receivedAt = Instant.now(); - String peerValue = peer == null ? "" : peer; - byte[] raw = jtMessageBytes == null ? new byte[0] : jtMessageBytes; - byte[] unescaped; - Jt808Message message; - try { - unescaped = Jt808Escape.unescape(raw, 0, raw.length); - message = decoder.decode(ByteBuffer.wrap(unescaped)); - } catch (RuntimeException e) { - dispatchMalformed(raw, peerValue, e, receivedAt); - return Optional.empty(); - } - try { - IdentityResolution identity = resolveIdentity(message.header().phone()); - Map meta = new HashMap<>(); - meta.put("vin", identity.identity().vin()); - meta.put("phone", message.header().phone()); - meta.put("identityResolved", Boolean.toString(identity.identity().resolved())); - meta.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - meta.put("identityError", "true"); - meta.put("identityErrorMessage", identity.errorMessage()); - } - meta.put("peer", peerValue); - meta.put("source", "jsatl12-signaling"); - dispatcher.accept(new RawFrame( - ProtocolId.JT808, - message.header().messageId(), - 0, - message, - unescaped, - meta, - receivedAt)); - return handleJsatl12Signaling(message); - } catch (RuntimeException e) { - dispatchMalformed(raw, peerValue, e, receivedAt); - return Optional.empty(); - } - } - - private Optional handleJsatl12Signaling(Jt808Message message) { - if (!(message.body() instanceof Jt808Body.Raw rawBody)) { - return Optional.empty(); - } - if (message.header().messageId() == 0x1210) { - attachmentTracker.registerAttachmentList(message.header().phone(), rawBody.bytes()); - return Optional.empty(); - } - if (message.header().messageId() == 0x1212) { - Jsatl12UploadCompletionAck ack = - attachmentTracker.completeUpload(message.header().phone(), rawBody.bytes()); - return Optional.of(Jt808FrameEncoder.encode( - 0x9212, - message.header().phone(), - message.header().serialNo(), - encodeAckBody(ack), - message.header().version())); - } - return Optional.empty(); - } - - private static byte[] encodeAckBody(Jsatl12UploadCompletionAck ack) { - byte[] name = ack.name().getBytes(StandardCharsets.UTF_8); - if (name.length > 255) { - throw new IllegalArgumentException("jsatl12 completion ack filename too long: " + ack.name()); - } - ByteArrayOutputStream out = new ByteArrayOutputStream(4 + name.length + ack.missingRanges().size() * 8); - out.write(name.length); - out.write(name, 0, name.length); - out.write(ack.type() & 0xFF); - out.write(ack.result() & 0xFF); - out.write(ack.missingRanges().size() & 0xFF); - for (Jsatl12UploadCompletionAck.Range range : ack.missingRanges()) { - writeDword(out, range.offset()); - writeDword(out, range.length()); - } - return out.toByteArray(); - } - - private static void writeDword(ByteArrayOutputStream out, long value) { - if (value < 0 || value > 0xFFFF_FFFFL) { - throw new IllegalArgumentException("jsatl12 dword out of range: " + value); - } - out.write((int) ((value >>> 24) & 0xFF)); - out.write((int) ((value >>> 16) & 0xFF)); - out.write((int) ((value >>> 8) & 0xFF)); - out.write((int) (value & 0xFF)); - } - - private IdentityResolution resolveIdentity(String phone) { - if (identityResolver == null) { - return new IdentityResolution( - new VehicleIdentity(phone, false, VehicleIdentitySource.FALLBACK_PHONE), - null); - } - try { - return new IdentityResolution( - identityResolver.resolve(new VehicleIdentityLookup(ProtocolId.JT808, "", phone, "", "")), - null); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private void dispatchMalformed(byte[] raw, String peer, RuntimeException error, Instant receivedAt) { - Map meta = new HashMap<>(); - meta.put("vin", "unknown"); - meta.put("identityResolved", "false"); - meta.put("identitySource", VehicleIdentitySource.UNKNOWN.name()); - meta.put("peer", peer); - meta.put("source", "jsatl12-jt-message"); - meta.put("parseError", "true"); - meta.put("parseErrorMessage", error.getMessage() == null ? "" : error.getMessage()); - dispatcher.accept(new RawFrame( - ProtocolId.JSATL12, - Jsatl12MessageId.MALFORMED_JT_MESSAGE, - 0, - new Jsatl12MalformedJtMessage(raw, peer, error.getMessage(), receivedAt), - raw, - meta, - receivedAt)); - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) {} -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedFrame.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedFrame.java deleted file mode 100644 index fff586e3..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedFrame.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import java.time.Instant; - -public record Jsatl12MalformedFrame( - byte[] rawBytes, - String peer, - String errorMessage, - Instant receivedAt -) {} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedJtMessage.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedJtMessage.java deleted file mode 100644 index 3814155f..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MalformedJtMessage.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import java.time.Instant; - -public record Jsatl12MalformedJtMessage( - byte[] rawBytes, - String peer, - String errorMessage, - Instant receivedAt -) {} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MessageId.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MessageId.java deleted file mode 100644 index e170150d..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12MessageId.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -public final class Jsatl12MessageId { - - public static final int DATA_CHUNK_ARCHIVED = 0x30316364; - public static final int MALFORMED_JT_MESSAGE = 0x30316A65; - public static final int DATA_CHUNK_ARCHIVE_FAILED = 0x30316665; - public static final int MALFORMED_FRAME = 0x30316672; - - private Jsatl12MessageId() {} -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12NettyServer.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12NettyServer.java deleted file mode 100644 index d765de4b..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12NettyServer.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.protocol.jsatl12.codec.Jsatl12FrameDecoder; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; - -import java.util.concurrent.ThreadFactory; -import java.util.function.Consumer; - -/** - * 苏标报警附件 Netty 服务端。独立端口(默认 7612)。 - */ -public final class Jsatl12NettyServer implements InitializingBean, DisposableBean { - - private static final Logger log = LoggerFactory.getLogger(Jsatl12NettyServer.class); - - private final int port; - private final int workerThreads; - private final int maxFrameLength; - private final ArchiveStore archive; - private final Jsatl12JtMessageBridge jtMessageBridge; - private final Jsatl12AttachmentTracker attachmentTracker; - private final VehicleIdentityResolver identityResolver; - private final Consumer dataChunkDispatcher; - - private EventLoopGroup boss; - private EventLoopGroup workers; - private Channel serverChannel; - - public Jsatl12NettyServer(int port, - int workerThreads, - int maxFrameLength, - ArchiveStore archive, - Jsatl12JtMessageBridge jtMessageBridge, - Jsatl12AttachmentTracker attachmentTracker, - Consumer dataChunkDispatcher) { - this(port, workerThreads, maxFrameLength, archive, jtMessageBridge, attachmentTracker, null, dataChunkDispatcher); - } - - public Jsatl12NettyServer(int port, - int workerThreads, - int maxFrameLength, - ArchiveStore archive, - Jsatl12JtMessageBridge jtMessageBridge, - Jsatl12AttachmentTracker attachmentTracker, - VehicleIdentityResolver identityResolver, - Consumer dataChunkDispatcher) { - this.port = port; - this.workerThreads = workerThreads; - this.maxFrameLength = maxFrameLength; - this.archive = archive; - this.jtMessageBridge = jtMessageBridge; - this.attachmentTracker = attachmentTracker == null ? new Jsatl12AttachmentTracker() : attachmentTracker; - this.identityResolver = identityResolver; - this.dataChunkDispatcher = dataChunkDispatcher; - } - - @Override - public void afterPropertiesSet() throws Exception { - ThreadFactory bossTf = r -> new Thread(r, "jsatl12-boss"); - ThreadFactory wkTf = r -> new Thread(r, "jsatl12-worker"); - this.boss = new NioEventLoopGroup(1, bossTf); - int threads = workerThreads > 0 ? workerThreads : Runtime.getRuntime().availableProcessors(); - this.workers = new NioEventLoopGroup(threads, wkTf); - - ServerBootstrap b = new ServerBootstrap(); - b.group(boss, workers) - .channel(NioServerSocketChannel.class) - .option(ChannelOption.SO_BACKLOG, 256) - .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.SO_KEEPALIVE, true) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) { - ch.pipeline() - .addLast("frame", new Jsatl12FrameDecoder(maxFrameLength)) - .addLast("dispatch", new Jsatl12ChannelHandler( - archive, jtMessageBridge, attachmentTracker, identityResolver, dataChunkDispatcher)); - } - }); - this.serverChannel = b.bind(port).sync().channel(); - log.info("jsatl12 Netty server listening on :{} workers={} maxFrameLength={}", - port, threads, maxFrameLength); - } - - @Override - public void destroy() { - try { - if (serverChannel != null) serverChannel.close().sync(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - if (boss != null) boss.shutdownGracefully(); - if (workers != null) workers.shutdownGracefully(); - log.info("jsatl12 Netty server stopped"); - } - } -} diff --git a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12UploadCompletionAck.java b/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12UploadCompletionAck.java deleted file mode 100644 index b09de61a..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12UploadCompletionAck.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import java.util.List; - -public record Jsatl12UploadCompletionAck( - String name, - int type, - int result, - List missingRanges -) { - public Jsatl12UploadCompletionAck { - name = name == null ? "" : name; - missingRanges = missingRanges == null ? List.of() : List.copyOf(missingRanges); - } - - public record Range(long offset, long length) {} -} diff --git a/modules/protocols/protocol-jsatl12/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/protocols/protocol-jsatl12/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 1080dfb0..00000000 --- a/modules/protocols/protocol-jsatl12/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.protocol.jsatl12.config.Jsatl12AutoConfiguration diff --git a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12DataPacketDecoderTest.java b/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12DataPacketDecoderTest.java deleted file mode 100644 index c46ff7e5..00000000 --- a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12DataPacketDecoderTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.codec; - -import io.netty.channel.embedded.EmbeddedChannel; -import io.netty.buffer.Unpooled; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jsatl12DataPacketDecoderTest { - - @Test - void dataPacketUsesFilenameOffsetLengthLayoutInsteadOfMagicLengthLayout() { - EmbeddedChannel channel = new EmbeddedChannel(new Jsatl12FrameDecoder(128)); - byte[] packet = dataPacket("ADAS_001.jpg", 3, new byte[]{0x04, 0x05, 0x06}); - - channel.writeInbound(Unpooled.wrappedBuffer(packet)); - - Jsatl12RawFrame frame = channel.readInbound(); - assertThat(frame.type()).isEqualTo(Jsatl12RawFrame.Type.DATA_CHUNK); - assertThat(frame.bytes()).containsExactly(packet); - Object next = channel.readInbound(); - assertThat(next).isNull(); - } - - private static byte[] dataPacket(String name, int offset, byte[] data) { - byte[] out = new byte[62 + data.length]; - out[0] = 0x30; - out[1] = 0x31; - out[2] = 0x63; - out[3] = 0x64; - byte[] nameBytes = name.getBytes(StandardCharsets.US_ASCII); - System.arraycopy(nameBytes, 0, out, 4, nameBytes.length); - writeDword(out, 54, offset); - writeDword(out, 58, data.length); - System.arraycopy(data, 0, out, 62, data.length); - return out; - } - - private static void writeDword(byte[] out, int offset, long value) { - out[offset] = (byte) ((value >>> 24) & 0xFF); - out[offset + 1] = (byte) ((value >>> 16) & 0xFF); - out[offset + 2] = (byte) ((value >>> 8) & 0xFF); - out[offset + 3] = (byte) (value & 0xFF); - } -} diff --git a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoderTest.java b/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoderTest.java deleted file mode 100644 index 2899e1ec..00000000 --- a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/codec/Jsatl12FrameDecoderTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.codec; - -import io.netty.buffer.Unpooled; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jsatl12FrameDecoderTest { - - @Test - void decodesDataChunkFrame() { - EmbeddedChannel channel = new EmbeddedChannel(new Jsatl12FrameDecoder(16)); - byte[] packet = dataPacket("ADAS_001.jpg", 0, new byte[]{0x01, 0x02, 0x03}); - - channel.writeInbound(Unpooled.wrappedBuffer(packet)); - - Jsatl12RawFrame frame = channel.readInbound(); - assertThat(frame.type()).isEqualTo(Jsatl12RawFrame.Type.DATA_CHUNK); - assertThat(frame.bytes()).containsExactly(packet); - } - - @Test - void oversizedDataFrameEmitsMalformedCandidate() { - EmbeddedChannel channel = new EmbeddedChannel(new Jsatl12FrameDecoder(2)); - byte[] packet = dataPacket("ADAS_001.jpg", 0, new byte[]{0x01, 0x02, 0x03}); - - channel.writeInbound(Unpooled.wrappedBuffer(packet)); - - Jsatl12RawFrame decoded = channel.readInbound(); - assertThat(decoded.type()).isEqualTo(Jsatl12RawFrame.Type.MALFORMED); - assertThat(decoded.bytes()).containsExactly(packet); - } - - @Test - void oversizedUnclosedJtFrameEmitsMalformedCandidate() { - EmbeddedChannel channel = new EmbeddedChannel(new Jsatl12FrameDecoder(4)); - - channel.writeInbound(Unpooled.wrappedBuffer(new byte[]{ - 0x7e, 0x01, 0x02, 0x03, 0x04, 0x05 - })); - - Jsatl12RawFrame decoded = channel.readInbound(); - assertThat(decoded.type()).isEqualTo(Jsatl12RawFrame.Type.MALFORMED); - assertThat(decoded.bytes()).containsExactly( - 0x7e, 0x01, 0x02, 0x03, 0x04, 0x05); - assertThat((Object) channel.readInbound()).isNull(); - } - - private static byte[] dataPacket(String name, int offset, byte[] data) { - byte[] out = new byte[62 + data.length]; - out[0] = 0x30; - out[1] = 0x31; - out[2] = 0x63; - out[3] = 0x64; - byte[] nameBytes = name.getBytes(StandardCharsets.US_ASCII); - System.arraycopy(nameBytes, 0, out, 4, nameBytes.length); - writeDword(out, 54, offset); - writeDword(out, 58, data.length); - System.arraycopy(data, 0, out, 62, data.length); - return out; - } - - private static void writeDword(byte[] out, int offset, long value) { - out[offset] = (byte) ((value >>> 24) & 0xFF); - out[offset + 1] = (byte) ((value >>> 16) & 0xFF); - out[offset + 2] = (byte) ((value >>> 8) & 0xFF); - out[offset + 3] = (byte) (value & 0xFF); - } -} diff --git a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandlerTest.java b/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandlerTest.java deleted file mode 100644 index 634b9d41..00000000 --- a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/handler/Jsatl12ArchiveEventHandlerTest.java +++ /dev/null @@ -1,175 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12ArchiveFailure; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12ArchivedChunk; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12MalformedFrame; -import com.lingniu.ingest.protocol.jsatl12.inbound.Jsatl12MalformedJtMessage; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jsatl12ArchiveEventHandlerTest { - - @Test - void archivedDataChunkProducesMediaMetaEvent() { - Jsatl12ArchiveEventHandler handler = new Jsatl12ArchiveEventHandler(); - Jsatl12ArchivedChunk chunk = new Jsatl12ArchivedChunk( - "file:///archive/jsatl12/2026/06/22/peer/file.bin", - "jsatl12/2026/06/22/peer/file.bin", - "ADAS_001.jpg", - 64, - 128, - 128, - "10.0.0.8:7612", - Instant.parse("2026-06-22T01:02:03Z")); - - List events = handler.onDataChunkArchived(chunk); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.MediaMeta.class); - VehicleEvent.MediaMeta media = (VehicleEvent.MediaMeta) event; - assertThat(media.source()).isEqualTo(ProtocolId.JSATL12); - assertThat(media.vin()).isEqualTo("unknown"); - assertThat(media.eventTime()).isEqualTo(Instant.parse("2026-06-22T01:02:03Z")); - assertThat(media.archiveRef()).isEqualTo("file:///archive/jsatl12/2026/06/22/peer/file.bin"); - assertThat(media.sizeBytes()).isEqualTo(128); - assertThat(media.mediaType()).isEqualTo("jsatl12-data-chunk"); - assertThat(media.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("archiveKey", "jsatl12/2026/06/22/peer/file.bin") - .containsEntry("fileName", "ADAS_001.jpg") - .containsEntry("fileOffset", "64") - .containsEntry("declaredChunkSizeBytes", "128") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } - - @Test - void archivedDataChunkKeepsResolvedVehicleIdentity() { - Jsatl12ArchiveEventHandler handler = new Jsatl12ArchiveEventHandler(); - Jsatl12ArchivedChunk chunk = new Jsatl12ArchivedChunk( - "file:///archive/jsatl12/2026/06/22/peer/file.bin", - "jsatl12/2026/06/22/peer/file.bin", - "ADAS_001.jpg", - 0, - 128, - 128, - "10.0.0.8:7612", - Instant.parse("2026-06-22T01:02:03Z"), - "123456789012", - "LNVIN00000001212", - true, - "BOUND_PHONE"); - - List events = handler.onDataChunkArchived(chunk); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.MediaMeta.class); - VehicleEvent.MediaMeta media = (VehicleEvent.MediaMeta) event; - assertThat(media.vin()).isEqualTo("LNVIN00000001212"); - assertThat(media.metadata()) - .containsEntry("vin", "LNVIN00000001212") - .containsEntry("phone", "123456789012") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE"); - }); - } - - @Test - void malformedJtMessageProducesPassthroughEvent() { - Jsatl12ArchiveEventHandler handler = new Jsatl12ArchiveEventHandler(); - byte[] raw = new byte[]{0x01, 0x02, 0x03}; - Jsatl12MalformedJtMessage malformed = new Jsatl12MalformedJtMessage( - raw, - "10.0.0.8:7612", - "jt808 frame too short", - Instant.parse("2026-06-22T01:02:03Z")); - - List events = handler.onMalformedJtMessage(malformed); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.JSATL12); - assertThat(passthrough.vin()).isEqualTo("unknown"); - assertThat(passthrough.eventTime()).isEqualTo(Instant.parse("2026-06-22T01:02:03Z")); - assertThat(passthrough.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("peer", "10.0.0.8:7612") - .containsEntry("parseError", "true") - .containsEntry("source", "jsatl12-jt-message") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - assertThat(passthrough.data()).containsExactly(raw); - }); - } - - @Test - void archiveFailureProducesPassthroughEvent() { - Jsatl12ArchiveEventHandler handler = new Jsatl12ArchiveEventHandler(); - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64, 0, 0, 0, 1, 0x7f}; - Jsatl12ArchiveFailure failure = new Jsatl12ArchiveFailure( - raw, - "jsatl12/2026/06/22/peer/file.bin", - 1, - "10.0.0.8:7612", - "archive backend unavailable", - Instant.parse("2026-06-22T01:02:03Z")); - - List events = handler.onArchiveFailure(failure); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.JSATL12); - assertThat(passthrough.vin()).isEqualTo("unknown"); - assertThat(passthrough.eventTime()).isEqualTo(Instant.parse("2026-06-22T01:02:03Z")); - assertThat(passthrough.data()).containsExactly(raw); - assertThat(passthrough.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("archiveError", "true") - .containsEntry("archiveKey", "jsatl12/2026/06/22/peer/file.bin") - .containsEntry("chunkSizeBytes", "1") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } - - @Test - void malformedFrameProducesPassthroughEvent() { - Jsatl12ArchiveEventHandler handler = new Jsatl12ArchiveEventHandler(); - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64, 0, 0, 0, 3, 0x01, 0x02, 0x03}; - Jsatl12MalformedFrame malformed = new Jsatl12MalformedFrame( - raw, - "10.0.0.8:7612", - "malformed jsatl12 frame", - Instant.parse("2026-06-22T01:02:03Z")); - - List events = handler.onMalformedFrame(malformed); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.JSATL12); - assertThat(passthrough.vin()).isEqualTo("unknown"); - assertThat(passthrough.eventTime()).isEqualTo(Instant.parse("2026-06-22T01:02:03Z")); - assertThat(passthrough.data()).containsExactly(raw); - assertThat(passthrough.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("peer", "10.0.0.8:7612") - .containsEntry("source", "jsatl12-frame") - .containsEntry("frameError", "true") - .containsEntry("frameErrorMessage", "malformed jsatl12 frame") - .containsEntry("parseError", "true") - .containsEntry("parseErrorMessage", "malformed jsatl12 frame") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } -} diff --git a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTrackerTest.java b/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTrackerTest.java deleted file mode 100644 index 931c6e25..00000000 --- a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12AttachmentTrackerTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; -import static org.assertj.core.api.Assertions.assertThat; - -class Jsatl12AttachmentTrackerTest { - - @Test - void completionAckRequestsOnlyMissingRangesUntilFileIsComplete() { - Jsatl12AttachmentTracker tracker = new Jsatl12AttachmentTracker(); - String phone = "13800138000"; - String fileName = "ADAS_001.jpg"; - - tracker.registerAttachmentList(phone, t1210Body(fileName, 6)); - tracker.recordDataPacket(phone, dataPacket(fileName, 0, new byte[]{0x01, 0x02, 0x03})); - - Jsatl12UploadCompletionAck first = tracker.completeUpload(phone, t1212Body(fileName, 2, 6)); - - assertThat(first.name()).isEqualTo(fileName); - assertThat(first.type()).isEqualTo(2); - assertThat(first.result()).isEqualTo(1); - assertThat(first.missingRanges()).containsExactly(new Jsatl12UploadCompletionAck.Range(3, 3)); - - tracker.recordDataPacket(phone, dataPacket(fileName, 3, new byte[]{0x04, 0x05, 0x06})); - - Jsatl12UploadCompletionAck second = tracker.completeUpload(phone, t1212Body(fileName, 2, 6)); - - assertThat(second.result()).isZero(); - assertThat(second.missingRanges()).isEmpty(); - assertThat(tracker.knownFiles(phone)).doesNotContain(fileName); - } - - @Test - void dataPacketCanBeParsedFromProductionLayout() { - Jsatl12DataPacket packet = Jsatl12DataPacket.parse(dataPacket("BSD_002.bin", 12, new byte[]{0x01, 0x02})); - - assertThat(packet.name()).isEqualTo("BSD_002.bin"); - assertThat(packet.offset()).isEqualTo(12); - assertThat(packet.length()).isEqualTo(2); - assertThat(packet.data()).containsExactly(0x01, 0x02); - } - - private static byte[] t1210Body(String fileName, long size) { - byte[] name = fileName.getBytes(StandardCharsets.US_ASCII); - byte[] out = new byte[57 + 1 + name.length + 4]; - putAscii(out, 0, 7, "DEV0001"); - putAscii(out, 7, 7, "DEV0001"); - out[13] = 0x26; - out[14] = 0x06; - out[15] = 0x22; - out[16] = 0x10; - out[17] = 0x30; - out[18] = 0x45; - out[19] = 0x01; - out[20] = 0x01; - putAscii(out, 23, 32, "ALARM-NO-001"); - out[55] = 0; - out[56] = 1; - out[57] = (byte) name.length; - System.arraycopy(name, 0, out, 58, name.length); - writeDword(out, 58 + name.length, size); - return out; - } - - private static byte[] t1212Body(String fileName, int type, long size) { - byte[] name = fileName.getBytes(StandardCharsets.US_ASCII); - byte[] out = new byte[1 + name.length + 1 + 4]; - out[0] = (byte) name.length; - System.arraycopy(name, 0, out, 1, name.length); - out[1 + name.length] = (byte) type; - writeDword(out, 2 + name.length, size); - return out; - } - - private static byte[] dataPacket(String name, int offset, byte[] data) { - byte[] out = new byte[62 + data.length]; - out[0] = 0x30; - out[1] = 0x31; - out[2] = 0x63; - out[3] = 0x64; - putAscii(out, 4, 50, name); - writeDword(out, 54, offset); - writeDword(out, 58, data.length); - System.arraycopy(data, 0, out, 62, data.length); - return out; - } - - private static void putAscii(byte[] out, int offset, int len, String value) { - byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - System.arraycopy(bytes, 0, out, offset, Math.min(bytes.length, len)); - } - - private static void writeDword(byte[] out, int offset, long value) { - out[offset] = (byte) ((value >>> 24) & 0xFF); - out[offset + 1] = (byte) ((value >>> 16) & 0xFF); - out[offset + 2] = (byte) ((value >>> 8) & 0xFF); - out[offset + 3] = (byte) (value & 0xFF); - } -} diff --git a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandlerTest.java b/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandlerTest.java deleted file mode 100644 index 27486689..00000000 --- a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12ChannelHandlerTest.java +++ /dev/null @@ -1,297 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.protocol.jsatl12.codec.Jsatl12RawFrame; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jsatl12ChannelHandlerTest { - - @Test - void dataChunkArchivesPayloadAndDispatchesQueryableArchiveReference() { - RecordingArchiveStore archive = new RecordingArchiveStore(); - List dispatched = new ArrayList<>(); - Jsatl12ChannelHandler handler = new Jsatl12ChannelHandler(archive, null, dispatched::add); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(new Jsatl12RawFrame( - Jsatl12RawFrame.Type.DATA_CHUNK, - dataPacket("ADAS_001.jpg", 0, new byte[]{0x01, 0x02, 0x03}))); - - assertThat(archive.lastChunk).containsExactly(0x01, 0x02, 0x03); - assertThat(dispatched).hasSize(1); - RawFrame frame = dispatched.getFirst(); - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JSATL12); - assertThat(frame.command()).isEqualTo(Jsatl12MessageId.DATA_CHUNK_ARCHIVED); - assertThat(frame.payload()).isInstanceOf(Jsatl12ArchivedChunk.class); - Jsatl12ArchivedChunk chunk = (Jsatl12ArchivedChunk) frame.payload(); - assertThat(chunk.archiveRef()).isEqualTo("memory://" + archive.lastKey); - assertThat(chunk.sizeBytes()).isEqualTo(3); - assertThat(chunk.peer()).isEqualTo("unknown"); - assertThat(frame.sourceMeta()) - .containsEntry("source", "jsatl12-data-chunk") - .containsEntry("fileName", "ADAS_001.jpg") - .containsEntry("fileOffset", "0") - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - } - - @Test - void dataChunkUsesAttachmentListPhoneForVehicleIdentity() { - RecordingArchiveStore archive = new RecordingArchiveStore(); - Jsatl12AttachmentTracker tracker = new Jsatl12AttachmentTracker(); - String phone = "123456789012"; - tracker.registerAttachmentList(phone, t1210Body("ADAS_001.jpg", 3)); - InMemoryVehicleIdentityService identities = new InMemoryVehicleIdentityService(); - identities.bind(new VehicleIdentityBinding( - ProtocolId.JT808, - "LNVIN00000001212", - phone, - "", - "")); - List dispatched = new ArrayList<>(); - Jsatl12ChannelHandler handler = new Jsatl12ChannelHandler( - archive, - null, - tracker, - identities, - dispatched::add); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(new Jsatl12RawFrame( - Jsatl12RawFrame.Type.DATA_CHUNK, - dataPacket("ADAS_001.jpg", 0, new byte[]{0x01, 0x02, 0x03}))); - - assertThat(dispatched).singleElement().satisfies(frame -> { - assertThat(frame.sourceMeta()) - .containsEntry("phone", phone) - .containsEntry("vin", "LNVIN00000001212") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE"); - assertThat(frame.payload()).isInstanceOf(Jsatl12ArchivedChunk.class); - Jsatl12ArchivedChunk chunk = (Jsatl12ArchivedChunk) frame.payload(); - assertThat(chunk.phone()).isEqualTo(phone); - assertThat(chunk.vin()).isEqualTo("LNVIN00000001212"); - assertThat(chunk.identityResolved()).isTrue(); - assertThat(chunk.identitySource()).isEqualTo("BOUND_PHONE"); - }); - } - - @Test - void dataChunkStillArchivesWhenIdentityResolverFails() { - RecordingArchiveStore archive = new RecordingArchiveStore(); - Jsatl12AttachmentTracker tracker = new Jsatl12AttachmentTracker(); - String phone = "123456789012"; - tracker.registerAttachmentList(phone, t1210Body("ADAS_001.jpg", 3)); - List dispatched = new ArrayList<>(); - Jsatl12ChannelHandler handler = new Jsatl12ChannelHandler( - archive, - null, - tracker, - lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }, - dispatched::add); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] frameBytes = dataPacket("ADAS_001.jpg", 0, new byte[]{0x01, 0x02, 0x03}); - - channel.writeInbound(new Jsatl12RawFrame(Jsatl12RawFrame.Type.DATA_CHUNK, frameBytes)); - - assertThat(archive.lastChunk).containsExactly(0x01, 0x02, 0x03); - assertThat(dispatched).singleElement().satisfies(frame -> { - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JSATL12); - assertThat(frame.command()).isEqualTo(Jsatl12MessageId.DATA_CHUNK_ARCHIVED); - assertThat(frame.rawBytes()).isNull(); - assertThat(frame.payload()).isInstanceOf(Jsatl12ArchivedChunk.class); - Jsatl12ArchivedChunk chunk = (Jsatl12ArchivedChunk) frame.payload(); - assertThat(chunk.phone()).isEqualTo(phone); - assertThat(chunk.vin()).isEqualTo("unknown"); - assertThat(chunk.identityResolved()).isFalse(); - assertThat(chunk.identitySource()).isEqualTo("UNKNOWN"); - assertThat(frame.sourceMeta()) - .containsEntry("phone", phone) - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable") - .containsEntry("archiveRef", "memory://" + archive.lastKey); - }); - } - - @Test - void dataChunkArchiveFailureDispatchesPassthroughCandidateWithRawPayload() { - FailingArchiveStore archive = new FailingArchiveStore(); - List dispatched = new ArrayList<>(); - Jsatl12ChannelHandler handler = new Jsatl12ChannelHandler(archive, null, dispatched::add); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] frameBytes = dataPacket("ADAS_001.jpg", 0, new byte[]{0x01, 0x02, 0x03}); - - channel.writeInbound(new Jsatl12RawFrame(Jsatl12RawFrame.Type.DATA_CHUNK, frameBytes)); - - assertThat(dispatched).singleElement().satisfies(frame -> { - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JSATL12); - assertThat(frame.command()).isEqualTo(Jsatl12MessageId.DATA_CHUNK_ARCHIVE_FAILED); - assertThat(frame.rawBytes()).containsExactly(frameBytes); - assertThat(frame.payload()).isInstanceOf(Jsatl12ArchiveFailure.class); - Jsatl12ArchiveFailure failure = (Jsatl12ArchiveFailure) frame.payload(); - assertThat(failure.rawBytes()).containsExactly(frameBytes); - assertThat(failure.sizeBytes()).isEqualTo(3); - assertThat(failure.peer()).isEqualTo("unknown"); - assertThat(failure.errorMessage()).contains("archive backend unavailable"); - assertThat(frame.sourceMeta()) - .containsEntry("source", "jsatl12-data-chunk") - .containsEntry("archiveError", "true") - .containsEntry("chunkSizeBytes", "3") - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } - - @Test - void malformedFrameDispatchesPassthroughCandidateWithRawPayload() { - RecordingArchiveStore archive = new RecordingArchiveStore(); - List dispatched = new ArrayList<>(); - Jsatl12ChannelHandler handler = new Jsatl12ChannelHandler(archive, null, dispatched::add); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] raw = new byte[]{ - 0x30, 0x31, 0x63, 0x64, - 0x00, 0x00, 0x00, 0x03, - 0x01, 0x02, 0x03 - }; - - channel.writeInbound(new Jsatl12RawFrame(Jsatl12RawFrame.Type.MALFORMED, raw)); - - assertThat(dispatched).singleElement().satisfies(frame -> { - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JSATL12); - assertThat(frame.command()).isEqualTo(Jsatl12MessageId.MALFORMED_FRAME); - assertThat(frame.rawBytes()).containsExactly(raw); - assertThat(frame.payload()).isInstanceOf(Jsatl12MalformedFrame.class); - Jsatl12MalformedFrame malformed = (Jsatl12MalformedFrame) frame.payload(); - assertThat(malformed.rawBytes()).containsExactly(raw); - assertThat(malformed.peer()).isEqualTo("unknown"); - assertThat(malformed.errorMessage()).contains("malformed jsatl12 frame"); - assertThat(frame.sourceMeta()) - .containsEntry("source", "jsatl12-frame") - .containsEntry("frameError", "true") - .containsEntry("frameSizeBytes", Integer.toString(raw.length)) - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - } - - private static byte[] dataPacket(String name, int offset, byte[] data) { - byte[] out = new byte[62 + data.length]; - out[0] = 0x30; - out[1] = 0x31; - out[2] = 0x63; - out[3] = 0x64; - byte[] nameBytes = name.getBytes(StandardCharsets.US_ASCII); - System.arraycopy(nameBytes, 0, out, 4, nameBytes.length); - writeDword(out, 54, offset); - writeDword(out, 58, data.length); - System.arraycopy(data, 0, out, 62, data.length); - return out; - } - - private static byte[] t1210Body(String fileName, long size) { - byte[] name = fileName.getBytes(StandardCharsets.US_ASCII); - byte[] out = new byte[57 + 1 + name.length + 4]; - putAscii(out, 0, 7, "DEV0001"); - putAscii(out, 7, 7, "DEV0001"); - putAscii(out, 23, 32, "ALARM-NO-001"); - out[55] = 0; - out[56] = 1; - out[57] = (byte) name.length; - System.arraycopy(name, 0, out, 58, name.length); - writeDword(out, 58 + name.length, size); - return out; - } - - private static void putAscii(byte[] out, int offset, int len, String value) { - byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - System.arraycopy(bytes, 0, out, offset, Math.min(bytes.length, len)); - } - - private static void writeDword(byte[] out, int offset, long value) { - out[offset] = (byte) ((value >>> 24) & 0xFF); - out[offset + 1] = (byte) ((value >>> 16) & 0xFF); - out[offset + 2] = (byte) ((value >>> 8) & 0xFF); - out[offset + 3] = (byte) (value & 0xFF); - } - - private static final class RecordingArchiveStore implements ArchiveStore { - private String lastKey; - private byte[] lastChunk; - - @Override - public String put(String key, InputStream data, long length) { - throw new UnsupportedOperationException(); - } - - @Override - public String append(String key, byte[] chunk) { - this.lastKey = key; - this.lastChunk = chunk; - return "memory://" + key; - } - - @Override - public InputStream get(String key) { - return new ByteArrayInputStream(new byte[0]); - } - - @Override - public boolean exists(String key) { - return false; - } - - @Override - public long size(String key) throws IOException { - return lastChunk == null ? 0 : lastChunk.length; - } - } - - private static final class FailingArchiveStore implements ArchiveStore { - @Override - public String put(String key, InputStream data, long length) throws IOException { - throw new IOException("archive backend unavailable"); - } - - @Override - public String append(String key, byte[] chunk) throws IOException { - throw new IOException("archive backend unavailable"); - } - - @Override - public InputStream get(String key) { - return new ByteArrayInputStream(new byte[0]); - } - - @Override - public boolean exists(String key) { - return false; - } - - @Override - public long size(String key) throws IOException { - throw new IOException("archive backend unavailable"); - } - } -} diff --git a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridgeTest.java b/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridgeTest.java deleted file mode 100644 index 44045475..00000000 --- a/modules/protocols/protocol-jsatl12/src/test/java/com/lingniu/ingest/protocol/jsatl12/inbound/Jsatl12JtMessageBridgeTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.lingniu.ingest.protocol.jsatl12.inbound; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.protocol.jt808.codec.BodyParserRegistry; -import com.lingniu.ingest.protocol.jt808.codec.Jt808Escape; -import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameEncoder; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.codec.parser.HeartbeatBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.PassthroughBodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jsatl12JtMessageBridgeTest { - - @Test - void decodesJtMessageAndDispatchesAsJt808RawFrame() { - List dispatched = new ArrayList<>(); - InMemoryVehicleIdentityService identities = new InMemoryVehicleIdentityService(); - identities.bind(new VehicleIdentityBinding( - ProtocolId.JT808, - "LNVIN00000001212", - "123456789012", - "", - "")); - Jsatl12JtMessageBridge bridge = new Jsatl12JtMessageBridge( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new HeartbeatBodyParser()))), - identities, - dispatched::add); - - bridge.dispatch(buildFrame(Jt808MessageId.TERMINAL_HEARTBEAT, "123456789012", 3), "127.0.0.1:7612"); - - assertThat(dispatched).hasSize(1); - RawFrame frame = dispatched.get(0); - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JT808); - assertThat(frame.command()).isEqualTo(Jt808MessageId.TERMINAL_HEARTBEAT); - assertThat(frame.sourceMeta()) - .containsEntry("vin", "LNVIN00000001212") - .containsEntry("phone", "123456789012") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE"); - assertThat(frame.payload()).isInstanceOf(com.lingniu.ingest.protocol.jt808.model.Jt808Message.class); - assertThat(((com.lingniu.ingest.protocol.jt808.model.Jt808Message) frame.payload()).body()) - .isInstanceOf(Jt808Body.Heartbeat.class); - } - - @Test - void bridgeUsesExtendedJt808Parsers() { - List dispatched = new ArrayList<>(); - Jsatl12JtMessageBridge bridge = new Jsatl12JtMessageBridge( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new PassthroughBodyParser()))), - dispatched::add); - byte[] body = new byte[]{0x41, 0x01, 0x02}; - - bridge.dispatch(buildFrame(Jt808MessageId.TERMINAL_PASSTHROUGH, "123456789012", 4, body), - "127.0.0.1:7612"); - - assertThat(dispatched).hasSize(1); - assertThat(dispatched.get(0).command()).isEqualTo(Jt808MessageId.TERMINAL_PASSTHROUGH); - assertThat(((com.lingniu.ingest.protocol.jt808.model.Jt808Message) dispatched.get(0).payload()).body()) - .isInstanceOf(Jt808Body.Passthrough.class); - } - - @Test - void malformedJtMessageIsDispatchedAsJsatl12PassthroughCandidate() { - List dispatched = new ArrayList<>(); - Jsatl12JtMessageBridge bridge = new Jsatl12JtMessageBridge( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new HeartbeatBodyParser()))), - dispatched::add); - byte[] malformed = new byte[]{0x01, 0x02, 0x03}; - - bridge.dispatch(malformed, "127.0.0.1:7612"); - - assertThat(dispatched).hasSize(1); - RawFrame frame = dispatched.getFirst(); - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JSATL12); - assertThat(frame.command()).isEqualTo(Jsatl12MessageId.MALFORMED_JT_MESSAGE); - assertThat(frame.rawBytes()).containsExactly(malformed); - assertThat(frame.sourceMeta()) - .containsEntry("peer", "127.0.0.1:7612") - .containsEntry("parseError", "true") - .containsEntry("source", "jsatl12-jt-message") - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - assertThat(frame.payload()).isInstanceOf(Jsatl12MalformedJtMessage.class); - } - - @Test - void identityResolverFailureStillDispatchesDecodedJt808MessageWithUnknownIdentity() { - List dispatched = new ArrayList<>(); - Jsatl12JtMessageBridge bridge = new Jsatl12JtMessageBridge( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new HeartbeatBodyParser()))), - lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }, - dispatched::add); - byte[] raw = buildFrame(Jt808MessageId.TERMINAL_HEARTBEAT, "123456789012", 3); - - bridge.dispatch(raw, "127.0.0.1:7612"); - - assertThat(dispatched).singleElement().satisfies(frame -> { - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JT808); - assertThat(frame.command()).isEqualTo(Jt808MessageId.TERMINAL_HEARTBEAT); - assertThat(frame.rawBytes()).isNotNull(); - assertThat(frame.payload()).isInstanceOf(com.lingniu.ingest.protocol.jt808.model.Jt808Message.class); - assertThat(frame.sourceMeta()) - .containsEntry("source", "jsatl12-signaling") - .containsEntry("peer", "127.0.0.1:7612") - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable"); - }); - } - - @Test - void uploadCompletionReturnsT9212RetransmissionAck() { - List dispatched = new ArrayList<>(); - Jsatl12AttachmentTracker tracker = new Jsatl12AttachmentTracker(); - Jsatl12JtMessageBridge bridge = new Jsatl12JtMessageBridge( - new Jt808MessageDecoder(new BodyParserRegistry(List.of())), - tracker, - dispatched::add); - String phone = "123456789012"; - String fileName = "ADAS_001.jpg"; - - Optional t1210Ack = bridge.dispatch(buildFrame(0x1210, phone, 10, t1210Body(fileName, 6)), - "127.0.0.1:7612"); - tracker.recordDataPacket(phone, dataPacket(fileName, 0, new byte[]{0x01, 0x02, 0x03})); - Optional t9212 = bridge.dispatch(buildFrame(0x1212, phone, 11, t1212Body(fileName, 2, 6)), - "127.0.0.1:7612"); - - assertThat(t1210Ack).isEmpty(); - assertThat(t9212).isPresent(); - byte[] unescaped = Jt808Escape.unescape(t9212.get(), 1, t9212.get().length - 2); - var ack = new Jt808MessageDecoder(new BodyParserRegistry(List.of())) - .decode(ByteBuffer.wrap(unescaped)); - assertThat(ack.header().messageId()).isEqualTo(0x9212); - assertThat(ack.header().phone()).isEqualTo(phone); - assertThat(ack.header().serialNo()).isEqualTo(11); - Jt808Body.Raw raw = (Jt808Body.Raw) ack.body(); - byte[] body = raw.bytes(); - int nameLen = body[0] & 0xFF; - assertThat(new String(body, 1, nameLen, StandardCharsets.UTF_8)).isEqualTo(fileName); - assertThat(body[1 + nameLen] & 0xFF).isEqualTo(2); - assertThat(body[2 + nameLen] & 0xFF).isEqualTo(1); - assertThat(body[3 + nameLen] & 0xFF).isEqualTo(1); - assertThat(readDword(body, 4 + nameLen)).isEqualTo(3); - assertThat(readDword(body, 8 + nameLen)).isEqualTo(3); - } - - private static byte[] buildFrame(int msgId, String phone, int serial) { - return buildFrame(msgId, phone, serial, new byte[0]); - } - - private static byte[] buildFrame(int msgId, String phone, int serial, byte[] body) { - byte[] framed = Jt808FrameEncoder.encode(msgId, phone, serial, body); - return Jt808Escape.unescape(framed, 1, framed.length - 2); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static byte[] t1210Body(String fileName, long size) { - byte[] name = fileName.getBytes(StandardCharsets.US_ASCII); - byte[] out = new byte[57 + 1 + name.length + 4]; - putAscii(out, 0, 7, "DEV0001"); - putAscii(out, 7, 7, "DEV0001"); - putAscii(out, 23, 32, "ALARM-NO-001"); - out[55] = 0; - out[56] = 1; - out[57] = (byte) name.length; - System.arraycopy(name, 0, out, 58, name.length); - writeDword(out, 58 + name.length, size); - return out; - } - - private static byte[] t1212Body(String fileName, int type, long size) { - byte[] name = fileName.getBytes(StandardCharsets.US_ASCII); - byte[] out = new byte[1 + name.length + 1 + 4]; - out[0] = (byte) name.length; - System.arraycopy(name, 0, out, 1, name.length); - out[1 + name.length] = (byte) type; - writeDword(out, 2 + name.length, size); - return out; - } - - private static byte[] dataPacket(String name, int offset, byte[] data) { - byte[] out = new byte[62 + data.length]; - out[0] = 0x30; - out[1] = 0x31; - out[2] = 0x63; - out[3] = 0x64; - putAscii(out, 4, 50, name); - writeDword(out, 54, offset); - writeDword(out, 58, data.length); - System.arraycopy(data, 0, out, 62, data.length); - return out; - } - - private static void putAscii(byte[] out, int offset, int len, String value) { - byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - System.arraycopy(bytes, 0, out, offset, Math.min(bytes.length, len)); - } - - private static void writeDword(byte[] out, int offset, long value) { - out[offset] = (byte) ((value >>> 24) & 0xFF); - out[offset + 1] = (byte) ((value >>> 16) & 0xFF); - out[offset + 2] = (byte) ((value >>> 8) & 0xFF); - out[offset + 3] = (byte) (value & 0xFF); - } - - private static long readDword(byte[] raw, int offset) { - return ((raw[offset] & 0xFFL) << 24) - | ((raw[offset + 1] & 0xFFL) << 16) - | ((raw[offset + 2] & 0xFFL) << 8) - | (raw[offset + 3] & 0xFFL); - } -} diff --git a/modules/protocols/protocol-jt1078/pom.xml b/modules/protocols/protocol-jt1078/pom.xml deleted file mode 100644 index 4110d5ec..00000000 --- a/modules/protocols/protocol-jt1078/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - protocol-jt1078 - protocol-jt1078 - - JT/T 1078 音视频信令接入。复用 protocol-jt808 的 Netty server 与会话, - 只通过注册新的 BodyParser 热插拔扩展消息集(0x1003 / 0x1005 / 0x1205 / 0x1206)。 - - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - protocol-jt808 - - - com.lingniu.ingest - vehicle-identity - - - com.lingniu.ingest - vehicle-identity-test-support - test - - - com.lingniu.ingest - sink-archive - - - org.springframework.boot - spring-boot-starter - - - io.netty - netty-all - - - com.github.ben-manes.caffeine - caffeine - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.springframework.boot - spring-boot-test - test - - - diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileListBodyParser.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileListBodyParser.java deleted file mode 100644 index 884a045a..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileListBodyParser.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.codec.parser; - -import com.lingniu.ingest.protocol.jt1078.model.Jt1078MessageId; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; - -import java.nio.ByteBuffer; - -/** 0x1205 终端上传文件列表,摘要字段结构化,明细原始 body 保留。 */ -public final class FileListBodyParser implements BodyParser { - @Override public int messageId() { return Jt1078MessageId.TERMINAL_FILE_LIST; } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - byte[] raw = new byte[body.remaining()]; - body.get(raw); - ByteBuffer b = ByteBuffer.wrap(raw); - int responseSerialNo = b.remaining() >= 2 ? b.getShort() & 0xFFFF : 0; - long fileCount = b.remaining() >= 4 ? b.getInt() & 0xFFFFFFFFL : 0L; - return new Jt808Body.FileList(responseSerialNo, fileCount, raw); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileUploadCompleteBodyParser.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileUploadCompleteBodyParser.java deleted file mode 100644 index 9787cd65..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/FileUploadCompleteBodyParser.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.codec.parser; - -import com.lingniu.ingest.protocol.jt1078.model.Jt1078MessageId; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; - -import java.nio.ByteBuffer; - -/** 0x1206 文件上传完成通知。 */ -public final class FileUploadCompleteBodyParser implements BodyParser { - @Override public int messageId() { return Jt1078MessageId.TERMINAL_FILE_UPLOAD_DONE; } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - byte[] raw = new byte[body.remaining()]; - body.get(raw); - ByteBuffer b = ByteBuffer.wrap(raw); - int responseSerialNo = b.remaining() >= 2 ? b.getShort() & 0xFFFF : 0; - int result = b.hasRemaining() ? b.get() & 0xFF : 0; - return new Jt808Body.FileUploadComplete(responseSerialNo, result, raw); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/MediaAttributesBodyParser.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/MediaAttributesBodyParser.java deleted file mode 100644 index 580fb11c..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/MediaAttributesBodyParser.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.codec.parser; - -import com.lingniu.ingest.protocol.jt1078.model.Jt1078MessageId; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; - -import java.nio.ByteBuffer; - -/** 0x1003 终端上传音视频属性。 */ -public final class MediaAttributesBodyParser implements BodyParser { - @Override public int messageId() { return Jt1078MessageId.TERMINAL_MEDIA_ATTRS_REPORT; } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - byte[] raw = new byte[body.remaining()]; - body.get(raw); - ByteBuffer b = ByteBuffer.wrap(raw); - return new Jt808Body.MediaAttributes( - readU8(b), - readU8(b), - readU8(b), - readU8(b), - readU16(b), - readU8(b) == 1, - readU8(b), - readU8(b), - readU8(b), - raw); - } - - private static int readU8(ByteBuffer b) { - return b.hasRemaining() ? b.get() & 0xFF : 0; - } - - private static int readU16(ByteBuffer b) { - return b.remaining() >= 2 ? b.getShort() & 0xFFFF : 0; - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/PassengerVolumeBodyParser.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/PassengerVolumeBodyParser.java deleted file mode 100644 index 7feedd24..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/codec/parser/PassengerVolumeBodyParser.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.codec.parser; - -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.protocol.jt1078.model.Jt1078MessageId; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; - -import java.nio.ByteBuffer; - -/** 0x1005 终端上传乘客流量,字段结构化后仍按可查询透传事件入库。 */ -public final class PassengerVolumeBodyParser implements BodyParser { - @Override public int messageId() { return Jt1078MessageId.TERMINAL_PASSENGER_VOLUME; } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - byte[] raw = new byte[body.remaining()]; - body.get(raw); - ByteBuffer b = ByteBuffer.wrap(raw); - return new Jt808Body.PassengerVolume( - readU8(b), - readBcdTime(b), - readBcdTime(b), - readU16(b), - readU16(b), - raw); - } - - private static int readU8(ByteBuffer b) { - return b.hasRemaining() ? b.get() & 0xFF : 0; - } - - private static int readU16(ByteBuffer b) { - return b.remaining() >= 2 ? b.getShort() & 0xFFFF : 0; - } - - private static String readBcdTime(ByteBuffer b) { - if (b.remaining() < 6) { - byte[] tail = new byte[b.remaining()]; - b.get(tail); - return ""; - } - byte[] time = new byte[6]; - b.get(time); - String ts = BcdCodec.decode(time); - if (ts.length() != 12) { - return ""; - } - return "20" + ts.substring(0, 2) - + "-" + ts.substring(2, 4) - + "-" + ts.substring(4, 6) - + "T" + ts.substring(6, 8) - + ":" + ts.substring(8, 10) - + ":" + ts.substring(10, 12); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfiguration.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfiguration.java deleted file mode 100644 index 8cbaae3a..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfiguration.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.config; - -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.protocol.jt1078.codec.parser.FileUploadCompleteBodyParser; -import com.lingniu.ingest.protocol.jt1078.codec.parser.FileListBodyParser; -import com.lingniu.ingest.protocol.jt1078.codec.parser.MediaAttributesBodyParser; -import com.lingniu.ingest.protocol.jt1078.codec.parser.PassengerVolumeBodyParser; -import com.lingniu.ingest.protocol.jt1078.handler.Jt1078MalformedRtpHandler; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpEventPublisher; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaArchiveService; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaStreamServer; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078UdpMediaStreamServer; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.config.SinkArchiveAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * jt1078 在 jt808 之前装配它自己的 Parser Bean,Spring 会把它们注入到 jt808 的 - * {@code BodyParserRegistry} 里,实现"同一个 Netty 端口复用、消息集热插拔"。 - */ -@AutoConfiguration(before = Jt808AutoConfiguration.class, after = SinkArchiveAutoConfiguration.class) -@EnableConfigurationProperties(Jt1078Properties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.jt1078", name = "enabled", havingValue = "true") -public class Jt1078AutoConfiguration { - - @Bean - public BodyParser jt1078MediaAttributesBodyParser() { - return new MediaAttributesBodyParser(); - } - - @Bean - public BodyParser jt1078PassengerVolumeBodyParser() { - return new PassengerVolumeBodyParser(); - } - - @Bean - public BodyParser jt1078FileListBodyParser() { - return new FileListBodyParser(); - } - - @Bean - public BodyParser jt1078FileUploadCompleteBodyParser() { - return new FileUploadCompleteBodyParser(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(ArchiveStore.class) - public Jt1078MediaArchiveService jt1078MediaArchiveService(ArchiveStore archive, - VehicleIdentityResolver identityResolver, - DisruptorEventBus eventBus, - Jt1078Properties props) { - return new Jt1078MediaArchiveService( - archive, identityResolver, eventBus::publish, props.getMediaStream().getSegmentSeconds()); - } - - @Bean - @ConditionalOnMissingBean - public Jt1078MalformedRtpEventPublisher jt1078MalformedRtpEventPublisher(VehicleIdentityResolver identityResolver, - Dispatcher dispatcher) { - return new Jt1078MalformedRtpEventPublisher(identityResolver, dispatcher::dispatch); - } - - @Bean - @ConditionalOnMissingBean - public Jt1078MalformedRtpHandler jt1078MalformedRtpHandler(VehicleIdentityResolver identityResolver) { - return new Jt1078MalformedRtpHandler(identityResolver); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(Jt1078MediaArchiveService.class) - @ConditionalOnExpression("${lingniu.ingest.jt1078.media-stream.enabled:true} " - + "&& ${lingniu.ingest.jt1078.media-stream.tcp-enabled:true}") - public Jt1078MediaStreamServer jt1078MediaStreamServer(Jt1078Properties props, - Jt1078MediaArchiveService archiveService, - Jt1078MalformedRtpEventPublisher malformedPublisher) { - Jt1078Properties.MediaStream media = props.getMediaStream(); - return new Jt1078MediaStreamServer( - media.getPort(), media.getWorkerThreads(), media.getMaxPacketLength(), archiveService, malformedPublisher); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(Jt1078MediaArchiveService.class) - @ConditionalOnExpression("${lingniu.ingest.jt1078.media-stream.enabled:true} " - + "&& ${lingniu.ingest.jt1078.media-stream.udp-enabled:false}") - public Jt1078UdpMediaStreamServer jt1078UdpMediaStreamServer(Jt1078Properties props, - Jt1078MediaArchiveService archiveService, - Jt1078MalformedRtpEventPublisher malformedPublisher) { - Jt1078Properties.MediaStream media = props.getMediaStream(); - return new Jt1078UdpMediaStreamServer( - media.getUdpPort(), media.getWorkerThreads(), media.getMaxPacketLength(), archiveService, malformedPublisher); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078Properties.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078Properties.java deleted file mode 100644 index 770c5a55..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078Properties.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "lingniu.ingest.jt1078") -public class Jt1078Properties { - - /** 是否启用 JT/T 1078 音视频能力;它不参与 32960 快照/历史字段查询。 */ - private boolean enabled = false; - private MediaStream mediaStream = new MediaStream(); - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public MediaStream getMediaStream() { return mediaStream; } - public void setMediaStream(MediaStream mediaStream) { this.mediaStream = mediaStream; } - - public static class MediaStream { - /** 媒体流子模块开关,便于只启用信令、不落媒体文件。 */ - private boolean enabled = true; - /** TCP 媒体流监听,适合生产默认路径。 */ - private boolean tcpEnabled = true; - /** UDP 媒体流监听默认关闭,开启前需要确认网络和包乱序处理能力。 */ - private boolean udpEnabled = false; - private int port = 11078; - private int udpPort = 11078; - private int workerThreads = 0; - /** 单包最大长度保护,防止异常媒体流耗尽内存。 */ - private int maxPacketLength = 1024 * 1024; - /** 媒体文件分段秒数,影响归档文件粒度。 */ - private int segmentSeconds = 300; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public boolean isTcpEnabled() { return tcpEnabled; } - public void setTcpEnabled(boolean tcpEnabled) { this.tcpEnabled = tcpEnabled; } - public boolean isUdpEnabled() { return udpEnabled; } - public void setUdpEnabled(boolean udpEnabled) { this.udpEnabled = udpEnabled; } - public int getPort() { return port; } - public void setPort(int port) { this.port = port; } - public int getUdpPort() { return udpPort; } - public void setUdpPort(int udpPort) { this.udpPort = udpPort; } - public int getWorkerThreads() { return workerThreads; } - public void setWorkerThreads(int workerThreads) { this.workerThreads = workerThreads; } - public int getMaxPacketLength() { return maxPacketLength; } - public void setMaxPacketLength(int maxPacketLength) { this.maxPacketLength = maxPacketLength; } - public int getSegmentSeconds() { return segmentSeconds; } - public void setSegmentSeconds(int segmentSeconds) { this.segmentSeconds = segmentSeconds; } - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078SignalAutoConfiguration.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078SignalAutoConfiguration.java deleted file mode 100644 index 60585cfc..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078SignalAutoConfiguration.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.config; - -import com.lingniu.ingest.protocol.jt1078.handler.Jt1078MediaSignalHandler; -import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration; -import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; - -/** - * 1078 信令复用 JT808 解码和 mapper,必须在 JT808 AutoConfiguration 之后判断。 - */ -@AutoConfiguration(after = Jt808AutoConfiguration.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.jt1078", name = "enabled", havingValue = "true") -@ConditionalOnBean(Jt808EventMapper.class) -public class Jt1078SignalAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public Jt1078MediaSignalHandler jt1078MediaSignalHandler(Jt808EventMapper mapper) { - return new Jt1078MediaSignalHandler(mapper); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078Commands.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078Commands.java deleted file mode 100644 index 34027988..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078Commands.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.downlink; - -import com.lingniu.ingest.protocol.jt1078.model.Jt1078MessageId; -import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.Charset; - -/** JT/T 1078 下行信令 body 构造器,外层帧仍由 JT808 通道发送。 */ -public final class Jt1078Commands { - - private static final Charset GBK = Charset.forName("GBK"); - - private Jt1078Commands() {} - - /** 0x1003 查询终端音视频属性。 */ - public static Jt808Commands.DownlinkCommand queryMediaAttributes() { - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_QUERY_MEDIA_ATTRS, new byte[0]); - } - - /** 0x9101 实时音视频传输请求。 */ - public static Jt808Commands.DownlinkCommand realtimePlay(String ip, - int tcpPort, - int udpPort, - int channelNo, - int mediaType, - int streamType) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeStringWithU8Length(os, ip); - writeU16(os, tcpPort); - writeU16(os, udpPort); - writeU8(os, channelNo); - writeU8(os, mediaType); - writeU8(os, streamType); - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_REALTIME_PLAY, os.toByteArray()); - } - - /** 0x9102 音视频实时传输控制。 */ - public static Jt808Commands.DownlinkCommand realtimeControl(int channelNo, - int command, - int closeType, - int streamType) { - ByteArrayOutputStream os = new ByteArrayOutputStream(4); - writeU8(os, channelNo); - writeU8(os, command); - writeU8(os, closeType); - writeU8(os, streamType); - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_REALTIME_CONTROL, os.toByteArray()); - } - - /** 0x9201 平台下发远程录像回放请求。 */ - public static Jt808Commands.DownlinkCommand historyPlay(String ip, - int tcpPort, - int udpPort, - int channelNo, - int mediaType, - int streamType, - int storageType, - int playbackMode, - int playbackSpeed, - String startTime, - String endTime) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeStringWithU8Length(os, ip); - writeU16(os, tcpPort); - writeU16(os, udpPort); - writeU8(os, channelNo); - writeU8(os, mediaType); - writeU8(os, streamType); - writeU8(os, storageType); - writeU8(os, playbackMode); - writeU8(os, playbackSpeed); - writeBcdTime(os, startTime); - writeBcdTime(os, endTime); - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_HISTORY_PLAY, os.toByteArray()); - } - - /** 0x9202 平台下发远程录像回放控制。 */ - public static Jt808Commands.DownlinkCommand historyControl(int channelNo, - int playbackMode, - int playbackSpeed, - String playbackTime) { - ByteArrayOutputStream os = new ByteArrayOutputStream(9); - writeU8(os, channelNo); - writeU8(os, playbackMode); - writeU8(os, playbackSpeed); - writeBcdTime(os, playbackTime); - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_HISTORY_CONTROL, os.toByteArray()); - } - - /** 0x9205 查询资源列表。 */ - public static Jt808Commands.DownlinkCommand resourceSearch(int channelNo, - String startTime, - String endTime, - long warnBit1, - long warnBit2, - int mediaType, - int streamType, - int storageType) { - ByteArrayOutputStream os = new ByteArrayOutputStream(24); - writeU8(os, channelNo); - writeBcdTime(os, startTime); - writeBcdTime(os, endTime); - writeU32(os, warnBit1); - writeU32(os, warnBit2); - writeU8(os, mediaType); - writeU8(os, streamType); - writeU8(os, storageType); - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_RESOURCE_SEARCH, os.toByteArray()); - } - - /** 0x9206 文件上传指令。 */ - public static Jt808Commands.DownlinkCommand fileUpload(String ip, - int port, - String username, - String password, - String path, - int channelNo, - String startTime, - String endTime, - long warnBit1, - long warnBit2, - int mediaType, - int streamType, - int storageType, - int condition) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeStringWithU8Length(os, ip); - writeU16(os, port); - writeStringWithU8Length(os, username); - writeStringWithU8Length(os, password); - writeStringWithU8Length(os, path); - writeU8(os, channelNo); - writeBcdTime(os, startTime); - writeBcdTime(os, endTime); - writeU32(os, warnBit1); - writeU32(os, warnBit2); - writeU8(os, mediaType); - writeU8(os, streamType); - writeU8(os, storageType); - writeU8(os, condition); - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_FILE_UPLOAD, os.toByteArray()); - } - - /** 0x9207 文件上传控制。 */ - public static Jt808Commands.DownlinkCommand fileUploadControl(int responseSerialNo, int command) { - ByteArrayOutputStream os = new ByteArrayOutputStream(3); - writeU16(os, responseSerialNo); - writeU8(os, command); - return new Jt808Commands.DownlinkCommand(Jt1078MessageId.PLATFORM_FILE_UPLOAD_CONTROL, os.toByteArray()); - } - - private static void writeStringWithU8Length(ByteArrayOutputStream os, String value) { - byte[] bytes = value == null ? new byte[0] : value.getBytes(GBK); - if (bytes.length > 255) { - throw new IllegalArgumentException("jt1078 string field too long: " + bytes.length); - } - writeU8(os, bytes.length); - os.write(bytes, 0, bytes.length); - } - - private static void writeBcdTime(ByteArrayOutputStream os, String value) { - String digits = value == null ? "" : value.replaceAll("\\D", ""); - if (digits.length() == 14 && digits.startsWith("20")) { - digits = digits.substring(2); - } - if (digits.isBlank()) { - digits = "000000000000"; - } - if (digits.length() != 12) { - throw new IllegalArgumentException("jt1078 time must be YYMMDDHHMMSS or yyyy-MM-dd HH:mm:ss: " + value); - } - for (int i = 0; i < digits.length(); i += 2) { - int high = Character.digit(digits.charAt(i), 10); - int low = Character.digit(digits.charAt(i + 1), 10); - if (high < 0 || low < 0) { - throw new IllegalArgumentException("jt1078 time contains non-decimal digit: " + value); - } - os.write((high << 4) | low); - } - } - - private static void writeU8(ByteArrayOutputStream os, int v) { - os.write(v & 0xFF); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MalformedRtpHandler.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MalformedRtpHandler.java deleted file mode 100644 index 5939384f..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MalformedRtpHandler.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.EventEmit; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaMessageId; - -import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - -@ProtocolHandler(protocol = ProtocolId.JT1078) -public final class Jt1078MalformedRtpHandler { - - private final VehicleIdentityResolver identityResolver; - - public Jt1078MalformedRtpHandler(VehicleIdentityResolver identityResolver) { - this.identityResolver = Objects.requireNonNull(identityResolver, "identityResolver must not be null"); - } - - @MessageMapping(command = Jt1078MediaMessageId.MALFORMED_RTP, desc = "JT1078 RTP 入口坏包兜底") - @EventEmit(VehicleEvent.Passthrough.class) - public List onMalformedRtp(Jt1078MalformedRtpPacket malformed) { - if (malformed == null) { - return List.of(); - } - Instant eventTime = malformed.receivedAt() == null ? Instant.now() : malformed.receivedAt(); - IdentityResolution identity = resolveIdentity(malformed); - Map metadata = new HashMap<>(); - metadata.put("transport", safe(malformed.transport())); - metadata.put("peer", safe(malformed.peer())); - metadata.put("sim", safe(malformed.sim())); - metadata.put("vin", identity.identity().vin()); - metadata.put("identityResolved", Boolean.toString(identity.identity().resolved())); - metadata.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - metadata.put("identityError", "true"); - metadata.put("identityErrorMessage", identity.errorMessage()); - } - metadata.put("malformedRtp", "true"); - metadata.put("parseError", "true"); - metadata.put("parseErrorMessage", safe(malformed.reason())); - metadata.put("packetLength", Integer.toString(malformed.packetLength())); - metadata.put("reason", safe(malformed.reason())); - return List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - identity.identity().vin(), - ProtocolId.JT1078, - eventTime, - Instant.now(), - null, - Map.copyOf(metadata), - Jt1078MediaMessageId.MALFORMED_RTP, - malformed.rawBytes() == null ? new byte[0] : malformed.rawBytes())); - } - - private IdentityResolution resolveIdentity(Jt1078MalformedRtpPacket malformed) { - try { - return new IdentityResolution(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.JT1078, "", safe(malformed.sim()), "", "")), null); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private static String safe(String value) { - return value == null ? "" : value; - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) { - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MediaSignalHandler.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MediaSignalHandler.java deleted file mode 100644 index 9d7b56bd..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/handler/Jt1078MediaSignalHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.EventEmit; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.protocol.jt1078.model.Jt1078MessageId; -import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; - -import java.util.List; - -/** - * JT/T 1078 信令复用 JT808 包头、连接与 dispatcher,但路由独立放在 1078 模块内。 - */ -@ProtocolHandler(protocol = ProtocolId.JT808) -public final class Jt1078MediaSignalHandler { - - private final Jt808EventMapper mapper; - - public Jt1078MediaSignalHandler(Jt808EventMapper mapper) { - this.mapper = mapper; - } - - @MessageMapping(command = { - Jt1078MessageId.TERMINAL_MEDIA_ATTRS_REPORT, - Jt1078MessageId.TERMINAL_PASSENGER_VOLUME, - Jt1078MessageId.TERMINAL_FILE_LIST, - Jt1078MessageId.TERMINAL_FILE_UPLOAD_DONE - }, desc = "JT/T 1078 音视频信令") - @EventEmit({VehicleEvent.MediaMeta.class, VehicleEvent.Passthrough.class}) - public List onMediaSignal(Jt808Message msg) { - return mapper.toEvents(msg); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpEventPublisher.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpEventPublisher.java deleted file mode 100644 index 5ebff586..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpEventPublisher.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; - -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; - -public final class Jt1078MalformedRtpEventPublisher { - - private final VehicleIdentityResolver identityResolver; - private final Consumer dispatcher; - - public Jt1078MalformedRtpEventPublisher(VehicleIdentityResolver identityResolver, - Consumer dispatcher) { - this.identityResolver = Objects.requireNonNull(identityResolver, "identityResolver must not be null"); - this.dispatcher = Objects.requireNonNull(dispatcher, "dispatcher must not be null"); - } - - public void publish(Jt1078MalformedRtpPacket malformed) { - if (malformed == null) { - return; - } - Instant now = malformed.receivedAt() == null ? Instant.now() : malformed.receivedAt(); - IdentityResolution identity = resolveIdentity(malformed); - Map metadata = new HashMap<>(); - metadata.put("transport", safe(malformed.transport())); - metadata.put("peer", safe(malformed.peer())); - metadata.put("sim", safe(malformed.sim())); - metadata.put("identityResolved", Boolean.toString(identity.identity().resolved())); - metadata.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - metadata.put("identityError", "true"); - metadata.put("identityErrorMessage", identity.errorMessage()); - } - metadata.put("malformedRtp", "true"); - metadata.put("parseError", "true"); - metadata.put("parseErrorMessage", safe(malformed.reason())); - metadata.put("packetLength", Integer.toString(malformed.packetLength())); - metadata.put("reason", safe(malformed.reason())); - metadata.put("vin", identity.identity().vin()); - dispatcher.accept(new RawFrame( - ProtocolId.JT1078, - Jt1078MediaMessageId.MALFORMED_RTP, - 0, - malformed, - malformed.rawBytes() == null ? new byte[0] : malformed.rawBytes(), - Map.copyOf(metadata), - now - )); - } - - private IdentityResolution resolveIdentity(Jt1078MalformedRtpPacket malformed) { - try { - return new IdentityResolution(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.JT1078, "", malformed.sim(), "", "")), null); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private static String safe(String value) { - return value == null ? "" : value; - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) {} -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpPacket.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpPacket.java deleted file mode 100644 index c31e0a72..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MalformedRtpPacket.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import java.time.Instant; - -public record Jt1078MalformedRtpPacket( - String transport, - String peer, - String sim, - byte[] rawBytes, - int packetLength, - String reason, - Instant receivedAt -) { - public Jt1078MalformedRtpPacket(String transport, - String peer, - byte[] rawBytes, - int packetLength, - String reason, - Instant receivedAt) { - this(transport, peer, "", rawBytes, packetLength, reason, receivedAt); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaArchiveService.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaArchiveService.java deleted file mode 100644 index 352b72c0..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaArchiveService.java +++ /dev/null @@ -1,197 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; - -public final class Jt1078MediaArchiveService { - - private static final Logger log = LoggerFactory.getLogger(Jt1078MediaArchiveService.class); - private static final DateTimeFormatter DAY_FMT = DateTimeFormatter.ofPattern("yyyy/MM/dd").withZone(ZoneId.systemDefault()); - - private final ArchiveStore archive; - private final VehicleIdentityResolver identityResolver; - private final Consumer eventPublisher; - private final int segmentSeconds; - private final Cache publishedSegmentRefs = Caffeine.newBuilder() - .expireAfterAccess(Duration.ofHours(2)) - .maximumSize(200_000) - .build(); - - public Jt1078MediaArchiveService(ArchiveStore archive, - VehicleIdentityResolver identityResolver, - Consumer eventPublisher, - int segmentSeconds) { - this.archive = archive; - this.identityResolver = identityResolver; - this.eventPublisher = eventPublisher; - this.segmentSeconds = Math.max(60, segmentSeconds); - } - - public void archive(Jt1078RtpPacket packet, String peer) { - if (packet == null || packet.payload() == null || packet.payload().length == 0) { - return; - } - IdentityResolution identity = resolveIdentity(packet); - String vin = identity.identity().vin(); - long segment = segment(packet.timestamp()); - String key = key(vin, packet, segment); - try { - // JT1078 是 RTP 分片流,同一分段追加写入 .media;只发布一次 MediaMeta 作为索引事件。 - String uri = archive.append(key, packet.payload()); - long segmentSizeBytes = archiveSize(key, packet.payload().length); - publishOnce(packet, peer, identity, vin, segment, key, uri, segmentSizeBytes); - } catch (Exception e) { - log.warn("[jt1078] media archive failed sim={} channel={} sequence={} peer={}", - packet.sim(), packet.channelId(), packet.sequence(), peer, e); - publishArchiveFailure(packet, peer, identity, vin, segment, key, e); - } - } - - private void publishArchiveFailure(Jt1078RtpPacket packet, - String peer, - IdentityResolution identity, - String vin, - long segment, - String archiveKey, - Exception error) { - Instant now = Instant.now(); - Map metadata = baseMetadata(packet, peer, identity); - metadata.put("sequence", Integer.toString(packet.sequence())); - metadata.put("segment", Long.toString(segment)); - metadata.put("archiveKey", archiveKey == null ? "" : archiveKey); - metadata.put("archiveError", "true"); - metadata.put("archiveErrorMessage", error.getMessage() == null ? "" : error.getMessage()); - eventPublisher.accept(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, - ProtocolId.JT1078, - now, - now, - null, - Map.copyOf(metadata), - packet.dataType(), - packet.payload())); - } - - private void publishOnce(Jt1078RtpPacket packet, - String peer, - IdentityResolution identity, - String vin, - long segment, - String archiveKey, - String uri, - long segmentSizeBytes) { - String segmentKey = archiveKey == null || archiveKey.isBlank() - ? packet.sim() + ':' + packet.channelId() + ':' + packet.dataType() + ':' + segment - : archiveKey; - String old = publishedSegmentRefs.getIfPresent(segmentKey); - if (old != null) { - // 同一个分段只发一次索引事件,避免每个 RTP 包都在历史库产生一条 media meta。 - return; - } - publishedSegmentRefs.put(segmentKey, uri); - Instant now = Instant.now(); - Map metadata = baseMetadata(packet, peer, identity); - metadata.put("segment", Long.toString(segment)); - metadata.put("sequence", Integer.toString(packet.sequence())); - metadata.put("segmentSizeBytes", Long.toString(segmentSizeBytes)); - metadata.put("archiveKey", archiveKey == null ? "" : archiveKey); - eventPublisher.accept(new VehicleEvent.MediaMeta( - UUID.randomUUID().toString(), - vin, - ProtocolId.JT1078, - now, - now, - null, - Map.copyOf(metadata), - packet.sim() + "-" + packet.channelId() + "-" + segment, - mediaType(packet), - segmentSizeBytes, - uri)); - } - - private long archiveSize(String key, int fallbackBytes) { - try { - return archive.size(key); - } catch (Exception e) { - return Math.max(0, fallbackBytes); - } - } - - private IdentityResolution resolveIdentity(Jt1078RtpPacket packet) { - try { - return new IdentityResolution(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.JT1078, "", packet.sim(), "", "")), null); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private static Map baseMetadata(Jt1078RtpPacket packet, - String peer, - IdentityResolution identity) { - Map metadata = new LinkedHashMap<>(); - metadata.put("sim", packet.sim()); - metadata.put("channelId", Integer.toString(packet.channelId())); - metadata.put("dataType", Integer.toString(packet.dataType())); - metadata.put("packetType", Integer.toString(packet.packetType())); - metadata.put("peer", peer == null ? "" : peer); - metadata.put("vin", identity.identity().vin()); - metadata.put("identityResolved", Boolean.toString(identity.identity().resolved())); - metadata.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - metadata.put("identityError", "true"); - metadata.put("identityErrorMessage", identity.errorMessage()); - } - return metadata; - } - - private long segment(long timestamp) { - long seconds = timestamp > 10_000_000_000L ? timestamp / 1000 : timestamp; - // 按配置秒数对齐分段,保证同一路通道同一时间窗落到同一个 archive key。 - return seconds - Math.floorMod(seconds, segmentSeconds); - } - - private static String key(String vin, Jt1078RtpPacket packet, long segment) { - String day = DAY_FMT.format(Instant.ofEpochSecond(Math.max(0, segment))); - String safeVin = safe(vin); - return "jt1078/" + day + "/" + safeVin + "/ch-" + packet.channelId() - + "/dt-" + packet.dataType() + "/" + segment + ".media"; - } - - private static String mediaType(Jt1078RtpPacket packet) { - return "rtp,dataType=" + packet.dataType() - + ",packetType=" + packet.packetType() - + ",channel=" + packet.channelId(); - } - - private static String safe(String raw) { - if (raw == null || raw.isBlank()) { - return "unknown"; - } - return raw.replaceAll("[^A-Za-z0-9_.-]", "_"); - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) { - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaMessageId.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaMessageId.java deleted file mode 100644 index 5ee98aeb..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaMessageId.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -public final class Jt1078MediaMessageId { - - /** 内部消息:JT1078 RTP 入口坏包,走 Dispatcher 统一 RawArchive + Passthrough 链路。 */ - public static final int MALFORMED_RTP = 0x72747065; // "rtpe" - - private Jt1078MediaMessageId() {} -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamHandler.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamHandler.java deleted file mode 100644 index d4bd4e66..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamHandler.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; - -import java.net.InetSocketAddress; - -public final class Jt1078MediaStreamHandler extends SimpleChannelInboundHandler { - - private final Jt1078MediaArchiveService archiveService; - private final Jt1078MalformedRtpEventPublisher malformedPublisher; - - public Jt1078MediaStreamHandler(Jt1078MediaArchiveService archiveService, - Jt1078MalformedRtpEventPublisher malformedPublisher) { - this.archiveService = archiveService; - this.malformedPublisher = malformedPublisher; - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof Jt1078RtpPacket packet) { - archiveService.archive(packet, addr(ctx)); - } else if (msg instanceof Jt1078MalformedRtpPacket malformed) { - malformedPublisher.publish(malformed); - } else { - ctx.fireChannelRead(msg); - } - } - - private static String addr(ChannelHandlerContext ctx) { - if (ctx.channel().remoteAddress() instanceof InetSocketAddress i) { - return i.getAddress().getHostAddress() + ":" + i.getPort(); - } - return "unknown"; - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamServer.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamServer.java deleted file mode 100644 index 476f0863..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078MediaStreamServer.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; - -import java.util.concurrent.ThreadFactory; - -public final class Jt1078MediaStreamServer implements InitializingBean, DisposableBean { - - private static final Logger log = LoggerFactory.getLogger(Jt1078MediaStreamServer.class); - - private final int port; - private final int workerThreads; - private final int maxPacketLength; - private final Jt1078MediaArchiveService archiveService; - private final Jt1078MalformedRtpEventPublisher malformedPublisher; - - private EventLoopGroup boss; - private EventLoopGroup workers; - private Channel serverChannel; - - public Jt1078MediaStreamServer(int port, - int workerThreads, - int maxPacketLength, - Jt1078MediaArchiveService archiveService, - Jt1078MalformedRtpEventPublisher malformedPublisher) { - this.port = port; - this.workerThreads = workerThreads; - this.maxPacketLength = maxPacketLength; - this.archiveService = archiveService; - this.malformedPublisher = malformedPublisher; - } - - @Override - public void afterPropertiesSet() throws Exception { - ThreadFactory bossTf = r -> new Thread(r, "jt1078-media-boss"); - ThreadFactory wkTf = r -> new Thread(r, "jt1078-media-worker"); - this.boss = new NioEventLoopGroup(1, bossTf); - this.workers = new NioEventLoopGroup( - workerThreads == 0 ? Runtime.getRuntime().availableProcessors() : workerThreads, - wkTf); - - ServerBootstrap b = new ServerBootstrap(); - b.group(boss, workers) - .channel(NioServerSocketChannel.class) - .option(ChannelOption.SO_BACKLOG, 1024) - .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.SO_KEEPALIVE, true) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) { - ch.pipeline() - .addLast("rtp", new Jt1078RtpPacketDecoder(maxPacketLength)) - .addLast("archive", new Jt1078MediaStreamHandler(archiveService, malformedPublisher)); - } - }); - this.serverChannel = b.bind(port).sync().channel(); - log.info("[jt1078] media stream server listening on :{}", port); - } - - @Override - public void destroy() { - try { - if (serverChannel != null) serverChannel.close().sync(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - if (boss != null) boss.shutdownGracefully(); - if (workers != null) workers.shutdownGracefully(); - log.info("[jt1078] media stream server stopped"); - } - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedMediaStreamHandler.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedMediaStreamHandler.java deleted file mode 100644 index 3ebf6ab9..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedMediaStreamHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.ChannelHandlerContext; - -public final class Jt1078ReceivedMediaStreamHandler extends SimpleChannelInboundHandler { - - private final Jt1078MediaArchiveService archiveService; - private final Jt1078MalformedRtpEventPublisher malformedPublisher; - - public Jt1078ReceivedMediaStreamHandler(Jt1078MediaArchiveService archiveService, - Jt1078MalformedRtpEventPublisher malformedPublisher) { - this.archiveService = archiveService; - this.malformedPublisher = malformedPublisher; - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof Jt1078ReceivedRtpPacket received) { - archiveService.archive(received.packet(), received.peer()); - } else if (msg instanceof Jt1078MalformedRtpPacket malformed) { - malformedPublisher.publish(malformed); - } else { - ctx.fireChannelRead(msg); - } - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedRtpPacket.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedRtpPacket.java deleted file mode 100644 index 7405bc5e..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078ReceivedRtpPacket.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -public record Jt1078ReceivedRtpPacket( - Jt1078RtpPacket packet, - String peer -) { - public Jt1078ReceivedRtpPacket { - peer = peer == null ? "" : peer; - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacket.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacket.java deleted file mode 100644 index 92d53e46..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacket.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -public record Jt1078RtpPacket( - int sequence, - String sim, - int channelId, - int dataType, - int packetType, - long timestamp, - byte[] payload -) {} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketDecoder.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketDecoder.java deleted file mode 100644 index 953743ae..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketDecoder.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; - -import java.time.Instant; -import java.util.List; - -/** - * JT/T 1078 TCP RTP 包解码器。 - * - *

    包头固定魔数 0x30316364,header 固定 28 字节,随后是 bodyLength 指定的媒体负载。 - */ -public final class Jt1078RtpPacketDecoder extends ByteToMessageDecoder { - - private final int maxPacketLength; - - public Jt1078RtpPacketDecoder(int maxPacketLength) { - this.maxPacketLength = maxPacketLength; - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { - while (true) { - if (in.readableBytes() < Jt1078RtpPacketParser.HEADER_LEN) { - return; - } - int frameStart = findMagic(in); - if (frameStart < 0) { - in.skipBytes(Math.max(0, in.readableBytes() - 3)); - return; - } - if (frameStart > in.readerIndex()) { - in.skipBytes(frameStart - in.readerIndex()); - } - if (in.readableBytes() < Jt1078RtpPacketParser.HEADER_LEN) { - return; - } - int length = in.getUnsignedShort(in.readerIndex() + 26); - int packetLength = Jt1078RtpPacketParser.HEADER_LEN + length; - if (packetLength > maxPacketLength) { - String sim = Jt1078RtpPacketParser.peekSim(in); - int available = in.readableBytes(); - int rawLength = Math.min(available, packetLength); - byte[] raw = new byte[rawLength]; - in.readBytes(raw); - out.add(new Jt1078MalformedRtpPacket( - "tcp", - peer(ctx), - sim, - raw, - packetLength, - "jt1078 rtp packet too large: " + packetLength, - Instant.now())); - return; - } - if (in.readableBytes() < packetLength) { - return; - } - ByteBuf packet = in.readSlice(packetLength); - out.add(Jt1078RtpPacketParser.parse(packet)); - } - } - - private static String peer(ChannelHandlerContext ctx) { - if (ctx == null || ctx.channel() == null || ctx.channel().remoteAddress() == null) { - return ""; - } - return ctx.channel().remoteAddress().toString(); - } - - private static int findMagic(ByteBuf in) { - int start = in.readerIndex(); - int end = in.writerIndex() - 3; - for (int i = start; i < end; i++) { - if (in.getInt(i) == Jt1078RtpPacketParser.MAGIC) { - return i; - } - } - return -1; - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketParser.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketParser.java deleted file mode 100644 index 639f1691..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078RtpPacketParser.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import io.netty.buffer.ByteBuf; -import io.netty.handler.codec.CorruptedFrameException; - -final class Jt1078RtpPacketParser { - - static final int MAGIC = 0x30316364; - static final int HEADER_LEN = 28; - - private Jt1078RtpPacketParser() {} - - static Jt1078RtpPacket parse(ByteBuf in) { - int magic = in.readInt(); - if (magic != MAGIC) { - throw new CorruptedFrameException("jt1078 invalid magic: 0x" + Integer.toHexString(magic)); - } - int sequence = in.readUnsignedShort(); - String sim = readBcd(in, 6); - int channelId = in.readUnsignedByte(); - int dataAndPacketType = in.readUnsignedByte(); - // 高 4 位是数据类型,低 4 位是分包类型;归档 key 需要二者共同区分。 - int dataType = (dataAndPacketType >>> 4) & 0x0F; - int packetType = dataAndPacketType & 0x0F; - long timestamp = in.readLong(); - in.skipBytes(4); - int bodyLength = in.readUnsignedShort(); - if (bodyLength > in.readableBytes()) { - throw new CorruptedFrameException("jt1078 body length exceeds readable bytes: " - + bodyLength + " > " + in.readableBytes()); - } - byte[] payload = new byte[bodyLength]; - in.readBytes(payload); - return new Jt1078RtpPacket(sequence, sim, channelId, dataType, packetType, timestamp, payload); - } - - static String peekSim(ByteBuf in) { - if (in == null || in.readableBytes() < HEADER_LEN || in.getInt(in.readerIndex()) != MAGIC) { - return ""; - } - // UDP/TCP 解码器在完整 parse 前先取 SIM,用于日志和初步身份定位;不移动 readerIndex。 - StringBuilder sb = new StringBuilder(12); - int simOffset = in.readerIndex() + 6; - for (int i = 0; i < 6; i++) { - int b = in.getUnsignedByte(simOffset + i); - sb.append((b >>> 4) & 0x0F); - sb.append(b & 0x0F); - } - return sb.toString(); - } - - private static String readBcd(ByteBuf in, int len) { - StringBuilder sb = new StringBuilder(len * 2); - for (int i = 0; i < len; i++) { - int b = in.readUnsignedByte(); - sb.append((b >>> 4) & 0x0F); - sb.append(b & 0x0F); - } - return sb.toString(); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpMediaStreamServer.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpMediaStreamServer.java deleted file mode 100644 index ffdd886e..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpMediaStreamServer.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import io.netty.bootstrap.Bootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.DatagramChannel; -import io.netty.channel.socket.nio.NioDatagramChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; - -import java.util.concurrent.ThreadFactory; - -public final class Jt1078UdpMediaStreamServer implements InitializingBean, DisposableBean { - - private static final Logger log = LoggerFactory.getLogger(Jt1078UdpMediaStreamServer.class); - - private final int port; - private final int workerThreads; - private final int maxPacketLength; - private final Jt1078MediaArchiveService archiveService; - private final Jt1078MalformedRtpEventPublisher malformedPublisher; - - private EventLoopGroup workers; - private Channel serverChannel; - - public Jt1078UdpMediaStreamServer(int port, - int workerThreads, - int maxPacketLength, - Jt1078MediaArchiveService archiveService, - Jt1078MalformedRtpEventPublisher malformedPublisher) { - this.port = port; - this.workerThreads = workerThreads; - this.maxPacketLength = maxPacketLength; - this.archiveService = archiveService; - this.malformedPublisher = malformedPublisher; - } - - @Override - public void afterPropertiesSet() throws Exception { - ThreadFactory wkTf = r -> new Thread(r, "jt1078-media-udp-worker"); - int threads = workerThreads == 0 ? Runtime.getRuntime().availableProcessors() : workerThreads; - this.workers = new NioEventLoopGroup(threads, wkTf); - - Bootstrap b = new Bootstrap(); - b.group(workers) - .channel(NioDatagramChannel.class) - .option(ChannelOption.SO_BROADCAST, false) - .option(ChannelOption.SO_RCVBUF, Math.max(1024 * 1024, maxPacketLength * 2)) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(DatagramChannel ch) { - ch.pipeline() - .addLast("rtp", new Jt1078UdpRtpPacketDecoder(maxPacketLength)) - .addLast("archive", new Jt1078ReceivedMediaStreamHandler(archiveService, malformedPublisher)); - } - }); - this.serverChannel = b.bind(port).sync().channel(); - log.info("[jt1078] udp media stream server listening on :{} workers={} maxPacketLength={}", - port, threads, maxPacketLength); - } - - @Override - public void destroy() { - try { - if (serverChannel != null) serverChannel.close().sync(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - if (workers != null) workers.shutdownGracefully(); - log.info("[jt1078] udp media stream server stopped"); - } - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpRtpPacketDecoder.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpRtpPacketDecoder.java deleted file mode 100644 index f9bd4b6c..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/media/Jt1078UdpRtpPacketDecoder.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.media; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.socket.DatagramPacket; -import io.netty.handler.codec.CorruptedFrameException; -import io.netty.handler.codec.MessageToMessageDecoder; - -import java.net.InetSocketAddress; -import java.time.Instant; -import java.util.List; - -public final class Jt1078UdpRtpPacketDecoder extends MessageToMessageDecoder { - - private final int maxPacketLength; - - public Jt1078UdpRtpPacketDecoder(int maxPacketLength) { - this.maxPacketLength = maxPacketLength; - } - - @Override - protected void decode(ChannelHandlerContext ctx, DatagramPacket msg, List out) throws Exception { - int packetLength = msg.content().readableBytes(); - if (packetLength > maxPacketLength) { - out.add(malformed(msg, packetLength, "jt1078 udp rtp packet too large: " + packetLength)); - return; - } - if (packetLength < Jt1078RtpPacketParser.HEADER_LEN) { - out.add(malformed(msg, packetLength, "jt1078 udp rtp packet too short: " + packetLength)); - return; - } - try { - out.add(new Jt1078ReceivedRtpPacket( - Jt1078RtpPacketParser.parse(msg.content().slice()), - peer(msg.sender()))); - } catch (CorruptedFrameException | IndexOutOfBoundsException e) { - out.add(malformed(msg, packetLength, e.getMessage())); - } - } - - private static Jt1078MalformedRtpPacket malformed(DatagramPacket msg, int packetLength, String reason) { - byte[] raw = new byte[Math.max(0, msg.content().readableBytes())]; - msg.content().getBytes(msg.content().readerIndex(), raw); - return new Jt1078MalformedRtpPacket( - "udp", - peer(msg.sender()), - Jt1078RtpPacketParser.peekSim(msg.content().slice()), - raw, - packetLength, - reason == null ? "" : reason, - Instant.now()); - } - - private static String peer(InetSocketAddress sender) { - if (sender == null) { - return ""; - } - return sender.getAddress().getHostAddress() + ":" + sender.getPort(); - } -} diff --git a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/model/Jt1078MessageId.java b/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/model/Jt1078MessageId.java deleted file mode 100644 index de31a886..00000000 --- a/modules/protocols/protocol-jt1078/src/main/java/com/lingniu/ingest/protocol/jt1078/model/Jt1078MessageId.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.model; - -/** JT/T 1078 消息 ID 常量(核心信令子集)。 */ -public final class Jt1078MessageId { - - private Jt1078MessageId() {} - - public static final int TERMINAL_MEDIA_ATTRS_REPORT = 0x1003; - public static final int TERMINAL_PASSENGER_VOLUME = 0x1005; - public static final int TERMINAL_FILE_LIST = 0x1205; - public static final int TERMINAL_FILE_UPLOAD_DONE = 0x1206; - - public static final int PLATFORM_QUERY_MEDIA_ATTRS = 0x1003; - public static final int PLATFORM_REALTIME_PLAY = 0x9101; - public static final int PLATFORM_REALTIME_CONTROL = 0x9102; - public static final int PLATFORM_HISTORY_PLAY = 0x9201; - public static final int PLATFORM_HISTORY_CONTROL = 0x9202; - public static final int PLATFORM_RESOURCE_SEARCH = 0x9205; - public static final int PLATFORM_FILE_UPLOAD = 0x9206; - public static final int PLATFORM_FILE_UPLOAD_CONTROL = 0x9207; -} diff --git a/modules/protocols/protocol-jt1078/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/protocols/protocol-jt1078/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 8539eae3..00000000 --- a/modules/protocols/protocol-jt1078/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,2 +0,0 @@ -com.lingniu.ingest.protocol.jt1078.config.Jt1078AutoConfiguration -com.lingniu.ingest.protocol.jt1078.config.Jt1078SignalAutoConfiguration diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078EventMappingTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078EventMappingTest.java deleted file mode 100644 index 522616ea..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078EventMappingTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.protocol.jt1078.model.Jt1078MessageId; -import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078EventMappingTest { - - private final Jt808EventMapper mapper = new Jt808EventMapper(new InMemoryVehicleIdentityService()); - private final Jt808Header header = new Jt808Header( - 0x1003, 0, 0, false, Jt808Header.ProtocolVersion.V2013, "13800138000", 1, 0, 0); - - @Test - void mediaAttributesProduceQueryableMediaMetaEvent() { - Jt808Body.MediaAttributes attrs = new Jt808Body.MediaAttributes(1, 2, 3, 4, 320, true, 98, 4, 8, new byte[]{1, 2}); - - List events = mapper.toEvents(new Jt808Message(header, attrs)); - - assertThat(events).hasSize(1); - assertThat(events.getFirst()).isInstanceOf(VehicleEvent.MediaMeta.class); - VehicleEvent.MediaMeta event = (VehicleEvent.MediaMeta) events.getFirst(); - assertThat(event.mediaType()).contains("videoEncoding=98"); - assertThat(event.metadata()).containsEntry("jt1078Message", "mediaAttributes"); - } - - @Test - void fileUploadCompleteProducesPassthroughEventWithResultMetadata() { - Jt808Body.FileUploadComplete done = new Jt808Body.FileUploadComplete(0x1234, 0, new byte[]{0x12, 0x34, 0}); - - List events = mapper.toEvents(new Jt808Message(header, done)); - - assertThat(events).hasSize(1); - assertThat(events.getFirst()).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough event = (VehicleEvent.Passthrough) events.getFirst(); - assertThat(event.metadata()).containsEntry("jt1078Message", "fileUploadComplete"); - assertThat(event.metadata()).containsEntry("uploadResult", "0"); - } - - @Test - void fileListProducesPassthroughEventWithQueryableSummaryMetadata() { - Jt808Header fileListHeader = new Jt808Header( - Jt1078MessageId.TERMINAL_FILE_LIST, 0, 0, false, - Jt808Header.ProtocolVersion.V2013, "13800138000", 1, 0, 0); - byte[] raw = new byte[]{0x12, 0x34, 0, 0, 0, 2}; - Jt808Body.FileList fileList = new Jt808Body.FileList(0x1234, 2, raw); - - List events = mapper.toEvents(new Jt808Message(fileListHeader, fileList)); - - assertThat(events).hasSize(1); - assertThat(events.getFirst()).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough event = (VehicleEvent.Passthrough) events.getFirst(); - assertThat(event.passthroughType()).isEqualTo(Jt1078MessageId.TERMINAL_FILE_LIST); - assertThat(event.data()).containsExactly(raw); - assertThat(event.metadata()) - .containsEntry("jt1078Message", "fileList") - .containsEntry("responseSerialNo", "4660") - .containsEntry("fileCount", "2"); - } - - @Test - void passengerVolumeProducesPassthroughEventWithQueryableMetadata() { - Jt808Header passengerHeader = new Jt808Header( - Jt1078MessageId.TERMINAL_PASSENGER_VOLUME, 0, 0, false, - Jt808Header.ProtocolVersion.V2013, "13800138000", 1, 0, 0); - byte[] raw = new byte[]{1, 2, 3}; - Jt808Body.PassengerVolume passengerVolume = new Jt808Body.PassengerVolume( - 2, "2026-06-22T15:30:00", "2026-06-22T16:00:00", 21, 3, raw); - - List events = mapper.toEvents(new Jt808Message(passengerHeader, passengerVolume)); - - assertThat(events).hasSize(1); - assertThat(events.getFirst()).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough event = (VehicleEvent.Passthrough) events.getFirst(); - assertThat(event.passthroughType()).isEqualTo(Jt1078MessageId.TERMINAL_PASSENGER_VOLUME); - assertThat(event.data()).containsExactly(raw); - assertThat(event.metadata()) - .containsEntry("jt1078Message", "passengerVolume") - .containsEntry("channelId", "2") - .containsEntry("startTime", "2026-06-22T15:30:00") - .containsEntry("endTime", "2026-06-22T16:00:00") - .containsEntry("passengerGetOn", "21") - .containsEntry("passengerGetOff", "3"); - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpEventPublisherTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpEventPublisherTest.java deleted file mode 100644 index 5e77dc33..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpEventPublisherTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpEventPublisher; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaMessageId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.ArrayList; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078MalformedRtpEventPublisherTest { - - @Test - void malformedRtpPacketProducesRawFrameForUnifiedArchiveAndPassthrough() { - ArrayList frames = new ArrayList<>(); - Jt1078MalformedRtpEventPublisher publisher = new Jt1078MalformedRtpEventPublisher( - new InMemoryVehicleIdentityService(), frames::add); - Instant receivedAt = Instant.parse("2026-06-22T08:30:00Z"); - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64}; - - publisher.publish(new Jt1078MalformedRtpPacket( - "udp", "10.0.0.8:1078", raw, raw.length, "jt1078 udp rtp packet too short: 4", receivedAt)); - - assertThat(frames).hasSize(1); - RawFrame frame = frames.getFirst(); - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JT1078); - assertThat(frame.command()).isEqualTo(Jt1078MediaMessageId.MALFORMED_RTP); - assertThat(frame.payload()).isInstanceOf(Jt1078MalformedRtpPacket.class); - assertThat(frame.rawBytes()).containsExactly(raw); - assertThat(frame.receivedAt()).isEqualTo(receivedAt); - assertThat(frame.sourceMeta()).containsEntry("transport", "udp") - .containsEntry("peer", "10.0.0.8:1078") - .containsEntry("malformedRtp", "true") - .containsEntry("parseError", "true") - .containsEntry("parseErrorMessage", "jt1078 udp rtp packet too short: 4") - .containsEntry("packetLength", "4") - .containsEntry("reason", "jt1078 udp rtp packet too short: 4"); - } - - @Test - void malformedRtpPacketUsesSharedIdentityWhenSimIsAvailable() { - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT1078, "LNVIN107800000001", "013812345678", "", "")); - ArrayList frames = new ArrayList<>(); - Jt1078MalformedRtpEventPublisher publisher = new Jt1078MalformedRtpEventPublisher(identity, frames::add); - Instant receivedAt = Instant.parse("2026-06-22T08:30:00Z"); - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64, 0, 1}; - - publisher.publish(new Jt1078MalformedRtpPacket( - "tcp", - "10.0.0.8:1078", - "013812345678", - raw, - 128, - "jt1078 rtp packet too large: 128", - receivedAt)); - - assertThat(frames).hasSize(1); - RawFrame frame = frames.getFirst(); - assertThat(frame.sourceMeta()) - .containsEntry("vin", "LNVIN107800000001") - .containsEntry("sim", "013812345678") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE"); - } - - @Test - void identityResolverFailureStillPublishesMalformedRtpWithOriginalPayload() { - ArrayList frames = new ArrayList<>(); - Jt1078MalformedRtpEventPublisher publisher = new Jt1078MalformedRtpEventPublisher( - lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }, - frames::add); - Instant receivedAt = Instant.parse("2026-06-22T08:30:00Z"); - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64, 0, 1}; - - publisher.publish(new Jt1078MalformedRtpPacket( - "tcp", - "10.0.0.8:1078", - "013812345678", - raw, - raw.length, - "jt1078 rtp packet too short: 6", - receivedAt)); - - assertThat(frames).singleElement().satisfies(frame -> { - assertThat(frame.protocolId()).isEqualTo(ProtocolId.JT1078); - assertThat(frame.command()).isEqualTo(Jt1078MediaMessageId.MALFORMED_RTP); - assertThat(frame.rawBytes()).containsExactly(raw); - assertThat(frame.sourceMeta()) - .containsEntry("vin", "unknown") - .containsEntry("sim", "013812345678") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable") - .containsEntry("parseError", "true") - .containsEntry("parseErrorMessage", "jt1078 rtp packet too short: 6"); - }); - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpHandlerTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpHandlerTest.java deleted file mode 100644 index 35f2bea0..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MalformedRtpHandlerTest.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.RawArchiveKeys; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.protocol.jt1078.handler.Jt1078MalformedRtpHandler; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Constructor; -import java.time.Instant; -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; - -import static org.assertj.core.api.Assertions.assertThat; - -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 - void malformedRtpRawFramePayloadMapsToPassthroughWithDiagnostics() { - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64}; - Jt1078MalformedRtpHandler handler = new Jt1078MalformedRtpHandler(new InMemoryVehicleIdentityService()); - - List events = handler.onMalformedRtp(new Jt1078MalformedRtpPacket( - "udp", - "10.0.0.8:1078", - raw, - raw.length, - "jt1078 udp rtp packet too short: 4", - Instant.parse("2026-06-22T08:30:00Z"))); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.JT1078); - assertThat(passthrough.vin()).isEqualTo("unknown"); - assertThat(passthrough.eventTime()).isEqualTo(Instant.parse("2026-06-22T08:30:00Z")); - assertThat(passthrough.data()).containsExactly(raw); - assertThat(passthrough.metadata()) - .containsEntry("transport", "udp") - .containsEntry("peer", "10.0.0.8:1078") - .containsEntry("vin", "unknown") - .containsEntry("malformedRtp", "true") - .containsEntry("parseError", "true") - .containsEntry("parseErrorMessage", "jt1078 udp rtp packet too short: 4") - .containsEntry("packetLength", "4"); - }); - } - - @Test - void malformedRtpHandlerResolvesVinBySim() { - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT1078, "LNVIN107800000001", "013812345678", "", "")); - Jt1078MalformedRtpHandler handler = new Jt1078MalformedRtpHandler(identity); - - List events = handler.onMalformedRtp(new Jt1078MalformedRtpPacket( - "tcp", - "10.0.0.8:1078", - "013812345678", - new byte[]{0x30, 0x31, 0x63, 0x64}, - 128, - "jt1078 rtp packet too large: 128", - Instant.parse("2026-06-22T08:30:00Z"))); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event.vin()).isEqualTo("LNVIN107800000001"); - assertThat(event.metadata()) - .containsEntry("vin", "LNVIN107800000001") - .containsEntry("sim", "013812345678") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE"); - }); - } - - @Test - void identityResolverFailureStillProducesPassthroughWithRawPayload() { - Jt1078MalformedRtpHandler handler = new Jt1078MalformedRtpHandler(lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }); - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64}; - - List events = handler.onMalformedRtp(new Jt1078MalformedRtpPacket( - "udp", - "10.0.0.8:1078", - "013812345678", - raw, - raw.length, - "jt1078 udp rtp packet too short: 4", - Instant.parse("2026-06-22T08:30:00Z"))); - - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.vin()).isEqualTo("unknown"); - assertThat(passthrough.data()).containsExactly(raw); - assertThat(passthrough.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("sim", "013812345678") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable") - .containsEntry("parseError", "true"); - }); - } - - @Test - void dispatcherEnrichmentCanAttachRawArchiveReferenceToMalformedRtpPassthrough() { - byte[] raw = new byte[]{0x30, 0x31, 0x63, 0x64}; - Jt1078MalformedRtpHandler handler = new Jt1078MalformedRtpHandler(new InMemoryVehicleIdentityService()); - - VehicleEvent.Passthrough event = (VehicleEvent.Passthrough) handler.onMalformedRtp(new Jt1078MalformedRtpPacket( - "udp", "10.0.0.8:1078", raw, raw.length, "bad", Instant.parse("2026-06-22T08:30:00Z"))) - .getFirst(); - - assertThat(event.metadata()).doesNotContainKey(RawArchiveKeys.META_URI); - assertThat(event.data()).containsExactly(raw); - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MediaArchiveServiceTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MediaArchiveServiceTest.java deleted file mode 100644 index f3b7916a..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078MediaArchiveServiceTest.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaArchiveService; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078RtpPacket; -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.LocalArchiveStore; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078MediaArchiveServiceTest { - - @TempDir - Path tempDir; - - @Test - void archivesPayloadBySegmentAndPublishesMediaMetaOncePerSegment() throws Exception { - LocalArchiveStore store = new LocalArchiveStore(tempDir.toString()); - List published = new ArrayList<>(); - Jt1078MediaArchiveService service = new Jt1078MediaArchiveService( - store, new InMemoryVehicleIdentityService(), published::add, 300); - - Jt1078RtpPacket p1 = new Jt1078RtpPacket(1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2}); - Jt1078RtpPacket p2 = new Jt1078RtpPacket(2, "013800138000", 3, 0, 1, 1_100L, new byte[]{3, 4, 5}); - service.archive(p1, "127.0.0.1:12345"); - service.archive(p2, "127.0.0.1:12345"); - - assertThat(published).hasSize(1); - VehicleEvent.MediaMeta meta = (VehicleEvent.MediaMeta) published.getFirst(); - assertThat(meta.source()).isEqualTo(ProtocolId.JT1078); - assertThat(meta.vin()).isEqualTo("013800138000"); - assertThat(meta.sizeBytes()).isEqualTo(2); - assertThat(meta.archiveRef()).startsWith("file://"); - assertThat(meta.metadata()) - .containsEntry("vin", "013800138000") - .containsEntry("channelId", "3") - .containsEntry("segment", "900") - .containsEntry("segmentSizeBytes", "2") - .containsEntry("archiveKey", "jt1078/1970/01/01/013800138000/ch-3/dt-0/900.media"); - - Path archived = Path.of(meta.archiveRef().substring("file://".length())); - assertThat(Files.readAllBytes(archived)).containsExactly(1, 2, 3, 4, 5); - } - - @Test - void archiveMediaMetaUsesResolvedInternalVinInMetadataAndArchiveKey() throws Exception { - LocalArchiveStore store = new LocalArchiveStore(tempDir.toString()); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT1078, "LNVIN107800000009", "013800138000", "", "")); - List published = new ArrayList<>(); - Jt1078MediaArchiveService service = new Jt1078MediaArchiveService( - store, identity, published::add, 300); - - service.archive(new Jt1078RtpPacket( - 1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2}), "127.0.0.1:12345"); - - assertThat(published).singleElement() - .isInstanceOf(VehicleEvent.MediaMeta.class) - .satisfies(event -> { - assertThat(event.vin()).isEqualTo("LNVIN107800000009"); - assertThat(event.metadata()) - .containsEntry("vin", "LNVIN107800000009") - .containsEntry("sim", "013800138000") - .containsEntry("identityResolved", "true") - .containsEntry("archiveKey", "jt1078/1970/01/01/LNVIN107800000009/ch-3/dt-0/900.media"); - }); - } - - @Test - void identityChangeWithinSameSegmentPublishesMediaMetaForNewArchiveKey() throws Exception { - LocalArchiveStore store = new LocalArchiveStore(tempDir.toString()); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - List published = new ArrayList<>(); - Jt1078MediaArchiveService service = new Jt1078MediaArchiveService( - store, identity, published::add, 300); - - service.archive(new Jt1078RtpPacket( - 1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2}), "127.0.0.1:12345"); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT1078, "LNVIN107800000009", "013800138000", "", "")); - service.archive(new Jt1078RtpPacket( - 2, "013800138000", 3, 0, 1, 1_100L, new byte[]{3, 4}), "127.0.0.1:12345"); - - assertThat(published) - .extracting(event -> event.metadata().get("archiveKey")) - .containsExactly( - "jt1078/1970/01/01/013800138000/ch-3/dt-0/900.media", - "jt1078/1970/01/01/LNVIN107800000009/ch-3/dt-0/900.media"); - assertThat(published) - .extracting(VehicleEvent::vin) - .containsExactly("013800138000", "LNVIN107800000009"); - } - - @Test - void identityResolverFailureStillArchivesPayloadAndPublishesMediaMeta() throws Exception { - LocalArchiveStore store = new LocalArchiveStore(tempDir.toString()); - List published = new ArrayList<>(); - Jt1078MediaArchiveService service = new Jt1078MediaArchiveService( - store, - lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }, - published::add, - 300); - - service.archive(new Jt1078RtpPacket( - 1, "013800138000", 3, 0, 0, 1_000L, new byte[]{1, 2}), "127.0.0.1:12345"); - - assertThat(published).singleElement() - .isInstanceOf(VehicleEvent.MediaMeta.class) - .satisfies(event -> { - VehicleEvent.MediaMeta media = (VehicleEvent.MediaMeta) event; - assertThat(media.vin()).isEqualTo("unknown"); - assertThat(media.archiveRef()).startsWith("file://"); - assertThat(media.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("sim", "013800138000") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable") - .containsEntry("archiveKey", "jt1078/1970/01/01/unknown/ch-3/dt-0/900.media"); - Path archived = Path.of(media.archiveRef().substring("file://".length())); - assertThat(Files.readAllBytes(archived)).containsExactly(1, 2); - }); - } - - @Test - void archiveFailurePublishesPassthroughErrorEventWithOriginalPayload() { - List published = new ArrayList<>(); - Jt1078MediaArchiveService service = new Jt1078MediaArchiveService( - new FailingArchiveStore(), new InMemoryVehicleIdentityService(), published::add, 300); - - Jt1078RtpPacket packet = new Jt1078RtpPacket( - 9, "013800138000", 4, 2, 1, 1_000L, new byte[]{9, 8, 7}); - - service.archive(packet, "127.0.0.1:9000"); - - assertThat(published).singleElement() - .satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.JT1078); - assertThat(passthrough.vin()).isEqualTo("013800138000"); - assertThat(passthrough.passthroughType()).isEqualTo(2); - assertThat(passthrough.data()).containsExactly(9, 8, 7); - assertThat(passthrough.metadata()) - .containsEntry("archiveError", "true") - .containsEntry("vin", "013800138000") - .containsEntry("sim", "013800138000") - .containsEntry("channelId", "4") - .containsEntry("sequence", "9") - .containsEntry("peer", "127.0.0.1:9000") - .containsEntry("segment", "900") - .containsEntry("archiveKey", "jt1078/1970/01/01/013800138000/ch-4/dt-2/900.media"); - }); - } - - private static final class FailingArchiveStore implements ArchiveStore { - @Override - public String put(String key, InputStream data, long length) throws IOException { - throw new IOException("archive backend unavailable"); - } - - @Override - public String append(String key, byte[] chunk) throws IOException { - throw new IOException("archive backend unavailable"); - } - - @Override - public InputStream get(String key) throws IOException { - throw new IOException("archive backend unavailable"); - } - - @Override - public boolean exists(String key) { - return false; - } - - @Override - public long size(String key) throws IOException { - throw new IOException("archive backend unavailable"); - } - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078ParserTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078ParserTest.java deleted file mode 100644 index 5770b162..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078ParserTest.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078; - -import com.lingniu.ingest.protocol.jt1078.codec.parser.FileUploadCompleteBodyParser; -import com.lingniu.ingest.protocol.jt1078.codec.parser.FileListBodyParser; -import com.lingniu.ingest.protocol.jt1078.codec.parser.MediaAttributesBodyParser; -import com.lingniu.ingest.protocol.jt1078.codec.parser.PassengerVolumeBodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078ParserTest { - - private final Jt808Header header = new Jt808Header( - 0, 0, 0, false, Jt808Header.ProtocolVersion.V2013, "13800138000", 1, 0, 0); - - @Test - void parsesMediaAttributesAsSemanticBody() { - byte[] raw = new byte[]{1, 2, 3, 4, 0x01, 0x40, 1, 98, 4, 8}; - - Jt808Body body = new MediaAttributesBodyParser().parse(header, ByteBuffer.wrap(raw)); - - assertThat(body).isInstanceOf(Jt808Body.MediaAttributes.class); - Jt808Body.MediaAttributes attrs = (Jt808Body.MediaAttributes) body; - assertThat(attrs.inputAudioEncoding()).isEqualTo(1); - assertThat(attrs.inputAudioChannels()).isEqualTo(2); - assertThat(attrs.inputAudioSampleRate()).isEqualTo(3); - assertThat(attrs.inputAudioBitDepth()).isEqualTo(4); - assertThat(attrs.audioFrameLength()).isEqualTo(320); - assertThat(attrs.audioOutputSupported()).isTrue(); - assertThat(attrs.videoEncoding()).isEqualTo(98); - assertThat(attrs.maxAudioChannels()).isEqualTo(4); - assertThat(attrs.maxVideoChannels()).isEqualTo(8); - assertThat(attrs.raw()).containsExactly(raw); - } - - @Test - void parsesFileUploadCompleteAsSemanticBody() { - byte[] raw = new byte[]{0x12, 0x34, 1, 2, 3, 4}; - - Jt808Body body = new FileUploadCompleteBodyParser().parse(header, ByteBuffer.wrap(raw)); - - assertThat(body).isInstanceOf(Jt808Body.FileUploadComplete.class); - Jt808Body.FileUploadComplete done = (Jt808Body.FileUploadComplete) body; - assertThat(done.responseSerialNo()).isEqualTo(0x1234); - assertThat(done.result()).isEqualTo(1); - assertThat(done.raw()).containsExactly(raw); - } - - @Test - void parsesFileListSummaryAsSemanticBody() { - byte[] raw = new byte[]{0x12, 0x34, 0x00, 0x00, 0x00, 0x02, 9, 8, 7}; - - Jt808Body body = new FileListBodyParser().parse(header, ByteBuffer.wrap(raw)); - - assertThat(body).isInstanceOf(Jt808Body.FileList.class); - Jt808Body.FileList list = (Jt808Body.FileList) body; - assertThat(list.responseSerialNo()).isEqualTo(0x1234); - assertThat(list.fileCount()).isEqualTo(2); - assertThat(list.raw()).containsExactly(raw); - } - - @Test - void parsesPassengerVolumeAsSemanticBody() { - byte[] raw = new byte[]{ - 0x02, - 0x26, 0x06, 0x22, 0x15, 0x30, 0x00, - 0x26, 0x06, 0x22, 0x16, 0x00, 0x00, - 0x00, 0x15, - 0x00, 0x03 - }; - - Jt808Body body = new PassengerVolumeBodyParser().parse(header, ByteBuffer.wrap(raw)); - - assertThat(body).isInstanceOf(Jt808Body.PassengerVolume.class); - Jt808Body.PassengerVolume passengerVolume = (Jt808Body.PassengerVolume) body; - assertThat(passengerVolume.channelId()).isEqualTo(2); - assertThat(passengerVolume.startTime()).isEqualTo("2026-06-22T15:30:00"); - assertThat(passengerVolume.endTime()).isEqualTo("2026-06-22T16:00:00"); - assertThat(passengerVolume.passengerGetOn()).isEqualTo(21); - assertThat(passengerVolume.passengerGetOff()).isEqualTo(3); - assertThat(passengerVolume.raw()).containsExactly(raw); - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078RtpPacketDecoderTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078RtpPacketDecoderTest.java deleted file mode 100644 index 6743e8cd..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078RtpPacketDecoderTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078; - -import com.lingniu.ingest.protocol.jt1078.media.Jt1078RtpPacket; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078RtpPacketDecoder; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket; -import io.netty.buffer.Unpooled; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078RtpPacketDecoderTest { - - @Test - void decodesCompleteTcpRtpPacketAndKeepsPayloadOnly() { - EmbeddedChannel channel = new EmbeddedChannel(new Jt1078RtpPacketDecoder(1024)); - - channel.writeInbound(Unpooled.wrappedBuffer(packet(7, "013800138000", 2, 3, 1, 123456789L, - new byte[]{1, 2, 3, 4}))); - - Jt1078RtpPacket packet = channel.readInbound(); - assertThat(packet.sequence()).isEqualTo(7); - assertThat(packet.sim()).isEqualTo("013800138000"); - assertThat(packet.channelId()).isEqualTo(2); - assertThat(packet.dataType()).isEqualTo(3); - assertThat(packet.packetType()).isEqualTo(1); - assertThat(packet.timestamp()).isEqualTo(123456789L); - assertThat(packet.payload()).containsExactly(1, 2, 3, 4); - } - - @Test - void waitsForFragmentedPayloadBeforeEmittingPacket() { - byte[] packet = packet(8, "013800138000", 1, 0, 0, 1L, new byte[]{9, 8, 7}); - EmbeddedChannel channel = new EmbeddedChannel(new Jt1078RtpPacketDecoder(1024)); - - channel.writeInbound(Unpooled.wrappedBuffer(packet, 0, packet.length - 1)); - assertThat((Object) channel.readInbound()).isNull(); - channel.writeInbound(Unpooled.wrappedBuffer(packet, packet.length - 1, 1)); - - Jt1078RtpPacket decoded = channel.readInbound(); - assertThat(decoded.payload()).containsExactly(9, 8, 7); - } - - @Test - void oversizedTcpRtpPacketEmitsMalformedCandidateInsteadOfClosingPipeline() { - byte[] packet = packet(9, "013800138000", 1, 0, 0, 1L, new byte[]{1, 2, 3, 4}); - EmbeddedChannel channel = new EmbeddedChannel(new Jt1078RtpPacketDecoder(30)); - - channel.writeInbound(Unpooled.wrappedBuffer(packet)); - - Jt1078MalformedRtpPacket malformed = channel.readInbound(); - assertThat(malformed.transport()).isEqualTo("tcp"); - assertThat(malformed.sim()).isEqualTo("013800138000"); - assertThat(malformed.reason()).contains("too large"); - assertThat(malformed.rawBytes()).containsExactly(packet); - assertThat(malformed.packetLength()).isEqualTo(packet.length); - assertThat((Object) channel.readInbound()).isNull(); - } - - static byte[] packet(int sequence, String sim, int channelId, int dataType, int packetType, - long timestamp, byte[] payload) { - byte[] out = new byte[28 + payload.length]; - out[0] = 0x30; - out[1] = 0x31; - out[2] = 0x63; - out[3] = 0x64; - out[4] = (byte) (sequence >>> 8); - out[5] = (byte) sequence; - byte[] bcd = bcd(sim); - System.arraycopy(bcd, 0, out, 6, bcd.length); - out[12] = (byte) channelId; - out[13] = (byte) ((dataType << 4) | (packetType & 0x0F)); - for (int i = 0; i < 8; i++) { - out[14 + i] = (byte) (timestamp >>> (56 - i * 8)); - } - out[26] = (byte) (payload.length >>> 8); - out[27] = (byte) payload.length; - System.arraycopy(payload, 0, out, 28, payload.length); - return out; - } - - private static byte[] bcd(String digits) { - byte[] out = new byte[6]; - for (int i = 0; i < out.length; i++) { - int hi = Character.digit(digits.charAt(i * 2), 10); - int lo = Character.digit(digits.charAt(i * 2 + 1), 10); - out[i] = (byte) ((hi << 4) | lo); - } - return out; - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078UdpRtpPacketDecoderTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078UdpRtpPacketDecoderTest.java deleted file mode 100644 index 0e22d695..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/Jt1078UdpRtpPacketDecoderTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078; - -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078RtpPacket; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078ReceivedRtpPacket; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078UdpRtpPacketDecoder; -import io.netty.buffer.Unpooled; -import io.netty.channel.embedded.EmbeddedChannel; -import io.netty.channel.socket.DatagramPacket; -import org.junit.jupiter.api.Test; - -import java.net.InetSocketAddress; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078UdpRtpPacketDecoderTest { - - @Test - void decodesSingleUdpDatagramRtpPacket() { - EmbeddedChannel channel = new EmbeddedChannel(new Jt1078UdpRtpPacketDecoder(1024)); - InetSocketAddress sender = new InetSocketAddress("127.0.0.1", 19000); - InetSocketAddress recipient = new InetSocketAddress("127.0.0.1", 10780); - - channel.writeInbound(new DatagramPacket(Unpooled.wrappedBuffer(Jt1078RtpPacketDecoderTest.packet( - 9, "013800138000", 4, 0, 2, 123456789L, new byte[]{5, 6, 7})), recipient, sender)); - - Jt1078ReceivedRtpPacket received = channel.readInbound(); - Jt1078RtpPacket decoded = received.packet(); - assertThat(received.peer()).isEqualTo("127.0.0.1:19000"); - assertThat(decoded.sequence()).isEqualTo(9); - assertThat(decoded.sim()).isEqualTo("013800138000"); - assertThat(decoded.channelId()).isEqualTo(4); - assertThat(decoded.packetType()).isEqualTo(2); - assertThat(decoded.payload()).containsExactly(5, 6, 7); - } - - @Test - void shortUdpDatagramEmitsMalformedCandidateWithPeerAndRawBytes() { - EmbeddedChannel channel = new EmbeddedChannel(new Jt1078UdpRtpPacketDecoder(1024)); - InetSocketAddress sender = new InetSocketAddress("127.0.0.1", 19000); - InetSocketAddress recipient = new InetSocketAddress("127.0.0.1", 10780); - - channel.writeInbound(new DatagramPacket(Unpooled.wrappedBuffer(new byte[]{1, 2, 3}), recipient, sender)); - - Jt1078MalformedRtpPacket malformed = channel.readInbound(); - assertThat(malformed.transport()).isEqualTo("udp"); - assertThat(malformed.peer()).isEqualTo("127.0.0.1:19000"); - assertThat(malformed.reason()).contains("too short"); - assertThat(malformed.rawBytes()).containsExactly(1, 2, 3); - assertThat(malformed.packetLength()).isEqualTo(3); - } - - @Test - void invalidUdpDatagramEmitsMalformedCandidateInsteadOfThrowing() { - EmbeddedChannel channel = new EmbeddedChannel(new Jt1078UdpRtpPacketDecoder(1024)); - InetSocketAddress sender = new InetSocketAddress("127.0.0.1", 19000); - InetSocketAddress recipient = new InetSocketAddress("127.0.0.1", 10780); - byte[] invalid = Jt1078RtpPacketDecoderTest.packet( - 9, "013800138000", 4, 0, 2, 123456789L, new byte[]{5, 6, 7}); - invalid[0] = 0x12; - - channel.writeInbound(new DatagramPacket(Unpooled.wrappedBuffer(invalid), recipient, sender)); - - Jt1078MalformedRtpPacket malformed = channel.readInbound(); - assertThat(malformed.transport()).isEqualTo("udp"); - assertThat(malformed.peer()).isEqualTo("127.0.0.1:19000"); - assertThat(malformed.reason()).contains("invalid magic"); - assertThat(malformed.rawBytes()).containsExactly(invalid); - assertThat(malformed.packetLength()).isEqualTo(invalid.length); - } - - @Test - void oversizedUdpDatagramKeepsSimForIdentityResolution() { - EmbeddedChannel channel = new EmbeddedChannel(new Jt1078UdpRtpPacketDecoder(30)); - InetSocketAddress sender = new InetSocketAddress("127.0.0.1", 19000); - InetSocketAddress recipient = new InetSocketAddress("127.0.0.1", 10780); - byte[] oversized = Jt1078RtpPacketDecoderTest.packet( - 10, "013800138000", 4, 0, 2, 123456789L, new byte[]{5, 6, 7, 8}); - - channel.writeInbound(new DatagramPacket(Unpooled.wrappedBuffer(oversized), recipient, sender)); - - Jt1078MalformedRtpPacket malformed = channel.readInbound(); - assertThat(malformed.transport()).isEqualTo("udp"); - assertThat(malformed.sim()).isEqualTo("013800138000"); - assertThat(malformed.reason()).contains("too large"); - assertThat(malformed.rawBytes()).containsExactly(oversized); - assertThat(malformed.packetLength()).isEqualTo(oversized.length); - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfigurationTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfigurationTest.java deleted file mode 100644 index d93a981f..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/config/Jt1078AutoConfigurationTest.java +++ /dev/null @@ -1,270 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.config; - -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.config.IngestCoreAutoConfiguration; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityRegistry; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.identity.config.VehicleIdentityAutoConfiguration; -import com.lingniu.ingest.protocol.jt1078.handler.Jt1078MediaSignalHandler; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpEventPublisher; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MalformedRtpPacket; -import com.lingniu.ingest.protocol.jt1078.media.Jt1078MediaArchiveService; -import com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration; -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.sink.archive.ArchiveStore; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.UnaryOperator; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078AutoConfigurationTest { - - private final ApplicationContextRunner runner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - IngestCoreAutoConfiguration.class, - Jt1078AutoConfiguration.class, - Jt1078SignalAutoConfiguration.class)) - .withUserConfiguration(SupportBeans.class) - .withPropertyValues( - "lingniu.ingest.jt1078.enabled=true", - "lingniu.ingest.jt1078.media-stream.enabled=false"); - - @Test - void mediaStreamDefaultsToLegacyProductionPort() { - Jt1078Properties props = new Jt1078Properties(); - - assertThat(props.getMediaStream().getPort()).isEqualTo(11078); - assertThat(props.getMediaStream().getUdpPort()).isEqualTo(11078); - } - - @Test - void mediaArchiveStartsWithoutJt808Mapper() { - runner.run(context -> { - assertThat(context).hasSingleBean(Jt1078MediaArchiveService.class); - assertThat(context).doesNotHaveBean(Jt808EventMapper.class); - assertThat(context).doesNotHaveBean(Jt1078MediaSignalHandler.class); - }); - } - - @Test - void mediaSignalHandlerStartsWhenJt808IsEnabled() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - Jt1078AutoConfiguration.class, - Jt1078SignalAutoConfiguration.class, - Jt808AutoConfiguration.class, - IngestCoreAutoConfiguration.class, - SessionCoreAutoConfiguration.class, - VehicleIdentityAutoConfiguration.class)) - .withUserConfiguration(SupportBeans.class) - .withPropertyValues( - "lingniu.ingest.jt1078.enabled=true", - "lingniu.ingest.jt1078.media-stream.enabled=false", - "lingniu.ingest.jt808.enabled=true", - "lingniu.ingest.jt808.port=0") - .run(context -> { - assertThat(context).hasSingleBean(Jt808EventMapper.class); - assertThat(context).hasSingleBean(Jt1078MediaSignalHandler.class); - }); - } - - @Test - void malformedRtpPublisherUsesConfiguredIdentityResolver() { - runner.run(context -> { - Jt1078MalformedRtpEventPublisher publisher = context.getBean(Jt1078MalformedRtpEventPublisher.class); - CapturingSink sink = context.getBean(CapturingSink.class); - - publisher.publish(new Jt1078MalformedRtpPacket( - "tcp", - "10.0.0.8:1078", - "013812345678", - new byte[]{0x30, 0x31, 0x63, 0x64}, - 128, - "jt1078 rtp packet too large: 128", - java.time.Instant.parse("2026-06-22T08:30:00Z"))); - - List events = sink.awaitCount(2); - assertThat(events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - assertThat(event.vin()).isEqualTo("LNVIN107800000001"); - assertThat(event.metadata()) - .containsEntry("sim", "013812345678") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE") - .containsEntry("parseError", "true"); - }); - assertThat(events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - assertThat(event.vin()).isEqualTo("LNVIN107800000001"); - assertThat(event.metadata()) - .containsEntry("sim", "013812345678") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE") - .containsEntry("parseError", "true") - .containsKey("rawArchiveUri"); - }); - }); - } - - @Configuration(proxyBeanMethods = false) - static class SupportBeans { - - @Bean - ArchiveStore archiveStore() { - return new InMemoryArchiveStore(); - } - - @Bean - VehicleIdentityResolver vehicleIdentityResolver() { - return lookup -> { - if (!lookup.vin().isBlank()) { - return new VehicleIdentity(lookup.vin(), true, VehicleIdentitySource.EXPLICIT_VIN); - } - if ("013812345678".equals(lookup.phone())) { - return new VehicleIdentity("LNVIN107800000001", true, VehicleIdentitySource.BOUND_PHONE); - } - return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); - }; - } - - @Bean - VehicleIdentityRegistry vehicleIdentityRegistry() { - return binding -> { - }; - } - - @Bean - SessionStore sessionStore() { - return new EmptySessionStore(); - } - - @Bean(destroyMethod = "close") - DisruptorEventBus disruptorEventBus(CapturingSink sink) { - return new DisruptorEventBus(1024, "blocking", List.of(sink)); - } - - @Bean - CapturingSink capturingSink() { - return new CapturingSink(); - } - } - - private static final class EmptySessionStore implements SessionStore { - @Override - public void put(DeviceSession session) { - } - - @Override - public Optional findBySessionId(String sessionId) { - return Optional.empty(); - } - - @Override - public Optional findByVin(String vin) { - return Optional.empty(); - } - - @Override - public Optional findByPhone(String phone) { - return Optional.empty(); - } - - @Override - public Optional update(String sessionId, UnaryOperator updater) { - return Optional.empty(); - } - - @Override - public void remove(String sessionId) { - } - - @Override - public int size() { - return 0; - } - } - - private static final class CapturingSink implements EventSink { - private final CopyOnWriteArrayList events = new CopyOnWriteArrayList<>(); - - @Override - public String name() { - return "capturing"; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - events.add(event); - return CompletableFuture.completedFuture(null); - } - - VehicleEvent awaitOne() throws InterruptedException { - for (int i = 0; i < 50; i++) { - if (!events.isEmpty()) { - return events.getFirst(); - } - Thread.sleep(20); - } - throw new AssertionError("expected one event"); - } - - List awaitCount(int count) throws InterruptedException { - for (int i = 0; i < 50; i++) { - if (events.size() >= count) { - return List.copyOf(events); - } - Thread.sleep(20); - } - throw new AssertionError("expected " + count + " events, got " + events.size()); - } - } - - private static final class InMemoryArchiveStore implements ArchiveStore { - - @Override - public String put(String key, InputStream data, long length) throws IOException { - data.transferTo(OutputStream.nullOutputStream()); - return "memory://" + key; - } - - @Override - public String append(String key, byte[] chunk) { - return "memory://" + key; - } - - @Override - public InputStream get(String key) { - return new ByteArrayInputStream(new byte[0]); - } - - @Override - public boolean exists(String key) { - return true; - } - - @Override - public long size(String key) { - return 0; - } - } -} diff --git a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078CommandsTest.java b/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078CommandsTest.java deleted file mode 100644 index 98289f0e..00000000 --- a/modules/protocols/protocol-jt1078/src/test/java/com/lingniu/ingest/protocol/jt1078/downlink/Jt1078CommandsTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.lingniu.ingest.protocol.jt1078.downlink; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt1078CommandsTest { - - @Test - void realtimePlayEncodesServerPortsChannelAndStreamOptions() { - var command = Jt1078Commands.realtimePlay("10.1.2.3", 11078, 11079, 2, 1, 0); - - assertThat(command.messageId()).isEqualTo(0x9101); - assertThat(command.body()).containsExactly( - 0x08, '1', '0', '.', '1', '.', '2', '.', '3', - 0x2B, 0x46, - 0x2B, 0x47, - 0x02, - 0x01, - 0x00); - } - - @Test - void resourceSearchEncodesBcdTimeAndQueryOptions() { - var command = Jt1078Commands.resourceSearch( - 3, - "2026-06-22 08:09:10", - "260622180000", - 0x01020304L, - 0x05060708L, - 3, - 2, - 1); - - assertThat(command.messageId()).isEqualTo(0x9205); - assertThat(command.body()).containsExactly( - 0x03, - 0x26, 0x06, 0x22, 0x08, 0x09, 0x10, - 0x26, 0x06, 0x22, 0x18, 0x00, 0x00, - 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, - 0x03, - 0x02, - 0x01); - } - - @Test - void queryMediaAttributesUses1003RequestMessageIdWithEmptyBody() { - var command = Jt1078Commands.queryMediaAttributes(); - - assertThat(command.messageId()).isEqualTo(0x1003); - assertThat(command.body()).isEmpty(); - } - - @Test - void realtimeControlEncodesChannelCommandCloseTypeAndStreamType() { - var command = Jt1078Commands.realtimeControl(2, 1, 0, 1); - - assertThat(command.messageId()).isEqualTo(0x9102); - assertThat(command.body()).containsExactly(0x02, 0x01, 0x00, 0x01); - } - - @Test - void historyPlayEncodesPlaybackOptionsAndBcdTimes() { - var command = Jt1078Commands.historyPlay( - "media.example.cn", 11078, 0, 4, - 3, 2, 1, 0, 0, - "260622080910", "260622180000"); - - assertThat(command.messageId()).isEqualTo(0x9201); - assertThat(command.body()).containsExactly( - 0x10, 'm', 'e', 'd', 'i', 'a', '.', 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'n', - 0x2B, 0x46, - 0x00, 0x00, - 0x04, - 0x03, - 0x02, - 0x01, - 0x00, - 0x00, - 0x26, 0x06, 0x22, 0x08, 0x09, 0x10, - 0x26, 0x06, 0x22, 0x18, 0x00, 0x00); - } - - @Test - void historyControlEncodesPlaybackTime() { - var command = Jt1078Commands.historyControl(4, 5, 0, "260622090000"); - - assertThat(command.messageId()).isEqualTo(0x9202); - assertThat(command.body()).containsExactly( - 0x04, 0x05, 0x00, - 0x26, 0x06, 0x22, 0x09, 0x00, 0x00); - } - - @Test - void fileUploadEncodesServerAccountPathAndQueryOptions() { - var command = Jt1078Commands.fileUpload( - "10.1.2.3", 11078, "user", "pass", "/data/upload", - 2, "260622080910", "260622180000", - 1, 2, 3, 2, 1, 0x07); - - assertThat(command.messageId()).isEqualTo(0x9206); - assertThat(command.body()).containsExactly( - 0x08, '1', '0', '.', '1', '.', '2', '.', '3', - 0x2B, 0x46, - 0x04, 'u', 's', 'e', 'r', - 0x04, 'p', 'a', 's', 's', - 0x0C, '/', 'd', 'a', 't', 'a', '/', 'u', 'p', 'l', 'o', 'a', 'd', - 0x02, - 0x26, 0x06, 0x22, 0x08, 0x09, 0x10, - 0x26, 0x06, 0x22, 0x18, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x02, - 0x03, - 0x02, - 0x01, - 0x07); - } - - @Test - void fileUploadControlEncodesResponseSerialAndCommand() { - var command = Jt1078Commands.fileUploadControl(0x1234, 2); - - assertThat(command.messageId()).isEqualTo(0x9207); - assertThat(command.body()).containsExactly(0x12, 0x34, 0x02); - } -} diff --git a/modules/protocols/protocol-jt808/pom.xml b/modules/protocols/protocol-jt808/pom.xml deleted file mode 100644 index efcaadfd..00000000 --- a/modules/protocols/protocol-jt808/pom.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - protocol-jt808 - protocol-jt808 - JT/T 808 协议接入(Netty Server,默认 TCP 808)。 - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - ingest-core - - - com.lingniu.ingest - ingest-codec-common - - - com.lingniu.ingest - session-core - - - com.lingniu.ingest - vehicle-identity - - - com.lingniu.ingest - vehicle-identity-test-support - test - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-configuration-processor - true - - - io.netty - netty-all - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParser.java deleted file mode 100644 index c8ed4678..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParser.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; - -import java.nio.ByteBuffer; - -/** - * 单一消息体解析器 SPI。每个消息 ID 一个实现,替代旧代码里的巨型 switch。 - * - *

    约定:{@code parse} 调用时 buffer.position 指向 body 第 0 字节, - * 解析完成后 position 停在 body 末尾(不消费校验码)。 - */ -public interface BodyParser { - - int messageId(); - - Jt808Body parse(Jt808Header header, ByteBuffer body); -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParserRegistry.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParserRegistry.java deleted file mode 100644 index ea4751c2..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/BodyParserRegistry.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * JT808 消息体解析器注册中心。 - */ -public final class BodyParserRegistry { - - private final Map parsers = new HashMap<>(); - - public BodyParserRegistry(List list) { - for (BodyParser p : list) parsers.put(p.messageId(), p); - } - - public BodyParser find(int messageId) { - return parsers.get(messageId); - } - - public int size() { - return parsers.size(); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808Escape.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808Escape.java deleted file mode 100644 index 75f00215..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808Escape.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -/** - * JT/T 808 转义规则: - *

    - *   0x7e → 0x7d 0x02
    - *   0x7d → 0x7d 0x01
    - * 
    - * 转义仅作用于 消息头 + 消息体 + 校验码,不包含首尾的 0x7e 标识位。 - */ -public final class Jt808Escape { - - private Jt808Escape() {} - - /** 反转义:从 framed 字节数组中剥除 0x7e 首尾后,恢复原始字节。 */ - public static byte[] unescape(byte[] framed, int offset, int length) { - byte[] out = new byte[length]; - int outLen = 0; - for (int i = offset; i < offset + length; i++) { - byte b = framed[i]; - if (b == 0x7d && i + 1 < offset + length) { - byte next = framed[i + 1]; - if (next == 0x02) { - out[outLen++] = 0x7e; - i++; - continue; - } else if (next == 0x01) { - out[outLen++] = 0x7d; - i++; - continue; - } - } - out[outLen++] = b; - } - byte[] trimmed = new byte[outLen]; - System.arraycopy(out, 0, trimmed, 0, outLen); - return trimmed; - } - - /** 转义:在原始字节上应用 7d/7e 替换,结果不含首尾 0x7e。 */ - public static byte[] escape(byte[] raw) { - byte[] out = new byte[raw.length * 2]; - int outLen = 0; - for (byte b : raw) { - if (b == 0x7e) { - out[outLen++] = 0x7d; - out[outLen++] = 0x02; - } else if (b == 0x7d) { - out[outLen++] = 0x7d; - out[outLen++] = 0x01; - } else { - out[outLen++] = b; - } - } - byte[] trimmed = new byte[outLen]; - System.arraycopy(out, 0, trimmed, 0, outLen); - return trimmed; - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoder.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoder.java deleted file mode 100644 index 6bcd5fc9..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoder.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.ByteToMessageDecoder; - -import java.util.List; - -/** - * Netty 层 JT808 分帧 + 反转义。 - * - *

    寻找配对的 {@code 0x7e ... 0x7e},提取中间字节并调用 {@link Jt808Escape#unescape} 还原, - * 输出到 pipeline 的是 {@code byte[]}(反转义后的原始数据,不含首尾 0x7e)。 - */ -public class Jt808FrameDecoder extends ByteToMessageDecoder { - - private static final int MAX_FRAME = 16 * 1024; - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { - while (in.readableBytes() >= 2) { - int startIdx = in.indexOf(in.readerIndex(), in.writerIndex(), (byte) 0x7e); - if (startIdx < 0) { - int len = in.readableBytes(); - byte[] raw = new byte[len]; - in.readBytes(raw); - out.add(new Jt808MalformedFrame(raw, peer(ctx), "jt808 missing frame boundary")); - return; - } - if (startIdx != in.readerIndex()) { - int len = startIdx - in.readerIndex(); - byte[] raw = new byte[len]; - in.readBytes(raw); - out.add(new Jt808MalformedFrame(raw, peer(ctx), "jt808 leading bytes before frame boundary")); - } - int endIdx = in.indexOf(in.readerIndex() + 1, in.writerIndex(), (byte) 0x7e); - if (endIdx < 0) { - if (in.readableBytes() > MAX_FRAME) { - int len = in.readableBytes(); - byte[] raw = new byte[len]; - in.readBytes(raw); - out.add(new Jt808MalformedFrame(raw, peer(ctx), "jt808 unclosed frame too large: " + len)); - } - return; - } - int rawLen = endIdx - (in.readerIndex() + 1); - if (rawLen == 0) { - // 两个相邻 0x7e:视作空帧,跳过一个 - in.skipBytes(1); - continue; - } - byte[] raw = new byte[rawLen]; - in.skipBytes(1); - in.readBytes(raw); - in.skipBytes(1); // 结束 0x7e - byte[] unescaped = Jt808Escape.unescape(raw, 0, raw.length); - out.add(unescaped); - } - } - - private static String peer(ChannelHandlerContext ctx) { - if (ctx == null || ctx.channel() == null || ctx.channel().remoteAddress() == null) { - return ""; - } - return ctx.channel().remoteAddress().toString(); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoder.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoder.java deleted file mode 100644 index 3b052690..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoder.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; - -import java.io.ByteArrayOutputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * JT/T 808 下行帧编码器:把 header + body 字节拼装 → 加 XOR 校验 → 转义 → 外包 0x7e。 - * - *

    支持 2013 与 2019 版基础头;短命令可直接编码,长命令通过 {@link #encodeSubpackages} - * 拆分为 JT/T 808 分包帧。 - */ -public final class Jt808FrameEncoder { - - private static final int MAX_BODY_LENGTH = 0x03FF; - - private Jt808FrameEncoder() {} - - public static byte[] encode(int messageId, String phone, int serialNo, byte[] body) { - return encode(messageId, phone, serialNo, body, Jt808Header.ProtocolVersion.V2013); - } - - public static byte[] encode(int messageId, String phone, int serialNo, byte[] body, - Jt808Header.ProtocolVersion version) { - byte[] raw = buildRaw(messageId, phone, serialNo, body, version, 0, 0); - return wrap(raw); - } - - public static List encodeSubpackages(int messageId, String phone, int serialNo, byte[] body) { - return encodeSubpackages(messageId, phone, serialNo, body, Jt808Header.ProtocolVersion.V2013); - } - - public static List encodeSubpackages(int messageId, - String phone, - int serialNo, - byte[] body, - Jt808Header.ProtocolVersion version) { - if (body == null) { - body = new byte[0]; - } - if (body.length <= MAX_BODY_LENGTH) { - return List.of(encode(messageId, phone, serialNo, body, version)); - } - int totalPackets = (body.length + MAX_BODY_LENGTH - 1) / MAX_BODY_LENGTH; - if (totalPackets > 0xFFFF) { - throw new IllegalArgumentException("jt808 subpackage count too large: " + totalPackets); - } - List frames = new ArrayList<>(totalPackets); - for (int i = 0; i < totalPackets; i++) { - int offset = i * MAX_BODY_LENGTH; - int len = Math.min(MAX_BODY_LENGTH, body.length - offset); - byte[] chunk = new byte[len]; - System.arraycopy(body, offset, chunk, 0, len); - frames.add(wrap(buildRaw(messageId, phone, serialNo, chunk, version, totalPackets, i + 1))); - } - return List.copyOf(frames); - } - - private static byte[] wrap(byte[] raw) { - byte bcc = BccChecksum.compute(raw, 0, raw.length); - - ByteArrayOutputStream almost = new ByteArrayOutputStream(raw.length + 1); - almost.write(raw, 0, raw.length); - almost.write(bcc & 0xFF); - - byte[] escaped = Jt808Escape.escape(almost.toByteArray()); - byte[] out = new byte[escaped.length + 2]; - out[0] = 0x7e; - System.arraycopy(escaped, 0, out, 1, escaped.length); - out[out.length - 1] = 0x7e; - return out; - } - - private static byte[] buildRaw(int messageId, String phone, int serialNo, byte[] body, - Jt808Header.ProtocolVersion version, int totalPackets, int packetSeq) { - if (body == null) { - body = new byte[0]; - } - if (body.length > MAX_BODY_LENGTH) { - throw new IllegalArgumentException("jt808 body too large without subpackage: " - + body.length + " > " + MAX_BODY_LENGTH); - } - boolean subpacket = totalPackets > 0; - boolean v2019 = version == Jt808Header.ProtocolVersion.V2019; - int phoneLen = v2019 ? 10 : 6; - int len = 2 /* msgId */ + 2 /* attrs */ + (v2019 ? 1 : 0) - + phoneLen + 2 /* serial */ + (subpacket ? 4 : 0) + body.length; - ByteArrayOutputStream os = new ByteArrayOutputStream(len); - - // msgId - os.write((messageId >> 8) & 0xFF); - os.write(messageId & 0xFF); - - // attrs: 2019 版 bit14 置 1 - int attrs = body.length & 0x03FF; - if (subpacket) attrs |= 0x2000; - if (v2019) attrs |= 0x4000; - os.write((attrs >> 8) & 0xFF); - os.write(attrs & 0xFF); - - // 2019 版多一个 protocolVersion 字节(值 1) - if (v2019) os.write(0x01); - - // phone BCD - String padded = padLeft(phone, phoneLen * 2); - byte[] bcd = BcdCodec.encode(padded); - os.write(bcd, 0, phoneLen); - - // serial - os.write((serialNo >> 8) & 0xFF); - os.write(serialNo & 0xFF); - - if (subpacket) { - os.write((totalPackets >> 8) & 0xFF); - os.write(totalPackets & 0xFF); - os.write((packetSeq >> 8) & 0xFF); - os.write(packetSeq & 0xFF); - } - - // body - os.write(body, 0, body.length); - return os.toByteArray(); - } - - private static String padLeft(String s, int len) { - if (s == null) s = ""; - if (s.length() >= len) return s.substring(s.length() - len); - StringBuilder sb = new StringBuilder(len); - for (int i = s.length(); i < len; i++) sb.append('0'); - sb.append(s); - return sb.toString(); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MalformedFrame.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MalformedFrame.java deleted file mode 100644 index dfe62dd2..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MalformedFrame.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -public record Jt808MalformedFrame( - byte[] rawBytes, - String peer, - String reason -) {} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MessageDecoder.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MessageDecoder.java deleted file mode 100644 index ee57bedd..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/Jt808MessageDecoder.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import com.lingniu.ingest.api.spi.DecodeException; -import com.lingniu.ingest.api.spi.FrameDecoder; -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * JT808 完整帧(已反转义、含校验码,不含首尾 0x7e) → {@link Jt808Message}。 - * - *

    入参 {@link ByteBuffer} 的生命周期由调用方管理,本类只读取。 - */ -public final class Jt808MessageDecoder implements FrameDecoder { - - private static final int HEADER_LEN_2013 = 12; - private static final int HEADER_LEN_2019 = 16; // + phone 延长 4 字节 - - private final BodyParserRegistry registry; - - public Jt808MessageDecoder(BodyParserRegistry registry) { - this.registry = registry; - } - - @Override - public Jt808Message decode(ByteBuffer buffer) { - byte[] bytes = new byte[buffer.remaining()]; - buffer.get(bytes); - - if (bytes.length < HEADER_LEN_2013 + 1) { - throw new DecodeException("jt808 frame too short: " + bytes.length); - } - - // 校验码:从 msgId 开始到校验码前一字节 - byte expected = bytes[bytes.length - 1]; - if (!BccChecksum.verify(bytes, 0, bytes.length - 1, expected)) { - throw new DecodeException("jt808 XOR checksum mismatch"); - } - - int messageId = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); - int attrs = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); - int bodyLength = attrs & 0x03FF; - int encryptType = (attrs >> 10) & 0x07; - boolean subpacket = (attrs & 0x2000) != 0; - // JT/T 808-2019 通过属性 bit14 标识,终端手机号从 6B BCD 扩展到 10B BCD。 - boolean v2019 = (attrs & 0x4000) != 0; - - Jt808Header.ProtocolVersion version = v2019 - ? Jt808Header.ProtocolVersion.V2019 - : Jt808Header.ProtocolVersion.V2013; - int phoneLen = v2019 ? 10 : 6; - int headerLen = 4 /* msgId+attrs */ + (v2019 ? 1 : 0) /* protocol version */ + phoneLen + 2 /* serial */; - int phoneOffset = 4 + (v2019 ? 1 : 0); - - String phone = BcdCodec.decode(Arrays.copyOfRange(bytes, phoneOffset, phoneOffset + phoneLen)) - .replaceAll("^0+", ""); - int serial = ((bytes[phoneOffset + phoneLen] & 0xFF) << 8) - | (bytes[phoneOffset + phoneLen + 1] & 0xFF); - - int totalPkgs = 0, pkgSeq = 0; - int bodyStart = headerLen; - if (subpacket) { - // 分包头紧跟消息流水号之后;当前只暴露包总数/序号,重组由上游或后续模块处理。 - totalPkgs = ((bytes[bodyStart] & 0xFF) << 8) | (bytes[bodyStart + 1] & 0xFF); - pkgSeq = ((bytes[bodyStart + 2] & 0xFF) << 8) | (bytes[bodyStart + 3] & 0xFF); - bodyStart += 4; - } - - int bodyEnd = bodyStart + bodyLength; - if (bodyEnd > bytes.length - 1) { - throw new DecodeException("jt808 body length " + bodyLength + " exceeds frame"); - } - - Jt808Header header = new Jt808Header( - messageId, bodyLength, encryptType, subpacket, version, - phone, serial, totalPkgs, pkgSeq); - - ByteBuffer bodyBuf = ByteBuffer.wrap(bytes, bodyStart, bodyLength); - BodyParser parser = registry.find(messageId); - Jt808Body body; - if (parser == null) { - // 未实现的消息体保持 Raw,仍可进入 archive/event-history,避免协议扩展导致数据丢失。 - byte[] raw = new byte[bodyLength]; - bodyBuf.get(raw); - body = new Jt808Body.Raw(messageId, raw); - } else { - body = parser.parse(header, bodyBuf); - } - return new Jt808Message(header, body); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/AuthBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/AuthBodyParser.java deleted file mode 100644 index 4ca31857..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/AuthBodyParser.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class AuthBodyParser implements BodyParser { - - private static final Pattern IMEI = Pattern.compile("(?= 2 ? body.getShort() & 0xFFFF : 0; - int batchType = body.remaining() >= 1 ? body.get() & 0xFF : 0; - List locations = new ArrayList<>(count); - for (int i = 0; i < count && body.remaining() >= 2; i++) { - int len = body.getShort() & 0xFFFF; - if (len > body.remaining()) { - len = body.remaining(); - } - ByteBuffer slice = body.slice(body.position(), len); - Jt808Body parsed = locationParser.parse(header, slice); - if (parsed instanceof Jt808Body.Location location) { - locations.add(location); - } - body.position(body.position() + len); - } - return new Jt808Body.LocationBatch(batchType, List.copyOf(locations)); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/LocationBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/LocationBodyParser.java deleted file mode 100644 index de5da2b3..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/LocationBodyParser.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * 0x0200 位置信息汇报。 - * - *

    固定 28 字节 + 可选附加项。附加项按 JT/T 808 的 ID/长度/值 TLV 保留原始字节, - * 由上层统计或导出服务按配置解释。 - *

    - *   alarmFlag(4)
    - *   statusFlag(4)
    - *   latitude(4)   // 1/1000000 度
    - *   longitude(4)  // 1/1000000 度
    - *   altitudeM(2)
    - *   speed(2)      // 1/10 km/h
    - *   direction(2)
    - *   time(6 BCD, YYMMDDHHMMSS)
    - * 
    - */ -public final class LocationBodyParser implements BodyParser { - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_LOCATION; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer buf) { - long alarmFlag = buf.getInt() & 0xFFFFFFFFL; - long statusFlag = buf.getInt() & 0xFFFFFFFFL; - long latRaw = buf.getInt() & 0xFFFFFFFFL; - long lonRaw = buf.getInt() & 0xFFFFFFFFL; - int altitudeM = buf.getShort(); - double speed = (buf.getShort() & 0xFFFF) * 0.1; - int direction = buf.getShort() & 0xFFFF; - - byte[] timeBcd = new byte[6]; - buf.get(timeBcd); - String ts = BcdCodec.decode(timeBcd); - - LocalDateTime ldt = LocalDateTime.of( - 2000 + Integer.parseInt(ts.substring(0, 2)), - Integer.parseInt(ts.substring(2, 4)), - Integer.parseInt(ts.substring(4, 6)), - Integer.parseInt(ts.substring(6, 8)), - Integer.parseInt(ts.substring(8, 10)), - Integer.parseInt(ts.substring(10, 12))); - - Map extensions = parseExtensions(buf); - - return new Jt808Body.Location( - alarmFlag, statusFlag, - lonRaw / 1_000_000.0, latRaw / 1_000_000.0, - altitudeM, speed, direction, - ldt.atZone(ZoneId.of("Asia/Shanghai")).toInstant(), - extensions); - } - - private static Map parseExtensions(ByteBuffer buf) { - if (!buf.hasRemaining()) { - return Map.of(); - } - Map out = new LinkedHashMap<>(); - while (buf.remaining() >= 2) { - int id = buf.get() & 0xFF; - int len = buf.get() & 0xFF; - if (len > buf.remaining()) { - byte[] truncated = new byte[buf.remaining()]; - buf.get(truncated); - out.put(id, truncated); - break; - } - byte[] value = new byte[len]; - buf.get(value); - out.put(id, value); - } - if (buf.hasRemaining()) { - byte[] tail = new byte[buf.remaining()]; - buf.get(tail); - out.put(0xFFFF, tail); - } - return Map.copyOf(out); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaEventBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaEventBodyParser.java deleted file mode 100644 index 182d2fe8..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaEventBodyParser.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; - -public final class MediaEventBodyParser implements BodyParser { - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_MEDIA_EVENT; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - long mediaId = body.remaining() >= 4 ? body.getInt() & 0xFFFFFFFFL : 0; - int mediaType = body.hasRemaining() ? body.get() & 0xFF : 0; - int mediaFormat = body.hasRemaining() ? body.get() & 0xFF : 0; - int eventCode = body.hasRemaining() ? body.get() & 0xFF : 0; - int channelId = body.hasRemaining() ? body.get() & 0xFF : 0; - return new Jt808Body.MediaEvent(mediaId, mediaType, mediaFormat, eventCode, channelId); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaUploadBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaUploadBodyParser.java deleted file mode 100644 index 8a0c3869..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/MediaUploadBodyParser.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; - -public final class MediaUploadBodyParser implements BodyParser { - - private final LocationBodyParser locationParser = new LocationBodyParser(); - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_MEDIA_UPLOAD; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - long mediaId = body.remaining() >= 4 ? body.getInt() & 0xFFFFFFFFL : 0; - int mediaType = body.hasRemaining() ? body.get() & 0xFF : 0; - int mediaFormat = body.hasRemaining() ? body.get() & 0xFF : 0; - int eventCode = body.hasRemaining() ? body.get() & 0xFF : 0; - int channelId = body.hasRemaining() ? body.get() & 0xFF : 0; - Jt808Body.Location location = null; - if (body.remaining() >= 28) { - ByteBuffer loc = body.slice(body.position(), 28); - if (locationParser.parse(header, loc) instanceof Jt808Body.Location parsed) { - location = parsed; - } - body.position(body.position() + 28); - } - byte[] data = new byte[body.remaining()]; - body.get(data); - return new Jt808Body.MediaUpload(mediaId, mediaType, mediaFormat, eventCode, channelId, location, data); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/PassthroughBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/PassthroughBodyParser.java deleted file mode 100644 index 83c47848..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/PassthroughBodyParser.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; - -public final class PassthroughBodyParser implements BodyParser { - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_PASSTHROUGH; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - int type = body.hasRemaining() ? body.get() & 0xFF : 0; - byte[] data = new byte[body.remaining()]; - body.get(data); - return new Jt808Body.Passthrough(type, data); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/RegisterBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/RegisterBodyParser.java deleted file mode 100644 index bdd76ca4..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/RegisterBodyParser.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; - -/** - * 0x0100 终端注册。 - * - *

    2013 body 格式: - *

    - *   provinceId(2)
    - *   cityId(2)
    - *   makerId(5 ASCII)
    - *   deviceType(20 ASCII) -- 2011 版为 8
    - *   deviceId(7 ASCII)
    - *   plateColor(1)
    - *   plate(GBK, 剩余全部字节)
    - * 
    - */ -public final class RegisterBodyParser implements BodyParser { - - private static final Charset GBK = Charset.forName("GBK"); - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_REGISTER; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer buf) { - int province = buf.getShort() & 0xFFFF; - int city = buf.getShort() & 0xFFFF; - String maker = readAscii(buf, 5); - String deviceType = readAscii(buf, 20); - String deviceId = readAscii(buf, 7); - int plateColor = buf.get() & 0xFF; - byte[] plateBytes = new byte[buf.remaining()]; - buf.get(plateBytes); - String plate = new String(plateBytes, GBK).trim(); - return new Jt808Body.Register(province, city, maker, deviceType, deviceId, plateColor, plate); - } - - private static String readAscii(ByteBuffer buf, int len) { - byte[] b = new byte[len]; - buf.get(b); - return new String(b, StandardCharsets.US_ASCII).trim(); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalAttrsBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalAttrsBodyParser.java deleted file mode 100644 index b30c24ff..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalAttrsBodyParser.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -public final class TerminalAttrsBodyParser implements BodyParser { - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_ATTRS_REPORT; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - int terminalType = readU16(body); - String maker = readAscii(body, 5); - String model = readAscii(body, 20); - String terminalId = readAscii(body, 7); - String iccid = readBcd(body, 10); - String hardwareVersion = readAscii(body, 10); - String firmwareVersion = readAscii(body, 10); - int gnssProperty = body.hasRemaining() ? body.get() & 0xFF : 0; - int commProperty = body.hasRemaining() ? body.get() & 0xFF : 0; - if (body.hasRemaining()) { - body.position(body.limit()); - } - return new Jt808Body.TerminalAttrs( - terminalType, maker, model, terminalId, iccid, hardwareVersion, firmwareVersion, - gnssProperty, commProperty); - } - - private static int readU16(ByteBuffer body) { - return body.remaining() >= 2 ? body.getShort() & 0xFFFF : 0; - } - - private static String readAscii(ByteBuffer body, int len) { - int n = Math.min(len, body.remaining()); - byte[] bytes = new byte[n]; - body.get(bytes); - if (n < len && body.hasRemaining()) { - body.position(Math.min(body.limit(), body.position() + (len - n))); - } - return new String(bytes, StandardCharsets.US_ASCII).trim(); - } - - private static String readBcd(ByteBuffer body, int len) { - int n = Math.min(len, body.remaining()); - byte[] bytes = new byte[n]; - body.get(bytes); - return BcdCodec.decode(bytes).trim(); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalParamsReportBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalParamsReportBodyParser.java deleted file mode 100644 index a88e50e5..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/TerminalParamsReportBodyParser.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; -import java.util.LinkedHashMap; -import java.util.Map; - -public final class TerminalParamsReportBodyParser implements BodyParser { - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_PARAMS_REPORT; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - int responseSerialNo = body.remaining() >= 2 ? body.getShort() & 0xFFFF : 0; - int count = body.remaining() >= 1 ? body.get() & 0xFF : 0; - Map parameters = new LinkedHashMap<>(); - for (int i = 0; i < count && body.remaining() >= 5; i++) { - long paramId = body.getInt() & 0xFFFFFFFFL; - int len = body.get() & 0xFF; - if (len > body.remaining()) { - len = body.remaining(); - } - byte[] value = new byte[len]; - body.get(value); - parameters.put(paramId, value); - } - return new Jt808Body.ParamsReport(responseSerialNo, Map.copyOf(parameters)); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/UnregisterBodyParser.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/UnregisterBodyParser.java deleted file mode 100644 index 7b2d3a5f..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/codec/parser/UnregisterBodyParser.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec.parser; - -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.nio.ByteBuffer; - -public final class UnregisterBodyParser implements BodyParser { - - @Override - public int messageId() { - return Jt808MessageId.TERMINAL_UNREGISTER; - } - - @Override - public Jt808Body parse(Jt808Header header, ByteBuffer body) { - body.position(body.limit()); - return new Jt808Body.Unregister(); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808AutoConfiguration.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808AutoConfiguration.java deleted file mode 100644 index d19fef39..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808AutoConfiguration.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.config; - -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentityRegistry; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.protocol.jt808.codec.BodyParser; -import com.lingniu.ingest.protocol.jt808.codec.BodyParserRegistry; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.codec.parser.AuthBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.HeartbeatBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.LocationBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.LocationBatchBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.MediaEventBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.MediaUploadBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.PassthroughBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.RegisterBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.TerminalAttrsBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.TerminalParamsReportBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.UnregisterBodyParser; -import com.lingniu.ingest.protocol.jt808.downlink.Jt808CommandDispatcher; -import com.lingniu.ingest.protocol.jt808.handler.Jt808LocationHandler; -import com.lingniu.ingest.protocol.jt808.inbound.Jt808NettyServer; -import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper; -import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry; -import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests; -import com.lingniu.ingest.session.CommandDispatcher; -import com.lingniu.ingest.session.SessionStore; -import com.lingniu.ingest.session.config.SessionCoreAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -import java.util.List; - -@AutoConfiguration(before = SessionCoreAutoConfiguration.class) -@EnableConfigurationProperties(Jt808Properties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.jt808", name = "enabled", havingValue = "true") -public class Jt808AutoConfiguration { - - @Bean public BodyParser jt808RegisterBodyParser() { return new RegisterBodyParser(); } - @Bean public BodyParser jt808AuthBodyParser() { return new AuthBodyParser(); } - @Bean public BodyParser jt808LocationBodyParser() { return new LocationBodyParser(); } - @Bean public BodyParser jt808HeartbeatBodyParser() { return new HeartbeatBodyParser(); } - @Bean public BodyParser jt808UnregisterBodyParser() { return new UnregisterBodyParser(); } - @Bean public BodyParser jt808TerminalParamsReportBodyParser() { return new TerminalParamsReportBodyParser(); } - @Bean public BodyParser jt808TerminalAttrsBodyParser() { return new TerminalAttrsBodyParser(); } - @Bean public BodyParser jt808LocationBatchBodyParser() { return new LocationBatchBodyParser(); } - @Bean public BodyParser jt808MediaEventBodyParser() { return new MediaEventBodyParser(); } - @Bean public BodyParser jt808MediaUploadBodyParser() { return new MediaUploadBodyParser(); } - @Bean public BodyParser jt808PassthroughBodyParser() { return new PassthroughBodyParser(); } - - @Bean - @ConditionalOnMissingBean - public BodyParserRegistry jt808BodyParserRegistry(List parsers) { - return new BodyParserRegistry(parsers); - } - - @Bean - @ConditionalOnMissingBean - public Jt808MessageDecoder jt808MessageDecoder(BodyParserRegistry registry) { - return new Jt808MessageDecoder(registry); - } - - @Bean - @ConditionalOnMissingBean - public Jt808EventMapper jt808EventMapper(VehicleIdentityResolver identityResolver) { - return new Jt808EventMapper(identityResolver); - } - - @Bean - @ConditionalOnMissingBean - public Jt808LocationHandler jt808LocationHandler(Jt808EventMapper mapper) { - return new Jt808LocationHandler(mapper); - } - - @Bean - @ConditionalOnMissingBean - public Jt808ChannelRegistry jt808ChannelRegistry() { - return new Jt808ChannelRegistry(); - } - - @Bean - @ConditionalOnMissingBean - public Jt808PendingRequests jt808PendingRequests() { - return new Jt808PendingRequests(); - } - - /** - * JT808 模块启用时抢占 {@link CommandDispatcher} Bean。 - * 通过 {@code @AutoConfiguration(before = SessionCoreAutoConfiguration.class)} 确保本 Bean - * 先注册,session-core 的 {@code @ConditionalOnMissingBean} 会因此跳过 Noop 默认实现。 - */ - @Bean - @ConditionalOnMissingBean - public CommandDispatcher jt808CommandDispatcher(SessionStore sessions, - Jt808ChannelRegistry channelRegistry, - Jt808PendingRequests pendingRequests) { - return new Jt808CommandDispatcher(sessions, channelRegistry, pendingRequests); - } - - @Bean - @ConditionalOnMissingBean - public Jt808NettyServer jt808NettyServer(Jt808Properties props, - Jt808MessageDecoder decoder, - Dispatcher dispatcher, - SessionStore sessions, - VehicleIdentityRegistry identityRegistry, - VehicleIdentityResolver identityResolver, - Jt808ChannelRegistry channelRegistry, - Jt808PendingRequests pendingRequests) { - return new Jt808NettyServer(props.getPort(), props.getWorkerThreads(), - decoder, dispatcher, sessions, identityRegistry, identityResolver, - channelRegistry, pendingRequests); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808Properties.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808Properties.java deleted file mode 100644 index da28853d..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/config/Jt808Properties.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "lingniu.ingest.jt808") -public class Jt808Properties { - /** 是否启动 JT/T 808 TCP 监听。与 GB32960 独立,通常用于部标终端。 */ - private boolean enabled = false; - /** JT808 默认监听端口;不要与 32960 端口共用。 */ - private int port = 808; - /** Netty worker 线程数;0 表示使用框架默认值。 */ - private int workerThreads = 0; - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public int getPort() { return port; } - public void setPort(int port) { this.port = port; } - public int getWorkerThreads() { return workerThreads; } - public void setWorkerThreads(int workerThreads) { this.workerThreads = workerThreads; } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcher.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcher.java deleted file mode 100644 index 7ba34622..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcher.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.downlink; - -import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameEncoder; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry; -import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests; -import com.lingniu.ingest.session.CommandDispatcher; -import com.lingniu.ingest.session.DeviceSession; -import com.lingniu.ingest.session.SessionStore; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * JT808 侧的 {@link CommandDispatcher} 实现:把 session-core 的调用桥接到具体 Netty Channel。 - * - *

    {@link #notify(String, Object)} 异步下发后立即返回; - * {@link #request(String, Object, Class, Duration)} 通过 {@link Jt808PendingRequests} 等待设备应答。 - * - *

    入参 {@code command} 必须是 {@link Jt808Commands.DownlinkCommand},其他类型返回失败 future。 - */ -public final class Jt808CommandDispatcher implements CommandDispatcher { - - private static final Logger log = LoggerFactory.getLogger(Jt808CommandDispatcher.class); - - private final SessionStore sessions; - private final Jt808ChannelRegistry channelRegistry; - private final Jt808PendingRequests pendingRequests; - - public Jt808CommandDispatcher(SessionStore sessions, - Jt808ChannelRegistry channelRegistry, - Jt808PendingRequests pendingRequests) { - this.sessions = sessions; - this.channelRegistry = channelRegistry; - this.pendingRequests = pendingRequests; - } - - @Override - public CompletableFuture notify(String sessionId, Object command) { - CommandContext ctx; - try { - ctx = resolve(sessionId, command); - } catch (IllegalArgumentException e) { - return CompletableFuture.failedFuture(e); - } - return writeFrames(ctx, false); - } - - @Override - public CompletableFuture request(String sessionId, Object command, - Class responseType, Duration timeout) { - CommandContext ctx; - try { - ctx = resolve(sessionId, command); - } catch (IllegalArgumentException e) { - return CompletableFuture.failedFuture(e); - } - - CompletableFuture pending = pendingRequests.await(ctx.phone, ctx.serial); - writeFrames(ctx, true).whenComplete((unused, ex) -> { - if (ex != null) { - // 写 channel 失败时必须移除 pending,否则后续同 phone/serial 重用会匹配到旧 future。 - pendingRequests.cancel(ctx.phone, ctx.serial); - pending.completeExceptionally(ex); - } - }); - - return pending.orTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS) - .whenComplete((r, ex) -> { - // 超时/取消同样清理 pending,避免长期运行后内存泄漏。 - if (ex != null) pendingRequests.cancel(ctx.phone, ctx.serial); - }) - .thenApply(msg -> { - if (responseType.isInstance(msg)) return responseType.cast(msg); - throw new CompletionException(new ClassCastException( - "jt808 response type mismatch, expect=" + responseType.getSimpleName() - + " actual=" + msg.getClass().getSimpleName())); - }); - } - - private CompletableFuture writeFrames(CommandContext ctx, boolean request) { - var frames = Jt808FrameEncoder.encodeSubpackages( - ctx.cmd.messageId(), ctx.phone, ctx.serial, ctx.cmd.body()); - CompletableFuture cf = new CompletableFuture<>(); - AtomicInteger remaining = new AtomicInteger(frames.size()); - for (byte[] frame : frames) { - ctx.channel.writeAndFlush(Unpooled.wrappedBuffer(frame)).addListener(f -> { - if (!f.isSuccess()) { - if (request) { - pendingRequests.cancel(ctx.phone, ctx.serial); - } - cf.completeExceptionally(f.cause()); - return; - } - if (remaining.decrementAndGet() == 0) { - cf.complete(null); - } - }); - } - return cf; - } - - private CommandContext resolve(String sessionId, Object command) { - if (!(command instanceof Jt808Commands.DownlinkCommand cmd)) { - throw new IllegalArgumentException( - "expected Jt808Commands.DownlinkCommand, got " + command.getClass().getName()); - } - String phone = resolvePhone(sessionId); - Channel channel = channelRegistry.find(phone) - .orElseThrow(() -> new IllegalStateException("no active channel for phone=" + phone)); - int serial = channelRegistry.nextSerial(phone); - return new CommandContext(phone, serial, channel, cmd); - } - - private String resolvePhone(String sessionId) { - Optional s = sessions.findBySessionId(sessionId) - .or(() -> sessions.findByPhone(sessionId)) - .or(() -> sessions.findByVin(sessionId)); - // HTTP 调用方可以传 sessionId/VIN/phone;最终写 Netty Channel 必须落到 JT808 phone。 - return s.map(DeviceSession::phone) - .filter(p -> p != null && !p.isBlank()) - .orElse(sessionId); // 回落:直接把 sessionId 当 phone - } - - private record CommandContext(String phone, int serial, Channel channel, Jt808Commands.DownlinkCommand cmd) {} -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808Commands.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808Commands.java deleted file mode 100644 index d62b7a62..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808Commands.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.downlink; - -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.List; - -/** - * 常用下行命令 body 构造器。返回的是 body 字节,外层由 {@link com.lingniu.ingest.protocol.jt808.codec.Jt808FrameEncoder} 包装。 - */ -public final class Jt808Commands { - - private static final Charset GBK = Charset.forName("GBK"); - - private Jt808Commands() {} - - /** 0x8001 平台通用应答(ack 一条终端上行)。 */ - public static DownlinkCommand platformAck(int ackSerial, int ackMsgId, int result) { - ByteArrayOutputStream os = new ByteArrayOutputStream(5); - writeU16(os, ackSerial); - writeU16(os, ackMsgId); - os.write(result & 0xFF); - return new DownlinkCommand(Jt808MessageId.PLATFORM_GENERAL_RESPONSE, os.toByteArray()); - } - - /** 0x8100 注册应答(result=0 成功)。 */ - public static DownlinkCommand registerAck(int ackSerial, int result, String token) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeU16(os, ackSerial); - os.write(result & 0xFF); - if (result == 0 && token != null) { - byte[] tokenBytes = token.getBytes(StandardCharsets.US_ASCII); - os.write(tokenBytes, 0, tokenBytes.length); - } - return new DownlinkCommand(Jt808MessageId.PLATFORM_REGISTER_ACK, os.toByteArray()); - } - - /** 0x8201 位置信息查询。 */ - public static DownlinkCommand queryLocation() { - return new DownlinkCommand(Jt808MessageId.PLATFORM_QUERY_LOCATION, new byte[0]); - } - - /** 0x8104 查询终端参数(全量)。 */ - public static DownlinkCommand queryParameters() { - return new DownlinkCommand(Jt808MessageId.PLATFORM_QUERY_PARAMS, new byte[0]); - } - - /** 0x8107 查询终端属性。 */ - public static DownlinkCommand queryAttributes() { - return new DownlinkCommand(Jt808MessageId.PLATFORM_QUERY_ATTRS, new byte[0]); - } - - /** - * 0x8103 设置终端参数:参数项列表格式。 - * - *

    body 形如 {@code count(1) + N*paramItem},每个 paramItem 为 {@code paramId(4) + length(1) + value}。 - */ - public static DownlinkCommand setParameters(java.util.List items) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - os.write(items.size() & 0xFF); - for (ParamItem item : items) { - writeU32(os, item.paramId()); - os.write(item.value().length & 0xFF); - os.write(item.value(), 0, item.value().length); - } - return new DownlinkCommand(Jt808MessageId.PLATFORM_SET_PARAMS, os.toByteArray()); - } - - /** 0x8105 终端控制:命令字(1) + 命令参数(GBK)。 */ - public static DownlinkCommand terminalControl(int commandWord, String commandParams) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - os.write(commandWord & 0xFF); - if (commandParams != null) { - byte[] bytes = commandParams.getBytes(GBK); - os.write(bytes, 0, bytes.length); - } - return new DownlinkCommand(Jt808MessageId.PLATFORM_TERMINAL_CONTROL, os.toByteArray()); - } - - /** 0x8203 人工确认报警:报警消息流水号(WORD) + 报警类型(DWORD)。 */ - public static DownlinkCommand alarmAck(int responseSerialNo, long alarmType) { - ByteArrayOutputStream os = new ByteArrayOutputStream(6); - writeU16(os, responseSerialNo); - writeU32(os, alarmType); - return new DownlinkCommand(Jt808MessageId.PLATFORM_ALARM_ACK, os.toByteArray()); - } - - /** 0x8601 删除圆形区域:区域总数(BYTE) + N * 区域ID(DWORD)。 */ - public static DownlinkCommand deleteArea(List areaIds) { - if (areaIds.size() > 255) { - throw new IllegalArgumentException("jt808 delete-area supports at most 255 area ids"); - } - ByteArrayOutputStream os = new ByteArrayOutputStream(1 + areaIds.size() * 4); - os.write(areaIds.size() & 0xFF); - for (Long areaId : areaIds) { - writeU32(os, areaId == null ? 0 : areaId); - } - return new DownlinkCommand(Jt808MessageId.PLATFORM_DELETE_CIRCLE_AREA, os.toByteArray()); - } - - public record DownlinkCommand(int messageId, byte[] body) {} - - public record ParamItem(long paramId, byte[] value) {} - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandler.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandler.java deleted file mode 100644 index 83750860..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandler.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.annotation.AsyncBatch; -import com.lingniu.ingest.api.annotation.EventEmit; -import com.lingniu.ingest.api.annotation.MessageMapping; -import com.lingniu.ingest.api.annotation.ProtocolHandler; -import com.lingniu.ingest.api.annotation.RateLimited; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; - -import java.util.ArrayList; -import java.util.List; - -/** - * JT808 示例 Handler:演示注解驱动的路由与事件产出。 - * - *

    位置上报使用 {@code @AsyncBatch} 批量聚合(对标旧代码里的 4000/1s 批量落库)。 - * 带 {@code @AsyncBatch} 的方法签名约定为 {@code List}。 - */ -@ProtocolHandler(protocol = ProtocolId.JT808) -public class Jt808LocationHandler { - - private final Jt808EventMapper mapper; - - public Jt808LocationHandler(Jt808EventMapper mapper) { - this.mapper = mapper; - } - - @MessageMapping(command = { - Jt808MessageId.TERMINAL_LOCATION, - Jt808MessageId.TERMINAL_LOCATION_BATCH - }, desc = "位置信息汇报(批量)") - @RateLimited(perVin = 20) - @AsyncBatch(size = 4000, waitMs = 1000, poolSize = 2) - @EventEmit(VehicleEvent.Location.class) - public List onLocationBatch(List batch) { - List all = new ArrayList<>(batch.size()); - for (Jt808Message msg : batch) { - all.addAll(mapper.toEvents(msg)); - } - return all; - } - - @MessageMapping(command = {Jt808MessageId.TERMINAL_REGISTER, Jt808MessageId.TERMINAL_AUTH}, desc = "注册/鉴权") - @EventEmit(VehicleEvent.Login.class) - public List onLogin(Jt808Message msg) { - return mapper.toEvents(msg); - } - - @MessageMapping(command = { - Jt808MessageId.TERMINAL_UNREGISTER, - Jt808MessageId.TERMINAL_PARAMS_REPORT, - Jt808MessageId.TERMINAL_ATTRS_REPORT, - Jt808MessageId.TERMINAL_MEDIA_EVENT, - Jt808MessageId.TERMINAL_MEDIA_UPLOAD, - Jt808MessageId.TERMINAL_PASSTHROUGH - }, desc = "终端状态/属性/多媒体/透传") - @EventEmit({VehicleEvent.Logout.class, VehicleEvent.Login.class, VehicleEvent.MediaMeta.class, VehicleEvent.Passthrough.class}) - public List onExtended(Jt808Message msg) { - return mapper.toEvents(msg); - } - - @MessageMapping(command = Jt808MessageId.TERMINAL_HEARTBEAT, desc = "心跳") - @EventEmit(VehicleEvent.Heartbeat.class) - public List onHeartbeat(Jt808Message msg) { - return mapper.toEvents(msg); - } - - @MessageMapping(desc = "未解析 JT808 上行兜底透传") - @EventEmit(VehicleEvent.Passthrough.class) - public List onRaw(Jt808Message msg) { - if (!(msg.body() instanceof Jt808Body.Raw) && !(msg.body() instanceof Jt808Body.Malformed)) { - return List.of(); - } - return mapper.toEvents(msg); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java deleted file mode 100644 index f849f288..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandler.java +++ /dev/null @@ -1,547 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.inbound; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityRegistry; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameEncoder; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MalformedFrame; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808LocationAdditionalInfo; -import com.lingniu.ingest.protocol.jt808.model.Jt808LocationFlagInfo; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; -import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry; -import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests; -import com.lingniu.ingest.session.DeviceSession; -import com.lingniu.ingest.session.SessionStore; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.SimpleChannelInboundHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * JT808 Netty 入站处理器。职责: - *

      - *
    1. 解码 → 构造 RawFrame → 交给 {@link Dispatcher} - *
    2. 对 0x0100 注册 / 0x0102 鉴权 → 创建 / 更新 {@link DeviceSession} 并绑定 Channel - *
    3. 对 0x0001 终端通用应答 → 唤醒 {@link Jt808PendingRequests} 里等待的 future - *
    4. 对 0x0002 心跳 → 下发 0x8001 通用应答(若平台要求) - *
    5. 连接断开 → 清理 Channel 绑定 - *
    - */ -public class Jt808ChannelHandler extends SimpleChannelInboundHandler { - - private static final Logger log = LoggerFactory.getLogger(Jt808ChannelHandler.class); - private static final String PROCESSING_ERROR_PREFIX = "processing failed: "; - - private final Jt808MessageDecoder decoder; - private final Dispatcher dispatcher; - private final SessionStore sessions; - private final VehicleIdentityRegistry identityRegistry; - private final VehicleIdentityResolver identityResolver; - private final Jt808ChannelRegistry channelRegistry; - private final Jt808PendingRequests pendingRequests; - - public Jt808ChannelHandler(Jt808MessageDecoder decoder, Dispatcher dispatcher, - SessionStore sessions, VehicleIdentityRegistry identityRegistry, - Jt808ChannelRegistry channelRegistry, - Jt808PendingRequests pendingRequests) { - this(decoder, dispatcher, sessions, identityRegistry, - identityRegistry instanceof VehicleIdentityResolver resolver ? resolver : null, - channelRegistry, pendingRequests); - } - - public Jt808ChannelHandler(Jt808MessageDecoder decoder, Dispatcher dispatcher, - SessionStore sessions, VehicleIdentityRegistry identityRegistry, - VehicleIdentityResolver identityResolver, - Jt808ChannelRegistry channelRegistry, - Jt808PendingRequests pendingRequests) { - this.decoder = decoder; - this.dispatcher = dispatcher; - this.sessions = sessions; - this.identityRegistry = identityRegistry; - this.identityResolver = identityResolver; - this.channelRegistry = channelRegistry; - this.pendingRequests = pendingRequests; - } - - @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { - if (msg instanceof byte[] frame) { - handleDecodedFrame(ctx, frame); - } else if (msg instanceof Jt808MalformedFrame malformed) { - dispatchFrameError(ctx, malformed); - } else { - ctx.fireChannelRead(msg); - } - } - - private void handleDecodedFrame(ChannelHandlerContext ctx, byte[] frame) { - Jt808Message msg; - try { - msg = decoder.decode(ByteBuffer.wrap(frame)); - } catch (Exception e) { - log.warn("[jt808] decode failed peer={} len={} error={}", addr(ctx), frame.length, e.getMessage()); - log.debug("[jt808] decode failed stack peer={}", addr(ctx), e); - dispatchMalformed(ctx, frame, e.getMessage(), false); - return; - } - - try { - processDecodedFrame(ctx, frame, msg); - } catch (RuntimeException e) { - // 解码成功但业务处理失败时仍派发一条 malformed RawFrame, - // 保证原始帧能被 archive、Kafka 和历史索引留痕,方便事后排查。 - log.warn("[jt808] processing failed peer={} phone={} msgId=0x{} error={}", - addr(ctx), msg.header().phone(), Integer.toHexString(msg.header().messageId()), e.getMessage()); - log.debug("[jt808] processing failed stack peer={}", addr(ctx), e); - dispatchProcessingError(ctx, frame, msg, e.getMessage()); - } - } - - private void processDecodedFrame(ChannelHandlerContext ctx, byte[] frame, Jt808Message msg) { - // ====== 会话维护 & 同步应答匹配 ====== - String phone = msg.header().phone(); - IdentityResolution identity = resolveIdentity(msg); - if (msg.body() instanceof Jt808Body.Register reg) { - identity = registerIdentity(msg, reg, identity); - } - byte[] registerAck = null; - switch (msg.header().messageId()) { - case Jt808MessageId.TERMINAL_REGISTER -> { - DeviceSession session = upsertSession(ctx, msg, identity.identity()); - channelRegistry.bind(phone, ctx.channel()); - var command = Jt808Commands.registerAck(msg.header().serialNo(), 0, session.token()); - registerAck = Jt808FrameEncoder.encode( - command.messageId(), - phone, - channelRegistry.nextSerial(phone), - command.body()); - } - case Jt808MessageId.TERMINAL_AUTH -> { - upsertSession(ctx, msg, identity.identity()); - channelRegistry.bind(phone, ctx.channel()); - ackTerminal(ctx, phone, msg.header().serialNo(), msg.header().messageId(), 0); - } - case Jt808MessageId.TERMINAL_HEARTBEAT -> { - channelRegistry.bind(phone, ctx.channel()); - ackTerminal(ctx, phone, msg.header().serialNo(), msg.header().messageId(), 0); - } - case Jt808MessageId.TERMINAL_GENERAL_RESPONSE -> { - // body: ackSerial(2) + ackMsgId(2) + result(1) - if (msg.body() instanceof Jt808Body.Raw raw && raw.bytes().length >= 5) { - int ackSerial = ((raw.bytes()[0] & 0xFF) << 8) | (raw.bytes()[1] & 0xFF); - pendingRequests.complete(phone, ackSerial, msg); - } - } - default -> { - // 定位/其他上行:也可能是对同步命令的应答 - pendingRequests.complete(phone, msg.header().serialNo(), msg); - } - } - - // ====== 派发到 Dispatcher ====== - Map meta = new HashMap<>(32); - meta.put("vin", identity.identity().vin()); - meta.put("phone", phone); - meta.put("identityResolved", Boolean.toString(identity.identity().resolved())); - meta.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - meta.put("identityError", "true"); - meta.put("identityErrorMessage", identity.errorMessage()); - } - meta.put("seq", Integer.toString(msg.header().serialNo())); - meta.put("messageId", "0x" + Jt808MessageId.hex(msg.header().messageId())); - meta.put("messageName", Jt808MessageId.nameOf(msg.header().messageId())); - meta.put("reportType", Jt808MessageId.reportTypeOf(msg.header().messageId())); - putBodyMetadata(meta, msg.body()); - meta.put("peer", addr(ctx)); - RawFrame rf = new RawFrame( - ProtocolId.JT808, - msg.header().messageId(), - 0, - msg, - frame, - meta, - Instant.now()); - if (registerAck != null) { - byte[] ack = registerAck; - dispatcher.dispatchAndAwait(rf) - .thenRun(() -> ctx.writeAndFlush(Unpooled.wrappedBuffer(ack))) - .exceptionally(ex -> { - log.warn("[jt808] register ack withheld because dispatch durability failed peer={} phone={} seq={} error={}", - addr(ctx), phone, msg.header().serialNo(), - ex.getMessage() == null ? ex.getClass().getSimpleName() : ex.getMessage()); - return null; - }); - return; - } - dispatcher.dispatch(rf); - } - - private IdentityResolution resolveIdentity(Jt808Message msg) { - String phone = msg.header().phone(); - if (identityResolver == null) { - // 没有绑定表时不伪造 VIN;phone 会保留在 metadata/session 中用于下行定位。 - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.FALLBACK_PHONE), - null); - } - try { - // 优先把 JT808 phone/device/plate 映射到内部 VIN,后续所有事件都用内部 VIN 做主键。 - return new IdentityResolution( - normalizeUnresolvedIdentity(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, - "", - phone, - deviceId(msg.body()), - plate(msg.body())))), - null); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private static VehicleIdentity normalizeUnresolvedIdentity(VehicleIdentity identity) { - if (identity == null) { - return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); - } - if (identity.resolved()) { - return identity; - } - return new VehicleIdentity("unknown", false, identity.source()); - } - - private IdentityResolution registerIdentity(Jt808Message msg, - Jt808Body.Register reg, - IdentityResolution current) { - VehicleIdentity currentIdentity = current.identity(); - boolean alreadyResolved = currentIdentity != null && currentIdentity.resolved(); - String vin = alreadyResolved ? currentIdentity.vin() : generatedInternalVin(msg.header().phone(), reg); - identityRegistry.bind(new VehicleIdentityBinding( - ProtocolId.JT808, - vin, - msg.header().phone(), - reg.deviceId(), - reg.plate(), - Map.of( - "province", Integer.toString(reg.province()), - "city", Integer.toString(reg.city()), - "maker", reg.maker(), - "deviceType", reg.deviceType(), - "plateColor", Integer.toString(reg.plateColor())))); - VehicleIdentitySource source = alreadyResolved ? currentIdentity.source() : VehicleIdentitySource.REGISTERED; - return new IdentityResolution(new VehicleIdentity(vin, true, source), current.errorMessage()); - } - - private void dispatchFrameError(ChannelHandlerContext ctx, Jt808MalformedFrame malformed) { - String peer = malformed.peer() == null || malformed.peer().isBlank() ? addr(ctx) : malformed.peer(); - dispatchMalformed(peer, malformed.rawBytes(), malformed.reason(), true); - } - - private void dispatchMalformed(ChannelHandlerContext ctx, byte[] frame, String errorMessage, boolean frameError) { - dispatchMalformed(addr(ctx), frame, errorMessage, frameError); - } - - private void dispatchMalformed(String peer, byte[] frame, String errorMessage, boolean frameError) { - String message = errorMessage == null ? "" : errorMessage; - // 无法解出 header 的坏帧也进入 Dispatcher,后续 archive sink 可以保留 raw bytes。 - Jt808Message malformed = new Jt808Message( - new Jt808Header(0, frame == null ? 0 : frame.length, 0, false, - Jt808Header.ProtocolVersion.V2013, "unknown", 0, 0, 0), - new Jt808Body.Malformed(frame == null ? new byte[0] : frame, peer, message)); - Map meta = new HashMap<>(8); - meta.put("vin", "unknown"); - meta.put("peer", peer); - meta.put("identityResolved", "false"); - meta.put("identitySource", VehicleIdentitySource.UNKNOWN.name()); - if (frameError) { - meta.put("frameError", "true"); - meta.put("frameErrorMessage", message); - } else { - meta.put("parseError", "true"); - meta.put("parseErrorMessage", message); - } - putBodyMetadata(meta, malformed.body()); - dispatcher.dispatch(new RawFrame( - ProtocolId.JT808, - 0, - 0, - malformed, - frame == null ? new byte[0] : frame, - meta, - Instant.now())); - } - - private void dispatchProcessingError(ChannelHandlerContext ctx, byte[] frame, Jt808Message msg, String errorMessage) { - String message = errorMessage == null ? "" : errorMessage; - Jt808Message failed = new Jt808Message( - new Jt808Header(0, frame == null ? 0 : frame.length, 0, false, - msg.header().version(), msg.header().phone(), msg.header().serialNo(), 0, 0), - new Jt808Body.Malformed(frame == null ? new byte[0] : frame, addr(ctx), - PROCESSING_ERROR_PREFIX + message)); - Map meta = new HashMap<>(16); - meta.put("vin", "unknown"); - meta.put("phone", msg.header().phone()); - meta.put("peer", addr(ctx)); - meta.put("seq", Integer.toString(msg.header().serialNo())); - meta.put("messageId", "0x" + Jt808MessageId.hex(msg.header().messageId())); - meta.put("messageName", Jt808MessageId.nameOf(msg.header().messageId())); - meta.put("reportType", Jt808MessageId.reportTypeOf(msg.header().messageId())); - meta.put("identityResolved", "false"); - meta.put("identitySource", VehicleIdentitySource.UNKNOWN.name()); - meta.put("processingError", "true"); - meta.put("processingErrorMessage", message); - putBodyMetadata(meta, failed.body()); - dispatcher.dispatch(new RawFrame( - ProtocolId.JT808, - 0, - 0, - failed, - frame == null ? new byte[0] : frame, - meta, - Instant.now())); - } - - private DeviceSession upsertSession(ChannelHandlerContext ctx, Jt808Message msg, VehicleIdentity identity) { - String phone = msg.header().phone(); - String sessionId = "jt808-" + phone; - DeviceSession session = sessions.findBySessionId(sessionId) - .map(s -> s.touch(Instant.now())) - .orElseGet(() -> new DeviceSession( - sessionId, ProtocolId.JT808, identity.vin(), phone, - generateToken(phone), - addr(ctx), - Instant.now(), Instant.now(), - Map.of("protocolVersion", msg.header().version().name()))); - session = session.withVin(identity.vin()); - sessions.put(session); - return session; - } - - private static String deviceId(Jt808Body body) { - if (body instanceof Jt808Body.Register reg) { - return reg.deviceId(); - } - if (body instanceof Jt808Body.Auth auth) { - return auth.imei(); - } - return ""; - } - - private static String plate(Jt808Body body) { - return body instanceof Jt808Body.Register reg ? reg.plate() : ""; - } - - private static void putBodyMetadata(Map meta, Jt808Body body) { - switch (body) { - case Jt808Body.Register reg -> { - put(meta, "body.province", reg.province()); - put(meta, "body.city", reg.city()); - put(meta, "body.maker", reg.maker()); - put(meta, "body.deviceType", reg.deviceType()); - put(meta, "body.deviceId", reg.deviceId()); - put(meta, "body.plateColor", reg.plateColor()); - put(meta, "body.plate", reg.plate()); - } - case Jt808Body.Auth auth -> { - put(meta, "body.token", auth.token()); - put(meta, "body.imei", auth.imei()); - put(meta, "body.softwareVersion", auth.softwareVersion()); - } - case Jt808Body.Location location -> putLocationMetadata(meta, "body.", location); - case Jt808Body.LocationBatch batch -> { - put(meta, "body.batchType", batch.batchType()); - put(meta, "body.locationCount", batch.locations() == null ? 0 : batch.locations().size()); - } - case Jt808Body.ParamsReport report -> { - put(meta, "body.responseSerialNo", report.responseSerialNo()); - put(meta, "body.parameterCount", report.parameters() == null ? 0 : report.parameters().size()); - if (report.parameters() != null) { - report.parameters().forEach((id, bytes) -> - put(meta, "body.param.0x" + String.format("%08X", id), toHex(bytes))); - } - } - case Jt808Body.TerminalAttrs attrs -> { - put(meta, "body.terminalType", attrs.terminalType()); - put(meta, "body.maker", attrs.maker()); - put(meta, "body.terminalModel", attrs.terminalModel()); - put(meta, "body.terminalId", attrs.terminalId()); - put(meta, "body.iccid", attrs.iccid()); - put(meta, "body.hardwareVersion", attrs.hardwareVersion()); - put(meta, "body.firmwareVersion", attrs.firmwareVersion()); - put(meta, "body.gnssProperty", attrs.gnssProperty()); - put(meta, "body.commProperty", attrs.commProperty()); - } - case Jt808Body.MediaEvent event -> { - put(meta, "body.mediaId", event.mediaId()); - put(meta, "body.mediaType", event.mediaType()); - put(meta, "body.mediaFormat", event.mediaFormat()); - put(meta, "body.eventCode", event.eventCode()); - put(meta, "body.channelId", event.channelId()); - } - case Jt808Body.MediaUpload upload -> { - put(meta, "body.mediaId", upload.mediaId()); - put(meta, "body.mediaType", upload.mediaType()); - put(meta, "body.mediaFormat", upload.mediaFormat()); - put(meta, "body.eventCode", upload.eventCode()); - put(meta, "body.channelId", upload.channelId()); - put(meta, "body.dataSizeBytes", upload.data() == null ? 0 : upload.data().length); - if (upload.location() != null) { - putLocationMetadata(meta, "body.location.", upload.location()); - } - } - case Jt808Body.MediaAttributes attrs -> { - put(meta, "body.inputAudioEncoding", attrs.inputAudioEncoding()); - put(meta, "body.inputAudioChannels", attrs.inputAudioChannels()); - put(meta, "body.inputAudioSampleRate", attrs.inputAudioSampleRate()); - put(meta, "body.inputAudioBitDepth", attrs.inputAudioBitDepth()); - put(meta, "body.audioFrameLength", attrs.audioFrameLength()); - put(meta, "body.audioOutputSupported", attrs.audioOutputSupported()); - put(meta, "body.videoEncoding", attrs.videoEncoding()); - put(meta, "body.maxAudioChannels", attrs.maxAudioChannels()); - put(meta, "body.maxVideoChannels", attrs.maxVideoChannels()); - put(meta, "body.rawSizeBytes", attrs.raw() == null ? 0 : attrs.raw().length); - } - case Jt808Body.FileUploadComplete complete -> { - put(meta, "body.responseSerialNo", complete.responseSerialNo()); - put(meta, "body.result", complete.result()); - put(meta, "body.rawSizeBytes", complete.raw() == null ? 0 : complete.raw().length); - } - case Jt808Body.FileList files -> { - put(meta, "body.responseSerialNo", files.responseSerialNo()); - put(meta, "body.fileCount", files.fileCount()); - put(meta, "body.rawSizeBytes", files.raw() == null ? 0 : files.raw().length); - } - case Jt808Body.PassengerVolume volume -> { - put(meta, "body.channelId", volume.channelId()); - put(meta, "body.startTime", volume.startTime()); - put(meta, "body.endTime", volume.endTime()); - put(meta, "body.passengerGetOn", volume.passengerGetOn()); - put(meta, "body.passengerGetOff", volume.passengerGetOff()); - put(meta, "body.rawSizeBytes", volume.raw() == null ? 0 : volume.raw().length); - } - case Jt808Body.Passthrough passthrough -> { - put(meta, "body.passthroughType", "0x" + String.format("%02X", passthrough.passthroughType())); - put(meta, "body.dataSizeBytes", passthrough.data() == null ? 0 : passthrough.data().length); - put(meta, "body.dataHex", toHex(passthrough.data())); - } - case Jt808Body.Raw raw -> { - put(meta, "body.rawMessageId", "0x" + Jt808MessageId.hex(raw.messageId())); - put(meta, "body.rawSizeBytes", raw.bytes() == null ? 0 : raw.bytes().length); - put(meta, "body.rawHex", toHex(raw.bytes())); - } - case Jt808Body.Malformed malformed -> { - put(meta, "body.peer", malformed.peer()); - put(meta, "body.errorMessage", malformed.errorMessage()); - put(meta, "body.rawSizeBytes", malformed.bytes() == null ? 0 : malformed.bytes().length); - } - case Jt808Body.Heartbeat ignored -> { - } - case Jt808Body.Unregister ignored -> { - } - } - } - - private static void putLocationMetadata(Map meta, String prefix, Jt808Body.Location location) { - put(meta, prefix + "alarmFlag", location.alarmFlag()); - put(meta, prefix + "statusFlag", location.statusFlag()); - meta.putAll(Jt808LocationFlagInfo.decodeStatus(location.statusFlag(), prefix + "status.")); - meta.putAll(Jt808LocationFlagInfo.decodeAlarm(location.alarmFlag(), prefix + "alarm.")); - put(meta, prefix + "longitude", location.longitude()); - put(meta, prefix + "latitude", location.latitude()); - put(meta, prefix + "altitudeM", location.altitudeM()); - put(meta, prefix + "speedKmh", location.speedKmh()); - put(meta, prefix + "directionDeg", location.directionDeg()); - put(meta, prefix + "deviceTime", location.deviceTime()); - if (location.extensionItems() != null) { - location.extensionItems().forEach((id, bytes) -> - put(meta, prefix + "extra.0x" + String.format("%02X", id), toHex(bytes))); - meta.putAll(Jt808LocationAdditionalInfo.decode(location.extensionItems(), prefix + "extra.")); - } - } - - private static void put(Map meta, String key, Object value) { - if (value != null) { - meta.put(key, String.valueOf(value)); - } - } - - private static String toHex(byte[] bytes) { - if (bytes == null || bytes.length == 0) { - return ""; - } - StringBuilder out = new StringBuilder(bytes.length * 2); - for (byte b : bytes) { - out.append(String.format("%02X", b & 0xFF)); - } - return out.toString(); - } - - private void ackTerminal(ChannelHandlerContext ctx, String phone, int ackSerial, int ackMsgId, int result) { - var cmd = Jt808Commands.platformAck(ackSerial, ackMsgId, result); - byte[] frame = Jt808FrameEncoder.encode(cmd.messageId(), phone, channelRegistry.nextSerial(phone), cmd.body()); - ctx.writeAndFlush(Unpooled.wrappedBuffer(frame)); - } - - private static String generateToken(String phone) { - return UUID.nameUUIDFromBytes(("jt808:" + phone).getBytes()).toString().replace("-", "").substring(0, 16); - } - - private static String generatedInternalVin(String phone, Jt808Body.Register reg) { - String material = String.join("|", phone, reg.deviceId(), reg.plate(), reg.maker(), reg.deviceType()); - try { - byte[] digest = MessageDigest.getInstance("SHA-256").digest(material.getBytes(StandardCharsets.UTF_8)); - StringBuilder suffix = new StringBuilder(12); - for (int i = 0; i < digest.length && suffix.length() < 12; i++) { - suffix.append(String.format("%02X", digest[i] & 0xFF)); - } - return "JT808" + suffix.substring(0, 12); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 not available", e); - } - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) { - channelRegistry.unbind(ctx.channel()) - .ifPresent(phone -> sessions.remove("jt808-" + phone)); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - log.warn("[jt808] channel error peer={}", addr(ctx), cause); - ctx.close(); - } - - private static String addr(ChannelHandlerContext ctx) { - if (ctx.channel().remoteAddress() instanceof InetSocketAddress i) { - return i.getAddress().getHostAddress() + ":" + i.getPort(); - } - return "unknown"; - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) {} -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808NettyServer.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808NettyServer.java deleted file mode 100644 index f7d63a1a..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808NettyServer.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.inbound; - -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.identity.VehicleIdentityRegistry; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.protocol.jt808.codec.Jt808FrameDecoder; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry; -import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests; -import com.lingniu.ingest.session.SessionStore; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.SocketChannel; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.InitializingBean; - -import java.util.concurrent.ThreadFactory; - -public class Jt808NettyServer implements InitializingBean, DisposableBean { - - private static final Logger log = LoggerFactory.getLogger(Jt808NettyServer.class); - - private final int port; - private final int workerThreads; - private final Jt808MessageDecoder messageDecoder; - private final Dispatcher dispatcher; - private final SessionStore sessions; - private final VehicleIdentityRegistry identityRegistry; - private final VehicleIdentityResolver identityResolver; - private final Jt808ChannelRegistry channelRegistry; - private final Jt808PendingRequests pendingRequests; - - private EventLoopGroup boss; - private EventLoopGroup workers; - private Channel serverChannel; - - public Jt808NettyServer(int port, int workerThreads, - Jt808MessageDecoder messageDecoder, Dispatcher dispatcher, - SessionStore sessions, - VehicleIdentityRegistry identityRegistry, - VehicleIdentityResolver identityResolver, - Jt808ChannelRegistry channelRegistry, - Jt808PendingRequests pendingRequests) { - this.port = port; - this.workerThreads = workerThreads; - this.messageDecoder = messageDecoder; - this.dispatcher = dispatcher; - this.sessions = sessions; - this.identityRegistry = identityRegistry; - this.identityResolver = identityResolver; - this.channelRegistry = channelRegistry; - this.pendingRequests = pendingRequests; - } - - @Override - public void afterPropertiesSet() throws Exception { - ThreadFactory bossTf = r -> new Thread(r, "jt808-boss"); - ThreadFactory wkTf = r -> new Thread(r, "jt808-worker"); - this.boss = new NioEventLoopGroup(1, bossTf); - this.workers = new NioEventLoopGroup( - workerThreads == 0 ? Runtime.getRuntime().availableProcessors() * 2 : workerThreads, - wkTf); - - ServerBootstrap b = new ServerBootstrap(); - b.group(boss, workers) - .channel(NioServerSocketChannel.class) - .option(ChannelOption.SO_BACKLOG, 1024) - .childOption(ChannelOption.TCP_NODELAY, true) - .childOption(ChannelOption.SO_KEEPALIVE, true) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(SocketChannel ch) { - ch.pipeline() - .addLast("frame", new Jt808FrameDecoder()) - .addLast("dispatch", new Jt808ChannelHandler( - messageDecoder, dispatcher, sessions, - identityRegistry, identityResolver, - channelRegistry, pendingRequests)); - } - }); - this.serverChannel = b.bind(port).sync().channel(); - log.info("[jt808] Netty server listening on :{}", port); - } - - @Override - public void destroy() { - try { - if (serverChannel != null) serverChannel.close().sync(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - if (boss != null) boss.shutdownGracefully(); - if (workers != null) workers.shutdownGracefully(); - log.info("[jt808] Netty server stopped"); - } - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java deleted file mode 100644 index d336762b..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapper.java +++ /dev/null @@ -1,379 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.mapper; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.LocationPayload; -import com.lingniu.ingest.api.event.TelemetryMetadataValues; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.spi.EventMapper; -import com.lingniu.ingest.identity.VehicleIdentity; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentityResolver; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808LocationAdditionalInfo; -import com.lingniu.ingest.protocol.jt808.model.Jt808LocationFlagInfo; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; - -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -/** - * JT808 消息 → 领域事件映射。sealed body 允许 switch 穷尽处理。 - * - *

    备注:JT808 里没有强制的 VIN 字段,事件 VIN 统一通过 {@link VehicleIdentityResolver} - * 解析;未绑定时才降级为终端手机号,metadata 同步保留内部 vin 与外部 phone。 - */ -public final class Jt808EventMapper implements EventMapper { - - private static final String PROCESSING_ERROR_PREFIX = "processing failed: "; - - private final VehicleIdentityResolver identityResolver; - - public Jt808EventMapper(VehicleIdentityResolver identityResolver) { - this.identityResolver = identityResolver; - } - - @Override - public List toEvents(Jt808Message message) { - var header = message.header(); - Instant ingestTime = Instant.now(); - IdentityResolution identity = resolveIdentity(message); - Map meta = baseMeta(header, identity); - String vin = identity.vin(); - - return switch (message.body()) { - case Jt808Body.Location loc -> List.of(new VehicleEvent.Location( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, loc.deviceTime(), ingestTime, null, locationMeta(meta, loc), - new LocationPayload(loc.longitude(), loc.latitude(), - loc.altitudeM(), loc.speedKmh(), loc.directionDeg(), - loc.alarmFlag(), loc.statusFlag(), totalMileageKm(loc)))); - - case Jt808Body.Register reg -> List.of(new VehicleEvent.Login( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, registerMeta(meta, reg), - reg.deviceId(), header.version().name())); - - case Jt808Body.Auth auth -> List.of(new VehicleEvent.Login( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, meta, - auth.imei() == null ? "" : auth.imei(), header.version().name())); - - case Jt808Body.Heartbeat _ -> List.of(new VehicleEvent.Heartbeat( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, meta)); - - case Jt808Body.Unregister _ -> List.of(new VehicleEvent.Logout( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, meta)); - - case Jt808Body.LocationBatch batch -> locationBatchEvents(vin, meta, batch, ingestTime); - - case Jt808Body.ParamsReport params -> List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(meta, "paramsCount", Integer.toString(params.parameters().size())), - header.messageId(), - encodeParamsSummary(params))); - - case Jt808Body.TerminalAttrs attrs -> List.of(new VehicleEvent.Login( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(meta, "terminalId", attrs.terminalId()), - attrs.iccid(), header.version().name())); - - case Jt808Body.MediaEvent media -> List.of(new VehicleEvent.MediaMeta( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(meta, "mediaEventCode", Integer.toString(media.eventCode())), - Long.toString(media.mediaId()), - mediaType(media.mediaType(), media.mediaFormat(), media.channelId()), - 0, - "")); - - case Jt808Body.MediaUpload media -> mediaUploadEvents(vin, meta, media, ingestTime); - - case Jt808Body.MediaAttributes attrs -> List.of(new VehicleEvent.MediaMeta( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(withMeta(meta, "jt1078Message", "mediaAttributes"), - "maxVideoChannels", Integer.toString(attrs.maxVideoChannels())), - "jt1078-attrs-" + header.phone(), - mediaAttributesType(attrs), - attrs.raw() == null ? 0 : attrs.raw().length, - "")); - - case Jt808Body.FileUploadComplete done -> List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(withMeta(withMeta(meta, "jt1078Message", "fileUploadComplete"), - "responseSerialNo", Integer.toString(done.responseSerialNo())), - "uploadResult", Integer.toString(done.result())), - header.messageId(), - done.raw() == null ? new byte[0] : done.raw())); - - case Jt808Body.FileList list -> List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(withMeta(withMeta(meta, "jt1078Message", "fileList"), - "responseSerialNo", Integer.toString(list.responseSerialNo())), - "fileCount", Long.toString(list.fileCount())), - header.messageId(), - list.raw() == null ? new byte[0] : list.raw())); - - case Jt808Body.PassengerVolume volume -> List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - passengerVolumeMeta(meta, volume), - header.messageId(), - volume.raw() == null ? new byte[0] : volume.raw())); - - case Jt808Body.Passthrough passthrough -> List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(meta, "passthroughType", "0x" + Integer.toHexString(passthrough.passthroughType())), - header.messageId(), - passthrough.data())); - - case Jt808Body.Raw raw -> List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(meta, "rawBody", "true"), - raw.messageId(), - raw.bytes())); - - case Jt808Body.Malformed malformed -> { - String errorMessage = malformed.errorMessage() == null ? "" : malformed.errorMessage(); - boolean processingError = isProcessingError(errorMessage); - boolean frameError = isFrameError(errorMessage); - String publicErrorMessage = processingError - ? errorMessage.substring(PROCESSING_ERROR_PREFIX.length()) - : errorMessage; - Map errorMeta = withMeta(meta, - processingError ? "processingError" : frameError ? "frameError" : "parseError", "true"); - errorMeta = withMeta(errorMeta, "peer", malformed.peer() == null ? "" : malformed.peer()); - errorMeta = withMeta(errorMeta, - processingError ? "processingErrorMessage" : frameError ? "frameErrorMessage" : "parseErrorMessage", - publicErrorMessage); - yield List.of(new VehicleEvent.Passthrough( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - errorMeta, - header.messageId(), - malformed.bytes() == null ? new byte[0] : malformed.bytes())); - } - }; - } - - private IdentityResolution resolveIdentity(Jt808Message message) { - if (message.body() instanceof Jt808Body.Malformed) { - // 坏帧没有可信 header/body,统一 unknown,原始字节仍由 passthrough 事件保留。 - return IdentityResolution.ok(new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN)); - } - var header = message.header(); - try { - return IdentityResolution.ok(normalizeUnresolvedIdentity(identityResolver.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", header.phone(), deviceId(message.body()), plate(message.body()))))); - } catch (RuntimeException e) { - return new IdentityResolution( - new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN), - e.getMessage() == null ? "" : e.getMessage()); - } - } - - private static VehicleIdentity normalizeUnresolvedIdentity(VehicleIdentity identity) { - if (identity == null) { - return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); - } - if (identity.resolved()) { - return identity; - } - return new VehicleIdentity("unknown", false, identity.source()); - } - - private static Map baseMeta(Jt808Header header, IdentityResolution identity) { - java.util.HashMap meta = new java.util.HashMap<>(); - meta.put("messageId", "0x" + Integer.toHexString(header.messageId())); - meta.put("protocolVersion", header.version().name()); - meta.put("vin", identity.vin()); - meta.put("phone", header.phone()); - meta.put("identityResolved", Boolean.toString(identity.identity().resolved())); - meta.put("identitySource", identity.identity().source().name()); - if (identity.errorMessage() != null) { - meta.put("identityError", "true"); - meta.put("identityErrorMessage", identity.errorMessage()); - } - return Map.copyOf(meta); - } - - private static boolean isFrameError(String errorMessage) { - return errorMessage != null && (errorMessage.startsWith("jt808 missing frame boundary") - || errorMessage.startsWith("jt808 leading bytes before frame boundary") - || errorMessage.startsWith("jt808 unclosed frame too large")); - } - - private static boolean isProcessingError(String errorMessage) { - return errorMessage != null && errorMessage.startsWith(PROCESSING_ERROR_PREFIX); - } - - private static List locationBatchEvents(String vin, - Map meta, - Jt808Body.LocationBatch batch, - Instant ingestTime) { - List out = new ArrayList<>(batch.locations().size()); - Map batchMeta = withMeta(meta, "batchType", Integer.toString(batch.batchType())); - // 批量位置拆成多条 Location 事件,方便历史库按单点时间查询和导出。 - for (Jt808Body.Location loc : batch.locations()) { - out.add(locationEvent(vin, batchMeta, loc, ingestTime)); - } - return out; - } - - private static List mediaUploadEvents(String vin, - Map meta, - Jt808Body.MediaUpload media, - Instant ingestTime) { - List out = new ArrayList<>(2); - if (media.location() != null) { - // 多媒体上传消息内嵌位置时同时产出 Location,避免只看到媒体元数据看不到拍摄位置。 - out.add(locationEvent(vin, withMeta(meta, "mediaId", Long.toString(media.mediaId())), - media.location(), ingestTime)); - } - out.add(new VehicleEvent.MediaMeta( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, ingestTime, ingestTime, null, - withMeta(meta, "mediaEventCode", Integer.toString(media.eventCode())), - Long.toString(media.mediaId()), - mediaType(media.mediaType(), media.mediaFormat(), media.channelId()), - media.data() == null ? 0 : media.data().length, - "")); - return out; - } - - private static Map passengerVolumeMeta(Map meta, - Jt808Body.PassengerVolume volume) { - java.util.HashMap copy = new java.util.HashMap<>(meta); - copy.put("jt1078Message", "passengerVolume"); - copy.put("channelId", Integer.toString(volume.channelId())); - copy.put("startTime", volume.startTime() == null ? "" : volume.startTime()); - copy.put("endTime", volume.endTime() == null ? "" : volume.endTime()); - copy.put("passengerGetOn", Integer.toString(volume.passengerGetOn())); - copy.put("passengerGetOff", Integer.toString(volume.passengerGetOff())); - return Map.copyOf(copy); - } - - private static VehicleEvent.Location locationEvent(String vin, - Map meta, - Jt808Body.Location loc, - Instant ingestTime) { - return new VehicleEvent.Location( - UUID.randomUUID().toString(), - vin, ProtocolId.JT808, loc.deviceTime(), ingestTime, null, locationMeta(meta, loc), - new LocationPayload(loc.longitude(), loc.latitude(), - loc.altitudeM(), loc.speedKmh(), loc.directionDeg(), - loc.alarmFlag(), loc.statusFlag(), totalMileageKm(loc))); - } - - private static Map locationMeta(Map meta, Jt808Body.Location loc) { - java.util.HashMap copy = new java.util.HashMap<>(meta); - copy.putAll(Jt808LocationFlagInfo.decodeStatus(loc.statusFlag(), "jt808.status.")); - copy.putAll(Jt808LocationFlagInfo.decodeAlarm(loc.alarmFlag(), "jt808.alarm.")); - if (!loc.extensionItems().isEmpty()) { - loc.extensionItems().forEach((id, bytes) -> - copy.put("jt808.extra.0x" + String.format("%02X", id), hex(bytes))); - copy.putAll(Jt808LocationAdditionalInfo.decode(loc.extensionItems())); - Double totalMileageKm = totalMileageKm(loc); - if (totalMileageKm != null) { - copy.put("total_mileage_km", TelemetryMetadataValues.doubleValue(totalMileageKm)); - } - } - return Map.copyOf(copy); - } - - private static Map registerMeta(Map meta, Jt808Body.Register reg) { - java.util.HashMap copy = new java.util.HashMap<>(meta); - copy.put("jt808.register.province", Integer.toString(reg.province())); - copy.put("jt808.register.city", Integer.toString(reg.city())); - copy.put("jt808.register.maker", nullToEmpty(reg.maker())); - copy.put("jt808.register.deviceType", nullToEmpty(reg.deviceType())); - copy.put("jt808.register.deviceId", nullToEmpty(reg.deviceId())); - copy.put("jt808.register.plateColor", Integer.toString(reg.plateColor())); - copy.put("jt808.register.plate", nullToEmpty(reg.plate())); - return Map.copyOf(copy); - } - - private static Map withMeta(Map meta, String key, String value) { - java.util.HashMap copy = new java.util.HashMap<>(meta); - copy.put(key, value == null ? "" : value); - return Map.copyOf(copy); - } - - private static Double totalMileageKm(Jt808Body.Location loc) { - return Jt808LocationAdditionalInfo.mileageKm(loc.extensionItems()); - } - - private static String hex(byte[] bytes) { - if (bytes == null || bytes.length == 0) { - return ""; - } - StringBuilder sb = new StringBuilder(bytes.length * 2); - for (byte b : bytes) { - sb.append(Character.forDigit((b >> 4) & 0x0F, 16)); - sb.append(Character.forDigit(b & 0x0F, 16)); - } - return sb.toString().toUpperCase(java.util.Locale.ROOT); - } - - private static byte[] encodeParamsSummary(Jt808Body.ParamsReport params) { - return ("responseSerialNo=" + params.responseSerialNo() - + ",parameters=" + params.parameters().keySet()).getBytes(java.nio.charset.StandardCharsets.UTF_8); - } - - private static String mediaType(int mediaType, int mediaFormat, int channelId) { - return "type=" + mediaType + ",format=" + mediaFormat + ",channel=" + channelId; - } - - private static String mediaAttributesType(Jt808Body.MediaAttributes attrs) { - return "audioEncoding=" + attrs.inputAudioEncoding() - + ",audioChannels=" + attrs.inputAudioChannels() - + ",audioSampleRate=" + attrs.inputAudioSampleRate() - + ",audioBitDepth=" + attrs.inputAudioBitDepth() - + ",audioFrameLength=" + attrs.audioFrameLength() - + ",audioOutput=" + attrs.audioOutputSupported() - + ",videoEncoding=" + attrs.videoEncoding() - + ",maxAudioChannels=" + attrs.maxAudioChannels() - + ",maxVideoChannels=" + attrs.maxVideoChannels(); - } - - private static String deviceId(Jt808Body body) { - if (body instanceof Jt808Body.Register reg) { - return reg.deviceId(); - } - if (body instanceof Jt808Body.Auth auth) { - return auth.imei(); - } - return ""; - } - - private static String plate(Jt808Body body) { - return body instanceof Jt808Body.Register reg ? reg.plate() : ""; - } - - private static String nullToEmpty(String value) { - return value == null ? "" : value; - } - - private record IdentityResolution(VehicleIdentity identity, String errorMessage) { - private static IdentityResolution ok(VehicleIdentity identity) { - return new IdentityResolution(identity, null); - } - - private String vin() { - return identity.vin(); - } - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Body.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Body.java deleted file mode 100644 index e651c375..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Body.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.model; - -import java.time.Instant; -import java.util.List; -import java.util.Map; - -/** - * 已解析的消息体。sealed,新增消息体时必须同步新增子类型与 Parser。 - */ -public sealed interface Jt808Body - permits Jt808Body.Register, Jt808Body.Auth, Jt808Body.Location, - Jt808Body.LocationBatch, Jt808Body.Unregister, - Jt808Body.ParamsReport, Jt808Body.TerminalAttrs, - Jt808Body.MediaEvent, Jt808Body.MediaUpload, - Jt808Body.MediaAttributes, Jt808Body.FileUploadComplete, - Jt808Body.FileList, Jt808Body.PassengerVolume, - Jt808Body.Passthrough, Jt808Body.Heartbeat, Jt808Body.Raw, - Jt808Body.Malformed { - - /** 终端注册 0x0100。 */ - record Register( - int province, - int city, - String maker, - String deviceType, - String deviceId, - int plateColor, - String plate - ) implements Jt808Body {} - - /** 终端鉴权 0x0102。 */ - record Auth(String token, String imei, String softwareVersion) implements Jt808Body {} - - /** 位置信息汇报 0x0200。 */ - record Location( - long alarmFlag, - long statusFlag, - double longitude, - double latitude, - int altitudeM, - double speedKmh, - int directionDeg, - Instant deviceTime, - Map extensionItems - ) implements Jt808Body { - public Location(long alarmFlag, - long statusFlag, - double longitude, - double latitude, - int altitudeM, - double speedKmh, - int directionDeg, - Instant deviceTime) { - this(alarmFlag, statusFlag, longitude, latitude, altitudeM, speedKmh, - directionDeg, deviceTime, Map.of()); - } - - public Location { - extensionItems = extensionItems == null ? Map.of() : Map.copyOf(extensionItems); - } - } - - /** 位置信息批量上传 0x0704。 */ - record LocationBatch( - int batchType, - List locations - ) implements Jt808Body {} - - /** 终端注销 0x0003。 */ - record Unregister() implements Jt808Body {} - - /** 查询终端参数应答 0x0104。 */ - record ParamsReport( - int responseSerialNo, - Map parameters - ) implements Jt808Body {} - - /** 查询终端属性应答 0x0107。 */ - record TerminalAttrs( - int terminalType, - String maker, - String terminalModel, - String terminalId, - String iccid, - String hardwareVersion, - String firmwareVersion, - int gnssProperty, - int commProperty - ) implements Jt808Body {} - - /** 多媒体事件信息上传 0x0800。 */ - record MediaEvent( - long mediaId, - int mediaType, - int mediaFormat, - int eventCode, - int channelId - ) implements Jt808Body {} - - /** 多媒体数据上传 0x0801。 */ - record MediaUpload( - long mediaId, - int mediaType, - int mediaFormat, - int eventCode, - int channelId, - Location location, - byte[] data - ) implements Jt808Body {} - - /** JT/T 1078 终端上传音视频属性 0x1003。 */ - record MediaAttributes( - int inputAudioEncoding, - int inputAudioChannels, - int inputAudioSampleRate, - int inputAudioBitDepth, - int audioFrameLength, - boolean audioOutputSupported, - int videoEncoding, - int maxAudioChannels, - int maxVideoChannels, - byte[] raw - ) implements Jt808Body {} - - /** JT/T 1078 文件上传完成通知 0x1206。 */ - record FileUploadComplete( - int responseSerialNo, - int result, - byte[] raw - ) implements Jt808Body {} - - /** JT/T 1078 终端上传文件列表 0x1205。 */ - record FileList( - int responseSerialNo, - long fileCount, - byte[] raw - ) implements Jt808Body {} - - /** JT/T 1078 终端上传乘客流量 0x1005。 */ - record PassengerVolume( - int channelId, - String startTime, - String endTime, - int passengerGetOn, - int passengerGetOff, - byte[] raw - ) implements Jt808Body {} - - /** 数据上行透传 0x0900。 */ - record Passthrough( - int passthroughType, - byte[] data - ) implements Jt808Body {} - - /** 心跳 0x0002(无 body)。 */ - record Heartbeat() implements Jt808Body {} - - /** 未实现 Parser 的消息体,透传原始字节。 */ - record Raw(int messageId, byte[] bytes) implements Jt808Body {} - - /** 入站帧整体解析失败,保留原始字节便于冷存、查询和排障。 */ - record Malformed(byte[] bytes, String peer, String errorMessage) implements Jt808Body {} -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Header.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Header.java deleted file mode 100644 index e1ce252b..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Header.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.model; - -/** - * JT/T 808 消息头。 - * - * @param messageId 消息 ID - * @param bodyLength 消息体长度(从 attrs 低 10 位解出) - * @param encryptType 加密类型(0=不加密 / 1=RSA) - * @param subpacket 是否分包 - * @param version 协议版本(2013 或 2019) - * @param phone 终端手机号 / 设备识别号(2013:12 位,2019:20 位) - * @param serialNo 消息流水号 - * @param totalPackets 分包总数(不分包时为 0) - * @param packetSeq 分包序号(从 1 开始) - */ -public record Jt808Header( - int messageId, - int bodyLength, - int encryptType, - boolean subpacket, - ProtocolVersion version, - String phone, - int serialNo, - int totalPackets, - int packetSeq -) { - public enum ProtocolVersion { V2013, V2019 } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationAdditionalInfo.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationAdditionalInfo.java deleted file mode 100644 index 3e5edcb7..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationAdditionalInfo.java +++ /dev/null @@ -1,247 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.model; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public final class Jt808LocationAdditionalInfo { - - public static final String DEFAULT_PREFIX = "jt808.extra."; - - private static final String[] VEHICLE_SIGNAL_BITS = { - "low_beam", - "high_beam", - "right_turn", - "left_turn", - "brake", - "reverse", - "fog_lamp", - "position_lamp", - "horn", - "air_conditioner", - "neutral", - "retarder", - "abs", - "heater", - "clutch" - }; - - private static final String[] IO_STATUS_BITS = { - "deep_sleep", - "sleep" - }; - - private Jt808LocationAdditionalInfo() { - } - - public static Map decode(Map extensionItems) { - return decode(extensionItems, DEFAULT_PREFIX); - } - - public static Map decode(Map extensionItems, String prefix) { - if (extensionItems == null || extensionItems.isEmpty()) { - return Map.of(); - } - String p = prefix == null ? "" : prefix; - Map out = new LinkedHashMap<>(); - extensionItems.forEach((id, bytes) -> decodeItem(out, p, id == null ? -1 : id, bytes)); - return Map.copyOf(out); - } - - public static Double mileageKm(Map extensionItems) { - if (extensionItems == null) { - return null; - } - byte[] bytes = extensionItems.get(0x01); - if (bytes == null || bytes.length != 4) { - return null; - } - return u32(bytes, 0) * 0.1; - } - - private static void decodeItem(Map out, String prefix, int id, byte[] bytes) { - if (bytes == null) { - return; - } - switch (id) { - case 0x01 -> put(out, prefix + "mileage_km", scaled(bytes, 4, 1)); - case 0x02 -> put(out, prefix + "fuel_l", scaled(bytes, 2, 1)); - case 0x03 -> put(out, prefix + "recorder_speed_kmh", scaled(bytes, 2, 1)); - case 0x04 -> put(out, prefix + "manual_alarm_event_id", unsigned(bytes, 2)); - case 0x05 -> decodeTirePressure(out, prefix, bytes); - case 0x06 -> put(out, prefix + "compartment_temp_c", signedHighBitWord(bytes)); - case 0x11 -> decodeOverspeed(out, prefix, bytes); - case 0x12 -> decodeAreaRouteAlarm(out, prefix, bytes); - case 0x13 -> decodeRouteTime(out, prefix, bytes); - case 0x25 -> decodeVehicleSignal(out, prefix, bytes); - case 0x2A -> decodeIoStatus(out, prefix, bytes); - case 0x2B -> decodeAnalog(out, prefix, bytes); - case 0x30 -> put(out, prefix + "network_signal_strength", unsigned(bytes, 1)); - case 0x31 -> put(out, prefix + "gnss_satellite_count", unsigned(bytes, 1)); - default -> { - } - } - } - - private static void decodeTirePressure(Map out, String prefix, byte[] bytes) { - if (bytes.length != 30) { - return; - } - List values = new ArrayList<>(); - List validIndexes = new ArrayList<>(); - List invalidIndexes = new ArrayList<>(); - for (int i = 0; i < bytes.length; i++) { - int value = u8(bytes, i); - if (value == 0xFF) { - invalidIndexes.add(i); - } else { - validIndexes.add(i); - values.add(Integer.toString(value)); - } - } - put(out, prefix + "tire_pressure_pa_values", String.join(",", values)); - put(out, prefix + "tire_pressure_valid_indexes", ranges(validIndexes)); - put(out, prefix + "tire_pressure_invalid_indexes", ranges(invalidIndexes)); - } - - private static void decodeOverspeed(Map out, String prefix, byte[] bytes) { - if (bytes.length != 1 && bytes.length != 5) { - return; - } - put(out, prefix + "overspeed.position_type", Integer.toString(u8(bytes, 0))); - if (bytes.length == 5) { - put(out, prefix + "overspeed.area_route_id", Long.toString(u32(bytes, 1))); - } - } - - private static void decodeAreaRouteAlarm(Map out, String prefix, byte[] bytes) { - if (bytes.length != 6) { - return; - } - put(out, prefix + "area_route_alarm.position_type", Integer.toString(u8(bytes, 0))); - put(out, prefix + "area_route_alarm.area_route_id", Long.toString(u32(bytes, 1))); - put(out, prefix + "area_route_alarm.direction", Integer.toString(u8(bytes, 5))); - } - - private static void decodeRouteTime(Map out, String prefix, byte[] bytes) { - if (bytes.length != 7) { - return; - } - put(out, prefix + "route_time.segment_id", Long.toString(u32(bytes, 0))); - put(out, prefix + "route_time.duration_seconds", Integer.toString(u16(bytes, 4))); - put(out, prefix + "route_time.result", Integer.toString(u8(bytes, 6))); - } - - private static void decodeVehicleSignal(Map out, String prefix, byte[] bytes) { - if (bytes.length != 4) { - return; - } - long raw = u32(bytes, 0); - put(out, prefix + "vehicle_signal_raw", Long.toString(raw)); - for (int i = 0; i < VEHICLE_SIGNAL_BITS.length; i++) { - put(out, prefix + "vehicle_signal." + VEHICLE_SIGNAL_BITS[i], Boolean.toString(bit(raw, i))); - } - } - - private static void decodeIoStatus(Map out, String prefix, byte[] bytes) { - if (bytes.length != 2) { - return; - } - int raw = u16(bytes, 0); - put(out, prefix + "io_status_raw", Integer.toString(raw)); - for (int i = 0; i < IO_STATUS_BITS.length; i++) { - put(out, prefix + "io_status." + IO_STATUS_BITS[i], Boolean.toString(bit(raw, i))); - } - } - - private static void decodeAnalog(Map out, String prefix, byte[] bytes) { - if (bytes.length != 4) { - return; - } - long raw = u32(bytes, 0); - put(out, prefix + "analog_raw", Long.toString(raw)); - put(out, prefix + "analog_ad0", Integer.toString((int) (raw & 0xFFFF))); - put(out, prefix + "analog_ad1", Integer.toString((int) ((raw >>> 16) & 0xFFFF))); - } - - private static String scaled(byte[] bytes, int expectedLength, int scale) { - if (bytes.length != expectedLength) { - return null; - } - long raw = expectedLength == 4 ? u32(bytes, 0) : u16(bytes, 0); - return BigDecimal.valueOf(raw, scale).stripTrailingZeros().toPlainString(); - } - - private static String unsigned(byte[] bytes, int expectedLength) { - if (bytes.length != expectedLength) { - return null; - } - return switch (expectedLength) { - case 1 -> Integer.toString(u8(bytes, 0)); - case 2 -> Integer.toString(u16(bytes, 0)); - case 4 -> Long.toString(u32(bytes, 0)); - default -> null; - }; - } - - private static String signedHighBitWord(byte[] bytes) { - if (bytes.length != 2) { - return null; - } - int raw = u16(bytes, 0); - int value = (raw & 0x8000) == 0 ? raw : -(raw & 0x7FFF); - return Integer.toString(value); - } - - private static int u8(byte[] bytes, int offset) { - return bytes[offset] & 0xFF; - } - - private static int u16(byte[] bytes, int offset) { - return (u8(bytes, offset) << 8) | u8(bytes, offset + 1); - } - - private static long u32(byte[] bytes, int offset) { - return ((long) u8(bytes, offset) << 24) - | ((long) u8(bytes, offset + 1) << 16) - | ((long) u8(bytes, offset + 2) << 8) - | u8(bytes, offset + 3); - } - - private static boolean bit(long raw, int bit) { - return ((raw >>> bit) & 1L) == 1L; - } - - private static void put(Map out, String key, String value) { - if (value != null) { - out.put(key, value); - } - } - - private static String ranges(List indexes) { - if (indexes.isEmpty()) { - return ""; - } - List ranges = new ArrayList<>(); - int start = indexes.getFirst(); - int previous = start; - for (int i = 1; i < indexes.size(); i++) { - int current = indexes.get(i); - if (current == previous + 1) { - previous = current; - continue; - } - ranges.add(formatRange(start, previous)); - start = current; - previous = current; - } - ranges.add(formatRange(start, previous)); - return String.join(",", ranges); - } - - private static String formatRange(int start, int end) { - return start == end ? Integer.toString(start) : start + "-" + end; - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationFlagInfo.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationFlagInfo.java deleted file mode 100644 index ea964d78..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808LocationFlagInfo.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.model; - -import java.util.LinkedHashMap; -import java.util.Map; - -public final class Jt808LocationFlagInfo { - - private static final String[] ALARM_BITS = { - "emergency", - "overspeed", - "fatigue_driving", - "warning", - "gnss_module_fault", - "gnss_antenna_disconnected", - "gnss_antenna_short_circuit", - "terminal_main_power_undervoltage", - "terminal_main_power_lost", - "display_fault", - "tts_fault", - "camera_fault", - "transport_ic_card_fault", - "overspeed_warning", - "fatigue_driving_warning", - "prohibited_driving", - null, - null, - "daily_driving_timeout", - "overtime_parking", - "area_in_out", - "route_in_out", - "route_time_abnormal", - "route_deviation", - "vss_fault", - "fuel_abnormal", - "stolen", - "illegal_ignition", - "illegal_displacement", - "collision_rollover", - "rollover_warning" - }; - - private Jt808LocationFlagInfo() { - } - - public static Map decodeAlarm(long alarmFlag, String prefix) { - Map out = new LinkedHashMap<>(); - String p = prefix == null ? "" : prefix; - for (int i = 0; i < ALARM_BITS.length; i++) { - String name = ALARM_BITS[i]; - if (name != null) { - out.put(p + name, Boolean.toString(bit(alarmFlag, i))); - } - } - return Map.copyOf(out); - } - - public static Map decodeStatus(long statusFlag, String prefix) { - Map out = new LinkedHashMap<>(); - String p = prefix == null ? "" : prefix; - out.put(p + "acc_on", Boolean.toString(bit(statusFlag, 0))); - out.put(p + "positioned", Boolean.toString(bit(statusFlag, 1))); - out.put(p + "south_latitude", Boolean.toString(bit(statusFlag, 2))); - out.put(p + "west_longitude", Boolean.toString(bit(statusFlag, 3))); - out.put(p + "out_of_service", Boolean.toString(bit(statusFlag, 4))); - out.put(p + "encrypted", Boolean.toString(bit(statusFlag, 5))); - out.put(p + "forward_collision_warning", Boolean.toString(bit(statusFlag, 6))); - out.put(p + "lane_departure_warning", Boolean.toString(bit(statusFlag, 7))); - out.put(p + "load_status", loadStatus(statusFlag)); - out.put(p + "fuel_circuit_cut", Boolean.toString(bit(statusFlag, 10))); - out.put(p + "electric_circuit_cut", Boolean.toString(bit(statusFlag, 11))); - out.put(p + "door_locked", Boolean.toString(bit(statusFlag, 12))); - out.put(p + "door1_open", Boolean.toString(bit(statusFlag, 13))); - out.put(p + "door2_open", Boolean.toString(bit(statusFlag, 14))); - out.put(p + "door3_open", Boolean.toString(bit(statusFlag, 15))); - out.put(p + "door4_open", Boolean.toString(bit(statusFlag, 16))); - out.put(p + "door5_open", Boolean.toString(bit(statusFlag, 17))); - out.put(p + "gps_used", Boolean.toString(bit(statusFlag, 18))); - out.put(p + "beidou_used", Boolean.toString(bit(statusFlag, 19))); - out.put(p + "glonass_used", Boolean.toString(bit(statusFlag, 20))); - out.put(p + "galileo_used", Boolean.toString(bit(statusFlag, 21))); - out.put(p + "moving", Boolean.toString(bit(statusFlag, 22))); - return Map.copyOf(out); - } - - private static String loadStatus(long statusFlag) { - int code = (int) ((statusFlag >>> 8) & 0x03); - return switch (code) { - case 0 -> "EMPTY"; - case 1 -> "HALF_LOADED"; - case 2 -> "RESERVED"; - case 3 -> "FULL"; - default -> "UNKNOWN"; - }; - } - - private static boolean bit(long raw, int bit) { - return ((raw >>> bit) & 1L) == 1L; - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Message.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Message.java deleted file mode 100644 index 61a60bb2..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808Message.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.model; - -/** - * 完整解析后的 JT808 消息。 - */ -public record Jt808Message(Jt808Header header, Jt808Body body) {} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808MessageId.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808MessageId.java deleted file mode 100644 index 358b020e..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/model/Jt808MessageId.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.model; - -/** - * JT/T 808 消息 ID 常量集(节选核心消息,按需继续扩展)。 - * - *

    使用常量类而非 enum:808 消息种类多(超过 80 个),新增高频,常量类比 enum 维护成本更低。 - */ -public final class Jt808MessageId { - - private Jt808MessageId() {} - - // ===== 终端上行 ===== - public static final int TERMINAL_GENERAL_RESPONSE = 0x0001; - public static final int TERMINAL_HEARTBEAT = 0x0002; - public static final int TERMINAL_UNREGISTER = 0x0003; - public static final int TERMINAL_REGISTER = 0x0100; - public static final int TERMINAL_AUTH = 0x0102; - public static final int TERMINAL_PARAMS_REPORT = 0x0104; - public static final int TERMINAL_ATTRS_REPORT = 0x0107; - public static final int TERMINAL_LOCATION = 0x0200; - public static final int TERMINAL_LOCATION_BATCH = 0x0704; - public static final int TERMINAL_EVENT_REPORT = 0x0301; - public static final int TERMINAL_CAN_DATA_REPORT = 0x0705; - public static final int TERMINAL_MEDIA_EVENT = 0x0800; - public static final int TERMINAL_MEDIA_UPLOAD = 0x0801; - public static final int TERMINAL_PASSTHROUGH = 0x0900; - - // ===== 平台下行 ===== - public static final int PLATFORM_GENERAL_RESPONSE = 0x8001; - public static final int PLATFORM_REGISTER_ACK = 0x8100; - public static final int PLATFORM_SET_PARAMS = 0x8103; - public static final int PLATFORM_QUERY_PARAMS = 0x8104; - public static final int PLATFORM_TERMINAL_CONTROL = 0x8105; - public static final int PLATFORM_QUERY_ATTRS = 0x8107; - public static final int PLATFORM_QUERY_LOCATION = 0x8201; - public static final int PLATFORM_TRACK_CONTROL = 0x8202; - public static final int PLATFORM_ALARM_ACK = 0x8203; - public static final int PLATFORM_DELETE_CIRCLE_AREA = 0x8601; - - public static String nameOf(int messageId) { - return switch (messageId) { - case TERMINAL_GENERAL_RESPONSE -> "TERMINAL_GENERAL_RESPONSE"; - case TERMINAL_HEARTBEAT -> "TERMINAL_HEARTBEAT"; - case TERMINAL_UNREGISTER -> "TERMINAL_UNREGISTER"; - case TERMINAL_REGISTER -> "TERMINAL_REGISTER"; - case TERMINAL_AUTH -> "TERMINAL_AUTH"; - case TERMINAL_PARAMS_REPORT -> "TERMINAL_PARAMS_REPORT"; - case TERMINAL_ATTRS_REPORT -> "TERMINAL_ATTRS_REPORT"; - case TERMINAL_LOCATION -> "TERMINAL_LOCATION"; - case TERMINAL_LOCATION_BATCH -> "TERMINAL_LOCATION_BATCH"; - case TERMINAL_EVENT_REPORT -> "TERMINAL_EVENT_REPORT"; - case TERMINAL_CAN_DATA_REPORT -> "TERMINAL_CAN_DATA_REPORT"; - case TERMINAL_MEDIA_EVENT -> "TERMINAL_MEDIA_EVENT"; - case TERMINAL_MEDIA_UPLOAD -> "TERMINAL_MEDIA_UPLOAD"; - case TERMINAL_PASSTHROUGH -> "TERMINAL_PASSTHROUGH"; - case PLATFORM_GENERAL_RESPONSE -> "PLATFORM_GENERAL_RESPONSE"; - case PLATFORM_REGISTER_ACK -> "PLATFORM_REGISTER_ACK"; - case PLATFORM_SET_PARAMS -> "PLATFORM_SET_PARAMS"; - case PLATFORM_QUERY_PARAMS -> "PLATFORM_QUERY_PARAMS"; - case PLATFORM_TERMINAL_CONTROL -> "PLATFORM_TERMINAL_CONTROL"; - case PLATFORM_QUERY_ATTRS -> "PLATFORM_QUERY_ATTRS"; - case PLATFORM_QUERY_LOCATION -> "PLATFORM_QUERY_LOCATION"; - case PLATFORM_TRACK_CONTROL -> "PLATFORM_TRACK_CONTROL"; - case PLATFORM_ALARM_ACK -> "PLATFORM_ALARM_ACK"; - case PLATFORM_DELETE_CIRCLE_AREA -> "PLATFORM_DELETE_CIRCLE_AREA"; - default -> "UNKNOWN_0x" + hex(messageId); - }; - } - - public static String reportTypeOf(int messageId) { - return switch (messageId) { - case TERMINAL_LOCATION -> "LOCATION"; - case TERMINAL_LOCATION_BATCH -> "LOCATION_BATCH"; - case TERMINAL_HEARTBEAT -> "HEARTBEAT"; - case TERMINAL_AUTH -> "AUTH"; - case TERMINAL_REGISTER -> "REGISTER"; - case TERMINAL_UNREGISTER -> "UNREGISTER"; - case TERMINAL_GENERAL_RESPONSE -> "TERMINAL_RESPONSE"; - case TERMINAL_PARAMS_REPORT -> "PARAMS_REPORT"; - case TERMINAL_ATTRS_REPORT -> "ATTRS_REPORT"; - case TERMINAL_MEDIA_EVENT -> "MEDIA_EVENT"; - case TERMINAL_MEDIA_UPLOAD -> "MEDIA_UPLOAD"; - case TERMINAL_PASSTHROUGH -> "PASSTHROUGH"; - default -> "RAW"; - }; - } - - public static String hex(int messageId) { - return String.format("%04X", messageId & 0xFFFF); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808ChannelRegistry.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808ChannelRegistry.java deleted file mode 100644 index 98130f7c..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808ChannelRegistry.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.session; - -import io.netty.channel.Channel; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * JT808 终端 Channel 注册表。按 {@code phone} 索引,一个 phone 对应一个活跃 Channel。 - * - *

    {@link Jt808CommandDispatcher} 通过本类查 Channel;{@code Jt808ChannelHandler} 在 - * 连接建立 / 断开 / 鉴权完成时维护映射。 - * - *

    线程安全:{@link ConcurrentHashMap}。 - */ -public final class Jt808ChannelRegistry { - - private static final Logger log = LoggerFactory.getLogger(Jt808ChannelRegistry.class); - - private final ConcurrentMap byPhone = new ConcurrentHashMap<>(); - private final ConcurrentMap reverse = new ConcurrentHashMap<>(); - private final ConcurrentMap serials = new ConcurrentHashMap<>(); - - public void bind(String phone, Channel channel) { - if (phone == null || phone.isBlank() || channel == null) return; - Channel old = byPhone.put(phone, channel); - reverse.put(channel, phone); - if (old != null && old != channel) { - reverse.remove(old); - // 同一 phone 新连接上线时关闭旧 channel,避免下行命令写到已被终端替换的连接。 - old.close(); - log.info("channel rebound phone={} oldChannel={} newChannel={}", phone, old.id(), channel.id()); - } - } - - public Optional find(String phone) { - if (phone == null) return Optional.empty(); - Channel c = byPhone.get(phone); - if (c == null || !c.isActive()) { - if (c != null) unbind(c); - return Optional.empty(); - } - return Optional.of(c); - } - - public Optional unbind(Channel channel) { - String phone = reverse.remove(channel); - if (phone != null) { - byPhone.remove(phone, channel); - } - return Optional.ofNullable(phone); - } - - public int nextSerial(String phone) { - // JT808 流水号 16 bit,无符号回绕到 0;pending 匹配也使用这个序号。 - return serials.computeIfAbsent(phone, k -> new AtomicInteger()) - .updateAndGet(i -> (i + 1) & 0xFFFF); - } - - public int size() { - return byPhone.size(); - } -} diff --git a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808PendingRequests.java b/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808PendingRequests.java deleted file mode 100644 index 8d03b852..00000000 --- a/modules/protocols/protocol-jt808/src/main/java/com/lingniu/ingest/protocol/jt808/session/Jt808PendingRequests.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.session; - -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * 下行命令的请求/应答同步匹配表。 - * - *

    调用 {@link #await(String, int)} 时注册一个 {@link CompletableFuture}, - * 对应 key = {@code phone + ':' + serial};当 {@code Jt808ChannelHandler} 收到 0x0001 通用应答 - * 或其它同步应答消息时调用 {@link #complete(String, int, Jt808Message)} 触发 future 完成。 - */ -public final class Jt808PendingRequests { - - private static final Logger log = LoggerFactory.getLogger(Jt808PendingRequests.class); - - private final ConcurrentMap> pending = new ConcurrentHashMap<>(); - - public CompletableFuture await(String phone, int serial) { - CompletableFuture cf = new CompletableFuture<>(); - // JT808 平台下行流水号按终端 phone 独立递增,因此 phone+serial 才能唯一定位一次请求。 - pending.put(key(phone, serial), cf); - return cf; - } - - public void complete(String phone, int serial, Jt808Message response) { - CompletableFuture cf = pending.remove(key(phone, serial)); - if (cf != null) { - cf.complete(response); - } else { - log.debug("no pending request for phone={} serial={}", phone, serial); - } - } - - public void cancel(String phone, int serial) { - CompletableFuture cf = pending.remove(key(phone, serial)); - if (cf != null) cf.cancel(true); - } - - public int size() { - return pending.size(); - } - - private static String key(String phone, int serial) { - return phone + ":" + serial; - } -} diff --git a/modules/protocols/protocol-jt808/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/protocols/protocol-jt808/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 9af2ea7e..00000000 --- a/modules/protocols/protocol-jt808/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.protocol.jt808.config.Jt808AutoConfiguration diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/TestSessionStore.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/TestSessionStore.java deleted file mode 100644 index dc1be186..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/TestSessionStore.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.lingniu.ingest.protocol.jt808; - -import com.lingniu.ingest.session.DeviceSession; -import com.lingniu.ingest.session.SessionStore; - -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.UnaryOperator; - -public final class TestSessionStore implements SessionStore { - - private final ConcurrentMap byId = new ConcurrentHashMap<>(); - private final ConcurrentMap vinToId = new ConcurrentHashMap<>(); - private final ConcurrentMap phoneToId = new ConcurrentHashMap<>(); - - @Override - public void put(DeviceSession session) { - byId.put(session.sessionId(), session); - if (session.vin() != null) { - vinToId.put(session.vin(), session.sessionId()); - } - if (session.phone() != null) { - phoneToId.put(session.phone(), session.sessionId()); - } - } - - @Override - public Optional findBySessionId(String sessionId) { - return Optional.ofNullable(byId.get(sessionId)); - } - - @Override - public Optional findByVin(String vin) { - String sessionId = vinToId.get(vin); - return sessionId == null ? Optional.empty() : findBySessionId(sessionId); - } - - @Override - public Optional findByPhone(String phone) { - String sessionId = phoneToId.get(phone); - return sessionId == null ? Optional.empty() : findBySessionId(sessionId); - } - - @Override - public synchronized Optional update(String sessionId, UnaryOperator updater) { - DeviceSession current = byId.get(sessionId); - if (current == null) { - return Optional.empty(); - } - DeviceSession next = updater.apply(current); - put(next); - return Optional.of(next); - } - - @Override - public void remove(String sessionId) { - DeviceSession removed = byId.remove(sessionId); - if (removed == null) { - return; - } - if (removed.vin() != null) { - vinToId.remove(removed.vin(), sessionId); - } - if (removed.phone() != null) { - phoneToId.remove(removed.phone(), sessionId); - } - } - - @Override - public int size() { - return byId.size(); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderGoldenTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderGoldenTest.java deleted file mode 100644 index 94c366fb..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderGoldenTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import com.lingniu.ingest.protocol.jt808.codec.parser.HeartbeatBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.LocationBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.RegisterBodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * JT808 黄金样本集回放测试:遍历 {@code src/test/resources/samples/jt808/*.hex}。 - * - *

    样本文件格式:一行十六进制(完整 0x7e 包裹帧),脚本脱敏产出。 - * 测试会去掉首尾 0x7e,执行反转义后调用 {@link Jt808MessageDecoder}。 - */ -class Jt808DecoderGoldenTest { - - private final Jt808MessageDecoder decoder = new Jt808MessageDecoder( - new BodyParserRegistry(List.of( - new RegisterBodyParser(), - new LocationBodyParser(), - new HeartbeatBodyParser()))); - - @TestFactory - Collection replaySamples() throws URISyntaxException, IOException { - var url = getClass().getClassLoader().getResource("samples/jt808"); - if (url == null) return List.of(); - Path dir = Paths.get(url.toURI()); - try (Stream s = Files.list(dir)) { - return s.filter(p -> p.toString().endsWith(".hex")) - .sorted() - .map(this::toTest) - .collect(Collectors.toList()); - } - } - - private DynamicTest toTest(Path sample) { - return DynamicTest.dynamicTest(sample.getFileName().toString(), () -> { - byte[] framed = readHex(sample); - assertThat(framed[0]).as("must start with 0x7e").isEqualTo((byte) 0x7e); - assertThat(framed[framed.length - 1]).as("must end with 0x7e").isEqualTo((byte) 0x7e); - - byte[] unescaped = Jt808Escape.unescape(framed, 1, framed.length - 2); - Jt808Message msg = decoder.decode(ByteBuffer.wrap(unescaped)); - - assertThat(msg.header().messageId()).isNotNegative(); - assertThat(msg.header().phone()).isNotEmpty(); - }); - } - - private static byte[] readHex(Path path) throws IOException { - StringBuilder sb = new StringBuilder(); - for (String line : Files.readAllLines(path)) { - String trimmed = line.trim(); - if (trimmed.isEmpty() || trimmed.startsWith("#")) continue; - sb.append(trimmed); - } - String hex = sb.toString(); - int len = hex.length() / 2; - byte[] out = new byte[len]; - for (int i = 0; i < len; i++) { - out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16); - } - return out; - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderTest.java deleted file mode 100644 index 198430f9..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808DecoderTest.java +++ /dev/null @@ -1,274 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.protocol.jt808.codec.parser.HeartbeatBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.AuthBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.LocationBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.LocationBatchBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.MediaEventBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.MediaUploadBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.PassthroughBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.TerminalAttrsBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.TerminalParamsReportBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.UnregisterBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.RegisterBodyParser; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808LocationAdditionalInfo; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.nio.ByteBuffer; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808DecoderTest { - - private final Jt808MessageDecoder decoder = new Jt808MessageDecoder( - new BodyParserRegistry(List.of( - new RegisterBodyParser(), - new AuthBodyParser(), - new LocationBodyParser(), - new LocationBatchBodyParser(), - new UnregisterBodyParser(), - new TerminalParamsReportBodyParser(), - new TerminalAttrsBodyParser(), - new MediaEventBodyParser(), - new MediaUploadBodyParser(), - new PassthroughBodyParser(), - new HeartbeatBodyParser()))); - - @Test - void decodesSyntheticLocationFrame() { - byte[] body = buildLocationBody(); - byte[] frame = buildFrame(Jt808MessageId.TERMINAL_LOCATION, "123456789012", 0x0001, body); - Jt808Message msg = decoder.decode(ByteBuffer.wrap(frame)); - - assertThat(msg.header().messageId()).isEqualTo(Jt808MessageId.TERMINAL_LOCATION); - assertThat(msg.header().phone()).isEqualTo("123456789012"); - assertThat(msg.header().serialNo()).isEqualTo(1); - - assertThat(msg.body()).isInstanceOf(Jt808Body.Location.class); - Jt808Body.Location loc = (Jt808Body.Location) msg.body(); - assertThat(loc.longitude()).isEqualTo(116.397128, org.assertj.core.data.Offset.offset(0.000001)); - assertThat(loc.latitude()).isEqualTo(39.916527, org.assertj.core.data.Offset.offset(0.000001)); - assertThat(loc.speedKmh()).isEqualTo(52.3, org.assertj.core.data.Offset.offset(0.01)); - } - - @Test - void decodesLocationExtensionItems() { - byte[] body = bytes(os -> { - os.write(buildLocationBody(), 0, buildLocationBody().length); - os.write(0x01); // mileage, DWORD, 0.1 km - os.write(4); - writeU32(os, 12345); - os.write(0x30); // wireless signal strength - os.write(1); - os.write(88); - }); - - Jt808Body.Location loc = (Jt808Body.Location) decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_LOCATION, "123456789012", 9, body))).body(); - - assertThat(loc.extensionItems()).containsOnlyKeys(0x01, 0x30); - assertThat(loc.extensionItems().get(0x01)).containsExactly(0x00, 0x00, 0x30, 0x39); - assertThat(loc.extensionItems().get(0x30)).containsExactly(88); - } - - @Test - void decodesForwardedLocationFrameGpsMileageExtension() { - byte[] framed = hex(""" - 7E020000320133077954250001000000000048000301D2C4C707376139000A00E6004F260630162357 - 01040001900C2504000000000202000030011F31010F867E - """); - byte[] unescaped = Jt808Escape.unescape(framed, 1, framed.length - 2); - - Jt808Body.Location loc = (Jt808Body.Location) decoder.decode(ByteBuffer.wrap(unescaped)).body(); - - assertThat(loc.longitude()).isEqualTo(121.069881); - assertThat(loc.latitude()).isEqualTo(30.590151); - assertThat(loc.speedKmh()).isEqualTo(23.0); - assertThat(loc.deviceTime().toString()).isEqualTo("2026-06-30T08:23:57Z"); - assertThat(loc.extensionItems()).containsOnlyKeys(0x01, 0x25, 0x02, 0x30, 0x31); - assertThat(loc.extensionItems().get(0x01)).containsExactly(0x00, 0x01, (byte) 0x90, 0x0C); - assertThat(Jt808LocationAdditionalInfo.mileageKm(loc.extensionItems())).isEqualTo(10241.2); - assertThat(Jt808LocationAdditionalInfo.decode(loc.extensionItems())) - .containsEntry("jt808.extra.mileage_km", "10241.2") - .containsEntry("jt808.extra.vehicle_signal_raw", "0") - .containsEntry("jt808.extra.fuel_l", "0") - .containsEntry("jt808.extra.network_signal_strength", "31") - .containsEntry("jt808.extra.gnss_satellite_count", "15"); - } - - @Test - void decodesSyntheticHeartbeatFrame() { - byte[] frame = buildFrame(Jt808MessageId.TERMINAL_HEARTBEAT, "123456789012", 0x0002, new byte[0]); - Jt808Message msg = decoder.decode(ByteBuffer.wrap(frame)); - assertThat(msg.header().messageId()).isEqualTo(Jt808MessageId.TERMINAL_HEARTBEAT); - assertThat(msg.body()).isInstanceOf(Jt808Body.Heartbeat.class); - } - - @Test - void decodesAuthFrameAndExtractsImeiWhenPresent() { - byte[] authBody = "AUTH-CODE,123456789012345,SW-1".getBytes(java.nio.charset.StandardCharsets.US_ASCII); - - Jt808Body.Auth auth = (Jt808Body.Auth) decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_AUTH, "123456789012", 10, authBody))).body(); - - assertThat(auth.token()).isEqualTo("AUTH-CODE,123456789012345,SW-1"); - assertThat(auth.imei()).isEqualTo("123456789012345"); - assertThat(auth.softwareVersion()).isEqualTo("SW-1"); - } - - @Test - void decodesLogoutParamsAttrsMediaAndPassthroughFrames() { - assertThat(decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_UNREGISTER, "123456789012", 3, new byte[0]))).body()) - .isInstanceOf(Jt808Body.Unregister.class); - - byte[] paramsBody = bytes(os -> { - writeU16(os, 7); - os.write(1); - writeU32(os, 0x00000080L); - os.write(1); - os.write(5); - }); - Jt808Body.ParamsReport params = (Jt808Body.ParamsReport) decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_PARAMS_REPORT, "123456789012", 4, paramsBody))).body(); - assertThat(params.responseSerialNo()).isEqualTo(7); - assertThat(params.parameters()).containsKey(0x00000080L); - - byte[] attrsBody = bytes(os -> { - writeU16(os, 0x1234); - writeAscii(os, "MAKER", 5); - writeAscii(os, "MODEL-X", 20); - writeAscii(os, "DEV1234", 7); - os.write(BcdCodec.encode("89860012345678901234"), 0, 10); - writeAscii(os, "HW1", 10); - writeAscii(os, "SW1", 10); - os.write(1); - os.write(2); - }); - Jt808Body.TerminalAttrs attrs = (Jt808Body.TerminalAttrs) decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_ATTRS_REPORT, "123456789012", 5, attrsBody))).body(); - assertThat(attrs.maker()).isEqualTo("MAKER"); - assertThat(attrs.terminalId()).isEqualTo("DEV1234"); - - byte[] mediaEventBody = bytes(os -> { - writeU32(os, 42); - os.write(0); - os.write(1); - os.write(2); - os.write(3); - }); - assertThat(decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_MEDIA_EVENT, "123456789012", 6, mediaEventBody))).body()) - .isInstanceOf(Jt808Body.MediaEvent.class); - - byte[] passthroughBody = bytes(os -> { - os.write(0x41); - os.write(new byte[]{0x01, 0x02, 0x03}, 0, 3); - }); - Jt808Body.Passthrough passthrough = (Jt808Body.Passthrough) decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_PASSTHROUGH, "123456789012", 7, passthroughBody))).body(); - assertThat(passthrough.passthroughType()).isEqualTo(0x41); - assertThat(passthrough.data()).containsExactly(0x01, 0x02, 0x03); - } - - @Test - void decodesLocationBatchFrame() { - byte[] location = buildLocationBody(); - byte[] batchBody = bytes(os -> { - writeU16(os, 2); - os.write(0); - writeU16(os, location.length); - os.write(location, 0, location.length); - writeU16(os, location.length); - os.write(location, 0, location.length); - }); - - Jt808Body.LocationBatch batch = (Jt808Body.LocationBatch) decoder.decode(ByteBuffer.wrap(buildFrame( - Jt808MessageId.TERMINAL_LOCATION_BATCH, "123456789012", 8, batchBody))).body(); - - assertThat(batch.batchType()).isEqualTo(0); - assertThat(batch.locations()).hasSize(2); - assertThat(batch.locations().getFirst().longitude()).isEqualTo(116.397128); - } - - // ===== helpers ===== - - private static byte[] buildLocationBody() { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeU32(os, 0); // alarmFlag - writeU32(os, 0x00030000); // statusFlag - writeU32(os, 39_916_527L); // lat - writeU32(os, 116_397_128L); // lon - writeU16(os, 50); // altitude - writeU16(os, 523); // speed 52.3 - writeU16(os, 90); // direction - byte[] bcd = BcdCodec.encode("240102030405"); - os.write(bcd, 0, 6); - return os.toByteArray(); - } - - private static byte[] bytes(IoConsumer writer) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writer.accept(os); - return os.toByteArray(); - } - - private static void writeAscii(ByteArrayOutputStream os, String value, int len) { - byte[] out = new byte[len]; - byte[] raw = value.getBytes(java.nio.charset.StandardCharsets.US_ASCII); - System.arraycopy(raw, 0, out, 0, Math.min(raw.length, out.length)); - os.write(out, 0, out.length); - } - - @FunctionalInterface - private interface IoConsumer { - void accept(T value); - } - - private static byte[] buildFrame(int msgId, String phone, int serial, byte[] body) { - // header: msgId(2) attrs(2) phone(6 BCD) serial(2) - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeU16(os, msgId); - int attrs = body.length & 0x03FF; // 2013 版,无子包、无加密 - writeU16(os, attrs); - byte[] phoneBcd = BcdCodec.encode(phone); - os.write(phoneBcd, 0, 6); - writeU16(os, serial); - os.write(body, 0, body.length); - byte[] raw = os.toByteArray(); - byte bcc = BccChecksum.compute(raw, 0, raw.length); - - ByteArrayOutputStream framed = new ByteArrayOutputStream(); - framed.write(raw, 0, raw.length); - framed.write(bcc & 0xFF); - return framed.toByteArray(); - } - - private static byte[] hex(String value) { - String compact = value.replaceAll("\\s+", ""); - byte[] out = new byte[compact.length() / 2]; - for (int i = 0; i < out.length; i++) { - out[i] = (byte) Integer.parseInt(compact.substring(i * 2, i * 2 + 2), 16); - } - return out; - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808EscapeTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808EscapeTest.java deleted file mode 100644 index 677e8276..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808EscapeTest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808EscapeTest { - - @Test - void roundTrip() { - byte[] raw = {0x30, 0x7e, 0x08, 0x7d, (byte) 0xFF}; - byte[] escaped = Jt808Escape.escape(raw); - assertThat(escaped).containsExactly(0x30, 0x7d, 0x02, 0x08, 0x7d, 0x01, 0xFF); - - byte[] back = Jt808Escape.unescape(escaped, 0, escaped.length); - assertThat(back).containsExactly(raw[0], raw[1], raw[2], raw[3], raw[4]); - } - - @Test - void noEscapeNeeded() { - byte[] raw = {0x01, 0x02, 0x03}; - assertThat(Jt808Escape.escape(raw)).containsExactly(0x01, 0x02, 0x03); - assertThat(Jt808Escape.unescape(raw, 0, raw.length)).containsExactly(0x01, 0x02, 0x03); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoderTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoderTest.java deleted file mode 100644 index 8fcc2d0c..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameDecoderTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import io.netty.buffer.Unpooled; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808FrameDecoderTest { - - @Test - void garbageWithoutFrameBoundaryEmitsMalformedCandidate() { - EmbeddedChannel channel = new EmbeddedChannel(new Jt808FrameDecoder()); - - channel.writeInbound(Unpooled.wrappedBuffer(new byte[]{0x01, 0x02, 0x03})); - - Jt808MalformedFrame malformed = channel.readInbound(); - assertThat(malformed.rawBytes()).containsExactly(0x01, 0x02, 0x03); - assertThat(malformed.reason()).contains("missing frame boundary"); - assertThat(malformed.peer()).isNotNull(); - assertThat((Object) channel.readInbound()).isNull(); - } - - @Test - void oversizedUnclosedFrameEmitsMalformedCandidate() { - byte[] bytes = new byte[16 * 1024 + 2]; - bytes[0] = 0x7e; - bytes[1] = 0x01; - EmbeddedChannel channel = new EmbeddedChannel(new Jt808FrameDecoder()); - - channel.writeInbound(Unpooled.wrappedBuffer(bytes)); - - Jt808MalformedFrame malformed = channel.readInbound(); - assertThat(malformed.rawBytes()).containsExactly(bytes); - assertThat(malformed.reason()).contains("too large"); - assertThat(malformed.peer()).isNotNull(); - assertThat((Object) channel.readInbound()).isNull(); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoderTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoderTest.java deleted file mode 100644 index a29d9e46..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/codec/Jt808FrameEncoderTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.codec; - -import com.lingniu.ingest.protocol.jt808.codec.parser.HeartbeatBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.LocationBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.RegisterBodyParser; -import com.lingniu.ingest.protocol.jt808.downlink.Jt808Commands; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * 端到端验证:encoder 生成的下行帧可以被 decoder 反向解析,且字段对称。 - */ -class Jt808FrameEncoderTest { - - private final Jt808MessageDecoder decoder = new Jt808MessageDecoder( - new BodyParserRegistry(List.of( - new RegisterBodyParser(), - new LocationBodyParser(), - new HeartbeatBodyParser()))); - - @Test - void encodeThenDecodePlatformAck() { - var cmd = Jt808Commands.platformAck(0x1234, 0x0200, 0); - byte[] framed = Jt808FrameEncoder.encode(cmd.messageId(), "123456789012", 7, cmd.body()); - - // strip outer 0x7e and unescape - byte[] unescaped = Jt808Escape.unescape(framed, 1, framed.length - 2); - Jt808Message msg = decoder.decode(ByteBuffer.wrap(unescaped)); - - assertThat(msg.header().messageId()).isEqualTo(Jt808MessageId.PLATFORM_GENERAL_RESPONSE); - assertThat(msg.header().phone()).isEqualTo("123456789012"); - assertThat(msg.header().serialNo()).isEqualTo(7); - assertThat(msg.body()).isInstanceOf(Jt808Body.Raw.class); - Jt808Body.Raw raw = (Jt808Body.Raw) msg.body(); - // body: ackSerial(2) + ackMsgId(2) + result(1) = 5 bytes - assertThat(raw.bytes()).hasSize(5); - } - - @Test - void encodeQueryLocationProducesFramedWith7eBoundary() { - byte[] framed = Jt808FrameEncoder.encode( - Jt808MessageId.PLATFORM_QUERY_LOCATION, "123456789012", 1, new byte[0]); - assertThat(framed[0]).isEqualTo((byte) 0x7e); - assertThat(framed[framed.length - 1]).isEqualTo((byte) 0x7e); - // 中间不应出现裸 0x7e - for (int i = 1; i < framed.length - 1; i++) { - assertThat(framed[i]).isNotEqualTo((byte) 0x7e); - } - } - - @Test - void rejectsOversizedBodyWhenSubpackageIsNotUsed() { - assertThatThrownBy(() -> Jt808FrameEncoder.encode( - Jt808MessageId.PLATFORM_SET_PARAMS, "123456789012", 1, new byte[1024])) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("jt808 body too large"); - } - - @Test - void encodesOversizedBodyAsSubpackages() { - byte[] body = new byte[2048]; - for (int i = 0; i < body.length; i++) { - body[i] = (byte) i; - } - - List frames = Jt808FrameEncoder.encodeSubpackages( - Jt808MessageId.PLATFORM_SET_PARAMS, "123456789012", 9, body); - - assertThat(frames).hasSize(3); - java.io.ByteArrayOutputStream joined = new java.io.ByteArrayOutputStream(); - for (int i = 0; i < frames.size(); i++) { - byte[] unescaped = Jt808Escape.unescape(frames.get(i), 1, frames.get(i).length - 2); - Jt808Message msg = decoder.decode(ByteBuffer.wrap(unescaped)); - assertThat(msg.header().subpacket()).isTrue(); - assertThat(msg.header().totalPackets()).isEqualTo(3); - assertThat(msg.header().packetSeq()).isEqualTo(i + 1); - assertThat(msg.header().serialNo()).isEqualTo(9); - assertThat(msg.body()).isInstanceOf(Jt808Body.Raw.class); - joined.writeBytes(((Jt808Body.Raw) msg.body()).bytes()); - } - assertThat(joined.toByteArray()).containsExactly(body); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/config/Jt808PropertiesTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/config/Jt808PropertiesTest.java deleted file mode 100644 index 0a77d6e4..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/config/Jt808PropertiesTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.config; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808PropertiesTest { - - @Test - void defaultsToCurrentProductionReceivePort() throws IOException { - assertThat(new Jt808Properties().getPort()).isEqualTo(808); - - String pom = Files.readString(repositoryRoot() - .resolve("modules/protocols/protocol-jt808/pom.xml")); - assertThat(pom) - .contains("默认 TCP 808") - .doesNotContain("10808"); - } - - private static Path repositoryRoot() { - Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath(); - while (current != null) { - if (Files.exists(current.resolve("pom.xml")) && Files.exists(current.resolve("modules"))) { - return current; - } - current = current.getParent(); - } - throw new IllegalStateException("repository root not found"); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcherTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcherTest.java deleted file mode 100644 index 94a92435..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandDispatcherTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.downlink; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.protocol.jt808.codec.BodyParserRegistry; -import com.lingniu.ingest.protocol.jt808.codec.Jt808Escape; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; -import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry; -import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests; -import com.lingniu.ingest.session.DeviceSession; -import com.lingniu.ingest.protocol.jt808.TestSessionStore; -import io.netty.buffer.ByteBuf; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.nio.ByteBuffer; -import java.time.Instant; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808CommandDispatcherTest { - - @Test - void notifyWritesSubpackageFramesForLargeBody() { - TestSessionStore sessions = new TestSessionStore(); - sessions.put(new DeviceSession("sid-1", ProtocolId.JT808, "LNVIN000000000303", - "123456789012", "", "127.0.0.1:10000", Instant.now(), Instant.now(), Map.of())); - Jt808ChannelRegistry channels = new Jt808ChannelRegistry(); - EmbeddedChannel channel = new EmbeddedChannel(); - channels.bind("123456789012", channel); - Jt808CommandDispatcher dispatcher = new Jt808CommandDispatcher( - sessions, channels, new Jt808PendingRequests()); - byte[] body = new byte[2048]; - - dispatcher.notify("sid-1", new Jt808Commands.DownlinkCommand( - Jt808MessageId.PLATFORM_SET_PARAMS, body)).join(); - - Jt808MessageDecoder decoder = new Jt808MessageDecoder(new BodyParserRegistry(List.of())); - for (int i = 1; i <= 3; i++) { - ByteBuf outbound = channel.readOutbound(); - byte[] frame = new byte[outbound.readableBytes()]; - outbound.readBytes(frame); - byte[] unescaped = Jt808Escape.unescape(frame, 1, frame.length - 2); - Jt808Message decoded = decoder.decode(ByteBuffer.wrap(unescaped)); - assertThat(decoded.header().subpacket()).isTrue(); - assertThat(decoded.header().totalPackets()).isEqualTo(3); - assertThat(decoded.header().packetSeq()).isEqualTo(i); - assertThat(decoded.body()).isInstanceOf(Jt808Body.Raw.class); - } - assertThat((Object) channel.readOutbound()).isNull(); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandsTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandsTest.java deleted file mode 100644 index f5a5dc6f..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/downlink/Jt808CommandsTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.downlink; - -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808CommandsTest { - - @Test - void deleteAreaEncodesCountAndDwordAreaIds() { - var command = Jt808Commands.deleteArea(List.of(1L, 0x01020304L)); - - assertThat(command.messageId()).isEqualTo(0x8601); - assertThat(command.body()).containsExactly( - 0x02, - 0x00, 0x00, 0x00, 0x01, - 0x01, 0x02, 0x03, 0x04); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandlerTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandlerTest.java deleted file mode 100644 index 03c05fd9..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/handler/Jt808LocationHandlerTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.handler; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.pipeline.IngestContext; -import com.lingniu.ingest.api.pipeline.RawFrame; -import com.lingniu.ingest.core.dispatcher.AnnotationHandlerBeanPostProcessor; -import com.lingniu.ingest.core.dispatcher.HandlerInvoker; -import com.lingniu.ingest.core.dispatcher.HandlerRegistry; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808LocationHandlerTest { - - @Test - void unknownRawMessageRoutesToPassthroughHandler() { - Jt808LocationHandler handler = new Jt808LocationHandler( - new Jt808EventMapper(new InMemoryVehicleIdentityService())); - HandlerRegistry registry = new HandlerRegistry(); - new AnnotationHandlerBeanPostProcessor(registry).postProcessAfterInitialization(handler, "jt808Handler"); - Jt808Message message = new Jt808Message( - new Jt808Header(0x0F01, 3, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 7, 0, 0), - new Jt808Body.Raw(0x0F01, new byte[]{0x01, 0x02, 0x03})); - - var definitions = registry.resolve(ProtocolId.JT808, 0x0F01, 0); - - assertThat(definitions).hasSize(1); - var events = new HandlerInvoker().invoke(definitions.getFirst(), new RawFrame( - ProtocolId.JT808, 0x0F01, 0, message, new byte[]{0x7e}, - Map.of("phone", "123456789012"), Instant.now()), new IngestContext("trace-1")); - assertThat(events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.passthroughType()).isEqualTo(0x0F01); - assertThat(passthrough.metadata()).containsEntry("rawBody", "true"); - }); - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java deleted file mode 100644 index 7f29f78b..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/inbound/Jt808ChannelHandlerTest.java +++ /dev/null @@ -1,857 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.inbound; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.codec.BcdCodec; -import com.lingniu.ingest.core.concurrency.AsyncBatchExecutor; -import com.lingniu.ingest.core.concurrency.DisruptorEventBus; -import com.lingniu.ingest.core.dispatcher.Dispatcher; -import com.lingniu.ingest.core.dispatcher.HandlerDefinition; -import com.lingniu.ingest.core.dispatcher.HandlerInvoker; -import com.lingniu.ingest.core.dispatcher.HandlerRegistry; -import com.lingniu.ingest.core.pipeline.InterceptorChain; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.identity.VehicleIdentityLookup; -import com.lingniu.ingest.identity.VehicleIdentitySource; -import com.lingniu.ingest.protocol.jt808.codec.BodyParserRegistry; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MalformedFrame; -import com.lingniu.ingest.protocol.jt808.codec.Jt808MessageDecoder; -import com.lingniu.ingest.protocol.jt808.codec.parser.AuthBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.LocationBodyParser; -import com.lingniu.ingest.protocol.jt808.codec.parser.RegisterBodyParser; -import com.lingniu.ingest.protocol.jt808.handler.Jt808LocationHandler; -import com.lingniu.ingest.protocol.jt808.mapper.Jt808EventMapper; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; -import com.lingniu.ingest.protocol.jt808.session.Jt808ChannelRegistry; -import com.lingniu.ingest.protocol.jt808.session.Jt808PendingRequests; -import com.lingniu.ingest.protocol.jt808.TestSessionStore; -import io.netty.buffer.ByteBuf; -import io.netty.channel.embedded.EmbeddedChannel; -import org.junit.jupiter.api.Test; - -import java.io.ByteArrayOutputStream; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.within; - -class Jt808ChannelHandlerTest { - - @Test - void registerSessionUsesResolvedInternalVinInsteadOfDeviceIdPlaceholder() { - TestSessionStore sessions = new TestSessionStore(); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT808, "LNVIN000000000808", "123456789012", "DEV808", "B80808")); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(new ImmediateKafkaSink())); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new RegisterBodyParser()))), - dispatcher, - sessions, - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(buildFrame( - Jt808MessageId.TERMINAL_REGISTER, - "123456789012", - 1, - buildRegisterBody("DEV808", "B80808"))); - - assertThat(sessions.findByPhone("123456789012")) - .get() - .extracting(session -> session.vin()) - .isEqualTo("LNVIN000000000808"); - assertThat(identity.resolve(new com.lingniu.ingest.identity.VehicleIdentityLookup( - ProtocolId.JT808, "", "123456789012", "", "")).vin()) - .isEqualTo("LNVIN000000000808"); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void unresolvedRegisterBindsGeneratedInternalVinToExternalIdentifiers() { - TestSessionStore sessions = new TestSessionStore(); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(new ImmediateKafkaSink())); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new RegisterBodyParser()))), - dispatcher, - sessions, - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(buildFrame( - Jt808MessageId.TERMINAL_REGISTER, - "123456789012", - 1, - buildRegisterBody("DEV808", "B80808"))); - - assertThat(identity.resolve(new VehicleIdentityLookup( - ProtocolId.JT808, "", "123456789012", "", ""))) - .satisfies(resolved -> { - assertThat(resolved.vin()).startsWith("JT808"); - assertThat(resolved.resolved()).isTrue(); - assertThat(resolved.source()).isEqualTo(VehicleIdentitySource.BOUND_PHONE); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void authSessionUsesResolvedInternalVinFromImei() { - TestSessionStore sessions = new TestSessionStore(); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT808, "LNVIN000000AUTH01", "123456789012", "123456789012345", "")); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(new ImmediateKafkaSink())); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new AuthBodyParser()))), - dispatcher, - sessions, - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(buildFrame( - Jt808MessageId.TERMINAL_AUTH, - "123456789012", - 2, - "TOKEN,123456789012345,SW1".getBytes(java.nio.charset.StandardCharsets.US_ASCII))); - - assertThat(sessions.findByPhone("123456789012")) - .get() - .extracting(session -> session.vin()) - .isEqualTo("LNVIN000000AUTH01"); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void registerAckWaitsForDispatchDurability() { - ControlledSink sink = new ControlledSink(); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new RegisterBodyParser()))), - dispatcher, - new TestSessionStore(), - new InMemoryVehicleIdentityService(), - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(buildFrame( - Jt808MessageId.TERMINAL_REGISTER, - "123456789012", - 1, - buildRegisterBody("DEV808", "B80808"))); - - assertThat((Object) channel.readOutbound()).isNull(); - sink.awaitPublishCount(1); - sink.future(0).complete(null); - - ByteBuf ack = readOutboundAfterRunningTasks(channel); - assertThat(ack).isNotNull(); - ack.release(); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void inactiveChannelRemovesSessionStoreEntry() { - TestSessionStore sessions = new TestSessionStore(); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(new ImmediateKafkaSink())); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new AuthBodyParser()))), - dispatcher, - sessions, - new InMemoryVehicleIdentityService(), - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(buildFrame( - Jt808MessageId.TERMINAL_AUTH, - "123456789012", - 2, - "TOKEN,123456789012345,SW1".getBytes(java.nio.charset.StandardCharsets.US_ASCII))); - assertThat(sessions.findByPhone("123456789012")).isPresent(); - - channel.close(); - - assertThat(sessions.findByPhone("123456789012")).isEmpty(); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void successfulFrameArchiveUsesResolvedVehicleIdentity() throws Exception { - RecordingSink sink = new RecordingSink(1); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT808, "LNVIN000000000808", "123456789012", "DEV808", "粤B80808")); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new LocationBodyParser()))), - dispatcher, - new TestSessionStore(), - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] frame = buildFrame(Jt808MessageId.TERMINAL_LOCATION, "123456789012", 1, buildLocationBody()); - - channel.writeInbound(frame); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.vin()).isEqualTo("LNVIN000000000808"); - assertThat(archive.metadata()) - .containsEntry("vin", "LNVIN000000000808") - .containsEntry("phone", "123456789012") - .containsEntry("messageId", "0x0200") - .containsEntry("messageName", "TERMINAL_LOCATION") - .containsEntry("reportType", "LOCATION") - .containsEntry("body.alarmFlag", "0") - .containsEntry("body.statusFlag", "196608") - .containsEntry("body.status.acc_on", "false") - .containsEntry("body.status.load_status", "EMPTY") - .containsEntry("body.status.door4_open", "true") - .containsEntry("body.status.door5_open", "true") - .containsEntry("body.alarm.overspeed", "false") - .containsEntry("body.longitude", "116.397128") - .containsEntry("body.latitude", "39.916527") - .containsEntry("body.altitudeM", "50") - .containsEntry("body.directionDeg", "90") - .containsEntry("body.deviceTime", "2024-01-01T19:04:05Z") - .containsEntry("body.extra.0x01", "00003039") - .containsEntry("body.extra.mileage_km", "1234.5") - .containsEntry("body.extra.0x30", "58") - .containsEntry("body.extra.network_signal_strength", "88") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE"); - assertThat(Double.parseDouble(archive.metadata().get("body.speedKmh"))).isCloseTo(52.3, within(0.001)); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void registerArchiveIncludesParsedBodyFieldsForRawQuery() throws Exception { - RecordingSink sink = new RecordingSink(1); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - identity.bind(new VehicleIdentityBinding( - ProtocolId.JT808, "LNVIN000000000808", "123456789012", "DEV808", "B80808")); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new RegisterBodyParser()))), - dispatcher, - new TestSessionStore(), - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] frame = buildFrame( - Jt808MessageId.TERMINAL_REGISTER, - "123456789012", - 3, - buildRegisterBody("DEV808", "B80808")); - - channel.writeInbound(frame); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.metadata()) - .containsEntry("messageId", "0x0100") - .containsEntry("messageName", "TERMINAL_REGISTER") - .containsEntry("reportType", "REGISTER") - .containsEntry("body.province", "44") - .containsEntry("body.city", "4401") - .containsEntry("body.maker", "MAKER") - .containsEntry("body.deviceType", "TYPE-A") - .containsEntry("body.deviceId", "DEV808") - .containsEntry("body.plateColor", "1") - .containsEntry("body.plate", "B80808"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void unboundRegisterCreatesVinBindingForRegisterAndFollowingRawFrames() throws Exception { - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new RegisterBodyParser(), new LocationBodyParser()))), - dispatcher, - new TestSessionStore(), - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - - channel.writeInbound(buildFrame( - Jt808MessageId.TERMINAL_REGISTER, - "13079963289", - 3, - buildRegisterBody("9963289", "HUA00113F"))); - channel.writeInbound(buildFrame( - Jt808MessageId.TERMINAL_LOCATION, - "13079963289", - 4, - buildLocationBody())); - - assertThat(sink.await()).isTrue(); - VehicleEvent.RawArchive register = (VehicleEvent.RawArchive) sink.events.get(0); - VehicleEvent.RawArchive location = (VehicleEvent.RawArchive) sink.events.get(1); - assertThat(register.metadata()) - .containsEntry("reportType", "REGISTER") - .containsEntry("body.deviceId", "9963289") - .containsEntry("body.plate", "HUA00113F") - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "REGISTERED"); - assertThat(register.vin()) - .startsWith("JT808") - .hasSize(17); - assertThat(location.metadata()) - .containsEntry("reportType", "LOCATION") - .containsEntry("vin", register.vin()) - .containsEntry("identityResolved", "true") - .containsEntry("identitySource", "BOUND_PHONE"); - assertThat(location.vin()).isEqualTo(register.vin()); - assertThat(identity.resolve(new com.lingniu.ingest.identity.VehicleIdentityLookup( - ProtocolId.JT808, "", "13079963289", "", "")).vin()) - .isEqualTo(register.vin()); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void unboundLocationArchiveUsesUnknownVinInsteadOfPhoneFallback() throws Exception { - RecordingSink sink = new RecordingSink(1); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new LocationBodyParser()))), - dispatcher, - new TestSessionStore(), - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] frame = buildFrame(Jt808MessageId.TERMINAL_LOCATION, "123456789012", 1, buildLocationBody()); - - channel.writeInbound(frame); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.vin()).isEqualTo("unknown"); - assertThat(archive.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("phone", "123456789012") - .containsEntry("messageId", "0x0200") - .containsEntry("messageName", "TERMINAL_LOCATION") - .containsEntry("reportType", "LOCATION") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "FALLBACK_PHONE"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void registerArchivePreservesRegistrationFieldsInMetadata() throws Exception { - RecordingSink sink = new RecordingSink(1); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - new HandlerRegistry(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new RegisterBodyParser()))), - dispatcher, - new TestSessionStore(), - identity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] frame = buildFrame( - Jt808MessageId.TERMINAL_REGISTER, - "123456789012", - 1, - buildRegisterBody("DEV808", "B80808")); - - channel.writeInbound(frame); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).singleElement().satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.command()).isEqualTo(Jt808MessageId.TERMINAL_REGISTER); - assertThat(archive.metadata()) - .containsEntry("body.province", "44") - .containsEntry("body.city", "4401") - .containsEntry("body.maker", "MAKER") - .containsEntry("body.deviceType", "TYPE-A") - .containsEntry("body.deviceId", "DEV808") - .containsEntry("body.plateColor", "1") - .containsEntry("body.plate", "B80808"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void malformedFrameIsArchivedAndDispatchedAsPassthrough() throws Exception { - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - registryWithRawHandler(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of())), - dispatcher, - new TestSessionStore(), - new InMemoryVehicleIdentityService(), - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] malformed = new byte[]{0x01, 0x02, 0x03}; - - channel.writeInbound(malformed); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive raw = (VehicleEvent.RawArchive) event; - assertThat(raw.source()).isEqualTo(ProtocolId.JT808); - assertThat(raw.command()).isZero(); - assertThat(raw.rawBytes()).containsExactly(malformed); - assertThat(raw.metadata()) - .containsEntry("parseError", "true") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.JT808); - assertThat(passthrough.vin()).isEqualTo("unknown"); - assertThat(passthrough.metadata()) - .containsEntry("parseError", "true") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - assertThat(passthrough.data()).containsExactly(malformed); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void frameBoundaryMalformedCandidateIsArchivedAndDispatchedAsPassthrough() throws Exception { - RecordingSink sink = new RecordingSink(2); - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - registryWithRawHandler(), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of())), - dispatcher, - new TestSessionStore(), - new InMemoryVehicleIdentityService(), - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] raw = new byte[]{0x01, 0x02, 0x03}; - - channel.writeInbound(new Jt808MalformedFrame(raw, "unknown", "jt808 missing frame boundary")); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.source()).isEqualTo(ProtocolId.JT808); - assertThat(archive.rawBytes()).containsExactly(raw); - assertThat(archive.metadata()).containsEntry("frameError", "true") - .containsEntry("frameErrorMessage", "jt808 missing frame boundary") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - }); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.source()).isEqualTo(ProtocolId.JT808); - assertThat(passthrough.metadata()).containsEntry("frameError", "true") - .containsEntry("frameErrorMessage", "jt808 missing frame boundary") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN"); - assertThat(passthrough.data()).containsExactly(raw); - }); - - batchExecutor.close(); - eventBus.close(); - } - - @Test - void identityResolverFailureStillArchivesAndDispatchesDecodedLocationWithUnknownIdentity() throws Exception { - RecordingSink sink = new RecordingSink(2); - var failingIdentity = (com.lingniu.ingest.identity.VehicleIdentityResolver) lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }; - DisruptorEventBus eventBus = new DisruptorEventBus(1024, "blocking", List.of(sink)); - AsyncBatchExecutor batchExecutor = new AsyncBatchExecutor(eventBus::publish); - Dispatcher dispatcher = new Dispatcher( - registryWithLocationHandler(new DirectLocationHandler(new Jt808EventMapper(failingIdentity))), - new InterceptorChain(List.of()), - new HandlerInvoker(), - eventBus, - batchExecutor); - Jt808ChannelHandler handler = new Jt808ChannelHandler( - new Jt808MessageDecoder(new BodyParserRegistry(List.of(new LocationBodyParser()))), - dispatcher, - new TestSessionStore(), - new InMemoryVehicleIdentityService(), - failingIdentity, - new Jt808ChannelRegistry(), - new Jt808PendingRequests()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - byte[] frame = buildFrame(Jt808MessageId.TERMINAL_LOCATION, "123456789012", 1, buildLocationBody()); - - channel.writeInbound(frame); - - assertThat(sink.await()).isTrue(); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.RawArchive.class); - VehicleEvent.RawArchive archive = (VehicleEvent.RawArchive) event; - assertThat(archive.source()).isEqualTo(ProtocolId.JT808); - assertThat(archive.command()).isEqualTo(Jt808MessageId.TERMINAL_LOCATION); - assertThat(archive.rawBytes()).containsExactly(frame); - assertThat(archive.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("phone", "123456789012") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable"); - }); - assertThat(sink.events).anySatisfy(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Location.class); - VehicleEvent.Location location = (VehicleEvent.Location) event; - assertThat(location.source()).isEqualTo(ProtocolId.JT808); - assertThat(location.vin()).isEqualTo("unknown"); - assertThat(location.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("phone", "123456789012") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable"); - }); - - batchExecutor.close(); - eventBus.close(); - } - - private static HandlerRegistry registryWithRawHandler() throws NoSuchMethodException { - HandlerRegistry registry = new HandlerRegistry(); - Jt808LocationHandler rawHandler = new Jt808LocationHandler( - new Jt808EventMapper(new InMemoryVehicleIdentityService())); - registry.register(new HandlerDefinition( - ProtocolId.JT808, - 0, - 0, - "test raw", - rawHandler, - Jt808LocationHandler.class.getMethod("onRaw", Jt808Message.class), - Jt808Message.class, - null, - null, - null)); - return registry; - } - - private static HandlerRegistry registryWithLocationHandler(DirectLocationHandler locationHandler) throws NoSuchMethodException { - HandlerRegistry registry = new HandlerRegistry(); - registry.register(new HandlerDefinition( - ProtocolId.JT808, - Jt808MessageId.TERMINAL_LOCATION, - 0, - "test location", - locationHandler, - DirectLocationHandler.class.getMethod("onLocation", Jt808Message.class), - Jt808Message.class, - null, - null, - null)); - return registry; - } - - public static final class DirectLocationHandler { - private final Jt808EventMapper mapper; - - private DirectLocationHandler(Jt808EventMapper mapper) { - this.mapper = mapper; - } - - public List onLocation(Jt808Message msg) { - return mapper.toEvents(msg); - } - } - - private static byte[] buildLocationBody() { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeU32(os, 0); - writeU32(os, 0x00030000); - writeU32(os, 39_916_527L); - writeU32(os, 116_397_128L); - writeU16(os, 50); - writeU16(os, 523); - writeU16(os, 90); - byte[] bcd = BcdCodec.encode("240102030405"); - os.write(bcd, 0, 6); - os.write(0x01); - os.write(4); - writeU32(os, 12345); - os.write(0x30); - os.write(1); - os.write(0x58); - return os.toByteArray(); - } - - private static byte[] buildRegisterBody(String deviceId, String plate) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeU16(os, 44); - writeU16(os, 4401); - writeAscii(os, "MAKER", 5); - writeAscii(os, "TYPE-A", 20); - writeAscii(os, deviceId, 7); - os.write(1); - byte[] plateBytes = plate.getBytes(java.nio.charset.StandardCharsets.US_ASCII); - os.write(plateBytes, 0, plateBytes.length); - return os.toByteArray(); - } - - private static void writeAscii(ByteArrayOutputStream os, String value, int len) { - byte[] raw = value.getBytes(java.nio.charset.StandardCharsets.US_ASCII); - int copy = Math.min(raw.length, len); - os.write(raw, 0, copy); - for (int i = copy; i < len; i++) { - os.write(0x20); - } - } - - private static byte[] buildFrame(int msgId, String phone, int serial, byte[] body) { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - writeU16(os, msgId); - writeU16(os, body.length & 0x03FF); - byte[] phoneBcd = BcdCodec.encode(phone); - os.write(phoneBcd, 0, 6); - writeU16(os, serial); - os.write(body, 0, body.length); - byte[] raw = os.toByteArray(); - byte bcc = BccChecksum.compute(raw, 0, raw.length); - - ByteArrayOutputStream framed = new ByteArrayOutputStream(); - framed.write(raw, 0, raw.length); - framed.write(bcc & 0xFF); - return framed.toByteArray(); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } - - private static ByteBuf readOutboundAfterRunningTasks(EmbeddedChannel channel) { - long deadline = System.currentTimeMillis() + 3000; - while (System.currentTimeMillis() < deadline) { - channel.runPendingTasks(); - ByteBuf outbound = channel.readOutbound(); - if (outbound != null) { - return outbound; - } - Thread.onSpinWait(); - } - return null; - } - - private static final class ControlledSink implements EventSink { - private final List> futures = new CopyOnWriteArrayList<>(); - - @Override - public String name() { - return "kafka"; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - CompletableFuture future = new CompletableFuture<>(); - futures.add(future); - return future; - } - - private CompletableFuture future(int index) { - return futures.get(index); - } - - private void awaitPublishCount(int expected) { - long deadline = System.currentTimeMillis() + 3000; - while (System.currentTimeMillis() < deadline) { - if (futures.size() >= expected) { - return; - } - Thread.onSpinWait(); - } - throw new AssertionError("expected " + expected + " sink publishes, actual=" + futures.size()); - } - } - - private static final class ImmediateKafkaSink implements EventSink { - @Override - public String name() { - return "kafka"; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - return CompletableFuture.completedFuture(null); - } - } - - private static final class RecordingSink implements EventSink { - private final List events = new CopyOnWriteArrayList<>(); - private final CountDownLatch latch; - - private RecordingSink(int expectedEvents) { - this.latch = new CountDownLatch(expectedEvents); - } - - @Override - public String name() { - return "kafka"; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - events.add(event); - latch.countDown(); - return CompletableFuture.completedFuture(null); - } - - private boolean await() throws InterruptedException { - return latch.await(3, TimeUnit.SECONDS); - } - } -} diff --git a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java b/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java deleted file mode 100644 index 03279b1e..00000000 --- a/modules/protocols/protocol-jt808/src/test/java/com/lingniu/ingest/protocol/jt808/mapper/Jt808EventMapperTest.java +++ /dev/null @@ -1,326 +0,0 @@ -package com.lingniu.ingest.protocol.jt808.mapper; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.identity.InMemoryVehicleIdentityService; -import com.lingniu.ingest.identity.VehicleIdentityBinding; -import com.lingniu.ingest.protocol.jt808.model.Jt808Body; -import com.lingniu.ingest.protocol.jt808.model.Jt808Header; -import com.lingniu.ingest.protocol.jt808.model.Jt808Message; -import com.lingniu.ingest.protocol.jt808.model.Jt808MessageId; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808EventMapperTest { - - private final InMemoryVehicleIdentityService identity = new InMemoryVehicleIdentityService(); - private final Jt808EventMapper mapper = new Jt808EventMapper(identity); - - @Test - void locationBodyProducesLocationEvent() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_LOCATION, 28, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var body = new Jt808Body.Location( - 0, 0, 116.397128, 39.916527, 50, 52.3, 90, Instant.parse("2024-01-02T03:04:05Z")); - var msg = new Jt808Message(header, body); - - List events = mapper.toEvents(msg); - - assertThat(events).hasSize(1); - assertThat(events.get(0)).isInstanceOf(VehicleEvent.Location.class); - assertThat(events.get(0).source()).isEqualTo(ProtocolId.JT808); - assertThat(events.get(0).vin()).isEqualTo("unknown"); - assertThat(events.get(0).metadata()) - .containsEntry("vin", "unknown") - .containsEntry("phone", "123456789012") - .containsEntry("jt808.status.acc_on", "false") - .containsEntry("jt808.status.positioned", "false") - .containsEntry("jt808.status.load_status", "EMPTY") - .containsEntry("jt808.alarm.overspeed", "false") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "FALLBACK_PHONE"); - } - - @Test - void locationExtensionItemsAreExposedAsMetadata() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_LOCATION, 35, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var body = new Jt808Body.Location( - 0, 0, 116.397128, 39.916527, 50, 52.3, 90, - Instant.parse("2024-01-02T03:04:05Z"), - java.util.Map.of(0x01, new byte[]{0x00, 0x00, 0x30, 0x39}, 0x30, new byte[]{0x58})); - - List events = mapper.toEvents(new Jt808Message(header, body)); - - assertThat(events).singleElement() - .extracting(VehicleEvent::metadata) - .satisfies(meta -> assertThat(meta) - .containsEntry("jt808.extra.0x01", "00003039") - .containsEntry("jt808.extra.0x30", "58") - .containsEntry("jt808.extra.mileage_km", "1234.5") - .containsEntry("jt808.extra.network_signal_strength", "88")); - } - - @Test - void locationAdditionalItemsAreDecodedAsTable27Fields() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_LOCATION, 64, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var body = new Jt808Body.Location( - 0, 0, 116.397128, 39.916527, 50, 52.3, 90, - Instant.parse("2024-01-02T03:04:05Z"), - java.util.Map.ofEntries( - java.util.Map.entry(0x01, new byte[]{0x00, 0x00, 0x30, 0x39}), - java.util.Map.entry(0x02, new byte[]{0x00, 0x64}), - java.util.Map.entry(0x03, new byte[]{0x01, (byte) 0xF4}), - java.util.Map.entry(0x04, new byte[]{0x00, 0x02}), - java.util.Map.entry(0x05, tirePressureBytes()), - java.util.Map.entry(0x06, new byte[]{(byte) 0x80, 0x05}), - java.util.Map.entry(0x11, new byte[]{0x01, 0x00, 0x00, 0x00, 0x2A}), - java.util.Map.entry(0x12, new byte[]{0x04, 0x00, 0x00, 0x00, 0x2B, 0x01}), - java.util.Map.entry(0x13, new byte[]{0x00, 0x00, 0x00, 0x2C, 0x00, 0x78, 0x01}), - java.util.Map.entry(0x25, new byte[]{0x00, 0x00, 0x00, 0x05}), - java.util.Map.entry(0x2A, new byte[]{0x00, 0x03}), - java.util.Map.entry(0x2B, new byte[]{0x12, 0x34, 0x56, 0x78}), - java.util.Map.entry(0x30, new byte[]{0x1F}), - java.util.Map.entry(0x31, new byte[]{0x0C}))); - - VehicleEvent.Location event = (VehicleEvent.Location) mapper.toEvents(new Jt808Message(header, body)).getFirst(); - - assertThat(event.payload().totalMileageKm()).isEqualTo(1234.5); - assertThat(event.metadata()) - .containsEntry("jt808.extra.mileage_km", "1234.5") - .containsEntry("jt808.extra.fuel_l", "10") - .containsEntry("jt808.extra.recorder_speed_kmh", "50") - .containsEntry("jt808.extra.manual_alarm_event_id", "2") - .containsEntry("jt808.extra.tire_pressure_pa_values", "32,33") - .containsEntry("jt808.extra.tire_pressure_invalid_indexes", "2-29") - .containsEntry("jt808.extra.compartment_temp_c", "-5") - .containsEntry("jt808.extra.overspeed.position_type", "1") - .containsEntry("jt808.extra.overspeed.area_route_id", "42") - .containsEntry("jt808.extra.area_route_alarm.position_type", "4") - .containsEntry("jt808.extra.area_route_alarm.area_route_id", "43") - .containsEntry("jt808.extra.area_route_alarm.direction", "1") - .containsEntry("jt808.extra.route_time.segment_id", "44") - .containsEntry("jt808.extra.route_time.duration_seconds", "120") - .containsEntry("jt808.extra.route_time.result", "1") - .containsEntry("jt808.extra.vehicle_signal_raw", "5") - .containsEntry("jt808.extra.vehicle_signal.low_beam", "true") - .containsEntry("jt808.extra.vehicle_signal.high_beam", "false") - .containsEntry("jt808.extra.vehicle_signal.right_turn", "true") - .containsEntry("jt808.extra.io_status_raw", "3") - .containsEntry("jt808.extra.io_status.deep_sleep", "true") - .containsEntry("jt808.extra.io_status.sleep", "true") - .containsEntry("jt808.extra.analog_raw", "305419896") - .containsEntry("jt808.extra.analog_ad0", "22136") - .containsEntry("jt808.extra.analog_ad1", "4660") - .containsEntry("jt808.extra.network_signal_strength", "31") - .containsEntry("jt808.extra.gnss_satellite_count", "12"); - } - - @Test - void locationMileageExtensionIsMappedToInternalMileageField() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_LOCATION, 34, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var body = new Jt808Body.Location( - 0, 0, 116.397128, 39.916527, 50, 52.3, 90, - Instant.parse("2024-01-02T03:04:05Z"), - java.util.Map.of(0x01, new byte[]{0x00, 0x00, 0x30, 0x39})); - - VehicleEvent.Location event = (VehicleEvent.Location) mapper.toEvents(new Jt808Message(header, body)).getFirst(); - - assertThat(event.payload().totalMileageKm()).isEqualTo(1234.5); - assertThat(event.metadata()) - .containsEntry("jt808.extra.0x01", "00003039") - .containsEntry("jt808.extra.mileage_km", "1234.5") - .containsEntry("total_mileage_km", "1234.5"); - } - - @Test - void locationBodyUsesBoundVehicleIdentity() { - identity.bind(new VehicleIdentityBinding(ProtocolId.JT808, - "LNVIN000000000009", "123456789012", "DEV009", "粤B99999")); - var header = new Jt808Header( - Jt808MessageId.TERMINAL_LOCATION, 28, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var body = new Jt808Body.Location( - 0, 0, 116.397128, 39.916527, 50, 52.3, 90, Instant.parse("2024-01-02T03:04:05Z")); - - List events = mapper.toEvents(new Jt808Message(header, body)); - - assertThat(events).hasSize(1); - assertThat(events.get(0).vin()).isEqualTo("LNVIN000000000009"); - assertThat(events.get(0).metadata()) - .containsEntry("vin", "LNVIN000000000009") - .containsEntry("identityResolved", "true"); - } - - @Test - void identityResolverFailureStillProducesLocationEventWithUnknownVin() { - Jt808EventMapper mapperWithFailingIdentity = new Jt808EventMapper(lookup -> { - throw new IllegalStateException("identity backend unavailable"); - }); - var header = new Jt808Header( - Jt808MessageId.TERMINAL_LOCATION, 28, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var body = new Jt808Body.Location( - 0, 0, 116.397128, 39.916527, 50, 52.3, 90, Instant.parse("2024-01-02T03:04:05Z")); - - List events = mapperWithFailingIdentity.toEvents(new Jt808Message(header, body)); - - assertThat(events).singleElement() - .satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Location.class); - assertThat(event.vin()).isEqualTo("unknown"); - assertThat(event.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("phone", "123456789012") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "UNKNOWN") - .containsEntry("identityError", "true") - .containsEntry("identityErrorMessage", "identity backend unavailable"); - }); - } - - @Test - void heartbeatBodyProducesHeartbeatEvent() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_HEARTBEAT, 0, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var msg = new Jt808Message(header, new Jt808Body.Heartbeat()); - assertThat(mapper.toEvents(msg)).hasSize(1) - .first().isInstanceOf(VehicleEvent.Heartbeat.class); - } - - @Test - void registerBodyPreservesRegistrationFieldsInMetadata() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_REGISTER, 37, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var body = new Jt808Body.Register( - 44, 1, "G7", "MODEL-X", "DEV123456", 2, "粤B12345"); - - List events = mapper.toEvents(new Jt808Message(header, body)); - - assertThat(events).singleElement() - .satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Login.class); - assertThat(event.metadata()) - .containsEntry("jt808.register.province", "44") - .containsEntry("jt808.register.city", "1") - .containsEntry("jt808.register.maker", "G7") - .containsEntry("jt808.register.deviceType", "MODEL-X") - .containsEntry("jt808.register.deviceId", "DEV123456") - .containsEntry("jt808.register.plateColor", "2") - .containsEntry("jt808.register.plate", "粤B12345"); - }); - } - - @Test - void unregisterBodyProducesLogoutEvent() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_UNREGISTER, 0, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - - assertThat(mapper.toEvents(new Jt808Message(header, new Jt808Body.Unregister()))) - .singleElement() - .isInstanceOf(VehicleEvent.Logout.class); - } - - @Test - void locationBatchProducesOneLocationEventPerPoint() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_LOCATION_BATCH, 0, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - var loc = new Jt808Body.Location( - 0, 0, 116.397128, 39.916527, 50, 52.3, 90, Instant.parse("2024-01-02T03:04:05Z")); - - List events = mapper.toEvents(new Jt808Message( - header, new Jt808Body.LocationBatch(0, List.of(loc, loc)))); - - assertThat(events).hasSize(2) - .allSatisfy(event -> assertThat(event).isInstanceOf(VehicleEvent.Location.class)); - } - - @Test - void mediaAndPassthroughBodiesProduceQueryableEvents() { - var header = new Jt808Header( - Jt808MessageId.TERMINAL_MEDIA_EVENT, 0, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 1, 0, 0); - assertThat(mapper.toEvents(new Jt808Message( - header, new Jt808Body.MediaEvent(42, 0, 1, 2, 3)))) - .singleElement() - .isInstanceOf(VehicleEvent.MediaMeta.class); - - var passHeader = new Jt808Header( - Jt808MessageId.TERMINAL_PASSTHROUGH, 0, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 2, 0, 0); - assertThat(mapper.toEvents(new Jt808Message( - passHeader, new Jt808Body.Passthrough(0x41, new byte[]{1, 2, 3})))) - .singleElement() - .satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - VehicleEvent.Passthrough passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.passthroughType()).isEqualTo(Jt808MessageId.TERMINAL_PASSTHROUGH); - assertThat(passthrough.metadata()).containsEntry("passthroughType", "0x41"); - assertThat(passthrough.data()).containsExactly(1, 2, 3); - }); - } - - @Test - void rawBodyProducesPassthroughEventSoUnknownMessagesRemainQueryable() { - var header = new Jt808Header( - 0x0F01, 3, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 9, 0, 0); - - assertThat(mapper.toEvents(new Jt808Message( - header, new Jt808Body.Raw(0x0F01, new byte[]{0x11, 0x22, 0x33})))) - .singleElement() - .satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - var passthrough = (VehicleEvent.Passthrough) event; - assertThat(passthrough.passthroughType()).isEqualTo(0x0F01); - assertThat(passthrough.data()).containsExactly(0x11, 0x22, 0x33); - assertThat(passthrough.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("phone", "123456789012") - .containsEntry("identityResolved", "false") - .containsEntry("identitySource", "FALLBACK_PHONE") - .containsEntry("rawBody", "true"); - }); - } - - @Test - void malformedBodyPassthroughMetadataUsesUnknownInternalVin() { - var header = new Jt808Header( - 0, 3, 0, false, - Jt808Header.ProtocolVersion.V2013, "123456789012", 9, 0, 0); - - assertThat(mapper.toEvents(new Jt808Message( - header, new Jt808Body.Malformed(new byte[]{0x01, 0x02, 0x03}, "127.0.0.1:7611", "decode failed")))) - .singleElement() - .satisfies(event -> { - assertThat(event).isInstanceOf(VehicleEvent.Passthrough.class); - assertThat(event.vin()).isEqualTo("unknown"); - assertThat(event.metadata()) - .containsEntry("vin", "unknown") - .containsEntry("identityResolved", "false") - .containsEntry("parseError", "true"); - }); - } - - private static byte[] tirePressureBytes() { - byte[] bytes = new byte[30]; - java.util.Arrays.fill(bytes, (byte) 0xFF); - bytes[0] = 32; - bytes[1] = 33; - return bytes; - } -} diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_001.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_001.hex deleted file mode 100644 index 48bcc762..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_001.hex +++ /dev/null @@ -1 +0,0 @@ -7e010200070000000000230000687569746f6e67417e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_002.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_002.hex deleted file mode 100644 index 9ef924e7..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/auth_002.hex +++ /dev/null @@ -1 +0,0 @@ -7e0102001800000000006500016e65382f51525a50664738596f7145575155577236513d3d607e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_001.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_001.hex deleted file mode 100644 index c515a631..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_001.hex +++ /dev/null @@ -1 +0,0 @@ -7e02000036000000000001072d000000000008000301ceb03b0732f26e000d02af0010260413123353010400052425020200000302000025040000000030011f3101192c7e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_002.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_002.hex deleted file mode 100644 index 1832fb3a..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_002.hex +++ /dev/null @@ -1 +0,0 @@ -7e020040460100000000000000000002002c00000000000c000301bb0e2107213304003f0000010d26041312335514040000000017020000010400006722030200002504000000002a020000300115310118ea0402008000fb7e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_010.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_010.hex deleted file mode 100644 index 2819da10..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_010.hex +++ /dev/null @@ -1 +0,0 @@ -7e020000360000000000100278000000000008000301d596aa0735b9910001004b01472604131234030104000a42c10202000003020000250400000000300115310117107e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_100.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_100.hex deleted file mode 100644 index fa3a3be1..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_100.hex +++ /dev/null @@ -1 +0,0 @@ -7e020000360000000001020443000000000008000301d2d74907376cde000f00000000260413123354010400078789020200000302000025040000000030011b310112db7e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_200.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_200.hex deleted file mode 100644 index ce0d2a99..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_200.hex +++ /dev/null @@ -1 +0,0 @@ -7e02004048010000000000000000020204d2000000000048000301cc1d4f073ed6640010033301492604131234001404000000001702000001040000f0542504000000002a02000030011f310110ea04020c8300ef0400000000d67e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_400.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_400.hex deleted file mode 100644 index 3540edbf..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_400.hex +++ /dev/null @@ -1 +0,0 @@ -7e02000036000000000413013d000000000008000201d392280736f1e30007000000002604131311220104000f334a020200000302000025040000000030011931010a097e diff --git a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_batch_001.hex b/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_batch_001.hex deleted file mode 100644 index 104ef178..00000000 --- a/modules/protocols/protocol-jt808/src/test/resources/samples/jt808/location_batch_001.hex +++ /dev/null @@ -1 +0,0 @@ -7e0704003b000000000058034e0001010036000000000008000301e26c120725c15100110000000726041313111301040006ffc6030200002504000000002a02000030010a31010c3c7e diff --git a/modules/services/event-history-service/pom.xml b/modules/services/event-history-service/pom.xml deleted file mode 100644 index e9065f59..00000000 --- a/modules/services/event-history-service/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - event-history-service - event-history-service - Kafka full-field event consumer + TDengine historical query API. - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - tdengine-history-store - - - com.lingniu.ingest - sink-kafka - - - com.lingniu.ingest - protocol-gb32960 - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-autoconfigure - - - io.swagger.core.v3 - swagger-annotations-jakarta - 2.2.47 - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.springframework.boot - spring-boot-starter-test - test - - - diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java deleted file mode 100644 index 45f7b4d7..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/ApiExceptionHandler.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MissingServletRequestParameterException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.resource.NoResourceFoundException; - -import java.io.IOException; -import java.time.OffsetDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeParseException; - -/** - * 统一 API 错误响应。 - * - *

    业务接口抛出的参数错误、时间格式错误和 Spring 参数绑定错误都会转成包含具体原因的 - * JSON,而不是让前端只能看到默认 400/500。这里的 timestamp 仅表示错误发生时间, - * 按东八区展示;业务数据里的 eventTime/ingestTime 不在这里改时区。 - */ -@RestControllerAdvice -public final class ApiExceptionHandler { - - private static final ZoneId DEFAULT_TIME_ZONE = ZoneId.of("Asia/Shanghai"); - - @ExceptionHandler(MissingServletRequestParameterException.class) - public ResponseEntity missingRequestParameter(MissingServletRequestParameterException ex, - HttpServletRequest request) { - return error(HttpStatus.BAD_REQUEST, - "missing required query parameter: " + ex.getParameterName(), - request); - } - - @ExceptionHandler(MethodArgumentTypeMismatchException.class) - public ResponseEntity typeMismatch(MethodArgumentTypeMismatchException ex, - HttpServletRequest request) { - String message = "invalid query parameter: " + ex.getName(); - if (ex.getValue() != null) { - message += " value=" + ex.getValue(); - } - return error(HttpStatus.BAD_REQUEST, message, request); - } - - @ExceptionHandler(ResponseStatusException.class) - public ResponseEntity responseStatus(ResponseStatusException ex, - HttpServletRequest request) { - HttpStatus status = HttpStatus.resolve(ex.getStatusCode().value()); - if (status == null) { - status = HttpStatus.INTERNAL_SERVER_ERROR; - } - String message = ex.getReason(); - if (message == null || message.isBlank()) { - message = status.getReasonPhrase(); - } - return error(status, message, request); - } - - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity illegalArgument(IllegalArgumentException ex, - HttpServletRequest request) { - return error(HttpStatus.BAD_REQUEST, safeMessage(ex, "bad request"), request); - } - - @ExceptionHandler(DateTimeParseException.class) - public ResponseEntity dateTimeParse(DateTimeParseException ex, - HttpServletRequest request) { - return error(HttpStatus.BAD_REQUEST, "invalid date/time parameter: " + ex.getParsedString(), request); - } - - @ExceptionHandler(NoResourceFoundException.class) - public ResponseEntity noResource(NoResourceFoundException ex, - HttpServletRequest request) { - return error(HttpStatus.NOT_FOUND, "resource not found: " + request.getRequestURI(), request); - } - - @ExceptionHandler(IOException.class) - public ResponseEntity io(IOException ex, - HttpServletRequest request) { - return error(HttpStatus.SERVICE_UNAVAILABLE, safeMessage(ex, "history storage unavailable"), request); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity generic(Exception ex, - HttpServletRequest request) { - // 保留异常 message 方便联调定位;底层如果没有 message,则退回通用文案。 - return error(HttpStatus.INTERNAL_SERVER_ERROR, safeMessage(ex, "internal server error"), request); - } - - private static ResponseEntity error(HttpStatus status, String message, HttpServletRequest request) { - return ResponseEntity.status(status).body(new ApiError( - OffsetDateTime.now(DEFAULT_TIME_ZONE).toString(), - status.value(), - status.getReasonPhrase(), - message, - request.getRequestURI())); - } - - private static String safeMessage(Exception ex, String fallback) { - String message = ex.getMessage(); - return message == null || message.isBlank() ? fallback : message; - } - - public record ApiError( - String timestamp, - int status, - String error, - String message, - String path) { - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/DefaultProductionProtocols.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/DefaultProductionProtocols.java deleted file mode 100644 index eb532833..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/DefaultProductionProtocols.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -import java.util.Locale; -import java.util.Set; - -final class DefaultProductionProtocols { - - private static final Set SUPPORTED = Set.of( - "GB32960", - "JT808", - "MQTT_YUTONG"); - private static final String SUPPORTED_MESSAGE = "protocol must be one of GB32960, JT808, MQTT_YUTONG"; - - private DefaultProductionProtocols() { - } - - static String requireSupported(String protocol) { - String normalized = protocol == null ? "" : protocol.trim().toUpperCase(Locale.ROOT); - if (!SUPPORTED.contains(normalized)) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, SUPPORTED_MESSAGE); - } - return normalized; - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestor.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestor.java deleted file mode 100644 index 0e8f596b..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestor.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.lingniu.ingest.api.consumer.EnvelopeBatchIngestor; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import com.lingniu.ingest.tdenginehistory.TdengineEnvelopeRows; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * Consumer-side entry point that stores Kafka envelope values into TDengine history tables. - * - *

    当前 32960 本机运行配置没有启用 Kafka consumer;这个类保留给“其他服务把 envelope - * 写回历史库”的解耦部署方式。失败时返回结构化结果,由 Kafka worker 决定是否进 DLQ。 - */ -public final class EventHistoryEnvelopeIngestor implements EnvelopeBatchIngestor { - - private static final Logger log = LoggerFactory.getLogger(EventHistoryEnvelopeIngestor.class); - - private final TdengineHistoryWriter tdengineWriter; - - public EventHistoryEnvelopeIngestor(TdengineHistoryWriter tdengineWriter) { - if (tdengineWriter == null) { - throw new IllegalArgumentException("tdengineWriter must not be null"); - } - this.tdengineWriter = tdengineWriter; - } - - public void ingest(byte[] kafkaValue) throws IOException { - VehicleEnvelope envelope = parse(kafkaValue); - writeTdengineFacts(List.of(envelope)); - } - - @Override - public EnvelopeIngestResult tryIngest(byte[] kafkaValue) { - return tryIngestAll(List.of(kafkaValue)).getFirst(); - } - - @Override - public List tryIngestAll(List kafkaValues) { - if (kafkaValues == null || kafkaValues.isEmpty()) { - return List.of(); - } - List results = new ArrayList<>(kafkaValues.size()); - List valid = new ArrayList<>(kafkaValues.size()); - for (byte[] kafkaValue : kafkaValues) { - VehicleEnvelope envelope = null; - try { - envelope = parse(kafkaValue); - results.add(null); - valid.add(new BatchEntry(results.size() - 1, envelope)); - } catch (IllegalArgumentException ex) { - results.add(envelope == null - ? EnvelopeIngestResult.invalid(ex.getMessage()) - : EnvelopeIngestResult.skipped(envelope.getEventId(), envelope.getVin(), ex.getMessage())); - } - } - if (valid.isEmpty()) { - return List.copyOf(results); - } - try { - writeTdengineFacts(valid.stream().map(BatchEntry::envelope).toList()); - for (BatchEntry entry : valid) { - results.set(entry.index(), EnvelopeIngestResult.stored( - entry.eventId(), - entry.vin())); - } - } catch (IOException ex) { - log.error("event history TDengine ingest failed batchSize={} validSize={} firstEventId={} firstVin={}", - kafkaValues.size(), - valid.size(), - valid.getFirst().envelope().getEventId(), - valid.getFirst().envelope().getVin(), - ex); - for (BatchEntry entry : valid) { - results.set(entry.index(), EnvelopeIngestResult.failed( - entry.envelope().getEventId(), - entry.envelope().getVin(), - ex.getMessage())); - } - } - return List.copyOf(results); - } - - private record BatchEntry(int index, VehicleEnvelope envelope) { - private String eventId() { - return envelope.getEventId(); - } - - private String vin() { - return envelope.getVin(); - } - } - - private static VehicleEnvelope parse(byte[] kafkaValue) { - if (kafkaValue == null || kafkaValue.length == 0) { - throw new IllegalArgumentException("VehicleEnvelope bytes must not be empty"); - } - try { - return VehicleEnvelope.parseFrom(kafkaValue); - } catch (InvalidProtocolBufferException e) { - throw new IllegalArgumentException("failed to parse VehicleEnvelope", e); - } - } - - private void writeTdengineFacts(List envelopes) throws IOException { - var rawFrames = envelopes.stream() - .flatMap(envelope -> TdengineEnvelopeRows.rawFrame(envelope).stream()) - .toList(); - if (!rawFrames.isEmpty()) { - tdengineWriter.appendRawFrames(rawFrames); - } - var locations = envelopes.stream() - .flatMap(envelope -> TdengineEnvelopeRows.location(envelope).stream()) - .toList(); - if (!locations.isEmpty()) { - tdengineWriter.appendLocations(locations); - } - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameService.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameService.java deleted file mode 100644 index 562f21db..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameService.java +++ /dev/null @@ -1,865 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message; -import com.lingniu.ingest.protocol.gb32960.model.InfoBlock; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -/** - * GB32960 RAW 历史帧查询和业务快照组装服务。 - * - *

    TDengine raw_frames 保存 archive:// URI。查询时先从 TDengine 找到 RAW 索引, - * 再读取本地 RAW .bin,用当前协议解析器即时解码。 - * 这种设计让写入路径保持轻量,也允许后续修复解析器后直接重放历史 RAW 得到新字段。 - * - *

    snapshot 的聚合单位是 {@code vin + eventTime}。同一时刻可能有多个 32960 子包, - * 比如广东燃料电池堆电压分帧;这里会把这些子包合并成前端可消费的单个逻辑快照。 - */ -public final class Gb32960DecodedFrameService { - - private static final Logger log = LoggerFactory.getLogger(Gb32960DecodedFrameService.class); - private static final TypeReference> MAP_TYPE = new TypeReference<>() {}; - private static final int OUTPUT_DOUBLE_SCALE = 6; - private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai"); - - private final TdengineHistoryReader tdengineReader; - private final Gb32960MessageDecoder decoder; - private final Path archiveRoot; - private final ObjectMapper objectMapper; - - public Gb32960DecodedFrameService(TdengineHistoryReader tdengineReader, - Gb32960MessageDecoder decoder, - Path archiveRoot, - ObjectMapper objectMapper) { - if (tdengineReader == null) { - log.info("gb32960 decoded frame service has no history index backend; rawUri direct lookup remains available"); - } else { - log.info("gb32960 decoded frame service using TDengine raw_frames index"); - } - if (decoder == null) { - throw new IllegalArgumentException("decoder must not be null"); - } - if (archiveRoot == null) { - throw new IllegalArgumentException("archiveRoot must not be null"); - } - this.tdengineReader = tdengineReader; - this.decoder = decoder; - this.archiveRoot = archiveRoot.toAbsolutePath().normalize(); - this.objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper; - } - - public List query(LocalDate dateFrom, - LocalDate dateTo, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount) throws IOException { - return query(dateFrom, dateTo, null, null, order, limit, vin, platformAccount); - } - - public List query(LocalDate dateFrom, - LocalDate dateTo, - java.time.Instant eventTimeFrom, - java.time.Instant eventTimeTo, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount) throws IOException { - int frameLimit = Math.max(1, Math.min(limit, 1000)); - if (tdengineReader == null) { - return List.of(); - } - return queryFromTdengine(dateFrom, dateTo, eventTimeFrom, eventTimeTo, order, frameLimit, vin, platformAccount); - } - - public DecodedFramePage queryPage(QueryTimeRange range, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount, - TdenginePageCursor cursor) throws IOException { - int frameLimit = Math.max(1, Math.min(limit, 1000)); - if (tdengineReader == null || vin == null || vin.isBlank()) { - return new DecodedFramePage(List.of(), null); - } - return queryPageFromTdengine( - range.dateFrom(), - range.dateTo(), - range.eventTimeFrom(), - range.eventTimeTo(), - order, - frameLimit, - vin, - platformAccount, - cursor); - } - - private List queryFromTdengine(LocalDate dateFrom, - LocalDate dateTo, - java.time.Instant eventTimeFrom, - java.time.Instant eventTimeTo, - HistoryQueryOrder order, - int frameLimit, - String vin, - String platformAccount) throws IOException { - String vehicleKey = vin == null ? "" : vin.trim(); - TdenginePage page = tdengineReader.queryRawFrames(new TdengineRawFrameQuery( - "GB32960", - vehicleKey, - fromInstant(dateFrom, eventTimeFrom), - toExclusiveInstant(dateTo, eventTimeTo), - tdengineOrder(order), - frameLimit, - null)); - List out = new ArrayList<>(page.items().size()); - LinkedHashSet seenUris = new LinkedHashSet<>(); - for (TdengineRawFrameRow row : page.items()) { - String rawArchiveUri = row.rawUri(); - if (rawArchiveUri == null || rawArchiveUri.isBlank() || !seenUris.add(rawArchiveUri)) { - continue; - } - DecodedFrame frame = decodeIfAvailable(row, platformAccount); - if (frame != null) { - out.add(frame); - } - } - return out; - } - - private DecodedFramePage queryPageFromTdengine(LocalDate dateFrom, - LocalDate dateTo, - java.time.Instant eventTimeFrom, - java.time.Instant eventTimeTo, - HistoryQueryOrder order, - int frameLimit, - String vin, - String platformAccount, - TdenginePageCursor cursor) throws IOException { - TdenginePage page = tdengineReader.queryRawFrames(new TdengineRawFrameQuery( - "GB32960", - vin.trim(), - fromInstant(dateFrom, eventTimeFrom), - toExclusiveInstant(dateTo, eventTimeTo), - tdengineOrder(order), - frameLimit, - cursor)); - List out = new ArrayList<>(page.items().size()); - LinkedHashSet seenUris = new LinkedHashSet<>(); - for (TdengineRawFrameRow row : page.items()) { - String rawArchiveUri = row.rawUri(); - if (rawArchiveUri == null || rawArchiveUri.isBlank() || !seenUris.add(rawArchiveUri)) { - continue; - } - DecodedFrame frame = decodeIfAvailable(row, platformAccount); - if (frame != null) { - out.add(frame); - } - } - CursorResponse next = page.nextCursor() - .map(value -> new CursorResponse(value.ts().toString(), value.tieBreaker())) - .orElse(null); - return new DecodedFramePage(out, next); - } - - public List snapshots(QueryTimeRange range, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount) throws IOException { - return snapshots(range.dateFrom(), range.dateTo(), range.eventTimeFrom(), range.eventTimeTo(), - order, limit, vin, platformAccount); - } - - public List snapshotFields(QueryTimeRange range, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount, - String fields) throws IOException { - List selectedFields = parseSelectedFields(fields); - if (selectedFields.isEmpty()) { - throw new IllegalArgumentException("fields is required for gb32960 snapshot field query"); - } - validateSelectedFields(selectedFields); - return snapshots(range, order, limit, vin, platformAccount).stream() - .map(snapshot -> FieldSnapshot.from(snapshot, selectedFields)) - .toList(); - } - - public String snapshotFieldsCsv(QueryTimeRange range, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount, - String fields) throws IOException { - List selectedFields = parseSelectedFields(fields); - if (selectedFields.isEmpty()) { - throw new IllegalArgumentException("fields is required for gb32960 snapshot field query"); - } - validateSelectedFields(selectedFields); - List snapshots = snapshots(range, order, limit, vin, platformAccount).stream() - .map(snapshot -> FieldSnapshot.from(snapshot, selectedFields)) - .toList(); - Map headers = chineseHeadersByField(); - StringBuilder csv = new StringBuilder(256 + snapshots.size() * Math.max(128, selectedFields.size() * 32)); - csv.append("VIN,事件时间,首次接收时间,最后接收时间,原始包"); - for (String field : selectedFields) { - csv.append(',').append(csv(headers.getOrDefault(field, field))); - } - csv.append('\n'); - for (FieldSnapshot snapshot : snapshots) { - csv.append(csv(snapshot.vin())).append(',') - .append(csv(snapshot.eventTime())).append(',') - .append(csv(snapshot.ingestTimeFirst())).append(',') - .append(csv(snapshot.ingestTimeLast())).append(',') - .append(csv(sourceFrameUris(snapshot.sourceFrames()))); - for (String field : selectedFields) { - csv.append(',').append(csv(csvValue(snapshot.fields().get(field)))); - } - csv.append('\n'); - } - return csv.toString(); - } - - public DecodedFrame findByRawArchiveUri(String rawArchiveUri, String platformAccount) throws IOException { - if (rawArchiveUri == null || rawArchiveUri.isBlank()) { - return null; - } - return decodeWithoutIndex(rawArchiveUri, platformAccount); - } - - public List snapshots(LocalDate dateFrom, - LocalDate dateTo, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount) throws IOException { - if (vin == null || vin.isBlank()) { - throw new IllegalArgumentException("vin is required for gb32960 snapshots"); - } - return snapshots(dateFrom, dateTo, null, null, order, limit, vin, platformAccount); - } - - private List snapshots(LocalDate dateFrom, - LocalDate dateTo, - java.time.Instant eventTimeFrom, - java.time.Instant eventTimeTo, - HistoryQueryOrder order, - int limit, - String vin, - String platformAccount) throws IOException { - if (vin == null || vin.isBlank()) { - throw new IllegalArgumentException("vin is required for gb32960 snapshots"); - } - int snapshotLimit = Math.max(1, Math.min(limit, 1000)); - // 经验上一个完整 snapshot 可能由多个 RAW 子包组成,查询帧数需要放大后再按 snapshot 截断。 - List frames = query(dateFrom, dateTo, eventTimeFrom, eventTimeTo, - order, snapshotLimit * 30, vin, platformAccount); - LinkedHashMap builders = new LinkedHashMap<>(); - for (DecodedFrame frame : frames) { - if (frame.eventTime() == null || frame.eventTime().isBlank()) { - continue; - } - String key = frame.vin() + "|" + frame.eventTime(); - builders.computeIfAbsent(key, ignored -> new SnapshotBuilder(frame)).add(frame); - } - List out = new ArrayList<>(snapshotLimit); - for (SnapshotBuilder builder : builders.values()) { - out.add(builder.build()); - if (out.size() >= snapshotLimit) { - break; - } - } - return out; - } - - private DecodedFrame decodeIfAvailable(TdengineRawFrameRow row, String platformAccount) throws IOException { - try { - return decode(row, platformAccount); - } catch (NoSuchFileException e) { - log.warn("skip gb32960 frame because raw archive is missing frameId={} rawArchiveUri={}", - row.frameId(), row.rawUri()); - return null; - } - } - - private DecodedFrame decode(TdengineRawFrameRow row, String platformAccount) throws IOException { - Path rawPath = archivePath(row.rawUri()); - byte[] rawBytes = Files.readAllBytes(rawPath); - Gb32960Message message = decoder.decode(ByteBuffer.wrap(rawBytes), platformAccount(row, platformAccount)); - List> blocks = new ArrayList<>(); - for (InfoBlock block : message.infoBlocks()) { - blocks.add(blockJson(block)); - } - Instant ingestTime = row.receivedAt() == null ? Files.getLastModifiedTime(rawPath).toInstant() : row.receivedAt(); - return new DecodedFrame( - row.frameId(), - message.header().vin(), - message.header().command().name(), - message.header().responseFlag().name(), - message.header().protocolVersion().name(), - message.header().encryptType().name(), - message.header().dataLength(), - message.header().eventTime() == null ? null : message.header().eventTime().toString(), - ingestTime.toString(), - row.rawUri(), - rawBytes.length, - blocks); - } - - private DecodedFrame decodeWithoutIndex(String rawArchiveUri, String platformAccount) throws IOException { - Path rawPath = archivePath(rawArchiveUri); - byte[] rawBytes = Files.readAllBytes(rawPath); - Gb32960Message message = decoder.decode(ByteBuffer.wrap(rawBytes), platformAccount); - List> blocks = new ArrayList<>(); - for (InfoBlock block : message.infoBlocks()) { - blocks.add(blockJson(block)); - } - String ingestTime = Files.getLastModifiedTime(rawPath).toInstant().toString(); - return new DecodedFrame( - rawArchiveEventId(rawArchiveUri), - message.header().vin(), - message.header().command().name(), - message.header().responseFlag().name(), - message.header().protocolVersion().name(), - message.header().encryptType().name(), - message.header().dataLength(), - message.header().eventTime() == null ? null : message.header().eventTime().toString(), - ingestTime, - rawArchiveUri, - rawBytes.length, - blocks); - } - - private Map blockJson(InfoBlock block) { - Map json = new LinkedHashMap<>(); - json.put("type", block.type().name()); - json.put("className", block.getClass().getSimpleName()); - json.put("data", normalizeJsonValue(objectMapper.convertValue(block, MAP_TYPE))); - if (block instanceof InfoBlock.Raw raw) { - json.put("typeCode", raw.typeCode()); - json.put("bytesHex", hex(raw.bytes())); - } - return json; - } - - private static List parseSelectedFields(String fields) { - if (fields == null || fields.isBlank()) { - return List.of(); - } - List out = new ArrayList<>(); - for (String field : fields.split(",")) { - String normalized = field.trim(); - if (!normalized.isBlank()) { - out.add(normalized); - } - } - return List.copyOf(out); - } - - private static void validateSelectedFields(List selectedFields) { - LinkedHashSet knownFields = new LinkedHashSet<>(); - for (Gb32960FieldDictionary.Packet packet : Gb32960FieldDictionary.packets()) { - for (Gb32960FieldDictionary.Field field : packet.fields()) { - knownFields.add(packet.code() + "." + field.key()); - } - } - List unknown = selectedFields.stream() - .filter(field -> !knownFields.contains(field)) - .toList(); - if (!unknown.isEmpty()) { - throw new IllegalArgumentException("unknown gb32960 snapshot fields: " + String.join(",", unknown)); - } - } - - private static Map chineseHeadersByField() { - Map headers = new LinkedHashMap<>(); - for (Gb32960FieldDictionary.Packet packet : Gb32960FieldDictionary.packets()) { - for (Gb32960FieldDictionary.Field field : packet.fields()) { - String header = packet.nameZh() + "-" + field.nameZh(); - if (field.unit() != null && !field.unit().isBlank()) { - header += "(" + field.unit() + ")"; - } - headers.put(packet.code() + "." + field.key(), header); - } - } - return headers; - } - - private static String sourceFrameUris(List sourceFrames) { - if (sourceFrames == null || sourceFrames.isEmpty()) { - return ""; - } - return sourceFrames.stream() - .map(SourceFrame::rawArchiveUri) - .filter(uri -> uri != null && !uri.isBlank()) - .reduce((left, right) -> left + ";" + right) - .orElse(""); - } - - private static String csvValue(Object value) { - if (value == null) { - return ""; - } - if (value instanceof Iterable iterable) { - List values = new ArrayList<>(); - for (Object item : iterable) { - values.add(String.valueOf(item)); - } - return String.join(";", values); - } - return String.valueOf(value); - } - - private static String csv(String value) { - if (value == null) { - return ""; - } - boolean quote = value.indexOf(',') >= 0 - || value.indexOf('"') >= 0 - || value.indexOf('\n') >= 0 - || value.indexOf('\r') >= 0; - if (!quote) { - return value; - } - return "\"" + value.replace("\"", "\"\"") + "\""; - } - - static Object normalizeJsonValue(Object value) { - if (value instanceof Double d) { - return normalizeFloatingPoint(d); - } - if (value instanceof Float f) { - return normalizeFloatingPoint(f.doubleValue()); - } - if (value instanceof Map map) { - Map normalized = new LinkedHashMap<>(); - for (Map.Entry entry : map.entrySet()) { - normalized.put(String.valueOf(entry.getKey()), normalizeJsonValue(entry.getValue())); - } - return normalized; - } - if (value instanceof List list) { - List normalized = new ArrayList<>(list.size()); - for (Object item : list) { - normalized.add(normalizeJsonValue(item)); - } - return normalized; - } - return value; - } - - private static BigDecimal normalizeFloatingPoint(double value) { - if (!Double.isFinite(value)) { - return BigDecimal.ZERO; - } - // 协议缩放后的 double 容易出现 12.300000000000002;对外统一保留 6 位并去掉尾零。 - BigDecimal normalized = BigDecimal.valueOf(value) - .setScale(OUTPUT_DOUBLE_SCALE, RoundingMode.HALF_UP) - .stripTrailingZeros(); - return normalized.scale() < 0 ? normalized.setScale(0) : normalized; - } - - @SuppressWarnings("unchecked") - private Map deepCopy(Map value) { - return objectMapper.convertValue(value, MAP_TYPE); - } - - private Path archivePath(String rawArchiveUri) throws IOException { - String key = rawArchiveUri.startsWith("archive://") - ? rawArchiveUri.substring("archive://".length()) - : rawArchiveUri; - Path path = archiveRoot.resolve(key.replace("..", "_").replaceAll("^/+", "")).normalize(); - // archive:// 是外部输入,必须限制在 archiveRoot 内,避免通过 ../ 读取任意文件。 - if (!path.startsWith(archiveRoot)) { - throw new IOException("raw archive path escapes root: " + rawArchiveUri); - } - return path; - } - - private String platformAccount(TdengineRawFrameRow row, String override) { - if (override != null && !override.isBlank()) { - return override; - } - if (row.metadataJson() == null || row.metadataJson().isBlank()) { - return null; - } - try { - return objectMapper.readValue(row.metadataJson(), MAP_TYPE).getOrDefault("platformAccount", "").toString(); - } catch (IOException ignored) { - return null; - } - } - - private static Instant fromInstant(LocalDate dateFrom, Instant eventTimeFrom) { - return eventTimeFrom != null ? eventTimeFrom : dateFrom.atStartOfDay(DEFAULT_ZONE).toInstant(); - } - - private static Instant toExclusiveInstant(LocalDate dateTo, Instant eventTimeTo) { - Instant inclusive = eventTimeTo != null - ? eventTimeTo - : dateTo.plusDays(1).atStartOfDay(DEFAULT_ZONE).toInstant().minusMillis(1); - return inclusive.plusMillis(1); - } - - private static TdengineQueryOrder tdengineOrder(HistoryQueryOrder order) { - return order == HistoryQueryOrder.ASC ? TdengineQueryOrder.ASC : TdengineQueryOrder.DESC; - } - - private static String rawArchiveEventId(String rawArchiveUri) { - if (rawArchiveUri == null || rawArchiveUri.isBlank()) { - return ""; - } - String key = rawArchiveUri.startsWith("archive://") - ? rawArchiveUri.substring("archive://".length()) - : rawArchiveUri; - int slash = key.lastIndexOf('/'); - String fileName = slash >= 0 ? key.substring(slash + 1) : key; - return fileName.endsWith(".bin") ? fileName.substring(0, fileName.length() - 4) : fileName; - } - - private static String hex(byte[] bytes) { - if (bytes == null || bytes.length == 0) { - return ""; - } - StringBuilder out = new StringBuilder(bytes.length * 2); - for (byte b : bytes) { - out.append(Character.forDigit((b >> 4) & 0xF, 16)); - out.append(Character.forDigit(b & 0xF, 16)); - } - return out.toString(); - } - - public record DecodedFrame( - String eventId, - String vin, - String command, - String responseFlag, - String protocolVersion, - String encryptType, - int dataLength, - String eventTime, - String ingestTime, - String rawArchiveUri, - int rawSizeBytes, - List> blocks) { - } - - public record CursorResponse(String ts, String id) { - } - - public record DecodedFramePage( - List items, - CursorResponse nextCursor) { - } - - public record SourceFrame( - String eventId, - String ingestTime, - String rawArchiveUri, - int rawSizeBytes, - List blockTypes) { - } - - public record LogicalSnapshot( - String vin, - String command, - String responseFlag, - String protocolVersion, - String encryptType, - String eventTime, - String ingestTimeFirst, - String ingestTimeLast, - List sourceFrames, - List> blocks) { - } - - public record FieldSnapshot( - String vin, - String eventTime, - String ingestTimeFirst, - String ingestTimeLast, - List sourceFrames, - Map fields) { - - private static FieldSnapshot from(LogicalSnapshot snapshot, List selectedFields) { - Map values = new LinkedHashMap<>(); - for (String selectedField : selectedFields) { - values.put(selectedField, findField(snapshot.blocks(), selectedField)); - } - return new FieldSnapshot( - snapshot.vin(), - snapshot.eventTime(), - snapshot.ingestTimeFirst(), - snapshot.ingestTimeLast(), - snapshot.sourceFrames(), - values); - } - - private static Object findField(List> blocks, String selectedField) { - int dot = selectedField.indexOf('.'); - if (dot <= 0 || dot == selectedField.length() - 1) { - return null; - } - String type = selectedField.substring(0, dot); - String path = selectedField.substring(dot + 1); - for (Map block : blocks) { - if (type.equals(block.get("type"))) { - return valueAt(block.get("data"), path.split("\\.")); - } - } - return null; - } - - private static Object valueAt(Object current, String[] path) { - Object value = current; - for (String part : path) { - value = step(value, part); - if (value == null) { - return null; - } - } - return value; - } - - @SuppressWarnings("unchecked") - private static Object step(Object value, String part) { - if (value instanceof Map map) { - return map.get(part); - } - if (value instanceof List list) { - if (part.matches("\\d+")) { - int index = Integer.parseInt(part); - return index >= 0 && index < list.size() ? list.get(index) : null; - } - // 对数组字段支持 "stacks.cellVoltages" 这类投影,返回每个元素上对应属性的列表。 - List out = new ArrayList<>(list.size()); - for (Object item : list) { - if (item instanceof Map itemMap) { - out.add(((Map) itemMap).get(part)); - } - } - return out; - } - return null; - } - } - - private final class SnapshotBuilder { - private final DecodedFrame first; - private final List sourceFrames = new ArrayList<>(); - private final LinkedHashMap> blocks = new LinkedHashMap<>(); - - private SnapshotBuilder(DecodedFrame first) { - this.first = first; - } - - private void add(DecodedFrame frame) { - sourceFrames.add(new SourceFrame( - frame.eventId(), - frame.ingestTime(), - frame.rawArchiveUri(), - frame.rawSizeBytes(), - frame.blocks().stream().map(block -> String.valueOf(block.get("type"))).toList())); - for (Map block : frame.blocks()) { - String type = String.valueOf(block.get("type")); - if ("GD_FC_STACK".equals(type)) { - // 广东燃料电池堆电压可能按帧号拆成多个 RAW,这里按 cell 起始序号合并。 - mergeStack(block); - } else { - // 非分帧块同一 snapshot 内只保留第一次出现的块,避免后到空值覆盖前面完整值。 - blocks.putIfAbsent(type, deepCopy(block)); - } - } - } - - private void mergeStack(Map block) { - Map current = blocks.get("GD_FC_STACK"); - if (current == null) { - blocks.put("GD_FC_STACK", deepCopy(block)); - return; - } - Map currentData = map(current.get("data")); - Map incomingData = map(block.get("data")); - List> currentStacks = listOfMaps(currentData.get("stacks")); - List> incomingStacks = listOfMaps(incomingData.get("stacks")); - for (int i = 0; i < incomingStacks.size(); i++) { - if (i >= currentStacks.size()) { - currentStacks.add(deepCopy(incomingStacks.get(i))); - } else { - mergeStackItem(currentStacks.get(i), incomingStacks.get(i)); - } - } - currentData.put("stackCount", currentStacks.size()); - } - - private void mergeStackItem(Map target, Map incoming) { - int targetStart = intValue(target.get("frameCellStart"), 1); - int incomingStart = intValue(incoming.get("frameCellStart"), 1); - List targetVoltages = doubleList(target.get("frameCellVoltagesV")); - List incomingVoltages = doubleList(incoming.get("frameCellVoltagesV")); - // GB/T 分帧的起始序号是 1-based;内部 List 是 0-based,所以写入时统一减一。 - int cellCount = Math.max(intValue(target.get("cellCount"), 0), intValue(incoming.get("cellCount"), 0)); - int needed = Math.max(cellCount, Math.max( - targetStart - 1 + targetVoltages.size(), - incomingStart - 1 + incomingVoltages.size())); - List merged = new ArrayList<>(needed); - for (int i = 0; i < needed; i++) { - merged.add(null); - } - putVoltages(merged, targetStart, targetVoltages); - putVoltages(merged, incomingStart, incomingVoltages); - while (!merged.isEmpty() && merged.getLast() == null) { - merged.removeLast(); - } - target.put("frameCellStart", 1); - target.put("frameCellCount", merged.size()); - target.put("frameCellVoltagesV", merged); - target.put("cellCount", Math.max(cellCount, merged.size())); - normalizeStackItem(target); - } - - private LogicalSnapshot build() { - List orderedFrames = sourceFrames.stream() - .sorted(Comparator.comparing(SourceFrame::ingestTime)) - .toList(); - Map stackBlock = blocks.get("GD_FC_STACK"); - if (stackBlock != null) { - normalizeStackBlock(stackBlock); - } - List> orderedBlocks = new ArrayList<>(blocks.values()); - orderedBlocks.sort(Comparator.comparingInt(block -> blockOrder(String.valueOf(block.get("type"))))); - List> normalizedBlocks = orderedBlocks.stream() - .map(block -> map(normalizeJsonValue(block))) - .toList(); - return new LogicalSnapshot( - first.vin(), - first.command(), - first.responseFlag(), - first.protocolVersion(), - first.encryptType(), - first.eventTime(), - orderedFrames.isEmpty() ? null : orderedFrames.getFirst().ingestTime(), - orderedFrames.isEmpty() ? null : orderedFrames.getLast().ingestTime(), - sourceFrames, - normalizedBlocks); - } - } - - private static void normalizeStackBlock(Map stackBlock) { - Map data = map(stackBlock.get("data")); - for (Map stack : listOfMaps(data.get("stacks"))) { - normalizeStackItem(stack); - } - } - - private static void normalizeStackItem(Map target) { - int start = intValue(target.get("frameCellStart"), 1); - List voltages = doubleList(target.get("frameCellVoltagesV")); - int cellCount = intValue(target.get("cellCount"), 0); - int needed = Math.max(cellCount, start - 1 + voltages.size()); - List normalized = new ArrayList<>(needed); - for (int i = 0; i < needed; i++) { - normalized.add(null); - } - putVoltages(normalized, start, voltages); - while (cellCount == 0 && !normalized.isEmpty() && normalized.getLast() == null) { - normalized.removeLast(); - } - long missing = normalized.stream().filter(item -> item == null).count(); - // 给前端明确的完整性标记,避免只能靠数组长度猜测是否缺帧。 - target.put("frameCellStart", 1); - target.put("frameCellCount", normalized.size()); - target.put("frameCellVoltagesV", normalized); - target.put("cellCount", Math.max(cellCount, normalized.size())); - target.put("frameCellComplete", missing == 0 && (cellCount == 0 || normalized.size() >= cellCount)); - target.put("frameCellMissingCount", missing); - } - - @SuppressWarnings("unchecked") - private static Map map(Object value) { - return value instanceof Map m ? (Map) m : new LinkedHashMap<>(); - } - - @SuppressWarnings("unchecked") - private static List> listOfMaps(Object value) { - return value instanceof List list ? (List>) list : new ArrayList<>(); - } - - private static int intValue(Object value, int fallback) { - return value instanceof Number n ? n.intValue() : fallback; - } - - private static List doubleList(Object value) { - if (!(value instanceof List list)) { - return List.of(); - } - List out = new ArrayList<>(list.size()); - for (Object item : list) { - out.add(item instanceof Number n ? n.doubleValue() : null); - } - return out; - } - - private static void putVoltages(List target, int start, List values) { - int offset = Math.max(0, start - 1); - for (int i = 0; i < values.size(); i++) { - int index = offset + i; - if (index >= target.size()) { - target.add(values.get(i)); - } else if (values.get(i) != null) { - target.set(index, values.get(i)); - } - } - } - - private static int blockOrder(String type) { - return switch (type) { - case "VEHICLE" -> 10; - case "DRIVE_MOTOR" -> 20; - case "FUEL_CELL_V2016", "FUEL_CELL_V2025" -> 30; - case "ENGINE" -> 40; - case "POSITION_V2016", "POSITION_V2025" -> 50; - case "EXTREME_V2016" -> 60; - case "ALARM_V2016", "ALARM_V2025" -> 70; - case "VOLTAGE_V2016", "MIN_PARALLEL_VOLTAGE_V2025" -> 80; - case "TEMPERATURE_V2016", "BATTERY_TEMPERATURE_V2025" -> 90; - case "FUEL_CELL_STACK", "GD_FC_STACK" -> 100; - case "SUPER_CAPACITOR" -> 110; - case "SUPER_CAPACITOR_EXTREME" -> 120; - case "GD_FC_AUXILIARY" -> 130; - case "GD_FC_DCDC" -> 140; - case "GD_FC_AIR_CONDITIONER" -> 150; - case "GD_FC_VEHICLE_INFO" -> 160; - case "GD_FC_DEMO_EXTENSION" -> 170; - case "GD_FC_VENDOR_TLV" -> 180; - default -> 1000; - }; - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionary.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionary.java deleted file mode 100644 index f131a1d0..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionary.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import java.util.List; - -/** - * GB32960 字段字典。 - * - *

    字典是前端字段选择器、中文表头、Tooltip、枚举/位图展示的单一来源。 - * 字段 key 必须和 {@link Gb32960DecodedFrameService} 输出的 block data 路径一致; - * 支持点号路径和数组投影路径,例如 {@code GD_FC_STACK.stacks.frameCellVoltagesV}。 - */ -public final class Gb32960FieldDictionary { - - private Gb32960FieldDictionary() { - } - - public static List packets() { - return PACKETS; - } - - private static final List PACKETS = List.of( - // 标准 32960 字段优先放前面;广东燃料电池扩展字段放后面,方便前端按常用程度展示。 - packet("VEHICLE", "整车数据", "GB/T 32960 整车运行状态、车速、里程、电压、电流、SOC 等核心字段。", - field("vehicleState", "车辆状态", "", "车辆运行、停止等状态编码。", - mappings( - mapping("01", "1", "启动", "车辆处于可行驶启动状态。"), - mapping("02", "2", "熄火", "车辆处于熄火状态。"), - mapping("03", "3", "其他", "车辆处于其他状态。"), - mapping("FE", "254", "异常", "协议异常值。"), - mapping("FF", "255", "无效", "协议无效值。"))), - field("chargingState", "充电状态", "", "车辆充电状态编码。", - mappings( - mapping("01", "1", "停车充电", "车辆停车充电。"), - mapping("02", "2", "行驶充电", "车辆行驶充电。"), - mapping("03", "3", "未充电", "车辆未充电。"), - mapping("04", "4", "充电完成", "车辆充电完成。"), - mapping("FE", "254", "异常", "协议异常值。"), - mapping("FF", "255", "无效", "协议无效值。"))), - field("runningMode", "运行模式", "", "纯电、混动、燃料电池等运行模式编码。", - mappings( - mapping("01", "1", "纯电", "纯电驱动模式。"), - mapping("02", "2", "混合动力", "混合动力驱动模式。"), - mapping("03", "3", "燃料电池", "燃料电池驱动模式。"), - mapping("FE", "254", "异常", "协议异常值。"), - mapping("FF", "255", "无效", "协议无效值。"))), - field("speedKmh", "车速", "km/h", "车辆当前速度。"), - field("totalMileageKm", "累计里程", "km", "车辆累计行驶里程。"), - field("totalVoltageV", "总电压", "V", "动力系统总电压。"), - field("totalCurrentA", "总电流", "A", "动力系统总电流。"), - field("socPercent", "SOC", "%", "动力电池荷电状态。"), - field("dcDcStatus", "DC/DC 状态", "", "DC/DC 工作状态编码。", - mappings( - mapping("01", "1", "工作", "DC/DC 处于工作状态。"), - mapping("02", "2", "断开", "DC/DC 处于断开状态。"), - mapping("FE", "254", "异常", "协议异常值。"), - mapping("FF", "255", "无效", "协议无效值。"))), - field("gearRaw", "挡位原始值", "", "挡位、驱动力和制动力组合原始编码。"), - field("insulationResistanceKohm", "绝缘电阻", "kOhm", "高压系统绝缘电阻。"), - field("acceleratorPedal", "加速踏板行程", "%", "2016 版字段,加速踏板开度。"), - field("brakePedal", "制动踏板状态", "%", "2016 版字段,制动踏板状态或开度。")), - packet("DRIVE_MOTOR", "驱动电机数据", "GB/T 32960 驱动电机控制器和电机运行状态。", - field("motors.serialNo", "驱动电机序号", "", "电机编号。"), - field("motors.state", "驱动电机状态", "", "电机工作状态编码。", - mappings( - mapping("01", "1", "耗电", "驱动电机耗电。"), - mapping("02", "2", "发电", "驱动电机发电。"), - mapping("03", "3", "关闭", "驱动电机关闭。"), - mapping("04", "4", "准备", "驱动电机准备。"), - mapping("FE", "254", "异常", "协议异常值。"), - mapping("FF", "255", "无效", "协议无效值。"))), - field("motors.controllerTempC", "控制器温度", "C", "驱动电机控制器温度。"), - field("motors.rpm", "转速", "rpm", "驱动电机转速。"), - field("motors.torqueNm", "转矩", "N.m", "驱动电机输出转矩。"), - field("motors.motorTempC", "电机温度", "C", "驱动电机温度。"), - field("motors.controllerInputVoltageV", "控制器输入电压", "V", "电机控制器直流母线输入电压。"), - field("motors.controllerDcCurrentA", "控制器直流母线电流", "A", "电机控制器直流母线电流。")), - packet("FUEL_CELL_V2016", "燃料电池数据(2016)", "GB/T 32960.3-2016 燃料电池电压、电流、氢耗、温度、浓度、压力等字段。", - field("fcVoltageV", "燃料电池电压", "V", "燃料电池系统输出电压。"), - field("fcCurrentA", "燃料电池电流", "A", "燃料电池系统输出电流。"), - field("hydrogenConsumptionKgPer100km", "氢耗", "kg/100km", "燃料电池氢气消耗率。"), - field("probeTempC", "探针温度", "C", "燃料电池温度探针列表。"), - field("maxHydrogenSystemTempC", "氢系统最高温度", "C", "氢系统最高温度。"), - field("maxHydrogenConcentrationPercent", "最高氢浓度", "%", "氢气浓度最高值。"), - field("maxHydrogenPressureMpa", "最高氢压力", "MPa", "氢系统最高压力。"), - field("hvDcDcStatus", "高压 DC/DC 状态", "", "燃料电池高压 DC/DC 状态。")), - packet("POSITION_V2016", "车辆位置数据(2016)", "GB/T 32960.3-2016 定位状态和经纬度。", - field("statusFlag", "定位状态标志", "", "定位有效性及经纬度方向标志。", - mappings( - mapping("bit0=0", "bit0=0", "有效定位", "定位状态有效。"), - mapping("bit0=1", "bit0=1", "无效定位", "定位状态无效。"), - mapping("bit1=0", "bit1=0", "北纬", "纬度方向为北纬。"), - mapping("bit1=1", "bit1=1", "南纬", "纬度方向为南纬。"), - mapping("bit2=0", "bit2=0", "东经", "经度方向为东经。"), - mapping("bit2=1", "bit2=1", "西经", "经度方向为西经。"))), - field("longitude", "经度", "deg", "车辆经度。"), - field("latitude", "纬度", "deg", "车辆纬度。")), - packet("POSITION_V2025", "车辆位置数据(2025)", "GB/T 32960.3-2025 定位状态、坐标系和经纬度。", - field("statusFlag", "定位状态标志", "", "定位有效性及经纬度方向标志。", - mappings( - mapping("bit0=0", "bit0=0", "有效定位", "定位状态有效。"), - mapping("bit0=1", "bit0=1", "无效定位", "定位状态无效。"), - mapping("bit1=0", "bit1=0", "北纬", "纬度方向为北纬。"), - mapping("bit1=1", "bit1=1", "南纬", "纬度方向为南纬。"), - mapping("bit2=0", "bit2=0", "东经", "经度方向为东经。"), - mapping("bit2=1", "bit2=1", "西经", "经度方向为西经。"))), - field("coordSystem", "坐标系", "", "2025 版新增坐标系编码。", - mappings( - mapping("01", "1", "WGS84", "WGS84 坐标系。"), - mapping("02", "2", "GCJ-02", "GCJ-02 坐标系。"), - mapping("03", "3", "其他", "其他坐标系。"))), - field("longitude", "经度", "deg", "车辆经度。"), - field("latitude", "纬度", "deg", "车辆纬度。")), - packet("ALARM_V2016", "报警数据(2016)", "GB/T 32960.3-2016 最高报警等级、通用报警位和故障码列表。", - field("maxLevel", "最高报警等级", "", "当前最高报警等级。", - mappings( - mapping("00", "0", "无故障", "当前无报警故障。"), - mapping("01", "1", "一级故障", "一级报警故障。"), - mapping("02", "2", "二级故障", "二级报警故障。"), - mapping("03", "3", "三级故障", "三级报警故障。"))), - field("generalAlarmFlag", "通用报警标志", "", "通用报警位图。"), - field("batteryFaults", "可充电储能装置故障码", "", "动力电池相关故障码列表。"), - field("motorFaults", "驱动电机故障码", "", "驱动电机相关故障码列表。"), - field("engineFaults", "发动机故障码", "", "发动机相关故障码列表。"), - field("otherFaults", "其他故障码", "", "其他故障码列表。")), - packet("GD_FC_STACK", "广东燃料电池电堆数据", "广东燃料电池汽车数据接入规范扩展,描述电堆状态、压力、温度和单体电压分段。", - field("stackCount", "电堆数量", "", "本包包含的电堆数量。"), - field("stacks.engineWorkState", "发动机工作状态", "", "燃料电池发动机工作状态。"), - field("stacks.stackWaterOutletTempC", "电堆出水温度", "C", "电堆冷却水出口温度。"), - field("stacks.hydrogenInletPressureKpa", "氢气入口压力", "kPa", "电堆氢气入口压力。"), - field("stacks.airInletPressureKpa", "空气入口压力", "kPa", "电堆空气入口压力。"), - field("stacks.airInletTempC", "空气入口温度", "C", "电堆空气入口温度。"), - field("stacks.maxCellVoltageV", "单体最高电压", "V", "本电堆单体最高电压。"), - field("stacks.minCellVoltageV", "单体最低电压", "V", "本电堆单体最低电压。"), - field("stacks.avgCellVoltageV", "单体平均电压", "V", "本电堆单体平均电压。"), - field("stacks.cellCount", "单体总数", "", "电堆单体总数量。"), - field("stacks.frameCellStart", "本帧单体起始序号", "", "当前分段电压起始单体序号。"), - field("stacks.frameCellCount", "本帧单体数量", "", "当前分段包含的单体数量。"), - field("stacks.frameCellVoltagesV", "本帧单体电压列表", "V", "当前分段的单体电压数组。"), - field("stacks.frameCellComplete", "单体电压是否完整", "", "snapshot 合并后是否已覆盖全部单体。"), - field("stacks.frameCellMissingCount", "缺失单体数量", "", "snapshot 合并后仍缺失的单体数量。")), - packet("GD_FC_DCDC", "广东燃料电池 DC/DC 数据", "广东燃料电池汽车数据接入规范扩展,描述 DC/DC 输入输出和控制器温度。", - field("inputVoltageV", "输入电压", "V", "DC/DC 输入电压。"), - field("inputCurrentA", "输入电流", "A", "DC/DC 输入电流。"), - field("outputVoltageV", "输出电压", "V", "DC/DC 输出电压。"), - field("outputCurrentA", "输出电流", "A", "DC/DC 输出电流。"), - field("controllerTempC", "控制器温度", "C", "DC/DC 控制器温度。")), - packet("GD_FC_AIR_CONDITIONER", "广东燃料电池空调数据", "广东燃料电池汽车数据接入规范扩展,描述空调状态、功率和压缩机输入电压。", - field("status", "空调状态", "", "空调工作状态。", - mappings( - mapping("00", "0", "关闭", "空调关闭。"), - mapping("01", "1", "启动", "空调启动。"), - mapping("FE", "254", "异常", "协议异常值。"), - mapping("FF", "255", "无效", "协议无效值。"))), - field("powerKw", "空调功率", "kW", "空调消耗功率。"), - field("compressorInputVoltageV", "压缩机输入电压", "V", "空调压缩机输入电压。")), - packet("GD_FC_VEHICLE_INFO", "广东燃料电池车辆信息数据", "广东燃料电池汽车数据接入规范扩展,描述碰撞报警、环境温度压力和车载氢量。", - field("collisionAlarm", "碰撞报警", "", "碰撞报警状态。", - mappings( - mapping("00", "0", "无碰撞报警", "当前无碰撞报警。"), - mapping("01", "1", "有碰撞报警", "当前有碰撞报警。"), - mapping("FE", "254", "异常", "协议异常值。"), - mapping("FF", "255", "无效", "协议无效值。"))), - field("ambientTempC", "环境温度", "C", "车辆环境温度。"), - field("ambientPressureKpa", "环境压力", "kPa", "车辆环境压力。"), - field("hydrogenMassKg", "车载氢量", "kg", "当前车载氢气质量。")) - ); - - private static Packet packet(String code, String nameZh, String description, Field... fields) { - return new Packet(code, nameZh, description, List.of(fields)); - } - - private static Field field(String key, String nameZh, String unit, String description) { - return field(key, nameZh, unit, description, List.of()); - } - - private static Field field(String key, String nameZh, String unit, String description, - List valueMappings) { - return new Field(key, nameZh, unit, description, valueMappings); - } - - private static List mappings(ValueMapping... mappings) { - return List.of(mappings); - } - - private static ValueMapping mapping(String code, String value, String nameZh, String description) { - return new ValueMapping(code, value, nameZh, description); - } - - /** - * 一个数据包/信息体类型的字段集合,例如 VEHICLE 或 GD_FC_DCDC。 - */ - public record Packet(String code, String nameZh, String description, List fields) { - } - - /** - * 可查询字段定义。key 是接口查询参数使用的英文路径,nameZh/unit/description 用于前端展示。 - */ - public record Field(String key, String nameZh, String unit, String description, - List valueMappings) { - } - - /** - * 协议枚举值或位图值的展示映射。code 保留原始协议表示,value 是解析后的业务值。 - */ - public record ValueMapping(String code, String value, String nameZh, String description) { - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FrameController.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FrameController.java deleted file mode 100644 index 9daf98a9..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Gb32960FrameController.java +++ /dev/null @@ -1,214 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; - -/** - * GB32960 历史查询 HTTP 边界。 - * - *

    这个 Controller 只做参数校验、时间范围解析和 Swagger 说明,真正的 RAW 读取、 - * 协议解码、snapshot 合并、字段投影都放在 {@link Gb32960DecodedFrameService}。 - * 这样可以保证接口层足够薄,后续如果要把查询服务拆出去,只需要迁移 service。 - */ -@RestController -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true") -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true") -@ConditionalOnBean(Gb32960DecodedFrameService.class) -@RequestMapping("/api/event-history/gb32960") -@Tag(name = "gb-32960-frame-controller", description = "GB32960 专用历史帧、业务快照、字段查询和字段字典接口。") -public final class Gb32960FrameController { - - private final Gb32960DecodedFrameService service; - - public Gb32960FrameController(Gb32960DecodedFrameService service) { - if (service == null) { - throw new IllegalArgumentException("service must not be null"); - } - this.service = service; - } - - @GetMapping("/frames") - @Operation( - summary = "查询 GB32960 原始帧解析结果", - description = "按时间范围查询某辆车或账号下的 GB32960 原始帧,并返回每帧解析出的整包 blocks。主要用于排查原始报文、解析结果和子包内容。") - public List frames( - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:48:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:49:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") HistoryQueryOrder order, - @Parameter(description = "返回帧数量上限。", example = "10") - @RequestParam(defaultValue = "10") int limit, - @Parameter(description = "车辆 VIN;用于缩小到单车查询。", example = "LB9A32A20P0LS1257") - @RequestParam(required = false) String vin, - @Parameter(description = "平台账号;用于选择对应厂商扩展解析规则。", example = "Hyundai") - @RequestParam(required = false) String platformAccount) throws IOException { - QueryTimeRange range = QueryTimeRange.parse(dateFrom, dateTo); - return service.query( - range.dateFrom(), - range.dateTo(), - range.eventTimeFrom(), - range.eventTimeTo(), - order, - limit, - vin, - platformAccount); - } - - @GetMapping("/frames/page") - @Operation( - summary = "分页查询 GB32960 原始帧解析结果", - description = "按 VIN 和时间范围分页查询 TDengine raw_frames,并回读 archive 原始包即时解析。" - + "分页使用 cursorTs + cursorId,不使用 offset;适合生产热历史连续翻页。") - public Gb32960DecodedFrameService.DecodedFramePage framesPage( - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:48:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:49:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") HistoryQueryOrder order, - @Parameter(description = "返回帧数量上限,最大 1000。", example = "100") - @RequestParam(defaultValue = "100") int limit, - @Parameter(description = "车辆 VIN,必填;用于命中 TDengine raw child table。", required = true, example = "LB9A32A20P0LS1257") - @RequestParam String vin, - @Parameter(description = "平台账号;用于选择对应厂商扩展解析规则。", example = "Hyundai") - @RequestParam(required = false) String platformAccount, - @Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z") - @RequestParam(required = false) String cursorTs, - @Parameter(description = "上一页返回的 nextCursor.id。", example = "gb32960-frame-xxx") - @RequestParam(required = false) String cursorId) throws IOException { - if (vin == null || vin.isBlank()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 frame page query"); - } - return service.queryPage( - QueryTimeRange.parse(dateFrom, dateTo), - order, - limit, - vin, - platformAccount, - cursor(cursorTs, cursorId)); - } - - @GetMapping("/frame") - @Operation( - summary = "按 rawArchiveUri 查询单个 GB32960 原始帧", - description = "直接读取指定 archive://*.bin 原始包并返回解析结果。主要用于从 snapshots 的 sourceFrames 回查某个原始报文。") - public Gb32960DecodedFrameService.DecodedFrame frame( - @Parameter(description = "原始包归档地址。", example = "archive://2026/06/23/GB32960/LB9A32A20P0LS1257/1782182749928000.bin") - @RequestParam String rawArchiveUri, - @Parameter(description = "平台账号;用于选择对应厂商扩展解析规则。", example = "Hyundai") - @RequestParam(required = false) String platformAccount) throws IOException { - return service.findByRawArchiveUri(rawArchiveUri, platformAccount); - } - - private static TdenginePageCursor cursor(String cursorTs, String cursorId) { - boolean hasTs = cursorTs != null && !cursorTs.isBlank(); - boolean hasId = cursorId != null && !cursorId.isBlank(); - if (!hasTs && !hasId) { - return null; - } - if (!hasTs || !hasId) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "cursorTs and cursorId must be provided together"); - } - return new TdenginePageCursor(Instant.parse(cursorTs.trim()), cursorId.trim()); - } - - @GetMapping("/snapshots") - @Operation( - summary = "查询 GB32960 完整业务快照", - description = "按 VIN 和时间范围查询完整业务快照。会把同一车辆、同一 eventTime 的多个子包合并为一个 snapshot,适合车辆详情页和某一时刻全字段查看。VIN 为必填。") - public List snapshots( - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:48:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:49:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") HistoryQueryOrder order, - @Parameter(description = "返回 snapshot 数量上限。", example = "10") - @RequestParam(defaultValue = "10") int limit, - @Parameter(description = "车辆 VIN,必填;snapshot 查询只支持单车,避免跨车合并和低效扫描。", required = true, example = "LB9A32A20P0LS1257") - @RequestParam String vin, - @Parameter(description = "平台账号;用于选择对应厂商扩展解析规则。", example = "Hyundai") - @RequestParam(required = false) String platformAccount) throws IOException { - // snapshot 是按同一 VIN + eventTime 合并多个 RAW 子包,跨车查询会造成错误合并和低效扫描。 - if (vin == null || vin.isBlank()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 snapshots"); - } - return service.snapshots(QueryTimeRange.parse(dateFrom, dateTo), order, limit, vin, platformAccount); - } - - @GetMapping("/snapshots/fields") - @Operation( - summary = "按所需字段查询 GB32960 快照", - description = "高频业务查询接口。按 VIN、时间范围和字段列表返回轻量快照,只包含 fields 指定字段;字段格式为 数据包代码.字段名,例如 VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV。字段名应来自 dictionary 接口。VIN 和 fields 为必填。") - public List snapshotFields( - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:48:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:49:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") HistoryQueryOrder order, - @Parameter(description = "返回 snapshot 数量上限。", example = "10") - @RequestParam(defaultValue = "10") int limit, - @Parameter(description = "车辆 VIN,必填;字段查询只支持单车。", required = true, example = "LB9A32A20P0LS1257") - @RequestParam String vin, - @Parameter(description = "逗号分隔的字段列表,字段来自 dictionary;格式为 数据包代码.字段名。", required = true, example = "VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV,GD_FC_DCDC.outputCurrentA") - @RequestParam String fields, - @Parameter(description = "平台账号;用于选择对应厂商扩展解析规则。", example = "Hyundai") - @RequestParam(required = false) String platformAccount) throws IOException { - // 字段投影接口是前端高频接口,强制单车查询可以命中 VIN 分区,避免全库扫描后再 join。 - if (vin == null || vin.isBlank()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 snapshot field query"); - } - return service.snapshotFields(QueryTimeRange.parse(dateFrom, dateTo), order, limit, vin, platformAccount, fields); - } - - @GetMapping(value = "/snapshots/fields.csv", produces = "text/csv;charset=UTF-8") - @Operation( - summary = "导出 GB32960 指定字段快照 CSV", - description = "参数与 snapshots/fields 一致,返回 CSV 文本。基础列和字段列表表头均为中文,字段表头来自 dictionary,例如 整车数据-累计里程(km)。适合前端下载和临时报表。") - public String snapshotFieldsCsv( - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:48:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-23T10:49:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") HistoryQueryOrder order, - @Parameter(description = "返回 snapshot 数量上限。", example = "1000") - @RequestParam(defaultValue = "1000") int limit, - @Parameter(description = "车辆 VIN,必填;字段导出只支持单车。", required = true, example = "LB9A32A20P0LS1257") - @RequestParam String vin, - @Parameter(description = "逗号分隔的字段列表,字段来自 dictionary;格式为 数据包代码.字段名。", required = true, example = "VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV,GD_FC_DCDC.outputCurrentA") - @RequestParam String fields, - @Parameter(description = "平台账号;用于选择对应厂商扩展解析规则。", example = "Hyundai") - @RequestParam(required = false) String platformAccount) throws IOException { - // CSV 复用字段投影逻辑,表头中文化由 dictionary 提供,避免导出和页面展示出现两套字段定义。 - if (vin == null || vin.isBlank()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vin is required for gb32960 snapshot field csv export"); - } - return service.snapshotFieldsCsv(QueryTimeRange.parse(dateFrom, dateTo), order, limit, vin, platformAccount, fields); - } - - @GetMapping("/dictionary") - @Operation( - summary = "查询 GB32960 字段字典", - description = "返回数据包代码、数据包中文名、字段 key、中文名、单位、解释和枚举/位图值域对照。前端应用可用它渲染字段选择器、表头、Tooltip 和状态码中文展示。") - public List dictionary() { - return Gb32960FieldDictionary.packets(); - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/HistoryQueryOrder.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/HistoryQueryOrder.java deleted file mode 100644 index 8b41219a..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/HistoryQueryOrder.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -public enum HistoryQueryOrder { - ASC, - DESC -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryController.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryController.java deleted file mode 100644 index 613b91ce..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryController.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; - -@RestController -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true") -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true") -@ConditionalOnBean(TdengineHistoryReader.class) -@RequestMapping("/api/event-history/jt808") -@Tag(name = "jt-808-location-controller", description = "JT808 位置历史分页查询接口。") -public final class Jt808LocationHistoryController { - - private final TdengineHistoryReader reader; - - public Jt808LocationHistoryController(TdengineHistoryReader reader) { - if (reader == null) { - throw new IllegalArgumentException("reader must not be null"); - } - this.reader = reader; - } - - @GetMapping("/locations") - @Operation( - summary = "查询 JT808 位置历史", - description = "按终端手机号和时间范围查询 TDengine 中的 JT808 位置点。分页使用 cursorTs + cursorId,不使用 offset,适合高并发历史轨迹查询。") - public LocationPageResponse locations( - @Parameter(description = "JT808 终端手机号或终端标识;内部会映射到 jt808: 车辆键。", required = true, example = "g7gps") - @RequestParam String phone, - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:00:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:10:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") TdengineQueryOrder order, - @Parameter(description = "返回位置点数量上限,最大 1000。", example = "100") - @RequestParam(defaultValue = "100") int limit, - @Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z") - @RequestParam(required = false) String cursorTs, - @Parameter(description = "上一页返回的 nextCursor.id。", example = "jt808-location-xxx") - @RequestParam(required = false) String cursorId) throws IOException { - if (phone == null || phone.isBlank()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "phone is required for jt808 location query"); - } - QueryTimeRange range = QueryTimeRange.parse(dateFrom, dateTo); - TdenginePage page = reader.queryLocations(new TdengineLocationQuery( - "JT808", - vehicleKey(phone), - range.eventTimeFrom(), - range.eventTimeTo().plusMillis(1), - order, - limit, - cursor(cursorTs, cursorId))); - return LocationPageResponse.from(page); - } - - private static String vehicleKey(String phone) { - String value = phone.trim(); - return value.startsWith("jt808:") ? value : "jt808:" + value; - } - - private static TdenginePageCursor cursor(String cursorTs, String cursorId) { - boolean hasTs = cursorTs != null && !cursorTs.isBlank(); - boolean hasId = cursorId != null && !cursorId.isBlank(); - if (!hasTs && !hasId) { - return null; - } - if (!hasTs || !hasId) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "cursorTs and cursorId must be provided together"); - } - return new TdenginePageCursor(Instant.parse(cursorTs.trim()), cursorId.trim()); - } - - public record LocationPageResponse( - List items, - CursorResponse nextCursor) { - - private static LocationPageResponse from(TdenginePage page) { - CursorResponse next = page.nextCursor() - .map(cursor -> new CursorResponse(cursor.ts().toString(), cursor.tieBreaker())) - .orElse(null); - return new LocationPageResponse( - page.items().stream().map(LocationResponse::from).toList(), - next); - } - } - - public record CursorResponse(String ts, String id) { - } - - public record LocationResponse( - String eventTime, - String receivedAt, - String factId, - String frameId, - String phone, - String vin, - String vehicleKey, - double longitude, - double latitude, - double altitudeM, - double speedKmh, - double directionDeg, - long alarmFlag, - long statusFlag, - Double totalMileageKm, - String rawUri) { - - private static LocationResponse from(TdengineLocationRow row) { - return new LocationResponse( - row.ts().toString(), - row.receivedAt().toString(), - row.factId(), - row.frameId(), - row.phone(), - row.vin(), - row.vehicleKey(), - row.longitude(), - row.latitude(), - row.altitudeM(), - row.speedKmh(), - row.directionDeg(), - row.alarmFlag(), - row.statusFlag(), - row.totalMileageKm(), - row.rawUri()); - } - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryController.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryController.java deleted file mode 100644 index b6d79f94..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryController.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; -import java.util.Locale; - -@RestController -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true") -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true") -@ConditionalOnBean(TdengineHistoryReader.class) -@RequestMapping("/api/event-history/jt808") -@Tag(name = "jt-808-raw-frame-controller", description = "JT808 原始帧索引分页查询接口。") -public final class Jt808RawFrameHistoryController { - - private final TdengineHistoryReader reader; - - public Jt808RawFrameHistoryController(TdengineHistoryReader reader) { - if (reader == null) { - throw new IllegalArgumentException("reader must not be null"); - } - this.reader = reader; - } - - @GetMapping("/raw-frames") - @Operation( - summary = "查询 JT808 原始帧索引", - description = "按 phone、VIN、vehicleKey、messageId、parseStatus 和时间范围查询 TDengine raw_frames。" - + "返回完整 metadataJson,包含注册帧字段、身份解析来源、rawArchiveUri 等排障信息。" - + "分页使用 cursorTs + cursorId,不使用 offset。") - public RawFramePageResponse rawFrames( - @Parameter(description = "JT808 终端手机号或终端标识。", example = "13079963320") - @RequestParam(required = false) String phone, - @Parameter(description = "VIN。VIN 已反写后可用该字段查询。", example = "LNVIN000000000001") - @RequestParam(required = false) String vin, - @Parameter(description = "内部车辆键;传入后精确过滤 vehicle_key。", example = "jt808:13079963320") - @RequestParam(required = false) String vehicleKey, - @Parameter(description = "JT808 消息 ID,支持十进制,例如注册帧 256、位置帧 512。", example = "256") - @RequestParam(required = false) Integer messageId, - @Parameter(description = "解析状态,例如 SUCCEEDED、FAILED。", example = "SUCCEEDED") - @RequestParam(required = false) String parseStatus, - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:00:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:10:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") TdengineQueryOrder order, - @Parameter(description = "返回原始帧数量上限,最大 1000。", example = "100") - @RequestParam(defaultValue = "100") int limit, - @Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z") - @RequestParam(required = false) String cursorTs, - @Parameter(description = "上一页返回的 nextCursor.id。", example = "jt808-frame-xxx") - @RequestParam(required = false) String cursorId) throws IOException { - if (allBlank(phone, vin, vehicleKey) && messageId == null && isBlank(parseStatus)) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "phone, vin, vehicleKey, messageId or parseStatus is required for jt808 raw frame query"); - } - QueryTimeRange range = QueryTimeRange.parse(dateFrom, dateTo); - TdenginePage page = reader.queryRawFrames(new TdengineRawFrameQuery( - "JT808", - normalize(vehicleKey), - normalize(vin), - normalize(phone), - messageId, - normalize(parseStatus).toUpperCase(Locale.ROOT), - range.eventTimeFrom(), - range.eventTimeTo().plusMillis(1), - order, - limit, - cursor(cursorTs, cursorId))); - return RawFramePageResponse.from(page); - } - - private static TdenginePageCursor cursor(String cursorTs, String cursorId) { - boolean hasTs = cursorTs != null && !cursorTs.isBlank(); - boolean hasId = cursorId != null && !cursorId.isBlank(); - if (!hasTs && !hasId) { - return null; - } - if (!hasTs || !hasId) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "cursorTs and cursorId must be provided together"); - } - return new TdenginePageCursor(Instant.parse(cursorTs.trim()), cursorId.trim()); - } - - private static boolean allBlank(String... values) { - for (String value : values) { - if (!isBlank(value)) { - return false; - } - } - return true; - } - - private static boolean isBlank(String value) { - return value == null || value.isBlank(); - } - - private static String normalize(String value) { - return value == null ? "" : value.trim(); - } - - public record RawFramePageResponse( - List items, - CursorResponse nextCursor) { - - private static RawFramePageResponse from(TdenginePage page) { - CursorResponse next = page.nextCursor() - .map(cursor -> new CursorResponse(cursor.ts().toString(), cursor.tieBreaker())) - .orElse(null); - return new RawFramePageResponse( - page.items().stream().map(RawFrameResponse::from).toList(), - next); - } - } - - public record CursorResponse(String ts, String id) { - } - - public record RawFrameResponse( - String eventTime, - String receivedAt, - String frameId, - int messageId, - String messageIdHex, - int subType, - String rawUri, - String checksum, - long rawSizeBytes, - String parseStatus, - String parseError, - String peer, - String metadataJson, - String protocol, - String vehicleKey, - String vin, - String phone) { - - private static RawFrameResponse from(TdengineRawFrameRow row) { - return new RawFrameResponse( - row.ts().toString(), - row.receivedAt().toString(), - row.frameId(), - row.messageId(), - "0x%04X".formatted(row.messageId()), - row.subType(), - row.rawUri(), - row.checksum(), - row.rawSizeBytes(), - row.parseStatus(), - row.parseError(), - row.peer(), - row.metadataJson(), - row.protocol(), - row.vehicleKey(), - row.vin(), - row.phone()); - } - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/LocationHistoryController.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/LocationHistoryController.java deleted file mode 100644 index f346a9d5..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/LocationHistoryController.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; - -@RestController -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true") -@ConditionalOnBean(TdengineHistoryReader.class) -@RequestMapping("/api/event-history") -@Tag(name = "location-history-controller", description = "通用位置历史分页查询接口。") -public final class LocationHistoryController { - - private final TdengineHistoryReader reader; - - public LocationHistoryController(TdengineHistoryReader reader) { - if (reader == null) { - throw new IllegalArgumentException("reader must not be null"); - } - this.reader = reader; - } - - @GetMapping("/locations") - @Operation( - summary = "查询车辆位置历史", - description = "按协议、车辆标识和时间范围查询 TDengine 位置点。分页使用 cursorTs + cursorId,不使用 offset。") - public LocationPageResponse locations( - @Parameter(description = "协议名,例如 GB32960、JT808、MQTT_YUTONG。", required = true, example = "MQTT_YUTONG") - @RequestParam String protocol, - @Parameter(description = "内部车辆键。传入后优先使用。", example = "LMRKH9AC2R1004087") - @RequestParam(required = false) String vehicleKey, - @Parameter(description = "VIN。GB32960、宇通通常可直接用 VIN。", example = "LMRKH9AC2R1004087") - @RequestParam(required = false) String vin, - @Parameter(description = "JT808 终端手机号或终端标识;JT808 会自动映射为 jt808:。", example = "g7gps") - @RequestParam(required = false) String phone, - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:00:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:10:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") TdengineQueryOrder order, - @Parameter(description = "返回位置点数量上限,最大 1000。", example = "100") - @RequestParam(defaultValue = "100") int limit, - @Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z") - @RequestParam(required = false) String cursorTs, - @Parameter(description = "上一页返回的 nextCursor.id。", example = "location-xxx") - @RequestParam(required = false) String cursorId) throws IOException { - String normalizedProtocol = DefaultProductionProtocols.requireSupported( - require(protocol, "protocol is required for location query")); - QueryTimeRange range = QueryTimeRange.parse(dateFrom, dateTo); - TdenginePage page = reader.queryLocations(new TdengineLocationQuery( - normalizedProtocol, - resolveVehicleKey(normalizedProtocol, vehicleKey, vin, phone), - range.eventTimeFrom(), - range.eventTimeTo().plusMillis(1), - order, - limit, - cursor(cursorTs, cursorId))); - return LocationPageResponse.from(page); - } - - private static String resolveVehicleKey(String protocol, String vehicleKey, String vin, String phone) { - String explicitVehicleKey = trimToNull(vehicleKey); - if (explicitVehicleKey != null) { - return explicitVehicleKey; - } - String vinValue = trimToNull(vin); - if (vinValue != null) { - return vinValue; - } - String phoneValue = trimToNull(phone); - if (phoneValue != null) { - if ("JT808".equals(protocol) && !phoneValue.startsWith("jt808:")) { - return "jt808:" + phoneValue; - } - return phoneValue; - } - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "vehicleKey, vin or phone is required"); - } - - private static String require(String value, String message) { - String trimmed = trimToNull(value); - if (trimmed == null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, message); - } - return trimmed; - } - - private static String trimToNull(String value) { - if (value == null || value.isBlank()) { - return null; - } - return value.trim(); - } - - private static TdenginePageCursor cursor(String cursorTs, String cursorId) { - boolean hasTs = cursorTs != null && !cursorTs.isBlank(); - boolean hasId = cursorId != null && !cursorId.isBlank(); - if (!hasTs && !hasId) { - return null; - } - if (!hasTs || !hasId) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "cursorTs and cursorId must be provided together"); - } - return new TdenginePageCursor(Instant.parse(cursorTs.trim()), cursorId.trim()); - } - - public record LocationPageResponse( - List items, - CursorResponse nextCursor) { - - private static LocationPageResponse from(TdenginePage page) { - CursorResponse next = page.nextCursor() - .map(cursor -> new CursorResponse(cursor.ts().toString(), cursor.tieBreaker())) - .orElse(null); - return new LocationPageResponse( - page.items().stream().map(LocationResponse::from).toList(), - next); - } - } - - public record CursorResponse(String ts, String id) { - } - - public record LocationResponse( - String eventTime, - String receivedAt, - String factId, - String frameId, - String phone, - String vin, - String vehicleKey, - double longitude, - double latitude, - double altitudeM, - double speedKmh, - double directionDeg, - long alarmFlag, - long statusFlag, - Double totalMileageKm, - String rawUri, - String protocol) { - - private static LocationResponse from(TdengineLocationRow row) { - return new LocationResponse( - row.ts().toString(), - row.receivedAt().toString(), - row.factId(), - row.frameId(), - row.phone(), - row.vin(), - row.vehicleKey(), - row.longitude(), - row.latitude(), - row.altitudeM(), - row.speedKmh(), - row.directionDeg(), - row.alarmFlag(), - row.statusFlag(), - row.totalMileageKm(), - row.rawUri(), - row.protocol()); - } - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/QueryTimeRange.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/QueryTimeRange.java deleted file mode 100644 index f71f2dc4..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/QueryTimeRange.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.ZoneId; - -/** - * 查询时间范围解析器。 - * - *

    HTTP 查询允许传日期、带时区时间或不带时区的本地时间: - * 日期用于分区裁剪,不带时区的时间统一按 Asia/Shanghai 转成 UTC Instant。 - * 返回值同时包含 LocalDate 分区边界和精确到秒/毫秒的 eventTime 边界。 - */ -final class QueryTimeRange { - - private static final ZoneId DEFAULT_ZONE = ZoneId.of("Asia/Shanghai"); - - private final LocalDate dateFrom; - private final LocalDate dateTo; - private final Instant eventTimeFrom; - private final Instant eventTimeTo; - - private QueryTimeRange(LocalDate dateFrom, LocalDate dateTo, Instant eventTimeFrom, Instant eventTimeTo) { - this.dateFrom = dateFrom; - this.dateTo = dateTo; - this.eventTimeFrom = eventTimeFrom; - this.eventTimeTo = eventTimeTo; - } - - static QueryTimeRange parse(String from, String to) { - Boundary start = parseBoundary(from, false); - Boundary end = parseBoundary(to, true); - if (end.instant() != null && start.instant() != null && end.instant().isBefore(start.instant())) { - throw new IllegalArgumentException("dateTo must not be before dateFrom"); - } - if (end.date().isBefore(start.date())) { - throw new IllegalArgumentException("dateTo must not be before dateFrom"); - } - return new QueryTimeRange(start.date(), end.date(), start.instant(), end.instant()); - } - - LocalDate dateFrom() { - return dateFrom; - } - - LocalDate dateTo() { - return dateTo; - } - - Instant eventTimeFrom() { - return eventTimeFrom; - } - - Instant eventTimeTo() { - return eventTimeTo; - } - - private static Boundary parseBoundary(String raw, boolean endOfRange) { - if (raw == null || raw.isBlank()) { - throw new IllegalArgumentException("date range must not be blank"); - } - String value = raw.trim(); - if (value.length() == 10) { - // 纯日期查询覆盖东八区自然日;结束日期取当天最后一毫秒,兼容 dateTo=2026-06-23。 - LocalDate date = LocalDate.parse(value); - Instant instant = endOfRange - ? date.plusDays(1).atStartOfDay(DEFAULT_ZONE).toInstant().minusMillis(1) - : date.atStartOfDay(DEFAULT_ZONE).toInstant(); - return new Boundary(date, instant); - } - try { - // 带 Z 或 +08:00 的时间按调用方显式时区解析。 - Instant instant = OffsetDateTime.parse(value).toInstant(); - return new Boundary(LocalDate.ofInstant(instant, DEFAULT_ZONE), instant); - } catch (RuntimeException ignored) { - // 不带时区的前端输入按东八区解释,接口返回的数据时间本身仍保持原始 UTC 字符串。 - LocalDateTime local = LocalDateTime.parse(value); - Instant instant = local.atZone(DEFAULT_ZONE).toInstant(); - return new Boundary(local.toLocalDate(), instant); - } - } - - private record Boundary(LocalDate date, Instant instant) { - } -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/RawFrameHistoryController.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/RawFrameHistoryController.java deleted file mode 100644 index 31008426..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/RawFrameHistoryController.java +++ /dev/null @@ -1,261 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -@RestController -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true") -@ConditionalOnBean(TdengineHistoryReader.class) -@RequestMapping("/api/event-history") -@Tag(name = "Raw Frame API", description = "原始帧索引分页查询接口。") -public final class RawFrameHistoryController { - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final TypeReference> MAP_TYPE = new TypeReference<>() {}; - private final TdengineHistoryReader reader; - - public RawFrameHistoryController(TdengineHistoryReader reader) { - if (reader == null) { - throw new IllegalArgumentException("reader must not be null"); - } - this.reader = reader; - } - - @GetMapping("/raw-frames") - @Operation( - summary = "查询原始帧索引", - description = "按 protocol、vehicleKey、VIN、phone、messageId、parseStatus 和时间范围查询 TDengine raw_frames。" - + "返回 metadataJson 与 rawUri,分页使用 cursorTs + cursorId。") - public RawFramePageResponse rawFrames( - @Parameter(description = "协议类型,例如 GB32960、JT808、MQTT_YUTONG。", example = "JT808") - @RequestParam String protocol, - @Parameter(description = "内部车辆键;传入后精确过滤 vehicle_key。", example = "jt808:g7gps") - @RequestParam(required = false) String vehicleKey, - @Parameter(description = "VIN。已完成身份反写的 raw 帧可用该字段查询。", example = "LB9A32A24P0LS1270") - @RequestParam(required = false) String vin, - @Parameter(description = "终端手机号或终端标识,主要用于 JT808。", example = "g7gps") - @RequestParam(required = false) String phone, - @Parameter(description = "消息 ID,支持十进制或 0x 十六进制,例如 512 或 0x0200。", example = "0x0200") - @RequestParam(required = false) String messageId, - @Parameter(description = "解析状态,例如 SUCCEEDED、FAILED、NOT_PARSED。", example = "SUCCEEDED") - @RequestParam(required = false) String parseStatus, - @Parameter(description = "开始时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:00:00") - @RequestParam String dateFrom, - @Parameter(description = "结束时间,支持日期或精确到秒的时间;无时区时按东八区解析。", example = "2026-06-29T13:10:00") - @RequestParam String dateTo, - @Parameter(description = "排序方向。", example = "DESC") - @RequestParam(defaultValue = "DESC") TdengineQueryOrder order, - @Parameter(description = "返回原始帧数量上限,最大 1000。", example = "100") - @RequestParam(defaultValue = "100") int limit, - @Parameter(description = "上一页返回的 nextCursor.ts。", example = "2026-06-29T05:00:01Z") - @RequestParam(required = false) String cursorTs, - @Parameter(description = "上一页返回的 nextCursor.id。", example = "frame-xxx") - @RequestParam(required = false) String cursorId) throws IOException { - Integer parsedMessageId = parseMessageId(messageId); - if (allBlank(vehicleKey, vin, phone) && parsedMessageId == null && isBlank(parseStatus)) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, - "vehicleKey, vin, phone, messageId or parseStatus is required for raw frame query"); - } - String normalizedProtocol = DefaultProductionProtocols.requireSupported(protocol); - QueryTimeRange range = QueryTimeRange.parse(dateFrom, dateTo); - TdenginePage page = reader.queryRawFrames(new TdengineRawFrameQuery( - normalizedProtocol, - normalize(vehicleKey), - normalize(vin), - normalize(phone), - parsedMessageId, - normalize(parseStatus).toUpperCase(Locale.ROOT), - range.eventTimeFrom(), - range.eventTimeTo().plusMillis(1), - order, - limit, - cursor(cursorTs, cursorId))); - return RawFramePageResponse.from(page); - } - - private static Integer parseMessageId(String messageId) { - String value = normalize(messageId); - if (value.isBlank()) { - return null; - } - try { - if (value.startsWith("0x") || value.startsWith("0X")) { - return Integer.parseInt(value.substring(2), 16); - } - return Integer.valueOf(value); - } catch (NumberFormatException ex) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "messageId must be decimal or 0x hex", ex); - } - } - - private static TdenginePageCursor cursor(String cursorTs, String cursorId) { - boolean hasTs = cursorTs != null && !cursorTs.isBlank(); - boolean hasId = cursorId != null && !cursorId.isBlank(); - if (!hasTs && !hasId) { - return null; - } - if (!hasTs || !hasId) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "cursorTs and cursorId must be provided together"); - } - return new TdenginePageCursor(Instant.parse(cursorTs.trim()), cursorId.trim()); - } - - private static boolean allBlank(String... values) { - for (String value : values) { - if (!isBlank(value)) { - return false; - } - } - return true; - } - - private static boolean isBlank(String value) { - return value == null || value.isBlank(); - } - - private static String normalize(String value) { - return value == null ? "" : value.trim(); - } - - public record RawFramePageResponse( - List items, - CursorResponse nextCursor) { - - private static RawFramePageResponse from(TdenginePage page) { - CursorResponse next = page.nextCursor() - .map(cursor -> new CursorResponse(cursor.ts().toString(), cursor.tieBreaker())) - .orElse(null); - List items = new java.util.ArrayList<>(page.items().size()); - for (TdengineRawFrameRow row : page.items()) { - items.add(RawFrameResponse.from(row)); - } - return new RawFramePageResponse( - items, - next); - } - } - - public record CursorResponse(String ts, String id) { - } - - public record RawFrameResponse( - String eventTime, - String receivedAt, - String frameId, - int messageId, - String messageIdHex, - int subType, - String rawUri, - String checksum, - long rawSizeBytes, - String parseStatus, - String parseError, - String peer, - String metadataJson, - String parsedJson, - Map metadata, - Map parsedFields, - String protocol, - String vehicleKey, - String vin, - String phone) { - - private static RawFrameResponse from(TdengineRawFrameRow row) { - Map metadata = metadata(row.metadataJson()); - return new RawFrameResponse( - row.ts().toString(), - row.receivedAt().toString(), - row.frameId(), - row.messageId(), - "0x%04X".formatted(row.messageId()), - row.subType(), - row.rawUri(), - row.checksum(), - row.rawSizeBytes(), - row.parseStatus(), - row.parseError(), - row.peer(), - row.metadataJson(), - row.parsedJson(), - metadata, - parsedFields(metadata, parsed(row.parsedJson())), - row.protocol(), - row.vehicleKey(), - row.vin(), - row.phone()); - } - - private static Map metadata(String metadataJson) { - if (metadataJson == null || metadataJson.isBlank()) { - return Map.of(); - } - try { - return OBJECT_MAPPER.readValue(metadataJson, MAP_TYPE); - } catch (Exception ignored) { - return Map.of("_raw", metadataJson); - } - } - - private static Map parsedFields(Map metadata, - Map parsed) { - Map out = new LinkedHashMap<>(); - if (parsed.isEmpty()) { - for (Map.Entry entry : metadata.entrySet()) { - String key = entry.getKey(); - if (key == null || key.isBlank()) { - continue; - } - if (key.startsWith("raw.") || key.startsWith("jt808.")) { - out.put(key, entry.getValue()); - } else { - out.put("raw." + key, entry.getValue()); - } - } - } else { - out.putAll(parsed); - } - return immutableMap(out); - } - - private static Map parsed(String parsedJson) { - if (parsedJson == null || parsedJson.isBlank()) { - return Map.of(); - } - try { - return OBJECT_MAPPER.readValue(parsedJson, MAP_TYPE); - } catch (Exception ignored) { - return Map.of("_raw", parsedJson); - } - } - - private static Map immutableMap(Map values) { - return Collections.unmodifiableMap(new LinkedHashMap<>(values)); - } - } - -} diff --git a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfiguration.java b/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfiguration.java deleted file mode 100644 index d2a8838e..00000000 --- a/modules/services/event-history-service/src/main/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfiguration.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.lingniu.ingest.eventhistory.config; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import com.lingniu.ingest.eventhistory.ApiExceptionHandler; -import com.lingniu.ingest.eventhistory.EventHistoryEnvelopeIngestor; -import com.lingniu.ingest.eventhistory.Gb32960DecodedFrameService; -import com.lingniu.ingest.eventhistory.Gb32960FrameController; -import com.lingniu.ingest.eventhistory.Jt808LocationHistoryController; -import com.lingniu.ingest.eventhistory.Jt808RawFrameHistoryController; -import com.lingniu.ingest.eventhistory.LocationHistoryController; -import com.lingniu.ingest.eventhistory.RawFrameHistoryController; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960CommandParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParserRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.AlarmV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.DriveMotorV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.EngineV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.ExtremeValueV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.FuelCellV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.PositionV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.TemperatureV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VoltageV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.AlarmV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.BatteryTemperatureV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.DriveMotorV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.EngineV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.FuelCellStackV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.FuelCellV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.MinParallelVoltageV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.PositionV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.SuperCapacitorExtremeV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.SuperCapacitorV2025BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.VehicleV2025BlockParser; -import com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration; -import com.lingniu.ingest.tdenginehistory.config.TdengineHistoryAutoConfiguration; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Value; - -import java.net.URI; -import java.nio.file.Path; -import java.util.List; - -/** - * Event History 查询/消费服务自动装配。 - * - *

    该模块有两类入口: - *

      - *
    • TDengine HTTP 查询入口:查询位置、RAW 帧和按需解码的 GB32960 帧。 - *
    • Kafka consumer 入口:消费 envelope 并写入 TDengine 历史表。 - *
    - * - *

    当前生产 history app 以 TDengine 为准;Kafka consumer 是否启动还取决于 - * {@code lingniu.ingest.sink.kafka.consumer.enabled=true}。 - */ -@AutoConfiguration -@AutoConfigureAfter({ - KafkaSinkAutoConfiguration.class, - TdengineHistoryAutoConfiguration.class -}) -@ConditionalOnProperty(prefix = "lingniu.ingest.event-history", name = "enabled", havingValue = "true") -public class EventHistoryAutoConfiguration { - - @Bean - @ConditionalOnProperty(prefix = "lingniu.ingest.tdengine-history", name = "telemetry-fields-enabled") - public Object rejectedRemovedTelemetryFieldsSwitch() { - throw new IllegalStateException( - "tdengine-history.telemetry-fields-enabled has been removed; history stores raw_frames and location rows only"); - } - - @Bean - @ConditionalOnBean(TdengineHistoryWriter.class) - @ConditionalOnMissingBean - public EventHistoryEnvelopeIngestor tdengineEventHistoryEnvelopeIngestor(TdengineHistoryWriter writer) { - return new EventHistoryEnvelopeIngestor(writer); - } - - @Bean - @ConditionalOnBean({EventHistoryEnvelopeIngestor.class, EnvelopeDeadLetterSink.class}) - @ConditionalOnMissingBean(name = "eventHistoryEnvelopeConsumerProcessor") - public EnvelopeConsumerProcessor eventHistoryEnvelopeConsumerProcessor(EventHistoryEnvelopeIngestor ingestor, - EnvelopeDeadLetterSink deadLetterSink) { - // 只注册 processor;真正拉 Kafka 的 runner 按 consumer.enabled 决定是否创建。 - return new EnvelopeConsumerProcessor("event-history", ingestor, deadLetterSink); - } - - @Bean - @ConditionalOnBean({EventHistoryEnvelopeIngestor.class, EnvelopeDeadLetterSink.class}) - @ConditionalOnMissingBean(name = "eventHistoryRawEnvelopeConsumerProcessor") - public EnvelopeConsumerProcessor eventHistoryRawEnvelopeConsumerProcessor(EventHistoryEnvelopeIngestor ingestor, - EnvelopeDeadLetterSink deadLetterSink) { - return new EnvelopeConsumerProcessor("event-history-raw", ingestor, deadLetterSink); - } - - @Bean - @ConditionalOnMissingBean - public ApiExceptionHandler apiExceptionHandler() { - return new ApiExceptionHandler(); - } - - @Bean - @ConditionalOnMissingBean - public Gb32960MessageDecoder gb32960HistoryMessageDecoder() { - List parsers = List.of( - new VehicleV2016BlockParser(), - new DriveMotorV2016BlockParser(), - new FuelCellV2016BlockParser(), - new EngineV2016BlockParser(), - new PositionV2016BlockParser(), - new ExtremeValueV2016BlockParser(), - new AlarmV2016BlockParser(), - new VoltageV2016BlockParser(), - new TemperatureV2016BlockParser(), - new VehicleV2025BlockParser(), - new DriveMotorV2025BlockParser(), - new FuelCellV2025BlockParser(), - new EngineV2025BlockParser(), - new PositionV2025BlockParser(), - new AlarmV2025BlockParser(), - new MinParallelVoltageV2025BlockParser(), - new BatteryTemperatureV2025BlockParser(), - new FuelCellStackV2025BlockParser(), - new SuperCapacitorV2025BlockParser(), - new SuperCapacitorExtremeV2025BlockParser()); - Gb32960BodyParser bodyParser = new Gb32960BodyParser(new InfoBlockParserRegistry(parsers)); - return new Gb32960MessageDecoder(bodyParser, new Gb32960CommandParser()); - } - - @Bean - @ConditionalOnBean(Gb32960MessageDecoder.class) - @ConditionalOnMissingBean - public Gb32960DecodedFrameService gb32960DecodedFrameService(ObjectProvider reader, - Gb32960MessageDecoder decoder, - @Value("${lingniu.ingest.event-history.archive-path:${SINK_ARCHIVE_PATH:./archive/}}") - String archivePath) { - return new Gb32960DecodedFrameService(reader.getIfAvailable(), decoder, archiveRoot(archivePath), null); - } - - @Bean - @ConditionalOnBean(Gb32960DecodedFrameService.class) - @ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true") - @ConditionalOnMissingBean - public Gb32960FrameController gb32960FrameController(Gb32960DecodedFrameService service) { - return new Gb32960FrameController(service); - } - - @Bean - @ConditionalOnBean(TdengineHistoryReader.class) - @ConditionalOnMissingBean - public LocationHistoryController locationHistoryController(TdengineHistoryReader reader) { - return new LocationHistoryController(reader); - } - - @Bean - @ConditionalOnBean(TdengineHistoryReader.class) - @ConditionalOnMissingBean - public RawFrameHistoryController rawFrameHistoryController(TdengineHistoryReader reader) { - return new RawFrameHistoryController(reader); - } - - @Bean - @ConditionalOnBean(TdengineHistoryReader.class) - @ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true") - @ConditionalOnMissingBean - public Jt808LocationHistoryController jt808LocationHistoryController(TdengineHistoryReader reader) { - return new Jt808LocationHistoryController(reader); - } - - @Bean - @ConditionalOnBean(TdengineHistoryReader.class) - @ConditionalOnProperty(prefix = "lingniu.ingest.event-history.api", name = "specialized-enabled", havingValue = "true") - @ConditionalOnMissingBean - public Jt808RawFrameHistoryController jt808RawFrameHistoryController(TdengineHistoryReader reader) { - return new Jt808RawFrameHistoryController(reader); - } - - static Path archiveRoot(String value) { - if (value == null || value.isBlank()) { - return Path.of(System.getProperty("java.io.tmpdir"), "lingniu-archive"); - } - // 支持 file:// URI,便于部署配置里统一用 URI 风格表达 archive 根路径。 - if (value.startsWith("file://")) { - return Path.of(URI.create(value)); - } - return Path.of(value); - } -} diff --git a/modules/services/event-history-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/services/event-history-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 04b40140..00000000 --- a/modules/services/event-history-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.eventhistory.config.EventHistoryAutoConfiguration diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java deleted file mode 100644 index 9e0adbf1..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/ApiExceptionHandlerTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.springframework.http.HttpMethod; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.resource.NoResourceFoundException; - -import java.io.IOException; -import java.nio.file.Path; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; - -class ApiExceptionHandlerTest { - - @TempDir - Path archiveRoot; - - @Test - void missingRequiredQueryParameterReturnsConcreteReason() throws Exception { - MockMvc mvc = standaloneSetup(new Gb32960FrameController(service())) - .setControllerAdvice(new ApiExceptionHandler()) - .build(); - - mvc.perform(get("/api/event-history/gb32960/snapshots") - .param("dateFrom", "2026-06-23") - .param("dateTo", "2026-06-23") - .param("limit", "1")) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.message").value("missing required query parameter: vin")) - .andExpect(jsonPath("$.timestamp").value(org.hamcrest.Matchers.endsWith("+08:00"))) - .andExpect(jsonPath("$.path").value("/api/event-history/gb32960/snapshots")); - } - - @Test - void responseStatusExceptionReturnsReason() throws Exception { - MockMvc mvc = standaloneSetup(new Gb32960FrameController(service())) - .setControllerAdvice(new ApiExceptionHandler()) - .build(); - - mvc.perform(get("/api/event-history/gb32960/snapshots") - .param("dateFrom", "2026-06-23") - .param("dateTo", "2026-06-23") - .param("limit", "1") - .param("vin", "")) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.message").value("vin is required for gb32960 snapshots")); - } - - @Test - void ioExceptionReturnsServiceUnavailable() throws Exception { - MockMvc mvc = standaloneSetup(new IoFailureController()) - .setControllerAdvice(new ApiExceptionHandler()) - .build(); - - mvc.perform(get("/io")) - .andExpect(status().isServiceUnavailable()) - .andExpect(jsonPath("$.message").value("tdengine history query failed")) - .andExpect(jsonPath("$.path").value("/io")); - } - - @Test - void noResourceReturnsNotFound() { - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getRequestURI()).thenReturn("/missing"); - - var response = new ApiExceptionHandler().noResource( - new NoResourceFoundException(HttpMethod.GET, "/missing"), - request); - - assertThat(response.getStatusCode().value()).isEqualTo(404); - assertThat(response.getBody()).isNotNull(); - assertThat(response.getBody().message()).isEqualTo("resource not found: /missing"); - } - - private Gb32960DecodedFrameService service() { - return new Gb32960DecodedFrameService( - (TdengineHistoryReader) null, - mock(Gb32960MessageDecoder.class), - archiveRoot, - null); - } - - @RestController - private static final class IoFailureController { - @GetMapping("/io") - String io() throws IOException { - throw new IOException("tdengine history query failed"); - } - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestorTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestorTest.java deleted file mode 100644 index d8534d69..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/EventHistoryEnvelopeIngestorTest.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import com.lingniu.ingest.api.consumer.EnvelopeIngestor; -import com.lingniu.ingest.sink.kafka.proto.RawArchiveRef; -import com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import com.lingniu.ingest.sink.kafka.proto.LocationPayload; -import com.lingniu.ingest.sink.kafka.proto.ParseStatusProto; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class EventHistoryEnvelopeIngestorTest { - - @Test - void rejectsInvalidEnvelopeBytes() { - CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter(); - EventHistoryEnvelopeIngestor ingestor = - new EventHistoryEnvelopeIngestor(tdengineWriter); - - assertThat(ingestor).isInstanceOf(EnvelopeIngestor.class); - assertThatThrownBy(() -> ingestor.ingest(new byte[]{0x01, 0x02})) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("VehicleEnvelope"); - } - - @Test - void tryIngestInvalidEnvelopeBytesReturnsInvalidWithoutWritingTdengine() { - CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter(); - EventHistoryEnvelopeIngestor ingestor = - new EventHistoryEnvelopeIngestor(tdengineWriter); - - var result = ingestor.tryIngest(new byte[]{0x01, 0x02}); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.INVALID_ENVELOPE); - assertThat(result.message()).contains("VehicleEnvelope"); - assertThat(tdengineWriter.rawFrames).isEmpty(); - assertThat(tdengineWriter.locations).isEmpty(); - } - - @Test - void tryIngestWritesRawAndLocationFactsToTdengineWhenWriterExists() throws Exception { - CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter(); - EventHistoryEnvelopeIngestor ingestor = new EventHistoryEnvelopeIngestor(tdengineWriter); - - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setSchemaVersion("1.0") - .setEventId("jt808-location-1") - .setVin("VINJT808001") - .setSource("JT808") - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .putMetadata("vehicle_key", "jt808:g7gps") - .putMetadata("frame_id", "frame-jt808-1") - .setRawArchive(RawArchiveRef.newBuilder() - .setUri("archive://jt808/2026/06/29/frame-jt808-1.bin") - .setSizeBytes(68)) - .setRawFrameFact(RawFrameFactPayload.newBuilder() - .setFrameId("frame-jt808-1") - .setVehicleKey("jt808:g7gps") - .setVin("VINJT808001") - .setPhone("013800000000") - .setMessageId(0x0200) - .setRawUri("archive://jt808/2026/06/29/frame-jt808-1.bin") - .setRawSizeBytes(68) - .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED)) - .setLocation(LocationPayload.newBuilder() - .setLongitude(113.12) - .setLatitude(23.45) - .setSpeedKmh(42.5)) - .build(); - - EnvelopeIngestResult result = ingestor.tryIngest(envelope.toByteArray()); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.STORED); - assertThat(tdengineWriter.rawFrames) - .extracting(TdengineRawFrameRow::frameId) - .containsExactly("frame-jt808-1"); - assertThat(tdengineWriter.locations) - .extracting(TdengineLocationRow::factId) - .containsExactly("jt808-location-1"); - assertThat(tdengineWriter.locations.getFirst().longitude()).isEqualTo(113.12); - } - - @Test - void tryIngestCanWriteTdengineFactsDirectly() { - CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter(); - EventHistoryEnvelopeIngestor ingestor = new EventHistoryEnvelopeIngestor(tdengineWriter); - - EnvelopeIngestResult result = ingestor.tryIngest( - jt808LocationEnvelope("jt808-location-1", "frame-jt808-1", "013800000001").toByteArray()); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.STORED); - assertThat(tdengineWriter.rawFrames).extracting(TdengineRawFrameRow::frameId) - .containsExactly("frame-jt808-1"); - assertThat(tdengineWriter.locations).extracting(TdengineLocationRow::factId) - .containsExactly("jt808-location-1"); - } - - @Test - void tryIngestAllBatchesFileStoreAndTdengineWrites() throws Exception { - CapturingTdengineWriter tdengineWriter = new CapturingTdengineWriter(); - EventHistoryEnvelopeIngestor ingestor = new EventHistoryEnvelopeIngestor(tdengineWriter); - - VehicleEnvelope first = jt808LocationEnvelope("jt808-location-1", "frame-jt808-1", "013800000001"); - VehicleEnvelope second = jt808LocationEnvelope("jt808-location-2", "frame-jt808-2", "013800000002"); - - List results = ingestor.tryIngestAll(List.of( - first.toByteArray(), - new byte[]{0x01, 0x02}, - second.toByteArray())); - - assertThat(results).extracting(EnvelopeIngestResult::status) - .containsExactly( - EnvelopeIngestResult.Status.STORED, - EnvelopeIngestResult.Status.INVALID_ENVELOPE, - EnvelopeIngestResult.Status.STORED); - assertThat(tdengineWriter.rawFrames).extracting(TdengineRawFrameRow::frameId) - .containsExactly("frame-jt808-1", "frame-jt808-2"); - assertThat(tdengineWriter.locations).extracting(TdengineLocationRow::factId) - .containsExactly("jt808-location-1", "jt808-location-2"); - } - - private static VehicleEnvelope jt808LocationEnvelope(String eventId, String frameId, String phone) { - return VehicleEnvelope.newBuilder() - .setSchemaVersion("1.0") - .setEventId(eventId) - .setVin("VINJT808001") - .setSource("JT808") - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .putMetadata("vehicle_key", "jt808:" + phone) - .putMetadata("frame_id", frameId) - .setRawArchive(RawArchiveRef.newBuilder() - .setUri("archive://jt808/2026/06/29/" + frameId + ".bin") - .setSizeBytes(68)) - .setRawFrameFact(RawFrameFactPayload.newBuilder() - .setFrameId(frameId) - .setVehicleKey("jt808:" + phone) - .setVin("VINJT808001") - .setPhone(phone) - .setMessageId(0x0200) - .setRawUri("archive://jt808/2026/06/29/" + frameId + ".bin") - .setRawSizeBytes(68) - .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED)) - .setLocation(LocationPayload.newBuilder() - .setLongitude(113.12) - .setLatitude(23.45) - .setSpeedKmh(42.5)) - .build(); - } - - private static final class CapturingTdengineWriter implements TdengineHistoryWriter { - private final List rawFrames = new ArrayList<>(); - private final List locations = new ArrayList<>(); - - @Override - public void appendRawFrames(List rows) { - rawFrames.addAll(rows); - } - - @Override - public void appendLocations(List rows) { - locations.addAll(rows); - } - - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameServiceTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameServiceTest.java deleted file mode 100644 index 99162807..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960DecodedFrameServiceTest.java +++ /dev/null @@ -1,685 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.codec.BccChecksum; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960BodyParser; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.protocol.gb32960.codec.InfoBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.v2016.VehicleV2016BlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcDcDcBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.parser.vendor.guangdong.GdFcStackBlockParser; -import com.lingniu.ingest.protocol.gb32960.codec.profile.Gb32960ProfileRegistry; -import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionCatalog; -import com.lingniu.ingest.protocol.gb32960.codec.profile.VendorExtensionSelector; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.io.ByteArrayOutputStream; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.time.LocalDate; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.within; - -class Gb32960DecodedFrameServiceTest { - - @TempDir - Path archiveRoot; - - @Test - void decodesFullGb32960BlocksFromRawArchive() throws Exception { - String key = "2026/06/22/GB32960/LNVFC000000000001/raw-1.bin"; - Path rawFile = archiveRoot.resolve(key); - Files.createDirectories(rawFile.getParent()); - Files.write(rawFile, realtimeFrame()); - String uri = "archive://" + key; - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - new CapturingTdengineReader(List.of(rawRow("raw-1", uri))), - decoder(), - archiveRoot, - null); - - List frames = service.query( - LocalDate.parse("2026-06-22"), - LocalDate.parse("2026-06-22"), - HistoryQueryOrder.DESC, - 10, - null, - null); - - assertThat(frames).hasSize(1); - Gb32960DecodedFrameService.DecodedFrame frame = frames.getFirst(); - assertThat(frame.vin()).isEqualTo("LNVFC000000000001"); - assertThat(frame.command()).isEqualTo("REALTIME_REPORT"); - assertThat(frame.blocks()).extracting(block -> block.get("type")) - .containsExactly("VEHICLE", "GD_FC_DCDC"); - - @SuppressWarnings("unchecked") - Map vehicle = (Map) frames.getFirst().blocks().getFirst().get("data"); - assertThat(vehicle.get("totalMileageKm").toString()).isEqualTo("10000"); - assertThat(vehicle).containsEntry("socPercent", 85); - - @SuppressWarnings("unchecked") - Map dcdc = (Map) frames.getFirst().blocks().get(1).get("data"); - assertThat(dcdc.get("inputVoltageV").toString()).isEqualTo("300"); - assertThat(dcdc.get("outputCurrentA").toString()).isEqualTo("110"); - } - - @Test - void queryNormalizesFloatingPointNoiseForJsonOutput() throws Exception { - Object normalized = Gb32960DecodedFrameService.normalizeJsonValue(636.3000000000001); - - assertThat(normalized).isInstanceOf(BigDecimal.class); - assertThat(normalized.toString()).isEqualTo("636.3"); - } - - @Test - void findsSingleFrameDirectlyByRawArchiveUri() throws Exception { - String key = "2026/06/22/GB32960/LNVFC000000000001/1792527837000000.bin"; - writeArchive(key, realtimeFrame()); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - (TdengineHistoryReader) null, - decoder(), - archiveRoot, - null); - - Gb32960DecodedFrameService.DecodedFrame frame = - service.findByRawArchiveUri("archive://" + key, "Hyundai"); - - assertThat(frame.eventId()).isEqualTo("1792527837000000"); - assertThat(frame.vin()).isEqualTo("LNVFC000000000001"); - assertThat(frame.blocks()).extracting(block -> block.get("type")) - .containsExactly("VEHICLE", "GD_FC_DCDC"); - } - - @Test - void skipsMissingRawArchiveAndContinues() throws Exception { - String missingKey = "2026/06/22/GB32960/LNVFC000000000001/missing.bin"; - String availableKey = "2026/06/22/GB32960/LNVFC000000000001/raw-2.bin"; - Path rawFile = archiveRoot.resolve(availableKey); - Files.createDirectories(rawFile.getParent()); - Files.write(rawFile, realtimeFrame()); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - new CapturingTdengineReader(List.of( - rawRow("missing", "archive://" + missingKey), - rawRow("available", "archive://" + availableKey))), - decoder(), - archiveRoot, - null); - - List frames = service.query( - LocalDate.parse("2026-06-22"), - LocalDate.parse("2026-06-22"), - HistoryQueryOrder.DESC, - 10, - null, - null); - - assertThat(frames).hasSize(1); - assertThat(frames.getFirst().eventId()).isEqualTo("available"); - assertThat(frames.getFirst().blocks()).extracting(block -> block.get("type")) - .containsExactly("VEHICLE", "GD_FC_DCDC"); - } - - @Test - void snapshotsRequireVinSoSegmentsAreMergedOnlyWithinOneVehicle() { - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - (TdengineHistoryReader) null, - decoder(), - archiveRoot, - null); - - assertThatThrownBy(() -> service.snapshots( - LocalDate.parse("2026-06-22"), - LocalDate.parse("2026-06-22"), - HistoryQueryOrder.DESC, - 10, - null, - null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("vin is required"); - } - - @Test - void snapshotsMergeGb32960StackCellVoltageSegmentsByEventTime() throws Exception { - String vin = "LNVFC000000000001"; - String key1 = "2026/06/22/GB32960/" + vin + "/stack-1.bin"; - String key2 = "2026/06/22/GB32960/" + vin + "/stack-2.bin"; - String key3 = "2026/06/22/GB32960/" + vin + "/stack-3.bin"; - writeArchive(key1, stackFrame(1, 4, true)); - writeArchive(key2, stackFrame(5, 3, false)); - writeArchive(key3, stackFrame(8, 2, false)); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - new CapturingTdengineReader(List.of( - rawRow("segment-3", "archive://" + key3), - rawRow("segment-2", "archive://" + key2), - rawRow("segment-1", "archive://" + key1))), - decoder(), - archiveRoot, - null); - - List snapshots = service.snapshots( - LocalDate.parse("2026-06-22"), - LocalDate.parse("2026-06-22"), - HistoryQueryOrder.DESC, - 10, - vin, - null); - - assertThat(snapshots).hasSize(1); - Gb32960DecodedFrameService.LogicalSnapshot snapshot = snapshots.getFirst(); - assertThat(snapshot.vin()).isEqualTo(vin); - assertThat(snapshot.eventTime()).isEqualTo("2026-06-22T10:00:00Z"); - assertThat(snapshot.sourceFrames()).hasSize(3); - assertThat(snapshot.blocks()).extracting(block -> block.get("type")) - .contains("VEHICLE", "GD_FC_STACK"); - - @SuppressWarnings("unchecked") - Map stackBlock = (Map) snapshot.blocks().stream() - .filter(block -> "GD_FC_STACK".equals(block.get("type"))) - .findFirst() - .orElseThrow() - .get("data"); - @SuppressWarnings("unchecked") - List> stacks = (List>) stackBlock.get("stacks"); - assertThat(stacks).hasSize(1); - @SuppressWarnings("unchecked") - List mergedVoltages = (List) stacks.getFirst().get("frameCellVoltagesV"); - assertThat(stacks.getFirst()).containsEntry("frameCellStart", 1); - assertThat(stacks.getFirst()).containsEntry("frameCellCount", 9); - assertThat(stacks.getFirst()).containsEntry("frameCellComplete", true); - assertThat(stacks.getFirst()).containsEntry("frameCellMissingCount", 0L); - assertThat(mergedVoltages).hasSize(9); - List expected = List.of(1.001, 1.002, 1.003, 1.004, 1.005, 1.006, 1.007, 1.008, 1.009); - for (int i = 0; i < expected.size(); i++) { - assertThat(((Number) mergedVoltages.get(i)).doubleValue()).isCloseTo(expected.get(i), within(0.000001)); - } - } - - @Test - void snapshotsUseTdengineRawFrames() throws Exception { - String vin = "LNVFC000000000001"; - String key = "2026/06/22/GB32960/" + vin + "/tdengine-raw.bin"; - String uri = "archive://" + key; - writeArchive(key, realtimeFrame()); - CapturingTdengineReader reader = new CapturingTdengineReader(List.of(new TdengineRawFrameRow( - Instant.parse("2026-06-22T10:00:00Z"), - "tdengine-raw", - Instant.parse("2026-06-22T10:00:01Z"), - 0x02, - 0, - Instant.parse("2026-06-22T10:00:00Z"), - uri, - "sha256:test", - realtimeFrame().length, - "SUCCEEDED", - "", - "127.0.0.1:32960", - "{\"platformAccount\":\"Hyundai\"}", - "", - "GB32960", - vin, - vin, - ""))); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - reader, - decoder(), - archiveRoot, - null); - - List snapshots = service.snapshots( - QueryTimeRange.parse("2026-06-22T18:00:00", "2026-06-22T18:00:00"), - HistoryQueryOrder.DESC, - 10, - vin, - null); - - assertThat(reader.lastQuery.protocol()).isEqualTo("GB32960"); - assertThat(reader.lastQuery.vehicleKey()).isEqualTo(vin); - assertThat(snapshots).hasSize(1); - assertThat(snapshots.getFirst().sourceFrames()).extracting(Gb32960DecodedFrameService.SourceFrame::rawArchiveUri) - .containsExactly(uri); - assertThat(snapshots.getFirst().blocks()).extracting(block -> block.get("type")) - .containsExactly("VEHICLE", "GD_FC_DCDC"); - } - - @Test - void queryCanUseTdengineRawFramesWithoutVinForPlatformLoginFrames() throws Exception { - String key = "2026/06/22/GB32960/unknown-vin/platform-login.bin"; - String uri = "archive://" + key; - writeArchive(key, platformLoginFrame()); - CapturingTdengineReader reader = new CapturingTdengineReader(List.of(new TdengineRawFrameRow( - Instant.parse("2026-06-22T10:00:00Z"), - "tdengine-platform-login", - Instant.parse("2026-06-22T10:00:01Z"), - 0x05, - 0, - Instant.parse("2026-06-22T10:00:00Z"), - uri, - "sha256:test", - platformLoginFrame().length, - "SUCCEEDED", - "", - "8.134.95.166:37302", - "{\"platformAccount\":\"Hyundai\"}", - "", - "GB32960", - "unknown:GB32960:platform-login", - "", - ""))); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - reader, - decoder(), - archiveRoot, - null); - - List frames = service.query( - LocalDate.parse("2026-06-22"), - LocalDate.parse("2026-06-22"), - HistoryQueryOrder.DESC, - 10, - null, - "Hyundai"); - - assertThat(reader.lastQuery.protocol()).isEqualTo("GB32960"); - assertThat(reader.lastQuery.vehicleKey()).isBlank(); - assertThat(reader.lastQuery.vin()).isBlank(); - assertThat(frames).hasSize(1); - assertThat(frames.getFirst().command()).isEqualTo("PLATFORM_LOGIN"); - assertThat(frames.getFirst().rawArchiveUri()).isEqualTo(uri); - } - - @Test - void framePageUsesTdengineCursorAndReturnsNextCursor() throws Exception { - String vin = "LNVFC000000000001"; - String key = "2026/06/22/GB32960/" + vin + "/tdengine-page.bin"; - String uri = "archive://" + key; - writeArchive(key, realtimeFrame()); - TdenginePageCursor nextCursor = new TdenginePageCursor( - Instant.parse("2026-06-22T10:00:00Z"), "tdengine-page"); - CapturingTdengineReader reader = new CapturingTdengineReader(List.of(new TdengineRawFrameRow( - Instant.parse("2026-06-22T10:00:00Z"), - "tdengine-page", - Instant.parse("2026-06-22T10:00:01Z"), - 0x02, - 0, - Instant.parse("2026-06-22T10:00:00Z"), - uri, - "sha256:test", - realtimeFrame().length, - "SUCCEEDED", - "", - "127.0.0.1:32960", - "{\"platformAccount\":\"Hyundai\"}", - "", - "GB32960", - vin, - vin, - "")), nextCursor); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - reader, - decoder(), - archiveRoot, - null); - - Gb32960DecodedFrameService.DecodedFramePage page = service.queryPage( - QueryTimeRange.parse("2026-06-22T18:00:00", "2026-06-22T18:00:00"), - HistoryQueryOrder.ASC, - 10, - vin, - null, - new TdenginePageCursor(Instant.parse("2026-06-22T09:59:00Z"), "previous")); - - assertThat(reader.lastQuery.protocol()).isEqualTo("GB32960"); - assertThat(reader.lastQuery.vehicleKey()).isEqualTo(vin); - assertThat(reader.lastQuery.order()).isEqualTo(TdengineQueryOrder.ASC); - assertThat(reader.lastQuery.cursor()).isEqualTo(new TdenginePageCursor( - Instant.parse("2026-06-22T09:59:00Z"), "previous")); - assertThat(page.items()).hasSize(1); - assertThat(page.items().getFirst().rawArchiveUri()).isEqualTo(uri); - assertThat(page.nextCursor()).isEqualTo(new Gb32960DecodedFrameService.CursorResponse( - "2026-06-22T10:00:00Z", "tdengine-page")); - } - - @Test - void snapshotsKeepMissingStackCellsAsNullsUpToCellCount() throws Exception { - String vin = "LNVFC000000000001"; - String key = "2026/06/22/GB32960/" + vin + "/stack-tail-missing.bin"; - writeArchive(key, stackFrame(1, 4, true)); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - new CapturingTdengineReader(List.of(rawRow("partial", "archive://" + key))), - decoder(), - archiveRoot, - null); - - List snapshots = service.snapshots( - LocalDate.parse("2026-06-22"), - LocalDate.parse("2026-06-22"), - HistoryQueryOrder.DESC, - 10, - vin, - null); - - @SuppressWarnings("unchecked") - Map stackBlock = (Map) snapshots.getFirst().blocks().stream() - .filter(block -> "GD_FC_STACK".equals(block.get("type"))) - .findFirst() - .orElseThrow() - .get("data"); - @SuppressWarnings("unchecked") - List> stacks = (List>) stackBlock.get("stacks"); - @SuppressWarnings("unchecked") - List voltages = (List) stacks.getFirst().get("frameCellVoltagesV"); - - assertThat(stacks.getFirst()).containsEntry("frameCellStart", 1); - assertThat(stacks.getFirst()).containsEntry("frameCellCount", 9); - assertThat(stacks.getFirst()).containsEntry("frameCellComplete", false); - assertThat(stacks.getFirst()).containsEntry("frameCellMissingCount", 5L); - assertThat(voltages).hasSize(9); - assertThat(voltages.subList(4, 9)).containsOnlyNulls(); - } - - @Test - void snapshotFieldsReturnsOnlySelectedFields() throws Exception { - String vin = "LNVFC000000000001"; - String key = "2026/06/22/GB32960/" + vin + "/selected-fields.bin"; - writeArchive(key, realtimeFrame()); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - new CapturingTdengineReader(List.of(rawRow("selected", "archive://" + key))), - decoder(), - archiveRoot, - null); - - List snapshots = service.snapshotFields( - QueryTimeRange.parse("2026-06-22T18:00:00", "2026-06-22T18:00:00"), - HistoryQueryOrder.DESC, - 10, - vin, - null, - "VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV,GD_FC_DCDC.outputCurrentA"); - - assertThat(snapshots).hasSize(1); - assertThat(snapshots.getFirst().fields()) - .containsEntry("VEHICLE.totalMileageKm", new BigDecimal("10000")) - .containsEntry("GD_FC_DCDC.inputVoltageV", new BigDecimal("300")) - .containsEntry("GD_FC_DCDC.outputCurrentA", new BigDecimal("110")); - } - - @Test - void snapshotFieldsCsvUsesChineseDictionaryHeaders() throws Exception { - String vin = "LNVFC000000000001"; - String key = "2026/06/22/GB32960/" + vin + "/selected-fields-csv.bin"; - writeArchive(key, realtimeFrame()); - - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - new CapturingTdengineReader(List.of(rawRow("selected-csv", "archive://" + key))), - decoder(), - archiveRoot, - null); - - String csv = service.snapshotFieldsCsv( - QueryTimeRange.parse("2026-06-22T18:00:00", "2026-06-22T18:00:00"), - HistoryQueryOrder.DESC, - 10, - vin, - null, - "VEHICLE.totalMileageKm,GD_FC_DCDC.inputVoltageV"); - - assertThat(csv).startsWith("VIN,事件时间,首次接收时间,最后接收时间,原始包,整车数据-累计里程(km),广东燃料电池 DC/DC 数据-输入电压(V)\n"); - assertThat(csv).contains("LNVFC000000000001,2026-06-22T10:00:00Z"); - assertThat(csv).contains(",10000,300\n"); - } - - @Test - void snapshotFieldsRejectsUnknownDictionaryFields() throws Exception { - Gb32960DecodedFrameService service = new Gb32960DecodedFrameService( - (TdengineHistoryReader) null, - decoder(), - archiveRoot, - null); - - assertThatThrownBy(() -> service.snapshotFields( - QueryTimeRange.parse("2026-06-22T18:00:00", "2026-06-22T18:00:00"), - HistoryQueryOrder.DESC, - 10, - "LNVFC000000000001", - null, - "VEHICLE.totalMileageKm,VEHICLE.notExists")) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("unknown gb32960 snapshot fields: VEHICLE.notExists"); - } - - private static Gb32960MessageDecoder decoder() { - List standardParsers = List.of(new VehicleV2016BlockParser()); - VendorExtensionCatalog catalog = new VendorExtensionCatalog(Map.of( - "guangdong-fc", List.of(new GdFcDcDcBlockParser(), new GdFcStackBlockParser()))); - Gb32960ProfileRegistry registry = new Gb32960ProfileRegistry( - standardParsers, catalog, Set.of("guangdong-fc")); - VendorExtensionSelector selector = ctx -> "guangdong-fc"; - return new Gb32960MessageDecoder(new Gb32960BodyParser(registry, selector)); - } - - private static TdengineRawFrameRow rawRow(String eventId, String rawArchiveUri) { - return rawRow(eventId, rawArchiveUri, "LNVFC000000000001"); - } - - private static TdengineRawFrameRow rawRow(String eventId, String rawArchiveUri, String vin) { - return new TdengineRawFrameRow( - Instant.parse("2026-06-22T10:00:00Z"), - eventId, - Instant.parse("2026-06-22T10:00:01Z"), - 0x02, - 0, - Instant.parse("2026-06-22T10:00:00Z"), - rawArchiveUri, - "sha256:test", - 64, - "SUCCEEDED", - "", - "127.0.0.1:32960", - "{\"platformAccount\":\"Hyundai\"}", - "", - "GB32960", - vin, - vin, - ""); - } - - private static byte[] realtimeFrame() { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - body.write(26); body.write(6); body.write(22); body.write(18); body.write(0); body.write(0); - - body.write(0x01); - body.write(0x01); - body.write(0x03); - body.write(0x02); - writeU16(body, 0); - writeU32(body, 100_000L); - writeU16(body, 5605); - writeU16(body, 10000); - body.write(85); - body.write(0x01); - body.write(0x00); - writeU16(body, 10000); - body.write(0x00); - body.write(0x00); - - body.write(0x32); - writeU16(body, 3000); - writeU16(body, 1200); - writeU16(body, 5600); - writeU16(body, 1100); - body.write(105); - - byte[] bodyBytes = body.toByteArray(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x02); - out.write(0xFE); - byte[] vin = "LNVFC000000000001".getBytes(StandardCharsets.US_ASCII); - out.write(vin, 0, 17); - out.write(0x01); - writeU16(out, bodyBytes.length); - out.write(bodyBytes, 0, bodyBytes.length); - byte[] almost = out.toByteArray(); - out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF); - return out.toByteArray(); - } - - private static byte[] platformLoginFrame() { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - body.write(26); body.write(6); body.write(22); body.write(18); body.write(0); body.write(0); - writeU16(body, 1); - writeAsciiPadded(body, "Hyundai", 12); - writeAsciiPadded(body, "f2e3445d7cda409fb4f", 20); - body.write(0x01); - - byte[] bodyBytes = body.toByteArray(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x05); - out.write(0xFE); - for (int i = 0; i < 17; i++) { - out.write(0); - } - out.write(0x01); - writeU16(out, bodyBytes.length); - out.write(bodyBytes, 0, bodyBytes.length); - byte[] almost = out.toByteArray(); - out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF); - return out.toByteArray(); - } - - private static void writeAsciiPadded(ByteArrayOutputStream out, String value, int length) { - byte[] bytes = value.getBytes(StandardCharsets.US_ASCII); - int count = Math.min(bytes.length, length); - out.write(bytes, 0, count); - for (int i = count; i < length; i++) { - out.write(0); - } - } - - private void writeArchive(String key, byte[] frame) throws Exception { - Path rawFile = archiveRoot.resolve(key); - Files.createDirectories(rawFile.getParent()); - Files.write(rawFile, frame); - } - - private static byte[] stackFrame(int frameCellStart, int frameCellCount, boolean includeVehicle) { - ByteArrayOutputStream body = new ByteArrayOutputStream(); - body.write(26); body.write(6); body.write(22); body.write(18); body.write(0); body.write(0); - - if (includeVehicle) { - body.write(0x01); - body.write(0x01); - body.write(0x03); - body.write(0x02); - writeU16(body, 0); - writeU32(body, 100_000L); - writeU16(body, 5605); - writeU16(body, 10000); - body.write(85); - body.write(0x01); - body.write(0x00); - writeU16(body, 10000); - body.write(0x00); - body.write(0x00); - } - - body.write(0x30); - body.write(0x01); - body.write(0x01); - body.write(90); - writeU16(body, 1850); - writeU16(body, 1500); - body.write(70); - writeU16(body, 12); - writeU16(body, 88); - writeU16(body, 1009); - writeU16(body, 1001); - writeU16(body, 1005); - writeU16(body, 9); - writeU16(body, frameCellStart); - body.write(frameCellCount); - for (int i = 0; i < frameCellCount; i++) { - writeU16(body, 1000 + frameCellStart + i); - } - - byte[] bodyBytes = body.toByteArray(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - out.write(0x23); out.write(0x23); - out.write(0x02); - out.write(0xFE); - byte[] vin = "LNVFC000000000001".getBytes(StandardCharsets.US_ASCII); - out.write(vin, 0, 17); - out.write(0x01); - writeU16(out, bodyBytes.length); - out.write(bodyBytes, 0, bodyBytes.length); - byte[] almost = out.toByteArray(); - out.write(BccChecksum.compute(almost, 2, almost.length - 2) & 0xFF); - return out.toByteArray(); - } - - private static void writeU16(ByteArrayOutputStream os, int v) { - os.write((v >> 8) & 0xFF); - os.write(v & 0xFF); - } - - private static void writeU32(ByteArrayOutputStream os, long v) { - os.write((int) ((v >> 24) & 0xFF)); - os.write((int) ((v >> 16) & 0xFF)); - os.write((int) ((v >> 8) & 0xFF)); - os.write((int) (v & 0xFF)); - } - - private static final class CapturingTdengineReader implements TdengineHistoryReader { - private final List rows; - private final TdenginePageCursor nextCursor; - private TdengineRawFrameQuery lastQuery; - - private CapturingTdengineReader(List rows) { - this(rows, null); - } - - private CapturingTdengineReader(List rows, TdenginePageCursor nextCursor) { - this.rows = rows; - this.nextCursor = nextCursor; - } - - @Override - public TdenginePage queryRawFrames(TdengineRawFrameQuery query) { - lastQuery = query; - return new TdenginePage<>(rows, java.util.Optional.ofNullable(nextCursor)); - } - - @Override - public TdenginePage queryLocations(TdengineLocationQuery query) { - throw new UnsupportedOperationException(); - } - - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionaryTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionaryTest.java deleted file mode 100644 index 250194de..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Gb32960FieldDictionaryTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static java.util.stream.Collectors.toMap; -import static org.assertj.core.api.Assertions.assertThat; - -class Gb32960FieldDictionaryTest { - - @Test - void vehicleRunningModeIncludesProtocolValueMappings() { - Gb32960FieldDictionary.Field runningMode = Gb32960FieldDictionary.packets().stream() - .filter(packet -> packet.code().equals("VEHICLE")) - .flatMap(packet -> packet.fields().stream()) - .filter(field -> field.key().equals("runningMode")) - .findFirst() - .orElseThrow(); - - Map namesByCode = runningMode.valueMappings().stream() - .collect(toMap(Gb32960FieldDictionary.ValueMapping::code, - Gb32960FieldDictionary.ValueMapping::nameZh)); - - assertThat(namesByCode) - .containsEntry("01", "纯电") - .containsEntry("02", "混合动力") - .containsEntry("03", "燃料电池"); - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryControllerTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryControllerTest.java deleted file mode 100644 index 489afc97..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808LocationHistoryControllerTest.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class Jt808LocationHistoryControllerTest { - - @Test - void queriesLocationsByPhoneWithKeysetCursor() throws Exception { - CapturingReader reader = new CapturingReader(new TdenginePage<>( - List.of(row("fact-1", "2026-06-29T05:00:01Z")), - Optional.of(new TdenginePageCursor(Instant.parse("2026-06-29T05:00:01Z"), "fact-1")))); - Jt808LocationHistoryController controller = new Jt808LocationHistoryController(reader); - - Jt808LocationHistoryController.LocationPageResponse response = controller.locations( - "g7gps", - "2026-06-29T13:00:00+08:00", - "2026-06-29T13:10:00+08:00", - TdengineQueryOrder.DESC, - 50, - "2026-06-29T05:00:00Z", - "fact-0"); - - assertThat(reader.query.protocol()).isEqualTo("JT808"); - assertThat(reader.query.vehicleKey()).isEqualTo("jt808:g7gps"); - assertThat(reader.query.order()).isEqualTo(TdengineQueryOrder.DESC); - assertThat(reader.query.limit()).isEqualTo(50); - assertThat(reader.query.cursor()).isEqualTo(new TdenginePageCursor( - Instant.parse("2026-06-29T05:00:00Z"), "fact-0")); - assertThat(response.items()) - .extracting(Jt808LocationHistoryController.LocationResponse::factId) - .containsExactly("fact-1"); - assertThat(response.items().getFirst().longitude()).isEqualTo(113.12); - assertThat(response.nextCursor()).isEqualTo(new Jt808LocationHistoryController.CursorResponse( - "2026-06-29T05:00:01Z", "fact-1")); - } - - @Test - void returnsLocationRawUriFromCoreColumn() throws Exception { - CapturingReader reader = new CapturingReader(new TdenginePage<>( - List.of(row( - "fact-blank-raw", - "2026-06-29T05:00:01Z", - "archive://jt808/core-frame.bin")), - Optional.empty())); - Jt808LocationHistoryController controller = new Jt808LocationHistoryController(reader); - - Jt808LocationHistoryController.LocationPageResponse response = controller.locations( - "g7gps", - "2026-06-29T13:00:00+08:00", - "2026-06-29T13:10:00+08:00", - TdengineQueryOrder.DESC, - 50, - null, - null); - - assertThat(response.items().getFirst().rawUri()) - .isEqualTo("archive://jt808/core-frame.bin"); - } - - @Test - void rejectsBlankPhone() { - Jt808LocationHistoryController controller = new Jt808LocationHistoryController( - new CapturingReader(new TdenginePage<>(List.of(), Optional.empty()))); - - assertThatThrownBy(() -> controller.locations( - " ", - "2026-06-29T13:00:00+08:00", - "2026-06-29T13:10:00+08:00", - TdengineQueryOrder.DESC, - 50, - null, - null)) - .isInstanceOfSatisfying(ResponseStatusException.class, ex -> - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST)); - } - - private static TdengineLocationRow row(String factId, String ts) { - return row(factId, ts, "archive://jt808/frame-1.bin"); - } - - private static TdengineLocationRow row(String factId, String ts, String rawUri) { - Instant instant = Instant.parse(ts); - return new TdengineLocationRow( - instant, - factId, - "frame-1", - instant.plusMillis(500), - 113.12, - 23.45, - 8.0, - 42.5, - 90.0, - 1L, - 3L, - null, - rawUri, - "JT808", - "jt808:g7gps", - "", - "g7gps"); - } - - private static final class CapturingReader implements TdengineHistoryReader { - private final TdenginePage response; - private TdengineLocationQuery query; - - private CapturingReader(TdenginePage response) { - this.response = response; - } - - @Override - public TdenginePage queryRawFrames( - com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery query) { - throw new UnsupportedOperationException(); - } - - @Override - public TdenginePage queryLocations(TdengineLocationQuery query) throws IOException { - this.query = query; - return response; - } - - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryControllerTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryControllerTest.java deleted file mode 100644 index 6cee6ebe..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/Jt808RawFrameHistoryControllerTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.List; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class Jt808RawFrameHistoryControllerTest { - - @Test - void queriesRawFramesByPhoneMessageIdAndCursor() throws Exception { - CapturingReader reader = new CapturingReader(new TdenginePage<>( - List.of(row("frame-1", "2026-06-29T12:00:01Z")), - Optional.of(new TdenginePageCursor(Instant.parse("2026-06-29T12:00:01Z"), "frame-1")))); - Jt808RawFrameHistoryController controller = new Jt808RawFrameHistoryController(reader); - - Jt808RawFrameHistoryController.RawFramePageResponse response = controller.rawFrames( - "13079963320", - null, - null, - 0x0100, - null, - "2026-06-29T00:00:00+08:00", - "2026-06-29T23:59:59+08:00", - TdengineQueryOrder.DESC, - 20, - "2026-06-29T12:00:00Z", - "frame-0"); - - assertThat(reader.query.protocol()).isEqualTo("JT808"); - assertThat(reader.query.vehicleKey()).isBlank(); - assertThat(reader.query.phone()).isEqualTo("13079963320"); - assertThat(reader.query.messageId()).isEqualTo(0x0100); - assertThat(reader.query.cursor()).isEqualTo(new TdenginePageCursor( - Instant.parse("2026-06-29T12:00:00Z"), "frame-0")); - assertThat(response.items()).hasSize(1); - Jt808RawFrameHistoryController.RawFrameResponse item = response.items().getFirst(); - assertThat(item.messageId()).isEqualTo(0x0100); - assertThat(item.messageIdHex()).isEqualTo("0x0100"); - assertThat(item.metadataJson()).contains("jt808.register.deviceId"); - assertThat(response.nextCursor()).isEqualTo(new Jt808RawFrameHistoryController.CursorResponse( - "2026-06-29T12:00:01Z", "frame-1")); - } - - @Test - void rejectsUnboundedRawFrameQuery() { - Jt808RawFrameHistoryController controller = new Jt808RawFrameHistoryController( - new CapturingReader(new TdenginePage<>(List.of(), Optional.empty()))); - - assertThatThrownBy(() -> controller.rawFrames( - null, - null, - null, - null, - null, - "2026-06-29", - "2026-06-29", - TdengineQueryOrder.DESC, - 20, - null, - null)) - .isInstanceOfSatisfying(ResponseStatusException.class, ex -> - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST)); - } - - private static TdengineRawFrameRow row(String frameId, String ts) { - Instant instant = Instant.parse(ts); - return new TdengineRawFrameRow( - instant, - frameId, - instant.plusMillis(500), - 0x0100, - 0, - instant, - "archive://2026/06/29/JT808/unknown/" + frameId + ".bin", - "sha256:" + frameId, - 72, - "SUCCEEDED", - "", - "222.66.200.68:41000", - "{\"jt808.register.deviceId\":\"9963320\",\"jt808.register.plate\":\"沪A61559F\"}", - "", - "JT808", - "jt808:13079963320", - "unknown", - "13079963320"); - } - - private static final class CapturingReader implements TdengineHistoryReader { - private final TdenginePage response; - private TdengineRawFrameQuery query; - - private CapturingReader(TdenginePage response) { - this.response = response; - } - - @Override - public TdenginePage queryRawFrames(TdengineRawFrameQuery query) throws IOException { - this.query = query; - return response; - } - - @Override - public TdenginePage queryLocations(TdengineLocationQuery query) { - throw new UnsupportedOperationException(); - } - - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/LocationHistoryControllerTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/LocationHistoryControllerTest.java deleted file mode 100644 index f572aa76..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/LocationHistoryControllerTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class LocationHistoryControllerTest { - - @Test - void queriesGenericLocationHistoryWithCursor() throws Exception { - AtomicReference captured = new AtomicReference<>(); - TdengineHistoryReader reader = new StubReader(captured, new TdenginePage<>( - List.of(row("fact-1")), - Optional.of(new TdenginePageCursor(Instant.parse("2026-06-29T05:00:01Z"), "fact-1")))); - LocationHistoryController controller = new LocationHistoryController(reader); - - LocationHistoryController.LocationPageResponse response = controller.locations( - "mqtt_yutong", - null, - "LMRKH9AC2R1004087", - null, - "2026-06-29T13:00:00", - "2026-06-29T13:10:00", - TdengineQueryOrder.DESC, - 100, - null, - null); - - assertThat(captured.get().protocol()).isEqualTo("MQTT_YUTONG"); - assertThat(captured.get().vehicleKey()).isEqualTo("LMRKH9AC2R1004087"); - assertThat(response.items()).extracting(LocationHistoryController.LocationResponse::factId) - .containsExactly("fact-1"); - assertThat(response.nextCursor()).isEqualTo(new LocationHistoryController.CursorResponse( - "2026-06-29T05:00:01Z", "fact-1")); - } - - @Test - void mapsJt808PhoneToVehicleKey() throws Exception { - AtomicReference captured = new AtomicReference<>(); - LocationHistoryController controller = new LocationHistoryController( - new StubReader(captured, new TdenginePage<>(List.of(), null))); - - controller.locations( - "JT808", - null, - null, - "g7gps", - "2026-06-29T13:00:00", - "2026-06-29T13:10:00", - TdengineQueryOrder.DESC, - 100, - null, - null); - - assertThat(captured.get().vehicleKey()).isEqualTo("jt808:g7gps"); - } - - @Test - void rejectsRemovedXindaProtocolOnDefaultLocationApi() { - AtomicReference captured = new AtomicReference<>(); - LocationHistoryController controller = new LocationHistoryController( - new StubReader(captured, new TdenginePage<>(List.of(), null))); - - assertThatThrownBy(() -> controller.locations( - "XINDA_PUSH", - null, - "LB9A32A24P0LS1270", - null, - "2026-06-29T13:00:00", - "2026-06-29T13:10:00", - TdengineQueryOrder.DESC, - 100, - null, - null)) - .isInstanceOfSatisfying(ResponseStatusException.class, ex -> - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST)); - assertThat(captured).hasValue(null); - } - - private static TdengineLocationRow row(String factId) { - Instant ts = Instant.parse("2026-06-29T05:00:00Z"); - return new TdengineLocationRow( - ts, - factId, - "frame-1", - ts, - 121.1, - 30.5, - 0.0, - 32.0, - 90.0, - 0, - 1, - null, - "", - "MQTT_YUTONG", - "LMRKH9AC2R1004087", - "LMRKH9AC2R1004087", - ""); - } - - private record StubReader(AtomicReference captured, - TdenginePage page) implements TdengineHistoryReader { - - @Override - public com.lingniu.ingest.tdenginehistory.TdenginePage queryRawFrames( - com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery query) { - throw new UnsupportedOperationException(); - } - - @Override - public TdenginePage queryLocations(TdengineLocationQuery query) { - captured.set(query); - return page; - } - - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/RawFrameHistoryControllerTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/RawFrameHistoryControllerTest.java deleted file mode 100644 index 9d59ade1..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/RawFrameHistoryControllerTest.java +++ /dev/null @@ -1,277 +0,0 @@ -package com.lingniu.ingest.eventhistory; - -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineLocationQuery; -import com.lingniu.ingest.tdenginehistory.TdengineLocationRow; -import com.lingniu.ingest.tdenginehistory.TdenginePage; -import com.lingniu.ingest.tdenginehistory.TdenginePageCursor; -import com.lingniu.ingest.tdenginehistory.TdengineQueryOrder; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameQuery; -import com.lingniu.ingest.tdenginehistory.TdengineRawFrameRow; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -import java.io.IOException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class RawFrameHistoryControllerTest { - - @Test - void queriesRawFramesByProtocolHexMessageIdAndCursor() throws Exception { - CapturingReader reader = new CapturingReader(new TdenginePage<>( - List.of(row("frame-1", "2026-06-29T12:00:01Z")), - Optional.of(new TdenginePageCursor(Instant.parse("2026-06-29T12:00:01Z"), "frame-1")))); - RawFrameHistoryController controller = new RawFrameHistoryController(reader); - - RawFrameHistoryController.RawFramePageResponse response = controller.rawFrames( - "mqtt_yutong", - null, - "LB9A32A24P0LS1270", - null, - "0x0200", - "not_parsed", - "2026-06-29T00:00:00+08:00", - "2026-06-29T23:59:59+08:00", - TdengineQueryOrder.DESC, - 20, - "2026-06-29T12:00:00Z", - "frame-0"); - - assertThat(reader.query.protocol()).isEqualTo("MQTT_YUTONG"); - assertThat(reader.query.vin()).isEqualTo("LB9A32A24P0LS1270"); - assertThat(reader.query.messageId()).isEqualTo(0x0200); - assertThat(reader.query.parseStatus()).isEqualTo("NOT_PARSED"); - assertThat(reader.query.cursor()).isEqualTo(new TdenginePageCursor( - Instant.parse("2026-06-29T12:00:00Z"), "frame-0")); - assertThat(response.items()).hasSize(1); - RawFrameHistoryController.RawFrameResponse item = response.items().getFirst(); - assertThat(item.messageId()).isEqualTo(0x0200); - assertThat(item.messageIdHex()).isEqualTo("0x0200"); - assertThat(item.rawUri()).contains("MQTT_YUTONG"); - assertThat(item.metadataJson()).contains("rawArchiveUri"); - assertThat(item.metadata()) - .containsEntry("rawArchiveUri", item.rawUri()) - .containsEntry("plateNo", "粤BD12345") - .containsEntry("rawJson", "{\"carName\":\"粤BD12345\"}"); - assertThat(item.parsedFields()) - .containsEntry("raw.rawArchiveUri", item.rawUri()) - .containsEntry("raw.plateNo", "粤BD12345"); - assertThat(reader.locationRawUri).isNull(); - assertThat(response.nextCursor()).isEqualTo(new RawFrameHistoryController.CursorResponse( - "2026-06-29T12:00:01Z", "frame-1")); - } - - @Test - void returnsParsedJsonWithoutQueryingDerivedTelemetryTables() throws Exception { - Instant ts = Instant.parse("2026-06-29T12:00:01Z"); - TdengineRawFrameRow raw = new TdengineRawFrameRow( - ts, - "frame-1", - ts.plusMillis(500), - 0x0200, - 0, - ts, - "archive://2026/06/29/JT808/VIN-JT808-1/frame-1.bin", - "sha256:frame-1", - 72, - "SUCCEEDED", - "", - "115.231.168.135:43625", - "{\"rawArchiveUri\":\"archive://2026/06/29/JT808/VIN-JT808-1/frame-1.bin\"}", - "{\"location\":{\"longitude\":121.312516,\"latitude\":30.832808,\"speedKmh\":25.0}," - + "\"extras\":{\"gpsMileageKm\":14504.446}}", - "JT808", - "VIN-JT808-1", - "VIN-JT808-1", - "g7gps"); - CapturingReader reader = new CapturingReader(new TdenginePage<>(List.of(raw), Optional.empty())); - reader.locationsByRawUri.add(new TdengineLocationRow( - ts, - "loc-1", - "frame-1", - ts.plusMillis(500), - 121.312516, - 30.832808, - 6.0, - 25.0, - 333.0, - 0, - 524290, - 14504.446, - raw.rawUri(), - "JT808", - "VIN-JT808-1", - "VIN-JT808-1", - "g7gps")); - RawFrameHistoryController controller = new RawFrameHistoryController(reader); - - RawFrameHistoryController.RawFramePageResponse response = controller.rawFrames( - "jt808", - null, - "VIN-JT808-1", - null, - "0x0200", - null, - "2026-06-29T00:00:00+08:00", - "2026-06-29T23:59:59+08:00", - TdengineQueryOrder.DESC, - 20, - null, - null); - - RawFrameHistoryController.RawFrameResponse item = response.items().getFirst(); - assertThat(reader.locationRawUri).isNull(); - assertThat(item.parsedFields()) - .containsKey("location") - .containsKey("extras"); - } - - @Test - void preservesNullValuesFromParsedJsonWithoutFailingResponseMapping() throws Exception { - Instant ts = Instant.parse("2026-06-29T12:00:01Z"); - TdengineRawFrameRow raw = new TdengineRawFrameRow( - ts, - "frame-with-null-json", - ts.plusMillis(500), - 0x0002, - 0, - ts, - "archive://2026/06/29/GB32960/LB9A32A24P0LS1270/frame-with-null-json.bin", - "sha256:null-json", - 128, - "NOT_PARSED", - "", - "8.134.95.166:49706", - "{\"rawArchiveUri\":\"archive://2026/06/29/GB32960/LB9A32A24P0LS1270/frame-with-null-json.bin\"}", - "{\"header\":{\"command\":\"REALTIME_REPORT\"},\"commandBody\":null,\"signature\":null}", - "GB32960", - "LB9A32A24P0LS1270", - "LB9A32A24P0LS1270", - ""); - RawFrameHistoryController controller = new RawFrameHistoryController( - new CapturingReader(new TdenginePage<>(List.of(raw), Optional.empty()))); - - RawFrameHistoryController.RawFramePageResponse response = controller.rawFrames( - "gb32960", - null, - "LB9A32A24P0LS1270", - null, - null, - "not_parsed", - "2026-06-29T00:00:00+08:00", - "2026-06-29T23:59:59+08:00", - TdengineQueryOrder.DESC, - 20, - null, - null); - - Map parsedFields = response.items().getFirst().parsedFields(); - assertThat(parsedFields).containsKeys("header", "commandBody", "signature"); - assertThat(parsedFields.get("commandBody")).isNull(); - assertThat(parsedFields.get("signature")).isNull(); - } - - @Test - void rejectsRawFrameQueryWithoutAnyNarrowingFilter() { - RawFrameHistoryController controller = new RawFrameHistoryController( - new CapturingReader(new TdenginePage<>(List.of(), Optional.empty()))); - - assertThatThrownBy(() -> controller.rawFrames( - "JT808", - null, - null, - null, - null, - null, - "2026-06-29", - "2026-06-30", - TdengineQueryOrder.DESC, - 20, - null, - null)) - .isInstanceOfSatisfying(ResponseStatusException.class, ex -> - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST)); - } - - @Test - void rejectsRemovedXindaProtocolOnDefaultRawFrameApi() { - RawFrameHistoryController controller = new RawFrameHistoryController( - new CapturingReader(new TdenginePage<>(List.of(), Optional.empty()))); - - assertThatThrownBy(() -> controller.rawFrames( - "XINDA_PUSH", - null, - "LB9A32A24P0LS1270", - null, - null, - null, - "2026-06-29", - "2026-06-30", - TdengineQueryOrder.DESC, - 20, - null, - null)) - .isInstanceOfSatisfying(ResponseStatusException.class, ex -> - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST)); - } - - private static TdengineRawFrameRow row(String frameId, String ts) { - Instant instant = Instant.parse(ts); - return new TdengineRawFrameRow( - instant, - frameId, - instant.plusMillis(500), - 0x0200, - 0, - instant, - "archive://2026/06/29/MQTT_YUTONG/VEHICLE-MQTT-1/" + frameId + ".bin", - "sha256:" + frameId, - 72, - "NOT_PARSED", - "", - "mqtt-yutong", - "{\"rawArchiveUri\":\"archive://2026/06/29/MQTT_YUTONG/VEHICLE-MQTT-1/" + frameId - + ".bin\",\"plateNo\":\"粤BD12345\",\"rawJson\":\"{\\\"carName\\\":\\\"粤BD12345\\\"}\"}", - "", - "MQTT_YUTONG", - "LB9A32A24P0LS1270", - "LB9A32A24P0LS1270", - ""); - } - - private static final class CapturingReader implements TdengineHistoryReader { - private final TdenginePage response; - private final List locationsByRawUri = new ArrayList<>(); - private TdengineRawFrameQuery query; - private String locationRawUri; - - private CapturingReader(TdenginePage response) { - this.response = response; - } - - @Override - public TdenginePage queryRawFrames(TdengineRawFrameQuery query) throws IOException { - this.query = query; - return response; - } - - @Override - public TdenginePage queryLocations(TdengineLocationQuery query) { - throw new UnsupportedOperationException(); - } - - @Override - public List queryLocationsByRawUri(String protocol, String rawUri, int limit) { - locationRawUri = rawUri; - return locationsByRawUri; - } - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfigurationTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfigurationTest.java deleted file mode 100644 index b0905dce..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryAutoConfigurationTest.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.lingniu.ingest.eventhistory.config; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import com.lingniu.ingest.eventhistory.ApiExceptionHandler; -import com.lingniu.ingest.eventhistory.EventHistoryEnvelopeIngestor; -import com.lingniu.ingest.eventhistory.Gb32960DecodedFrameService; -import com.lingniu.ingest.eventhistory.Gb32960FrameController; -import com.lingniu.ingest.eventhistory.Jt808LocationHistoryController; -import com.lingniu.ingest.eventhistory.Jt808RawFrameHistoryController; -import com.lingniu.ingest.eventhistory.LocationHistoryController; -import com.lingniu.ingest.eventhistory.RawFrameHistoryController; -import com.lingniu.ingest.protocol.gb32960.codec.Gb32960MessageDecoder; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class EventHistoryAutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EventHistoryAutoConfiguration.class)) - .withBean(EnvelopeDeadLetterSink.class, () -> record -> {}) - .withBean(TdengineHistoryWriter.class, () -> mock(TdengineHistoryWriter.class)); - - @Test - void createsHistoryBeansWhenEnabled() { - contextRunner - .withPropertyValues( - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.sink.kafka.consumer.enabled=true") - .run(context -> { - assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class); - assertThat(context.getBeansOfType(EnvelopeConsumerProcessor.class)) - .containsOnlyKeys( - "eventHistoryEnvelopeConsumerProcessor", - "eventHistoryRawEnvelopeConsumerProcessor"); - assertThat(context).hasSingleBean(Gb32960MessageDecoder.class); - assertThat(context).hasSingleBean(Gb32960DecodedFrameService.class); - assertThat(context).doesNotHaveBean(Gb32960FrameController.class); - }); - } - - @Test - void backsOffWhenDisabled() { - contextRunner.run(context -> { - assertThat(context).doesNotHaveBean(EventHistoryEnvelopeIngestor.class); - }); - } - - @Test - void createsGb32960FrameBeansWhenDecoderExists() { - contextRunner - .withPropertyValues( - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.event-history.api.specialized-enabled=true", - "lingniu.ingest.event-history.archive-path=/tmp/lingniu-test-archive") - .withBean(Gb32960MessageDecoder.class, () -> mock(Gb32960MessageDecoder.class)) - .run(context -> { - assertThat(context).hasSingleBean(Gb32960DecodedFrameService.class); - assertThat(context).hasSingleBean(Gb32960FrameController.class); - }); - } - - @Test - void createsGb32960FrameBeansWithTdengineReader() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EventHistoryAutoConfiguration.class)) - .withPropertyValues( - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.event-history.api.specialized-enabled=true", - "lingniu.ingest.event-history.archive-path=/tmp/lingniu-test-archive") - .withBean(Gb32960MessageDecoder.class, () -> mock(Gb32960MessageDecoder.class)) - .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class)) - .run(context -> { - assertThat(context).hasSingleBean(Gb32960DecodedFrameService.class); - assertThat(context).hasSingleBean(Gb32960FrameController.class); - }); - } - - @Test - void createsHistoryConsumerWithTdengineWriter() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EventHistoryAutoConfiguration.class)) - .withBean(EnvelopeDeadLetterSink.class, () -> record -> {}) - .withBean(TdengineHistoryWriter.class, () -> mock(TdengineHistoryWriter.class)) - .withPropertyValues("lingniu.ingest.event-history.enabled=true") - .run(context -> { - assertThat(context).hasSingleBean(EventHistoryEnvelopeIngestor.class); - assertThat(context.getBeansOfType(EnvelopeConsumerProcessor.class)) - .containsOnlyKeys( - "eventHistoryEnvelopeConsumerProcessor", - "eventHistoryRawEnvelopeConsumerProcessor"); - }); - } - - @Test - void createsJt808LocationHistoryControllerWhenTdengineReaderExists() { - contextRunner - .withPropertyValues( - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.event-history.api.specialized-enabled=true") - .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class)) - .run(context -> assertThat(context).hasSingleBean(Jt808LocationHistoryController.class)); - } - - @Test - void createsGenericLocationAndRawControllersWithoutTelemetryFieldsByDefault() { - contextRunner - .withPropertyValues("lingniu.ingest.event-history.enabled=true") - .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class)) - .run(context -> { - assertThat(context).hasSingleBean(LocationHistoryController.class); - assertThat(context).hasSingleBean(RawFrameHistoryController.class); - assertThat(context).hasSingleBean(ApiExceptionHandler.class); - assertThat(context).doesNotHaveBean("telemetryFieldHistoryController"); - assertThat(context).doesNotHaveBean(Jt808LocationHistoryController.class); - assertThat(context).doesNotHaveBean(Jt808RawFrameHistoryController.class); - }); - } - - @Test - void createsGenericRawFrameHistoryControllerWhenTdengineReaderExists() { - contextRunner - .withPropertyValues("lingniu.ingest.event-history.enabled=true") - .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class)) - .run(context -> assertThat(context).hasSingleBean(RawFrameHistoryController.class)); - } - - @Test - void createsJt808RawFrameHistoryControllerWhenTdengineReaderExists() { - contextRunner - .withPropertyValues( - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.event-history.api.specialized-enabled=true") - .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class)) - .run(context -> assertThat(context).hasSingleBean(Jt808RawFrameHistoryController.class)); - } - - @Test - void rejectsRemovedTelemetryFieldsSwitch() { - contextRunner - .withPropertyValues( - "lingniu.ingest.event-history.enabled=true", - "lingniu.ingest.tdengine-history.telemetry-fields-enabled=true") - .withBean(TdengineHistoryReader.class, () -> mock(TdengineHistoryReader.class)) - .run(context -> assertThat(context) - .hasFailed() - .getFailure() - .hasMessageContaining("tdengine-history.telemetry-fields-enabled has been removed") - .hasMessageContaining("raw_frames")); - } - - @Test - void parsesFileUriArchiveRoot() { - assertThat(EventHistoryAutoConfiguration.archiveRoot("file:///tmp/lingniu-test-archive")) - .isEqualTo(Path.of("/tmp/lingniu-test-archive")); - } -} diff --git a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryPomBoundaryTest.java b/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryPomBoundaryTest.java deleted file mode 100644 index 388f073a..00000000 --- a/modules/services/event-history-service/src/test/java/com/lingniu/ingest/eventhistory/config/EventHistoryPomBoundaryTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.lingniu.ingest.eventhistory.config; - -import org.junit.jupiter.api.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; - -import javax.xml.parsers.DocumentBuilderFactory; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -class EventHistoryPomBoundaryTest { - - @Test - void tdengineJdbcDriverIsOwnedByTdengineHistoryStoreModule() throws Exception { - assertThat(directDependencies(modulePom())) - .doesNotContain("com.taosdata.jdbc:taos-jdbcdriver"); - } - - @Test - void kafkaClientsIsOwnedByKafkaSinkModule() throws Exception { - for (Path pom : servicePoms()) { - assertThat(directDependencies(readPom(pom))) - .as(pom.toString()) - .doesNotContain("org.apache.kafka:kafka-clients"); - } - } - - private static Document modulePom() throws Exception { - return readPom(repositoryRoot().resolve("modules/services/event-history-service/pom.xml")); - } - - private static Document readPom(Path pom) throws Exception { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - return factory.newDocumentBuilder().parse(Files.newInputStream(pom)); - } - - private static List servicePoms() { - Path services = repositoryRoot().resolve("modules/services"); - return List.of( - services.resolve("event-history-service/pom.xml"), - services.resolve("vehicle-state-service/pom.xml"), - services.resolve("vehicle-stat-service/pom.xml")); - } - - private static List directDependencies(Document pom) { - Element dependencies = firstDirectChild(pom.getDocumentElement(), "dependencies"); - if (dependencies == null) { - return List.of(); - } - List coordinates = new ArrayList<>(); - NodeList children = dependencies.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (!(children.item(i) instanceof Element dependency) || !"dependency".equals(dependency.getTagName())) { - continue; - } - Element groupId = firstDirectChild(dependency, "groupId"); - Element artifactId = firstDirectChild(dependency, "artifactId"); - if (groupId != null && artifactId != null) { - coordinates.add(groupId.getTextContent().trim() + ":" + artifactId.getTextContent().trim()); - } - } - return List.copyOf(coordinates); - } - - private static Element firstDirectChild(Element parent, String tagName) { - NodeList children = parent.getChildNodes(); - for (int i = 0; i < children.getLength(); i++) { - if (children.item(i) instanceof Element child && tagName.equals(child.getTagName())) { - return child; - } - } - return null; - } - - private static Path repositoryRoot() { - Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath(); - while (current != null) { - if (Files.exists(current.resolve("pom.xml")) && Files.exists(current.resolve("modules"))) { - return current; - } - current = current.getParent(); - } - throw new IllegalStateException("repository root not found"); - } -} diff --git a/modules/services/vehicle-stat-service/pom.xml b/modules/services/vehicle-stat-service/pom.xml deleted file mode 100644 index 4855e022..00000000 --- a/modules/services/vehicle-stat-service/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - vehicle-stat-service - vehicle-stat-service - Kafka full-field event consumer + configurable vehicle daily statistics. - - - - com.lingniu.ingest - sink-kafka - - - org.springframework.boot - spring-boot-autoconfigure - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework - spring-jdbc - - - com.mysql - mysql-connector-j - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.mockito - mockito-core - test - - - org.springframework.boot - spring-boot-starter-test - test - - - com.h2database - h2 - test - - - diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/DailyMileageStrategy.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/DailyMileageStrategy.java deleted file mode 100644 index 06eaf8c3..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/DailyMileageStrategy.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -public enum DailyMileageStrategy { - JT808_TOTAL_MILEAGE_DIFF; - - public static DailyMileageStrategy fromStorage(String value) { - if (value == null || value.isBlank()) { - return JT808_TOTAL_MILEAGE_DIFF; - } - String normalized = value.trim(); - return DailyMileageStrategy.valueOf(normalized); - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java deleted file mode 100644 index f95159a7..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepository.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import org.springframework.dao.DuplicateKeyException; -import org.springframework.jdbc.core.ConnectionCallback; -import org.springframework.jdbc.core.JdbcTemplate; - -import java.sql.Date; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; -import java.util.OptionalDouble; - -public final class JdbcVehicleStatMetricRepository implements VehicleStatRepository { - - private static final String DAILY_MILEAGE_KEY = "daily_mileage_km"; - private static final String DAILY_MILEAGE_UNIT = "km"; - private static final String DAILY_MILEAGE_START_TOTAL_KEY = "daily_mileage_start_total_km"; - private static final String DAILY_MILEAGE_LATEST_TOTAL_KEY = "daily_mileage_latest_total_km"; - private static final String FIRST_TOTAL_COLUMN = "first_total_mileage_km"; - private static final String LATEST_TOTAL_COLUMN = "latest_total_mileage_km"; - - private final JdbcTemplate jdbcTemplate; - - public JdbcVehicleStatMetricRepository(JdbcTemplate jdbcTemplate) { - if (jdbcTemplate == null) { - throw new IllegalArgumentException("jdbcTemplate must not be null"); - } - this.jdbcTemplate = jdbcTemplate; - ensureSchema(); - } - - @Override - public Optional findDailyStat(String vin, LocalDate statDate) { - String normalizedVin = clean(vin); - List rows = jdbcTemplate.query(""" - SELECT metric_value, calculation_method - FROM vehicle_stat_metric - WHERE vin = ? AND stat_date = ? AND metric_key = ? - """, (rs, rowNum) -> new VehicleDailyStatResult( - normalizedVin, - statDate, - rs.getBigDecimal("metric_value") == null - ? OptionalDouble.empty() - : OptionalDouble.of(rs.getBigDecimal("metric_value").doubleValue()), - DailyMileageStrategy.fromStorage(rs.getString("calculation_method"))), - normalizedVin, Date.valueOf(statDate), DAILY_MILEAGE_KEY); - return rows.stream().findFirst(); - } - - @Override - public Optional recordDailyMileageSample(String vin, LocalDate statDate, double totalMileageKm) { - if (!Double.isFinite(totalMileageKm) || totalMileageKm < 0.0) { - return Optional.empty(); - } - String normalizedVin = clean(vin); - Date date = Date.valueOf(statDate); - String strategy = DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF.name(); - DailyMileageState existingState = findDailyMileageState(normalizedVin, date).orElse(null); - double firstTotalMileage = existingState == null || !Double.isFinite(existingState.firstTotalMileageKm()) - ? totalMileageKm - : Math.min(existingState.firstTotalMileageKm(), totalMileageKm); - double latestTotalMileage = existingState == null || !Double.isFinite(existingState.latestTotalMileageKm()) - ? totalMileageKm - : Math.max(existingState.latestTotalMileageKm(), totalMileageKm); - double dailyMileageKm = latestTotalMileage - firstTotalMileage; - upsertDailyMileageMetric(normalizedVin, date, dailyMileageKm, DAILY_MILEAGE_UNIT, strategy, - firstTotalMileage, latestTotalMileage); - deleteLegacyMileageStateMetrics(normalizedVin, date); - VehicleDailyStatResult result = new VehicleDailyStatResult( - normalizedVin, - statDate, - OptionalDouble.of(dailyMileageKm), - DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF); - return Optional.of(result); - } - - private Optional findDailyMileageState(String vin, Date statDate) { - List rows = jdbcTemplate.query(""" - SELECT first_total_mileage_km, latest_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = ? AND stat_date = ? AND metric_key = ? - """, (rs, rowNum) -> new DailyMileageState( - nullableDouble(rs, FIRST_TOTAL_COLUMN), - nullableDouble(rs, LATEST_TOTAL_COLUMN)), vin, statDate, DAILY_MILEAGE_KEY); - return rows.stream() - .filter(DailyMileageState::hasCompleteState) - .findFirst(); - } - - private void upsertDailyMileageMetric(String vin, Date statDate, double value, String unit, String strategy, - double firstTotalMileageKm, double latestTotalMileageKm) { - try { - jdbcTemplate.update(""" - INSERT INTO vehicle_stat_metric - (vin, stat_date, metric_key, metric_value, metric_unit, calculation_method, - first_total_mileage_km, latest_total_mileage_km) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - """, vin, statDate, DAILY_MILEAGE_KEY, value, unit, strategy, - firstTotalMileageKm, latestTotalMileageKm); - } catch (DuplicateKeyException ex) { - jdbcTemplate.update(""" - UPDATE vehicle_stat_metric - SET metric_value = ?, - metric_unit = ?, - calculation_method = ?, - first_total_mileage_km = ?, - latest_total_mileage_km = ?, - updated_at = CURRENT_TIMESTAMP - WHERE vin = ? AND stat_date = ? AND metric_key = ? - """, value, unit, strategy, firstTotalMileageKm, latestTotalMileageKm, - vin, statDate, DAILY_MILEAGE_KEY); - } - } - - private void deleteLegacyMileageStateMetrics(String vin, Date statDate) { - jdbcTemplate.update(""" - DELETE FROM vehicle_stat_metric - WHERE vin = ? AND stat_date = ? AND metric_key IN (?, ?) - """, vin, statDate, DAILY_MILEAGE_START_TOTAL_KEY, DAILY_MILEAGE_LATEST_TOTAL_KEY); - } - - private void ensureSchema() { - jdbcTemplate.execute(""" - CREATE TABLE IF NOT EXISTS vehicle_stat_metric ( - vin VARCHAR(64) NOT NULL, - stat_date DATE NOT NULL, - metric_key VARCHAR(64) NOT NULL, - metric_value DECIMAL(18,6) NULL, - metric_unit VARCHAR(16) NOT NULL, - calculation_method VARCHAR(64) NOT NULL, - first_total_mileage_km DECIMAL(18,6) NULL, - latest_total_mileage_km DECIMAL(18,6) NULL, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (vin, stat_date, metric_key) - ) - """); - ensureColumn(FIRST_TOTAL_COLUMN); - ensureColumn(LATEST_TOTAL_COLUMN); - } - - private void ensureColumn(String columnName) { - if (columnExists(columnName)) { - return; - } - jdbcTemplate.execute("ALTER TABLE vehicle_stat_metric ADD COLUMN " + columnName + " DECIMAL(18,6) NULL"); - } - - private boolean columnExists(String columnName) { - ConnectionCallback callback = connection -> { - try (ResultSet columns = connection.getMetaData().getColumns( - connection.getCatalog(), null, "vehicle_stat_metric", columnName)) { - if (columns.next()) { - return true; - } - } - try (ResultSet columns = connection.getMetaData().getColumns( - connection.getCatalog(), null, "VEHICLE_STAT_METRIC", columnName.toUpperCase())) { - return columns.next(); - } - }; - return Boolean.TRUE.equals(jdbcTemplate.execute(callback)); - } - - private static double nullableDouble(ResultSet rs, String columnName) throws SQLException { - double value = rs.getDouble(columnName); - return rs.wasNull() ? Double.NaN : value; - } - - private static String clean(String value) { - if (value == null || value.isBlank()) { - throw new IllegalArgumentException("vin must not be blank"); - } - return value.trim(); - } - - private record DailyMileageState(double firstTotalMileageKm, double latestTotalMileageKm) { - boolean hasCompleteState() { - return Double.isFinite(firstTotalMileageKm) && Double.isFinite(latestTotalMileageKm); - } - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleDailyStatResult.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleDailyStatResult.java deleted file mode 100644 index d769900f..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleDailyStatResult.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import java.time.LocalDate; -import java.util.OptionalDouble; - -public record VehicleDailyStatResult(String vin, - LocalDate statDate, - OptionalDouble dailyMileageKm, - DailyMileageStrategy dailyMileageStrategy) { - - public VehicleDailyStatResult { - if (vin == null || vin.isBlank()) { - throw new IllegalArgumentException("vin must not be blank"); - } - if (statDate == null) { - throw new IllegalArgumentException("statDate must not be null"); - } - if (dailyMileageKm == null) { - dailyMileageKm = OptionalDouble.empty(); - } - if (dailyMileageStrategy == null) { - dailyMileageStrategy = DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF; - } - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatController.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatController.java deleted file mode 100644 index f59b18a7..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatController.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.time.LocalDate; -import java.util.LinkedHashMap; -import java.util.Map; - -@RestController -@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat", name = "enabled", havingValue = "true") -@ConditionalOnBean(VehicleStatRepository.class) -@RequestMapping(path = "/api/vehicle-stat", produces = MediaType.APPLICATION_JSON_VALUE) -public final class VehicleStatController { - - private final VehicleStatRepository repository; - - public VehicleStatController(VehicleStatRepository repository) { - if (repository == null) { - throw new IllegalArgumentException("repository must not be null"); - } - this.repository = repository; - } - - @GetMapping("/{vin}/daily") - public ResponseEntity> daily( - @PathVariable String vin, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date) { - return repository.findDailyStat(vin, date) - .map(stat -> ResponseEntity.ok(toJson(stat))) - .orElseGet(() -> ResponseEntity.notFound().build()); - } - - private static Map toJson(VehicleDailyStatResult stat) { - Map json = new LinkedHashMap<>(); - json.put("vin", stat.vin()); - json.put("statDate", stat.statDate().toString()); - json.put("dailyMileageKm", stat.dailyMileageKm().isPresent() - ? stat.dailyMileageKm().getAsDouble() - : null); - json.put("dailyMileageStrategy", stat.dailyMileageStrategy().name()); - return json; - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestor.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestor.java deleted file mode 100644 index 35936a5c..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestor.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import com.lingniu.ingest.api.consumer.EnvelopeIngestor; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import com.lingniu.ingest.vehiclestat.jt808.Jt808MileageStreamProcessor; - -public final class VehicleStatEnvelopeIngestor implements EnvelopeIngestor { - - private final Jt808MileageStreamProcessor jt808MileageProcessor; - - public VehicleStatEnvelopeIngestor(Jt808MileageStreamProcessor jt808MileageProcessor) { - if (jt808MileageProcessor == null) { - throw new IllegalArgumentException("jt808MileageProcessor must not be null"); - } - this.jt808MileageProcessor = jt808MileageProcessor; - } - - public void ingest(byte[] kafkaValue) { - VehicleEnvelope envelope = parse(kafkaValue); - if (!envelope.hasTelemetrySnapshot()) { - return; - } - if ("JT808".equalsIgnoreCase(envelope.getSource())) { - jt808MileageProcessor.process(envelope); - } - } - - @Override - public EnvelopeIngestResult tryIngest(byte[] kafkaValue) { - VehicleEnvelope envelope = null; - try { - envelope = parse(kafkaValue); - if (!envelope.hasTelemetrySnapshot()) { - return EnvelopeIngestResult.skipped( - envelope.getEventId(), envelope.getVin(), "envelope telemetry_snapshot is required"); - } - if ("JT808".equalsIgnoreCase(envelope.getSource())) { - jt808MileageProcessor.process(envelope); - return EnvelopeIngestResult.processed(envelope.getEventId(), envelope.getVin()); - } - return EnvelopeIngestResult.skipped( - envelope.getEventId(), envelope.getVin(), "vehicle-stat only accepts JT808 telemetry"); - } catch (IllegalArgumentException ex) { - return envelope == null - ? EnvelopeIngestResult.invalid(ex.getMessage()) - : EnvelopeIngestResult.skipped(envelope.getEventId(), envelope.getVin(), ex.getMessage()); - } - } - - private static VehicleEnvelope parse(byte[] kafkaValue) { - if (kafkaValue == null || kafkaValue.length == 0) { - throw new IllegalArgumentException("VehicleEnvelope bytes must not be empty"); - } - try { - return VehicleEnvelope.parseFrom(kafkaValue); - } catch (InvalidProtocolBufferException ex) { - throw new IllegalArgumentException("VehicleEnvelope bytes are invalid", ex); - } - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatRepository.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatRepository.java deleted file mode 100644 index 27f8eb46..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/VehicleStatRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import java.time.LocalDate; -import java.util.Optional; - -public interface VehicleStatRepository { - - Optional recordDailyMileageSample(String vin, LocalDate statDate, double totalMileageKm); - - Optional findDailyStat(String vin, LocalDate statDate); -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfiguration.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfiguration.java deleted file mode 100644 index 8e1c069a..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfiguration.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.lingniu.ingest.vehiclestat.config; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository; -import com.lingniu.ingest.vehiclestat.VehicleStatController; -import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor; -import com.lingniu.ingest.vehiclestat.VehicleStatRepository; -import com.lingniu.ingest.vehiclestat.jt808.Jt808LocationPointExtractor; -import com.lingniu.ingest.vehiclestat.jt808.Jt808MileageStreamProcessor; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.jdbc.core.JdbcTemplate; - -import java.time.ZoneId; - -@AutoConfiguration(after = { - DataSourceAutoConfiguration.class, - JdbcTemplateAutoConfiguration.class -}) -@EnableConfigurationProperties(VehicleStatProperties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat", name = "enabled", havingValue = "true") -public class VehicleStatAutoConfiguration { - - @Bean - @ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat.jt808", name = "state-store") - public Object rejectedLegacyJt808MileageStateStore() { - throw new IllegalStateException( - "vehicle-stat.jt808.state-store has been removed; JT808 daily mileage uses vehicle_stat_metric only"); - } - - @Bean - @ConditionalOnBean(JdbcTemplate.class) - @ConditionalOnMissingBean - public VehicleStatRepository jdbcVehicleStatMetricRepository(JdbcTemplate jdbcTemplate) { - return new JdbcVehicleStatMetricRepository(jdbcTemplate); - } - - @Bean - @ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-stat.jt808", name = "enabled", havingValue = "true") - @ConditionalOnMissingBean - public Jt808LocationPointExtractor jt808LocationPointExtractor() { - return new Jt808LocationPointExtractor(); - } - - @Bean - @ConditionalOnBean({Jt808LocationPointExtractor.class, VehicleStatRepository.class}) - @ConditionalOnMissingBean - public Jt808MileageStreamProcessor jt808MileageStreamProcessor(Jt808LocationPointExtractor extractor, - VehicleStatRepository repository, - VehicleStatProperties props) { - return new Jt808MileageStreamProcessor(extractor, repository, ZoneId.of(props.getZoneId())); - } - - @Bean - @ConditionalOnBean(Jt808MileageStreamProcessor.class) - @ConditionalOnMissingBean - public VehicleStatEnvelopeIngestor vehicleStatEnvelopeIngestor( - Jt808MileageStreamProcessor jt808MileageProcessor) { - return new VehicleStatEnvelopeIngestor(jt808MileageProcessor); - } - - @Bean - @ConditionalOnBean(VehicleStatRepository.class) - @ConditionalOnMissingBean - public VehicleStatController vehicleStatController(VehicleStatRepository repository) { - return new VehicleStatController(repository); - } - - @Bean - @ConditionalOnBean({VehicleStatEnvelopeIngestor.class, EnvelopeDeadLetterSink.class}) - @ConditionalOnMissingBean(name = "vehicleStatEnvelopeConsumerProcessor") - public EnvelopeConsumerProcessor vehicleStatEnvelopeConsumerProcessor(VehicleStatEnvelopeIngestor ingestor, - EnvelopeDeadLetterSink deadLetterSink) { - // Bean 名必须和 Kafka 默认 binding 对齐,KafkaEnvelopeConsumerFactory 才能自动创建 worker。 - return new EnvelopeConsumerProcessor("vehicle-stat", ingestor, deadLetterSink); - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatProperties.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatProperties.java deleted file mode 100644 index 24b00c4f..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/config/VehicleStatProperties.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.lingniu.ingest.vehiclestat.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "lingniu.ingest.vehicle-stat") -public class VehicleStatProperties { - - /** 统计自然日口径,默认按国内业务使用东八区。 */ - private String zoneId = "Asia/Shanghai"; - - private Jt808 jt808 = new Jt808(); - - public String getZoneId() { - return zoneId; - } - - public void setZoneId(String zoneId) { - this.zoneId = zoneId; - } - - public Jt808 getJt808() { - return jt808; - } - - public void setJt808(Jt808 jt808) { - this.jt808 = jt808; - } - - public static class Jt808 { - private boolean enabled; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPoint.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPoint.java deleted file mode 100644 index dcccdb5f..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPoint.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.lingniu.ingest.vehiclestat.jt808; - -import java.time.Instant; - -public record Jt808LocationPoint( - String vehicleKey, - String vin, - String phone, - Instant eventTime, - double longitude, - double latitude, - Double speedKmh, - Long statusFlag, - Double totalMileageKm -) { - - public Jt808LocationPoint { - if (vehicleKey == null || vehicleKey.isBlank()) { - throw new IllegalArgumentException("vehicleKey must not be blank"); - } - if (eventTime == null) { - throw new IllegalArgumentException("eventTime must not be null"); - } - vin = vin == null ? "" : vin; - phone = phone == null ? "" : phone; - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractor.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractor.java deleted file mode 100644 index 8883391d..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractor.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.lingniu.ingest.vehiclestat.jt808; - -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; - -import java.time.Instant; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - -public final class Jt808LocationPointExtractor { - - public Optional extract(VehicleEnvelope envelope) { - if (envelope == null - || !"JT808".equalsIgnoreCase(envelope.getSource()) - || !envelope.hasTelemetrySnapshot() - || !"LOCATION".equalsIgnoreCase(envelope.getTelemetrySnapshot().getEventType())) { - return Optional.empty(); - } - Map fields = fields(envelope); - Double longitude = decimal(fields.get("longitude")); - Double latitude = decimal(fields.get("latitude")); - if (longitude == null || latitude == null || !validCoordinate(longitude, latitude)) { - return Optional.empty(); - } - - String vin = clean(envelope.getVin()); - String phone = clean(envelope.getMetadataMap().getOrDefault("phone", "")); - String vehicleKey = vehicleKey(vin, phone); - if (vehicleKey.isBlank()) { - return Optional.empty(); - } - return Optional.of(new Jt808LocationPoint( - vehicleKey, - vin, - phone, - Instant.ofEpochMilli(envelope.getEventTimeMs()), - longitude, - latitude, - decimal(fields.get("speed_kmh")), - integer(fields.get("location_status_raw")), - decimal(fields.get("total_mileage_km")))); - } - - private static Map fields(VehicleEnvelope envelope) { - Map out = new LinkedHashMap<>(); - for (TelemetryField field : envelope.getTelemetrySnapshot().getFieldsList()) { - if (!field.getKey().isBlank()) { - out.put(field.getKey(), field.getValue()); - } - } - return out; - } - - private static String vehicleKey(String vin, String phone) { - if (!vin.isBlank() && !"unknown".equalsIgnoreCase(vin)) { - return vin; - } - return phone.isBlank() ? "" : "jt808:" + phone; - } - - private static boolean validCoordinate(double longitude, double latitude) { - return Double.isFinite(longitude) - && Double.isFinite(latitude) - && longitude >= -180.0 - && longitude <= 180.0 - && latitude >= -90.0 - && latitude <= 90.0; - } - - private static Double decimal(String raw) { - if (raw == null || raw.isBlank()) { - return null; - } - try { - double value = Double.parseDouble(raw); - return Double.isFinite(value) ? value : null; - } catch (NumberFormatException ignored) { - return null; - } - } - - private static Long integer(String raw) { - if (raw == null || raw.isBlank()) { - return null; - } - try { - return Long.parseLong(raw); - } catch (NumberFormatException ignored) { - return null; - } - } - - private static String clean(String value) { - return value == null ? "" : value.trim(); - } -} diff --git a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessor.java b/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessor.java deleted file mode 100644 index 05fba77e..00000000 --- a/modules/services/vehicle-stat-service/src/main/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessor.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.lingniu.ingest.vehiclestat.jt808; - -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import com.lingniu.ingest.vehiclestat.VehicleStatRepository; - -import java.time.LocalDate; -import java.time.ZoneId; - -public final class Jt808MileageStreamProcessor { - - private final Jt808LocationPointExtractor extractor; - private final VehicleStatRepository repository; - private final ZoneId zoneId; - - public Jt808MileageStreamProcessor( - Jt808LocationPointExtractor extractor, - VehicleStatRepository repository, - ZoneId zoneId) { - this.extractor = extractor; - this.repository = repository; - this.zoneId = zoneId; - } - - public void process(VehicleEnvelope envelope) { - extractor.extract(envelope).ifPresent(this::processPoint); - } - - private void processPoint(Jt808LocationPoint point) { - if (point.totalMileageKm() == null || !Double.isFinite(point.totalMileageKm())) { - return; - } - LocalDate statDate = LocalDate.ofInstant(point.eventTime(), zoneId); - repository.recordDailyMileageSample(statVehicleId(point), statDate, point.totalMileageKm()); - } - - private static String statVehicleId(Jt808LocationPoint point) { - if (!point.vin().isBlank() && !"unknown".equalsIgnoreCase(point.vin())) { - return point.vin(); - } - return point.vehicleKey(); - } -} diff --git a/modules/services/vehicle-stat-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/services/vehicle-stat-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 953cfaa1..00000000 --- a/modules/services/vehicle-stat-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.vehiclestat.config.VehicleStatAutoConfiguration diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java deleted file mode 100644 index a7480a80..00000000 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/JdbcVehicleStatMetricRepositoryTest.java +++ /dev/null @@ -1,203 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import org.junit.jupiter.api.Test; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.DriverManagerDataSource; - -import java.time.LocalDate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class JdbcVehicleStatMetricRepositoryTest { - - @Test - void recordsDailyMileageIntoCommonMetricTable() { - JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( - "jdbc:h2:mem:vehicle_stat_metric;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", - "sa", - "")); - JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - - repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 6, 30), 100.0); - repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 6, 30), 115.5); - - assertThat(repository.findDailyStat("VIN001", LocalDate.of(2026, 6, 30))) - .isPresent() - .get() - .extracting(result -> result.dailyMileageKm().orElseThrow()) - .isEqualTo(15.5); - assertThat(jdbcTemplate.queryForObject("SELECT COUNT(*) FROM vehicle_stat_metric", Integer.class)) - .isOne(); - assertThat(jdbcTemplate.queryForObject( - "SELECT metric_key FROM vehicle_stat_metric WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'", - String.class)).isEqualTo("daily_mileage_km"); - assertThat(jdbcTemplate.queryForObject( - "SELECT calculation_method FROM vehicle_stat_metric WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km'", - String.class)).isEqualTo("JT808_TOTAL_MILEAGE_DIFF"); - assertThat(jdbcTemplate.queryForObject(""" - SELECT first_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(100.0); - assertThat(jdbcTemplate.queryForObject(""" - SELECT latest_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(115.5); - assertThat(jdbcTemplate.queryForObject(""" - SELECT COUNT(*) - FROM information_schema.tables - WHERE table_name = 'vehicle_daily_stat' - """, Integer.class)).isZero(); - } - - @Test - void recordsJt808GpsMileageDifferenceInsideMetricTable() { - JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( - "jdbc:h2:mem:vehicle_stat_metric_jt808_diff;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", - "sa", - "")); - JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - - VehicleDailyStatResult first = repository.recordDailyMileageSample( - "VIN001", LocalDate.of(2026, 7, 1), 1000.5).orElseThrow(); - VehicleDailyStatResult second = repository.recordDailyMileageSample( - "VIN001", LocalDate.of(2026, 7, 1), 1008.75).orElseThrow(); - - assertThat(first.dailyMileageKm()).hasValue(0.0); - assertThat(second.dailyMileageKm()).hasValue(8.25); - assertThat(jdbcTemplate.queryForObject( - "SELECT COUNT(*) FROM vehicle_stat_metric WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01'", - Integer.class)).isOne(); - assertThat(jdbcTemplate.queryForObject(""" - SELECT metric_value - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(8.25); - assertThat(jdbcTemplate.queryForList( - "SELECT table_name FROM information_schema.tables WHERE table_name = 'jt808_daily_mileage'", - String.class)).isEmpty(); - } - - @Test - void recalculatesJt808MileageFromSingleMetricRowAfterRestart() { - JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( - "jdbc:h2:mem:vehicle_stat_metric_jt808_out_of_order;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", - "sa", - "")); - JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - - repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 7, 1), 1000.5).orElseThrow(); - JdbcVehicleStatMetricRepository restartedRepository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - VehicleDailyStatResult higherSample = restartedRepository.recordDailyMileageSample( - "VIN001", LocalDate.of(2026, 7, 1), 1010.0).orElseThrow(); - - assertThat(higherSample.dailyMileageKm()).hasValue(9.5); - assertThat(jdbcTemplate.queryForObject(""" - SELECT COUNT(*) - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01' - """, Integer.class)).isOne(); - assertThat(jdbcTemplate.queryForObject(""" - SELECT first_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(1000.5); - assertThat(jdbcTemplate.queryForObject(""" - SELECT latest_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(1010.0); - assertThat(jdbcTemplate.queryForObject(""" - SELECT metric_value - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(9.5); - } - - @Test - void recalculatesJt808MileageWhenEarlierLowerMileageArrivesAfterRestart() { - JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( - "jdbc:h2:mem:vehicle_stat_metric_jt808_replay_lower;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", - "sa", - "")); - JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - - repository.recordDailyMileageSample("VIN001", LocalDate.of(2026, 7, 1), 1010.0).orElseThrow(); - JdbcVehicleStatMetricRepository restartedRepository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - VehicleDailyStatResult replayedEarlierSample = restartedRepository.recordDailyMileageSample( - "VIN001", LocalDate.of(2026, 7, 1), 1000.5).orElseThrow(); - - assertThat(replayedEarlierSample.dailyMileageKm()).hasValue(9.5); - assertThat(jdbcTemplate.queryForObject(""" - SELECT first_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(1000.5); - assertThat(jdbcTemplate.queryForObject(""" - SELECT latest_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(1010.0); - assertThat(jdbcTemplate.queryForObject(""" - SELECT COUNT(*) - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01' - """, Integer.class)).isOne(); - } - - @Test - void ignoresLegacySplitMileageStateMetricsWhenRecordingNewGpsTotalMileage() { - JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( - "jdbc:h2:mem:vehicle_stat_metric_ignore_legacy_state;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", - "sa", - "")); - JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - jdbcTemplate.update(""" - INSERT INTO vehicle_stat_metric - (vin, stat_date, metric_key, metric_value, metric_unit, calculation_method) - VALUES - ('VIN001', DATE '2026-07-01', 'daily_mileage_start_total_km', 100.0, 'km', 'JT808_TOTAL_MILEAGE_DIFF'), - ('VIN001', DATE '2026-07-01', 'daily_mileage_latest_total_km', 105.0, 'km', 'JT808_TOTAL_MILEAGE_DIFF') - """); - - VehicleDailyStatResult result = repository.recordDailyMileageSample( - "VIN001", LocalDate.of(2026, 7, 1), 110.0).orElseThrow(); - - assertThat(result.dailyMileageKm()).hasValue(0.0); - assertThat(jdbcTemplate.queryForObject(""" - SELECT first_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(110.0); - assertThat(jdbcTemplate.queryForObject(""" - SELECT latest_total_mileage_km - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND metric_key = 'daily_mileage_km' - """, Double.class)).isEqualTo(110.0); - assertThat(jdbcTemplate.queryForObject(""" - SELECT COUNT(*) - FROM vehicle_stat_metric - WHERE vin = 'VIN001' AND stat_date = DATE '2026-07-01' - """, Integer.class)).isOne(); - } - - @Test - void rejectsLegacyPreviousDayCalculationMethod() { - JdbcTemplate jdbcTemplate = new JdbcTemplate(new DriverManagerDataSource( - "jdbc:h2:mem:vehicle_stat_metric_legacy;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", - "sa", - "")); - JdbcVehicleStatMetricRepository repository = new JdbcVehicleStatMetricRepository(jdbcTemplate); - jdbcTemplate.update(""" - INSERT INTO vehicle_stat_metric - (vin, stat_date, metric_key, metric_value, metric_unit, calculation_method) - VALUES ('VIN001', DATE '2026-06-30', 'daily_mileage_km', 8.5, 'km', 'CURRENT_LAST_MINUS_PREVIOUS_LAST') - """); - - assertThatThrownBy(() -> repository.findDailyStat("VIN001", LocalDate.of(2026, 6, 30))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("CURRENT_LAST_MINUS_PREVIOUS_LAST"); - } -} diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatControllerTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatControllerTest.java deleted file mode 100644 index 0cb9db8b..00000000 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatControllerTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import org.junit.jupiter.api.Test; -import org.springframework.test.web.servlet.MockMvc; - -import java.time.LocalDate; -import java.util.Optional; -import java.util.OptionalDouble; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; - -class VehicleStatControllerTest { - - @Test - void returnsDailyStatJson() throws Exception { - InMemoryVehicleStatRepository repository = new InMemoryVehicleStatRepository(); - repository.recordDailyMileageSample("VIN001", LocalDate.parse("2026-06-22"), 100.0); - repository.recordDailyMileageSample("VIN001", LocalDate.parse("2026-06-22"), 135.5); - MockMvc mvc = standaloneSetup(new VehicleStatController(repository)).build(); - - mvc.perform(get("/api/vehicle-stat/VIN001/daily").param("date", "2026-06-22")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.vin").value("VIN001")) - .andExpect(jsonPath("$.statDate").value("2026-06-22")) - .andExpect(jsonPath("$.dailyMileageKm").value(35.5)) - .andExpect(jsonPath("$.dailyMileageStrategy").value("JT808_TOTAL_MILEAGE_DIFF")); - } - - private static final class InMemoryVehicleStatRepository implements VehicleStatRepository { - private final java.util.Map results = new java.util.HashMap<>(); - private final java.util.Map firstTotals = new java.util.HashMap<>(); - - @Override - public Optional recordDailyMileageSample(String vin, LocalDate statDate, - double totalMileageKm) { - String key = vin + ":" + statDate; - double firstTotal = firstTotals.computeIfAbsent(key, ignored -> totalMileageKm); - VehicleDailyStatResult result = new VehicleDailyStatResult( - vin, - statDate, - OptionalDouble.of(totalMileageKm - firstTotal), - DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF); - results.put(key, result); - return Optional.of(result); - } - - @Override - public Optional findDailyStat(String vin, LocalDate statDate) { - return Optional.ofNullable(results.get(vin + ":" + statDate)); - } - } -} diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestorTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestorTest.java deleted file mode 100644 index e13d6051..00000000 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatEnvelopeIngestorTest.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import com.lingniu.ingest.api.consumer.EnvelopeIngestor; -import com.lingniu.ingest.sink.kafka.proto.RawArchiveRef; -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import com.lingniu.ingest.vehiclestat.jt808.Jt808MileageStreamProcessor; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -class VehicleStatEnvelopeIngestorTest { - - @Test - void ignoresNonJt808Telemetry() { - Jt808MileageStreamProcessor jt808Processor = mock(Jt808MileageStreamProcessor.class); - VehicleEnvelope envelope = envelope("GB32960"); - VehicleStatEnvelopeIngestor ingestor = new VehicleStatEnvelopeIngestor(jt808Processor); - - ingestor.ingest(envelope.toByteArray()); - var result = ingestor.tryIngest(envelope.toByteArray()); - - verifyNoInteractions(jt808Processor); - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.SKIPPED); - assertThat(result.message()).contains("only accepts JT808"); - } - - @Test - void routesJt808TelemetryOnlyToJt808MileageProcessorWhenAvailable() { - Jt808MileageStreamProcessor jt808Processor = mock(Jt808MileageStreamProcessor.class); - VehicleEnvelope envelope = envelope("JT808"); - VehicleStatEnvelopeIngestor ingestor = new VehicleStatEnvelopeIngestor(jt808Processor); - - assertThat(ingestor).isInstanceOf(EnvelopeIngestor.class); - ingestor.ingest(envelope.toByteArray()); - var result = ingestor.tryIngest(envelope.toByteArray()); - - verify(jt808Processor, times(2)).process(envelope); - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.PROCESSED); - } - - @Test - void rejectsInvalidEnvelopeBytes() { - VehicleStatEnvelopeIngestor ingestor = new VehicleStatEnvelopeIngestor(mock(Jt808MileageStreamProcessor.class)); - - assertThatThrownBy(() -> ingestor.ingest(new byte[]{0x01, 0x02})) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("VehicleEnvelope"); - } - - @Test - void tryIngestInvalidEnvelopeBytesReturnsInvalidWithoutProcessing() { - Jt808MileageStreamProcessor jt808Processor = mock(Jt808MileageStreamProcessor.class); - VehicleStatEnvelopeIngestor ingestor = new VehicleStatEnvelopeIngestor(jt808Processor); - - var result = ingestor.tryIngest(new byte[]{0x01, 0x02}); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.INVALID_ENVELOPE); - assertThat(result.message()).contains("VehicleEnvelope"); - verifyNoInteractions(jt808Processor); - } - - @Test - void tryIngestEnvelopeWithoutTelemetrySnapshotReturnsSkipped() { - Jt808MileageStreamProcessor jt808Processor = mock(Jt808MileageStreamProcessor.class); - VehicleStatEnvelopeIngestor ingestor = new VehicleStatEnvelopeIngestor(jt808Processor); - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setEventId("event-login-1") - .setVin("VIN001") - .setSource("GB32960") - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .build(); - - var result = ingestor.tryIngest(envelope.toByteArray()); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.SKIPPED); - assertThat(result.eventId()).isEqualTo("event-login-1"); - assertThat(result.message()).contains("telemetry_snapshot"); - verifyNoInteractions(jt808Processor); - } - - @Test - void rawArchiveEnvelopeIsIgnoredWithoutProcessing() { - Jt808MileageStreamProcessor jt808Processor = mock(Jt808MileageStreamProcessor.class); - VehicleStatEnvelopeIngestor ingestor = new VehicleStatEnvelopeIngestor(jt808Processor); - VehicleEnvelope envelope = rawArchiveEnvelope(); - - ingestor.ingest(envelope.toByteArray()); - var result = ingestor.tryIngest(envelope.toByteArray()); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.SKIPPED); - assertThat(result.eventId()).isEqualTo("raw-event-1"); - assertThat(result.vin()).isEqualTo("VIN001"); - assertThat(result.message()).contains("telemetry_snapshot"); - verifyNoInteractions(jt808Processor); - } - - private static VehicleEnvelope envelope(String source) { - return VehicleEnvelope.newBuilder() - .setEventId("event-1") - .setVin("VIN001") - .setSource(source) - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .setTelemetrySnapshot(TelemetrySnapshot.newBuilder() - .setEventType("REALTIME") - .addFields(TelemetryField.newBuilder() - .setKey("total_mileage_km") - .setValueType("DOUBLE") - .setValue("123.45") - .setQuality("GOOD"))) - .build(); - } - - private static VehicleEnvelope rawArchiveEnvelope() { - return VehicleEnvelope.newBuilder() - .setEventId("raw-event-1") - .setVin("VIN001") - .setSource("GB32960") - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .setRawArchive(RawArchiveRef.newBuilder() - .setUri("archive://2026/06/23/GB32960/VIN001/raw-event-1.bin") - .setSizeBytes(128) - .build()) - .build(); - } -} diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java deleted file mode 100644 index b1071c28..00000000 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/VehicleStatRepositoryContractTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.lingniu.ingest.vehiclestat; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleStatRepositoryContractTest { - - @Test - void exposesOnlyMileageSampleAndQueryOperations() { - assertThat(Arrays.stream(VehicleStatRepository.class.getDeclaredMethods()) - .map(Method::getName)) - .containsExactlyInAnyOrder("recordDailyMileageSample", "findDailyStat"); - } - - @Test - void jt808MileagePlanDocumentsMetricTableDiffOnly() throws IOException { - String plan = Files.readString(repositoryRoot() - .resolve("docs/superpowers/plans/2026-06-30-kafka-streaming-mileage.md")); - - assertThat(plan) - .contains("Only supported message backbone: Kafka.") - .contains("Runtime state: none outside `vehicle_stat_metric`") - .contains("daily_mileage_km = max_total_mileage_km - min_total_mileage_km") - .contains("Do not create or write a protocol-specific JT808 daily-mileage table.") - .doesNotContain("Redis `Jt808MileageStateStore`") - .doesNotContain("VEHICLE_STAT_REPOSITORY_TYPE") - .doesNotContain("REDIS_HOST") - .doesNotContain("GPS segment distance") - .doesNotContain("speed integral"); - } - - @Test - void targetArchitectureDocumentsMetricTableAsOnlyJt808MileageRuntimeState() throws IOException { - String architecture = Files.readString(repositoryRoot().resolve("docs/target-architecture.md")); - - assertThat(architecture) - .contains("Only supported message backbone: Kafka.") - .contains("Runtime state: none outside `vehicle_stat_metric`") - .contains("Restart recovery reads the same metric row") - .contains("JT808 daily mileage is stored only in `vehicle_stat_metric`") - .doesNotContain("state-store") - .doesNotContain("Redis mileage state") - .doesNotContain("production memory"); - } - - @Test - void jt808MileageRunbookDocumentsMetricTableAsOnlyRuntimeState() throws IOException { - String runbook = Files.readString(repositoryRoot() - .resolve("docs/operations/jt808-daily-mileage-runbook.md")); - - assertThat(runbook) - .contains("Only supported message backbone: Kafka.") - .contains("Runtime state: none outside `vehicle_stat_metric`") - .contains("Restart recovery reads the same `daily_mileage_km` metric row") - .doesNotContain("Redis mileage state") - .doesNotContain("state-store"); - } - - private static Path repositoryRoot() { - Path path = Path.of("").toAbsolutePath(); - while (path != null && !Files.exists(path.resolve("docs/superpowers/plans"))) { - path = path.getParent(); - } - if (path == null) { - throw new IllegalStateException("repository root not found"); - } - return path; - } -} diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfigurationTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfigurationTest.java deleted file mode 100644 index 059dbf03..00000000 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/config/VehicleStatAutoConfigurationTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.lingniu.ingest.vehiclestat.config; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import com.lingniu.ingest.vehiclestat.JdbcVehicleStatMetricRepository; -import com.lingniu.ingest.vehiclestat.VehicleStatController; -import com.lingniu.ingest.vehiclestat.VehicleStatEnvelopeIngestor; -import com.lingniu.ingest.vehiclestat.VehicleStatRepository; -import com.lingniu.ingest.vehiclestat.jt808.Jt808MileageStreamProcessor; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.DriverManagerDataSource; - -import javax.sql.DataSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class VehicleStatAutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(VehicleStatAutoConfiguration.class)) - .withBean(EnvelopeDeadLetterSink.class, () -> record -> {}); - - @Test - void createsOnlyMetricRepositoryAndReadControllerWhenJt808MileageDisabled() { - contextRunner - .withBean(JdbcTemplate.class, () -> mock(JdbcTemplate.class)) - .withPropertyValues("lingniu.ingest.vehicle-stat.enabled=true") - .run(context -> { - assertThat(context).hasSingleBean(VehicleStatRepository.class); - assertThat(context).hasSingleBean(JdbcVehicleStatMetricRepository.class); - assertThat(context).hasSingleBean(VehicleStatController.class); - assertThat(context).doesNotHaveBean(Jt808MileageStreamProcessor.class); - assertThat(context).doesNotHaveBean(VehicleStatEnvelopeIngestor.class); - assertThat(context).doesNotHaveBean(EnvelopeConsumerProcessor.class); - }); - } - - @Test - void backsOffWhenDisabled() { - contextRunner.run(context -> { - assertThat(context).doesNotHaveBean(VehicleStatRepository.class); - assertThat(context).doesNotHaveBean(VehicleStatEnvelopeIngestor.class); - }); - } - - @Test - void doesNotCreateRepositoryWithoutJdbcMetricStore() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(VehicleStatAutoConfiguration.class)) - .withPropertyValues("lingniu.ingest.vehicle-stat.enabled=true") - .run(context -> { - assertThat(context).doesNotHaveBean(VehicleStatRepository.class); - assertThat(context).doesNotHaveBean(VehicleStatController.class); - }); - } - - @Test - void createsJt808MileageBeansWhenEnabledWithMetricRepositoryOnly() { - contextRunner - .withBean(JdbcTemplate.class, () -> mock(JdbcTemplate.class)) - .withPropertyValues( - "lingniu.ingest.vehicle-stat.enabled=true", - "lingniu.ingest.vehicle-stat.jt808.enabled=true") - .run(context -> { - assertThat(context).hasSingleBean(Jt808MileageStreamProcessor.class); - assertThat(context).hasSingleBean(VehicleStatEnvelopeIngestor.class); - assertThat(context).hasSingleBean(EnvelopeConsumerProcessor.class); - }); - } - - @Test - void rejectsLegacyStateStorePropertyInsteadOfCreatingHiddenRuntimeMode() { - contextRunner - .withBean(JdbcTemplate.class, () -> mock(JdbcTemplate.class)) - .withPropertyValues( - "lingniu.ingest.vehicle-stat.enabled=true", - "lingniu.ingest.vehicle-stat.jt808.enabled=true", - "lingniu.ingest.vehicle-stat.jt808.state-store=memory") - .run(context -> assertThat(context.getStartupFailure()) - .hasRootCauseInstanceOf(IllegalStateException.class) - .hasMessageContaining("vehicle-stat.jt808.state-store has been removed") - .hasMessageContaining("vehicle_stat_metric")); - } - - @Test - void createsJt808MileageBeansAfterJdbcAutoConfiguration() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of( - JdbcTemplateAutoConfiguration.class, - VehicleStatAutoConfiguration.class)) - .withBean(EnvelopeDeadLetterSink.class, () -> record -> {}) - .withBean(DataSource.class, () -> new DriverManagerDataSource( - "jdbc:h2:mem:vehicle_stat_auto;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1", - "sa", - "")) - .withPropertyValues( - "lingniu.ingest.vehicle-stat.enabled=true", - "lingniu.ingest.vehicle-stat.jt808.enabled=true") - .run(context -> { - assertThat(context).hasSingleBean(JdbcTemplate.class); - assertThat(context).hasSingleBean(JdbcVehicleStatMetricRepository.class); - assertThat(context).hasSingleBean(Jt808MileageStreamProcessor.class); - assertThat(context).hasSingleBean(VehicleStatEnvelopeIngestor.class); - assertThat(context).hasSingleBean(EnvelopeConsumerProcessor.class); - }); - } - -} diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractorTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractorTest.java deleted file mode 100644 index 579c7135..00000000 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808LocationPointExtractorTest.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.lingniu.ingest.vehiclestat.jt808; - -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import org.junit.jupiter.api.Test; - -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808LocationPointExtractorTest { - - private final Jt808LocationPointExtractor extractor = new Jt808LocationPointExtractor(); - - @Test - void extractsJt808LocationPointWithVin() { - var point = extractor.extract(envelope("JT808", "VIN001", "13900000001", - field("longitude", "118.901034"), - field("latitude", "31.946325"), - field("speed_kmh", "52.3"), - field("location_status_raw", "3"), - field("total_mileage_km", "1234.5"))).orElseThrow(); - - assertThat(point.vehicleKey()).isEqualTo("VIN001"); - assertThat(point.vin()).isEqualTo("VIN001"); - assertThat(point.phone()).isEqualTo("13900000001"); - assertThat(point.eventTime()).isEqualTo(Instant.ofEpochMilli(1_782_112_400_000L)); - assertThat(point.longitude()).isEqualTo(118.901034); - assertThat(point.latitude()).isEqualTo(31.946325); - assertThat(point.speedKmh()).isEqualTo(52.3); - assertThat(point.statusFlag()).isEqualTo(3L); - assertThat(point.totalMileageKm()).isEqualTo(1234.5); - } - - @Test - void usesPhoneFallbackWhenVinIsUnknown() { - var point = extractor.extract(envelope("JT808", "unknown", "40692934289", - field("longitude", "118.901034"), - field("latitude", "31.946325"))).orElseThrow(); - - assertThat(point.vehicleKey()).isEqualTo("jt808:40692934289"); - assertThat(point.vin()).isEqualTo("unknown"); - assertThat(point.phone()).isEqualTo("40692934289"); - } - - @Test - void ignoresNonJt808Envelope() { - assertThat(extractor.extract(envelope("GB32960", "VIN001", "", - field("longitude", "118.901034"), - field("latitude", "31.946325")))).isEmpty(); - } - - @Test - void ignoresEnvelopeWithoutCoordinates() { - assertThat(extractor.extract(envelope("JT808", "VIN001", "13900000001", - field("longitude", "118.901034")))).isEmpty(); - } - - @Test - void ignoresJt808NonLocationSnapshot() { - VehicleEnvelope envelope = envelope("JT808", "VIN001", "13900000001", - field("longitude", "118.901034"), - field("latitude", "31.946325")) - .toBuilder() - .setTelemetrySnapshot(TelemetrySnapshot.newBuilder() - .setEventType("REGISTER") - .addFields(field("longitude", "118.901034")) - .addFields(field("latitude", "31.946325"))) - .build(); - - assertThat(extractor.extract(envelope)).isEmpty(); - } - - private static VehicleEnvelope envelope(String source, String vin, String phone, TelemetryField... fields) { - TelemetrySnapshot.Builder snapshot = TelemetrySnapshot.newBuilder().setEventType("LOCATION"); - for (TelemetryField field : fields) { - snapshot.addFields(field); - } - return VehicleEnvelope.newBuilder() - .setEventId("event-1") - .setVin(vin) - .setSource(source) - .putMetadata("phone", phone) - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .setTelemetrySnapshot(snapshot) - .build(); - } - - private static TelemetryField field(String key, String value) { - return TelemetryField.newBuilder() - .setKey(key) - .setValueType("DOUBLE") - .setValue(value) - .setQuality("GOOD") - .build(); - } -} diff --git a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessorTest.java b/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessorTest.java deleted file mode 100644 index 6ef612e5..00000000 --- a/modules/services/vehicle-stat-service/src/test/java/com/lingniu/ingest/vehiclestat/jt808/Jt808MileageStreamProcessorTest.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.lingniu.ingest.vehiclestat.jt808; - -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import com.lingniu.ingest.vehiclestat.DailyMileageStrategy; -import com.lingniu.ingest.vehiclestat.VehicleDailyStatResult; -import com.lingniu.ingest.vehiclestat.VehicleStatRepository; -import org.junit.jupiter.api.Test; - -import java.lang.reflect.Modifier; -import java.time.Instant; -import java.time.LocalDate; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.OptionalDouble; - -import static org.assertj.core.api.Assertions.assertThat; - -class Jt808MileageStreamProcessorTest { - - @Test - void recordsDailyMileageSamplesToVehicleStatRepository() { - CapturingRepository repository = new CapturingRepository(); - Jt808MileageStreamProcessor processor = processor(repository); - - processor.process(envelope("2026-06-30T00:00:00Z", 120.0, 30.0, 100.0)); - processor.process(envelope("2026-06-30T00:01:00Z", 120.01, 30.0, 101.0)); - - assertThat(repository.samples).containsExactly(100.0, 101.0); - assertThat(repository.results).hasSize(2); - VehicleDailyStatResult latest = repository.results.getLast(); - assertThat(latest.vin()).isEqualTo("VIN001"); - assertThat(latest.statDate()).isEqualTo(LocalDate.of(2026, 6, 30)); - assertThat(latest.dailyMileageKm()).hasValue(1.0); - assertThat(latest.dailyMileageStrategy()).isEqualTo(DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF); - } - - @Test - void usesSimpleTotalMileageDifferenceInsteadOfAccumulatedPlausibilityDeltas() { - CapturingRepository repository = new CapturingRepository(); - Jt808MileageStreamProcessor processor = processor(repository); - - processor.process(envelope("2026-06-30T00:00:00Z", 120.0, 30.0, 100.0)); - processor.process(envelope("2026-06-30T00:01:00Z", 120.0001, 30.0, 200.0)); - - VehicleDailyStatResult latest = repository.results.getLast(); - assertThat(latest.dailyMileageKm()).hasValue(100.0); - } - - @Test - void skipsNonJt808Envelope() { - CapturingRepository repository = new CapturingRepository(); - Jt808MileageStreamProcessor processor = processor(repository); - VehicleEnvelope envelope = envelope("2026-06-30T00:00:00Z", 120.0, 30.0, 100.0) - .toBuilder() - .setSource("GB32960") - .build(); - - processor.process(envelope); - - assertThat(repository.results).isEmpty(); - } - - @Test - void processDoesNotSerializeAllVehicleMileageUpdatesBehindOneMonitor() throws NoSuchMethodException { - int modifiers = Jt808MileageStreamProcessor.class - .getDeclaredMethod("process", VehicleEnvelope.class) - .getModifiers(); - - assertThat(Modifier.isSynchronized(modifiers)).isFalse(); - } - - private static Jt808MileageStreamProcessor processor(VehicleStatRepository repository) { - return new Jt808MileageStreamProcessor( - new Jt808LocationPointExtractor(), - repository, - ZoneId.of("Asia/Shanghai")); - } - - private static VehicleEnvelope envelope(String eventTime, double longitude, double latitude, double totalMileageKm) { - long eventTimeMs = Instant.parse(eventTime).toEpochMilli(); - return VehicleEnvelope.newBuilder() - .setEventId("event-" + eventTimeMs) - .setVin("VIN001") - .setSource("JT808") - .setEventTimeMs(eventTimeMs) - .putMetadata("phone", "13900000001") - .setTelemetrySnapshot(TelemetrySnapshot.newBuilder() - .setEventType("LOCATION") - .addFields(field("longitude", longitude)) - .addFields(field("latitude", latitude)) - .addFields(field("speed_kmh", 36.0)) - .addFields(field("total_mileage_km", totalMileageKm))) - .build(); - } - - private static TelemetryField field(String key, double value) { - return TelemetryField.newBuilder() - .setKey(key) - .setValueType("DOUBLE") - .setValue(Double.toString(value)) - .setQuality("GOOD") - .build(); - } - - private static final class CapturingRepository implements VehicleStatRepository { - private final List results = new ArrayList<>(); - private final List samples = new ArrayList<>(); - private double firstSample = Double.NaN; - - @Override - public Optional recordDailyMileageSample(String vin, LocalDate statDate, - double totalMileageKm) { - samples.add(totalMileageKm); - if (!Double.isFinite(firstSample)) { - firstSample = totalMileageKm; - } - VehicleDailyStatResult result = new VehicleDailyStatResult( - vin, - statDate, - OptionalDouble.of(totalMileageKm - firstSample), - DailyMileageStrategy.JT808_TOTAL_MILEAGE_DIFF); - results.add(result); - return Optional.of(result); - } - - @Override - public Optional findDailyStat(String vin, LocalDate statDate) { - return Optional.empty(); - } - } -} diff --git a/modules/services/vehicle-state-service/pom.xml b/modules/services/vehicle-state-service/pom.xml deleted file mode 100644 index 360dcd44..00000000 --- a/modules/services/vehicle-state-service/pom.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - vehicle-state-service - vehicle-state-service - Kafka full-field event consumer + Redis hot vehicle state query API. - - - - com.lingniu.ingest - sink-kafka - - - org.springframework.boot - spring-boot-starter-data-redis - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-autoconfigure - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.mockito - mockito-core - test - - - org.springframework.boot - spring-boot-starter-test - test - - - diff --git a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepository.java b/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepository.java deleted file mode 100644 index e22888af..00000000 --- a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepository.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import org.springframework.data.redis.core.StringRedisTemplate; - -import java.util.Optional; - -public final class RedisVehicleStateRepository implements VehicleStateRepository { - - private final StringRedisTemplate redis; - - public RedisVehicleStateRepository(StringRedisTemplate redis) { - if (redis == null) { - throw new IllegalArgumentException("redis must not be null"); - } - this.redis = redis; - } - - @Override - public void putState(String vin, String json) { - // 每辆车一个 Redis key,读最新状态时不需要扫描或 join 历史表。 - put(key("vehicle:state:", vin), json); - } - - @Override - public void putLocation(String vin, String json) { - put(key("vehicle:location:", vin), json); - } - - @Override - public void putSafety(String vin, String json) { - put(key("vehicle:safety:", vin), json); - } - - @Override - public void putLastEvent(String vin, String json) { - put(key("vehicle:event:last:", vin), json); - } - - @Override - public Optional getState(String vin) { - return get(key("vehicle:state:", vin)); - } - - @Override - public Optional getLocation(String vin) { - return get(key("vehicle:location:", vin)); - } - - @Override - public Optional getSafety(String vin) { - return get(key("vehicle:safety:", vin)); - } - - @Override - public Optional getLastEvent(String vin) { - return get(key("vehicle:event:last:", vin)); - } - - private void put(String key, String json) { - redis.opsForValue().set(key, json == null ? "{}" : json); - } - - private Optional get(String key) { - return Optional.ofNullable(redis.opsForValue().get(key)); - } - - private static String key(String prefix, String vin) { - if (vin == null || vin.isBlank()) { - throw new IllegalArgumentException("vin must not be blank"); - } - return prefix + vin; - } -} diff --git a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateController.java b/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateController.java deleted file mode 100644 index 507114c3..00000000 --- a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateController.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.Optional; - -@RestController -@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-state", name = "enabled", havingValue = "true") -@ConditionalOnBean(VehicleStateRepository.class) -@RequestMapping(path = "/api/vehicle-state", produces = MediaType.APPLICATION_JSON_VALUE) -public final class VehicleStateController { - - private final VehicleStateRepository repository; - - public VehicleStateController(VehicleStateRepository repository) { - if (repository == null) { - throw new IllegalArgumentException("repository must not be null"); - } - this.repository = repository; - } - - @GetMapping("/{vin}") - public ResponseEntity state(@PathVariable String vin) { - // 查询的是 Redis 最新状态快照,不是历史明细库。 - return json(repository.getState(vin)); - } - - @GetMapping("/{vin}/location") - public ResponseEntity location(@PathVariable String vin) { - return json(repository.getLocation(vin)); - } - - @GetMapping("/{vin}/safety") - public ResponseEntity safety(@PathVariable String vin) { - return json(repository.getSafety(vin)); - } - - @GetMapping("/{vin}/last-event") - public ResponseEntity lastEvent(@PathVariable String vin) { - return json(repository.getLastEvent(vin)); - } - - private static ResponseEntity json(Optional value) { - return value.map(json -> ResponseEntity.ok() - .contentType(MediaType.APPLICATION_JSON) - .body(json)) - .orElseGet(() -> ResponseEntity.notFound().build()); - } -} diff --git a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestor.java b/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestor.java deleted file mode 100644 index 0411a863..00000000 --- a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestor.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import com.google.protobuf.InvalidProtocolBufferException; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import com.lingniu.ingest.api.consumer.EnvelopeIngestor; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; - -public final class VehicleStateEnvelopeIngestor implements EnvelopeIngestor { - - private final VehicleStateUpdater updater; - - public VehicleStateEnvelopeIngestor(VehicleStateUpdater updater) { - if (updater == null) { - throw new IllegalArgumentException("updater must not be null"); - } - this.updater = updater; - } - - public void ingest(byte[] kafkaValue) { - VehicleEnvelope envelope = parse(kafkaValue); - if (!envelope.hasTelemetrySnapshot()) { - return; - } - updater.update(envelope); - } - - @Override - public EnvelopeIngestResult tryIngest(byte[] kafkaValue) { - VehicleEnvelope envelope = null; - try { - envelope = parse(kafkaValue); - if (!envelope.hasTelemetrySnapshot()) { - return EnvelopeIngestResult.skipped( - envelope.getEventId(), envelope.getVin(), "envelope telemetry_snapshot is required"); - } - // Kafka 消费路径只接受标准 VehicleEnvelope;坏消息返回明确结果给处理器写 DLQ。 - updater.update(envelope); - return EnvelopeIngestResult.processed(envelope.getEventId(), envelope.getVin()); - } catch (IllegalArgumentException ex) { - return envelope == null - ? EnvelopeIngestResult.invalid(ex.getMessage()) - : EnvelopeIngestResult.skipped(envelope.getEventId(), envelope.getVin(), ex.getMessage()); - } - } - - private static VehicleEnvelope parse(byte[] kafkaValue) { - if (kafkaValue == null || kafkaValue.length == 0) { - throw new IllegalArgumentException("VehicleEnvelope bytes must not be empty"); - } - try { - return VehicleEnvelope.parseFrom(kafkaValue); - } catch (InvalidProtocolBufferException ex) { - throw new IllegalArgumentException("VehicleEnvelope bytes are invalid", ex); - } - } -} diff --git a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateRepository.java b/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateRepository.java deleted file mode 100644 index 0fc571bf..00000000 --- a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateRepository.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import java.util.Optional; - -public interface VehicleStateRepository { - - void putState(String vin, String json); - - void putLocation(String vin, String json); - - void putSafety(String vin, String json); - - void putLastEvent(String vin, String json); - - Optional getState(String vin); - - Optional getLocation(String vin); - - Optional getSafety(String vin); - - Optional getLastEvent(String vin); -} diff --git a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdater.java b/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdater.java deleted file mode 100644 index 8d6d6788..00000000 --- a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdater.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; - -import java.util.LinkedHashMap; -import java.util.Map; - -public final class VehicleStateUpdater { - - private final VehicleStateRepository repository; - - public VehicleStateUpdater(VehicleStateRepository repository) { - if (repository == null) { - throw new IllegalArgumentException("repository must not be null"); - } - this.repository = repository; - } - - public void update(VehicleEnvelope envelope) { - if (envelope == null) { - throw new IllegalArgumentException("envelope must not be null"); - } - if (!envelope.hasTelemetrySnapshot()) { - throw new IllegalArgumentException("envelope telemetry_snapshot is required"); - } - Map fields = fields(envelope); - String vin = envelope.getVin(); - - // vehicle-state 只维护“最新状态”缓存,覆盖写 Redis,不承担历史查询或 RAW 回放职责。 - repository.putState(vin, json(base(envelope, fields))); - repository.putLastEvent(vin, json(lastEvent(envelope))); - - if (fields.containsKey("longitude") && fields.containsKey("latitude")) { - repository.putLocation(vin, json(location(envelope, fields))); - } - if (hasSafetyFields(fields)) { - repository.putSafety(vin, json(safety(envelope, fields))); - } - } - - private static Map fields(VehicleEnvelope envelope) { - Map fields = new LinkedHashMap<>(); - for (TelemetryField field : envelope.getTelemetrySnapshot().getFieldsList()) { - // 同名字段以后到者为准,和最新状态语义一致。 - fields.put(field.getKey(), field.getValue()); - } - return fields; - } - - private static Map base(VehicleEnvelope envelope, Map fields) { - Map map = eventIdentity(envelope); - map.put("eventType", envelope.getTelemetrySnapshot().getEventType()); - map.put("fields", fields); - return map; - } - - private static Map location(VehicleEnvelope envelope, Map fields) { - Map map = eventIdentity(envelope); - putIfPresent(map, fields, "longitude"); - putIfPresent(map, fields, "latitude"); - putIfPresent(map, fields, "altitude_m"); - putIfPresent(map, fields, "direction_deg"); - putIfPresent(map, fields, "speed_kmh"); - return map; - } - - private static Map safety(VehicleEnvelope envelope, Map fields) { - Map map = eventIdentity(envelope); - putIfPresent(map, fields, "safety_category"); - putIfPresent(map, fields, "hydrogen_leak_detected"); - putIfPresent(map, fields, "hydrogen_leak_level"); - putIfPresent(map, fields, "hydrogen_leak_action_required"); - putIfPresent(map, fields, "alarm_level"); - return map; - } - - private static Map lastEvent(VehicleEnvelope envelope) { - return eventIdentity(envelope); - } - - private static Map eventIdentity(VehicleEnvelope envelope) { - Map map = new LinkedHashMap<>(); - map.put("eventId", envelope.getEventId()); - map.put("traceId", envelope.getTraceId()); - map.put("vin", envelope.getVin()); - map.put("source", envelope.getSource()); - map.put("eventTimeMs", Long.toString(envelope.getEventTimeMs())); - map.put("ingestTimeMs", Long.toString(envelope.getIngestTimeMs())); - return map; - } - - private static boolean hasSafetyFields(Map fields) { - // safety 是从标准字段中筛出来的窄视图,字段缺失时不写对应 Redis key。 - return fields.containsKey("safety_category") - || fields.containsKey("hydrogen_leak_detected") - || fields.containsKey("hydrogen_leak_level") - || fields.containsKey("hydrogen_leak_action_required") - || fields.containsKey("alarm_level"); - } - - private static void putIfPresent(Map map, Map fields, String key) { - if (fields.containsKey(key)) { - map.put(key, fields.get(key)); - } - } - - private static String json(Map map) { - StringBuilder out = new StringBuilder("{"); - boolean first = true; - for (Map.Entry entry : map.entrySet()) { - if (!first) { - out.append(','); - } - first = false; - out.append('"').append(escape(entry.getKey())).append('"').append(':'); - Object value = entry.getValue(); - if (value instanceof Map nested) { - out.append(jsonObject(nested)); - } else { - out.append('"').append(escape(String.valueOf(value))).append('"'); - } - } - return out.append('}').toString(); - } - - private static String jsonObject(Map map) { - StringBuilder out = new StringBuilder("{"); - boolean first = true; - for (Map.Entry entry : map.entrySet()) { - if (!first) { - out.append(','); - } - first = false; - out.append('"').append(escape(String.valueOf(entry.getKey()))).append('"') - .append(':') - .append('"').append(escape(String.valueOf(entry.getValue()))).append('"'); - } - return out.append('}').toString(); - } - - private static String escape(String value) { - return value.replace("\\", "\\\\").replace("\"", "\\\""); - } -} diff --git a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfiguration.java b/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfiguration.java deleted file mode 100644 index c808e737..00000000 --- a/modules/services/vehicle-state-service/src/main/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfiguration.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.lingniu.ingest.vehiclestate.config; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import com.lingniu.ingest.vehiclestate.RedisVehicleStateRepository; -import com.lingniu.ingest.vehiclestate.VehicleStateController; -import com.lingniu.ingest.vehiclestate.VehicleStateEnvelopeIngestor; -import com.lingniu.ingest.vehiclestate.VehicleStateRepository; -import com.lingniu.ingest.vehiclestate.VehicleStateUpdater; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.core.StringRedisTemplate; - -@AutoConfiguration -@ConditionalOnProperty(prefix = "lingniu.ingest.vehicle-state", name = "enabled", havingValue = "true") -public class VehicleStateAutoConfiguration { - - @Bean - @ConditionalOnBean(StringRedisTemplate.class) - @ConditionalOnMissingBean - public VehicleStateRepository vehicleStateRepository(StringRedisTemplate redis) { - // 车辆状态模块依赖 Redis;没有 Redis Bean 时不自动启用,避免误以为它是历史库。 - return new RedisVehicleStateRepository(redis); - } - - @Bean - @ConditionalOnBean(VehicleStateRepository.class) - @ConditionalOnMissingBean - public VehicleStateUpdater vehicleStateUpdater(VehicleStateRepository repository) { - return new VehicleStateUpdater(repository); - } - - @Bean - @ConditionalOnBean(VehicleStateUpdater.class) - @ConditionalOnMissingBean - public VehicleStateEnvelopeIngestor vehicleStateEnvelopeIngestor(VehicleStateUpdater updater) { - return new VehicleStateEnvelopeIngestor(updater); - } - - @Bean - @ConditionalOnBean({VehicleStateEnvelopeIngestor.class, EnvelopeDeadLetterSink.class}) - @ConditionalOnMissingBean(name = "vehicleStateEnvelopeConsumerProcessor") - public EnvelopeConsumerProcessor vehicleStateEnvelopeConsumerProcessor(VehicleStateEnvelopeIngestor ingestor, - EnvelopeDeadLetterSink deadLetterSink) { - // Bean 名必须和 Kafka 默认 binding 对齐,KafkaEnvelopeConsumerFactory 才能自动创建 worker。 - return new EnvelopeConsumerProcessor("vehicle-state", ingestor, deadLetterSink); - } - - @Bean - @ConditionalOnBean(VehicleStateRepository.class) - @ConditionalOnMissingBean - public VehicleStateController vehicleStateController(VehicleStateRepository repository) { - return new VehicleStateController(repository); - } -} diff --git a/modules/services/vehicle-state-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/services/vehicle-state-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index e6d5607b..00000000 --- a/modules/services/vehicle-state-service/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.vehiclestate.config.VehicleStateAutoConfiguration diff --git a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/InMemoryVehicleStateRepository.java b/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/InMemoryVehicleStateRepository.java deleted file mode 100644 index 4419eab6..00000000 --- a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/InMemoryVehicleStateRepository.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import java.util.Optional; - -final class InMemoryVehicleStateRepository implements VehicleStateRepository { - String state; - String location; - String safety; - String lastEvent; - - @Override - public void putState(String vin, String json) { - this.state = json; - } - - @Override - public void putLocation(String vin, String json) { - this.location = json; - } - - @Override - public void putSafety(String vin, String json) { - this.safety = json; - } - - @Override - public void putLastEvent(String vin, String json) { - this.lastEvent = json; - } - - @Override - public Optional getState(String vin) { - return Optional.ofNullable(state); - } - - @Override - public Optional getLocation(String vin) { - return Optional.ofNullable(location); - } - - @Override - public Optional getSafety(String vin) { - return Optional.ofNullable(safety); - } - - @Override - public Optional getLastEvent(String vin) { - return Optional.ofNullable(lastEvent); - } -} diff --git a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepositoryTest.java b/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepositoryTest.java deleted file mode 100644 index 06f59006..00000000 --- a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/RedisVehicleStateRepositoryTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import org.junit.jupiter.api.Test; -import org.springframework.data.redis.core.StringRedisTemplate; -import org.springframework.data.redis.core.ValueOperations; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class RedisVehicleStateRepositoryTest { - - @Test - void writesAndReadsHotStateJsonByStableRedisKey() { - StringRedisTemplate redis = mock(StringRedisTemplate.class); - @SuppressWarnings("unchecked") - ValueOperations ops = mock(ValueOperations.class); - when(redis.opsForValue()).thenReturn(ops); - when(ops.get("vehicle:state:VIN001")).thenReturn("{\"vin\":\"VIN001\"}"); - - RedisVehicleStateRepository repository = new RedisVehicleStateRepository(redis); - - repository.putState("VIN001", "{\"vin\":\"VIN001\"}"); - Optional value = repository.getState("VIN001"); - - verify(ops).set("vehicle:state:VIN001", "{\"vin\":\"VIN001\"}"); - assertThat(value).contains("{\"vin\":\"VIN001\"}"); - } - - @Test - void writesLocationSafetyAndLastEventToSeparateKeys() { - StringRedisTemplate redis = mock(StringRedisTemplate.class); - @SuppressWarnings("unchecked") - ValueOperations ops = mock(ValueOperations.class); - when(redis.opsForValue()).thenReturn(ops); - - RedisVehicleStateRepository repository = new RedisVehicleStateRepository(redis); - - repository.putLocation("VIN001", "{\"longitude\":113.12}"); - repository.putSafety("VIN001", "{\"hydrogen_leak_detected\":true}"); - repository.putLastEvent("VIN001", "{\"eventId\":\"event-1\"}"); - - verify(ops).set("vehicle:location:VIN001", "{\"longitude\":113.12}"); - verify(ops).set("vehicle:safety:VIN001", "{\"hydrogen_leak_detected\":true}"); - verify(ops).set("vehicle:event:last:VIN001", "{\"eventId\":\"event-1\"}"); - } -} diff --git a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateControllerTest.java b/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateControllerTest.java deleted file mode 100644 index 2aac4541..00000000 --- a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateControllerTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import org.junit.jupiter.api.Test; -import org.springframework.test.web.servlet.MockMvc; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup; - -class VehicleStateControllerTest { - - @Test - void returnsLatestStateJson() throws Exception { - VehicleStateRepository repository = mock(VehicleStateRepository.class); - when(repository.getState("VIN001")).thenReturn(java.util.Optional.of("{\"speed_kmh\":\"80.5\"}")); - MockMvc mvc = standaloneSetup(new VehicleStateController(repository)).build(); - - mvc.perform(get("/api/vehicle-state/VIN001")) - .andExpect(status().isOk()) - .andExpect(content().json("{\"speed_kmh\":\"80.5\"}")); - } - - @Test - void returnsLocationSafetyAndLastEventJson() throws Exception { - VehicleStateRepository repository = mock(VehicleStateRepository.class); - when(repository.getLocation("VIN001")).thenReturn(java.util.Optional.of("{\"longitude\":\"113.12\"}")); - when(repository.getSafety("VIN001")).thenReturn(java.util.Optional.of("{\"hydrogen_leak_detected\":\"true\"}")); - when(repository.getLastEvent("VIN001")).thenReturn(java.util.Optional.of("{\"eventId\":\"event-1\"}")); - MockMvc mvc = standaloneSetup(new VehicleStateController(repository)).build(); - - mvc.perform(get("/api/vehicle-state/VIN001/location")) - .andExpect(status().isOk()) - .andExpect(content().json("{\"longitude\":\"113.12\"}")); - mvc.perform(get("/api/vehicle-state/VIN001/safety")) - .andExpect(status().isOk()) - .andExpect(content().json("{\"hydrogen_leak_detected\":\"true\"}")); - mvc.perform(get("/api/vehicle-state/VIN001/last-event")) - .andExpect(status().isOk()) - .andExpect(content().json("{\"eventId\":\"event-1\"}")); - } - - @Test - void returnsNotFoundWhenStateIsMissing() throws Exception { - VehicleStateRepository repository = mock(VehicleStateRepository.class); - when(repository.getState("VIN001")).thenReturn(java.util.Optional.empty()); - MockMvc mvc = standaloneSetup(new VehicleStateController(repository)).build(); - - mvc.perform(get("/api/vehicle-state/VIN001")) - .andExpect(status().isNotFound()); - } -} diff --git a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestorTest.java b/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestorTest.java deleted file mode 100644 index dbf9dd92..00000000 --- a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateEnvelopeIngestorTest.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import com.lingniu.ingest.api.consumer.EnvelopeIngestor; -import com.lingniu.ingest.sink.kafka.proto.RawArchiveRef; -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import org.junit.jupiter.api.Test; - -import java.util.Optional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class VehicleStateEnvelopeIngestorTest { - - @Test - void parsesEnvelopeBytesAndUpdatesRepository() { - InMemoryVehicleStateRepository repository = new InMemoryVehicleStateRepository(); - VehicleStateEnvelopeIngestor ingestor = new VehicleStateEnvelopeIngestor(new VehicleStateUpdater(repository)); - - assertThat(ingestor).isInstanceOf(EnvelopeIngestor.class); - ingestor.ingest(envelope().toByteArray()); - - assertThat(repository.getState("VIN001")).hasValueSatisfying(json -> assertThat(json).contains("speed_kmh")); - } - - @Test - void rejectsInvalidEnvelopeBytes() { - VehicleStateEnvelopeIngestor ingestor = - new VehicleStateEnvelopeIngestor(new VehicleStateUpdater(new EmptyRepository())); - - assertThatThrownBy(() -> ingestor.ingest(new byte[]{0x01, 0x02})) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("VehicleEnvelope"); - } - - @Test - void tryIngestInvalidEnvelopeBytesReturnsInvalidWithoutUpdatingRepository() { - InMemoryVehicleStateRepository repository = new InMemoryVehicleStateRepository(); - VehicleStateEnvelopeIngestor ingestor = new VehicleStateEnvelopeIngestor(new VehicleStateUpdater(repository)); - - var result = ingestor.tryIngest(new byte[]{0x01, 0x02}); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.INVALID_ENVELOPE); - assertThat(result.message()).contains("VehicleEnvelope"); - assertThat(repository.getState("VIN001")).isEmpty(); - } - - @Test - void rawArchiveEnvelopeIsIgnoredWithoutUpdatingRepository() { - InMemoryVehicleStateRepository repository = new InMemoryVehicleStateRepository(); - VehicleStateEnvelopeIngestor ingestor = new VehicleStateEnvelopeIngestor(new VehicleStateUpdater(repository)); - VehicleEnvelope envelope = rawArchiveEnvelope(); - - ingestor.ingest(envelope.toByteArray()); - var result = ingestor.tryIngest(envelope.toByteArray()); - - assertThat(result.status()).isEqualTo(EnvelopeIngestResult.Status.SKIPPED); - assertThat(result.eventId()).isEqualTo("raw-event-1"); - assertThat(result.vin()).isEqualTo("VIN001"); - assertThat(result.message()).contains("telemetry_snapshot"); - assertThat(repository.getState("VIN001")).isEmpty(); - assertThat(repository.getLocation("VIN001")).isEmpty(); - assertThat(repository.getSafety("VIN001")).isEmpty(); - assertThat(repository.getLastEvent("VIN001")).isEmpty(); - } - - private static VehicleEnvelope envelope() { - return VehicleEnvelope.newBuilder() - .setEventId("event-1") - .setVin("VIN001") - .setSource("GB32960") - .setEventTimeMs(1) - .setIngestTimeMs(2) - .setTelemetrySnapshot(TelemetrySnapshot.newBuilder() - .setEventType("REALTIME") - .addFields(TelemetryField.newBuilder() - .setKey("speed_kmh") - .setValueType("DOUBLE") - .setValue("80.5") - .setUnit("km/h"))) - .build(); - } - - private static VehicleEnvelope rawArchiveEnvelope() { - return VehicleEnvelope.newBuilder() - .setEventId("raw-event-1") - .setVin("VIN001") - .setSource("GB32960") - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .setRawArchive(RawArchiveRef.newBuilder() - .setUri("archive://2026/06/23/GB32960/VIN001/raw-event-1.bin") - .setSizeBytes(128) - .build()) - .build(); - } - - private static final class EmptyRepository implements VehicleStateRepository { - @Override public void putState(String vin, String json) {} - @Override public void putLocation(String vin, String json) {} - @Override public void putSafety(String vin, String json) {} - @Override public void putLastEvent(String vin, String json) {} - @Override public Optional getState(String vin) { return Optional.empty(); } - @Override public Optional getLocation(String vin) { return Optional.empty(); } - @Override public Optional getSafety(String vin) { return Optional.empty(); } - @Override public Optional getLastEvent(String vin) { return Optional.empty(); } - } -} diff --git a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdaterTest.java b/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdaterTest.java deleted file mode 100644 index c896c85b..00000000 --- a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/VehicleStateUpdaterTest.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.lingniu.ingest.vehiclestate; - -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleStateUpdaterTest { - - @Test - void writesStateLocationSafetyAndLastEventFromTelemetrySnapshot() { - InMemoryVehicleStateRepository repository = new InMemoryVehicleStateRepository(); - VehicleStateUpdater updater = new VehicleStateUpdater(repository); - - updater.update(envelope() - .addField("speed_kmh", "DOUBLE", "80.5", "km/h") - .addField("longitude", "DOUBLE", "113.12", "deg") - .addField("latitude", "DOUBLE", "23.45", "deg") - .addField("hydrogen_leak_detected", "BOOLEAN", "true", "") - .addField("hydrogen_leak_level", "STRING", "CRITICAL", "") - .build()); - - assertThat(repository.state).contains("\"vin\":\"VIN001\""); - assertThat(repository.state).contains("\"speed_kmh\""); - assertThat(repository.location).contains("\"longitude\":\"113.12\""); - assertThat(repository.location).contains("\"latitude\":\"23.45\""); - assertThat(repository.safety).contains("\"hydrogen_leak_detected\":\"true\""); - assertThat(repository.lastEvent).contains("\"eventId\":\"event-1\""); - } - - @Test - void doesNotOverwriteLocationOrSafetyWhenFieldsAreMissing() { - InMemoryVehicleStateRepository repository = new InMemoryVehicleStateRepository(); - repository.location = "{\"longitude\":\"old\"}"; - repository.safety = "{\"hydrogen_leak_detected\":\"old\"}"; - - new VehicleStateUpdater(repository).update(envelope() - .addField("speed_kmh", "DOUBLE", "80.5", "km/h") - .build()); - - assertThat(repository.location).isEqualTo("{\"longitude\":\"old\"}"); - assertThat(repository.safety).isEqualTo("{\"hydrogen_leak_detected\":\"old\"}"); - assertThat(repository.state).contains("\"speed_kmh\""); - } - - private static EnvelopeBuilder envelope() { - return new EnvelopeBuilder(); - } - - private static final class EnvelopeBuilder { - private final TelemetrySnapshot.Builder snapshot = TelemetrySnapshot.newBuilder() - .setEventType("REALTIME") - .setRawArchiveUri("archive://raw/event-1.bin"); - - EnvelopeBuilder addField(String key, String valueType, String value, String unit) { - snapshot.addFields(TelemetryField.newBuilder() - .setKey(key) - .setValueType(valueType) - .setValue(value) - .setUnit(unit) - .setQuality("GOOD") - .setSourcePath("test")); - return this; - } - - VehicleEnvelope build() { - return VehicleEnvelope.newBuilder() - .setEventId("event-1") - .setTraceId("trace-1") - .setVin("VIN001") - .setSource("GB32960") - .setEventTimeMs(1_782_112_400_000L) - .setIngestTimeMs(1_782_112_401_000L) - .setTelemetrySnapshot(snapshot) - .build(); - } - } -} diff --git a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfigurationTest.java b/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfigurationTest.java deleted file mode 100644 index ba9cda84..00000000 --- a/modules/services/vehicle-state-service/src/test/java/com/lingniu/ingest/vehiclestate/config/VehicleStateAutoConfigurationTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.lingniu.ingest.vehiclestate.config; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import com.lingniu.ingest.vehiclestate.RedisVehicleStateRepository; -import com.lingniu.ingest.vehiclestate.VehicleStateController; -import com.lingniu.ingest.vehiclestate.VehicleStateEnvelopeIngestor; -import com.lingniu.ingest.vehiclestate.VehicleStateRepository; -import com.lingniu.ingest.vehiclestate.VehicleStateUpdater; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.data.redis.core.StringRedisTemplate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class VehicleStateAutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(VehicleStateAutoConfiguration.class)) - .withBean(EnvelopeDeadLetterSink.class, () -> record -> {}) - .withBean(StringRedisTemplate.class, () -> mock(StringRedisTemplate.class)); - - @Test - void createsVehicleStateBeansWhenEnabled() { - contextRunner - .withPropertyValues("lingniu.ingest.vehicle-state.enabled=true") - .run(context -> { - assertThat(context).hasSingleBean(VehicleStateRepository.class); - assertThat(context).hasSingleBean(RedisVehicleStateRepository.class); - assertThat(context).hasSingleBean(VehicleStateUpdater.class); - assertThat(context).hasSingleBean(VehicleStateEnvelopeIngestor.class); - assertThat(context).hasSingleBean(EnvelopeConsumerProcessor.class); - assertThat(context).hasSingleBean(VehicleStateController.class); - }); - } - - @Test - void backsOffWhenDisabled() { - contextRunner.run(context -> { - assertThat(context).doesNotHaveBean(VehicleStateRepository.class); - assertThat(context).doesNotHaveBean(VehicleStateUpdater.class); - assertThat(context).doesNotHaveBean(VehicleStateEnvelopeIngestor.class); - assertThat(context).doesNotHaveBean(VehicleStateController.class); - }); - } - - @Test - void doesNotCreateRepositoryWithoutRedisTemplate() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(VehicleStateAutoConfiguration.class)) - .withPropertyValues("lingniu.ingest.vehicle-state.enabled=true") - .run(context -> assertThat(context).doesNotHaveBean(VehicleStateRepository.class)); - } -} diff --git a/modules/sinks/sink-archive/pom.xml b/modules/sinks/sink-archive/pom.xml deleted file mode 100644 index 08d269e7..00000000 --- a/modules/sinks/sink-archive/pom.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - sink-archive - sink-archive - 原始报文冷存:本地文件系统实现。 - - - - com.lingniu.ingest - ingest-api - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - diff --git a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/ArchiveStore.java b/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/ArchiveStore.java deleted file mode 100644 index 211d633c..00000000 --- a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/ArchiveStore.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.lingniu.ingest.sink.archive; - -import java.io.IOException; -import java.io.InputStream; - -public interface ArchiveStore { - - String put(String key, InputStream data, long length) throws IOException; - - String append(String key, byte[] chunk) throws IOException; - - InputStream get(String key) throws IOException; - - boolean exists(String key); - - long size(String key) throws IOException; -} diff --git a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/LocalArchiveStore.java b/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/LocalArchiveStore.java deleted file mode 100644 index 89e6f04e..00000000 --- a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/LocalArchiveStore.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.lingniu.ingest.sink.archive; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -public final class LocalArchiveStore implements ArchiveStore { - - private final Path root; - private final ConcurrentMap appendLocks = new ConcurrentHashMap<>(); - - public LocalArchiveStore(String root) { - this(rootPath(root)); - } - - public LocalArchiveStore(Path root) { - this.root = root.toAbsolutePath().normalize(); - } - - @Override - public String put(String key, InputStream data, long length) throws IOException { - if (data == null) { - throw new IllegalArgumentException("data must not be null"); - } - Path target = resolve(key); - createDirectoriesInsideRoot(target.getParent()); - rejectSymlinkPath(target); - try (OutputStream out = Files.newOutputStream( - target, - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING, - StandardOpenOption.WRITE, - LinkOption.NOFOLLOW_LINKS)) { - data.transferTo(out); - } - return target.toUri().toString(); - } - - @Override - public String append(String key, byte[] chunk) throws IOException { - Path target = resolve(key); - synchronized (appendLocks.computeIfAbsent(target, ignored -> new Object())) { - createDirectoriesInsideRoot(target.getParent()); - rejectSymlinkPath(target); - Files.write( - target, - chunk == null ? new byte[0] : chunk, - StandardOpenOption.CREATE, - StandardOpenOption.APPEND, - StandardOpenOption.WRITE, - LinkOption.NOFOLLOW_LINKS); - } - return target.toUri().toString(); - } - - @Override - public InputStream get(String key) throws IOException { - Path target = resolveExisting(key); - return Files.newInputStream(target, StandardOpenOption.READ, LinkOption.NOFOLLOW_LINKS); - } - - @Override - public boolean exists(String key) { - try { - return Files.exists(resolveExisting(key), LinkOption.NOFOLLOW_LINKS); - } catch (IOException | IllegalArgumentException e) { - return false; - } - } - - @Override - public long size(String key) throws IOException { - return Files.size(resolveExisting(key)); - } - - private Path resolve(String key) { - if (key == null || key.isBlank()) { - throw new IllegalArgumentException("archive key must not be blank"); - } - Path target = root.resolve(key).normalize(); - if (!target.startsWith(root)) { - throw new IllegalArgumentException("archive key escapes root: " + key); - } - return target; - } - - private Path resolveExisting(String key) throws IOException { - Path target = resolve(key); - rejectSymlinkPath(target); - return target; - } - - private void createDirectoriesInsideRoot(Path directory) throws IOException { - rejectEscapedPath(directory); - if (Files.notExists(root, LinkOption.NOFOLLOW_LINKS)) { - Files.createDirectories(root); - } - Path current = root; - Path relative = root.relativize(directory); - for (Path segment : relative) { - current = current.resolve(segment); - if (Files.isSymbolicLink(current)) { - throw new IOException("archive path traverses symlink: " + current); - } - if (Files.notExists(current, LinkOption.NOFOLLOW_LINKS)) { - Files.createDirectory(current); - } else if (!Files.isDirectory(current, LinkOption.NOFOLLOW_LINKS)) { - throw new IOException("archive path segment is not a directory: " + current); - } - } - } - - private void rejectSymlinkPath(Path target) throws IOException { - rejectEscapedPath(target); - Path current = root; - Path relative = root.relativize(target); - for (Path segment : relative) { - current = current.resolve(segment); - if (Files.isSymbolicLink(current)) { - throw new IOException("archive path traverses symlink: " + current); - } - if (Files.notExists(current, LinkOption.NOFOLLOW_LINKS)) { - return; - } - } - } - - private void rejectEscapedPath(Path path) { - if (!path.normalize().startsWith(root)) { - throw new IllegalArgumentException("archive path escapes root: " + path); - } - } - - private static Path rootPath(String value) { - if (value == null || value.isBlank()) { - return Path.of(System.getProperty("java.io.tmpdir"), "lingniu-archive"); - } - if (value.startsWith("file://")) { - return Path.of(URI.create(value)); - } - return Path.of(value); - } -} diff --git a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/RawArchiveEventSink.java b/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/RawArchiveEventSink.java deleted file mode 100644 index 5a1e9e8d..00000000 --- a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/RawArchiveEventSink.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.lingniu.ingest.sink.archive; - -import com.lingniu.ingest.api.event.RawArchiveKeys; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; - -import java.io.IOException; -import java.nio.file.FileAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.HexFormat; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -public final class RawArchiveEventSink implements EventSink, AutoCloseable { - - private final Path root; - private final ExecutorService executor; - - public RawArchiveEventSink(Path root) { - if (root == null) { - throw new IllegalArgumentException("root must not be null"); - } - this.root = root.toAbsolutePath().normalize(); - this.executor = Executors.newVirtualThreadPerTaskExecutor(); - } - - @Override - public String name() { - return "raw-archive"; - } - - @Override - public boolean accepts(VehicleEvent event) { - return event instanceof VehicleEvent.RawArchive; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - if (!(event instanceof VehicleEvent.RawArchive raw)) { - return CompletableFuture.completedFuture(null); - } - return CompletableFuture.runAsync(() -> write(raw), executor); - } - - @Override - public void close() { - executor.shutdown(); - } - - public Path root() { - return root; - } - - private void write(VehicleEvent.RawArchive raw) { - byte[] bytes = raw.rawBytes() == null ? new byte[0] : raw.rawBytes(); - if (bytes.length == 0) { - return; - } - String key = RawArchiveKeys.key(raw); - Path target = resolveKey(key); - try { - createDirectoriesInsideRoot(target.getParent()); - rejectSymlinkPath(target); - try { - Files.write(target, bytes, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); - } catch (FileAlreadyExistsException duplicate) { - byte[] existing = Files.readAllBytes(target); - if (!Arrays.equals(sha256(existing), sha256(bytes))) { - throw new IOException("raw archive duplicate key has different content: " + key, duplicate); - } - } - } catch (IOException e) { - throw new RawArchiveWriteException("failed to write raw archive " + RawArchiveKeys.logicalUri(key), e); - } - } - - private Path resolveKey(String key) { - Path target = root.resolve(key == null ? "" : key).normalize(); - if (!target.startsWith(root)) { - throw new IllegalArgumentException("raw archive key escapes root: " + key); - } - return target; - } - - private void createDirectoriesInsideRoot(Path directory) throws IOException { - rejectEscapedPath(directory); - if (Files.notExists(root, LinkOption.NOFOLLOW_LINKS)) { - Files.createDirectories(root); - } - Path current = root; - Path relative = root.relativize(directory); - for (Path segment : relative) { - current = current.resolve(segment); - if (Files.isSymbolicLink(current)) { - throw new IOException("raw archive path traverses symlink: " + current); - } - if (Files.notExists(current, LinkOption.NOFOLLOW_LINKS)) { - Files.createDirectory(current); - } else if (!Files.isDirectory(current, LinkOption.NOFOLLOW_LINKS)) { - throw new IOException("raw archive path segment is not a directory: " + current); - } - } - } - - private void rejectSymlinkPath(Path target) throws IOException { - rejectEscapedPath(target); - Path current = root; - Path relative = root.relativize(target); - for (Path segment : relative) { - current = current.resolve(segment); - if (Files.isSymbolicLink(current)) { - throw new IOException("raw archive path traverses symlink: " + current); - } - if (Files.notExists(current, LinkOption.NOFOLLOW_LINKS)) { - return; - } - } - } - - private void rejectEscapedPath(Path path) { - if (!path.normalize().startsWith(root)) { - throw new IllegalArgumentException("raw archive path escapes root: " + path); - } - } - - private static byte[] sha256(byte[] bytes) { - try { - return MessageDigest.getInstance("SHA-256").digest(bytes); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is unavailable", e); - } - } - - static String checksum(byte[] bytes) { - return "sha256:" + HexFormat.of().formatHex(sha256(bytes == null ? new byte[0] : bytes)); - } - - public static final class RawArchiveWriteException extends RuntimeException { - public RawArchiveWriteException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfiguration.java b/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfiguration.java deleted file mode 100644 index 0bf04f4e..00000000 --- a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfiguration.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.lingniu.ingest.sink.archive.config; - -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.LocalArchiveStore; -import com.lingniu.ingest.sink.archive.RawArchiveEventSink; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -import java.nio.file.Path; - -@AutoConfiguration -@EnableConfigurationProperties(SinkArchiveProperties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.sink.archive", name = "enabled", havingValue = "true", matchIfMissing = true) -public class SinkArchiveAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "lingniu.ingest.sink.archive", name = "type", havingValue = "local", matchIfMissing = true) - public ArchiveStore archiveStore(SinkArchiveProperties properties) { - return new LocalArchiveStore(Path.of(properties.getPath())); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "lingniu.ingest.sink.archive", name = "type", havingValue = "local", matchIfMissing = true) - public RawArchiveEventSink rawArchiveEventSink(SinkArchiveProperties properties) { - return new RawArchiveEventSink(Path.of(properties.getPath())); - } -} diff --git a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveProperties.java b/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveProperties.java deleted file mode 100644 index 32668078..00000000 --- a/modules/sinks/sink-archive/src/main/java/com/lingniu/ingest/sink/archive/config/SinkArchiveProperties.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.lingniu.ingest.sink.archive.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "lingniu.ingest.sink.archive") -public class SinkArchiveProperties { - - private boolean enabled = true; - private String type = "local"; - private String path = "./archive/"; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public String getPath() { - return path; - } - - public void setPath(String path) { - this.path = path; - } -} diff --git a/modules/sinks/sink-archive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/sinks/sink-archive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 4dcf699d..00000000 --- a/modules/sinks/sink-archive/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.sink.archive.config.SinkArchiveAutoConfiguration diff --git a/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/LocalArchiveStoreTest.java b/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/LocalArchiveStoreTest.java deleted file mode 100644 index f23e90c2..00000000 --- a/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/LocalArchiveStoreTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.lingniu.ingest.sink.archive; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class LocalArchiveStoreTest { - - @TempDir - Path tempDir; - - @Test - void rejectsSymlinkTraversalOutsideRootForWriteReadAndSize() throws Exception { - Path root = tempDir.resolve("archive"); - Path outside = tempDir.resolve("outside"); - Files.createDirectories(root); - Files.createDirectories(outside); - Files.writeString(outside.resolve("existing.bin"), "secret", StandardCharsets.UTF_8); - Files.createSymbolicLink(root.resolve("link"), outside); - - LocalArchiveStore store = new LocalArchiveStore(root); - - assertThatThrownBy(() -> store.put( - "link/new.bin", - new ByteArrayInputStream("payload".getBytes(StandardCharsets.UTF_8)), - 7)) - .isInstanceOfAny(IllegalArgumentException.class, java.io.IOException.class); - assertThat(outside.resolve("new.bin")).doesNotExist(); - - assertThatThrownBy(() -> store.get("link/existing.bin")) - .isInstanceOfAny(IllegalArgumentException.class, java.io.IOException.class); - assertThatThrownBy(() -> store.size("link/existing.bin")) - .isInstanceOfAny(IllegalArgumentException.class, java.io.IOException.class); - } - - @Test - void appendsConcurrentChunksWithoutLosingData() throws Exception { - LocalArchiveStore store = new LocalArchiveStore(tempDir.resolve("archive")); - int chunks = 96; - - try (var executor = Executors.newFixedThreadPool(12)) { - List> futures = new ArrayList<>(); - for (int i = 0; i < chunks; i++) { - String chunk = "%03d\n".formatted(i); - futures.add(CompletableFuture.runAsync(() -> { - try { - store.append("same/key.bin", chunk.getBytes(StandardCharsets.UTF_8)); - } catch (Exception e) { - throw new RuntimeException(e); - } - }, executor)); - } - CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join(); - } - - String content = Files.readString(tempDir.resolve("archive/same/key.bin"), StandardCharsets.UTF_8); - assertThat(content).hasSize(chunks * 4); - assertThat(content.lines()).containsExactlyInAnyOrderElementsOf( - java.util.stream.IntStream.range(0, chunks) - .mapToObj("%03d"::formatted) - .toList()); - } -} diff --git a/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/RawArchiveEventSinkTest.java b/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/RawArchiveEventSinkTest.java deleted file mode 100644 index c6534851..00000000 --- a/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/RawArchiveEventSinkTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.lingniu.ingest.sink.archive; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.RawArchiveKeys; -import com.lingniu.ingest.api.event.VehicleEvent; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class RawArchiveEventSinkTest { - - @TempDir - Path tempDir; - - @Test - void writesRawBytesUsingSharedArchiveKey() throws Exception { - RawArchiveEventSink sink = new RawArchiveEventSink(tempDir); - VehicleEvent.RawArchive raw = rawArchive(Map.of(), new byte[]{0x23, 0x23, 0x01}); - - sink.publish(raw).join(); - - String key = RawArchiveKeys.key(raw); - assertThat(Files.readAllBytes(tempDir.resolve(key))).containsExactly(0x23, 0x23, 0x01); - assertThat(RawArchiveKeys.logicalUri(key)).isEqualTo("archive://" + key); - } - - @Test - void rejectsArchiveKeysThatEscapeRoot() { - RawArchiveEventSink sink = new RawArchiveEventSink(tempDir); - VehicleEvent.RawArchive raw = rawArchive( - Map.of(RawArchiveKeys.META_KEY, "../escape.bin"), - new byte[]{0x01}); - - assertThatThrownBy(() -> sink.publish(raw).join()) - .hasCauseInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("raw archive key escapes root"); - } - - @Test - void rejectsSymlinkTraversalBeforeCreatingDirectories() throws Exception { - Path root = tempDir.resolve("archive"); - Path outside = tempDir.resolve("outside"); - Files.createDirectories(root); - Files.createDirectories(outside); - Files.createSymbolicLink(root.resolve("link"), outside); - - RawArchiveEventSink sink = new RawArchiveEventSink(root); - VehicleEvent.RawArchive raw = rawArchive( - Map.of(RawArchiveKeys.META_KEY, "link/nested/raw.bin"), - new byte[]{0x01}); - - assertThatThrownBy(() -> sink.publish(raw).join()) - .hasRootCauseMessage("raw archive path traverses symlink: " + root.resolve("link")); - assertThat(outside.resolve("nested")).doesNotExist(); - } - - private static VehicleEvent.RawArchive rawArchive(Map metadata, byte[] rawBytes) { - return new VehicleEvent.RawArchive( - "raw-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-1", - metadata, - 0x02, - 0, - rawBytes); - } -} diff --git a/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfigurationTest.java b/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfigurationTest.java deleted file mode 100644 index 2156cb9e..00000000 --- a/modules/sinks/sink-archive/src/test/java/com/lingniu/ingest/sink/archive/config/SinkArchiveAutoConfigurationTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.lingniu.ingest.sink.archive.config; - -import com.lingniu.ingest.sink.archive.ArchiveStore; -import com.lingniu.ingest.sink.archive.RawArchiveEventSink; -import org.junit.jupiter.api.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.core.env.MapPropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -class SinkArchiveAutoConfigurationTest { - - @Test - void localArchiveTypeCreatesStoreAndSink() { - try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { - context.getEnvironment().getPropertySources().addFirst(new MapPropertySource( - "test", - java.util.Map.of("lingniu.ingest.sink.archive.path", "target/test-archive"))); - context.register(SinkArchiveAutoConfiguration.class); - context.refresh(); - - assertThat(context.getBeansOfType(ArchiveStore.class)).hasSize(1); - assertThat(context.getBeansOfType(RawArchiveEventSink.class)).hasSize(1); - } - } - - @Test - void nonLocalArchiveTypeDoesNotCreateSinkWithoutStore() { - try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) { - context.getEnvironment().getPropertySources().addFirst(new MapPropertySource( - "test", - java.util.Map.of("lingniu.ingest.sink.archive.type", "oss"))); - context.register(SinkArchiveAutoConfiguration.class); - context.refresh(); - - assertThat(context.getBeansOfType(ArchiveStore.class)).isEmpty(); - assertThat(context.getBeansOfType(RawArchiveEventSink.class)).isEmpty(); - } - } -} diff --git a/modules/sinks/sink-kafka/pom.xml b/modules/sinks/sink-kafka/pom.xml deleted file mode 100644 index aafb8d76..00000000 --- a/modules/sinks/sink-kafka/pom.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - sink-kafka - sink-kafka - Kafka producer + Protobuf Envelope + 重试/熔断/DLQ。 - - - - com.lingniu.ingest - ingest-api - - - com.lingniu.ingest - ingest-facts - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.apache.kafka - kafka-clients - - - com.google.protobuf - protobuf-java - - - com.google.protobuf - protobuf-java-util - - - io.github.resilience4j - resilience4j-retry - - - io.github.resilience4j - resilience4j-circuitbreaker - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - - - - - kr.motd.maven - os-maven-plugin - 1.7.1 - - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - 0.6.1 - - com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} - - - - - compile - test-compile - - - - - - - diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/EnvelopeMapper.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/EnvelopeMapper.java deleted file mode 100644 index 04cf415d..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/EnvelopeMapper.java +++ /dev/null @@ -1,226 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.event.AlarmPayload; -import com.lingniu.ingest.api.event.LocationPayload; -import com.lingniu.ingest.api.event.RawArchiveKeys; -import com.lingniu.ingest.api.event.RealtimePayload; -import com.lingniu.ingest.api.event.TelemetryFieldValue; -import com.lingniu.ingest.api.event.TelemetrySnapshot; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.event.VehicleEventTelemetrySnapshotMapper; -import com.lingniu.ingest.facts.RawFrameIdentity; -import com.lingniu.ingest.sink.kafka.proto.ParseStatusProto; -import com.lingniu.ingest.sink.kafka.proto.TelemetryField; -import com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot.Builder; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; - -/** - * 领域事件 → Protobuf Envelope 的纯函数映射。通过 switch 模式匹配穷尽处理, - * 新增 {@link VehicleEvent} 子类型时编译期就会提示补齐分支。 - */ -public final class EnvelopeMapper { - - private static final String SCHEMA_VERSION = "1.0"; - private final String nodeId; - - public EnvelopeMapper(String nodeId) { - this.nodeId = nodeId; - } - - public VehicleEnvelope toEnvelope(VehicleEvent event) { - VehicleEnvelope.Builder b = VehicleEnvelope.newBuilder() - .setSchemaVersion(SCHEMA_VERSION) - .setEventId(event.eventId()) - .setTraceId(event.traceId() == null ? "" : event.traceId()) - .setVin(event.vin()) - .setSource(event.source().name()) - .setEventTimeMs(event.eventTime().toEpochMilli()) - .setIngestTimeMs(event.ingestTime().toEpochMilli()) - .setIngestNodeId(nodeId); - if (event.metadata() != null) b.putAllMetadata(event.metadata()); - // telemetrySnapshot 是跨事件类型的统一字段视图,消费者可先读它,必要时再读具体 payload。 - VehicleEventTelemetrySnapshotMapper.toSnapshot(event) - .map(EnvelopeMapper::buildTelemetrySnapshot) - .ifPresent(b::setTelemetrySnapshot); - - switch (event) { - case VehicleEvent.Realtime r -> b.setRealtime(buildRealtime(r.payload())); - case VehicleEvent.Location l -> b.setLocation(buildLocation(l.payload())); - case VehicleEvent.Alarm a -> b.setAlarm(buildAlarm(a.payload())); - case VehicleEvent.Login lg -> b.setLogin( - com.lingniu.ingest.sink.kafka.proto.LoginPayload.newBuilder() - .setIccid(nullToEmpty(lg.iccid())) - .setProtocolVersion(nullToEmpty(lg.protocolVersion())) - .build()); - case VehicleEvent.Logout ignored -> b.setLogout( - com.lingniu.ingest.sink.kafka.proto.LogoutPayload.getDefaultInstance()); - case VehicleEvent.Heartbeat ignored -> b.setHeartbeat( - com.lingniu.ingest.sink.kafka.proto.HeartbeatPayload.getDefaultInstance()); - case VehicleEvent.MediaMeta m -> b.setMediaMeta( - com.lingniu.ingest.sink.kafka.proto.MediaMetaPayload.newBuilder() - .setMediaId(nullToEmpty(m.mediaId())) - .setMediaType(nullToEmpty(m.mediaType())) - .setSizeBytes(m.sizeBytes()) - .setArchiveRef(nullToEmpty(m.archiveRef())) - .build()); - case VehicleEvent.Passthrough p -> b.setPassthrough( - com.lingniu.ingest.sink.kafka.proto.PassthroughPayload.newBuilder() - .setPassthroughType(p.passthroughType()) - .setData(com.google.protobuf.ByteString.copyFrom( - p.data() == null ? new byte[0] : p.data())) - .build()); - case VehicleEvent.RawArchive ra -> { - byte[] rawBytes = ra.rawBytes() == null ? new byte[0] : ra.rawBytes(); - int size = rawBytes.length; - String key = rawArchiveKey(ra); - String uri = rawArchiveUri(ra, key); - String phone = metadataValue(ra, "phone"); - RawFrameIdentity identity = RawFrameIdentity.derive( - ra.source(), ra.vin(), phone, ra.eventId(), ra.command(), ra.infoType(), - ra.ingestTime(), rawBytes); - // RAW envelope 只携带 URI/size 引用信息,不把完整原始字节塞进 Kafka,避免 topic 膨胀。 - b.putMetadata(RawArchiveKeys.META_KEY, key); - b.putMetadata(RawArchiveKeys.META_URI, uri); - b.putMetadata(RawArchiveKeys.META_EVENT_ID, ra.eventId()); - b.setRawArchive(com.lingniu.ingest.sink.kafka.proto.RawArchiveRef.newBuilder() - .setUri(uri) - .setChecksum(identity.checksum()) - .setSizeBytes(size) - .setParsedJson(nullToEmpty(ra.parsedJson())) - .build()); - b.setRawFrameFact(com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload.newBuilder() - .setFrameId(identity.frameId()) - .setVehicleKey(identity.vehicleKey()) - .setVin(nullToEmpty(ra.vin())) - .setPhone(phone) - .setMessageId(ra.command()) - .setSubType(ra.infoType()) - .setRawUri(uri) - .setChecksum(identity.checksum()) - .setRawSizeBytes(size) - .setParseStatus(parseStatus(ra)) - .setParseError(parseError(ra)) - .setPeer(metadataValue(ra, "peer")) - .putAllMetadata(ra.metadata() == null ? java.util.Map.of() : ra.metadata()) - .build()); - } - } - return b.build(); - } - - private static com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot buildTelemetrySnapshot(TelemetrySnapshot snapshot) { - Builder b = com.lingniu.ingest.sink.kafka.proto.TelemetrySnapshot.newBuilder() - .setEventType(snapshot.eventType()) - .setRawArchiveUri(snapshot.rawArchiveUri()); - for (TelemetryFieldValue field : snapshot.fields()) { - b.addFields(TelemetryField.newBuilder() - .setKey(field.key()) - .setValueType(field.valueType().name()) - .setValue(field.value()) - .setUnit(field.unit()) - .setQuality(field.quality().name()) - .setSourcePath(field.sourcePath()) - .build()); - } - return b.build(); - } - - private static com.lingniu.ingest.sink.kafka.proto.RealtimePayload buildRealtime(RealtimePayload p) { - var b = com.lingniu.ingest.sink.kafka.proto.RealtimePayload.newBuilder(); - if (p.speedKmh() != null) b.setSpeedKmh(p.speedKmh()); - if (p.totalMileageKm() != null) b.setTotalMileageKm(p.totalMileageKm()); - if (p.batterySoc() != null) b.setBatterySoc(p.batterySoc()); - if (p.batteryVoltageV() != null) b.setBatteryVoltageV(p.batteryVoltageV()); - if (p.batteryCurrentA() != null) b.setBatteryCurrentA(p.batteryCurrentA()); - if (p.fcVoltageV() != null) b.setFcVoltageV(p.fcVoltageV()); - if (p.fcCurrentA() != null) b.setFcCurrentA(p.fcCurrentA()); - if (p.fcTempC() != null) b.setFcTempC(p.fcTempC()); - if (p.hydrogenRemainingKg() != null) b.setHydrogenRemainingKg(p.hydrogenRemainingKg()); - if (p.hydrogenHighPressureMpa() != null) b.setHydrogenHighPressureMpa(p.hydrogenHighPressureMpa()); - if (p.hydrogenLowPressureMpa() != null) b.setHydrogenLowPressureMpa(p.hydrogenLowPressureMpa()); - if (p.vehicleState() != null) b.setVehicleState(p.vehicleState().name()); - if (p.chargingState() != null) b.setChargingState(p.chargingState().name()); - if (p.runningMode() != null) b.setRunningMode(p.runningMode().name()); - if (p.gearLevel() != null) b.setGearLevel(p.gearLevel()); - if (p.acceleratorPedal() != null) b.setAcceleratorPedal(p.acceleratorPedal()); - if (p.brakePedal() != null) b.setBrakePedal(p.brakePedal()); - if (p.longitude() != null) b.setLongitude(p.longitude()); - if (p.latitude() != null) b.setLatitude(p.latitude()); - if (p.altitudeM() != null) b.setAltitudeM(p.altitudeM()); - if (p.directionDeg() != null) b.setDirectionDeg(p.directionDeg()); - if (p.ambientTempC() != null) b.setAmbientTempC(p.ambientTempC()); - if (p.coolantTempC() != null) b.setCoolantTempC(p.coolantTempC()); - return b.build(); - } - - private static com.lingniu.ingest.sink.kafka.proto.LocationPayload buildLocation(LocationPayload p) { - return com.lingniu.ingest.sink.kafka.proto.LocationPayload.newBuilder() - .setLongitude(p.longitude()) - .setLatitude(p.latitude()) - .setAltitudeM(p.altitudeM()) - .setSpeedKmh(p.speedKmh()) - .setDirectionDeg(p.directionDeg()) - .setAlarmFlag(p.alarmFlag()) - .setStatusFlag(p.statusFlag()) - .build(); - } - - private static com.lingniu.ingest.sink.kafka.proto.AlarmPayload buildAlarm(AlarmPayload p) { - var b = com.lingniu.ingest.sink.kafka.proto.AlarmPayload.newBuilder() - .setLevel(p.level().name()) - .setAlarmTypeCode(p.alarmTypeCode()) - .setAlarmTypeName(nullToEmpty(p.alarmTypeName())); - if (p.faultCodes() != null) b.addAllFaultCodes(p.faultCodes()); - if (p.activeBits() != null) b.addAllActiveBits(p.activeBits()); - if (p.longitude() != null) b.setLongitude(p.longitude()); - if (p.latitude() != null) b.setLatitude(p.latitude()); - return b.build(); - } - - private static String nullToEmpty(String s) { - return s == null ? "" : s; - } - - private static String rawArchiveKey(VehicleEvent.RawArchive raw) { - String key = raw.metadata() == null ? "" : raw.metadata().getOrDefault(RawArchiveKeys.META_KEY, ""); - return key == null || key.isBlank() ? RawArchiveKeys.key(raw) : key; - } - - private static String rawArchiveUri(VehicleEvent.RawArchive raw, String key) { - String uri = raw.metadata() == null ? "" : raw.metadata().getOrDefault(RawArchiveKeys.META_URI, ""); - return uri == null || uri.isBlank() ? RawArchiveKeys.logicalUri(key) : uri; - } - - private static String metadataValue(VehicleEvent event, String key) { - if (event.metadata() == null) { - return ""; - } - return nullToEmpty(event.metadata().get(key)); - } - - private static ParseStatusProto parseStatus(VehicleEvent.RawArchive raw) { - if (hasTruthyMetadata(raw, "frameError") - || hasTruthyMetadata(raw, "parseError") - || hasTruthyMetadata(raw, "processingError")) { - return ParseStatusProto.PARSE_STATUS_FAILED; - } - return ParseStatusProto.PARSE_STATUS_NOT_PARSED; - } - - private static String parseError(VehicleEvent.RawArchive raw) { - String frameError = metadataValue(raw, "frameErrorMessage"); - if (!frameError.isBlank()) { - return frameError; - } - String parseError = metadataValue(raw, "parseErrorMessage"); - if (!parseError.isBlank()) { - return parseError; - } - return metadataValue(raw, "processingErrorMessage"); - } - - private static boolean hasTruthyMetadata(VehicleEvent event, String key) { - return Boolean.parseBoolean(metadataValue(event, key)); - } - -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactory.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactory.java deleted file mode 100644 index ed83e261..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactory.java +++ /dev/null @@ -1,142 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.consumer.KafkaConsumer; -import org.apache.kafka.common.serialization.ByteArrayDeserializer; -import org.apache.kafka.common.serialization.StringDeserializer; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.function.Function; - -public final class KafkaEnvelopeConsumerFactory { - - private final Function> consumerFactory; - - public KafkaEnvelopeConsumerFactory() { - this(KafkaConsumer::new); - } - - KafkaEnvelopeConsumerFactory( - Function> consumerFactory) { - this.consumerFactory = consumerFactory; - } - - public List createWorkers(Map processors, - KafkaSinkProperties props) { - Map bindings = effectiveBindings(props); - List workers = new ArrayList<>(); - for (Map.Entry entry : bindings.entrySet()) { - String processorBeanName = entry.getKey(); - // binding 的 key 必须和 Spring Bean 名一致;这样配置只声明 topic/group, - // 实际处理逻辑仍由各业务模块自己的 EnvelopeConsumerProcessor 承接。 - EnvelopeConsumerProcessor processor = processors.get(processorBeanName); - KafkaSinkProperties.Binding binding = entry.getValue(); - if (processor == null || binding == null || !binding.isEnabled()) { - continue; - } - List topics = cleanTopics(binding.getTopics()); - if (topics.isEmpty()) { - continue; - } - int concurrency = Math.max(1, props.getConsumer().getConcurrency()); - for (int workerIndex = 0; workerIndex < concurrency; workerIndex++) { - KafkaEnvelopeConsumerWorker worker = new KafkaEnvelopeConsumerWorker( - consumerFactory.apply(consumerProperties(props, binding, processorBeanName, - workerIndex, concurrency)), - topicProcessors(topics, processor)); - worker.subscribe(topics); - workers.add(worker); - } - } - return workers; - } - - private Map effectiveBindings(KafkaSinkProperties props) { - Map configured = props.getConsumer().getBindings(); - if (configured != null && !configured.isEmpty()) { - return configured; - } - // 默认绑定仅给未显式配置 bindings 的轻量运行时兜底。 - // 生产 history app 会显式绑定各协议 event/raw topic,并写入 TDengine raw_frames/locations。 - KafkaSinkProperties.Topics topics = props.getTopics(); - Map defaults = new LinkedHashMap<>(); - defaults.put("eventHistoryEnvelopeConsumerProcessor", binding( - "vehicle-event-history", - topics.getRealtime(), topics.getLocation(), topics.getAlarm(), topics.getSession(), topics.getMediaMeta())); - defaults.put("vehicleStateEnvelopeConsumerProcessor", binding( - "vehicle-state", - topics.getRealtime(), topics.getLocation(), topics.getAlarm())); - defaults.put("vehicleStatEnvelopeConsumerProcessor", binding( - "vehicle-stat", - topics.getRealtime(), topics.getLocation())); - return defaults; - } - - private KafkaSinkProperties.Binding binding(String groupId, String... topics) { - KafkaSinkProperties.Binding binding = new KafkaSinkProperties.Binding(); - binding.setGroupId(groupId); - binding.setTopics(List.of(topics)); - return binding; - } - - private Map topicProcessors(List topics, EnvelopeConsumerProcessor processor) { - Map byTopic = new LinkedHashMap<>(); - for (String topic : topics) { - byTopic.put(topic, processor); - } - return byTopic; - } - - private List cleanTopics(List topics) { - if (topics == null || topics.isEmpty()) { - return List.of(); - } - LinkedHashSet clean = new LinkedHashSet<>(); - for (String topic : topics) { - if (topic != null && !topic.isBlank()) { - clean.add(topic); - } - } - return List.copyOf(clean); - } - - private Properties consumerProperties(KafkaSinkProperties props, - KafkaSinkProperties.Binding binding, - String processorBeanName, - int workerIndex, - int concurrency) { - KafkaSinkProperties.Consumer consumer = props.getConsumer(); - Properties p = new Properties(); - p.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, props.getBootstrapServers()); - p.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName()); - p.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class.getName()); - p.put(ConsumerConfig.GROUP_ID_CONFIG, groupId(binding, processorBeanName)); - p.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId(consumer.getClientIdPrefix(), processorBeanName, - workerIndex, concurrency)); - // 手动提交 offset:只有本批次至少有一条记录被处理器接收后才 commit, - // 避免轮询到空批次或未绑定 topic 时推进消费位点。 - p.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); - p.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, consumer.getAutoOffsetReset()); - p.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, consumer.getMaxPollRecords()); - p.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, consumer.getMaxPollIntervalMillis()); - return p; - } - - private String clientId(String prefix, String processorBeanName, int workerIndex, int concurrency) { - String base = prefix + "-" + processorBeanName; - return concurrency <= 1 ? base : base + "-" + workerIndex; - } - - private String groupId(KafkaSinkProperties.Binding binding, String processorBeanName) { - if (binding.getGroupId() != null && !binding.getGroupId().isBlank()) { - return binding.getGroupId(); - } - return processorBeanName.replace("EnvelopeConsumerProcessor", ""); - } -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunner.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunner.java deleted file mode 100644 index 17e90726..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunner.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.context.SmartLifecycle; - -import java.time.Duration; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; - -public final class KafkaEnvelopeConsumerRunner implements SmartLifecycle, AutoCloseable { - - private static final Logger log = LoggerFactory.getLogger(KafkaEnvelopeConsumerRunner.class); - - private final Supplier> workersSupplier; - private final Duration pollTimeout; - private final Duration loopBackoff; - private final boolean autoStartup; - private final AtomicBoolean running = new AtomicBoolean(false); - private final AtomicBoolean closed = new AtomicBoolean(false); - private volatile List workers; - private ExecutorService executor; - - public KafkaEnvelopeConsumerRunner(List workers, - Duration pollTimeout, - Duration loopBackoff, - boolean autoStartup) { - if (workers == null || workers.isEmpty()) { - throw new IllegalArgumentException("workers must not be empty"); - } - this.workersSupplier = () -> List.copyOf(workers); - this.workers = List.copyOf(workers); - this.pollTimeout = pollTimeout == null ? Duration.ofSeconds(1) : pollTimeout; - this.loopBackoff = loopBackoff == null ? Duration.ofSeconds(1) : loopBackoff; - this.autoStartup = autoStartup; - } - - public KafkaEnvelopeConsumerRunner(Supplier> workersSupplier, - Duration pollTimeout, - Duration loopBackoff, - boolean autoStartup) { - if (workersSupplier == null) { - throw new IllegalArgumentException("workersSupplier must not be null"); - } - this.workersSupplier = workersSupplier; - this.pollTimeout = pollTimeout == null ? Duration.ofSeconds(1) : pollTimeout; - this.loopBackoff = loopBackoff == null ? Duration.ofSeconds(1) : loopBackoff; - this.autoStartup = autoStartup; - } - - public List workers() { - return workersOrCreate(); - } - - @Override - public void start() { - if (!running.compareAndSet(false, true)) { - return; - } - List activeWorkers = workersOrCreate(); - // 每个 worker 一条后台线程,避免某个处理器阻塞时拖慢其他消费组。 - executor = Executors.newFixedThreadPool(activeWorkers.size(), r -> { - Thread thread = new Thread(r, "kafka-envelope-consumer"); - thread.setDaemon(true); - return thread; - }); - for (KafkaEnvelopeConsumerWorker worker : activeWorkers) { - executor.submit(() -> pollLoop(worker)); - } - } - - private List workersOrCreate() { - List current = workers; - if (current != null) { - return current; - } - synchronized (this) { - if (workers == null) { - List created = workersSupplier.get(); - if (created == null || created.isEmpty()) { - throw new IllegalStateException("no kafka envelope consumer workers created; check consumer bindings"); - } - workers = List.copyOf(created); - } - return workers; - } - } - - private void pollLoop(KafkaEnvelopeConsumerWorker worker) { - while (running.get()) { - try { - worker.pollOnce(pollTimeout); - } catch (RuntimeException ex) { - if (!running.get()) { - break; - } - // Kafka/处理器异常不让 Spring 生命周期退出;退避后继续消费, - // 具体坏消息由 EnvelopeConsumerProcessor 写入 DLQ。 - log.warn("Kafka envelope consumer poll failed; the worker will retry after backoff", ex); - sleepBackoff(); - } - } - } - - private void sleepBackoff() { - try { - Thread.sleep(loopBackoff.toMillis()); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } - - @Override - public void stop() { - if (!running.compareAndSet(true, false)) { - return; - } - wakeupWorkers(); - if (executor != null) { - executor.shutdown(); - try { - if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { - executor.shutdownNow(); - executor.awaitTermination(5, TimeUnit.SECONDS); - } - } catch (InterruptedException ex) { - executor.shutdownNow(); - Thread.currentThread().interrupt(); - } - } - closeWorkers(); - } - - @Override - public void stop(Runnable callback) { - stop(); - callback.run(); - } - - @Override - public boolean isRunning() { - return running.get(); - } - - @Override - public boolean isAutoStartup() { - return autoStartup; - } - - @Override - public void close() { - stop(); - closeWorkers(); - } - - private void closeWorkers() { - if (!closed.compareAndSet(false, true)) { - return; - } - List current = workers; - if (current == null) { - return; - } - for (KafkaEnvelopeConsumerWorker worker : current) { - worker.close(); - } - } - - private void wakeupWorkers() { - List current = workers; - if (current == null) { - return; - } - for (KafkaEnvelopeConsumerWorker worker : current) { - worker.wakeup(); - } - } -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorker.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorker.java deleted file mode 100644 index b1f9e9e3..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorker.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeConsumerRecord; -import org.apache.kafka.clients.consumer.Consumer; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public final class KafkaEnvelopeConsumerWorker implements AutoCloseable { - - private final Consumer consumer; - private final Map processorsByTopic; - - public KafkaEnvelopeConsumerWorker(Consumer consumer, - Map processorsByTopic) { - if (consumer == null) { - throw new IllegalArgumentException("consumer must not be null"); - } - if (processorsByTopic == null || processorsByTopic.isEmpty()) { - throw new IllegalArgumentException("processorsByTopic must not be empty"); - } - this.consumer = consumer; - this.processorsByTopic = Map.copyOf(processorsByTopic); - } - - public void subscribe(Collection topics) { - consumer.subscribe(topics); - } - - public int pollOnce(Duration timeout) { - ConsumerRecords records = consumer.poll(timeout == null ? Duration.ZERO : timeout); - Map> byProcessor = new LinkedHashMap<>(); - int processed = 0; - for (ConsumerRecord record : records) { - EnvelopeConsumerProcessor processor = processorsByTopic.get(record.topic()); - if (processor == null) { - // worker 可能订阅多个 topic;没有显式绑定处理器的 topic 不参与提交语义。 - continue; - } - byProcessor.computeIfAbsent(processor, ignored -> new ArrayList<>()).add(new EnvelopeConsumerRecord( - record.topic(), - record.partition(), - record.offset(), - record.key(), - record.value())); - processed++; - } - for (Map.Entry> entry : byProcessor.entrySet()) { - // EnvelopeConsumerProcessor 内部会把解析或业务错误转成 DLQ 记录, - // 这里保持 Kafka worker 的职责单一:轮询、分发、成功后提交 offset。 - entry.getKey().processBatch(entry.getValue()); - } - if (processed > 0) { - // commitSync 放在批次末尾,保证同一个 poll 批次内的消息按 Kafka offset 一起确认。 - consumer.commitSync(); - } - return processed; - } - - public void wakeup() { - consumer.wakeup(); - } - - @Override - public void close() { - consumer.close(); - } -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSink.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSink.java deleted file mode 100644 index 4ba2bf2c..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSink.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterRecord; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterSink; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.Producer; -import org.apache.kafka.clients.producer.ProducerRecord; - -import java.nio.charset.StandardCharsets; - -public final class KafkaEnvelopeDeadLetterSink implements EnvelopeDeadLetterSink { - - private final Producer producer; - private final String topic; - - public KafkaEnvelopeDeadLetterSink(KafkaProducer producer, String topic) { - this((Producer) producer, topic); - } - - KafkaEnvelopeDeadLetterSink(Producer producer, String topic) { - if (producer == null) { - throw new IllegalArgumentException("producer must not be null"); - } - if (topic == null || topic.isBlank()) { - throw new IllegalArgumentException("topic must not be blank"); - } - this.producer = producer; - this.topic = topic; - } - - @Override - public void publish(EnvelopeDeadLetterRecord record) { - if (record == null) { - throw new IllegalArgumentException("record must not be null"); - } - ProducerRecord out = new ProducerRecord<>(topic, record.key(), record.payload()); - // DLQ payload 保持原始 Kafka value;定位信息全部放 header,便于后续重放或人工排查。 - header(out, "dlq-service", record.service()); - header(out, "dlq-source-topic", record.topic()); - header(out, "dlq-source-partition", Integer.toString(record.partition())); - header(out, "dlq-source-offset", Long.toString(record.offset())); - header(out, "dlq-status", record.status().name()); - header(out, "dlq-event-id", record.eventId()); - header(out, "dlq-vin", record.vin()); - header(out, "dlq-message", record.message()); - header(out, "dlq-created-at", record.createdAt().toString()); - producer.send(out); - } - - private static void header(ProducerRecord record, String key, String value) { - record.headers().add(key, value.getBytes(StandardCharsets.UTF_8)); - } -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEventSink.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEventSink.java deleted file mode 100644 index cf879fce..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaEventSink.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.api.sink.EventSink; -import io.github.resilience4j.circuitbreaker.CircuitBreaker; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.CompletableFuture; - -/** - * Kafka Sink:序列化为 Protobuf Envelope,按 vin 分区,异步发送。 - * - *

    失败处理:Resilience4j 熔断;熔断开启时事件转投 DLQ topic。 - */ -public final class KafkaEventSink implements EventSink, AutoCloseable { - - private static final Logger log = LoggerFactory.getLogger(KafkaEventSink.class); - - private final KafkaProducer producer; - private final EnvelopeMapper mapper; - private final TopicRouter router; - private final String dlqTopic; - private final CircuitBreaker breaker; - - public KafkaEventSink(KafkaProducer producer, - EnvelopeMapper mapper, - TopicRouter router, - String dlqTopic, - CircuitBreaker breaker) { - this.producer = producer; - this.mapper = mapper; - this.router = router; - this.dlqTopic = dlqTopic; - this.breaker = breaker; - } - - @Override - public String name() { - return "kafka"; - } - - /** - * Split-service mode uses Kafka for both normalized events and raw archive records. - */ - @Override - public boolean accepts(VehicleEvent event) { - return true; - } - - @Override - public CompletableFuture publish(VehicleEvent event) { - CompletableFuture cf = new CompletableFuture<>(); - byte[] payload; - try { - payload = mapper.toEnvelope(event).toByteArray(); - } catch (Exception e) { - log.error("envelope build failed eventId={} vin={}", event.eventId(), event.vin(), e); - cf.completeExceptionally(e); - return cf; - } - - String topic = breaker.tryAcquirePermission() ? router.route(event) : dlqTopic; - ProducerRecord record = new ProducerRecord<>(topic, partitionKey(event), payload); - record.headers().add("event-id", event.eventId().getBytes()); - record.headers().add("trace-id", event.traceId() == null ? new byte[0] : event.traceId().getBytes()); - record.headers().add("source", event.source().name().getBytes()); - - producer.send(record, (metadata, ex) -> { - if (ex != null) { - breaker.onError(0, java.util.concurrent.TimeUnit.MILLISECONDS, ex); - cf.completeExceptionally(ex); - } else { - breaker.onSuccess(0, java.util.concurrent.TimeUnit.MILLISECONDS); - cf.complete(null); - } - }); - return cf; - } - - private static String partitionKey(VehicleEvent event) { - String vin = event.vin(); - if (isKnown(vin)) { - return vin; - } - String vehicleKey = metadata(event, "vehicleKey"); - if (!isKnown(vehicleKey)) { - vehicleKey = metadata(event, "vehicle_key"); - } - if (isKnown(vehicleKey)) { - return "vehicleKey:" + vehicleKey; - } - String phone = metadata(event, "phone"); - if (isKnown(phone)) { - return "phone:" + phone; - } - return vin == null || vin.isBlank() ? "unknown" : vin; - } - - private static String metadata(VehicleEvent event, String key) { - if (event.metadata() == null) { - return ""; - } - return event.metadata().getOrDefault(key, "").trim(); - } - - private static boolean isKnown(String value) { - return value != null && !value.isBlank() && !"unknown".equalsIgnoreCase(value.trim()); - } - - @Override - public void close() { - producer.flush(); - producer.close(); - } -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkAutoConfiguration.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkAutoConfiguration.java deleted file mode 100644 index 82a1dd91..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkAutoConfiguration.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import io.github.resilience4j.circuitbreaker.CircuitBreaker; -import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.ProducerConfig; -import org.apache.kafka.common.serialization.ByteArraySerializer; -import org.apache.kafka.common.serialization.StringSerializer; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -import java.time.Duration; -import java.util.Properties; - -/** - * Kafka Sink 自动装配。 - * - *

    {@code lingniu.ingest.sink.kafka.enabled=false} 时本模块完全不装配;Producer 和 Consumer - * 是两个独立开关,消费端还需要开启 {@code lingniu.ingest.sink.kafka.consumer.enabled=true} - * 并配置 bindings。 - */ -@AutoConfiguration -@EnableConfigurationProperties(KafkaSinkProperties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.sink.kafka", name = "enabled", havingValue = "true", matchIfMissing = true) -public class KafkaSinkAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public EnvelopeMapper envelopeMapper(KafkaSinkProperties props) { - return new EnvelopeMapper(props.getNodeId()); - } - - @Bean - @ConditionalOnMissingBean - public TopicRouter topicRouter(KafkaSinkProperties props) { - return new TopicRouter(props.getTopics()); - } - - @Bean - @ConditionalOnMissingBean - public CircuitBreaker kafkaSinkCircuitBreaker() { - return CircuitBreaker.of("kafka-sink", CircuitBreakerConfig.custom() - .slidingWindowSize(100) - .failureRateThreshold(50) - .waitDurationInOpenState(Duration.ofSeconds(10)) - .permittedNumberOfCallsInHalfOpenState(10) - .build()); - } - - @Bean(destroyMethod = "close") - @ConditionalOnMissingBean - public KafkaProducer kafkaProducer(KafkaSinkProperties props) { - Properties p = new Properties(); - p.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, props.getBootstrapServers()); - p.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); - p.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class.getName()); - p.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, props.getCompressionType()); - p.put(ProducerConfig.LINGER_MS_CONFIG, props.getLingerMs()); - p.put(ProducerConfig.BATCH_SIZE_CONFIG, props.getBatchSize()); - p.put(ProducerConfig.ACKS_CONFIG, props.getAcks()); - p.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, props.isEnableIdempotence()); - return new KafkaProducer<>(p); - } - - @Bean(destroyMethod = "close") - @ConditionalOnMissingBean - public KafkaEventSink kafkaEventSink(KafkaProducer producer, - EnvelopeMapper mapper, - TopicRouter router, - KafkaSinkProperties props, - CircuitBreaker breaker) { - // KafkaEventSink 是 EventBus 的生产端出口;它不会启动任何 Kafka 消费线程。 - return new KafkaEventSink(producer, mapper, router, props.getTopics().getDlq(), breaker); - } - - @Bean - @ConditionalOnMissingBean - public KafkaEnvelopeDeadLetterSink kafkaEnvelopeDeadLetterSink(KafkaProducer producer, - KafkaSinkProperties props) { - return new KafkaEnvelopeDeadLetterSink(producer, props.deadLetterTopic()); - } - -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfiguration.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfiguration.java deleted file mode 100644 index 602dc8ed..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -import java.time.Duration; -import java.util.List; -import java.util.Map; - -@AutoConfiguration(after = KafkaSinkAutoConfiguration.class) -@AutoConfigureAfter(name = { - "com.lingniu.ingest.eventhistory.config.EventHistoryAutoConfiguration", - "com.lingniu.ingest.vehiclestate.config.VehicleStateAutoConfiguration", - "com.lingniu.ingest.vehiclestat.config.VehicleStatAutoConfiguration" -}) -@EnableConfigurationProperties(KafkaSinkProperties.class) -@ConditionalOnProperty(prefix = "lingniu.ingest.sink.kafka", name = "enabled", havingValue = "true", matchIfMissing = true) -public class KafkaSinkConsumerAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public KafkaEnvelopeConsumerFactory kafkaEnvelopeConsumerFactory() { - return new KafkaEnvelopeConsumerFactory(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "lingniu.ingest.sink.kafka.consumer", name = "enabled", havingValue = "true") - public KafkaEnvelopeConsumerRunner kafkaEnvelopeConsumerRunner(ListableBeanFactory beanFactory, - KafkaEnvelopeConsumerFactory consumerFactory, - KafkaSinkProperties props) { - return new KafkaEnvelopeConsumerRunner( - () -> createWorkers(beanFactory, consumerFactory, props), - Duration.ofMillis(props.getConsumer().getPollTimeoutMillis()), - Duration.ofMillis(props.getConsumer().getLoopBackoffMillis()), - props.getConsumer().isAutoStartup()); - } - - private List createWorkers(ListableBeanFactory beanFactory, - KafkaEnvelopeConsumerFactory consumerFactory, - KafkaSinkProperties props) { - Map processors = beanFactory.getBeansOfType(EnvelopeConsumerProcessor.class); - return consumerFactory.createWorkers(processors, props); - } -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java deleted file mode 100644 index 69d55521..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -@ConfigurationProperties(prefix = "lingniu.ingest.sink.kafka") -public class KafkaSinkProperties { - - /** - * Kafka Sink 总开关。默认 {@code true}。 - * 设为 {@code false} 时 {@link KafkaSinkAutoConfiguration} 不装配 Kafka Producer、 - * EnvelopeMapper、TopicRouter、KafkaEventSink 等生产端组件。生产接入应用保持默认开启, - * 只有专门的查询/测试运行时才应关闭 Kafka sink。 - */ - private boolean enabled = true; - - /** Kafka bootstrap servers;生产环境应通过环境变量覆盖,不建议使用默认开发地址。 */ - private String bootstrapServers = "114.55.58.251:9092"; - private String compressionType = "zstd"; - private int lingerMs = 20; - private int batchSize = 65536; - private String acks = "all"; - private boolean enableIdempotence = true; - /** 写入 envelope 的节点标识,进入 Protobuf 字段 ingest_node_id,便于追踪多实例来源。 */ - private String nodeId = "ingest-local"; - private Topics topics = new Topics(); - private Consumer consumer = new Consumer(); - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public String getBootstrapServers() { return bootstrapServers; } - public void setBootstrapServers(String bootstrapServers) { this.bootstrapServers = bootstrapServers; } - public String getCompressionType() { return compressionType; } - public void setCompressionType(String compressionType) { this.compressionType = compressionType; } - public int getLingerMs() { return lingerMs; } - public void setLingerMs(int lingerMs) { this.lingerMs = lingerMs; } - public int getBatchSize() { return batchSize; } - public void setBatchSize(int batchSize) { this.batchSize = batchSize; } - public String getAcks() { return acks; } - public void setAcks(String acks) { this.acks = acks; } - public boolean isEnableIdempotence() { return enableIdempotence; } - public void setEnableIdempotence(boolean enableIdempotence) { this.enableIdempotence = enableIdempotence; } - public String getNodeId() { return nodeId; } - public void setNodeId(String nodeId) { this.nodeId = nodeId; } - public Topics getTopics() { return topics; } - public void setTopics(Topics topics) { this.topics = topics; } - public Consumer getConsumer() { return consumer; } - public void setConsumer(Consumer consumer) { this.consumer = consumer; } - - public String deadLetterTopic() { - String consumerDlq = consumer == null ? "" : consumer.getDlqTopic(); - if (consumerDlq != null && !consumerDlq.isBlank()) { - return consumerDlq; - } - return topics.getDlq(); - } - - public static class Topics { - /** 实时遥测事件 topic;GB32960 RAW-only 架构下可逐步弱化该 topic。 */ - private String realtime = "vehicle.event.gb32960.v1"; - /** 位置事件 topic;通常可由 realtime/RAW 派生。 */ - private String location = "vehicle.event.gb32960.v1"; - private String alarm = "vehicle.event.gb32960.v1"; - private String session = "vehicle.event.gb32960.v1"; - private String mediaMeta = "vehicle.media.meta.v1"; - /** RAW 归档引用 topic,payload 应携带 archive URI/size,不建议携带完整 raw bytes。 */ - private String rawArchive = "vehicle.raw.gb32960.v1"; - /** producer 熔断或 consumer 处理失败时的死信 topic。 */ - private String dlq = "vehicle.dlq.gb32960.v1"; - - public String getRealtime() { return realtime; } - public void setRealtime(String realtime) { this.realtime = realtime; } - public String getLocation() { return location; } - public void setLocation(String location) { this.location = location; } - public String getAlarm() { return alarm; } - public void setAlarm(String alarm) { this.alarm = alarm; } - public String getSession() { return session; } - public void setSession(String session) { this.session = session; } - public String getMediaMeta() { return mediaMeta; } - public void setMediaMeta(String mediaMeta) { this.mediaMeta = mediaMeta; } - public String getRawArchive() { return rawArchive; } - public void setRawArchive(String rawArchive) { this.rawArchive = rawArchive; } - public String getDlq() { return dlq; } - public void setDlq(String dlq) { this.dlq = dlq; } - } - - public static class Consumer { - /** Kafka 消费总开关。与 producer 总开关分离,默认 false,避免单体服务意外自消费。 */ - private boolean enabled = false; - private boolean autoStartup = true; - private String clientIdPrefix = "lingniu-envelope-consumer"; - private int pollTimeoutMillis = 1000; - private int loopBackoffMillis = 1000; - private String autoOffsetReset = "earliest"; - private int maxPollRecords = 500; - private int maxPollIntervalMillis = 1800000; - private int concurrency = 1; - /** 消费处理失败时的死信 topic;未配置时兼容使用 producer topics.dlq。 */ - private String dlqTopic = ""; - /** - * Processor bean name -> Kafka binding。 - * - *

    示例 key:{@code eventHistoryEnvelopeConsumerProcessor}、 - * {@code vehicleStateEnvelopeConsumerProcessor}、{@code vehicleStatEnvelopeConsumerProcessor}。 - */ - private Map bindings = new LinkedHashMap<>(); - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public boolean isAutoStartup() { return autoStartup; } - public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } - public String getClientIdPrefix() { return clientIdPrefix; } - public void setClientIdPrefix(String clientIdPrefix) { this.clientIdPrefix = clientIdPrefix; } - public int getPollTimeoutMillis() { return pollTimeoutMillis; } - public void setPollTimeoutMillis(int pollTimeoutMillis) { this.pollTimeoutMillis = pollTimeoutMillis; } - public int getLoopBackoffMillis() { return loopBackoffMillis; } - public void setLoopBackoffMillis(int loopBackoffMillis) { this.loopBackoffMillis = loopBackoffMillis; } - public String getAutoOffsetReset() { return autoOffsetReset; } - public void setAutoOffsetReset(String autoOffsetReset) { this.autoOffsetReset = autoOffsetReset; } - public int getMaxPollRecords() { return maxPollRecords; } - public void setMaxPollRecords(int maxPollRecords) { this.maxPollRecords = maxPollRecords; } - public int getMaxPollIntervalMillis() { return maxPollIntervalMillis; } - public void setMaxPollIntervalMillis(int maxPollIntervalMillis) { this.maxPollIntervalMillis = maxPollIntervalMillis; } - public int getConcurrency() { return concurrency; } - public void setConcurrency(int concurrency) { this.concurrency = concurrency; } - public String getDlqTopic() { return dlqTopic; } - public void setDlqTopic(String dlqTopic) { this.dlqTopic = dlqTopic; } - public Map getBindings() { return bindings; } - public void setBindings(Map bindings) { this.bindings = bindings; } - } - - public static class Binding { - /** 单个 processor binding 开关,用于临时停某个下游消费者而不关整个 consumer runner。 */ - private boolean enabled = true; - /** Kafka consumer group id。不同服务要独立消费同一 topic 时必须使用不同 group。 */ - private String groupId; - /** 该 processor 订阅的 topic 列表。 */ - private List topics = new ArrayList<>(); - - public boolean isEnabled() { return enabled; } - public void setEnabled(boolean enabled) { this.enabled = enabled; } - public String getGroupId() { return groupId; } - public void setGroupId(String groupId) { this.groupId = groupId; } - public List getTopics() { return topics; } - public void setTopics(List topics) { this.topics = topics; } - } -} diff --git a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/TopicRouter.java b/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/TopicRouter.java deleted file mode 100644 index f8c984c0..00000000 --- a/modules/sinks/sink-kafka/src/main/java/com/lingniu/ingest/sink/kafka/TopicRouter.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.event.VehicleEvent; - -/** - * 事件 → Kafka topic 路由。集中管理避免散落。 - */ -public final class TopicRouter { - - private final KafkaSinkProperties.Topics topics; - - public TopicRouter(KafkaSinkProperties.Topics topics) { - this.topics = topics; - } - - public String route(VehicleEvent event) { - return switch (event) { - case VehicleEvent.Realtime _ -> topics.getRealtime(); - case VehicleEvent.Location _ -> topics.getLocation(); - case VehicleEvent.Alarm _ -> topics.getAlarm(); - case VehicleEvent.Login _, - VehicleEvent.Logout _, - VehicleEvent.Heartbeat _ -> topics.getSession(); - case VehicleEvent.MediaMeta _ -> topics.getMediaMeta(); - case VehicleEvent.Passthrough _ -> topics.getAlarm(); - case VehicleEvent.RawArchive _ -> topics.getRawArchive(); - }; - } -} diff --git a/modules/sinks/sink-kafka/src/main/proto/vehicle_envelope.proto b/modules/sinks/sink-kafka/src/main/proto/vehicle_envelope.proto deleted file mode 100644 index 57acbda1..00000000 --- a/modules/sinks/sink-kafka/src/main/proto/vehicle_envelope.proto +++ /dev/null @@ -1,168 +0,0 @@ -syntax = "proto3"; - -package com.lingniu.ingest.sink.kafka.proto; - -option java_multiple_files = true; -option java_package = "com.lingniu.ingest.sink.kafka.proto"; -option java_outer_classname = "VehicleEnvelopeProto"; - -// 统一消息外壳:所有 Topic 共用此 Envelope,payload 通过 oneof 区分具体事件类型。 -message VehicleEnvelope { - string schema_version = 1; - string event_id = 2; - string trace_id = 3; - string vin = 4; - string source = 5; // ProtocolId 字符串形式 - string protocol_version = 6; - int64 event_time_ms = 7; - int64 ingest_time_ms = 8; - string ingest_node_id = 9; - map metadata = 10; - - oneof payload { - RealtimePayload realtime = 20; - LocationPayload location = 21; - AlarmPayload alarm = 22; - LoginPayload login = 23; - LogoutPayload logout = 24; - HeartbeatPayload heartbeat = 25; - MediaMetaPayload media_meta = 26; - PassthroughPayload passthrough = 27; - } - - // 可选:原始字节指针 - RawArchiveRef raw_archive = 40; - - // 全字段内部遥测快照。新下游消费者优先读取这里,避免依赖协议字段名。 - TelemetrySnapshot telemetry_snapshot = 50; - - // 新一代事实模型:RAW 帧索引与解析事实,均不携带完整 raw bytes。 - RawFrameFactPayload raw_frame_fact = 60; - DecodedFactPayload decoded_fact = 61; -} - -message RawArchiveRef { - string uri = 1; - string checksum = 2; - int64 size_bytes = 3; - string parsed_json = 4; -} - -message TelemetrySnapshot { - string event_type = 1; - string raw_archive_uri = 2; - repeated TelemetryField fields = 3; -} - -message TelemetryField { - string key = 1; - string value_type = 2; - string value = 3; - string unit = 4; - string quality = 5; - string source_path = 6; -} - -enum ParseStatusProto { - PARSE_STATUS_UNSPECIFIED = 0; - PARSE_STATUS_NOT_PARSED = 1; - PARSE_STATUS_SUCCEEDED = 2; - PARSE_STATUS_FAILED = 3; -} - -message RawFrameFactPayload { - string frame_id = 1; - string vehicle_key = 2; - string vin = 3; - string phone = 4; - int32 message_id = 5; - int32 sub_type = 6; - string raw_uri = 7; - string checksum = 8; - int64 raw_size_bytes = 9; - ParseStatusProto parse_status = 10; - string parse_error = 11; - string peer = 12; - map metadata = 13; -} - -message DecodedFactPayload { - string fact_id = 1; - string frame_id = 2; - string fact_type = 3; - string vehicle_key = 4; - string vin = 5; - string phone = 6; - string raw_uri = 7; - map fields = 8; - map metadata = 9; -} - -message RealtimePayload { - optional double speed_kmh = 1; - optional double total_mileage_km = 2; - optional double battery_soc = 3; - optional double battery_voltage_v = 4; - optional double battery_current_a = 5; - optional double fc_voltage_v = 6; - optional double fc_current_a = 7; - optional double fc_temp_c = 8; - optional double hydrogen_remaining_kg = 9; - optional double hydrogen_high_pressure_mpa = 10; - optional double hydrogen_low_pressure_mpa = 11; - optional string vehicle_state = 12; - optional string charging_state = 13; - optional string running_mode = 14; - optional int32 gear_level = 15; - optional double accelerator_pedal = 16; - optional double brake_pedal = 17; - optional double longitude = 18; - optional double latitude = 19; - optional double altitude_m = 20; - optional double direction_deg = 21; - optional double ambient_temp_c = 22; - optional double coolant_temp_c = 23; -} - -message LocationPayload { - double longitude = 1; - double latitude = 2; - double altitude_m = 3; - double speed_kmh = 4; - double direction_deg = 5; - int64 alarm_flag = 6; - int64 status_flag = 7; -} - -message AlarmPayload { - string level = 1; - int32 alarm_type_code = 2; - string alarm_type_name = 3; - repeated string fault_codes = 4; - optional double longitude = 5; - optional double latitude = 6; - // 通用报警标志位(2016 版 0~15 / 2025 版 0~27),对照 GB/T 32960.3 表 24。 - // 例:SOC_LOW / BATTERY_HIGH_TEMP / HYDROGEN_LEAK - repeated string active_bits = 7; -} - -message LoginPayload { - string iccid = 1; - string protocol_version = 2; -} - -message LogoutPayload {} - -message HeartbeatPayload {} - -message MediaMetaPayload { - string media_id = 1; - string media_type = 2; - int64 size_bytes = 3; - string archive_ref = 4; -} - -message PassthroughPayload { - int32 passthrough_type = 1; - bytes data = 2; -} diff --git a/modules/sinks/sink-kafka/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/sinks/sink-kafka/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 8c283ae7..00000000 --- a/modules/sinks/sink-kafka/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,2 +0,0 @@ -com.lingniu.ingest.sink.kafka.KafkaSinkAutoConfiguration -com.lingniu.ingest.sink.kafka.KafkaSinkConsumerAutoConfiguration diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/EnvelopeMapperTelemetrySnapshotTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/EnvelopeMapperTelemetrySnapshotTest.java deleted file mode 100644 index 5f700f70..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/EnvelopeMapperTelemetrySnapshotTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.RawArchiveKeys; -import com.lingniu.ingest.api.event.RealtimePayload; -import com.lingniu.ingest.api.event.VehicleEvent; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class EnvelopeMapperTelemetrySnapshotTest { - - @Test - void mapsVehicleEventToFullFieldTelemetrySnapshot() { - VehicleEvent.Realtime event = new VehicleEvent.Realtime( - "event-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-1", - Map.of("protocolVersion", "V2016", "rawArchiveUri", "archive://raw/event-1.bin"), - new RealtimePayload( - 80.5, - 12345.6, - 55.0, - 610.0, - -20.5, - null, - null, - null, - 8.2, - 35.0, - null, - RealtimePayload.VehicleState.STARTED, - RealtimePayload.ChargingState.UNCHARGED, - RealtimePayload.RunningMode.FUEL, - 3, - null, - null, - 113.12, - 23.45, - null, - null, - null, - null - ) - ); - - VehicleEnvelope envelope = new EnvelopeMapper("node-1").toEnvelope(event); - - assertThat(envelope.getTelemetrySnapshot().getEventType()).isEqualTo("REALTIME"); - assertThat(envelope.getTelemetrySnapshot().getRawArchiveUri()).isEqualTo("archive://raw/event-1.bin"); - assertThat(envelope.getTelemetrySnapshot().getFieldsList()) - .anySatisfy(field -> { - assertThat(field.getKey()).isEqualTo("total_mileage_km"); - assertThat(field.getValueType()).isEqualTo("DOUBLE"); - assertThat(field.getValue()).isEqualTo("12345.6"); - assertThat(field.getUnit()).isEqualTo("km"); - }) - .anySatisfy(field -> { - assertThat(field.getKey()).isEqualTo("hydrogen_high_pressure_mpa"); - assertThat(field.getValue()).isEqualTo("35.0"); - assertThat(field.getUnit()).isEqualTo("MPa"); - }); - } - - @Test - void rawArchiveEnvelopeCarriesLogicalArchiveUri() { - VehicleEvent.RawArchive raw = new VehicleEvent.RawArchive( - "raw-808-1", - "VIN-JT808-001", - ProtocolId.JT808, - Instant.parse("2026-06-22T08:00:00Z"), - Instant.parse("2026-06-22T08:00:01Z"), - "trace-raw-1", - Map.of(), - 0x0200, - 0, - new byte[]{0x7e, 0x02, 0x00, 0x7e}); - - VehicleEnvelope envelope = new EnvelopeMapper("node-1").toEnvelope(raw); - - String key = RawArchiveKeys.key(raw); - assertThat(envelope.hasRawArchive()).isTrue(); - assertThat(envelope.getRawArchive().getUri()).isEqualTo(RawArchiveKeys.logicalUri(key)); - assertThat(envelope.getRawArchive().getSizeBytes()).isEqualTo(4); - assertThat(envelope.getMetadataMap()).containsEntry(RawArchiveKeys.META_URI, RawArchiveKeys.logicalUri(key)); - assertThat(envelope.hasRawFrameFact()).isTrue(); - assertThat(envelope.getRawFrameFact().getFrameId()).startsWith("rf_"); - assertThat(envelope.getRawFrameFact().getVehicleKey()).isEqualTo("VIN-JT808-001"); - assertThat(envelope.getRawFrameFact().getVin()).isEqualTo("VIN-JT808-001"); - assertThat(envelope.getRawFrameFact().getMessageId()).isEqualTo(0x0200); - assertThat(envelope.getRawFrameFact().getRawUri()).isEqualTo(RawArchiveKeys.logicalUri(key)); - assertThat(envelope.getRawFrameFact().getRawSizeBytes()).isEqualTo(4); - assertThat(envelope.getRawFrameFact().getChecksum()).startsWith("sha256:"); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactoryTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactoryTest.java deleted file mode 100644 index df0b9ef0..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerFactoryTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.consumer.MockConsumer; -import org.apache.kafka.clients.consumer.OffsetResetStrategy; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Properties; - -import static org.assertj.core.api.Assertions.assertThat; - -class KafkaEnvelopeConsumerFactoryTest { - - @Test - void createsDefaultIndependentConsumersForKnownServiceProcessors() { - List created = new ArrayList<>(); - KafkaEnvelopeConsumerFactory factory = new KafkaEnvelopeConsumerFactory(props -> { - created.add(props); - return new MockConsumer<>(OffsetResetStrategy.EARLIEST); - }); - KafkaSinkProperties props = new KafkaSinkProperties(); - props.setBootstrapServers("kafka-1:9092"); - EnvelopeConsumerProcessor stateProcessor = processor(); - EnvelopeConsumerProcessor statProcessor = processor(); - - List workers = factory.createWorkers(Map.of( - "vehicleStateEnvelopeConsumerProcessor", stateProcessor, - "vehicleStatEnvelopeConsumerProcessor", statProcessor), props); - - assertThat(workers).hasSize(2); - assertThat(created) - .extracting(p -> p.getProperty(ConsumerConfig.GROUP_ID_CONFIG)) - .containsExactly("vehicle-state", "vehicle-stat"); - assertThat(created) - .allSatisfy(p -> { - assertThat(p.getProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG)).isEqualTo("kafka-1:9092"); - assertThat(p.get(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG)).isEqualTo(false); - assertThat(p.get(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG)).isEqualTo(1800000); - }); - } - - @Test - void createsParallelConsumersForEachBindingWhenConsumerConcurrencyIsConfigured() { - List created = new ArrayList<>(); - KafkaEnvelopeConsumerFactory factory = new KafkaEnvelopeConsumerFactory(props -> { - created.add(props); - return new MockConsumer<>(OffsetResetStrategy.EARLIEST); - }); - KafkaSinkProperties props = new KafkaSinkProperties(); - props.getConsumer().setConcurrency(3); - EnvelopeConsumerProcessor stateProcessor = processor(); - - List workers = factory.createWorkers(Map.of( - "vehicleStateEnvelopeConsumerProcessor", stateProcessor), props); - - assertThat(workers).hasSize(3); - assertThat(created) - .extracting(p -> p.getProperty(ConsumerConfig.GROUP_ID_CONFIG)) - .containsExactly("vehicle-state", "vehicle-state", "vehicle-state"); - assertThat(created) - .extracting(p -> p.getProperty(ConsumerConfig.CLIENT_ID_CONFIG)) - .containsExactly( - "lingniu-envelope-consumer-vehicleStateEnvelopeConsumerProcessor-0", - "lingniu-envelope-consumer-vehicleStateEnvelopeConsumerProcessor-1", - "lingniu-envelope-consumer-vehicleStateEnvelopeConsumerProcessor-2"); - } - - private EnvelopeConsumerProcessor processor() { - return new EnvelopeConsumerProcessor( - "service", - bytes -> EnvelopeIngestResult.processed("evt-1", "VIN001"), - record -> {}); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunnerTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunnerTest.java deleted file mode 100644 index 0a4d7f15..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerRunnerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; - -class KafkaEnvelopeConsumerRunnerTest { - - @Test - void stopWakesPollLoopAndClosesWorkerAfterPollThreadExits() throws Exception { - KafkaEnvelopeConsumerWorker worker = mock(KafkaEnvelopeConsumerWorker.class); - CountDownLatch pollEntered = new CountDownLatch(1); - CountDownLatch wakeupCalled = new CountDownLatch(1); - CountDownLatch pollReturned = new CountDownLatch(1); - CountDownLatch closeCalled = new CountDownLatch(1); - - doAnswer(invocation -> { - pollEntered.countDown(); - assertThat(wakeupCalled.await(1, TimeUnit.SECONDS)).isTrue(); - pollReturned.countDown(); - return 0; - }).when(worker).pollOnce(any()); - doAnswer(invocation -> { - wakeupCalled.countDown(); - return null; - }).when(worker).wakeup(); - doAnswer(invocation -> { - assertThat(pollReturned.getCount()).isZero(); - closeCalled.countDown(); - return null; - }).when(worker).close(); - KafkaEnvelopeConsumerRunner runner = new KafkaEnvelopeConsumerRunner( - List.of(worker), - Duration.ofSeconds(30), - Duration.ofMillis(1), - false); - - runner.start(); - assertThat(pollEntered.await(1, TimeUnit.SECONDS)).isTrue(); - runner.stop(); - - assertThat(wakeupCalled.getCount()).isZero(); - assertThat(closeCalled.getCount()).isZero(); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorkerTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorkerTest.java deleted file mode 100644 index 9d1a1b02..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeConsumerWorkerTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerRecord; -import com.lingniu.ingest.api.consumer.EnvelopeBatchIngestor; -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterRecord; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.MockConsumer; -import org.apache.kafka.clients.consumer.OffsetResetStrategy; -import org.apache.kafka.common.TopicPartition; -import org.junit.jupiter.api.Test; - -import java.time.Duration; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.assertj.core.api.Assertions.assertThat; - -class KafkaEnvelopeConsumerWorkerTest { - - @Test - void pollsKafkaRecordsAndDispatchesThemToConfiguredTopicProcessor() { - MockConsumer consumer = new MockConsumer<>(OffsetResetStrategy.EARLIEST); - TopicPartition partition = new TopicPartition("vehicle.realtime", 0); - consumer.assign(List.of(partition)); - consumer.updateBeginningOffsets(Map.of(partition, 0L)); - consumer.addRecord(new ConsumerRecord<>("vehicle.realtime", 0, 12L, "VIN001", new byte[]{0x01, 0x02})); - AtomicReference captured = new AtomicReference<>(); - EnvelopeConsumerProcessor processor = new EnvelopeConsumerProcessor( - "vehicle-state", - bytes -> EnvelopeIngestResult.invalid("bad envelope"), - captured::set); - - int processed = new KafkaEnvelopeConsumerWorker( - consumer, Map.of("vehicle.realtime", processor)).pollOnce(Duration.ZERO); - - assertThat(processed).isEqualTo(1); - assertThat(captured.get()).satisfies(record -> { - assertThat(record.topic()).isEqualTo("vehicle.realtime"); - assertThat(record.partition()).isEqualTo(0); - assertThat(record.offset()).isEqualTo(12L); - assertThat(record.key()).isEqualTo("VIN001"); - assertThat(record.payload()).containsExactly(0x01, 0x02); - }); - } - - @Test - void pollsKafkaRecordsAndDispatchesBatchWhenProcessorSupportsIt() { - MockConsumer consumer = new MockConsumer<>(OffsetResetStrategy.EARLIEST); - TopicPartition partition = new TopicPartition("vehicle.raw", 0); - consumer.assign(List.of(partition)); - consumer.updateBeginningOffsets(Map.of(partition, 0L)); - consumer.addRecord(new ConsumerRecord<>("vehicle.raw", 0, 12L, "VIN001", new byte[]{0x01})); - consumer.addRecord(new ConsumerRecord<>("vehicle.raw", 0, 13L, "VIN002", new byte[]{0x02})); - AtomicInteger batchCalls = new AtomicInteger(); - EnvelopeConsumerProcessor processor = new EnvelopeConsumerProcessor( - "event-history", - new EnvelopeBatchIngestor() { - @Override - public EnvelopeIngestResult tryIngest(byte[] kafkaValue) { - throw new AssertionError("single-record ingest should not be used for a batch-capable processor"); - } - - @Override - public List tryIngestAll(List kafkaValues) { - batchCalls.incrementAndGet(); - assertThat(kafkaValues).hasSize(2); - return List.of( - EnvelopeIngestResult.stored("event-1", "VIN001"), - EnvelopeIngestResult.invalid("bad envelope")); - } - }, - record -> {}); - - int processed = new KafkaEnvelopeConsumerWorker( - consumer, Map.of("vehicle.raw", processor)).pollOnce(Duration.ZERO); - - assertThat(processed).isEqualTo(2); - assertThat(batchCalls).hasValue(1); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSinkTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSinkTest.java deleted file mode 100644 index 94f51d96..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEnvelopeDeadLetterSinkTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeDeadLetterRecord; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import org.apache.kafka.clients.producer.MockProducer; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.serialization.ByteArraySerializer; -import org.apache.kafka.common.serialization.StringSerializer; -import org.junit.jupiter.api.Test; - -import java.nio.charset.StandardCharsets; -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class KafkaEnvelopeDeadLetterSinkTest { - - @Test - void publishesDeadLetterRecordToConfiguredDlqTopicWithDiagnosticHeaders() { - MockProducer producer = new MockProducer<>( - true, new StringSerializer(), new ByteArraySerializer()); - KafkaEnvelopeDeadLetterSink sink = new KafkaEnvelopeDeadLetterSink(producer, "vehicle.consumer.dlq"); - EnvelopeDeadLetterRecord record = new EnvelopeDeadLetterRecord( - "event-history", - "vehicle.realtime", - 3, - 99L, - "VIN001", - EnvelopeIngestResult.Status.INVALID_ENVELOPE, - "", - "", - "VehicleEnvelope bytes are invalid", - new byte[]{0x01, 0x02}, - Instant.parse("2026-06-22T08:00:00Z")); - - sink.publish(record); - - assertThat(producer.history()).hasSize(1); - ProducerRecord sent = producer.history().getFirst(); - assertThat(sent.topic()).isEqualTo("vehicle.consumer.dlq"); - assertThat(sent.key()).isEqualTo("VIN001"); - assertThat(sent.value()).containsExactly(0x01, 0x02); - assertThat(header(sent, "dlq-service")).isEqualTo("event-history"); - assertThat(header(sent, "dlq-source-topic")).isEqualTo("vehicle.realtime"); - assertThat(header(sent, "dlq-source-partition")).isEqualTo("3"); - assertThat(header(sent, "dlq-source-offset")).isEqualTo("99"); - assertThat(header(sent, "dlq-status")).isEqualTo("INVALID_ENVELOPE"); - assertThat(header(sent, "dlq-message")).isEqualTo("VehicleEnvelope bytes are invalid"); - } - - private static String header(ProducerRecord record, String key) { - return new String(record.headers().lastHeader(key).value(), StandardCharsets.UTF_8); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEventSinkTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEventSinkTest.java deleted file mode 100644 index 2aa77fc9..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaEventSinkTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.LocationPayload; -import com.lingniu.ingest.api.event.VehicleEvent; -import io.github.resilience4j.circuitbreaker.CircuitBreaker; -import org.apache.kafka.clients.producer.Callback; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class KafkaEventSinkTest { - - @Test - void acceptsRawArchiveRecordsForSplitServiceKafkaBoundary() { - KafkaEventSink sink = new KafkaEventSink( - null, - new EnvelopeMapper("node-1"), - new TopicRouter(new KafkaSinkProperties.Topics()), - "vehicle.dlq.gb32960.v1", - CircuitBreaker.ofDefaults("kafka-test")); - - assertThat(sink.accepts(rawArchive())).isTrue(); - } - - @Test - void usesPhoneAsKafkaKeyWhenVinIsUnknown() { - @SuppressWarnings("unchecked") - KafkaProducer producer = mock(KafkaProducer.class); - @SuppressWarnings("unchecked") - ArgumentCaptor> sent = ArgumentCaptor.forClass(ProducerRecord.class); - when(producer.send(sent.capture(), any())).thenAnswer(invocation -> { - invocation.getArgument(1).onCompletion(null, null); - return CompletableFuture.completedFuture(null); - }); - KafkaEventSink sink = new KafkaEventSink( - producer, - new EnvelopeMapper("node-1"), - new TopicRouter(new KafkaSinkProperties.Topics()), - "vehicle.dlq.gb32960.v1", - CircuitBreaker.ofDefaults("kafka-test")); - - sink.publish(jt808Location()).join(); - - assertThat(sent.getValue().key()).isEqualTo("phone:13079979000"); - } - - private static VehicleEvent.RawArchive rawArchive() { - return new VehicleEvent.RawArchive( - "raw-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-raw-1", - Map.of("platformAccount", "Hyundai"), - 0x02, - 0, - new byte[] {0x23, 0x23}); - } - - private static VehicleEvent.Location jt808Location() { - return new VehicleEvent.Location( - "loc-1", - "unknown", - ProtocolId.JT808, - Instant.parse("2026-06-29T11:22:00Z"), - Instant.parse("2026-06-29T11:22:01Z"), - "trace-loc-1", - Map.of("phone", "13079979000"), - new LocationPayload(121.1, 31.2, 8.0, 40.0, 90.0, 0L, 1L, null)); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfigurationTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfigurationTest.java deleted file mode 100644 index b4d680c5..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkConsumerAutoConfigurationTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.consumer.EnvelopeConsumerProcessor; -import com.lingniu.ingest.api.consumer.EnvelopeIngestResult; -import org.apache.kafka.clients.consumer.MockConsumer; -import org.apache.kafka.clients.consumer.OffsetResetStrategy; -import org.apache.kafka.clients.producer.KafkaProducer; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; - -import java.lang.reflect.Field; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -@ExtendWith(OutputCaptureExtension.class) -class KafkaSinkConsumerAutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withUserConfiguration(KafkaSinkAutoConfiguration.class, KafkaSinkConsumerAutoConfiguration.class) - .withAllowBeanDefinitionOverriding(true) - .withBean("vehicleStateEnvelopeConsumerProcessor", EnvelopeConsumerProcessor.class, - () -> new EnvelopeConsumerProcessor( - "vehicle-state", - bytes -> EnvelopeIngestResult.processed("evt-1", "VIN001"), - record -> {})) - .withBean("kafkaProducer", KafkaProducer.class, KafkaSinkConsumerAutoConfigurationTest::kafkaProducer) - .withBean(KafkaEnvelopeConsumerFactory.class, - () -> new KafkaEnvelopeConsumerFactory(props -> new MockConsumer<>(OffsetResetStrategy.EARLIEST))) - .withPropertyValues( - "lingniu.ingest.sink.kafka.enabled=true", - "lingniu.ingest.sink.kafka.consumer.enabled=true", - "lingniu.ingest.sink.kafka.consumer.auto-startup=false", - "lingniu.ingest.sink.kafka.consumer.bindings.vehicleStateEnvelopeConsumerProcessor.group-id=vehicle-state", - "lingniu.ingest.sink.kafka.consumer.bindings.vehicleStateEnvelopeConsumerProcessor.topics[0]=vehicle.realtime"); - - @Test - void createsKafkaEnvelopeConsumerRunnerWhenConsumerBindingIsConfigured(CapturedOutput output) { - contextRunner.run(context -> { - assertThat(context).hasSingleBean(KafkaEnvelopeConsumerRunner.class); - assertThat(context.getBean(KafkaEnvelopeConsumerRunner.class).workers()).hasSize(1); - }); - assertThat(output).doesNotContain("114.55.58.251"); - } - - @Test - void consumerDlqTopicOverridesProducerTopicDefaultsForDeadLetterSink() { - contextRunner - .withPropertyValues("lingniu.ingest.sink.kafka.consumer.dlq-topic=vehicle.dlq.consumer.v1") - .run(context -> { - assertThat(context).hasSingleBean(KafkaEnvelopeDeadLetterSink.class); - assertThat(deadLetterTopic(context.getBean(KafkaEnvelopeDeadLetterSink.class))) - .isEqualTo("vehicle.dlq.consumer.v1"); - }); - } - - @SuppressWarnings("unchecked") - private static KafkaProducer kafkaProducer() { - return mock(KafkaProducer.class); - } - - private static String deadLetterTopic(KafkaEnvelopeDeadLetterSink sink) { - try { - Field field = KafkaEnvelopeDeadLetterSink.class.getDeclaredField("topic"); - field.setAccessible(true); - return (String) field.get(sink); - } catch (ReflectiveOperationException e) { - throw new AssertionError(e); - } - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkPropertiesTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkPropertiesTest.java deleted file mode 100644 index 400f4f9f..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/KafkaSinkPropertiesTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import org.junit.jupiter.api.Test; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; - -class KafkaSinkPropertiesTest { - - @Test - void defaultKafkaBrokerUsesProductionAddressButCanStillBeOverriddenByConfigBinding() { - KafkaSinkProperties props = new KafkaSinkProperties(); - - assertThat(props.getBootstrapServers()).isEqualTo("114.55.58.251:9092"); - - props.setBootstrapServers("kafka.internal:9092"); - assertThat(props.getBootstrapServers()).isEqualTo("kafka.internal:9092"); - } - - @Test - void exposesNoBackendTypeBecauseKafkaIsTheOnlySink() { - assertThat(Arrays.stream(KafkaSinkProperties.class.getMethods()).map(method -> method.getName())) - .doesNotContain("getType", "setType"); - } - - @Test - void sourceCommentsDoNotDescribeKafkaDisabledAsNoopEventDropping() throws Exception { - String source = Files.readString(Path.of( - "src/main/java/com/lingniu/ingest/sink/kafka/KafkaSinkProperties.java")); - - assertThat(source) - .doesNotContain("Noop") - .doesNotContain("吞掉") - .doesNotContain("外部 sink"); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java deleted file mode 100644 index 7642426d..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/TopicRouterTest.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.api.ProtocolId; -import com.lingniu.ingest.api.event.AlarmPayload; -import com.lingniu.ingest.api.event.LocationPayload; -import com.lingniu.ingest.api.event.RealtimePayload; -import com.lingniu.ingest.api.event.VehicleEvent; -import org.junit.jupiter.api.Test; - -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static org.assertj.core.api.Assertions.assertThat; - -class TopicRouterTest { - - @Test - void rawArchiveRoutesToVersionedGb32960RawTopicByDefault() { - TopicRouter router = new TopicRouter(new KafkaSinkProperties.Topics()); - - assertThat(router.route(rawArchive())).isEqualTo("vehicle.raw.gb32960.v1"); - } - - @Test - void normalizedGb32960EventsRouteToVersionedEventTopicByDefault() { - TopicRouter router = new TopicRouter(new KafkaSinkProperties.Topics()); - - assertThat(router.route(realtime())).isEqualTo("vehicle.event.gb32960.v1"); - assertThat(router.route(location())).isEqualTo("vehicle.event.gb32960.v1"); - assertThat(router.route(alarm())).isEqualTo("vehicle.event.gb32960.v1"); - assertThat(router.route(login())).isEqualTo("vehicle.event.gb32960.v1"); - assertThat(router.route(logout())).isEqualTo("vehicle.event.gb32960.v1"); - assertThat(router.route(heartbeat())).isEqualTo("vehicle.event.gb32960.v1"); - } - - @Test - void dlqDefaultsToVersionedGb32960DlqTopic() { - KafkaSinkProperties.Topics topics = new KafkaSinkProperties.Topics(); - - assertThat(topics.getDlq()).isEqualTo("vehicle.dlq.gb32960.v1"); - } - - private static VehicleEvent.RawArchive rawArchive() { - return new VehicleEvent.RawArchive( - "raw-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-raw-1", - Map.of("platformAccount", "Hyundai"), - 0x02, - 0, - new byte[] {0x23, 0x23}); - } - - private static VehicleEvent.Realtime realtime() { - return new VehicleEvent.Realtime( - "event-1", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-1", - Map.of("platformAccount", "Hyundai"), - new RealtimePayload( - 42.1, 1234.5, 80.0, null, null, - null, null, null, null, null, null, - RealtimePayload.VehicleState.STARTED, - RealtimePayload.ChargingState.UNCHARGED, - RealtimePayload.RunningMode.ELECTRIC, - null, null, null, - 113.12, 23.45, null, null, null, null)); - } - - private static VehicleEvent.Location location() { - return new VehicleEvent.Location( - "event-2", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-2", - Map.of("platformAccount", "Hyundai"), - new LocationPayload(113.12, 23.45, 0, 42.1, 90, 0, 0)); - } - - private static VehicleEvent.Alarm alarm() { - return new VehicleEvent.Alarm( - "event-3", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-3", - Map.of("platformAccount", "Hyundai"), - new AlarmPayload( - AlarmPayload.AlarmLevel.MINOR, - 1, - "GB32960_ALARM", - List.of("1"), - Set.of("bit0"), - 113.12, - 23.45, - AlarmPayload.SafetyCategory.GENERAL, - false, - AlarmPayload.HydrogenLeakLevel.NONE, - false)); - } - - private static VehicleEvent.Login login() { - return new VehicleEvent.Login( - "event-4", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-4", - Map.of("platformAccount", "Hyundai"), - "iccid-1", - "V2016"); - } - - private static VehicleEvent.Logout logout() { - return new VehicleEvent.Logout( - "event-5", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-5", - Map.of("platformAccount", "Hyundai")); - } - - private static VehicleEvent.Heartbeat heartbeat() { - return new VehicleEvent.Heartbeat( - "event-6", - "VIN001", - ProtocolId.GB32960, - Instant.parse("2026-06-23T07:00:00Z"), - Instant.parse("2026-06-23T07:00:01Z"), - "trace-6", - Map.of("platformAccount", "Hyundai")); - } -} diff --git a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/VehicleEnvelopeProtoCompatibilityTest.java b/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/VehicleEnvelopeProtoCompatibilityTest.java deleted file mode 100644 index 405031e2..00000000 --- a/modules/sinks/sink-kafka/src/test/java/com/lingniu/ingest/sink/kafka/VehicleEnvelopeProtoCompatibilityTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.lingniu.ingest.sink.kafka; - -import com.lingniu.ingest.sink.kafka.proto.DecodedFactPayload; -import com.lingniu.ingest.sink.kafka.proto.ParseStatusProto; -import com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -class VehicleEnvelopeProtoCompatibilityTest { - - @Test - void rawFrameFactPayloadRoundTripsThroughEnvelope() throws Exception { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setSchemaVersion("2.0") - .setEventId("frame-1") - .setVin("VIN001") - .setSource("GB32960") - .setEventTimeMs(1782662400000L) - .setIngestTimeMs(1782662400001L) - .setRawFrameFact(RawFrameFactPayload.newBuilder() - .setFrameId("frame-1") - .setVehicleKey("VIN001") - .setMessageId(2) - .setRawUri("archive://raw/2026/06/29/GB32960/VIN001/frame-1.bin") - .setChecksum("sha256:abc") - .setRawSizeBytes(128) - .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED) - .build()) - .build(); - - VehicleEnvelope parsed = VehicleEnvelope.parseFrom(envelope.toByteArray()); - - assertThat(parsed.hasRawFrameFact()).isTrue(); - assertThat(parsed.getRawFrameFact().getFrameId()).isEqualTo("frame-1"); - assertThat(parsed.getRawFrameFact().getParseStatus()).isEqualTo(ParseStatusProto.PARSE_STATUS_SUCCEEDED); - } - - @Test - void decodedFactPayloadRoundTripsThroughEnvelope() throws Exception { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setSchemaVersion("2.0") - .setEventId("fact-1") - .setVin("VIN001") - .setSource("JT808") - .setEventTimeMs(1782662400000L) - .setIngestTimeMs(1782662400001L) - .setDecodedFact(DecodedFactPayload.newBuilder() - .setFactId("fact-1") - .setFrameId("frame-1") - .setFactType("location") - .setVehicleKey("jt808:013912345678") - .setRawUri("archive://raw/2026/06/29/JT808/jt808_013912345678/frame-1.bin") - .putFields("longitude", "120.1") - .putFields("latitude", "30.1") - .build()) - .build(); - - VehicleEnvelope parsed = VehicleEnvelope.parseFrom(envelope.toByteArray()); - - assertThat(parsed.hasDecodedFact()).isTrue(); - assertThat(parsed.getDecodedFact().getFieldsMap()).containsEntry("longitude", "120.1"); - } -} diff --git a/modules/sinks/tdengine-history-store/pom.xml b/modules/sinks/tdengine-history-store/pom.xml deleted file mode 100644 index be3ea689..00000000 --- a/modules/sinks/tdengine-history-store/pom.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - tdengine-history-store - tdengine-history-store - TDengine-backed hot history store schema and Kafka envelope mapping. - - - - com.lingniu.ingest - sink-kafka - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-autoconfigure - - - com.taosdata.jdbc - taos-jdbcdriver - - - com.zaxxer - HikariCP - - - org.springframework.boot - spring-boot-configuration-processor - true - - - com.fasterxml.jackson.core - jackson-databind - - - org.junit.jupiter - junit-jupiter - test - - - org.assertj - assertj-core - test - - - org.springframework.boot - spring-boot-starter-test - test - - - diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineBatchStatement.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineBatchStatement.java deleted file mode 100644 index d634ae4d..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineBatchStatement.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.util.List; -import java.util.ArrayList; -import java.util.Collections; - -public record TdengineBatchStatement( - String createChildTableSql, - String insertSql, - List values -) { - public TdengineBatchStatement { - values = values == null ? List.of() : Collections.unmodifiableList(new ArrayList<>(values)); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRows.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRows.java deleted file mode 100644 index b0d11c03..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRows.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.lingniu.ingest.sink.kafka.proto.LocationPayload; -import com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; - -import java.time.Instant; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - -public final class TdengineEnvelopeRows { - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - private TdengineEnvelopeRows() { - } - - public static Optional rawFrame(VehicleEnvelope envelope) { - if (envelope == null || !envelope.hasRawFrameFact()) { - return Optional.empty(); - } - RawFrameFactPayload raw = envelope.getRawFrameFact(); - Map metadata = new LinkedHashMap<>(); - metadata.putAll(envelope.getMetadataMap()); - metadata.putAll(raw.getMetadataMap()); - return Optional.of(new TdengineRawFrameRow( - rawFrameTs(envelope, metadata), - raw.getFrameId(), - instant(envelope.getIngestTimeMs()), - raw.getMessageId(), - raw.getSubType(), - instant(envelope.getEventTimeMs()), - raw.getRawUri(), - raw.getChecksum(), - raw.getRawSizeBytes(), - raw.getParseStatus().name().replace("PARSE_STATUS_", ""), - raw.getParseError(), - raw.getPeer(), - json(metadata), - parsedJson(envelope, metadata), - protocol(envelope), - vehicleKey(envelope, raw.getVehicleKey(), raw.getPhone()), - firstNonBlank(raw.getVin(), envelope.getVin()), - raw.getPhone() - )); - } - - public static Optional location(VehicleEnvelope envelope) { - if (envelope == null || !envelope.hasLocation()) { - return Optional.empty(); - } - LocationPayload location = envelope.getLocation(); - String rawUri = firstNonBlank( - envelope.hasRawArchive() ? envelope.getRawArchive().getUri() : "", - envelope.getMetadataOrDefault("rawArchiveUri", "")); - String frameId = envelope.getMetadataOrDefault("frame_id", envelope.getEventId()); - return Optional.of(new TdengineLocationRow( - instant(envelope.getEventTimeMs()), - envelope.getEventId(), - frameId, - instant(envelope.getIngestTimeMs()), - location.getLongitude(), - location.getLatitude(), - location.getAltitudeM(), - location.getSpeedKmh(), - location.getDirectionDeg(), - location.getAlarmFlag(), - location.getStatusFlag(), - totalMileage(envelope), - rawUri, - protocol(envelope), - vehicleKey(envelope, envelope.getMetadataOrDefault("vehicle_key", ""), - envelope.getMetadataOrDefault("phone", "")), - envelope.getVin(), - envelope.getMetadataOrDefault("phone", "") - )); - } - - private static Instant instant(long epochMillis) { - return Instant.ofEpochMilli(epochMillis); - } - - private static Instant rawFrameTs(VehicleEnvelope envelope, Map metadata) { - String rawArchiveEventId = firstNonBlank(metadata.get("rawArchiveEventId"), envelope.getEventId()); - if (rawArchiveEventId == null || rawArchiveEventId.isBlank()) { - return instant(envelope.getEventTimeMs()); - } - try { - long sequenceId = Long.parseLong(rawArchiveEventId); - long baseMillis = sequenceId / 1000L; - long sameMillisecondOffset = sequenceId % 1000L; - if (Math.abs(baseMillis - envelope.getEventTimeMs()) > 60_000L) { - return instant(envelope.getEventTimeMs()); - } - return Instant.ofEpochMilli(baseMillis + sameMillisecondOffset); - } catch (NumberFormatException ignored) { - return instant(envelope.getEventTimeMs()); - } - } - - private static String protocol(VehicleEnvelope envelope) { - return firstNonBlank(envelope.getSource(), "UNKNOWN"); - } - - private static String vehicleKey(VehicleEnvelope envelope, String explicitVehicleKey, String phone) { - String key = firstKnownNonBlank(explicitVehicleKey, envelope.getMetadataOrDefault("vehicle_key", ""), - envelope.getVin()); - if (!key.isBlank()) { - return key; - } - if ("JT808".equalsIgnoreCase(protocol(envelope))) { - String phoneValue = firstKnownNonBlank(phone, envelope.getMetadataOrDefault("phone", "")); - if (!phoneValue.isBlank()) { - return phoneValue.startsWith("jt808:") ? phoneValue : "jt808:" + phoneValue; - } - } - return firstNonBlank(explicitVehicleKey, envelope.getMetadataOrDefault("vehicle_key", ""), - envelope.getVin(), phone); - } - - private static Double totalMileage(VehicleEnvelope envelope) { - String value = envelope.getMetadataOrDefault("total_mileage_km", ""); - if (value.isBlank()) { - return null; - } - return Double.parseDouble(value); - } - - private static String parsedJson(VehicleEnvelope envelope, Map metadata) { - if (envelope.hasRawArchive() && !envelope.getRawArchive().getParsedJson().isBlank()) { - return envelope.getRawArchive().getParsedJson(); - } - return firstNonBlank(metadata.get("parsedJson"), metadata.get("parsed_json")); - } - - private static String json(Map metadata) { - try { - return OBJECT_MAPPER.writeValueAsString(metadata == null ? Map.of() : metadata); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("metadata cannot be serialized as json", e); - } - } - - private static String firstNonBlank(String... values) { - for (String value : values) { - if (value != null && !value.isBlank()) { - return value; - } - } - return ""; - } - - private static String firstKnownNonBlank(String... values) { - for (String value : values) { - if (value != null && !value.isBlank() && !"unknown".equalsIgnoreCase(value.trim())) { - return value; - } - } - return ""; - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueries.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueries.java deleted file mode 100644 index 6bc889c3..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueries.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.util.ArrayList; -import java.util.List; - -public final class TdengineHistoryQueries { - - private static final String RAW_COLUMNS = "ts, frame_id, received_at, message_id, sub_type, event_time, " - + "raw_uri, checksum, raw_size_bytes, parse_status, parse_error, peer, metadata_json, parsed_json, " - + "protocol, vehicle_key, vin, phone"; - private static final String LOCATION_COLUMNS = "ts, fact_id, frame_id, received_at, longitude, latitude, " - + "altitude_m, speed_kmh, direction_deg, alarm_flag, status_flag, total_mileage_km, raw_uri, " - + "protocol, vehicle_key, vin, phone"; - - private final TdengineHistorySchema schema; - - public TdengineHistoryQueries(TdengineHistorySchema schema) { - if (schema == null) { - throw new IllegalArgumentException("schema must not be null"); - } - this.schema = schema; - } - - public TdengineQueryStatement rawFrames(TdengineRawFrameQuery query) { - if (query.childTableQuery()) { - String table = schema.rawFrameTable(query.protocol(), query.vehicleKey()); - return query(table, RAW_COLUMNS, "frame_id", query.from(), query.to(), - query.order(), query.limit(), query.cursor()); - } - return rawFramesStable(query); - } - - public TdengineQueryStatement locations(TdengineLocationQuery query) { - String table = schema.locationTable(query.protocol(), query.vehicleKey()); - return query(table, LOCATION_COLUMNS, "fact_id", query.from(), query.to(), - query.order(), query.limit(), query.cursor()); - } - - public TdengineQueryStatement locationsByRawUri(String protocol, String rawUri, int limit) { - return byRawUri(schema.locationsStableTable(), LOCATION_COLUMNS, protocol, rawUri, "fact_id", limit); - } - - private static TdengineQueryStatement query(String table, - String columns, - String tieBreakerColumn, - Object from, - Object to, - TdengineQueryOrder order, - int limit, - TdenginePageCursor cursor) { - List values = new ArrayList<>(); - StringBuilder sql = new StringBuilder(256) - .append("SELECT ").append(columns) - .append(" FROM ").append(table) - .append(" WHERE ts >= ? AND ts < ?"); - values.add(from); - values.add(to); - if (cursor != null) { - String op = order == TdengineQueryOrder.ASC ? ">" : "<"; - sql.append(" AND (ts ").append(op).append(" ? OR (ts = ? AND ") - .append(tieBreakerColumn).append(' ').append(op).append(" ?))"); - values.add(cursor.ts()); - values.add(cursor.ts()); - values.add(cursor.tieBreaker()); - } - sql.append(" ORDER BY ts ").append(order.name()) - .append(", ").append(tieBreakerColumn).append(' ').append(order.name()) - .append(" LIMIT ?"); - values.add(limit); - return new TdengineQueryStatement(sql.toString(), values); - } - - private TdengineQueryStatement rawFramesStable(TdengineRawFrameQuery query) { - List values = new ArrayList<>(); - StringBuilder sql = new StringBuilder(384) - .append("SELECT ").append(RAW_COLUMNS) - .append(" FROM ").append(schema.rawFramesStableTable()) - .append(" WHERE ts >= ? AND ts < ?"); - values.add(query.from()); - values.add(query.to()); - addFilter(sql, values, "protocol", query.protocol()); - addFilter(sql, values, "vehicle_key", query.vehicleKey()); - addFilter(sql, values, "vin", query.vin()); - addFilter(sql, values, "phone", query.phone()); - if (query.messageId() != null) { - sql.append(" AND message_id = ?"); - values.add(query.messageId()); - } - addFilter(sql, values, "parse_status", query.parseStatus()); - if (query.cursor() != null) { - String op = query.order() == TdengineQueryOrder.ASC ? ">" : "<"; - sql.append(" AND (ts ").append(op).append(" ? OR (ts = ? AND frame_id ") - .append(op).append(" ?))"); - values.add(query.cursor().ts()); - values.add(query.cursor().ts()); - values.add(query.cursor().tieBreaker()); - } - sql.append(" ORDER BY ts ").append(query.order().name()) - .append(", frame_id ").append(query.order().name()) - .append(" LIMIT ?"); - values.add(query.limit()); - return new TdengineQueryStatement(sql.toString(), values); - } - - private static void addFilter(StringBuilder sql, List values, String column, String value) { - if (value != null && !value.isBlank()) { - sql.append(" AND ").append(column).append(" = ?"); - values.add(value); - } - } - - private static TdengineQueryStatement byRawUri(String table, - String columns, - String protocol, - String rawUri, - String tieBreakerColumn, - int limit) { - List values = new ArrayList<>(); - StringBuilder sql = new StringBuilder(256) - .append("SELECT ").append(columns) - .append(" FROM ").append(table) - .append(" WHERE raw_uri = ?"); - values.add(rawUri); - addFilter(sql, values, "protocol", protocol == null ? "" : protocol.trim().toUpperCase()); - sql.append(" ORDER BY ts DESC, ").append(tieBreakerColumn).append(" DESC LIMIT ?"); - values.add(Math.max(1, Math.min(limit, 1000))); - return new TdengineQueryStatement(sql.toString(), values); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryReader.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryReader.java deleted file mode 100644 index 5044a28c..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryReader.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.io.IOException; -import java.util.List; - -public interface TdengineHistoryReader { - - TdenginePage queryRawFrames(TdengineRawFrameQuery query) throws IOException; - - TdenginePage queryLocations(TdengineLocationQuery query) throws IOException; - - default List queryLocationsByRawUri(String protocol, String rawUri, int limit) - throws IOException { - return List.of(); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchema.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchema.java deleted file mode 100644 index 4e135832..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchema.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.util.List; - -/** - * SQL model for the TDengine hot history store. - */ -public final class TdengineHistorySchema { - - private final String database; - - public TdengineHistorySchema(String database) { - this.database = TdengineIdentifier.database(database); - } - - public List bootstrapSql() { - return List.of( - "CREATE DATABASE IF NOT EXISTS " + database + " PRECISION 'ms'", - "USE " + database, - rawFramesStableSql(), - vehicleLocationsStableSql() - ); - } - - public String rawFrameTable(String protocol, String vehicleKey) { - return "raw_" + TdengineIdentifier.fragment(protocol) + "_" + TdengineIdentifier.hash16(vehicleKey); - } - - public String rawFramesStableTable() { - return "raw_frames"; - } - - public String locationsStableTable() { - return "vehicle_locations"; - } - - public String locationTable(String protocol, String vehicleKey) { - return "loc_" + TdengineIdentifier.fragment(protocol) + "_" + TdengineIdentifier.hash16(vehicleKey); - } - - private static String rawFramesStableSql() { - return """ - CREATE STABLE IF NOT EXISTS raw_frames ( - ts TIMESTAMP, - frame_id NCHAR(64), - received_at TIMESTAMP, - message_id INT, - sub_type INT, - event_time TIMESTAMP, - raw_uri NCHAR(512), - checksum NCHAR(128), - raw_size_bytes BIGINT, - parse_status NCHAR(16), - parse_error NCHAR(512), - peer NCHAR(128), - metadata_json NCHAR(4096), - parsed_json VARCHAR(16374) - ) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) - )"""; - } - - private static String vehicleLocationsStableSql() { - return """ - CREATE STABLE IF NOT EXISTS vehicle_locations ( - ts TIMESTAMP, - fact_id NCHAR(64), - frame_id NCHAR(64), - received_at TIMESTAMP, - longitude DOUBLE, - latitude DOUBLE, - altitude_m DOUBLE, - speed_kmh DOUBLE, - direction_deg DOUBLE, - alarm_flag BIGINT, - status_flag BIGINT, - total_mileage_km DOUBLE, - raw_uri NCHAR(512) - ) TAGS ( - protocol NCHAR(16), - vehicle_key NCHAR(128), - vin NCHAR(64), - phone NCHAR(32) - )"""; - } - -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatements.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatements.java deleted file mode 100644 index af5aaefe..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatements.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.util.List; -import java.util.Arrays; - -public final class TdengineHistoryStatements { - - private static final String RAW_FRAME_COLUMNS = "ts, frame_id, received_at, message_id, sub_type, event_time, " - + "raw_uri, checksum, raw_size_bytes, parse_status, parse_error, peer, metadata_json, parsed_json"; - private static final String RAW_FRAME_PLACEHOLDERS = "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?"; - - private static final String LOCATION_COLUMNS = "ts, fact_id, frame_id, received_at, longitude, latitude, " - + "altitude_m, speed_kmh, direction_deg, alarm_flag, status_flag, total_mileage_km, raw_uri"; - private static final String LOCATION_PLACEHOLDERS = "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?"; - - private final TdengineHistorySchema schema; - - public TdengineHistoryStatements(TdengineHistorySchema schema) { - if (schema == null) { - throw new IllegalArgumentException("schema must not be null"); - } - this.schema = schema; - } - - public TdengineBatchStatement rawFrame(TdengineRawFrameRow row) { - String table = schema.rawFrameTable(row.protocol(), row.vehicleKey()); - return new TdengineBatchStatement( - "CREATE TABLE IF NOT EXISTS " + table + " USING raw_frames TAGS (" - + tags(row.protocol(), row.vehicleKey(), row.vin(), row.phone()) + ")", - "INSERT INTO " + table + " (" + RAW_FRAME_COLUMNS + ") VALUES (" + RAW_FRAME_PLACEHOLDERS + ")", - values( - row.ts(), row.frameId(), row.receivedAt(), row.messageId(), row.subType(), - row.eventTime(), row.rawUri(), row.checksum(), row.rawSizeBytes(), row.parseStatus(), - row.parseError(), row.peer(), row.metadataJson(), row.parsedJson() - ) - ); - } - - public TdengineBatchStatement location(TdengineLocationRow row) { - String table = schema.locationTable(row.protocol(), row.vehicleKey()); - return new TdengineBatchStatement( - "CREATE TABLE IF NOT EXISTS " + table + " USING vehicle_locations TAGS (" - + tags(row.protocol(), row.vehicleKey(), row.vin(), row.phone()) + ")", - "INSERT INTO " + table + " (" + LOCATION_COLUMNS + ") VALUES (" + LOCATION_PLACEHOLDERS + ")", - values( - row.ts(), row.factId(), row.frameId(), row.receivedAt(), row.longitude(), row.latitude(), - row.altitudeM(), row.speedKmh(), row.directionDeg(), row.alarmFlag(), row.statusFlag(), - row.totalMileageKm(), row.rawUri() - ) - ); - } - - private static String tags(String protocol, String vehicleKey, String vin, String phone) { - return quote(protocol) + ", " + quote(vehicleKey) + ", " + quote(vin) + ", " + quote(phone); - } - - private static String quote(String value) { - return "'" + (value == null ? "" : value.replace("'", "''")) + "'"; - } - - private static List values(Object... values) { - return Arrays.asList(values); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryWriter.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryWriter.java deleted file mode 100644 index e8d51a7c..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryWriter.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.io.IOException; -import java.util.List; - -public interface TdengineHistoryWriter { - - default void appendRawFrame(TdengineRawFrameRow row) throws IOException { - appendRawFrames(List.of(row)); - } - - void appendRawFrames(List rows) throws IOException; - - default void appendLocation(TdengineLocationRow row) throws IOException { - appendLocations(List.of(row)); - } - - void appendLocations(List rows) throws IOException; -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineIdentifier.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineIdentifier.java deleted file mode 100644 index 5f1d730a..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineIdentifier.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HexFormat; -import java.util.Locale; - -final class TdengineIdentifier { - - private TdengineIdentifier() { - } - - static String database(String value) { - String fragment = fragment(value); - if (fragment.isBlank()) { - throw new IllegalArgumentException("database must contain identifier characters"); - } - return fragment; - } - - static String fragment(String value) { - if (value == null) { - return "unknown"; - } - String normalized = value.trim().toLowerCase(Locale.ROOT) - .replaceAll("[^a-z0-9_]+", "_") - .replaceAll("_+", "_") - .replaceAll("^_|_$", ""); - return normalized.isBlank() ? "unknown" : normalized; - } - - static String hash16(String value) { - try { - byte[] digest = MessageDigest.getInstance("SHA-256") - .digest(String.valueOf(value).getBytes(StandardCharsets.UTF_8)); - return HexFormat.of().formatHex(digest, 0, 8); - } catch (NoSuchAlgorithmException e) { - throw new IllegalStateException("SHA-256 is not available", e); - } - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReader.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReader.java deleted file mode 100644 index cf3897e5..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReader.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import javax.sql.DataSource; -import java.io.IOException; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -public final class TdengineJdbcHistoryReader implements TdengineHistoryReader { - - private final DataSource dataSource; - private final TdengineHistoryQueries queries; - - public TdengineJdbcHistoryReader(DataSource dataSource, TdengineHistorySchema schema) { - if (dataSource == null) { - throw new IllegalArgumentException("dataSource must not be null"); - } - this.dataSource = dataSource; - this.queries = new TdengineHistoryQueries(schema); - } - - @Override - public TdenginePage queryRawFrames(TdengineRawFrameQuery query) throws IOException { - int pageSize = Math.min(query.limit(), 1000); - TdengineQueryStatement statement = queries.rawFrames(query.withLimit(pageSize + 1)); - return readPage(statement, pageSize, this::rawFrame, row -> new TdenginePageCursor(row.ts(), row.frameId())); - } - - @Override - public TdenginePage queryLocations(TdengineLocationQuery query) throws IOException { - int pageSize = Math.min(query.limit(), 1000); - TdengineQueryStatement statement = queries.locations(query.withLimit(pageSize + 1)); - return readPage(statement, pageSize, this::location, row -> new TdenginePageCursor(row.ts(), row.factId())); - } - - @Override - public List queryLocationsByRawUri(String protocol, String rawUri, int limit) - throws IOException { - if (rawUri == null || rawUri.isBlank()) { - return List.of(); - } - TdengineQueryStatement statement = queries.locationsByRawUri(protocol, rawUri, limit); - return readList(statement, this::location); - } - - private TdenginePage readPage(TdengineQueryStatement statement, - int pageSize, - SqlRowMapper mapper, - Function cursor) throws IOException { - List rows = new ArrayList<>(); - try (Connection connection = dataSource.getConnection(); - PreparedStatement prepared = connection.prepareStatement(statement.sql())) { - bind(prepared, statement.values()); - try (ResultSet resultSet = prepared.executeQuery()) { - while (resultSet.next()) { - rows.add(mapper.map(resultSet)); - } - } - } catch (SQLException e) { - if (isTableMissing(e)) { - return new TdenginePage<>(List.of(), Optional.empty()); - } - throw new IOException("tdengine history query failed", e); - } - boolean hasMore = rows.size() > pageSize; - List items = hasMore ? List.copyOf(rows.subList(0, pageSize)) : List.copyOf(rows); - Optional next = hasMore && !items.isEmpty() - ? Optional.of(cursor.apply(items.getLast())) - : Optional.empty(); - return new TdenginePage<>(items, next); - } - - private List readList(TdengineQueryStatement statement, SqlRowMapper mapper) throws IOException { - List rows = new ArrayList<>(); - try (Connection connection = dataSource.getConnection(); - PreparedStatement prepared = connection.prepareStatement(statement.sql())) { - bind(prepared, statement.values()); - try (ResultSet resultSet = prepared.executeQuery()) { - while (resultSet.next()) { - rows.add(mapper.map(resultSet)); - } - } - } catch (SQLException e) { - if (isTableMissing(e)) { - return List.of(); - } - throw new IOException("tdengine history query failed", e); - } - return List.copyOf(rows); - } - - private static boolean isTableMissing(SQLException e) { - for (Throwable current = e; current != null; current = current.getCause()) { - String message = current.getMessage(); - if (message != null) { - String normalized = message.toLowerCase(); - if (normalized.contains("table does not exist") - || normalized.contains("0x2603") - || normalized.contains("table not exist")) { - return true; - } - } - } - return false; - } - - private static void bind(PreparedStatement prepared, List values) throws SQLException { - for (int i = 0; i < values.size(); i++) { - Object value = values.get(i); - int parameterIndex = i + 1; - if (value instanceof Instant instant) { - prepared.setTimestamp(parameterIndex, Timestamp.from(instant)); - } else { - prepared.setObject(parameterIndex, value); - } - } - } - - private TdengineRawFrameRow rawFrame(ResultSet rs) throws SQLException { - return new TdengineRawFrameRow( - instant(rs, "ts"), - rs.getString("frame_id"), - instant(rs, "received_at"), - rs.getInt("message_id"), - rs.getInt("sub_type"), - instant(rs, "event_time"), - rs.getString("raw_uri"), - rs.getString("checksum"), - rs.getLong("raw_size_bytes"), - rs.getString("parse_status"), - rs.getString("parse_error"), - rs.getString("peer"), - rs.getString("metadata_json"), - rs.getString("parsed_json"), - rs.getString("protocol"), - rs.getString("vehicle_key"), - rs.getString("vin"), - rs.getString("phone") - ); - } - - private TdengineLocationRow location(ResultSet rs) throws SQLException { - return new TdengineLocationRow( - instant(rs, "ts"), - rs.getString("fact_id"), - rs.getString("frame_id"), - instant(rs, "received_at"), - rs.getDouble("longitude"), - rs.getDouble("latitude"), - rs.getDouble("altitude_m"), - rs.getDouble("speed_kmh"), - rs.getDouble("direction_deg"), - rs.getLong("alarm_flag"), - rs.getLong("status_flag"), - nullableDouble(rs, "total_mileage_km"), - rs.getString("raw_uri"), - rs.getString("protocol"), - rs.getString("vehicle_key"), - rs.getString("vin"), - rs.getString("phone") - ); - } - - private static Instant instant(ResultSet rs, String column) throws SQLException { - Timestamp timestamp = rs.getTimestamp(column); - return timestamp == null ? null : timestamp.toInstant(); - } - - private static Double nullableDouble(ResultSet rs, String column) throws SQLException { - Object value = rs.getObject(column); - return value instanceof Number number ? number.doubleValue() : null; - } - - @FunctionalInterface - private interface SqlRowMapper { - T map(ResultSet rs) throws SQLException; - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java deleted file mode 100644 index 5d4ed92e..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriter.java +++ /dev/null @@ -1,248 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import javax.sql.DataSource; -import java.io.IOException; -import java.sql.Connection; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -public final class TdengineJdbcHistoryWriter implements TdengineHistoryWriter { - - private static final int MAX_LITERAL_ROWS_PER_STATEMENT = 200; - - private final DataSource dataSource; - private final TdengineHistorySchema schema; - private final TdengineHistoryStatements statements; - private final Object schemaInitializationMonitor = new Object(); - private final Map lastTimestampByInsertSql = new ConcurrentHashMap<>(); - private volatile boolean schemaInitialized; - - public TdengineJdbcHistoryWriter(DataSource dataSource, TdengineHistorySchema schema) { - if (dataSource == null) { - throw new IllegalArgumentException("dataSource must not be null"); - } - if (schema == null) { - throw new IllegalArgumentException("schema must not be null"); - } - this.dataSource = dataSource; - this.schema = schema; - this.statements = new TdengineHistoryStatements(schema); - } - - public void initializeSchema() throws IOException { - inTransaction(connection -> { - try (Statement statement = connection.createStatement()) { - for (String sql : schema.bootstrapSql()) { - statement.execute(sql); - } - } - }); - schemaInitialized = true; - } - - @Override - public void appendRawFrames(List rows) throws IOException { - append(rows, statements::rawFrame); - } - - @Override - public void appendLocations(List rows) throws IOException { - append(rows, statements::location); - } - - private void append(List rows, Function mapper) throws IOException { - if (rows == null || rows.isEmpty()) { - return; - } - if (!schemaInitialized) { - synchronized (schemaInitializationMonitor) { - if (!schemaInitialized) { - append(rows, mapper, true); - schemaInitialized = true; - return; - } - } - } - append(rows, mapper, false); - } - - private void append(List rows, - Function mapper, - boolean initializeSchema) throws IOException { - inTransaction(connection -> { - if (initializeSchema) { - try (Statement statement = connection.createStatement()) { - for (String sql : schema.bootstrapSql()) { - statement.execute(sql); - } - } - } - Set createdChildTables = new LinkedHashSet<>(); - Map literalBatches = new LinkedHashMap<>(); - for (T row : rows) { - if (row == null) { - continue; - } - TdengineBatchStatement batch = mapper.apply(row); - if (createdChildTables.add(batch.createChildTableSql())) { - try (Statement statement = connection.createStatement()) { - statement.execute(batch.createChildTableSql()); - } - } - literalBatches.computeIfAbsent(batch.insertSql(), LiteralBatch::new) - .add(uniqueTimestampValues(batch.insertSql(), batch.values())); - } - for (LiteralBatch batch : literalBatches.values()) { - executeLiteralBatch(connection, batch); - } - }); - } - - private List uniqueTimestampValues(String insertSql, List values) { - if (values == null || values.isEmpty() || !(values.getFirst() instanceof Instant candidate)) { - return values; - } - Instant uniqueTimestamp = lastTimestampByInsertSql.compute(insertSql, (key, lastTimestamp) -> { - if (lastTimestamp == null || candidate.isAfter(lastTimestamp)) { - return candidate; - } - return lastTimestamp.plusMillis(1); - }); - if (candidate.equals(uniqueTimestamp)) { - return values; - } - List copy = new java.util.ArrayList<>(values); - copy.set(0, uniqueTimestamp); - return copy; - } - - private static void executeLiteralBatch(Connection connection, LiteralBatch batch) throws SQLException { - try (Statement statement = connection.createStatement()) { - List> rows = batch.rows(); - for (int start = 0; start < rows.size(); start += MAX_LITERAL_ROWS_PER_STATEMENT) { - int end = Math.min(start + MAX_LITERAL_ROWS_PER_STATEMENT, rows.size()); - statement.execute(toLiteralInsertSql(batch.insertSql(), rows.subList(start, end))); - } - } - } - - private static String toLiteralInsertSql(String insertSql, List> rows) { - int valuesIndex = insertSql.lastIndexOf("VALUES"); - if (valuesIndex < 0) { - throw new IllegalArgumentException("insert SQL must contain VALUES: " + insertSql); - } - if (rows == null || rows.isEmpty()) { - throw new IllegalArgumentException("literal rows must not be empty"); - } - StringBuilder sql = new StringBuilder(insertSql.substring(0, valuesIndex)) - .append("VALUES "); - for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) { - if (rowIndex > 0) { - sql.append(' '); - } - List values = rows.get(rowIndex); - sql.append('('); - for (int i = 0; i < values.size(); i++) { - if (i > 0) { - sql.append(", "); - } - sql.append(literal(values.get(i))); - } - sql.append(')'); - } - return sql.toString(); - } - - private static String literal(Object value) { - if (value == null) { - return "NULL"; - } - if (value instanceof Instant instant) { - return Long.toString(instant.toEpochMilli()); - } - if (value instanceof Timestamp timestamp) { - return Long.toString(timestamp.toInstant().toEpochMilli()); - } - if (value instanceof Double doubleValue) { - return Double.isFinite(doubleValue) ? doubleValue.toString() : "NULL"; - } - if (value instanceof Float floatValue) { - return Float.isFinite(floatValue) ? floatValue.toString() : "NULL"; - } - if (value instanceof Number number) { - return number.toString(); - } - return "'" + value.toString().replace("'", "''") + "'"; - } - - private void inTransaction(SqlWork work) throws IOException { - try (Connection connection = dataSource.getConnection()) { - boolean autoCommit = connection.getAutoCommit(); - connection.setAutoCommit(false); - try { - work.execute(connection); - connection.commit(); - } catch (JdbcRuntimeException e) { - rollback(connection); - throw e.getCause(); - } catch (SQLException e) { - rollback(connection); - throw e; - } finally { - connection.setAutoCommit(autoCommit); - } - } catch (SQLException e) { - throw new IOException("tdengine history write failed", e); - } - } - - private static void rollback(Connection connection) throws SQLException { - connection.rollback(); - } - - @FunctionalInterface - private interface SqlWork { - void execute(Connection connection) throws SQLException; - } - - private static final class JdbcRuntimeException extends RuntimeException { - private JdbcRuntimeException(SQLException cause) { - super(cause); - } - - @Override - public synchronized SQLException getCause() { - return (SQLException) super.getCause(); - } - } - - private static final class LiteralBatch { - private final String insertSql; - private final List> rows = new java.util.ArrayList<>(); - - private LiteralBatch(String insertSql) { - this.insertSql = insertSql; - } - - private void add(List row) { - rows.add(new java.util.ArrayList<>(row)); - } - - private String insertSql() { - return insertSql; - } - - private List> rows() { - return rows; - } - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationQuery.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationQuery.java deleted file mode 100644 index 700484f8..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationQuery.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.time.Instant; - -public record TdengineLocationQuery( - String protocol, - String vehicleKey, - Instant from, - Instant to, - TdengineQueryOrder order, - int limit, - TdenginePageCursor cursor -) { - public TdengineLocationQuery { - if (protocol == null || protocol.isBlank()) { - throw new IllegalArgumentException("protocol must not be blank"); - } - if (vehicleKey == null || vehicleKey.isBlank()) { - throw new IllegalArgumentException("vehicleKey must not be blank"); - } - if (from == null || to == null || !from.isBefore(to)) { - throw new IllegalArgumentException("query time range must be valid"); - } - order = order == null ? TdengineQueryOrder.DESC : order; - limit = Math.max(1, Math.min(limit, 1001)); - } - - TdengineLocationQuery withLimit(int newLimit) { - return new TdengineLocationQuery(protocol, vehicleKey, from, to, order, newLimit, cursor); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationRow.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationRow.java deleted file mode 100644 index 0b7f4567..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineLocationRow.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.time.Instant; - -public record TdengineLocationRow( - Instant ts, - String factId, - String frameId, - Instant receivedAt, - double longitude, - double latitude, - double altitudeM, - double speedKmh, - double directionDeg, - long alarmFlag, - long statusFlag, - Double totalMileageKm, - String rawUri, - String protocol, - String vehicleKey, - String vin, - String phone -) { -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePage.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePage.java deleted file mode 100644 index 5447cf98..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePage.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.util.List; -import java.util.Optional; - -public record TdenginePage(List items, Optional nextCursor) { - - public TdenginePage { - items = items == null ? List.of() : List.copyOf(items); - nextCursor = nextCursor == null ? Optional.empty() : nextCursor; - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePageCursor.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePageCursor.java deleted file mode 100644 index 76a2a3e3..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdenginePageCursor.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.time.Instant; - -public record TdenginePageCursor(Instant ts, String tieBreaker) { - - public TdenginePageCursor { - if (ts == null) { - throw new IllegalArgumentException("cursor ts must not be null"); - } - tieBreaker = tieBreaker == null ? "" : tieBreaker; - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryOrder.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryOrder.java deleted file mode 100644 index 3c1c0e71..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryOrder.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -public enum TdengineQueryOrder { - ASC, - DESC -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryStatement.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryStatement.java deleted file mode 100644 index 034fffd3..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineQueryStatement.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public record TdengineQueryStatement(String sql, List values) { - - public TdengineQueryStatement { - if (sql == null || sql.isBlank()) { - throw new IllegalArgumentException("sql must not be blank"); - } - values = values == null ? List.of() : Collections.unmodifiableList(new ArrayList<>(values)); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameQuery.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameQuery.java deleted file mode 100644 index 87f040c4..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameQuery.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.time.Instant; - -public record TdengineRawFrameQuery( - String protocol, - String vehicleKey, - String vin, - String phone, - Integer messageId, - String parseStatus, - Instant from, - Instant to, - TdengineQueryOrder order, - int limit, - TdenginePageCursor cursor -) { - public TdengineRawFrameQuery(String protocol, - String vehicleKey, - Instant from, - Instant to, - TdengineQueryOrder order, - int limit, - TdenginePageCursor cursor) { - this(protocol, vehicleKey, "", "", null, "", from, to, order, limit, cursor); - } - - public TdengineRawFrameQuery { - if (protocol == null || protocol.isBlank()) { - throw new IllegalArgumentException("protocol must not be blank"); - } - if (from == null || to == null || !from.isBefore(to)) { - throw new IllegalArgumentException("query time range must be valid"); - } - protocol = protocol.trim().toUpperCase(); - vehicleKey = trim(vehicleKey); - vin = trim(vin); - phone = trim(phone); - parseStatus = trim(parseStatus); - order = order == null ? TdengineQueryOrder.DESC : order; - limit = Math.max(1, Math.min(limit, 1001)); - } - - TdengineRawFrameQuery withLimit(int newLimit) { - return new TdengineRawFrameQuery(protocol, vehicleKey, vin, phone, messageId, parseStatus, - from, to, order, newLimit, cursor); - } - - boolean childTableQuery() { - return !vehicleKey.isBlank() - && vin.isBlank() - && phone.isBlank() - && messageId == null - && parseStatus.isBlank(); - } - - private static String trim(String value) { - return value == null ? "" : value.trim(); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameRow.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameRow.java deleted file mode 100644 index a965bcc0..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/TdengineRawFrameRow.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import java.time.Instant; - -public record TdengineRawFrameRow( - Instant ts, - String frameId, - Instant receivedAt, - int messageId, - int subType, - Instant eventTime, - String rawUri, - String checksum, - long rawSizeBytes, - String parseStatus, - String parseError, - String peer, - String metadataJson, - String parsedJson, - String protocol, - String vehicleKey, - String vin, - String phone -) { -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfiguration.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfiguration.java deleted file mode 100644 index 2d612fb3..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfiguration.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.lingniu.ingest.tdenginehistory.config; - -import com.lingniu.ingest.tdenginehistory.TdengineHistorySchema; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryStatements; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter; -import com.lingniu.ingest.tdenginehistory.TdengineJdbcHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineJdbcHistoryWriter; -import com.zaxxer.hikari.HikariConfig; -import com.zaxxer.hikari.HikariDataSource; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -import javax.sql.DataSource; - -@AutoConfiguration -@EnableConfigurationProperties(TdengineHistoryProperties.class) -public class TdengineHistoryAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty( - prefix = "lingniu.ingest.tdengine-history", - name = "enabled", - havingValue = "true") - public TdengineHistorySchema tdengineHistorySchema(TdengineHistoryProperties properties) { - return new TdengineHistorySchema(properties.getDatabase()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty( - prefix = "lingniu.ingest.tdengine-history", - name = "enabled", - havingValue = "true") - public TdengineHistoryStatements tdengineHistoryStatements(TdengineHistorySchema schema) { - return new TdengineHistoryStatements(schema); - } - - @Bean - @ConditionalOnMissingBean(DataSource.class) - @ConditionalOnProperty( - prefix = "lingniu.ingest.tdengine-history", - name = "enabled", - havingValue = "true") - public DataSource tdengineDataSource(TdengineHistoryProperties properties) { - HikariConfig config = new HikariConfig(); - config.setPoolName("tdengine-history"); - config.setDriverClassName(properties.getDriverClassName()); - config.setJdbcUrl(properties.getJdbcUrl()); - config.setUsername(properties.getUsername()); - config.setPassword(properties.getPassword()); - config.setMaximumPoolSize(Math.max(1, properties.getMaximumPoolSize())); - config.setMinimumIdle(Math.max(0, Math.min(properties.getMinimumIdle(), properties.getMaximumPoolSize()))); - config.setConnectionTimeout(Math.max(250, properties.getConnectionTimeoutMillis())); - config.setInitializationFailTimeout(properties.getInitializationFailTimeoutMillis()); - return new HikariDataSource(config); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(DataSource.class) - @ConditionalOnProperty( - prefix = "lingniu.ingest.tdengine-history", - name = "enabled", - havingValue = "true") - public TdengineHistoryWriter tdengineHistoryWriter(DataSource dataSource, TdengineHistorySchema schema) { - return new TdengineJdbcHistoryWriter(dataSource, schema); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(DataSource.class) - @ConditionalOnProperty( - prefix = "lingniu.ingest.tdengine-history", - name = "enabled", - havingValue = "true") - public TdengineHistoryReader tdengineHistoryReader(DataSource dataSource, TdengineHistorySchema schema) { - return new TdengineJdbcHistoryReader(dataSource, schema); - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryProperties.java b/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryProperties.java deleted file mode 100644 index d19a28eb..00000000 --- a/modules/sinks/tdengine-history-store/src/main/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryProperties.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.lingniu.ingest.tdenginehistory.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(prefix = "lingniu.ingest.tdengine-history") -public class TdengineHistoryProperties { - - /** - * 默认关闭,避免未配置 TDengine 连接时影响现有历史查询服务启动。 - */ - private boolean enabled = false; - - /** - * TDengine 历史库名。 - */ - private String database = "vehicle_history"; - - /** - * TDengine RESTful JDBC 地址。默认走 6041,部署时可用 TDENGINE_JDBC_URL 覆盖。 - */ - private String jdbcUrl; - - private String username = "root"; - - private String password = "taosdata"; - - private String driverClassName = "com.taosdata.jdbc.rs.RestfulDriver"; - - private int maximumPoolSize = 16; - - private int minimumIdle = 0; - - private long connectionTimeoutMillis = 5000; - - private long initializationFailTimeoutMillis = -1; - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getDatabase() { - return database; - } - - public void setDatabase(String database) { - this.database = database; - } - - public String getJdbcUrl() { - if (jdbcUrl == null || jdbcUrl.isBlank()) { - return "jdbc:TAOS-RS://127.0.0.1:6041/" + database; - } - return jdbcUrl; - } - - public void setJdbcUrl(String jdbcUrl) { - this.jdbcUrl = jdbcUrl; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getDriverClassName() { - return driverClassName; - } - - public void setDriverClassName(String driverClassName) { - this.driverClassName = driverClassName; - } - - public int getMaximumPoolSize() { - return maximumPoolSize; - } - - public void setMaximumPoolSize(int maximumPoolSize) { - this.maximumPoolSize = maximumPoolSize; - } - - public int getMinimumIdle() { - return minimumIdle; - } - - public void setMinimumIdle(int minimumIdle) { - this.minimumIdle = minimumIdle; - } - - public long getConnectionTimeoutMillis() { - return connectionTimeoutMillis; - } - - public void setConnectionTimeoutMillis(long connectionTimeoutMillis) { - this.connectionTimeoutMillis = connectionTimeoutMillis; - } - - public long getInitializationFailTimeoutMillis() { - return initializationFailTimeoutMillis; - } - - public void setInitializationFailTimeoutMillis(long initializationFailTimeoutMillis) { - this.initializationFailTimeoutMillis = initializationFailTimeoutMillis; - } -} diff --git a/modules/sinks/tdengine-history-store/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/modules/sinks/tdengine-history-store/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 2bd97e31..00000000 --- a/modules/sinks/tdengine-history-store/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -com.lingniu.ingest.tdenginehistory.config.TdengineHistoryAutoConfiguration diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRowsTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRowsTest.java deleted file mode 100644 index dca1641f..00000000 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineEnvelopeRowsTest.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import com.lingniu.ingest.sink.kafka.proto.LocationPayload; -import com.lingniu.ingest.sink.kafka.proto.ParseStatusProto; -import com.lingniu.ingest.sink.kafka.proto.RawArchiveRef; -import com.lingniu.ingest.sink.kafka.proto.RawFrameFactPayload; -import com.lingniu.ingest.sink.kafka.proto.VehicleEnvelope; -import org.junit.jupiter.api.Test; - -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class TdengineEnvelopeRowsTest { - - @Test - void mapsRawFrameFactEnvelopeToRawFrameRow() { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setEventId("evt-raw-1") - .setVin("VIN123") - .setSource("JT808") - .setEventTimeMs(1_772_000_001_234L) - .setIngestTimeMs(1_772_000_001_999L) - .putMetadata("channel", "tcp-808") - .setRawFrameFact(RawFrameFactPayload.newBuilder() - .setFrameId("frame-1") - .setVehicleKey("jt808:g7gps") - .setPhone("013800000000") - .setMessageId(0x0200) - .setSubType(0) - .setRawUri("archive://jt808/2026/06/29/frame-1.bin") - .setChecksum("sha256:abc") - .setRawSizeBytes(67) - .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED) - .setPeer("10.0.0.8:30001") - .putMetadata("auth", "passed")) - .build(); - - TdengineRawFrameRow row = TdengineEnvelopeRows.rawFrame(envelope).orElseThrow(); - - assertThat(row.ts()).isEqualTo(Instant.ofEpochMilli(1_772_000_001_234L)); - assertThat(row.receivedAt()).isEqualTo(Instant.ofEpochMilli(1_772_000_001_999L)); - assertThat(row.protocol()).isEqualTo("JT808"); - assertThat(row.vehicleKey()).isEqualTo("jt808:g7gps"); - assertThat(row.vin()).isEqualTo("VIN123"); - assertThat(row.phone()).isEqualTo("013800000000"); - assertThat(row.messageId()).isEqualTo(0x0200); - assertThat(row.rawUri()).isEqualTo("archive://jt808/2026/06/29/frame-1.bin"); - assertThat(row.metadataJson()).contains("\"channel\":\"tcp-808\"", "\"auth\":\"passed\""); - } - - @Test - void expandsRawFrameTsWithRawArchiveEventIdSequenceToAvoidSameMillisecondOverwrite() { - VehicleEnvelope first = rawFrameEnvelope("1782729152494000"); - VehicleEnvelope second = rawFrameEnvelope("1782729152494001"); - - TdengineRawFrameRow firstRow = TdengineEnvelopeRows.rawFrame(first).orElseThrow(); - TdengineRawFrameRow secondRow = TdengineEnvelopeRows.rawFrame(second).orElseThrow(); - - assertThat(firstRow.ts()).isEqualTo(Instant.ofEpochMilli(1_782_729_152_494L)); - assertThat(secondRow.ts()).isEqualTo(Instant.ofEpochMilli(1_782_729_152_495L)); - assertThat(firstRow.eventTime()).isEqualTo(Instant.ofEpochMilli(1_782_729_152_494L)); - assertThat(secondRow.eventTime()).isEqualTo(Instant.ofEpochMilli(1_782_729_152_494L)); - } - - @Test - void mapsLocationEnvelopeToVehicleLocationRow() { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setEventId("evt-location-1") - .setVin("VIN32960") - .setSource("GB32960") - .setEventTimeMs(1_772_000_002_000L) - .setIngestTimeMs(1_772_000_002_321L) - .putMetadata("vehicle_key", "vin:VIN32960") - .putMetadata("frame_id", "frame-location-1") - .setRawArchive(RawArchiveRef.newBuilder() - .setUri("archive://gb32960/2026/06/29/frame-location-1.bin") - .setChecksum("sha256:def") - .setSizeBytes(98)) - .setLocation(LocationPayload.newBuilder() - .setLongitude(113.12) - .setLatitude(23.45) - .setAltitudeM(8.0) - .setSpeedKmh(42.5) - .setDirectionDeg(91.0) - .setAlarmFlag(1) - .setStatusFlag(3)) - .build(); - - TdengineLocationRow row = TdengineEnvelopeRows.location(envelope).orElseThrow(); - - assertThat(row.ts()).isEqualTo(Instant.ofEpochMilli(1_772_000_002_000L)); - assertThat(row.factId()).isEqualTo("evt-location-1"); - assertThat(row.frameId()).isEqualTo("frame-location-1"); - assertThat(row.protocol()).isEqualTo("GB32960"); - assertThat(row.vehicleKey()).isEqualTo("vin:VIN32960"); - assertThat(row.longitude()).isEqualTo(113.12); - assertThat(row.latitude()).isEqualTo(23.45); - assertThat(row.speedKmh()).isEqualTo(42.5); - assertThat(row.totalMileageKm()).isNull(); - assertThat(row.rawUri()).isEqualTo("archive://gb32960/2026/06/29/frame-location-1.bin"); - } - - @Test - void mapsLocationRawUriFromMetadataWhenRawArchiveRefIsAbsent() { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setEventId("evt-location-metadata-raw") - .setVin("unknown") - .setSource("JT808") - .setEventTimeMs(1_772_000_002_000L) - .setIngestTimeMs(1_772_000_002_321L) - .putMetadata("phone", "13079963291") - .putMetadata("rawArchiveUri", "archive://jt808/metadata-only.bin") - .setLocation(LocationPayload.newBuilder() - .setLongitude(119.12) - .setLatitude(29.45)) - .build(); - - TdengineLocationRow row = TdengineEnvelopeRows.location(envelope).orElseThrow(); - - assertThat(row.rawUri()).isEqualTo("archive://jt808/metadata-only.bin"); - } - - @Test - void mapsJt808UnknownVinRowsToPhoneVehicleKey() { - VehicleEnvelope envelope = VehicleEnvelope.newBuilder() - .setEventId("evt-jt808-phone-1") - .setVin("unknown") - .setSource("JT808") - .setEventTimeMs(1_772_000_004_000L) - .setIngestTimeMs(1_772_000_004_500L) - .putMetadata("phone", "13079963291") - .putMetadata("frame_id", "frame-jt808-phone-1") - .setRawFrameFact(RawFrameFactPayload.newBuilder() - .setFrameId("frame-jt808-phone-1") - .setPhone("13079963291") - .setMessageId(0x0200) - .setRawUri("archive://jt808/frame-jt808-phone-1.bin") - .setChecksum("sha256:phone") - .setRawSizeBytes(88) - .setParseStatus(ParseStatusProto.PARSE_STATUS_SUCCEEDED)) - .setLocation(LocationPayload.newBuilder() - .setLongitude(119.1) - .setLatitude(29.2)) - .build(); - - assertThat(TdengineEnvelopeRows.rawFrame(envelope).orElseThrow().vehicleKey()) - .isEqualTo("jt808:13079963291"); - assertThat(TdengineEnvelopeRows.location(envelope).orElseThrow().vehicleKey()) - .isEqualTo("jt808:13079963291"); - } - - private static VehicleEnvelope rawFrameEnvelope(String rawArchiveEventId) { - return VehicleEnvelope.newBuilder() - .setEventId(rawArchiveEventId) - .setVin("unknown") - .setSource("JT808") - .setEventTimeMs(1_782_729_152_494L) - .setIngestTimeMs(1_782_729_152_539L) - .putMetadata("phone", "13079973200") - .putMetadata("rawArchiveEventId", rawArchiveEventId) - .setRawFrameFact(RawFrameFactPayload.newBuilder() - .setFrameId("rf-" + rawArchiveEventId) - .setPhone("13079973200") - .setMessageId(0x0200) - .setRawUri("archive://2026/06/29/JT808/unknown/" + rawArchiveEventId + ".bin") - .setChecksum("sha256:" + rawArchiveEventId) - .setRawSizeBytes(41) - .setParseStatus(ParseStatusProto.PARSE_STATUS_NOT_PARSED) - .putMetadata("rawArchiveEventId", rawArchiveEventId)) - .build(); - } -} diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueriesTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueriesTest.java deleted file mode 100644 index 84f2b279..00000000 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryQueriesTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import org.junit.jupiter.api.Test; - -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class TdengineHistoryQueriesTest { - - private final TdengineHistorySchema schema = new TdengineHistorySchema("vehicle_history"); - private final TdengineHistoryQueries queries = new TdengineHistoryQueries(schema); - - @Test - void rawFrameQueryUsesVehicleChildTableAndKeysetPagination() { - TdengineRawFrameQuery query = new TdengineRawFrameQuery( - "JT808", - "jt808:g7gps", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-30T00:00:00Z"), - TdengineQueryOrder.ASC, - 101, - new TdenginePageCursor(Instant.parse("2026-06-29T01:00:00Z"), "frame-100")); - - TdengineQueryStatement statement = queries.rawFrames(query); - - assertThat(statement.sql()) - .startsWith("SELECT ts, frame_id, received_at, message_id") - .contains(" FROM raw_jt808_") - .contains(" WHERE ts >= ? AND ts < ?") - .contains(" AND (ts > ? OR (ts = ? AND frame_id > ?))") - .contains(" ORDER BY ts ASC, frame_id ASC LIMIT ?"); - assertThat(statement.values()) - .containsExactly( - query.from(), query.to(), - query.cursor().ts(), query.cursor().ts(), query.cursor().tieBreaker(), - 101); - } - - @Test - void rawFrameQueryUsesStableWhenPhoneMessageIdOrParseStatusFiltersArePresent() { - TdengineRawFrameQuery query = new TdengineRawFrameQuery( - "JT808", - "", - "", - "13079963320", - 0x0100, - "SUCCEEDED", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-30T00:00:00Z"), - TdengineQueryOrder.DESC, - 50, - new TdenginePageCursor(Instant.parse("2026-06-29T12:00:00Z"), "frame-100")); - - TdengineQueryStatement statement = queries.rawFrames(query); - - assertThat(statement.sql()) - .startsWith("SELECT ts, frame_id, received_at, message_id") - .contains(" FROM raw_frames") - .contains(" WHERE ts >= ? AND ts < ?") - .contains(" AND protocol = ?") - .contains(" AND phone = ?") - .contains(" AND message_id = ?") - .contains(" AND parse_status = ?") - .contains(" AND (ts < ? OR (ts = ? AND frame_id < ?))") - .contains(" ORDER BY ts DESC, frame_id DESC LIMIT ?"); - assertThat(statement.values()) - .containsExactly( - query.from(), query.to(), - "JT808", "13079963320", 0x0100, "SUCCEEDED", - query.cursor().ts(), query.cursor().ts(), query.cursor().tieBreaker(), - 50); - } - - @Test - void locationQueryCapsLimitAndUsesDescendingCursor() { - TdengineLocationQuery query = new TdengineLocationQuery( - "GB32960", - "vin:VIN123", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-30T00:00:00Z"), - TdengineQueryOrder.DESC, - 5000, - new TdenginePageCursor(Instant.parse("2026-06-29T20:00:00Z"), "fact-200")); - - TdengineQueryStatement statement = queries.locations(query); - - assertThat(statement.sql()) - .contains(" FROM loc_gb32960_") - .contains(" AND (ts < ? OR (ts = ? AND fact_id < ?))") - .contains(" ORDER BY ts DESC, fact_id DESC LIMIT ?"); - assertThat(statement.values().getLast()).isEqualTo(1001); - } - -} diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchemaTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchemaTest.java deleted file mode 100644 index 35931d48..00000000 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistorySchemaTest.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import org.junit.jupiter.api.Test; - -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.assertj.core.api.Assertions.assertThat; - -class TdengineHistorySchemaTest { - - @Test - void bootstrapSqlCreatesOnlyRawAndLocationStablesFromSpec() { - TdengineHistorySchema schema = new TdengineHistorySchema("vehicle_history"); - - assertThat(schema.bootstrapSql()) - .first() - .isEqualTo("CREATE DATABASE IF NOT EXISTS vehicle_history PRECISION 'ms'"); - assertThat(schema.bootstrapSql()) - .contains("USE vehicle_history"); - assertThat(schema.bootstrapSql().get(2)) - .contains("CREATE STABLE IF NOT EXISTS raw_frames") - .contains("frame_id NCHAR(64)") - .contains("raw_uri NCHAR(512)") - .contains("metadata_json NCHAR(4096)") - .contains("parsed_json VARCHAR(16374)") - .contains("TAGS (") - .contains("protocol NCHAR(16)") - .contains("vehicle_key NCHAR(128)") - .contains("phone NCHAR(32)"); - assertThat(schema.bootstrapSql().get(3)) - .contains("CREATE STABLE IF NOT EXISTS vehicle_locations") - .contains("longitude DOUBLE") - .contains("total_mileage_km DOUBLE") - .contains("raw_uri NCHAR(512)"); - assertThat(schema.bootstrapSql()).hasSize(4); - assertThat(String.join("\n", schema.bootstrapSql())) - .doesNotContain("telemetry_fields") - .doesNotContain("field_key"); - } - - @Test - void childTableNamesAreStableAndSanitized() { - TdengineHistorySchema schema = new TdengineHistorySchema("vehicle_history"); - - assertThat(schema.rawFrameTable("JT808", "jt808:g7gps/013800000000")) - .matches("raw_jt808_[0-9a-f]{16}"); - assertThat(schema.locationTable("GB32960", "VIN WITH SPACE")) - .matches("loc_gb32960_[0-9a-f]{16}"); - } - - @Test - void historyStorePublicApiDoesNotExposeTelemetryFields() throws Exception { - String writer = Files.readString(Path.of( - "src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryWriter.java")); - String reader = Files.readString(Path.of( - "src/main/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryReader.java")); - - assertThat(writer) - .doesNotContain("TelemetryField") - .doesNotContain("appendTelemetry"); - assertThat(reader) - .doesNotContain("TelemetryField") - .doesNotContain("queryTelemetry"); - } -} diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatementsTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatementsTest.java deleted file mode 100644 index af42941b..00000000 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineHistoryStatementsTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import org.junit.jupiter.api.Test; - -import java.time.Instant; - -import static org.assertj.core.api.Assertions.assertThat; - -class TdengineHistoryStatementsTest { - - private final TdengineHistorySchema schema = new TdengineHistorySchema("vehicle_history"); - private final TdengineHistoryStatements statements = new TdengineHistoryStatements(schema); - - @Test - void rawFrameBatchStatementCreatesChildTableAndParameterizedInsert() { - TdengineRawFrameRow row = new TdengineRawFrameRow( - Instant.parse("2026-06-29T05:00:01Z"), - "frame-1", - Instant.parse("2026-06-29T05:00:02Z"), - 0x0200, - 0, - Instant.parse("2026-06-29T05:00:01Z"), - "archive://jt808/frame-1.bin", - "sha256:abc", - 67, - "SUCCEEDED", - "", - "10.0.0.1:808", - "{\"auth\":\"passed\"}", - "{\"body\":{\"speedKmh\":42.0}}", - "JT808", - "jt808:g7gps", - "VIN123", - "013800000000"); - - TdengineBatchStatement batch = statements.rawFrame(row); - - assertThat(batch.createChildTableSql()) - .startsWith("CREATE TABLE IF NOT EXISTS raw_jt808_") - .contains(" USING raw_frames TAGS ('JT808', 'jt808:g7gps', 'VIN123', '013800000000')"); - assertThat(batch.insertSql()) - .startsWith("INSERT INTO raw_jt808_") - .contains("(ts, frame_id, received_at, message_id, sub_type, event_time, raw_uri, checksum, raw_size_bytes, parse_status, parse_error, peer, metadata_json, parsed_json)") - .endsWith("VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - assertThat(batch.values()) - .containsExactly( - row.ts(), row.frameId(), row.receivedAt(), row.messageId(), row.subType(), - row.eventTime(), row.rawUri(), row.checksum(), row.rawSizeBytes(), row.parseStatus(), - row.parseError(), row.peer(), row.metadataJson(), row.parsedJson()); - } - - @Test - void locationBatchStatementEscapesTags() { - TdengineLocationRow row = new TdengineLocationRow( - Instant.parse("2026-06-29T05:00:01Z"), - "fact-1", - "frame-1", - Instant.parse("2026-06-29T05:00:02Z"), - 113.1, - 23.4, - 8.0, - 42.0, - 90.0, - 1, - 3, - null, - "archive://gb32960/frame-1.bin", - "GB32960", - "vin:O'HARE", - "O'HARE", - ""); - - TdengineBatchStatement batch = statements.location(row); - - assertThat(batch.createChildTableSql()) - .startsWith("CREATE TABLE IF NOT EXISTS loc_gb32960_") - .contains(" USING vehicle_locations TAGS ('GB32960', 'vin:O''HARE', 'O''HARE', '')"); - assertThat(batch.values()) - .containsExactly( - row.ts(), row.factId(), row.frameId(), row.receivedAt(), row.longitude(), row.latitude(), - row.altitudeM(), row.speedKmh(), row.directionDeg(), row.alarmFlag(), row.statusFlag(), - row.totalMileageKm(), row.rawUri()); - } - -} diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReaderTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReaderTest.java deleted file mode 100644 index 1ec60192..00000000 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryReaderTest.java +++ /dev/null @@ -1,199 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import org.junit.jupiter.api.Test; - -import javax.sql.DataSource; -import java.lang.reflect.Proxy; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class TdengineJdbcHistoryReaderTest { - - @Test - void readsRawFramesAndReturnsNextCursorWhenLimitHasMoreRow() throws Exception { - RecordingQueryJdbc jdbc = new RecordingQueryJdbc(); - jdbc.rows.add(rawFrameResult("frame-1", "2026-06-29T05:00:01Z")); - jdbc.rows.add(rawFrameResult("frame-2", "2026-06-29T05:00:02Z")); - TdengineJdbcHistoryReader reader = new TdengineJdbcHistoryReader( - jdbc.dataSource(), new TdengineHistorySchema("vehicle_history")); - - TdenginePage page = reader.queryRawFrames(new TdengineRawFrameQuery( - "JT808", - "jt808:g7gps", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-30T00:00:00Z"), - TdengineQueryOrder.ASC, - 1, - null)); - - assertThat(page.items()) - .extracting(TdengineRawFrameRow::frameId) - .containsExactly("frame-1"); - assertThat(page.nextCursor()) - .contains(new TdenginePageCursor(Instant.parse("2026-06-29T05:00:01Z"), "frame-1")); - assertThat(jdbc.preparedSql).contains("LIMIT ?"); - assertThat(jdbc.boundValues.get(3)).isEqualTo(2); - } - - @Test - void readsLocationRowsWithoutNextCursorWhenPageIsComplete() throws Exception { - RecordingQueryJdbc jdbc = new RecordingQueryJdbc(); - jdbc.rows.add(locationResult("fact-1", "2026-06-29T05:00:01Z")); - TdengineJdbcHistoryReader reader = new TdengineJdbcHistoryReader( - jdbc.dataSource(), new TdengineHistorySchema("vehicle_history")); - - TdenginePage page = reader.queryLocations(new TdengineLocationQuery( - "GB32960", - "vin:VIN123", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-30T00:00:00Z"), - TdengineQueryOrder.DESC, - 10, - null)); - - assertThat(page.items()) - .extracting(TdengineLocationRow::factId) - .containsExactly("fact-1"); - assertThat(page.nextCursor()).isEmpty(); - assertThat(page.items().getFirst().longitude()).isEqualTo(113.12); - } - - @Test - void returnsEmptyPageWhenTdengineChildTableDoesNotExistInNestedCause() throws Exception { - RecordingQueryJdbc jdbc = new RecordingQueryJdbc(); - jdbc.executeQueryFailure = new SQLException("wrapper", - new RuntimeException("TDengine error: table not exist")); - TdengineJdbcHistoryReader reader = new TdengineJdbcHistoryReader( - jdbc.dataSource(), new TdengineHistorySchema("vehicle_history")); - - TdenginePage page = reader.queryLocations(new TdengineLocationQuery( - "JT808", - "jt808:g7gps", - Instant.parse("2026-06-29T00:00:00Z"), - Instant.parse("2026-06-30T00:00:00Z"), - TdengineQueryOrder.ASC, - 10, - null)); - - assertThat(page.items()).isEmpty(); - assertThat(page.nextCursor()).isEmpty(); - } - - private static Map rawFrameResult(String frameId, String ts) { - Map row = new LinkedHashMap<>(); - row.put("ts", Timestamp.from(Instant.parse(ts))); - row.put("frame_id", frameId); - row.put("received_at", Timestamp.from(Instant.parse(ts).plusMillis(500))); - row.put("message_id", 0x0200); - row.put("sub_type", 0); - row.put("event_time", Timestamp.from(Instant.parse(ts))); - row.put("raw_uri", "archive://jt808/" + frameId + ".bin"); - row.put("checksum", "sha256:" + frameId); - row.put("raw_size_bytes", 67L); - row.put("parse_status", "SUCCEEDED"); - row.put("parse_error", ""); - row.put("peer", "10.0.0.1:808"); - row.put("metadata_json", "{}"); - row.put("protocol", "JT808"); - row.put("vehicle_key", "jt808:g7gps"); - row.put("vin", "VIN123"); - row.put("phone", "013800000000"); - return row; - } - - private static Map locationResult(String factId, String ts) { - Map row = new LinkedHashMap<>(); - row.put("ts", Timestamp.from(Instant.parse(ts))); - row.put("fact_id", factId); - row.put("frame_id", "frame-1"); - row.put("received_at", Timestamp.from(Instant.parse(ts).plusMillis(500))); - row.put("longitude", 113.12); - row.put("latitude", 23.45); - row.put("altitude_m", 8.0); - row.put("speed_kmh", 42.5); - row.put("direction_deg", 90.0); - row.put("alarm_flag", 1L); - row.put("status_flag", 3L); - row.put("total_mileage_km", null); - row.put("raw_uri", "archive://gb32960/frame-1.bin"); - row.put("metadata_json", "{}"); - row.put("protocol", "GB32960"); - row.put("vehicle_key", "vin:VIN123"); - row.put("vin", "VIN123"); - row.put("phone", ""); - return row; - } - - private static final class RecordingQueryJdbc { - private final List> rows = new ArrayList<>(); - private final Map boundValues = new HashMap<>(); - private SQLException executeQueryFailure; - private String preparedSql; - - DataSource dataSource() { - return (DataSource) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{DataSource.class}, - (proxy, method, args) -> switch (method.getName()) { - case "getConnection" -> connection(); - default -> null; - }); - } - - private Connection connection() { - return (Connection) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{Connection.class}, - (proxy, method, args) -> switch (method.getName()) { - case "prepareStatement" -> preparedStatement((String) args[0]); - case "getAutoCommit" -> true; - default -> null; - }); - } - - private PreparedStatement preparedStatement(String sql) { - preparedSql = sql; - return (PreparedStatement) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{PreparedStatement.class}, - (proxy, method, args) -> switch (method.getName()) { - case "setTimestamp", "setObject" -> { - boundValues.put((Integer) args[0], args[1]); - yield null; - } - case "executeQuery" -> { - if (executeQueryFailure != null) { - throw executeQueryFailure; - } - yield resultSet(); - } - default -> null; - }); - } - - private ResultSet resultSet() { - final int[] index = {-1}; - return (ResultSet) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{ResultSet.class}, - (proxy, method, args) -> switch (method.getName()) { - case "next" -> ++index[0] < rows.size(); - case "getTimestamp", "getString", "getInt", "getLong", "getDouble", "getObject" -> - rows.get(index[0]).get((String) args[0]); - case "wasNull" -> false; - default -> null; - }); - } - } -} diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java deleted file mode 100644 index bd91e249..00000000 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/TdengineJdbcHistoryWriterTest.java +++ /dev/null @@ -1,307 +0,0 @@ -package com.lingniu.ingest.tdenginehistory; - -import org.junit.jupiter.api.Test; - -import javax.sql.DataSource; -import java.lang.reflect.Proxy; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -class TdengineJdbcHistoryWriterTest { - - private final TdengineHistorySchema schema = new TdengineHistorySchema("vehicle_history"); - - @Test - void initializesSchemaWithBootstrapSql() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - - writer.initializeSchema(); - - assertThat(jdbc.executedSql) - .containsExactlyElementsOf(schema.bootstrapSql()); - assertThat(jdbc.commits).isEqualTo(1); - } - - @Test - void writesRawFramesInBatchesPerChildTable() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - TdengineRawFrameRow first = rawFrame("frame-1", Instant.parse("2026-06-29T05:00:01Z"), "none"); - TdengineRawFrameRow second = rawFrame("frame-2", Instant.parse("2026-06-29T05:00:02Z"), "none"); - - writer.appendRawFrames(List.of(first, second)); - - assertThat(jdbc.executedSql).startsWith(schema.bootstrapSql().toArray(String[]::new)); - assertThat(jdbc.executedSql) - .filteredOn(sql -> sql.contains("USING raw_frames TAGS")) - .hasSize(1); - assertThat(jdbc.preparedBatches).isEmpty(); - assertThat(jdbc.executedSql) - .anyMatch(sql -> sql.startsWith("INSERT INTO raw_jt808_") - && sql.contains(Long.toString(first.ts().toEpochMilli())) - && sql.contains("frame-1") - && sql.contains("frame-2") - && sql.contains(") (")); - assertThat(jdbc.commits).isEqualTo(1); - } - - @Test - void writesLocationRowsWithNullableMileage() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - TdengineLocationRow location = new TdengineLocationRow( - Instant.parse("2026-06-29T05:00:01Z"), - "fact-1", - "frame-1", - Instant.parse("2026-06-29T05:00:02Z"), - 113.1, - 23.4, - 8.0, - 42.0, - 90.0, - 1, - 3, - null, - "archive://gb32960/frame-1.bin", - "GB32960", - "vin:VIN123", - "VIN123", - ""); - - writer.appendLocations(List.of(location)); - - assertThat(jdbc.executedSql).startsWith(schema.bootstrapSql().toArray(String[]::new)); - assertThat(jdbc.executedSql) - .anyMatch(sql -> sql.contains("USING vehicle_locations TAGS")); - assertThat(jdbc.preparedBatches).isEmpty(); - assertThat(jdbc.executedSql) - .anyMatch(sql -> sql.startsWith("INSERT INTO loc_gb32960_") - && sql.contains("fact-1") - && sql.contains("NULL") - && sql.contains("archive://gb32960/frame-1.bin")); - } - - @Test - void schemaBootstrapRunsOnlyOnceAcrossWrites() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - - writer.appendRawFrames(List.of(rawFrame("frame-1", Instant.parse("2026-06-29T05:00:01Z")))); - writer.appendRawFrames(List.of(rawFrame("frame-2", Instant.parse("2026-06-29T05:00:02Z")))); - - assertThat(jdbc.executedSql) - .filteredOn(sql -> sql.startsWith("CREATE DATABASE")) - .hasSize(1); - assertThat(jdbc.commits).isEqualTo(2); - } - - @Test - void writesLiteralInsertWithoutPreparedBatch() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - jdbc.failPreparedBatches = true; - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - - writer.appendRawFrames(List.of(rawFrame("frame-1", Instant.parse("2026-06-29T05:00:01Z"), "none"))); - - assertThat(jdbc.preparedBatches).isEmpty(); - assertThat(jdbc.executedSql) - .anyMatch(sql -> sql.startsWith("INSERT INTO raw_jt808_") - && sql.contains("parse_error") - && sql.contains("'SUCCEEDED', 'none', '10.0.0.1:808'")); - assertThat(jdbc.commits).isEqualTo(1); - } - - @Test - void usesLiteralInsertForRowsWithEmptyStrings() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - - writer.appendRawFrames(List.of(rawFrame("frame-1", Instant.parse("2026-06-29T05:00:01Z")))); - - assertThat(jdbc.preparedBatches).isEmpty(); - assertThat(jdbc.executedSql) - .anyMatch(sql -> sql.startsWith("INSERT INTO raw_jt808_") - && sql.contains("parse_error") - && sql.contains("'SUCCEEDED', '', '10.0.0.1:808'")); - assertThat(jdbc.commits).isEqualTo(1); - } - - @Test - void groupsLiteralRowsForSameChildTableIntoOneInsert() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - - writer.appendRawFrames(List.of( - rawFrame("frame-1", Instant.parse("2026-06-29T05:00:01Z")), - rawFrame("frame-2", Instant.parse("2026-06-29T05:00:02Z")))); - - List inserts = jdbc.executedSql.stream() - .filter(sql -> sql.startsWith("INSERT INTO raw_jt808_")) - .toList(); - assertThat(inserts).hasSize(1); - assertThat(inserts.getFirst()) - .contains("frame-1") - .contains("frame-2") - .contains(") ("); - } - - @Test - void bumpsDuplicateTimestampsForSameChildTable() throws Exception { - RecordingJdbc jdbc = new RecordingJdbc(); - TdengineJdbcHistoryWriter writer = new TdengineJdbcHistoryWriter(jdbc.dataSource(), schema); - Instant ts = Instant.parse("2026-06-29T05:00:01Z"); - - writer.appendRawFrames(List.of( - rawFrame("frame-1", ts), - rawFrame("frame-2", ts))); - - String insert = jdbc.executedSql.stream() - .filter(sql -> sql.startsWith("INSERT INTO raw_jt808_")) - .findFirst() - .orElseThrow(); - assertThat(insert) - .contains("(" + ts.toEpochMilli() + ", 'frame-1'") - .contains("(" + (ts.toEpochMilli() + 1) + ", 'frame-2'"); - } - - private static TdengineRawFrameRow rawFrame(String frameId, Instant ts) { - return rawFrame(frameId, ts, ""); - } - - private static TdengineRawFrameRow rawFrame(String frameId, Instant ts, String parseError) { - return new TdengineRawFrameRow( - ts, - frameId, - ts.plusSeconds(1), - 0x0200, - 0, - ts, - "archive://jt808/" + frameId + ".bin", - "sha256:" + frameId, - 67, - "SUCCEEDED", - parseError, - "10.0.0.1:808", - "{}", - "{\"frame\":\"" + frameId + "\"}", - "JT808", - "jt808:g7gps", - "VIN123", - "013800000000"); - } - - private static final class RecordingJdbc { - private final List executedSql = new ArrayList<>(); - private final Map preparedBatches = new HashMap<>(); - private boolean failPreparedBatches; - private int commits; - private int rollbacks; - - DataSource dataSource() { - return (DataSource) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{DataSource.class}, - (proxy, method, args) -> switch (method.getName()) { - case "getConnection" -> connection(); - case "unwrap" -> null; - case "isWrapperFor" -> false; - default -> defaultValue(method.getReturnType()); - }); - } - - private Connection connection() { - return (Connection) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{Connection.class}, - (proxy, method, args) -> switch (method.getName()) { - case "createStatement" -> statement(); - case "prepareStatement" -> preparedStatement((String) args[0]); - case "commit" -> { - commits++; - yield null; - } - case "rollback" -> { - rollbacks++; - yield null; - } - case "getAutoCommit" -> true; - case "unwrap" -> null; - case "isWrapperFor" -> false; - default -> defaultValue(method.getReturnType()); - }); - } - - private Statement statement() { - return (Statement) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{Statement.class}, - (proxy, method, args) -> switch (method.getName()) { - case "execute", "executeUpdate" -> { - executedSql.add((String) args[0]); - yield method.getReturnType() == boolean.class ? true : 1; - } - case "unwrap" -> null; - case "isWrapperFor" -> false; - default -> defaultValue(method.getReturnType()); - }); - } - - private PreparedStatement preparedStatement(String sql) { - PreparedBatch batch = preparedBatches.computeIfAbsent(sql, PreparedBatch::new); - Map current = new HashMap<>(); - return (PreparedStatement) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[]{PreparedStatement.class}, - (proxy, method, args) -> switch (method.getName()) { - case "setTimestamp", "setObject" -> { - current.put((Integer) args[0], args[1]); - yield null; - } - case "addBatch" -> { - batch.rows().add(new HashMap<>(current)); - current.clear(); - yield null; - } - case "executeBatch" -> { - if (failPreparedBatches) { - throw new SQLException("simulated prepared batch failure"); - } - yield new int[batch.rows().size()]; - } - case "unwrap" -> null; - case "isWrapperFor" -> false; - default -> defaultValue(method.getReturnType()); - }); - } - - private static Object defaultValue(Class type) { - if (type == boolean.class) { - return false; - } - if (type == int.class) { - return 0; - } - if (type == long.class) { - return 0L; - } - return null; - } - - record PreparedBatch(String sql, List> rows) { - PreparedBatch(String sql) { - this(sql, new ArrayList<>()); - } - } - } -} diff --git a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfigurationTest.java b/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfigurationTest.java deleted file mode 100644 index 8117f110..00000000 --- a/modules/sinks/tdengine-history-store/src/test/java/com/lingniu/ingest/tdenginehistory/config/TdengineHistoryAutoConfigurationTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.lingniu.ingest.tdenginehistory.config; - -import com.lingniu.ingest.tdenginehistory.TdengineHistorySchema; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryReader; -import com.lingniu.ingest.tdenginehistory.TdengineHistoryWriter; -import com.zaxxer.hikari.HikariDataSource; -import org.junit.jupiter.api.Test; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import javax.sql.DataSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -class TdengineHistoryAutoConfigurationTest { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TdengineHistoryAutoConfiguration.class)); - - @Test - void staysOffByDefault() { - contextRunner.run(context -> { - assertThat(context).doesNotHaveBean(TdengineHistorySchema.class); - assertThat(context).doesNotHaveBean(DataSource.class); - assertThat(context.getBean(TdengineHistoryProperties.class).isEnabled()).isFalse(); - }); - } - - @Test - void createsSchemaWhenEnabled() { - contextRunner - .withPropertyValues( - "lingniu.ingest.tdengine-history.enabled=true", - "lingniu.ingest.tdengine-history.database=vehicle_history_hot") - .run(context -> { - assertThat(context).hasSingleBean(TdengineHistorySchema.class); - assertThat(context.getBean(TdengineHistorySchema.class).bootstrapSql().getFirst()) - .isEqualTo("CREATE DATABASE IF NOT EXISTS vehicle_history_hot PRECISION 'ms'"); - }); - } - - @Test - void createsWriterWhenEnabledAndDataSourceExists() { - contextRunner - .withBean(DataSource.class, () -> mock(DataSource.class)) - .withPropertyValues("lingniu.ingest.tdengine-history.enabled=true") - .run(context -> assertThat(context).hasSingleBean(TdengineHistoryWriter.class)); - } - - @Test - void createsReaderWhenEnabledAndDataSourceExists() { - contextRunner - .withBean(DataSource.class, () -> mock(DataSource.class)) - .withPropertyValues("lingniu.ingest.tdengine-history.enabled=true") - .run(context -> assertThat(context).hasSingleBean(TdengineHistoryReader.class)); - } - - @Test - void createsTdengineDataSourceWhenEnabled() { - contextRunner - .withPropertyValues( - "lingniu.ingest.tdengine-history.enabled=true", - "lingniu.ingest.tdengine-history.jdbc-url=jdbc:TAOS-RS://tdengine:6041/vehicle_history", - "lingniu.ingest.tdengine-history.username=root", - "lingniu.ingest.tdengine-history.password=secret", - "lingniu.ingest.tdengine-history.maximum-pool-size=24", - "lingniu.ingest.tdengine-history.minimum-idle=3", - "lingniu.ingest.tdengine-history.initialization-fail-timeout-millis=-1") - .run(context -> { - assertThat(context).hasSingleBean(DataSource.class); - HikariDataSource dataSource = context.getBean(HikariDataSource.class); - assertThat(dataSource.getJdbcUrl()).isEqualTo("jdbc:TAOS-RS://tdengine:6041/vehicle_history"); - assertThat(dataSource.getUsername()).isEqualTo("root"); - assertThat(dataSource.getMaximumPoolSize()).isEqualTo(24); - assertThat(dataSource.getMinimumIdle()).isEqualTo(3); - assertThat(context).hasSingleBean(TdengineHistoryWriter.class); - assertThat(context).hasSingleBean(TdengineHistoryReader.class); - }); - } -} diff --git a/modules/testing/vehicle-identity-test-support/pom.xml b/modules/testing/vehicle-identity-test-support/pom.xml deleted file mode 100644 index 31fad0dc..00000000 --- a/modules/testing/vehicle-identity-test-support/pom.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - ../../../pom.xml - - vehicle-identity-test-support - vehicle-identity-test-support - 车辆身份解析测试夹具,仅供协议和接入模块测试依赖。 - - - - com.lingniu.ingest - vehicle-identity - - - diff --git a/modules/testing/vehicle-identity-test-support/src/main/java/com/lingniu/ingest/identity/InMemoryVehicleIdentityService.java b/modules/testing/vehicle-identity-test-support/src/main/java/com/lingniu/ingest/identity/InMemoryVehicleIdentityService.java deleted file mode 100644 index b9d97bc3..00000000 --- a/modules/testing/vehicle-identity-test-support/src/main/java/com/lingniu/ingest/identity/InMemoryVehicleIdentityService.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.lingniu.ingest.identity; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -public final class InMemoryVehicleIdentityService implements VehicleIdentityResolver, VehicleIdentityRegistry { - - private final List bindings = new CopyOnWriteArrayList<>(); - - @Override - public VehicleIdentity resolve(VehicleIdentityLookup lookup) { - if (lookup == null) { - return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); - } - if (!lookup.vin().isBlank() && !"unknown".equalsIgnoreCase(lookup.vin())) { - return new VehicleIdentity(lookup.vin(), true, VehicleIdentitySource.EXPLICIT_VIN); - } - for (VehicleIdentityBinding binding : bindings) { - if (binding.protocol() != lookup.protocol()) { - continue; - } - if (!lookup.phone().isBlank() && lookup.phone().equals(binding.phone())) { - return new VehicleIdentity(binding.vin(), true, VehicleIdentitySource.BOUND_PHONE); - } - if (!lookup.deviceId().isBlank() && lookup.deviceId().equals(binding.deviceId())) { - return new VehicleIdentity(binding.vin(), true, VehicleIdentitySource.BOUND_DEVICE_ID); - } - if (!lookup.plate().isBlank() && lookup.plate().equals(binding.plate())) { - return new VehicleIdentity(binding.vin(), true, VehicleIdentitySource.BOUND_PLATE); - } - } - 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 new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN); - } - - @Override - public void bind(VehicleIdentityBinding binding) { - bindings.add(binding); - } -} diff --git a/pom.xml b/pom.xml deleted file mode 100644 index 464b70c6..00000000 --- a/pom.xml +++ /dev/null @@ -1,379 +0,0 @@ - - - 4.0.0 - - com.lingniu.ingest - lingniu-vehicle-ingest - 0.1.0-SNAPSHOT - pom - - lingniu-vehicle-ingest - - 羚牛车辆数据接入平台 v2。 - 模块化原子能力 + 协议解耦 + 业务/实时解耦 + 生产链路只支持 Kafka。 - Java 25 + Spring Boot 3.5.3 + Netty 4.2.9.Final + Disruptor + Virtual Threads。 - - - - modules/core/ingest-api - modules/core/ingest-facts - modules/core/ingest-codec-common - modules/core/ingest-core - modules/core/session-core - modules/core/vehicle-identity - modules/testing/vehicle-identity-test-support - modules/core/observability - modules/sinks/sink-kafka - modules/sinks/sink-archive - modules/sinks/tdengine-history-store - modules/services/event-history-service - modules/services/vehicle-stat-service - modules/protocols/protocol-gb32960 - modules/protocols/protocol-jt808 - modules/inbound/inbound-mqtt - modules/apps/gb32960-ingest-app - modules/apps/jt808-ingest-app - modules/apps/yutong-mqtt-app - modules/apps/vehicle-history-app - modules/apps/vehicle-analytics-app - - - - - optional-command-gateway - - modules/protocols/protocol-jt1078 - modules/apps/command-gateway - - - - - com.lingniu.ingest - protocol-jt1078 - ${project.version} - - - com.lingniu.ingest - command-gateway - ${project.version} - - - - - - optional-attachments - - modules/protocols/protocol-jsatl12 - - - - - com.lingniu.ingest - protocol-jsatl12 - ${project.version} - - - - - - optional-latest-state - - modules/services/vehicle-state-service - - - - - com.lingniu.ingest - vehicle-state-service - ${project.version} - - - - - - - - UTF-8 - UTF-8 - 25 - 25 - 25 - 25 - - 3.5.3 - 2025.0.0.0 - 4.2.9.Final - 4.0.0 - 4.28.3 - 3.8.1 - 3.1.8 - 2.2.0 - 1.13.6 - 1.2.5 - 3.8.4 - 2.8.17 - 5.11.3 - 3.26.3 - 5.14.2 - - - - - - - - io.netty - netty-bom - ${netty.version} - pom - import - - - org.springframework.boot - spring-boot-dependencies - ${spring-boot.version} - pom - import - - - com.alibaba.cloud - spring-cloud-alibaba-dependencies - ${spring-cloud-alibaba.version} - pom - import - - - io.github.resilience4j - resilience4j-bom - ${resilience4j.version} - pom - import - - - io.micrometer - micrometer-bom - ${micrometer.version} - pom - import - - - org.junit - junit-bom - ${junit.version} - pom - import - - - - - com.lingniu.ingest - ingest-api - ${project.version} - - - com.lingniu.ingest - ingest-facts - ${project.version} - - - com.lingniu.ingest - ingest-core - ${project.version} - - - com.lingniu.ingest - ingest-codec-common - ${project.version} - - - com.lingniu.ingest - session-core - ${project.version} - - - com.lingniu.ingest - vehicle-identity - ${project.version} - - - com.lingniu.ingest - vehicle-identity-test-support - ${project.version} - - - com.lingniu.ingest - observability - ${project.version} - - - com.lingniu.ingest - sink-kafka - ${project.version} - - - com.lingniu.ingest - sink-archive - ${project.version} - - - com.lingniu.ingest - tdengine-history-store - ${project.version} - - - com.lingniu.ingest - event-history-service - ${project.version} - - - com.lingniu.ingest - vehicle-stat-service - ${project.version} - - - com.lingniu.ingest - protocol-gb32960 - ${project.version} - - - com.lingniu.ingest - protocol-jt808 - ${project.version} - - - com.lingniu.ingest - inbound-mqtt - ${project.version} - - - com.lingniu.ingest - gb32960-ingest-app - ${project.version} - - - com.lingniu.ingest - jt808-ingest-app - ${project.version} - - - com.lingniu.ingest - yutong-mqtt-app - ${project.version} - - - com.lingniu.ingest - vehicle-history-app - ${project.version} - - - com.lingniu.ingest - vehicle-analytics-app - ${project.version} - - - - - com.lmax - disruptor - ${disruptor.version} - - - com.google.protobuf - protobuf-java - ${protobuf.version} - - - com.google.protobuf - protobuf-java-util - ${protobuf.version} - - - org.apache.kafka - kafka-clients - ${kafka.version} - - - org.eclipse.paho - org.eclipse.paho.client.mqttv3 - ${paho-mqtt.version} - - - com.taosdata.jdbc - taos-jdbcdriver - ${taos-jdbcdriver.version} - - - commons-logging - commons-logging - - - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - ${springdoc-openapi.version} - - - com.github.ben-manes.caffeine - caffeine - ${caffeine.version} - - - org.assertj - assertj-core - ${assertj.version} - test - - - org.mockito - mockito-core - ${mockito.version} - test - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.13.0 - - ${java.version} - true - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.5.2 - - - org.springframework.boot - spring-boot-maven-plugin - ${spring-boot.version} - - --sun-misc-unsafe-memory-access=allow -Dio.netty.transport.noNative=true - - - - - repackage - build-info - - - - - - - - diff --git a/reference/GBT+32960.3-2016.pdf b/reference/GBT+32960.3-2016.pdf deleted file mode 100644 index 8b9a945e7b28d51b08612a94c7626c386c9aa7d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1152211 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%0DP#67M)fCOV)4UG;W^=7kNm6;Qf$_VS0 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%0Abkx?9>N2j__{6=2U1ai=1LH*{? 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[_&0e(RBBY_zL1lvVB@0a69|+&_L- z3Wk7hFa2@U2QN)z`lh2ja3}})EAwZ4?mNDrakmw8h>9Y`Qwk>Y1kowX9K!w`1(J3| ztbY=6KKL90HFP#W(q)MHi9OF`Ky+i_)R6O#Ln&q-*~S5{|g2-IXcZ`A|PS z(c5VfRlqfoW73R z-$W+{o_{dQCo+6ld&t8vM_~>o2aV+pq_x&gBa*S@_(sqYv5S_bZ-J^_BKFeL!jB=~Y1;V%JaAOllfa_?68;JdjZ}h{YATBBub4RvY`Y@%HZlSC{a@Z?H0}jaKTGIh zJZ>7IgoL6k4UyVJhB&}{*0)^zH|VoZ`eRtD-2$^vG?JH9EPm(5jXS$&LZGfa87v*n z@@IC*Z0gj?Gxr%8sa_{BJ|Arko7cFr2pvc@h$}Rq-xjThTN%!o6Xp7#{J%Nbl$>;n zqnxG{e^lM0iY%I|r>I(gn{j3qGj&O5{(ko6l<`eA&%Ft`?Pyu)B1QWcE%DMV*5~rt zY_n!DN8QW>;^$cWB{?f`NY@%q@nhr`0U(a8p$i#qzgh09tBO1u4VU#S8;dB1BvNIz zm1;i8-&Ke0GTuI}_`;R37;bUb6Wt;wjGe4s5G!vTL9N-pR2N!VH(}^5oZ~Vg*2Uk- zuykuq5b3D5VYCD2_kVbYC+6M9R4ztk3bpc$oVh3TM4<}fyPMC02_9`4H zw8A#d_L{j`sRIvFbJylL)m_5M)CiSs{Rk4 z_5W|v3TCGNm8dG$|G~QUzt;YLMpe0)|IelsEqZ!&cx?&3|Ane5Be(pUrXYez&+x9} zjB@3OXo0~LC+Lnnd=k0*RS4Pm<@d5oxM-@DkevXkrO{RuRTlN@{x-ha@Av21$+tGi z_TM}O!k^vW?hWtTf4@Ek^Ax^yN**V7KOa9Azdo+Q{{DHp9?kRnynayl^Zx#s_4QFV zyRY$eKj{AZbN_}=AeTX5b@8j)ZQb9iZ^Kd7 z_`V%iO};6Vwf*MteO+Cx%k5d*{p0rpgS_%(jYN`~6P0Yw{hKt~D2{O@V;sADOKL|g zzXH8HBPSRH@ZPs3=Hv6qj+pnBWmH1y$XfngiVUN&oK+9S583{+}eZs-uMj_ zoF9q4FCEUOkGqX3GI7A>MlG77vSJD&_?PyOO}+bTIj~xNr7O$XF(?{*J}l}m?DC-{ zu*E@@_=FV`D@s!N;>;Z15B)0DGHGb$Eq=^_t>h6#phu3}g*IDLWAWTqxR01t$fkaw zdqKmpuxjU#aeF5AGH1M`%Uc4z4=g9WgpYcK8gIWsjH=VtWO-=R|8>|J+@Er4#0z|2 zU*>IsUaD~MNkptv8L5l>mb^ni9aK{KlmjCcvL6zcV)(?mgA(8fR@$K9=bnXG%KpTP z{FuQDR}#1~nt6etm5by{k=!FWoGOKXK*y z#oHocm{X-0j2n(w0QD|l2j85Sq>>J|VGL_hee7XYbEKD^U&Rf$03wI{hyqe$MZxTi z7ze=U;6-3-7(w~7GRrd~j=|0h^I4gZw4EUmKqW*lGr^(J_E`d;MM=dSMG4gV^0_r> z!YGXJ3X$n!LFRgeLOHtxr$yuveK|x4uR%@Lp4e>=CqnNa#bF&%Z@Cd+ zGe*#!`-Z#9Mi3eOOe9tm$I_yvA%Vq)*b&NThvbO@N-AWQ_a4xwI%2{}3##bT(vXZV zU~Y^vGDuuq*jC_+D0A_1vE4^SbocWkFuRG_W=9xGFabESoro&fr=KB%#TVES>wNxn z6d6Qp=1-sx>861wI78#8HL6lXN}!A<65F*ws%Go~9ROn|B3kq3o-Tk$ zF=pIqy5>jEFhXIzivO6Nv(a-8O`oA%8lVXC{Bu&}B}2qL1gGqj%sT zFT>Az*0eKd$DBhXg62c4tk3@FjuFD*r1fj5av$&tN2x$HHa;cde3)4?+ZR#O)vJ90 zqWp>3p_*EVWGTPWLQn((cTthf@_szrgRXi+vO|)D`K2L^K;=g+I{L%rnsPEJBT$S} z-ufv=tAajZdfG8%bpk6Uak5a)Y7z`AMqdjQeZCQ#bzsT*rGm7!kuPE< zl*&683d%%D2Z$#dYYdodlAQpt{kwt{l_e1#$7#<4(m97i9i+5bJ50vV^PpspJrK*| z_HDyeI464kIwog4S+*uZ-0ylIm`{&kSjr#>l|38#_47O57Smk4P5@P-nLuw)YJt>p zeSMps;m^x#3WA`nY%?5Eg|Q{`j_Re!No)}=8j+@mF>+Yy#X7C^aMkgGeGuht zr&#^=Y-Rnuu-bQ%>?VHpkfh4!9FLaX1>@ErKJ!kOca`#Rk?~@egk9|#BK%02HWNA= zis}ijH;1m5zn{FxCyODEr3Yhz#LA>Mwkp&KvXgfv+#5R=k^>#iV2YN`@av~wXS^J8 zr$4t=VT!et)^ zsUxMu9J<9Rc#}gY*p#fTS}~UhW9%3X0GrK15;&LLub2($9PAtl#y*S#Ka3+@FFG@a z(pLg5WtQ+?V|PK$P&ETK-dGk)->X2Uaf^V)9>Z7AWwNFNEc&j8xd%}!HL19M6VDjZ zWET*=4B$u~vHfRm{=Q&#LCeTRcK;W@Nj9-w7&o}f*8t9fYc%UG0>PPhS>&1eSAT91 zmVPeq1JelgN^;9V2p?{Nb2t1AriKjv9~4YUq}zn>kRnQ?}GLTw*xWF-D21=EExARZjb50k2`TuNAmUExcJFMfV_ILD}L zCe3UNB|*ILxLna7?*;<^(F_?TUb8U9wsUt!N!N4fTFS7F3h8HF$Rv( zNRrzW06z33G-Od*O@=gNVV>X^-JG4VG0BRYwyw7S?-@0X7zZb1A~*N5{7B_tS-ohH z#_ik>8|yvloQAPG&jylA>+Wha7R)IMS36=N+ovJOK53kCh;(spLwQcU1L>fz`ys52hA zAA)}w8-+S!$Dy*I6*(j+!hd}~YO^5Iz;USXBy#PF)V>_p?-O15rmKU+fHl~Ste`An zV*sLpOgU667aHINL=2qxUG6i$wat7l9F5RdG0=4HBpouDH6)Phd_HPmwTfCPK2V4z zbOjF25f}X-XaW+_^xPwaG}bGc2-a9g;?gNBL7JolOac#7Y%5=a7R=DI$c~Mm#$m`P z&QI{nR17v89iXh#EWbDL^23dG#tXb_Yw*2(0(?T@@LzRux1}Ej#k$fNtL9An_T(ZZaby@~jc83|4p?s;;BeS6%3tAp!Y`N?m3mprnJ0q@2ceIr3 z#Yg@CIz4iL)g8!%%sg09njsdqWQ84(BW!nOu45OxluQ+5IUO;kgpm~G6MqDeFldqp zV(Cr6hySZ`xtZ;+)5I>7SXh!58_rxJmT^EfTtsl<_G?0VGVB>li2AqZF0T<(g(3+! z-98*vHsCb*V5q;D?r(oYrHTX*mM29@>lv#gUIbfxJFTwoeP0Z|Vjfp|6_7wtD}Y9D z9-vBN%c)MhUoHD|yLm^QvK#<)Gs}5$gl`}m9;njo!Uvl#hM^#Aqf}UU8Z*Q)sbof{ zBMK=M;Y`Xr9=U{~R7RpEHOGRPM<%Uyut+Se6rnzHfbL37C;JG|o9JprCoPqU`%5g@ zloLItSRSP&98poM{Rco1wY+lLQa3*lNDkzF+HG0?-DbBkogbofu!EdUXOLoao?J%*t2+{!S8cdI zT`kgI=+qt^LLa3mw&o>TO%z8(MrqrNIH9@sp*1?Oib8C20;=5D2uo*X`%{pfHjBs@ z^Y}l}x=gKpqNN!Q+Blxjy-8EFCTIShuWG(h`zjiWI?|*~f)u+PR|bp1X;oD785aap zPr){1-EpVgyxKub#pgV0S$___=hORim4zNiho+)%+0_oBtUiQjl}TY^IMxw{9^ECo z!UpwVD_FIdTfE?TI;6AA`T^>WhQEyD5-O*_p^0WVx{KUJD@nGW1{GyRm>6inbI!fg zs&3Z>&t%P5M?vWL6DfxIS;q6bTUjL}nO=0zYQ2MMfS+k% zCO|5%@pzSoaZ*_yV&0)8c)MZfSXOlI{KSSGr5vU`1ZzJgnyVQS9@T zW|{j%9$POjEd*DjB!7mF-C`s)dU<6rl}sRjB$1dp;aAeU#UpBY(dt=sw2e{<00eTc zzZHL0svoFGp_@pep%w4i`*?2;0<2K3K}d(lY%Uv1IA~?)!3V1zO(b5<^LEyyT&U+9 zI)m;`sd$$=$9hheiE7lHaHAn6%FUeIEuW2k{I{I$07T4? zH`(8%af47&Nz`gMRNko|kccL6`8gn zfsk{I{M{ZJM-bbuK-phY7ovwrb?YL8xjV+tlB^mdH&$AF@*mq~Jw{raa(}KP;y_2${AZ|-T47!-rzJsn7YoBtY%s0QMJ55=9 zIJrw@Ew+Y-HEAdR?P#uMJeufs zV-o?#HGYTkUyV-XKeA$o1oc_MCMbS{b+T`G1h?-ia;kSp#jH7&>tw~)hW|KF(KI`z zdB!^*v6PTT){r4mfn>ltOQ*%_&Cc`bLIeVKW&3pZ)5jBjhVZgIIl7A9L5#sk=KTs! z`AxK)_fN*ZLv3UZ;?-0Z5~0KaPTa?!=ZB=Mm;qS9nF>w$KKO;2QG~6oKG;t)F+%DLY~@wT)jZX{(z(q?b^8mwl@ge@ z(6$_;_uAh1rT5wC)lTZG&#zmuItZEaTC)E#fmURkIa$9*sEO6B>rW6-mo=!B%hHR$ zEAamIeCt8Q5k^pE8LGFdiKQBuhGKXIFR7)pB(VaSvS9`j4ckQ}&9o#Pu!m98{%4tr zy6{tbDG@%n+GICyFPV!E8IL_|kU2%8^4s|!AGI?&|2}wk219izfdaJjp&dG!SXfj_ zUzo4hTcfEGOllKV+2Z5U37X(9pj4jUY!*?VB!@ueL?M8I(`ScSwKCn|9g}T+uV0to zu!VY{%!Jm12bV8>;Sf>P^Fk~j((X3yrstvK&^jFV?>>x@-4r1S8_&5_%LuEJivrBS z=?)C_{M|n@oEiq|A#r|49r+20H7C?#vjpKp*xu~shafsiGg=pJtA}9v?+YL92!|F3 z7B!;eLcst2=*cTKWc%pUzE6dKeUeKnvSMVWF8~lSS@>X`uApxS>{G2wpZtK&fRf^5 zmkSJy(&}e=k{P_pYw`w)aunOQbbP!xnPW53Q?Lvv0v6#`{}ek1V_#;Q|y>F_`ebNmcf-Y+j^#$nVFfHp~YLw zu*J;GQj3|HnVA_|%+O+1i&-s}THMz8;$9!yGY-#0*x?_$VpS#XjLeLztS8^qk)Z|Y zkrsaDQO>4$8K1|&G8xl;7$7>W+M z0J;YkYXu9m^xP!!0L!kj3;|!OxqeY2#0Q$FU8N`DWxHYiWMzh4?^a*vLkN*-zS-mL zI^Mrb6;)IIGy0ur=dF7)ubTZMreL85VJYv%#otcWkuX0O?VOab$Mlzs<{$?jwW>i8 z`)Z>BP0=zZ^*w_=(8r4}vtVj5BHc_OO)lc-Ym2Z=Ah+h`=xiCi>&u8Zvf5fk8&?b5 z%|Yn-XE@GIXrOy4gk=yMn=bs6=+NUT2&5H+%z%z~Qgvwa3!@jSzH{TfnD@d>eJsUa zW>D3UQgTWnqFqHkT)XEHHf}%zY~I1AK9%w_7(wo&8mffb9%RTojT;)L8!2stGN@3} zIZtLqZ~Vp!!52ye3%h}w!BFS@EA?$sDQhBSqlN;E)isUw-z4Z5vrC*~ru0~Gt3wYb_+*Vnm< z-FCDya}l}u?x4T;9C8I{82Lu%arkjU*NCiq>NPh0BRQG!xA$*$Fj0Fz1vxd87Q~++ zSzEY>R~G4VOU3gU$7(oHtgfQ_tcYf?kwjUlA`iTfpi~Ifw;@PVpif)gRLt?El`MC* z0mO+BBDhW=NE3Pm_x*{J)@u&-GV`FlxP)<2>5{q+(-tO2(?4?zeop@pSdvwf7UKY+ zCH`ICGlmGxp6XWts+hS$b>-{e=TtS+s)ZohpA{rr}}K#vp^LK`7y z>mpQ!NKK8VXiCa1ul3e#eqwKGm{zq~L1b{uAHBTl$HBIg1`37~6;`v;pk|Lfwus)W z)pR1D_W1VYP0S%82|WIZ9>e~C8SXQ<9?p&oPfV~^3pP=z^}x;=lhg+xlM=lk(h~NY zrKB^ej0^cOCUioy>FA-*`==RGmp<3~vU>MD@`xz=)ix0%S$kV&o$c_XzNM>MYF7Rh zr5P#Gu(s6vft`5??ef>Fz*3lPz^5d(MaCgl)h^}$PxXy7cZge(H z(SkmewwTT%B=}2xpXpY*VYH0tu*l0hC&=?hW%ODj(uz4BLVoU`$E=`(9X-=5rjNg0!V-oBsc2Xv+nrc{i zV^<7H02=up=`Fmg+CIVCsrWrfd}AOf>sE&BbGB!)KrzKQJZvqFXJ`b&E=+A;&tVhb zj%>iyfichVuIOZ_3ZeB{Gf2AU3M6?#7Bq~CxsoNR2f(ua)elx=;4iN1tthgemV6Lz{|0MX%77%Cs(A8-7d0@H?O_Z6 zb)>P%C+PhrH^jv&c1)}pFE6`ZMcG!ynI(IQr>>z%*GkrOpT{e?*rq2hCTfB!x}$>T zwA}(c$uX$*cK4aTdcbOji`GHKE;fAPEK-xNyt@yPc~3xh$Vl}QOK^2)#gj+zZgQ(m z*me@RWKhY-MmUFh&qsG?enB*}S99h-!I#bGxFOK7y;Jr#&^alsXo4@ZqY;>ScW%Df z?q*ZUt@vhVTxGh%QlzdPL3LrezhX}Mi>Am83A?s!VmNkQW)54kn-Iut6qP8)38P5W zcCl*;=G-WNzHdCxl{J?Pi^^VJ?!E0x-76~m1iO`W1x1A+a7+dJLVNOxm4DJ~(%gjT z914-#;ReD`j!F7m2DZUSJ5g3vK0AVH7;VExL^Yb1L9T2mU(Zx1HBFz2fF}o|ZUnPo zsZ37S`T{g>XIoACiVgMT2y~7VfG!5v%luH8T%-^P3T&Oce%?U zX>=P@OB+*&Aspn%L}<0S{LTVhB|PNXaBS7_ugt4eU>8j}gg-1YR%WUjJGQ&tI0$nGXfB_qULD*a0CX4&|ZYV`%9kbA~;7z!Rmqz8Tg_k zvW;vk)@mFNF-xlD|phSn^zn*il~&*S6+1 zv`gp_PPh$R8vPkIo6vWvX~<*$K$}K$$7UNiXUtwV_zpNn%$C+gKL4GHWN}8%`KJiL zC?!-8tP!@%I@euT;-j=v14gaMiGrxg2}>99hXYL*iC)!;LNA$@ibl9qMX^#nK7sw9PSK;WE6*> zpSj!&viexcoW{ox@)jQK;YtKT17Z2T7NCM_ap&LBecd3pPJck8+nd4HpGN3*;TZ^V#Iq#vJ$v`8VVP{UF1g;Ra+Nb^)9K2qic z*9nXly;O)SyASvB*|Q5lyo( zF(eZyUm}~+qWvUCHh;k_sNhV(imM>u`-5y9(Se z$=X=<#R=S0fO|t7V8}NB1aiOT5RMX;_xi;YCEo9_X^O&msJ+GP28~^3aT#c#o_rGfgqA!_w=TF28kHH*4?bW(_uF?ozEP zL{V5a3|n$m9JT<7h?;>n#$4g@utwb~wy=leIz)?KEfEHPHP~>hyOplQJ1Ei{uV&AW zr{cvhr9nDRZpRc&WVLtc-a<~Jn1g^g8ti_LXsMD7=CZOKNO@(!)R=H^QO98H!+G;( z$x^YO$r%6MIpy(}KbST@<5BDB&mhQ!CSm#Q|=!Boc(kGEo%gX=4j`fO^zdRQes|wGAO*s zQi>+=u3!io&)fa4mS_de09vG4WGID7cM}Ym*&auG3v~csM){rLqW3W*gK{YfRBrFB zTE)F}WUKag5N|D+aT)HxJVkJs58E$okKDy#H1*b1Ng9j0HSDAKY9ef;uN3^;NUO~h z1H2(PVvqj}%^iNI38MI48zp}tPo(H#6Rdp=^_#!FUD@8q6&Ac-ngO)l~YY&$UHZ}$~?1hPf_q>7KAvs1f%}QrOvoPt;5R8t#KTb z>X=NN=F~##I(j7#Mh~ov4v{uRnSfBo7NMAOE6Fe`kf*A6?Z8xZo*FS_ghi+E2ps^U z)Yvc%N^k42nt)qnX1c-_TcI zToNnSKWY;Hx3nx++5UG@SN~{I{8ufDfAaocQ&&7}JpT`8Q9AV$?M{9MhJS|yW?370 zdkhkSA&;wQ8gg4($e5vo1(TCXM>yh^ceh9DXODJjBh{A z(tirSA7wiT6YCr7-Jmxj?dpA#T}o|y_5W3 z{*S_cU)KMg<@dcE|J?bw+Z)>R=_UAl`;E_r^4EXV(whX@>ZpHx0ESP?{rRZ7-Qn}O zdg#l;Q;u-2@BQa2QVN0!;TLiB8(&c#>478!=TwBA4aVvDp_u$_pWf^F(;@DD@9oBb z>SGn#VaLb!egFM)aqmbmT=DPS)65L($Mz=o_kg)8MHiI9>m^G6!iVu1vNsQ#&C{~aD8M&DDR&~J611Q~ z&rI|qr%VhGRS+!IG!MPS>nHVf`gjQ@NDFulMW`?+sc;j&@4B#RsVdwqp2<*P4*$Xx z%pVU&dmupe{d?iy_xkDhepT+_|9oyH$kPo$t%k3w9^$lCVFtcjDO~zd0IFh&xWtpr zXMOBf@btT6ap|KVGn>}^SFTPBi1n{;+XH2wn|C60;WL>;E%G0%RZw7XI&uJh?UfUT z6WfL+PKz^np!M%q7(YsCKim_R?hJwWF^9{lv(Th>4{xI=7`Od8RJPS4Bw_Kj8+h&L zczp;kQBEom?xG`#gphXBR`Y?9{hwFJg@HP^`WIGvt6k0XRG;zJpUi#dF9Z(~&jgPn zLmt*=1-8vz`W<^Nx*fyZ?GZ#UJpsI-T8E@C7=AFPL7^B5OJK%< zs;0bQPDlo$%6b0gUutpTpwC33q<+v)Mf3ijTl@LYbbip1boa<;o~LB0F?ub~F6X73 zS--dhrLn4apNOmu%@+7o1 zUBC?{@!Pr9ZJVx&(xxPbc!mgc&Y{zQ!BJ#xsR$M?d3S)Lyfq2mQ*e!0)!RIVNx2!~ z3?JozMy!s39VJ>HxQwasoEljogbEpXa$8u$-2|R!GuXIzg4%El)gSf+Q$p5aARTP` z4%SOLJy(QAOciFy`7Ss;tU_|iUq_P8^nIK&vT5 zHk)w=oIeG1*@u4wd|mzwk#I;i@t`%?bt4K^BMMK>(IDnw`oID z#5r};{T0fvqSBZ4pCqGdm8~brr3}4y6F1JF#OT{uz5wzyB4)MoF|0UfXjN{(4}BQ9g3FdCrmaXy;QWWb@)U% z)?&ooNqDNv56*hWw{biUUAAsZeWO@pzm*4g{u#;x#aGrdHBmtJw(_yN#MJg)>_#=k zDD96H$RbTY%43;{5xjhF5+*3sh@{KwA~yTM(Te^{UA0R`z=GZu{9c7AV&D#e%I73w z>VxE#{j}GlNfVUYY;%_>o^i;`1U?}@X=gm2hwP$*pY?&DB={in&4F*1K_mw@-MaML zMJ%9v38L@SiF)4${k4vNFy@Zf5G;m>j(ykDUwKTC`TFpdyea31sq7mX)@(JL)x!PE z42YkHir{_rC`RrUbsJhb8*9As#i~GzV6tsKj%>_8+P8PQh9}9hBk_>7cfYZi;w|cw zLBJdiB$;3H>7^YZ^Or#VzAZ^hh>L(pv%7zIwiD-Clx5ryn_K(z@y5uJx&6xB#@fSS z^6zVV_7lG4-5UOHj(>N)LBrhgI|i_@S`v|nqn&~nnJx%@ivWYmm%N>JlG_oCgHGC- z0-$+&kpOdu0QIeie!~?Bi{KH+p57$BV5?&1k^+K;YjRQw$SdG7<VQ9U)y9-Kx})Uro>xtKj+It+;?bhe-c z&LVf#v5&TXp=vf_2Q-(J)$%+B0CpCWD6MKi@x`N{neXUW!J@-vrIBzx9%a(CT=N9hx(@)66>C#kGSshF*d-f;JAW+|yJ{ z3bpjS39=2%K9qOQ=ZNucdAmSbImXfE=n%*vf7uVN2u;DE!9YBe!DwfkXPKMyR0^_o^c+#gZLOel4c@XW1owWoQs**k6C{M zdSL`O9-+R;EaD{@kNghU2#OklM&m5usc2%LK^{^Nx}rP%JzCluy2<`p5|N;3+*y*Q zFRJ^y3Z`@v#Z8`+fogmL&l}UD{h3!eg)h&8>^U;4x@KO<>qL}AVar~FCcH@W;MORr zWFO8BD+T4p2$_K5h=ikc$nMFF7@Lwp>qviUU;yIo!jEq8)nbdpT?go#yR8+Cy_&_Q ztmo$L8uQ>!p!EiTt!e{0OL->NHmQrT;&9wbr4w1am~FbIX+tjvuA{gI>`GbN<=dhM z@@XK22S-u}5)mMcFP--tW9QszS`6&DpD!KPAG~a?bBz&x8?Mn7D#gtn6lS%vk=du( zg3b?h2}_L<=_J_Bv-IR5RYt-2S%Jps&RZpCT^=SXbW0xBc?X24ItffKoQ`#dzIoeG zyUG;SBA0HD6i3+G)^D3pU78}Zr;T^U_K1pW-9<-bB5`s0dp7!v22ZJrdJ_buqm);x zN5JBrV#j{!||v>^AGIaW| z|Eib0rU~Qe4_<6prD)17Tn6)Cat18N4KOBBq8*cB?L2@j3JUq80Za|x)E!F@jW)ix zy}+<2u84eMSxpoIAH`8$D#2p3sbt7A%vBZJe^654WGd>OLZ>n1LitTqO(Ro4@I)Fb znz$x^YHPK<5(ZUJu_?o7D@X#VsI3mFij~7By^(b<&D>1kATG$x>XQ0!4_(3fPV0z< z$3~jxl13bDO*v!j>*#ceC_!i<<|&Q1SL=28G)&eXRVuu9zo>>KX1OKJhT5EJB%y-( zT^k|ZPK+O5T#q_X_UuG5(vm(Qif=Ls-n8!yTah)Z)h>jfb!sI_ z$hHq!2EoOU1vZ0R|EK^OLfg=7_n|2?He1Lf5bk?hc`{xgt6dBg8~jps?>B6K|W z$mo`;uUZeK3{ERBSEMK~&u;|f_+Avm`8Eu5&yPe z<3cu#NVwI~#(E?@nW-)N2G$DM=z(dE?T%Zw6ypjA!Yya!ACSsDwtATcgi)=)iE?5^ z^3zqpG`E?mkphkyAUSLekeVyOJXS5joDdk8#W`8MRCD0LPK3e*44`ld%%r6*d|afa z2t^{6o_^4y!3!MV|Q1R(7y9$%j4?x)B|!1GyC5>n`QMs zf(Wr!FT~8c@1OrOEpC`HG|5=@mCJW63T z{4<+6%HS}FT8&$!Vr@3nNvqyH1)CR z8qfrCvG32Uw)id*_bQ4~_QImVYg!Yp$24_S4@;sPa{JD>NZcpUDv#VrDMY9x(8`lL z=q%s0i;-=sc&)4?Bk#DtQfnN^ zm642=rBWq`EW{`k5>0u1nSHhx$Rj8|zN905cY{$xTSBiEKD)Na3VpB7{Hu79n%||( zEmx0_9HMhBs|{Xi%~!QFXU3?;Wx(AUhH3jd^xlNj_EttYfkwAh#R(FF-8C#6z z^LuCHn{*~Nazahwq#wLnR|xBQg+tWcy5b$+q^A|x-eG#cN>ML*G}}0RblzLw2{;su zQ6VLkF7P0sb;x}ZCO6GD5>=BJPrq=`??C5-lDQ@~J8NT$X)TEfWO=KDw}3w-has;q zRQUZWN+2k<+{CL`3r#oZtySQ{mGk@^cE`{6{TY?sP~-%%K2IBS7ED_kRlN=*SyO~% zm0RJvar+FPmSW_LR5E>V*KBqf#FQt1qPY4U$7}kE5R{$h?wC_azU~fu{uC;xrm32s zQd09Jk?MOi#AGhcAJ#x?=hW&(g$#8xbG%WX@?m~vc-|SBh1w)ub-^wrSe^R<*4_KKhCyP)9z#w+~lq%v=b9?NG>26sTu6g4Spk1=ZpW(g9n= zA+=j0(M^}pFm0DnG=LdSDA|R%>wh#Xp(&(=Z78h5+)0v38PN-EmBSh77bOeY5OvIl zBH_{t-G$M=@Z;C8rRgrag%582?%ox*^jS}Jg6?s5Q+K4W-^9F zDeZ(BaOo*}2VywsA?%^jr`Nu!{|LCO8j6{bg?Zu#o6%}aS;=VZ+47dluzh9U?ki*% zp9+G#NOj*1=_GBw8D&W9r?NE$&ujpMX0T+aa~UPVaT$?Q&xJ_P4TlWWG_f3zw8^hM z^jZy)Bt0}sCbhwW1JbCb!$D2MVY9I-FD*9#6x{P6PG=*XKNh%C$0cwHRJ@c&HGk5g zxMh4{=<#RPSk0HaOKf^ut9Sdlh=-(l5b|Kwn$137F;7NsXbARFCKeTh>WuM_CyQ;x zLZ`|IZ!f|*s7GU)tFvJ;)bhUX=v4FOg3~j*J#F*V$EF(Qjnzo}Ra#cT1)~jwjH@mq zamSp$(39*}{4U3|A=o5nAe)B0GXz~C!~E4p-n5uGnI1{ZB&?U7Z~ zQ{KU^F>S|~4QRfGW_!BFafJF@QPnj2Iti6XrXmX?)MYMvnNh#~$2uOXB`E&}gGz^< z!eD;g@?nTNXK9u5Mw1B7*6SD)9lhfCVg}rb(@If8a~fjPymv(<^MiY6T~3`XO03Z* zk%j~ZVVrmK+%Ecl!=A*Hy_>$rl7#06j0~3yg<;ct3}S+9O2w`zd5F6E5;OQ}jbsLciz#IDI)fi#( z%S(Ul*R`vE`$J9a&s9exQff^qK7GlO88*LJWO@gZgP8_)8LXHjB%{Nj>f8Pi2`rytY_PJ0){spO%(XqRe-OyozzG*yUd;j60* zcz;58Lh1&9Cl5~hi=F+awiJQI&FJ_veE5|Krwr3{{Jx2BakFtbUm+!;Ma?YL4AVSe zcI6JQzg%Dcqo*!HCotp)nb%%pd%PY)Lhx^_XeOX{*0X&HS5Tm0g&xGJh)VD8Y>K60 zshGls1F2@7yHI6Q?s<^TF>okidO%Z9f*B;jzn525BV8|%7?~!ZU*__y9g`x0A>RHg z0<7GF0z6BFM;4!0uWEEiBB;=flP&8dew;}*B^-CK>{uiH7Z%m^m0W5$&B(NY^|Ash zs{FCTV(iUhiH!6RDaQ;ZGYn&2wCL2i(QMZA%BOoqc7haDoeWr zjC8TivK0sFrjTd9{d>uOHNu=L_=+cX^VSqII z7NcLK-?l9!CnuqmbXXMQc|9bBw42?KS=npiNRVpt$~8$YZd6P(JJKfs&0S8=)N`_r z4a>nM5rXssS*_6dpy)kIXb4xOqq8i`shsra`##^8TM3>X_36j~#-HAf^RjyE)Xlt+ zdV60_Fcrb95xgspx|rI=CUhR37W>3{2E&@njfVWTTK9KO*=ah~M94tx zzCK$RilJlLilJ&ugi+DWg(>8a;Ad4B;z#{6xH9Zcdqh3Z7R8k`PG|dwOQid3ODffF zXA?-XN=elx|2`v$X=|_rYz&4ijg=BHXX~z8rIJcm77-&gbi3FG>2(oc&~u zK?2-RV(P^x3*wy_(JFpefT zOX8PMczWtrr6e(f{XIL%6)?u8z@e{3utiBjDg0$6p{20{*&7yK_TeT+dR+l(0(cBy z47#hQQ@^Q2P1zPx`thE^l~6EDSgksf<#yKwPjdln->cao{VnIJsDc6UMNF2Y9vM3s zLfGm1;;_NB;M;^7mO|pTZ$zO*^*mEVyPnbdO_wWezq$_MNy^=svje9&w)Aes_SXnn zm!P9Chi;zKi3LE8T}mn#PBW>HvhruBW0@0(vtclMQPaxrM5p)M*M|cZe&R__j{RWQ z4KQt7z9GdLN}@h@kmRr-Z}PGmu3MSGXhLZ!781~)wULZzc2B)DagB`B+by+0oc{fd zi20XZ1{!v7*C&D3Km)YuhyD;6+SP0MkEEfv82fwL9VUH8jzk;-Z^w~ksL||tZb3-u zQFM5oQ+o9g z1I)W)ba$uy+mR|%N&Cdu3rH0eu{$a+CtNBzn$s^T>S`Ns4+`PW8+5vyyS4Ahjv>uT zy7oB&<6cixg)me$0sAR0x8c}M!)B^wi+0Je@#_ue!->Qc^Yv#!ss24H*W(p)f8N49 zQaTy4oOU>`Rb&9>UllQ*=~TOKF|nTLZE43e@^9-$DUqyOfRJVkmf4Fh1Y7}Wua&91 z$^zV<8`iQF@5BC zoWbH@$uXVfV47tH<%)4ac=3lIA+cwaq@ksMee$BQWU&qHk8}jElkXcdU1*O}Di4ri zcl6<+&KclsibIZ?m0b<4nt7n3V&5xQLpq>g67&SVWut5@ zeqk3-nOpaTA}89Yz}<{K_6p?_!=D~dp%0v8@B(Y>!E#qG(*-F;8%)sQS=BC#3|amw zHJwinmb{t+or0^{2K!1A4qYSvmT4~BVv*ad z=zV`>lw1vRpY6Q5^5gc8n&&P11cCiotY*fFU17%#JuD^ofvL_&bbQ@bjstAw)8QU| zRgHlhjH9vqT7&01>vz|dJCS=)2#sh>Iv=RU)BuNSiA@e@M>!X!2L{)0vw$y3yNGrh zYsbUoIA6yh|6q{YXnI_TuXsi|rM@Rkp>>~+m(-{Qg5$Th3GLC2Mj@;UkBm3SI1)bN zDYClDHg#bZ{d%Qa;|`Y&`T{N*9E2JJx2^!89osC`m&#IfYsEyofwRsfqB&+6>cr|; zUO9*}OH*G<`EyD@lHWUEL9RChkakk|X$x>G(LcW>+|K=ZYl-&|9d`cWu(1saso4=#ngS;2`JrqztM&2Bj8WC9Dt-SqWh^3n*5 zMLJpJvqo_6OdZOLCMq@-XZl+!MKQ!U$%)Q-(fyUjs7W`49yB$JvqC2BA-L_`^>bn> zlj>+8I=S~wh7@#xOt@lqC&G#xWUWun2X$1qZZUp z&$jm17v6)$UGhx~1F!4v3Zc_y{9Hk)&brw(RF7t5*2c0wLWRW@mGRV6x+~1=i?x|Z z-3abB`~$S9nxZW942NC-3;oK`05B;=DlVlZG8Ix~_zd+pZSgO`99CzrVyr~CNbUZA z-g1uLZS^TEf5O~-Z7k zhb$Y#dNx;S3g>N@cZXTza*}Pl*%fep^;XWTwQnmlDx@ng=Bc&HB|)|G=Y6Sg2{4%pkiqiyQIP1QxuL4R%+;&JXNX| zW{1MbF}Wh(`4zi?;s1pAsu?AI3e&>|>ikde5BC29_{YCdkAH(8{~@{ae+~5D_}>V6 zaQy>p{2TQ6SKj|$K#%{C^U%^$++@M^Q}D-$Zn?zE+OO@|dL1?*Y_rYTdVdD{aWhsOVO`8- zVdUt2pC3IsGn?IyRUOYCy}yZkwWb}g;*()3<81EOf8v;Jk7ZeQjH?^JRL$e;I6V=N1s&WimPhfuEKk^nF&xe{KO5qcZ$%m@HLefXKv!ZF<3)xb zVAr^nJ}1D;Amnw1;^VEAStBI<8g+4$GeG&M0P8BZ&ARKM!OXhV-%5Kl`}zsz6-PXg za8GPvLGa|ntAM)o=#AQfEvR7o`3+TxsQb8*=GOyOqmeV8R*K@0^N;lCV%N{WNwy8cGR{vJVc*0XSG&dN=ks&JsrEfqfHxJkQ){preta ze%ddJ z`qkD@-=!(tAanas``saRT^Z+mrsvq$r3)uZ~Lpr`HeEY_n z;;K{nKbgZg|C{lrX60&YM)UdC5?XmwP1o7$hL(g`#Ld;x!I?%(L0a|ePR!ZN$o1>? z^Dl@zh!uzlh%<--h|AZ{9K;o*4uk|m^UHPyG5xaGKv+OnLAd@ik(GnJxRI+FjW{10 z3o92Z8w(FB2P-Q(Cp`-fH46*%S2TGC)Bi;bC1(dyHxsk}f5rcIYF=#rV%Gk1y7@;N zP0`p|)zyw!frOKVg;~hxIWb z*~sGc)}s3IvYvrv?x(qV)~FFd87)%hZc-QucpX~^RmZVWQkV@c*(aC09f14?(HQAO zhz*YE@QD+iI9(kI`m-0V^YMun4XAT5Lg_aRIWdXvQEnBn`R#?Uhow$12RQREmP#gB zVJH=B5*&D@Wo*0Pt_rxZPGM;;`+KMleT2btk1 z(g&6Ir&0~DPVuEaVe{i48ohU64-K*hsieDh3EBr+ruw0WlZPOgCIy*rEpnFMc6(WoiH zocX<#^cdBf4k8WYyUn*^Gdcp}T={Rs1M8}>8RMdXMu5PfN-qHVHkbwFt9g|hAhAqi z+WXLzQ|^1!b4g+PTqchDq^nAB|6pS~M@iI6ZiAl+{L%=NT!vv(2v9<__>SK$!D~xP zZIH-mI4a6fhoG^fAoa(sZmv@&|QQ&;aC_Y zZ*~;t1SG-UoZJknVXGv=JkCPGBH#}^-UD&XbP(^DfFY%n0t`gj%M^;16%H9S_dB#| z2$TTo-UFMt!bXVO&hZPuq`=UPNn|8qi1^_pX_<^nifTAn0;-X2;CzPB!(@)nuqN$0 z8(57R9+WCe=`)*HOhO#E>w#jPSPhcD5`sDxlx0Z=RtqOD@udv} z7_`Qt3+5y&v9fz0U6H~+vluqS?~Fj%Wu$Pg7t3BH(OqmnX~Y?tL+d7=(+)Zz z$5KT{pd|aDJ?bMltU09xsXJM_D~xcUfr^e=UC0Rm92Sm%y|0p_cl^fI-r#9Mr8K;* z>&L9&hukPc1t&sL6#9sT8tRB-0TNAT=uCo}h{6Jgle_MrFA|!BZzm=m@(m**6o*(k?NlMVOC4U^-yk(N@WzxR&JkFtU)o0D%Aq_n8jT|;d-^C1ay(pqid2I#rqP}( z&fF7Q^pa$5xGzBS>IP=FrD?1;=R+$BKbJ$=>4dn)G(++ne$;ZRHW(Iiot*g)93&PH zf5I2RK;#Z|kOefI(Avo#bBP@bDg|+qxwb`dP%j`tZM)z!gMO@NuPj*8JfLyiMJ!S1 zeK*2!xQ6&+T%&PC$&v&gAVNL?(K-BjC zYB4=PX(*0Ac#3v}PA})7ELfsOblRBNbBjyc+QoLvT$^c6exRcaYM8>)vpX?QV~S*S zxPWy(uBG#JTu>Iqg~6Eggz-6>SISHCEQ7;54&fAQA$&rMyq?MovYmvNFK z6IU&jjdQ>kSaifwNA0*-_jRQ2!y+_B;G|<*ATV^6<{P@u4th9W3ue!teES965OQ#1 ztno4(D5)69JpzVvoe#SJ5j|EIvslLvjI$bM^iw^XY&LuLde6NwviNQ-<5+Gj(?8}w zJ%?#JE5X;k+5yD1AeL9!@SOA@=iN{@ran?680*Q2!NaykZnZ&RJ-v1iF}u?+LvYpy z=4#|qNJWa?Rs>qDf!Spe^~w2>q>UXN>^OJ&Ed=-HaIY52<}=KbwBeJqPa?}n=`?7D z3Ma}1C{Q0PP)Kzp-lHLFGa)ho5=>8V{C3lRXCZlU_==gqr6^58nS&Xln{r5#{j|{E2APP!(jt*RqZP?T|rA_ebeK~ z66J(bxGimFE#Gau zi#GkzfSkt@Pj!&Mf(lhnW@ihF!Su9Erct3pb%w^t`hg3W{GpL|kR3c3O+ks=(@{slo?b!A_5l6B#M0k_sI%k8ar7zFLUxFhIa^|F8i7_Z*a^#-~7+7 zOL=e1{Um}I&PnPT*_YL;gLC4hZxeIHSKm6RaTI}L)++I2h$KXo72@QmF$sYlnPFrG`56-ImCRb2ne*FltC-4=XLx8+%aU$WykrV>{1(p+G@~Q6W9FrW?tNxNaAaok!;@h;E@9Z`F1waFQtz&L- z`X*E`<6$+Dt2q=DIft-hJ@JW^UrH$aAup5P9nbt-?m2Un-2#4Vs-cwa&zLQ1nxXW* z!FqaQHt+)K>EN{SWS`lK3IEi|oIP<9UdSYR*~Z)&zN6H4`UlYNkVg}=C8cwFn2ePj zKPSV{q>$aATs^{N398xj>1bmroDCE;5uFk{F4pT}xQUz{d}vsNxw(gD)|Xu80h8S; zBj8%1?2KVfX}-ZeF=TO^3tF`+8D>|4ZKP0I_+)`?2PoJFKP7@*8 zz#0AQA^vbSuf*?A$ow#FBB`Gah0=5~zh%KTW3UNXcV}{$7a?TO4&liG>cOx6(KUjt zr6?jzKFLY%#G-wV;XB_lVUpESX3LfgXaxJ>W{uPC3-Jo;GsH}QH}!*9np$RvgABr& z8gSF9Z0whE96xJL*$zm4KFSt`Z|GJ#a&oTV16;Mtn=}fMByj`sOQ~%@ul6(D*s0)E zv~DpzSU{?`?yJkB}*LBzhR2o#oUJ>n0sD- zcMzC$HN^?n;kMs-9#QPO*`T-J{`GgE`ucNsNSJ%8<399f-gd{wIeoq0pL6CO*QX0^ zp?8R$kLsb=F#{a3?w)I3;aiio2sh9J@TYvD27k}~&&DoDpPR2!Hq8EaiRrU>ULy(W z8fKuD$W25BzgvnGH|C)eJBu}OS3X?+>f5yOL^q{we z;o|^=cYMs_QPx#=MA;*ZoD`OF6Sgv6Q$$j4gi3dP=_8^T)AO(xE5+5vR_rTbZdHB+t~hT*@7xEx$E*kK$EuH{C)o{VtG<#i1O21> zbcZr)cDtdpHvE!oSIo$boEW9?GNgoJMaRkkgexM z&EmtdMD_`W{8-`A~KP`^0Dlr9|2H>GJNs+Lxl{a~3sA zx~@e0#JdF=aaiq_!>( z0kGCZL=BE2!vh5)0p4f1i;ehR6llP46Ak4zpHK})p&nSGz(vBK;n~}Sj}kQqj1A*l z1u<#@DU4b?l$PfL8DK0&-Ufka&$*B0{_&F9?z?Z1|K+ze&*#tj_11gdBxi$Bvo91^ z$I){T7v7qqH7698tx9nbg816`t|dv3P8^vi2TI}7`ILA_RiXjsUA3g&^0))ui#&Os z)ikF>QW;=jIAWmspYl4#JIoy+{ycXD89$vli6$G zTMKSZ-xw$yTdo-p@OA=&6e@58h1yY&;2CSR98egY+#G~&j#J9t08k_?M50tAEn>U( zIS-Eig|T-4lC0_0h1<5RY1`(sZQI?`wrv~Jwr$(CZQGjL^SiKYd=miXJ<|h^WiQ}oq(Jc79}*(vufOOB3pPyJ zE1(cdG2sUQ?^0+itAB~+bp%G(u%Dvq`l=f4`aJUCepaQN0SKH7#`N7u-j7(Y1n4VW z#PF>VjDlhY&@WG~2ZXTZfPz>B2&BLR4ipFWnd`}Bz9ED4bg^O>!j0|=Q8%k=%t zFnal*Z=hm11A%@%7OF{4uL?RkFx-Yc3S2lKI5b~hATWTd+lzX;9C&uJphtH{cs;cuXh@AS%j)qb?)~gPc<%gZAFJ0;B<&wO`jPN^n!w6x0O5# zVB6{Wn_1(3+@7%m1Jh-@J}OjT7kdIHmoQVm$%bAs(fdnwYK=e?6yuFvxktL+_c^s; ze805fYp1#9-~^QX2U1zCB1E+0ab^X()G7qK{>^ImL=MCV<#w(90KHv-TNCEH*sr`5 z818?}-+Y^wqsjQ3oF9|=1%_hDI^hfSozaoOvpxO^=L|i1`B}7+{DGA^aQT3c)&jNA zjP$t={gTMYi6#h1|AF!$@5Pexa1!?k^m3<(Z?~Iuodf*q$P5}OQ!R4zZV?O~k67+v z9m`>rY$G&+ZF>AQXI=-dvo^m{(7nG@u8?50R;<$iVXOtFd|A{YikA%m`bBuOzduRe zw4_J-^jAPP0uba8QHva74gF6@1Ll|?uz)6dZ!BB(?G>V0cVB>kk;vcthNac3k-?uo zk+yZez)TeG&N03@uh~I&^tO9iJ=W>^?AQHTM(=V@N=ASI+J{44-tDCu!E6Exx_xk8 z+SQN9qBY0GYHDB6qkRK)GNUhvFy|K0mKx&{`%i#x^?qn2yt=b%WMrG$w`hR-s8-s( z8`CkD93O?0w&VyaZuT-s=lx-wV#eR^JP%`L3vC}< z(QCxri3g0udU6^MqqcHk5HNItzM$W`!%%dJ5yLEhRjZh+rV&mC{38|$vt%1=t`q02JYQ7!y?sXVt>vB6cZ4&}U zYf3ly4tgw;7Ocqr=_5u>jeu;5QBR)t{h zm)a_@>c7;gtrQfGGk#N>0ITpXwTr0-808EUbcFwjQ=Ucf>rxqaS*N*#-b5>v{r}3S zLd}AUe3moqQz}+do9F~CJ3@>Ok?s=X7clx3&`jNp*SX0*ZJ)Ie-z@lN z?EbRFmaQK44T^GaV+O`xZC}k{djoxRU<}w!y<_k;RPg`#pP2I~PuSATeAqfGC=MuH z`S>r6VaD3U21RWr&;kgRZIXfY)Vy`+*`vMnBY^*gk(0l_c+2}4zg-9z>ziW!fNzS8 zV6*;GJoCRPW)}U2;%SB?L+;@MoS^>$Y-(hky_~18OQqSctm((D=0WRp?fL?x=%ufr z(W`s(1xJ&15YU~8^ez8dRr|Ku?Rpl|<$A1W35 zW3mW`WTAJ+La&f{&6*Kg)->+zjVG-+AnEu2c5qS?l^6q}*o&1S91QK)zh3KJM!ZhyajVIa* zL{F#xYO@HKv2^H*40^pV7s74UJ^*HNK`(*oTM!K#ea-iO7mKw9<|Gf+bHzH}58hf} ztA*ue7sOpDpf_aW3Mt^`Tb}k;ceuiFxKL1xK|||+gwz4^DgfkFe95W!A85^H+jB^^ zi^EeYyRFsWqqy;gCG2A_D)wlSeT`gDW7w}eIs~pBpfz#y0KUoH4I?AQ*8RHU!z>ua zJ;GGmK|6ra4}fx`Kc7Sv z?N=*>RPaRgtetu&e4~0Po%bRQSUg2x3wb7@AN%@Q4Ik5YmM4+em)4ZrDk;;40w)?{K0d6 zhh2h8qwCzf>*RlfSNGhr1*}uXJ<7dP_O&bNk{**-5}ck~oG_;Au2b#M1Ml>FO3;sa25kb!x(N6X3t+o)&6yq{vSubmEgY|rC!=$ zKMFF^0}8MQs{8NQI!=FlI>fPZx80ifO&^*Jrzck?7|rW$+vu%7GAldqnE=oIS!>bboY@@#-&*3|S?M7VtPf=_)t=(L1^ zfzQ#o{3iPDb}(L!?rpqJ$NxOMxmZUzEIlk%cb{KvZ+c%`Z9Bt)8CbF{eh<3|@E?Q% zAY0XC(M@v(j&897zja%#jo6S)T67$@kjm))LMJrG`Q)OyP1Th9Rq7xwUu(nAWlpCf zm=obd>$XQ#Ot;1ufg+tbmV1@cf~wgw+ws%0@ze6bW3wUbn?lAHYvdQk$JfjG*U0); z;P@AN``1I+S4-NLXSg?)d-r?8w)N6A-Kpc<<)lBHs&FeEwkF$pv#Z0A^|L-RmH7&_#ZQ@ZFEn zRfGvx#nj4P)v&-YItKzCBU-3O<{Ls_4^+F2OTHQ(7{Pk8yN4Wg;KKeWc;H^=)j^yX_zleMwQVx?uE#qDZno{wJ4L=$&#z$a8m^IAE(q1Q^Kp}i zk=qa@B{zQg@yaeymAhZ$Mm$xM?ogSJD4Z6YvERpY3dbeI6Rql<7uKYg^9DTj;k+xT zhiiH6QUBKVw(9-|R>J4L2Rz7;e3MjOy(?PCz|%wAZtQmzJ=$&dzyoYIws;N>lm0g? zA`Vf~kPj7SX|Q+`VtbC8-vAs$?faA7Vpeyx@?PXsA%Nz=V$iX$Dduk7o2=;)8ajZ@ z@?HYJ)Li$%j4_S)%I9Ul|r!+%ZDaKIag}F)!CgdIh4hr?l zj8@tow2RYjlh@6*(2I1v2hdIIoRiURr`$DL;BlvTltw9B)%wuY-Aj$TGk0|9IGyP8b=dABj)r{vI(VE_7Q^>?5HW4v zm;JUeY4fsM;jT08_YB?6&8uy{vnc~@r&@iW+o6npy_*uAH>@ozdmEm$mvlO& z$WaA*e6p8Zj{2^^iyzmR^`#}=o*exLJPDj6^M9OjXJcgh|1k&6LeIhQ|CobjXXg07 z=AZ|)wJY#M-JDC5ax$<=5PkuPMa1SY7d%c)OzC0~#@`@E9IWsj50KW?BDdpI@1gAk1SK7<3NtDJ?5?j0X!YY)rDis3eCO-k8f+m$9Awi)dmWVx`-cL+j zaPXR%IzO+Ab=GuqY&XA8OmJPlcU(_!)ms#no{dT9sw=81tM-w5hD)#lW!EY6?J6pd z?ISHJMeSd=&4D#jC}Pg;IICjzpIaLDUsu_uWp~{HEweZs(s%i!Gt3%4?2aDw9 z*jdag{gepvFAXenixT#${N+f3^0i{Y70OVr`bH6vq}uSZ~((2URJHk3tf2Y#@s?35^X~TYR)V zJ^zzg5lfRoTW;R#v?ZA!bl-h71gmNax*k? zkWg7S*2TB7QVIsobqKL7&M<$`OBtmoA&<)PauNAdIN+k2MyIx4Sa39NOCYnmx`rzP z$7R8vO>iU8eM0+K)9Zv?Lk>Hg6^>ECn$?t%0F;l?g(MF>c?P&|hpjbeKEkd83EpemkVOG`gok%Bd_ej#Q-GI0BnAd7bcE33Hq3iS))zr8r|I_2{X(Sl zLtR3u+~M!*A@Q+N^Kar{%d zu+q}kxV>R02zPhTGTv3|CEY=AN%_8KOKz3%y01NY2Ya!ao7B_@{H7e;%dyw z5O033?L0x~ov1+>~U{j|V`!)mhNj116uU?48fYI!i2;WXT$F zEJO=dpJmG(Sel>KaMzN3Z;VGOUex$i3~#NZzrpQA)4|mJLju`bn2-LYBfvFyUxX_} zvfrgaLz3^%d^EX9vYE;<7VL6kF2z*?k3F6w$JcF^_X(*%f++7#aw1?RH%er8osi^h zgOM%b840wn$P1MQsDnwU0Lr+od1JKoOads5+h_tN4#~J2NZ}2Y9Cv3a8MG^7Xz``s zBn|}v$*8XRF@|-r9QZ)Ad|_sOT!F+^E=~6jD6p!$agP99K)=^!D7ocNp14y5maImU z@6q10P{NSe%Yt)^CjEA6#Cm5!7p92TweoWz64=F9995whxx>wHR)d7Mir7g{0K=to zEKbS-32#|bxxtcxb(dipVGb3SVY>ZSUAGR`B-I6LIy7n^1rAs)@IcmddJv=o-E*zt zpF#fw;5Y z>+#3!t25Ci>+kFyAdoNhdQc2S7O-ywix6!XB1VbK23p9c=aJ@zt||RUXN41CU`7%G z%I0sZ!1n)5U=EY1q!I`nDj6e|jYXt{hD`+G9l%SVRVl7-+X%BIxV73 zg&a(RjhewqjloMOrp>go=wv&4qAAEHW;2-wPn#h;R8M~A&%afHB&?w`S;L7?nWI%u z1TTa*IUE0=BUneS@a%>vLK7(rQG?BBKZ&3@-w~iH%D|k-(8|hSRC0p1iqpS_ftWY4 zrjc2MXpSEMHE>=jruRnvhMKH{+4|8IS+-3FC5fxE@)%|NuG#A%7H%wy>FvK_6-dky%qhCO#molU5McfRY}BLP>WzJ zk5-OdTHQm#j{%Dk*t3C~2yQ?Zd`gjV{%+ntXL3qRgZ5xyFfRWGAhKafG0q#q+>v#u zlBfqjc!cnZsCo2_!L>EH*o&P27a`Z+Qfx?Y;Xxup>KXiS7<>7JjP;<<>Z8VRA_4AT zB16#LRtyZkw1Ch^VeB#j|5-u|5+Jf=e@j0DGdQwkCv3YtGI}Z+WS9s@qjj9rI4G!K zB7~V(TTNt>R7>DQs6m8b)(#pu#ynb-fFpvHpK;MYXV&w?0)}kJ(-7#XWK{ccaM&EC zWS?VBPzDj2?Z&`$^4))wK{08d*`b035dzG}CsHcGfF!PnEkx(RF9X4VARoP-fL8FK z%;K2)PY@8Htea&tuF|KaHMY)3OK}1x^Nx*kSI5 z1fyY}wK1B>q+e9kKj3H-)j};oH6sQKHS8tv4c2EX#-I+i=G-#r1C4H}6r;A-N5R-e zs>s~-<=v6?zw*_cY1E^MmV|b~0AN*UZtE`!5bJ9`={Rza*!c+&ei<0D&3a%H$gY)v zkXxb-qgTUMD4iw%d(cvN|kvL!x%s9Y~+q7*YipO!>T z6JiKq({@le#KzA;9LRr`frtKBjb0dJh*auo#;ttCP+Xx#8W(HS277_&`@2>gP;Fxu*51ODv$WvZ}@V((`8SlNI;VjKTKX4|qQJvpD!{9nW9e!_g`* zUMD`3Zw~RQgHQ)#WnSRd)j1C>&%pREhq_Q|T2~geOZaWq6SNJQvfneKUd3!^$9S!~ zV@I>yoaXpGk1y91e-3WOBjEpN47Mev!Ft@R)Pl>3G?=ii?d2+(5~XIrI_I=K4DOq% zcB2ALTK2rh@N_(-RKoly%8YV*`pC@|rEi8&bLsDjQ68gnP*rqsu{u5K?`ae zJM*@{EWzGVc|O0B+uSjKVRqGX)lsBVWP@0KU>a^}GrrC&1M2PD{!Qrj+>G0WR)vW& z;bbLmC!njWrGGr<*_|aCgu|Lv)`*^7n*wM0=l6WKmr3ncj~9R2(buef%RP|fi_Uhb&1=5WsT&}K#XCEQn+v>aVz0KzX znU2aBQ>3Yeg@?x+%&Yf)!lX1&1(M#>YIQ?ephzkc^$F2RHJ3Y6;ud8nhpL_l>1HC5YN>{$2EW!UHIg^lgWx@rK+xEpE`sUD@CAx8J> zB4(*;15(q(pz*X%#`ib5l>~CL6MH{HLhvQ@GJ&-^&P#7+cGp8^@%6`kxBBOH1YK39 zkArb~GETSWgO5yS4ZX$VDm2Ww>n`xM*Ox>3_1+)&EKV1b+xg14Ngq90spg4(Riq`Y{6t8zE}pE99YZ!7Nb4#J zWk|D9xpWV%pLzvWAC9+7Q@BzYT&e6X^mbQzdmFt2tpQ;U;K;X915Ak1LfqK5mIL*O z(<0xEiYf44OHzr_fw!2cc5$Yg5sbvT*oS(};NK-~_Xgy4D5xe%zRNWyP4X`s#?Fsg zJ_-_=uD3}w{xVm+GWmhm?Rl^QTRFWZQea9KTPxdX_ZWih5#xPzuzuBj+WQQ4t*ZUD zchtVt_IbXAJzI^dNHF``^lSIzJ|dg#~?q#E)Z55sRLP6hU&demahA>UfHg*P@!jZu=^R5>B8=mcUCW>SN9R zr2(0#=3uffuAh_5zGU7X7m|pUU0-xv?yqB|=soeKvR{aj82}ZVgRXBEL29F(+jf7V z0%7axAj*!`Aq>6jr9d)laLb3MGCT=kThnZa)=PvZT`-72uy8vM-^$CYwLabwOckUm zk2GW&e-ty&>^U&_J24PB{{@ocKaD2_qW=xVX;E(cThD=e#OY?&4ayDg@E3~x;_z(g zkA>U$0a&hfX$tAU^CBqu5#9(KRKXkpy` zQ5lWL4Hj#IocAb0e%Thnrlj)Gu_TXDa1$^>-PuDtM)D?h4qGBVHq2C<0WeyY5<=;@ zBkrbSy0O;K@M6M^r=XX@Gq{N71l55Mm1dVJC5J_~(jzO)(yFyho{h{NDVLEJhuOMc z_ZLzzOy-j`0247x*ON4(`tSCrPfK>~p-`Ifdso|$a#!JpGk1$a+%Cy$5dtz{uJJwl8& zEW9^PkebZw-%*>>Y%QRk32bfrE{hX-P3)S9o2P84LNA~3xa;US{ozs4iq2IGrOFljgo9&-Vm(w?8X9c?| zi1?t?ugWZ{kJ(8?CJR4CpW&A3e`>JfS?xasm_se$k4l?k1a-fiM^ro33#Hgu=QGR$ zz{DH>0T{RhqP0f1wQKiPY@1{w#5*qB(IZ&;mg8+d(~1?Vq>+>#1o|3*vFkz#nj8hIp29LD>F<@yK#xbjv&p< zR2l|l9}leA3%S-FXn8Ps0q z{-JPd3qFnCfQ=f0wb1^@VEt&QkQ0SfNM)mvM@Z~b{>QWRFysLvkbGA<-&1+`sXbY% z_|oG1lsC$zbmAMCx=7?q=$CkMX?-&%)7G+W*on(Dek(?%U^o3RFezDrK74*eF!FuC zRLjrpId0E=!A}fQpXy4{@xz5?x=*9e?CjTz)Ac;Qv6>aL5pvATTvElN#8gbDrXAA| zVna!+gpB1Ojc%pxqR}nZ-2hvgKd}Wzf%W zvu{nVuOE-+e45?wCvnAD8r=2=dq?SN5|Wi|*~G)DJ47<16=^dwz%Eu@9R9{X%OUkf z_0M5^qJt&eV|VG!!`*lC@||bLv@$kY*jvq2ZBiCu{0CvwwKtv-(zj!Fho)L$aMa%A z5*30oMFoY;(_zRs6_cGa+S~n&GH4|iQK^?=vQT!ZP2BUc4vHFW#x70S=S99G?e(7njCho|StrM++ec4#;WI+QGyjSW^ppv|fB|}P zeS2zM*_Yj!1O=rVSUJHidXh?o3AUMM7U*jP<{2YNkt zc`Ah_&&za&VzU)C6_>i7W{I-F=%N9Ipc61867q@lmY+P?4RPOnN^&{1Z6Hp&irx9! z<@@t+l@YlcGn(|?$K!F;s+QYX(%`t3l6t%It=}R~Iol8vqZUt_e+%G*af#(m!R(h} zvaiRF$8;*$uKNuZS-zW<$#b7S6WhW!Fx^pLe@?94SzlbU*w-B~^M22)B;a>EBt$)b z*Ij;qfLG^!j}nd9tnt40YbWv!_8{HKZVxtC(Ngb5A3@ZqWJ#i@FwkjZ6@idg6?VV;x7gOdnWgUDJqsylXT{Xe4pXANTe7e18 zh#8f`PsrsZ74+ifx>Rdn>!-mo)xWwI#4njlFq$ zVridex-Sn#2`c=F3AKH&Xh|T5sJW_JiPx=l`#QaGKUwKozs~b5Tw|AEku`rsr8`jl_`(8b~BR5hd472G^-%E+{2v^+t0UZy=aiv!*7a+d(`hrD;&(2%<7xhKZ>Q<^r;h42=V8JR z%+PeI&A=wE5WZr+>n+7=j%Lv-c@OI~#X|eXEZpxMI~ICgFQXk^nP!@z=S)ZRnN?g8 zc}-J+9m3cb!zqmM6~}n-Anxp@4h)w@hk*rnNyWVg#W~GFs(M}ot7eJuLNA+ZGlLwP zo(+GudszPr&KCi83P{ig*L4PDcs%jy1%2^zYoTia;PW0d+<#W_Y4Lu}T~`cYgkiy| zO^^!C@LYv0py;4$@e>$5*yMNBdP14yz8Q+3E2`i5JYWZ894#8X$-__09Ts%lz1<#{ zAY)-$PT9k3-0D;!s!y^5QX@QStf0lm4PNBJb#Tq z3Xr}n2U)&NCiTncxHQe1F#});R+kSu*mN)@WO{fvqP6(uy+mPMXE}Z7D(kGFID4OrzzKLu_HNhjYY;GoW9o+2*VuT4h25bUr+Zcr#ksCwic&e`wP z-e{^HeVX`rX35RnRlSmSg09oT3)=^-Ld#`B`a#?duImHrb}Zsr9M23`rO2LBZXf>Sld28yt+v!G2;T zF-ww6DhM}Wqj<>U4%15LHku@^PICd$ic{3*vvrUak{6PyYbqMG3G60r%mn`MAX|Dh zs+P>|cnFyQvpw&*9S6_yd{}|~pnPOGMiyCiaQ*YEZ*2B9kkzINhckW0K7$+Z>Lku# zxhGqv`?d%q&9G`V2ftGqqujA?{B}7|B&)NhIGv;i$3qI>BJXs9c2!*OnOm5K)6w~H zyHq%x%3>J|muT1PNi;)=`*li5Gg_N;!TBn!a_AVw{R~qvZnX(Zhf|65Dhnb;}nCgo@9K_ zz-Wib+gfaqx3Ez`v$7q7o^g^ut0W`XOg{D|m(~e{Ot7zrTdX9Fes8uU>@0olokT=b zXS!PWPsp#Cjaa|fYZZa}b0xqG6ZbQSn@dyJW8lp6xy|0>RgWshPB?Eu>TzWQ)?u6T zFH6ro{`9twG?QrKee*T-qBZtp!nP#SBjFvjtj+WdY}w;n+c~QZDZOaV!oPEWV5@I+ z0#cEHFluV1o!utu@;b$aWEMrq7g%1hkyhgfPMztw_i=w~SB(*lrY4OP_8-GMbR|!k zv;7TQk?_{$KyIQrREe*H&Ui(K_S>pFLBn;YN_#(UwTBb3$^CnaKr5Wb))?Zc$qU2G zrMhSOMfW{e&x|1zD${icjn_DkCr%9U6pbg2ee`07&2%mC@E1yb2xinbR2B0;j2 zLa2el-I1cj338RhC?!_=r}Mv!O=?r+wIL=9$nyotOitrDwxER1uG46*!>9BiD-8;= zOnabBAU*Vb7wwBm^8x3bl~Jp8zkV(z=${rAzxK2LWTy`m`@dDLJKqrqYDlM*QCF^q2>$%|>&ffiHBVqUt zCmui+bQ&DWk?q*2QrukW&xLxB11Oh>T2137xV-jOxl=i5xZ;*m(hhI-@Iv-GL4(z- zxUJ1#b6I`~_xn+=)9QNktItVW-p}00r1$NPmi--??my5Wy60IwzjqIBAB$QQgJX&v z_3mBQLfO&B%`)TXF%|G)l8S0LlGA2-t+knlmGW!bxJBVgF6_UradEbSaG^(s6uaaH z`jvNaS88)iPG50si05d-#8fifoHHkxEzZnE<9>|J@>BGu$*0f)K5Bk0CM99pnPG* z)s^FsVZOrT0^M92OI=0p#>~sP>rromtDebxd5$SlnG; zvQ^jV0JcmQQ^)wzWN~+UYabGsYx%ZB`p~ovyU+a6y{1xhGwH(U=|LFJv!{cDv7zzz zWOEFoPzCBRC)16YssQsIHVw_vr1M*~i1f9uA$; zIsoHQiv1JO;YE$7+$BZ~+nH$Dv2zNb-MW|e z4>?r!&+N8zM6z0DHz52tXtB>TA6BvJADm5{q`z{OCNzup$_v{>(M_+f7ZF=B9_))I z*j-|%r@A*sRd!w#?^;FdN?C_t6{pS9a3B+> z&GYgK3aT2aR(Ql*_oHE?%uAP}w!u0dE#VUk^JmStvgX^c&Gd5l4jF@}q~k+`6&KPV z6Ar4aI(VMQO@;Djow*gA*k*dTrwd&-Q--|MmkHuwaoF8rd_%;iqyIXl2}pmrAbkG9*$(!cK4U?4puw4mqahKR}v! zBAW<}Bc7Wz5Z~g|z5ybOud1srm&&Eh!AW-LQQ7zHyQ;=yy1h-UG{X!=he-Y$ldGWS zOF-{t6K5^Nyo|Lp(w-XdGF$bkc??u9>x!|Vg?F!hVlU8OihV5{G><@xaDF6q8sR4v7 z)#?QdfFVHQUceEMEbGM4g81bU-6B9fKdt~rv z6-)0>rIUz2s!ZWxZ%nHO6!t{5(I|G{p@_d&Us}GH7GT0^_wR82Fy*fD-s0aW_;=@o zTj$XICP4I&=N>~^mN$*VC`6;^-OMa(8i{u($p|f^R4_4?3sNGruJlYMy{gI)N-lWZ zaH>MI8ui|r_DeYq<4ZxKx2KI|Orb$EZiViWD7QD_iO6%%6K@csZuMPLyX}=MZ)xpS zEopk80QtGvH$4aed&n7dPylKS#hkAkBpM5mox~kP-LFZXipN8#Wm6i9!K_p=de1(n zuAzH|lG)IOlAOGBOyO?MQ_c=%&f~&2gn7BY8^$nhszs!K!`&|m<&RUGYMN7HVEEgO z9OzFNC>kOLfeI^1@=BgbqVFY12vP-ZayWO&>di{D8l@tX4sn{ofvB1UwXs>r8^!JN z2WGk+5g8ITO)Zhy9w+0dsa9UiVEq93BH*-LC#pM~b(Wcq#6p`|$nwtgn2>8= z;RF&l@e6%i3&cb+k|m?-G?HcfupJT6DdN8I30+OO{BPmC>F@_eTUOO@@@Cz@SQ~pY4Xvvzr9<5kt zfxAdm(4eUDiF&axk`;j+KysP0pahRu`~b9)R1&@<2oJc6@}Lm>_;d+>2QU?bT;HVt z^ty)t=|bxLUQ&(ZTRk+rrP#Vi)d+(2(y5a|L#FbJc4%WI3@X0icvmw$lM>60*fAw! zPr{n)W#YEuaLO>NnsAHcE(ZJsm~qGpTCLw=T6t6aCaUF<&^Z}yV1l5|j%*PjAWxcT zRKajtTmjT-8NK$5t~=#!wesvEI81s>X7n_AI|5BqN`A8950QlFK?5ts>eL46^bKwd z$~_Z}{6c_WB33VK=}?wXM+Y4+U~6}PDEDa*p?kwMz2t(?kWqZ7 z)%^K^Hv#0_oVQ@+1u#DGB5mYoBvuIWoWyv^384ZagaRn2xp^nTnf~n!x|C1;q1Qwe z+~Ibk^uiOIv`JsES z@){b#Tu3+*dfu3^2LHF*{`n-sOvypg1ArL|j)J@<-F%7YHzXvYixvaTm5VmDCR18pM!a`r+l3{(A)O7if z*<(nWp^SAY`T%|h1V3O&NA`FTaVPHlK)5-CUl)GV_<;(uGu0=ftMHdl=dBn>5yT=103phv=7vLT z>08qo2v9?{ias?6Y-dAP%0q`5TMGFuM^<)z;q%g zqxwhG)KK=P@07FjawCgWkTNTq^h#iCfoPO+goYZlN-+3BDvwyom#5NlA;~uAK{mOD zV#EyqPexLzOz6I@_Aen*MGsPD+wZWAOU0T9r5Ff=B6G|Pv=B<&Gx^!a_X8708C4sl zC5muzYZk=eH72#aphuvZ0As{pg6>7uDEDyhsiYKQ1c;wZ5u+gi1E2)%e1W4qZL=;X^$_^B{Llg#0KrU*%k&4tqDMd)K zK>9G2*pJvTv1v;@9Y=^Y+OZ#y>SbVKD(^m_wHF{YNU;k@7&)NTXF@(r%AKy~6~+@) zOif5r1g9g%QCj&k5MNKbXMr3z0u@*`K>=nw6ca2n6<94Wg4jEJkzgqV6XbT z-ee?DSs2FPT+LC4@Ga0u0%cuBtz%&D0@;>g^uyPHW2^$O2IzZ3=?bB+I)$NnRGpMm zJJ+Tpy!&pqOBP8h5P5EZ^3;Pth*V0o3ZlH!K+K61YAC`V7Y9jkC2dCP#JQaxA*ObK zR6W{}gEyRYdxJM#QViPtHG#^NH$SY=HtGz^)7|Nug zO7ba?F?=+PuX;s0h55|jT73znO7et#c@UNja@-r^bSSbr@k9sGdQm{g4xw^x%9uhR zKmw(qaBfY$`v@<&DDsr#p%mW9;^;(2NK&O2X+U5KYUEk|6~UdJV}q1vPH>8l5p!S| zR!I~IdSfi&47TDVr9fH=#(rUsuv&PHew6IIB0qXbpsV{;Ie=h@Of2o$%?m=*h{bja zpZazzRjDP^Ktv6`Vgbcm(a;9=U=Zt>4diul!>6;S z4}VQUiTT=MM1OW-E^<*X0fd(DSfiF&5~M(~QXnZ;Bo9%X!|{65MpV0pa8qNXp@#l) zvkCSZFCCG7JA~7lGZY3_6WU!8`cMiciW&%!17Yq6eK%HvX;KIb-lUxX%B(_?rGs#e z31s@JsC;%IN2Xs1r)ZM&I--(NBT_x%7ocp0;AZB)KZq}GZh;Z=K>R02I#lAK7)Y_R z1UtBQ2x(iiG~-{;9Wdxh0))zY1`Vl!RKcLR17G21%fQuwB8>1rbtK2@CrDO;DUlB9 zPCB^L2_-_$YGp|@DSl)3og+-dFA3r^E(eb)mzm48!`|MubU;7+wnO6Ip*irMCasv>S`gYE) z<7ZcGySo#;K5brqy=H6Q%jK|GIud={F*4i4ey@lytgAk#sJbD^2p`eo^P!S4^W1+B%$|bz0p?GUrm}Vw=NwTRYSk?77I>E5UYi zst<;2<34rtWNtq0W7Eb#Q}EM@B8 zs)vRO%0%9!e$6BVwf}gb6^L+ab4fJ{ERBQ%f&DbK28xcz=w0t=%AeEcZlCMc_UZ5S z`8uvrTo!gW^cO{}5do4R0TK4Lqr>E-4JFNy>Bnv+c4*gd`&MhUuFZ~-Wo7b=FGTwP)c6G;sJLoTBnud+oT|6ZmrU5!b=J3uzs~ z&W%j6%9+JwoZ5>$#b+4HXzNPb3=M0keYVlRQn39i8pgq>u6sY`{&>IgP1$9$wbzLew(VHNH2x}PME%Q zuyC3YT@pb_jEYs{|0BUl(1C`{(byHD3Sz~7FrdFC{fqj|p*6qT$qmudkby0yAein) zKl_9-U$eY&jjJn$wPLie`&MJp{$i1*1JMP=Fq_J%c9{l`9`;z}s!H}4WB+oq zxI4HJHc-`BgJb;>D-`M>=q?^80d?(q!rY|2b1FV&v|GZ+`Ztcga5QlEek+Z-5<15f z?vk(}wDjksdA)mHeYZKpxmJz_YM$t|8P7kRSV+PVvmw1xh$;;$HlpLaH{PK2EB~B` z`T{yv71E+|UYo=MV%VggEpB}%O?KFRJL3vM_ba*!-|N{k-kMkYDWzZ zK4!ZCYdQhvuWL<*qk;n7Ys(ES+FhZZvVefk5~35`wrhzllQ1d_1}&+Y8?)82MvZO0 zS3kx5E76JpHcE7-y5s&!Qx%^MEe@`qLK7uItQ46w;f<`ZB-T9_qS2&+y9?0&w@AB@ z{3-oL5l)0evr<)VMupO>#Mxs~f@X5Uu!DH@pBu&Q9PPRd^S>#AI&kC+M`0ADVsCNr zK4w0tu&xedk(FDCh=tH_*P0u7banh*uL!Cxa6!LP^oz=W(h3hYZ3Cu|AuO0i^;c)FfK2;KJ^ zpRcQ9UWezdp^2rqb-NR3d9=lpsj_;r4nUZ`Kly;I44glwsYrZ zCo>luOT|VOy9F5-9Tu=^8t3v(jE<(FOiny|&vR9sKV8nFY3%=tv$ueaq>23nHw+ut zFf%hVGcz+YZ^y+Kw*1b(s6X=aWsI}B8$gj*o0;p ztK~wohJyg;r1EYufw8ZNDBpcs6UTS!ImlujZtj> zTrbXn4VA0X*88-tSvfX+vZQltH_XJ*gyVvv$0Hvn57a3vbpk_kN6d)eIM%&xJSONqx15isjaQ$>X$7|99{KltL6D( zFHVC^clA~}yaw9s(CU$Xo9FMO+(uj0F@3H+CutP9*Rg`Tz3euioHltUf_0)ho)#gt z8rFN^t+4p4^~d6W!qd$p+qL9NFUD>X!E68Um-;j{ZdALOEnVJ|7rk{gy?*PQJ4`V~ zoWaP19yQQ}?|CfA-}UWjSm0@~CZ|heUTnCaB60-Oy5_8a+-om1Yt0X>smk)jZ_ilF z!0XhcrUqEQVSODld@1`%mM1To$ANERy(Xx@hcTC(w#JB$#QPEJP0;yxL-6lG!oQ5B z&V;EYeH8Re6hf*nXyG8CIw%vNBG^>UA+tH!$tFhL1B1S-3oBh5LkUqn*GC^L(4X6KHn&aqz96-5%HxUJtBOciuz+WEH_-b z-BmGFuncrZ2^Oo#uKc4^Lq0Tjp?!?5H34v}h=%N;0YXOUhX<hONr6Eh>RZ%!wof#!o(=TZ8fKBU#=dh6Z zo%04nAOV=5(Rl%~4fR<_l&&en59LS3<=e@h?nvX8Hb2hu4w=2)S zpB$QFsy%6P>q}^#BJ-fKEwovf_aMQQxtD-Z1PM zb+G<$!cv_yB(OxjfYIuI=oHX<5X)?{{NKziO@=i3W=?2(o1WVFm|Xv%@VniLQ@_N5 z<3T9xH)OQ^eHlU8Rv_*0RPEg3U{|B$#YxtJZBdWu=3vjD1-CABNhWtmwhb-xu6-Mm z!4H4DH-*Wr1=qn*lSUhMS@f7#@|frV?_t0c^Me}RVtwf&(3I&S_Fo>e?ROWL<2A$j ziG#J?Nq}vJU&G+n!MGh)*F^zva)4BlnGm%nzQ|TeJL3ohL08MZl?goF=q-X$PV`a)p z`nd}3rW`+sW<9hUrVN@1lCABvECrAv-Poq*J<=*fkt&vcTRn&@$}l`|3Oe<>k0I%E zNLnp$p6JF{wPtAcW{py*>s+!)g5o0c>{yobrMqI8S}z)&Q}~0T8Z>#pfX!Nhme1|B z#-i3kJ4azUo!6x)YLYps3Qb{pb5us^kPg;hvbe=Xihjd?D)en0`YIM@6l{{#VtKxR z#wBpp_P;*jHLC^7M&EB&tY%>=L%t{UtwYNH z(*!na(t2HPXv=!pFj*-xpMKOVNRz@x>)+o9#pyquJ)VX9oweg*4rqxV;U~vnqm7Kj zdlYwGxPKkBd+1^tXdX%kc2+7ZqmlNVF;FXNEq^HwDc6-#1cJGc$%U3Ami}&Pb3>R- z;a>EtxVmuvv1JRouI7ACs0XhSqaufL-lUDty4k=QJ6CM0qkK)NXMJd?f~8<*z%Q*q zhm0xidtbTD0OIOa_PC|d?znq}Ja%LqR+##2XhX}v8o0%O>C~~qfDcR0GLqRTMzbkK zRsgtnY3o%bylqpzd878#JAl!M41-_yLemxxB5P)~9M1_=Sqi!gkG{UwbBDvforB!& z&oh=d%i*dU+h=bH3mV3=PIuN*ZwVEBWpOeV&>;-=7Wl&j1b>^R&@56t;M zR^}NN&3q=E^iJ%eOJNoYlXiICp0fB=*XU| z&;C5+X&d3V=&VWm=@0^32%Fv9iSvDVdT$g#2SW(f^UD86R`j&ReEL(Kiq#iTnK?8U z)b~PsAEN-gW!BI9mvL=9Ov}Cn;x+XFNV@jkvF|!$>Gt!I1EJIAuk}UeyA04dIbFQo z{G7UUps5m5?yr3VB{}DO%Yn?%?|60a!kQ)y--K_Nvb?rhq1d`x>Gs*H>AvA$FwjsO z7jh5S`g}TVD&4+YaoF&BITC@j@jPdwEp7ykyjmizs=lB&#?IR)?qn+neAsXhcwPK) zr@B+&tD4WCT_=wIEp zT-Vc!vM}~`h+v(U2FTJ3PN}6e^{P8Og{dwDo;np>7LTF#!jsrxH*?at2C;nPi_D?Y zG$~hsRoIJJ^=Hf`@w7N>{Ik3KD^}-?0+{0Nkj<8*u_{T?&R7CDY!rUqy*L?-*`EE> z9N>?B+<7>lf@p~F#k}D4)C^B|XOq+LT;e)HSW(}53s2WQ1{-e!8-E2G4+R?^1RE~~ z8=oZ#ZU-W-vo=qa6qYb`6I z-5%u!UV+zy$4L6iWy~#QETN_kPg`dDe34xH6K%dtX?!XS_x)bP%ZuCYL)4_qJFMpU zam`oCP*-alZd7~ein-L8%~R>(J4<%Tr#Pnz-Sj&xnI{c%i5{~!+@Pc4C-nKv*|+Ub z(PW-<_N1_~i2_{A3>W}D%<$c2k4PU(Xy^aVIFs@1elN|=I+e){FKujkKGRq&#XPjo zmAfuF3a-+T;QCFUp>-PLQEq~H1!`2O6np=e_825CH)8Z9sCBJ(QD~5_$p#J`K4_q= z5Lex5oKh!>!Fy+xb#EEt`QSp`XI|FD^5f5lj5E1EIIi#4BH+gcm#68JewN*@8O`a8 z6t(%!6&e+#EnALno$B6{F2?^Kc=w0_VmIqc=v?EhfKD9;aC>5%i7&{u$@+ zv_$--@z36za@WuLU?1w0{96KtJYId7t~KAX%9aY_3v3M|INbHlw+OG!c9L|4S}DN} zmUcc)wOZO7-HMo?Ks z=hTGqbY~;R)uhMQq{q{YMW9VXRJto}SumJ$d{38Ns+`e6`KeW7V=lLC0Yv*Qh#vnK z&fRcS*RUl%>4n1vCAL&0hY<6lXc|FdyZO_(R(_YG5r4szp}9~ z|DWMkxe0p>HF1g;6@SPu5bHZ`qCt@7i!Wei7&tL>{#L5DgRrDb;L%0pr|Nvd6e}G3A55rESw~6))*%?U2!v>0wMBNr({uB)-uuM|5RUS^YkPHi z)%%0j=Ca|Le>jBI1MXEe7Ow(qU}X0OFh9tKgAH)$k8)m%#~gU|TRvU6JgzZW z(TvNvB_DSRAE+r(>l-Jlh$u!viz7R3V|-FS(S~TYgp50dgC;!CHtBdV737P&prald zO;f484CaVhNmCueckmW8R^f7Hf*4@(^Po1me~}c8(anMWbmS1OI8loUj~9Eig|;U| zZlN^hF;3P7kMkBR;8qva?Ie^)rM`I)Pq+jdB`83&6v`m$bG6OE95A57Gfw7b@safl z1i_V2lkK$=V@R1qiPDUMLID}veXYOt2tq_OA{-^uau{W`N`1s zB33+yU*z46aZG>b^Wt!MgDV^gIb%i)*cGOshmvV_dx=ZU2_OV#nc)WNpteaEqSncp zi5yZdLv%rnZi0r-?}2ChGP2w~&*yBb+3)rWQ9Hz>GZH&GFVGD9(ptj*1|@8QsxpoR zZ8YaWEEU!IuZ&9{f(}4}j{5 z+es0LIp>_qkIY>Hml_(M)pPERSP`;Lc(3PKYTvu%tI&+XD?;Y2`kg*U>}&pfbOn;J zpQf8^2sJC2%cVYQ5uF$#OEl(iD_E(|89|k}!z7J--te1pe1+4m+@o=)8fhaz=aV{8)+Ms zZM`WLl<;pYHSjL2iVaNgW>qEf0=sIilrxjtpupa~;GbSD$x^k1DDNRTp)SVu1q#3x&3 zZ2{g#p@z`7SN+`0uu7IlA7)o*kbO-%7G3C8#8B}(mdTfTgm<~TWm#f`gk4!4^=Z9{ zNf(KtqF!3@-7CSGE|x+gh(f>ff4Sa3)AVTuKSbZ;{+dBCCUzn+k030LzcCnfBVxF# z42P~EtQ8jYJ1~Dyn?v;s_#+qU0}6vV4|cB?Z$9FK07kgN#NCG8AB(@5=lJM_3mj zf4EIBQ>x+@A2bwc9WU#6NF>huSWXXwv6xzKfnuV^f;0=VNTWDU52<>#2Pz`0HI*4+UhVl#jl>?`zzp0JU zbbAQ#A3?WdaYUuhl+El37yd0rxByX7ZG=C`V0}a-a-;w$<@5v1@*cJ7oR7i=0Jgd@ zl%^rVX19p;ZWGsdi)7auiv3QKkyd>JbkhW%^b|F!Y)b0@l78$5=O-^H7V$6TAD|tu zC;%{FLBL;_$r(2~*|Y%j8*BTpjs)GGgLc8L#IW`OBZ069HHYySgE!s2ng{4G#ACY2 zuv^1|1@!1B;;o{ptGK{cbTc>f4`H2)@Dm4cO|;sG4x*;IZ&sUs1GX(|4_69p4@~{S zQ|}N8H)_)9t09VvFes$VY8!A~r7Z~8G^F(q7OXo%>gA1CENR8DR!YYxh=#|8-r;U; z97Yw&f^mkS&vl}c#m?rm3pyDTK2dp46Sc<)o)BU5++mjTGfPh&SZDN&__Kq3z9C}H z@S>0`^>d71>W6;yU`?YJPPG^*09_`ip=Wn~4ey2Wm}&#_9{0%lHRZiW7aq(ofXWL? zF@Vw#Q%xHWH|K9XI2H>?sR;~jGjGL*N!-4olmUsBy0#NQP9hH`dck@ZPuao27LaoP^gX44#@%`sIQUn zUM9$g2-SI%M|?0C8pUpKzGDnY_gGPN#E3f$h&?^X*u5Dz;ly-f!w_8*Osd<7zfkYB zkZaDlU0dpUYI`iady;g*!5V;m7xLdTC^#aLw!33wB=a9Jdv&s{1M2GQz@P4b^vJq& z#$8bpsIQ$qEm2kxkYQrSC{cdmg2-T=0Uxo)3F0^aF^zZfxBD0i%$G_&=^@jV4P@Mr z(S68EBE+fY0}mwZvqxAnhLrH%J>-?%A(qW}Q>PC8e))nuEKs%BQGGiIJEW0LGe3xO z>nNR1Na043w!~Bevc`#Z;aHk`_|krQ-~$P}J(Fo^$0%1;`;f<_vuMdrO$VZNDU)Qd zBFu_a&~Igf#5Mq9O)%~xMn<11Jz0jnc>%wXeGSp-NGRG;ua82Q+boTo*odH5q8uTr zRW+lH?GgBvNP^(z|LoMa>JFg<5r<8->L8_!xBeZ#!7!yw0*~k6eQF7W{4C@8?}LFKqgW5vp#~-#Y1s- znSX~%JYF~G*DHJBU=M$dw7(m1hS zRk&Qg$ckVTY8c5LDCt^tC64x(_j(#kMUl^;3pRrIh(sy$`@D8>ks#8p(Suw0M(@#G zvd_UHX0VRM&{3G-EM(9dii8kIa~!!}(cJsNFwmViWQTc?g(Av~5>##<$VZxdT1cm^ zZx+b8ZlQSpN&OYr2_jwQ{+Ef=XRC#NOB*K!rG&3nhal2NySXnVCMir`2L_R9*V>j=ufxq z5E?bXy#2HZ35~<6=Dm9e{0KgD?|!H7vxR_^0Q4;9z8^F^3w9rmV%YoNEyet0=yLoz zcdzk(?!WHH#`nBz`Mm!5^*-0#bx+7a&i$Po-vThaNIMP&zKFmcO+oNbCbq^7kcc_sC?;t~#@)VTc zs~tv>;p`&#MxlRwAXij1c~3R}&D=MZNjeg5Qokt;7#R6F5Xtd^+`=HB!YNQ1nblxCqzT*Hj9H``V* zvvE2UDVqvk&t?uBj9Pt^i+2@lld$w$RdTqpG%^6T^|NbMZfJKu&v(DICH>$PFpx;F zWI;9Os>}=PW3|OQEto=oNV8oQDMM*l_rLq2!=bRB!K99 z(+GvO9!w8;A3xiy0gpkJ>T3>s-AJ15x|=x0f4RBleT$)^4OV-B>$eVoqUn)2gYr;1 zqXxp(SrVB;ysQ}&y6T0cJT#>deBE~l+?_~tJrs8cRERX3G`Mn!c-<& z$4$DYsCwt94o5k9%whQ5lhmBmR{g>WvxTjX%x+iV*EpP5yASq-cubVV%bnwUjdSmE z|G4hH8`QbpghU7Q*Y-6PR) z-4Zw3{Z47yTt6vg#_0cMq`v?iK6%=e zNUt!xtZmK%>VTHEw4Tn}&Q9#v;5Mwj#DnNmvoPqg^fgIw}RQo{#D~fj}mW5)-Y@tE-TW`(3N$x5i{sWrnD#b(Hvc|oI`;MX{ zJ#Gx?^1dD1s`Dp6_bij!n_ZB2B!A8zd$%)gp^R|MR!pZ5Hwwk&39>3BUYzs(;cfrd z(;tPxGS4Fb!FCQKcd+NM5<$l{-_sQQL_wDkV^dwP?@IS_Q=L3lons@+g36{mS*`1a z<>ls1*#(bI zedMY5TD7s4YY2gF$iE>@zNueRd;2i9a!6Ce2S!yDaSqeh3xg=L&UG#bMoJ0S0)E3! zr;8{-4f3y4Xjd(pu-P+f*oLc+jjAUtz?idaE|tD8Sk|A z_td3XJrB>2Y;o#W@rx+%R~~pj)=h7;USIioXuIuKzHv(WZG>4T+zxzTUu&82Mi(cE zM@8?;yW+ADx_s1?=%;SCjBqtME}8{a$lU+^Y7E5Ol-@^tLAd7ZG?~vxaS5AMeN~^6 z;z3c-^R6t%fUu*Qp7T6AEM`5ILccG?Q;-Hmdi%oxNmD6)9vYHmHK&_L6FqY}xx=+# zaJ?$iTa$Gfu`WiWnB2uinY+&Vq(&XZMqSMd&%w&3m2ikOf9eEx=$-=0C#0?V%i=b_ zs!|1erLp@BttDwp4zfX8%7!A9ZBDto>Ale}L%p$Fr|lwf0S|0&jpY>DS^94ObaS_s z=kD*n12gz&X-d>x>BJH=O>qAOX0#c28mlX@9z8-Ykt0uUz3~9)msGzSDg8lpN=Igm zH;t;kyP{jlK1oiEoGmCcqJ0F9iaeWg5lL&XUd)tDBez*>2Rv0Y>QR($rbGd06I>HD z8VlQhbi+@cpvN%e6DTAU4i5BtNW2nhfpfx@*pQ-wEM*^(%ry$WZ3KIjH_=^bHy7dS zO;n?hM?t!y!_qvi_uPkG&pAb zVdW1T(`b@CzK{8O2=LQ(I9CnOXWDw#fAzGb-gp(&IBJ_7E${=N>aMQMSn=$J-baPIRFwiUT{&6yz9_9Fi$OToqpCE7ZLpac>DYC%Kn(T zHF7z@((faquU#G-}4q^9Tp|TYQnN&CdXjEL`AxZPapo4gzojMEI#s!;8Ga zj~I8X7RD_acVs8U6;TjKDex;hphfNJ=S1=`WAazLylvJ9_+K`ypPoWB?`qj{1bXyl zn<{NxC`s3+n;(;sol29FJbm>wT0-B(Y4AduQA@Y3 z0bRBoUDgXB$)zsp0hgR?61@GN7?5n(clwGB_u2xKFJ7wdvF3gtJ}iw^hnz4fJuE-y z3a?qx$_;Ok4xQ$bwkVOlNLl^;NS}VhyZIbYHaF0Be81iKI@~f3P|zI+(xLUr)T?rd z5>w!*Oev~NQT0OTswu*EZ6WK$I&+H}Ozd|Y+qAFq_>FlM)VXQQw9lyd6Fur@ctYkY z9kZ=P4(+`JTeo-&J`uUzeEwGC-uGq(U+q#k7k`sn&$(dBn0s+nVln1z!`Nw?QGFr% z6a5*Uq?`*N0=6I@g?&`@2CkR(tNdx@d~c@RsB`N#{^G6G<)eSsQ>J?F6!#z_n1^V= zl2({gYl&Nw)7g}2y>*%D5h|a1o=Cfd0q`eaQ*DFWQ(S+=x%T_pA2&=9jC`rIBFVIl2v)KaB1P+; zhMP@Ode0^Jl@}%8zKD>EO9JnsC7tSS>|~f0IOMg7#MwtBFh%A&^BbhsW0#nkTOB{? zMs?gd^3#yZ9C$C=7Bie6hb#T@KU30lD6@`AsYdokxF?lR9OZ6HVWhX%H#B^dd^OO# zq>K!1-IU`oFbs&PFnKO0cC?il*;f<<@yH-dl0LxGA@k}UeL%~z(;FpwFTwNXf4E== ztQT~5hIpa0!}w3LY+(6RfY0H!$BbZKoh&>ADa2%7p=fF)(4ymtTOT#P47pNF+(F)` z$(*n43vGRFDrxxB@>`hJA@3pfTbMGEEfzNmg#fEO%=HF}_=SH%LzbT7KHL8qGXyLLz+=+$ zk})sB!ANpuHwzkuz2)cYHT18loW`T>!aZxsu4woE>K(WYRoi%4`7gr?RmRV(iZzFP zW@um5&T`WS7kYkNq4+mzvO{S3V7%3M>J)IB9j6}!1aS}R9_m1)8B&>$ zyn*YQP?^xYfiE!0XeD!OG5Agf4Z1h{9G+T80(EOX$pn3(IEaH2+5@C^5 z_f&Yh`L8u*YZ1leOtz*=kwp!M*KHL_qW?i80s1{(?c=03 zDfpO2e15>1E4?zNb1OBM0qDeaxISuN-^YBe{Ax*L-ZqRb?WB#$VtYIde} z?WxKhlzNq)KX{=t$kqbzsVmv9L%Flr$f);y!7nVLr0Q- zsG@Yo8s;JIS&M3>MZX}xPl@vdSl3ubRCH}>sVCR`e%vf@Hdp0`V6S*BJR9b`V**)T zFiX(?ifO(xYf$6b6f5#Qd@x^TweyK`vKoHLPAUQm&f8jw-0Z2rzE(>zx5!rDKW4|) zR|DJT>|zFSLwy-CiYw0n8W_}C;J&2)uxV}?iE+z$P*mwyqF(o=Kcw-(6Srul)sv-{p!So#Q>03< z`4udlqKw~}*}cLsclq1aN%u>ZI&|zTDZ+hC|GxVi$2+hYzuUB=oDHyKIR6IG;CZdB z_u0p+5b!W5to5OD>F4$Y*XCz#cZf`q$ZIMTU;h(aMZu44e4&vOV7Cp@AX(&Jq}Em4 zF0SejINQYWseQYt^Wo~3^{U{*IqX)Sa%hU`4HdHUZX#Y5rul5qtyQsNo)A>v;X z8KvFlGF3N*5$4Xi?HwbI%U8MM!ixTgNWuoxT-cs6(enCJkv3(r;tPCG-_ym_>uK|V z%I09VQV3vM)C?hbuN0EU;L~%Mbrxg;)|13kL((zOPukE{5J2vif2|`Ga8&Dh!y9=$ zTcEA&eP;N1K{UL$RpjEN*9rK*Uc530JfFi_FwMzstJ*A)pHcy+QpI5UHH8vrrPGza z!Uen^*Bu;@Y~1hQE_RPmCn6_dCQpMY9PAWw&Q_-jxxZcz1@y1aDq8^Ay=gc%t4zIM zTljU}HV!xc0=^yf5!BjyFCeCGzxMLo+1&6Ut$Oq6hymTjyx<)}cdkH8BUNv%PdDva zZ_bO{W@%dc-c)RatJnVFs+~FG#-2HnytSM~}(frJMt&A`XA> zd;Ox%u`Zf3d7#)R2=L)-h*^7^K+SI{_Q0u@!~gS6zbJb&h#%;jEs{%8ak?1e+BhIk z@vlm5cHd9}Ev1RU3sN=~nmR}93B|*(NZm?PC#+Ppbg-<+=H+)qgQOMcMM`+rr&egg zh@rCc_pJ6!DE>vNvdm)o-h170Mi(~$nslC&mm1k8rULjrq9h<|pAx2ibmbp4vnNhr z3gu7529r@QAQHmTN8f-&21<0l4JgnYATlFVU52q3+Myd>a%R<8{C!GyD9Juuooy`r z_V2!&i!#}jnmE6Xy^1c}cBxvPuj{%u(_|l? zLekvUDSWnfD=@a}#Feo2V!~YL_!J%Fah==(y$aChMnMzsv3#=pzMX~(WkXQULb-9B z)$`~Dy6nF7$y1OyB|7+iirv3f%LQ_XZ`sa-Jj}Ur?r!ilUT78CByh(J#`b#%Jb6l? z>cfYnzw`Y`;bXdKnI6&_X_gGqx~_*aIrVC(q^m8E|7-FaK^?OUE#e-miG4aoq^W&k zt&yE&Mm^-a+C!I52FYjLSVPuA}A?C zwXmExBRt5S|0%45B8AZdYP?b>q}R&kbQ%`QWc3eB)@65YAe86(fQCg5&+~iZ$5h)f z|1*wOvS;o!pYzc~#%mUr#nA?iiS<11Am+R(^QVA(-A;I$9M!0R@svs|2)G>Uz5dB8abJ4DrijW*1l@UrexzJB3vkapGI&)43RH5IX*|IPd zaY~J3UgA2ZlxCML;`yo?V&;LE$|>Ar{7rdODOk#*B&Q31DTx=W{N+@vg{Z97Ro~bZ zJjp)*@?50RE$A6bSgJC@<)4|duEZl7mScA$lsU}VIZ5cMDaiB_n4UH&^jOx&<0EXS zibi90OWOsCeX5j98CVrE&N-B2MRfAu9ZB0B1*-E>m&sb|OMfV-y4jJSuq0p`6q$YU zgVG5b{{}gh&)CDxN&K|LDT-5{WuF;fvC+xlEP}0&rF{ceqX-ieX3K~D#I~rjlGQ@K zH;QD0bb>WsX_?4SWV|6onGWqClO|w0rwp|QabE3H9Z+O)Fr!9FO%G9%qRvg%N|5Sv z*yGG-;_!?`rp(9@hR;&Tf$e|T`BQBAQR!Iy{gaiV_$hR;JL&!qpTXHk_f<^Qj*>R3 z2cN0_YwBYOl~$3XPJz~+KjpGYFlv_aX-#?ImNNH3g9^6*G!I#$A!8mVIcF_EqS z`y+lrbrx=gg=VZch#t0+;?I0&o3#VPr1c`y%mpiwv5b%xB+Kn?0EJ*A=PoP8oB}i~ zr_>u#eP9M>AR%!rbSuf)_)Ik=F}N~DnWcC@j%shT>kS$ZExR;w>kJN!gOz7e0jyyj zTuK16>8xSzRfrW%>`xkOIXB8S;?F0x04L?B1OW&T9~assWbnEa2QZkjF&4rsBCk|| z&@#a+iwE_(iUTa*VhLhiOGzGeBIIo^sInId19^PUC+0&SY&|v0DV@<1gg`Mzo!&`H z5ZI~hm`Z&P>tg!|p}(*LNH_bXFfK)e>@SHO;gPT{Q+yGGD$-PJv2a@4x-&}$2+~IS zIG7BUkqC2eH4z43;YCfF;8xZHG+LN8QL@>pvI63CwH4FY6M)%a9u1BzGEH_ob%sZW zY$vsvIma(`ZK7fhX>Qm+Xd<=pf>uHr6fYD^Xi?1#QEMWs`D_UC0T)nFg%d!};_szU z)!fOGOVU`6k`<~>WD|H?mvrW0VQUs4V2`F==s-=|x3-|GESaN?Gymck?+)I56bOugH zm&-GRsm8!J%E2pj~9-vIUp3z#6OTW`QGs$|%g(VoNt=%he0SM_`F)P=kw7}|0n zC`8(FY<^Qs1Gx&R0O7%8m_KD9=Dvi%3tW&Z3rYKg$>nN;3C+UnBR^=%L4yc!3b5w3 zM6=Kn`3Z%a%Ti%!t)N3flDD*#Jp;+$b4U;1>M8%27nOj}TEVrBIa2iMKR~)$i+z8I z6}FO@p;V=uznBBXD7cI$XbK$7r%xgrSXLo5T1D)?IFuxW zCT55dAcg$50E=8tl97b=IOLJmazUcDBQFsNmBK9~Hn4;_`;FGiW=sQ&SudN?+65jh z96zJmS9d89T~`P0^tdD4rLVp_^`Wcc%y&B>T~GVP9N|I)r;^A!== z>wD#>&X9|3qO>Qg@QAiBl83;~_Ey57%N>7ukO_WLNfx6hl94s^!7WHc!WEe+cZ;*Y zo~7)_P~<8r!{am0jQ&nySCI(-WUWlxpmWPniA!3uVYeffC;t6iRzVlf9T(t2)I^XD ze@d&UX0G&A6xpXjMr{zUCJ}v@Mr{n*$1K&Zi7Xpx5v|jk9gJOIF95oP-pA^ss^sdO z=pv|TqZy3br-)fbUi8pkp)HI$3O7$SAOI>(i1g*C^!h8SK^(39c&~yw00E+z3HD(M z`y6F0RSO8gqAoAocN;at6SDQqurJUyjgda}>a7tpmE{uy3&G#wF`5m=e3aoN7fDqa zlKvQgz!vxJ!6+&%Zezk%hzG+Nr0~H){!lDi2@r4Eh@|AF#@C>2zY9o=56N%!Qsy)i zb93c}MfOgiDC6G;Bk>s#1Z} zDp~~`)#Js3$htzvUE!eyG^4hSB$qKlMNKZsz1+CxI$3Q7ZNAC0f{hW zLenBh;-@i@v0jfR`BU`nG{hw~2hVjLC=MacB+qKvo!?#1QZgTe5?M9O5N+~P$ooUh zYux%N+9akb3Q<<9z$ZmkS5z_ujsi!^CC+XJF=|pjauQEUsU~S7hE;d`y{6t%aq;>3XYvXAQ(T&|o7}1rAxC6R@Si zXBi;Eo?3Qw^60lVtIy)Tse;t>k>evlst+I!B@&{w7OSAS?UtM}bM)ZUA-uhf59+8C zQL;Ir!MU6Sc7->W64Z)uQ$?|o&{q3h=F zd`GEHt_QB*SLo>y$+Rppr`1a!;=jIWtkPCTuLF478F^v$e|X?nz2c}O6-ahObGD+4 z#MlYZj8mhW6#H4x&dgI@W8Ujn{Tr!Fyif5g5eF?pN|SSMPmh3Lc=GfCBRt28h!hEm z(?##?kIie~ym^|$Wn#j@CLiPjQVix$5X`Ye4*Ac*7;Ate- z|1)*^a~f#SlwVwEYq6gk_tWEA%0yfu1LhNu=te}57_`Du#yTQdbQ#Mw$u zW0UCeW9XvRxzo$xjm^YuMc~N6@1gMfivPb~M6*Zzw}^!KEe>VGu;5s*PYrTn?CVB5GyN~&WhQbH2B}5l-1hEJ&51oMsRFMq zl4*-QS>U@0Se)H=uIO6t)%({Slz_tpuO+s5%i3Qkx#FiwKxT8rq{ro@r1Wa~NF7Bi&lXv8nFw=w^yZqK#t%zV0ugXJexRLqggTVgr?i~K^RNoUSpDFh8 zx<)RGK0do%5f>tooAp(o8(A(Cy!U}~;nPXmX)8HW6T?R0aRra5UVpaB3KyJE;yG@9 z9N}a^q`8t9OQ&6P9!Hc$<{*t-FJ5v9?f+8A6GDcwMYn7D#Qo0wi7DdM1CJ|CmY=mr z`}O--ORFxpPxT<&2;RLv@C1N{0kI1DtUVX42cID3!vyYs%-+Yp8}v!t_Wl_TbN}vR zGW_{%KaY*^o&waW`j7nSmy8fK2iv6(^ZM}}FYU7sI)Yz$^jdlIX}z^XBas%Nb%JTz zf+H_NYuPAQuuVM)RL|%_oDhaqm4&G=k1ewbonnW$ur0=`i&w@QZNql#^w<$EhoS&8 zi%XB~^jq1%uc#6&v^87aXCDmT*=%;ke6-l+t(g%}qIKO+(y4W_{*pBYX zIZoFrPH`o7e)pD1=r!1pJ9uK6+pL>EErsEMR)51YtA`kw8|56Uu`&p+k*vLzWbmsjlF-TyL*v%GT4;Cseh-UTy4TfCp)E6^9~^4XxN4l4P*!bF=& z6As+7U01a4p(=65WSJ&Pgg{0RHvj2y;UQqABVW}>YWB1E;y}QxUyY_gauwY|29}h0 zO5GuTgfq$M%8azxh|7I1U=o^o(AADn2+&IPNS!WR8*p5Q==%qJNin;_B zFV?_R^M?S1)Galma@Xf{e0mGmwOkwFTOv}yhexkhyiE1^Rf80lrU!KL=Or-n3Pswb ztb`!ioBWd;TX_+~1N2EgMNUWg$hA#Z$Zqa3AKhOG4tk!8<~$X*TZ_$>9syd>-j%__%&H(E84fW}QIN$%DWM zh642-==ieU$K++iJbGRq7ryq|g(!@!rqEZk_FQ?|_NHGog7TO|a53@%o4Q#8+VWl? z*QYcQZHGuyF7*-^Vj&YSQzg+t635e#Elv>Oxg8Yl9*u|`wtC-NJx zux6w!SQ7V7AooOa{cVN5F7h5Y8C0UdX1Sg?NLKF}x!fpxWSHt2utsl3%t?7{hB%kW zRHvtc>LhrhkQ*9gqwitKQySzi2y()Cy~Es7NyhMh#Qc21`AIST&Y;Na`J-0kmK>vP z${ox-Z^U)PNC-nb{Bk?Y8|A5cD9vRBtlHR+`vR;~(SFwrEcWKjGBcK^AG>u9zO^3* zOoRUdb6mQwTH^t1+1CBAfh??kGi6%cGx4(8r#VYWzWGxVO}x3|Zgoy-^=iyw9MY_V zCO64xYG`#%coq5gH*vX}04K?=B%w0_bJXE#a^y}If7pEX-}A{p%@94pxacHUFF>aK z5_k`=jvmf{4*&X91R5Og<1MZaJ)R6L_U(O|i|qbyDv?hmu~@5Z+K6JYdJvzj52}TV z=#Q2)!iQDF@wB_H2d#Y3blanruP zxr}j`iW)?9fX6%{%kM*mMnjBqyLs#L=l?f=@{hl9*+Xbx~6%o`edZD0cFTV%jw3?@mHizRX!Rb$3#c_Ox8E;(OnNf@8O+h>V zEQ8vln-#YXkzljT{3ThwAk*r;2f6Y>AXO+PbkwwR}~Z zrAuYc4ZWb?`l&88uK7Ny!v&gTgThI@Q4SV^6Jp>zo8dgK%toA7=R6ria>5(dem-a* z4nLI)S3()%oZi}=-fqamIoK4Z;ZOIMzH0OeKN7k)nCE%Dv?XATq+*RUaqO-~o@W1C zzHI@^@=Ofh;A!RiEL8CS$Me;wn&oi<4Bkd0c-yl;x5u|ebk<@1J1KH$B#uZczr=g8 z$|R_hoEgsF-&r*^{P^!P3aj3%@c*FS=XMayF|8UZjcrrOi%GH%)_05gl$%4P#|od9 zI^B1~A2%OS_I!-EN0vzk`HJTX$XluvU`1`=QQzeU7KyJP0ao~vPIOGp&hd^}308w3 zgqiQ7%=N0|lYELBb7RCc6#bT*PWpvMggcxpl|7kw4~1<6S@C9$R#y-d-+rP>4OQGE zQpFhBiU=n)cyON<5Mbk0>k;Q@W3_q_aLdWUUrF~DPjrjy8=Zt=0s-!FW{n~`1x(c2 z)O&mzivAjvhmpc{pd zhlQdeTULET(2XbKf;0QVjajQA_nWUu@vL5&)>m{X8;m!?tn~ZMZ$SjE5*^#-@S>cWvN%=p7 z=X$S(9fkt?X*D1`jTr0*+w?Mu^ruV&g@7y8!*na92CrBC+povh|Nbw=-T}I@rdt>8 zI31@uwmP=eF+1p3JMP$acGTgHZQHi3opi^xZR6(s&VT=V?z!JMM9|WB63M_sR?_kHd ze4zUcyzy*$zZ3TuygS#8*e6QN0#2^(M`ScX|I?*YeY$7ZLeT}3BT8{99ukxIRqPrR zu3AZo&qyxHe*o}7JSqO|TFOh!nzAl+$sSywrduVCM1k~+_R%1is!r$h#Zo*}d# zqeJDRf}e0I(}18S&B=y=ik};fmDr7Wn=TXHYN+D(@QRF9>P+as3=dRJPKSOY9>dzE zrkHPb!)yL>iHrrhtxn7Q9_~A*9KB&I!w3w;<%YMBE=v!5R=Q0RZh_sN8UnX(Oz*KZ z*S?l;ZNcx`B~|Odv{_TQDiP=Og2gEH+#d~XO;bNLGbwvm6O2-RSY!kUX({JqHq@Wx zM|L)y&U$vejd6-Gg{}0QlvQ~>eW#*hDb$5-3OHmt4@l6rh$w**v&2 z_pGI90UIsbfh#}#J59W4H+x+!dtDBDUEG^jo#S>M*la1>7h~J4=A9PT(8pYw2RB1F z9Jt=wTD+DbOlxb=8v(;(fA|NFg$q10rPSJgwpR{kfcv^NH(ArK>qJnF4N^V1gHe08 zpD!L9_yusIqB^(nFCKW?)$@jroelS+BR!jZxKIwUaxyZvA6k9Dq#5dTJQH(PYji3* zZ!M)juSX-{nqLb9jYxbhY95h!%$}zoRd{(OTvTK0!W~wGY@Z=%ht@1=EAlJ9MG$MX zpG8m56_kG5;OnXzCwGU5~Zo_4QaT{ zcZF0eeIIc-5OFzUXrU#G!>|pn_wGo$5s+-!4C=!j+N8=W$$!@d6p7?d zVDJ;+)I61c|E0^_iNGKQghhMO^nMsf)qZbUt<8p^b%;#Zo0M7b3oT0_*A~FGB;l>7 z$uV&Z^S26jwJPCv@J`vAsAG%esk65RFJ^UuC!q4wxm#r#oAB9CBk#-@@Yqnp8K+W% zCHaz9AJu>DeazSDo-~La(}~>qI6|Z*Qn?$5s_rM`w(z z+Eq6;^B6`S@5hJ}9IjBh7Y9ED-|O~M+aUBb^rj9wt{tQHPCVRij&@z7UBItb2OaN@ zN%41#isyvc&jtKl21Fh6evx$zFf*PI)YpS4)jIg5Wnle(&~7wl;!va0FiTUNpkMoq zcd>J_JF$A-F#Yt6aG5J(k+sb#MW=C`5nz(UP&alLCflfPvV&x+DX{(>8 zxA+6w*P&cbNG#^9vvWTC`^@0+W_@LE61Z#mvweBW_^+qTG57o|?Dv(Whg3oUXX5D^ zb(1c86J=yasC5ThKIf4Z)~GvEDB*rR?J(3t<7R%M#LimaI7EjNQs?+{)bHH;x9NWx zpP-)6@m;ET;alodTS808M@R*5@nXD+u&oc*Ex6(afNt7LIeqK(ls;gjvG&G4%rlx(OWnW`C@Sif_hX3Sg5yEun7!$4N z@BVHZe!ewS-wE3#1+4}#bSzgxL5$_p2dauW0GuuQj%N)M2zid?t##YCz~AY+o=@)A z%uo1VmeMXFb)AhCrQEP(+_2kjG(6_zWR_C@>h87WXJkQvbiMbui&TY){O{uSu3YPG zj+Otu!zJ6wru}dIEWF3+jt@q;{ljn0QnN?j8I_qt0MP+4W^6GN)8Tg4Bh3*J4ValH$d{QYm;h&Pk z;8_DLdRYhJNoQ!p+nVLv8EJRiM_uF+OE5NenHgAwLzFT7q6?Q!w)h(c1Zd;N7i|EP zG1Wq)aldi%b_%B;RWTF{2aCihrD4lPe{imRQ7vuGD?TguRJ9|W*hk1K5rNZqfCDyB zaSETyHdcIS*=QAi$B=c_yb%{sH47Kr3Iu>O`2|3T=@USY97U?N&V+f%Ko zI;Pk`tD+e7<0u|tB94t-^IP!@WIp$<(#iT%yn$7MQ#js#@q7pGA5uF_1w_PHR171+4;9EpQm> z&C&7Ii&}phxoML3nh>GmV=fJGmrM$oIzu31K09U3Y|Bk~qSl4;4jLpL*&!BYD=C$T z{P2(*`y)=MaT>+P?ltQmBoE)+=Zxo<^GP!Y!g zMQdyKav6$B8EF~Rd;l!`mC3M0HRp0G>G(IpJ-mGj+Jhp}MgNNIAO+CCnYD6VNGY$v zM}W6qv4CTdu7;=JG?EHqI{SH)Uy{w;-z+4=DS}(uS0M|PO}u{C}6e53U!Qmz+*^Gsa)a)6F6p%{|-h_Dj1i}gU~7i;(dxzC{4 zctw@plB84OBcIq%KGAVjEB($l9z?V2hu%{|Z|Zqfh1LMMq(vs0rWMlW<{oB?4}@~goIO&73Ui=Q}tmj8rFuQw4skCBZP`h@{>DKagkj1CZ2UQhbw+m~7!rbOjWoL)$VQ%Lh3!kVq)yKHrC zJB-e*_z6b|esw#C=?bAhCPeBhmX$oMM2HDoK04xCS?(6CacYQ4&qJG^kkfVdZz0Sd zY%Ty1Mgun3uhggp@D~A7)UuF4C0Go|?RmdVQXz7DkMSOI7#lT6qP4)W*%P##yv=jhm7Vc4h3McsU5wQND%0t}7!oC+DRtZGDyd97I$Xj8f;*B%v-lq9y8_7DVO;;NFSzx_7zAP#3p zr>CD!I0HYE3Om!Ahd_e$MGMqJG=@Gks-x@U-Jr#|_+RA4h;^_ke@F7x$o6NLipxGCU+zAE=8Ks)dK?)aupIKb+*CFj}rmN5p zdJ@AWQpQFb_$m$S)o9Kki(^=kP;gMU2;Dm*gl=X(dq5dQhJXrJO^P&q8H$OtpBZoW zy>bnV&|V_So3DcGa7gZM{pU7`dR8(*SlC@tv)tU$I8>KU&eVXE;wEDXV_G$eONbA$ zoNr}kXfaDv0GkI%iK`;A?l2)Z4BVCl3$hEgFf>g29;}?0F?=I+i79 zYEmX+jWf)&urjs^Uf@K=g>Gdy* z8}!Nh8#xO`J^f;t>eI*?|HkuJ)m^P+8gM%0*e&#xK8;NND1;jSJn@Ju~RUX5vHuiCI_J)v@ zkd=!ct!7Q1*s@@$xY?&96?_5xrP+3hZhU7hUwD(^&j%3Nx#Sz1?4qhTb;OF02E!Z9XZXw`;bO3St|HF~UV-rtEte?>F~kfveX zY}g<_U$g}cxzi#L886m$+aVVOg?$oLt;#K(R;VpbE@GbyD$~x*W$=YnN=4tE6!6@C z>=SKq_t>0Rz3A%51&2-a6*bUk1mT^?P9SlF#_xm+~nUAIXUy z53_jJD$Ii0&O09qxQN=`^Qt;;?~=~C+|G|}U$6EBu6u_Bxp56k^7OyPqy|$+=I=92 z(kvne2e-fNRHp7_R~LyMrh@~5t2*A-iZXrfCfDz$qKMvJ>@CLl5%|7J4`O;fAoTQ7 zCtt}>Xb`}BB`W|VZ-0dz+t$Bd^Lks{7rdVM*?BMOpQxy?yXmeoZLB?D>+_hQN1Bcw z(_#1X>qx|Knrs~k97IYk3S7_pS^Jx(Et)kq!h}dEili`apb8-hbdU;Rq`xFR=gFHz z&Ap)4Rl~j4+qK|zM%Viq>iOHAsgL!yFFUi8w+p+cKynTIbjzqoPE z*L>EWXI^xBQ6!;5n_D)ctMaW7l^cKL5C-xqqapY~e(gx7HbMx5jA8o_#D62$WK3eZ zA?;}Hq*o$41xN*D_^#XGTWRj}5=HzL-Xx zx(EU9--utCHu*b`gVI8FE?1SBGMXJ`>EzU#euNme)P7iGGukYVQf2WTVi}V^~R%wqdPSe39CJ}X#_pAD4=KQTyW0=#?yi5E>sjc>|#}(a7v*q@8 zoLM#tDN<{|I#FTD1^c_kben2rFy6p+z6~>JtfZvp-UDwe?`2Tm#<;sN_ZJ010nz$Rh3xyFO>VbHsXqH^ zErAsgjAm66q`pmNN5Wn^`toCD0$8dc-^YtpnTy z&_%6WgMbB8#@P-Q6v=^<%Pl*!kM4=|b8PtdBBfRQZ96gMQ;-d}HczQpIV^rz&mcid z4Vq;+8~*XQi!9dP950ODBH3s(6bLCIIn@fq7$NjxSY(JXfx{w3q_t?8|H{IV{UgEL zvCoYWhQ}Td?SGmRWR%p~K&at^`?}a(>JS8kOUqD?gZqLHqo^VK%zA=Et2{j8!E0v> zt`boX_TO4g4Dy1+)vxzri8x%(BByv5&I(61H-P!GLMVrGw=@yIvZd6c@GF6|AyN~2)2Kn5wNL$v8Y-HE^jrtVr_S)RJ(J~Qg~Zg z!R_X;->`Q}UXgf}?AW5zb^+hU2xu5nZKMAPr`pa;i(GX*^#vC!@S<9i=;Z7FgeF!j z?Y5e8>X;O}T=HT(8>AS!uZDVJXkQCfan%r)rxS{}zlOG{cy7_B-D_do1HhJwCz2ka zQjA|$y5Dp?w@{uxMzLNSW$-nxBo~@(yw_J}Zdf9IFLk2xTgtQKTejK>4{jWV@JgE6 z`Lt%yCgre`TgtnDOQUW}*_8sUW5g;h`=}AdIc(jmEpRv&0e;zK5Y)DI1t282;pEce z>%H%HwL7R*N0qd z6kq*Uz%!L`BZ!bH;Eb&X9TDFtsWnQ@8NVh)JQaao_eqPM_ECLqo+ME%v)bkRzIW?A z-rK$*ZW2H)7wt>)1@}H}Qj+nc&+eW6N*i?S{tr;%pPE^BDlrikZLBb?B6Fu_iRQgZ zl@$=+S=V~&F1y_=8s))Vw(1yd(}=~bISbATjU4lxzyaj8FWlnj!wM+eC=Uo?2+wevf<(^_9orSM>QSN0E7QL=gQJ#Ayw4P$;ottGEh9dp{RgbQe zd~y1iZ>3;o5kmfo7xft;i!v5Fjw`JgJBiC7epAZr2z@oE&tSWp!W|sK=YNWuQ>rVt zCE#O_65XEJPuN=V=ys)r_T2N&!#AAgLe8{+R_27R(<1M)4I7MMWs-p@E5U~o3K=zWG^I0t4 zN@nhSs9Aqsb!9Zf=DfvBt-R>2tS;7Rh92tdxOK8-U{n99t!8wag57n)cYn^~L6Fs` z*Ld2y%X;I$e7ZLQRK9{2I|ZxVr5XySlUekm~>p4PcFG(Qs{E?Y$imx5pLD* zVMh6}=nK9cCwY@)B@wRi3!JXdy>&25<9l`nUmf9ZWKX^gjJIWuR+ zo+{iz8w&i2$aVob52y8rQDyh2GuPrKq5@Bh<%j__;wZysQ`e)Ai)PbF=t+DseuO1! zcczy%zd6xsfVK$NjSHiJgQ)G$m(KoR%O+;w_r;~^T+3$>JQUyQdzooFr1VbR)SzD* zC%EI)@7E)Ls^7D|XSW5SHP1lB1odqiHS-goOZM&Bp9#`9Bi_6}KsE3azzreuw+Y+r zQbc}-i}(*jnc{h({97#5?02u(`usn0rRbyP>Zp5_8WLheZ8>MbW~pA;;_~&qI+E4r z;Urv3INI+AtU8PMuU!1Yz%LtS&tU1%!qbu}SJ(6DjP1fyCgnX860KcUy(L?0D?nf+ z4G?qb7v?*<-z$IbS{SL`-XWoeWMBLkg|*;JwRv*46lxkG5X{`;!>-?zYj+)%qrJxA zl07As)D%uAGz8tOz4pxo9OrJ?(yn;Y&C7SkMD%!~cv5aeO$2ufIFSMkko%@3k8k_h zlov$`*<3g;zkRB2?wPkPx)lQAy4**k8sMV(-Esq4BB;6NkjDPeHG+(};iHX#5Ak_S z9}^^xq+ZZ3|1cuQEn{wG#@+rgyLf`frdGw;N8dG|{z0=i%U`4wFw|G@Fa{@UV8qG- znexK$IQ2(TEb?C>FZTb7D3AIAuSj$3IV+2{V~+E=9>Ym-f=AX8(bejF*K3jBJ)Dob zwo+7iE6X815laD>s^{)mCYZ*gYpOcybg|LvDhqmI1ai*RDRwqPBU$rUB+lt zjB3%p&%Ob6>b6 z=I2+%WkrSSei^5S#z*n5LNn)voC_pG@sq@M`B3KZ(~OBCe=Ez8UfZz~WW9lqB>PGz zzqSNwG^@miA_e!RTpDqsscj=m_OxhysD&j87)34iaP(;q8I2;(G!x*L%4!KZ`XB`5 z&0f5U>LJx$_ZbVlrp*L;F)Af@`v7L?;RwZC8cW zs~b}^u;uJH2GZsZ9B!{bG^P$N!Wpf(kZs~tj{^cPoTsf?WzWg;_qp*+pARKHE-qS7 zV^qRFqjh>oFvu@&GrkNV0_{^tT)QX49N;aZ{OQO0&4pJWKG|5Vld~5LRmfr5b zm+_}O89tujK0nIR&$Po_p;R<>`)4_?N~G z#Z~j^&cNIH(xPXCO)Gr2e7k|ar_3W(A;vzU;MIbSR0Up!8oq83EzIj-^C-ipjy19N z%rW3XTo94u$)D(RHtecn`+28H-FPzHWdp&DOL3a5Tf((&Bi+392-!W3=hb27j-W1J zbx{aA5%jf16#(8pt|Lh2y%)eYVEMcp@MH#;=C2bdS&eEy@NnxLjO!^23u@P-lcSfQQ_PPd5o-)K!1X)QKO0!r30k{DJ$_9 zBJtSRp!#1vm&y7^@jJSdmTFJg1u{x|j?uw90h(7mhL(4k^@kCz_v4l#J!E4gcVoe|BU>Vxgi{;5&6d!M6*11v?F!;Tx-l)zQI5suCFe^#;pczeA$OWz zmz-bIQ5vmUo>kkdyv}t6SKdD^YyEugQJZ#iYR{)DB=w;Os1APIuI$>Qe=O$*EWJ*H zcjZr=JI@sD2}W9-#xq@>LkW%<=E#0PCHJf>eepG#tzOzN;HuMgH29yz8lkJgmkQb7vr^mH?4W^0w$s22h2Awp|*7vO#g~*e$5!{ zuKHLP2yF;v)vE!nNWqA%Jl0lkG?d(tSGZil)7uFCMt(VR2uU0BnPtSE)D=ClZvF0w zojYH+gxZ+8$MW{#lPsq}Uru(8|7z9!SxzNs&nXsmViu=O zM#@7%$>aBX;m~Jxbyx%e2l01D+gg)8DHH6_BK;T3Z5%#G7zStznEsv#Kha?t?0!G( zP=|1bv4O2GyI;OhkFqFXh>wtlaox_!f}g*ym1UmYF9)1nc}}mZsJnXWtg3!zdf@(f zjp0?SJm^C%$LI#{epM+#9ajYb`JTlexA(ycuW6@zw9BUvX zf$8%kdRLx=nOON70W^<&l!;k)n7ev5Vb_$WJLB^R94bbTq2orA3e{Ty|B!WD(!QDD zZMLup#we~*^258>7M5`kY@p%~aZvAhajxFx#P}6~)egUcSOjhdy*Y$_Xc~;jI)Wep zIZ*MzCoWvp#Jq-6H`ACRBqywok5*8LR)N_JQ=zk!X2NiKRCM28s{ad-$!a0|bWMCA zPVNim6&e)NIF1!3icENHF3CJiteGC7j2rsBWmpPa+BXRrMXd|IQc+VAprlSFopP$X zn?|X!1G?Zg_BSF}DgL|*vP6EF0}DHYA1pb6Amty@z_dcK14;ddc+>h_VQEdRY93{s z6K!`xLw@FZcJ-IJworm_LjIosB(A&*M=LWaz=-Fz}#VP_XmGQ){tbA#3XSlZpu7sdHpgf+4=NnL3z_ z^~IS7aXetBFne!M@R+(s7JPw8@d1D|7=9WLHp65d48RC)X1P;Q2Ywq5=#Gl_bw^w1 z8N1TubkSTJW1OgjF$!W5^T#fV6^C+j51mR}SKO5Jqw%HsXeC)^%9taSjHJWwKVNW8 zhUgWHY2k==5N02mR>;D1nl&RpqWLA1vQE^R(k3WB^HX=NPZ&AW&BxBEac#GADmXTI zI3v?cQ<(XaQh@#bCr$J};2zw$`eHPVB2x^Yuu>HEaG){4u>p!gvTT?&pU~Wl7W%%T zA)rPL(Gfx+Y6MYVXA~7WGG^l{`#b8sfZY8DXg|*4_WpTMW(UcOv4d1oYj_z$WBHjf z09FbRiNaQXLowpGV1yO2K22W$RDYO>>Wc5@?#GsFQGTiN2yUk5;_Ee#1}Op*m}HlP zzT28GuFAJ2wjFku&Nq6)%RWeo0{mdD3m*iVaUR4%K}V#18I*+DN6QjJnRa12xal`t_9LtKo_-#Hfw2h%*L$++3+#x2dw z`o>Hx;{`(>^97mJ?&U*;dcH0WL9rs!jbr9W=GTOlt{%1p9gu=xa*NL{g#4kzf=zI> zu@@of4J{mhVaWu=LfDpRu#mSgWAT{DAov=~Cc#0*l;i9-VvaOW<*~{8Yq7LYh>$Nz{BA{TGrn`(om1^pgw} z2mQbIqR;)nYO%%WpSeKnJ=fT2QoK^g*w4r_{+-OPfia8z?GLL6{8#SM?qxJ8=9=DO zc#PuF<&9|f#HJp(I+?3aT@0RwXrvn5S3OT#MRv3w1g7q|X9zVSY?NP6sEp*l0JZXm zaD^ozu9WA>#~~>pK8T4R1{t`e|FQvxCAVBO6!ztachUpRceVowBKL$P`$i^+sWz`fCqu&YRAG0m{LN69Al5m0G z7$5L0 z3O{sNx3Q)9h3}$&M<=q~|ALdWUCa#-Bi)U(jZG_YF|$o;ep>Lb;aH=CI4K} z3yYyiE7%V1Ws1MON4mglZ^zhRf`cXfLDC^#2Bl7=)<^agZ$rm?8f?q_gfM00JS{Ut@KCE$9F%_y9 zL@>PMqyuNd<{PdgwZ=wBTyaUx#epnWolh=urs3+qbPhSg>$RppvSB=7#=-fUbMSLNE%Q<-aJX8YR` zK2yhAsHdxL=hNZ-=5LfPY}?-#kB3JQZC~lSuOtPZ%DQr?ufrUUrJ+o0>h~bDw6fZt zw&?bk-X~PPjGAQcK0=WS?!nouE4{(_uGw2RQc{bsdJ45&6ul81OYeU zp3xfp_8${z7Pq9ucL%OrH@8vcz6JtI7FC^3wf-%^X3ZN>Ffz<5elDA&X7>vLbgslL z-p})0?@2y)cO#~mAKnXJMfkB+35!#S{7cm_PRxef_Iqx0VfdD~_Nu<$&ICW`WhwOM z5&oX_Brd$cUV9aBx7$^j&GQyl^|lZXm*fMLdhF|+c{u=^dWlXsPBkQ-A5$+iewWb;sx52>f?+c}ed`A-;Cz^_*GevNB@N1|>XkRQ>4Ywf`isi8N- zQ#^4}mB;$#+(+1tGGbT&vC0@u7(0RprDd@YO>coScO|4jY6O{}qs1BdplO*vg5agFmWhDcT5dEq~5`in*gLI|30RHI(A(-v8|Mxn@#Z zjvFBP_yu7`EK(WEc^b~e7G_R$Gla)X2msz2Sb`sXMv59-QzqD;?j!jFC1;Phxr_7V z0?eA3rNy-U%5^WLj|yg73nq9VK}+*gSQrIEy!dZ~zfvulB>W#j5>k+0&x2>#uvrg0 z#d@rbDFf=Cd)W0!0_x#-n+fTR1AYg!y1A9dBQxSDoTcD1(IyMSB;om5Rrhp%8V_#8 z?c~l+b1#lZekWkw4qo-Em3;0G#ym93Qa~6j6n!8>2>T%{4WmR_rl6vcoo6gXKSo%@PAy(sCDqDNv^~7@}MM>%Xgbdo~5IW(~0C*w0NFqz)3nHH=C^e;*)wuvxIC)Pfew-e}yz0&TbT z$VZWfiOZ6QW!T)5iA%5A!409${;PNSBa5+^!p%xP^0`3<43h+sDr3TJ2D8FtyV*Sr zgzN_hL{2dC?RGM)<#MU+Nco6f8-w z12OfiZCE6^R^o`&sX@H0IhC}r1Ncb_qqz|fjvaT?FyXFULjm5GItV5)gJvI;p~8qI z-ha^9pRxZ7l*V|rCNy=-b`EwD$NxACklxiurJ*z6$?*BbnedBRz{t0`NLO^zM8L_n zf9J)rVv86+H>a|j60^-#+o-oqRof7IrQ2)=tr&S-Hte2LJ%R$D-JPY-zeVIU%Qk=!@5h|n8!X#sBM~1r=EH))pFmVE~G=Z)G=^H zvxLLys&!<(%?#)lu)G0^GC7wTA^pF7UulADf10A(2sM4*dF;~kUM^KR306(4ro|AI z29qMD&Y@J)yHKZ{<7NW1fO?*Y32W{b0Uy*3d2mqz?<-ARuNy3-VR#MEt|?UitruSqA?FNFyt8KIA26OM! zbK7t3(%$W_?$@8UJ&C6yHA&UE4ot}C)=K&C!&|TTZMq45 z#FkuV?w@&vRR%4XnAeXn6=^QQ(KAz0?w7=T@;;%9`S|sLN5DY<8*CX;EwQZJuLNO< z`N-is6Wct?iT`fwHZFs8%eyMNpHb_&DeCf}c|$)SsI zX9nq$>sFle?W$TzgfiC{jz!?x)7<*|B$uAg^%~%OKBl_40N(*$8Kfc7*2n>S>SQ(m z2`u_e)yQhYf;86%TgF*KY+eTvx6H}73Qa|g$iYlKTQ1i>+i~%iFi{wj#5QT-G)C^=y@qAz2UU;r9 zYLpf|`mKlCjFZyL@up%K>_0^w-}z(7EDNcWEa$u0)VOLz2>lxNGIA3$2C6lSoYRFo zmg)o*-!{(AULMERU*|lX%orT-d|!^TXp_a|SM`^}>xM&}P)*0V!O;@n=)pPXz2;Tj zrolKZVe2zS-dP(>_Qi;L+~KW(cE{EN&CO)Mwbwjj1>GLYy-hu8uXCAIHSs?~8`#}+ zJ~M$EuSw8TcAD3m@m0FV3akywHp*AT< zjbpodHdmEkA?l`{OjGtxgwrheI!RBB8-&8hk)B8wiL^g$dRyn^ASQV10H{aUgN1$Q zTSg;3#W{BOuBY7{qyni@x{iz(VvZss4`Ss?mVNLnJOStlDnwd<)u=R6e;MstyF%3x zRzgM!?`al9?SifW1!l9uIyMB6E>MJUWvpjP7LUQzoayVd;y(3~vSCI%qBhu7Vy`RW zGm|?1JE}kod1?U_I}6vCt(1L(QndAyU;u?`Jz_Pak{pTuDNZ0&>{3B1BReMiTU>zt z(EsrN`4lV`QOd!@I)zm|y{hc+PEb>lsE7^MLTHHu!IT@cXgU@*ki=2yG|Fwwm1THv z)`=-Rb10C7jHAa&3}ch3Vno(#9WGD>n8UgA zerp-Ps|Stp)9FAg1-|WQcAtd*3?<9alb_94ZKsupsmebLrZr~6DlmC&;pp$z*E&V4 zYV^34?lh?o&8Rf~vg`k-(BHjjO*OclVMV@$#j-yQ7x!KjT25jpe>)MQPJNJ_2~%*X z5UGGSevZD@HO=H7e%A<}M=m@h2Awt90<{wVl7yyCR9mLZP57nacT^0_{`}daSE<>o zUFX(fa_9g)Z~!}euw54h9=h{+nt;ivI?U5#wQp$(p4{HiGojOi6$+@ z#inY5^Ba&uwGfB9xqhF&i@6JEh_OE%ZkVBWPb3Az0eQF+u&8!BzN_0Tw%=q5ihUOl z#4_Xi+34#c&w6w5agq2`tDs&j^z66_j-R+9 z;+V8#z^TRL1CI{i(cRQp84ejo*ShOzvXsoGuQ=w36#oUYlsRYM9FG~EpKFx0rIn94 z4Ru%-jW5a45w{^0%W+nqxsT@{&zY|z<|b%(lf;(lDpBIIPbKij1Jtc@Z6nq)g8@06 zui%%Ks@53HV&u4~6(1*;*A(YmyysLPea5APk*MSIpYR*QqG2Oz2^-gl@xu)?Za)B&*L|hXs_eZhUF^n{NV%_&}$3`;X3I zdw@}^F(bqFQ=)1sQw%M@2qXq))12A9|MoMUrgorU)om1GajOO$oZ0pshY2>UhG73* zGKm!&p}Psjf;wH<_}0-QgEtdP6dDIyO=6Fes*v4!b3z0+8%!p>7K;4}5UO#ttX~rt z#!7L4I{c(z)OuayBi$7wTVr1?1kjphwXxDF;3HCss-dpLKg@ueU-NvozTGus(c#K` za;VWT5vn`)zJ6P#OC4Z=GCLX@Fif#2cc#Sz>2JxSm0*ubF~!H|Qj^OK$58H2H&Bkh zIayX5wRM)SLl>zvCM($<@s+#O4cFrzydfCHm~$!U`q8@_!cNf`)W}$v-b(+ZmO|~8 zu7Xywh;d(f)PQY>ZH#|%ZJvKWjF_zOuID!WBOUvneRqD@iQTQnqU|FjwE7V0+(wAf9}Oej75ptSDdRjj^Q2zmwl7PADokLrlP) zJ#78(3jWtK)u!>mc|ViVTt3FZ@^SI4xq4ToWjAzHosOw=*12xrA&-l5)k+a)v+p1k z)m@U%am=h_Nr}8d|2W{GGd3V$o4{RmljLHP5M2mcoG`r1r9jv4qd*YIuU=!BKz1Ks zLs{FqT>DkpV(PaRZU@TOh83shvDLZU0uGqt@O257>GdORpLSotu{((=!Rf7XynXax zuBi?t+Vlo!r0(xN+xYLE?^&3CayKA^ULEZxN(WBimJ3tFJ&dWOrj9l8LnWrf$jx(O zEG$tHG6cVfRpb-T>dMa@8JGQNA~t7B5f5+Z{`O;r)^`;KJmM*}4>X4+0CO0m!2uEUJb zR0Ne$F-NnDa)@lO{W4Tpmr-3kt`{}Gm65mV+!#}tCRC%CKgR1Wu;@6bjP-f;OwdxE z2Cv#+q@4Xh`B}S`1x~G;eEJJ6_le^?9!js@)E%<%vcHGb3F62gsnJ;V$K?-Hbk+?Xb=N#F!w9gJ_->j5_ugr_OU% z#-?XnlukF1`%hRWILbIGSs6{*V-THg8`Xg6{m|-1=na?OZYn=qSU&Hl)jF&*c5cou z-ov)TT}G;=E~6F})*Knu*-B1o3Wg^FLK{8?E|DDq57bhoaM$u5t3Zv6P_@vL5Pv}X zuY{4ifX93VEJ}#P|s7MDc1|Q{GIDeJ4GmKhxsuNAO z2+KSgiCx-8JSPa>h@0cr8UDQ;nT!2@<^q_5=fATfb2I-h*1iHZlb%U)l1!MHnVFfH znVFLbGcz+Yb0*A;6J}=S2{ZG(-~X@nw7S*mPCBV%x7}*DtGdhXc2zy*_^)4+j5BoQQ&5x5fYejO1ZB;V(~Gbl9@SqswMWc0Bt5)r&HxmltFhH>D?Ac>aJUT?k{CP^J%_juTA>uM3(+1grl9_v^kR-Vpdk$`-V(-F*lRFnb5v5&2>m zTBf2yip%j`GI&(xXU4ffB&&<@pP*#y9OHKx_#YSn{eCxcp$tQP?}#&U5harA^^Ae( zA>ZdkVQR{l-i~+bMiKdfnBaNLUs{{H_x&SviDw=`VOOhholNjwvN z5TMINT%rN8vjfTpf;&3fD zZ3IoFxd4t}2pZ}njFZeYMRyU*g}7)=FWP@?WRU6{M6%Y`V09&Xt;BbaCqhk)Y$_r& zch}t$Jf@Ve%gDh`9dtepLzYv#=n!m?{;^pDgA+Z=GRq)3 zXs`$5_|i9h0gOY7K?)A{-qUn2V}^ZGw+cHOcF?PoXnwCiG7iO!<3~4>S}=)ryoX-z zz9(SjgZ6Cb9G&*NMZ{fhZ@t#qoI1K zB2Soc08UwneRfis>n3LiVn>}JC5lBFSRw=kj|v_F&0vAF=0$&jSkfdmH>)H>--U_^ z4$iTN5nyLu_E-oth96Nz#IeroT)|d`Ih!0CdB$5YgaWWnIAjzVl(DX8q9u?`DP)+m zd{g_=`E~W-b|TJ#OdPq|BfkU;h?z$`c!n8hB%@Joa`K9SBpAX+>M3)gm{c#Krb+AJ z#rdL)B>I1ILSs}OkU3y{AtxPE*c9EAHHq4h_)cH=`vyqouhfT+#(5Zi_B%~c3F+@^ z9NswZ!YwC@HkBx9V8+5Tx$#Zew6o_K>KpkPD2|4q_RRu|;AM?GG=^_O9IA6va3Xa( zPuc__7cm*P?J@!lb~E1xk&GH5hRdJ=Ovt-}``?XYBDi5AgN$Q9>O4v5sM!oGQ76E* zV?@+rhY)ih0SxsQAsV^{Dn|SEIG&g`Ya}x99%jmDhAJE_QB0bHKX(gd&=~nS$RsVI zMv&5jeKLn=Y&caio=vSaEQ}L?7$xm#45kYFQROJ2(#dd)A!Ed=#*Hl1GPewKp|6O> z8bpAMf@=r%bdkdL5c=mi4&)HXEq?r@d7uGS#|b4u^EI?Gigk!c4A)Q+cL4qi0C+QY zbx*@Jg0VevKMfm=tdqbXy9fKm4E_PvUl>i7j;$l-q|K9Be}$;_ku8ap&rUN#Q^UQ&$Q~l2|8THJ0+o#3Sh$7(6cal!#6k^x4Y+m!c(~i4pD2z{ ze^l>JJcG=xme4T6R8LT{p->w@stlYoj&@yiMxlR5yNU%}>>+E#3b`EN=<$Y}cUND3 z4BHMBaJCNp*3@a2@C|GVMz{wJo+Nr!lj!)7Hp}B~4+);gGu&Ozf52#u@*cK(x4N3f z;2sYQ8G%?9c#L@FZ|#m&EGWiB0tcdGV4P1|78q|nXEWOSI$5+G_S=?0+P16dE6A}xg)l{*8{%JB?@s*L&;?L_!dV(13NJ~mT$4sOOCaY?v%A3Z9NHpjpFzcA4s{aavv{v3$R~XIV_rqE27Va7{$b9dqmt8EXt;z z&3fRw8cSRn;t?jAQ5C%X#}V*yjEQ-yg>tyT=meK2^vD-Q2SNsm-Xf79ki7gjV7Rf! zr6EJmdl*!>h~X3M3wc$dk@lQ&O1YurKUYQrKjttL=Jq#SNFaj*yWmnnydG#F4!|Np zBFPhn#Bpa$s%bjfPIoS$eJ0DOT%A4MoeQz%D_jNYjKd-KH+=82f=6(-b$3H526|5A zd<+iXHrt^*sg~$6tW7jm|4YZ~M8$VRr%8grDv)g)ZVuyxTQ<v1f*;5i6>S{ zZ3j+p0!9^+KHx6hNuqtmViFohhPYi2G|oZs2Tama^J!pUSH4M(j?aU!AUFc+Sc9ga zw@Dx>8;>}XNz&=ez-ZHG|_9E=drt0~S?*iJ+| zs0Y!Wzk@|M%iLgTBS=pp2KHgB(N6deq1g$Vn(K~sljB5=c5vc|lSC)gqdy6#9VPrV z9#)NvZ5$-NJ^)}mh<%DQ-GojC6+_|m1Wepg8<0fHKf+RC+J!`}VuaD+2(ymdfic9P zNAlZn(7fVoOp<>>_5dMv8y-cTF#S+S(k8N!wVCV%sssjBNH}&p-a#lj@h&y zZ!~0)42C=iN!0KbzC{~#5<@Z?uLalynBZQv$T?b8F)&MfEYh7xbTA?motxlW@_y3T zIQ4JapN8D|4}kBI6p^yLCPKUyNj8y#HVei6PYx;x61p&Ef1Vj0wt7q!q$6wcfhNTk z1DRqB(7R=vVs>ji$3f2val|N|P%z3dV<2j?c(5uGdJaQQ;jz*5LYxzmi&@RVv&im| zuCP&wR`;ACN@%q~4*UkfP0hivuq0!Qup;=l*?UJiv<8-VVq*7-H01oBYEN=-gLQRp zuanSK7u`7OhPZcn)8T&{^`ET2ky@?yhW=u&~_V-`5!lVXM1laq22f% zFJd>PTHnxr-IT^x5Qs`$uYPg}X>dR7nKY{x3|mthqTxGHyudw%_k9Y;V)uJm z>izorJk9qsXovSP4K?iw{k6`~p{hwONWIr6bU;J@v%zdolX<_YTxeN`+o~pq)o`lA%uKe63_w%ODbc(#qwHxCxP+luP{}4lAv-=6rBYVCie>5N%9aTj0#*Sc1n|3qc1T+2OF>KXoX@$*|-0QMa+OLxzot~n7 zG=QAj#L%~iqZnGQk%3=JWF9EYNjIc1ycpza{FxQ{zDSgG%HE1ab8~OeCgztT7Ns1q zsQiSYeg^~=r5^E+IuU3}`lEzH$_1!unQmnPK5eO$m{!cMgsj@UH?(f`KeQ)soiF3% z^cA*V_e|GS$winlvIjKdH;>9Yh3vFtF000wfV1NP*ZlPN(?oS<8jm984(efnnQ0#8 zjOxtke@(YFtCnC`sS-ssc!_nG@vdo>E!C~3Cb}Bk^MzZDJu=+TXdiyHADV7!6RuSs z4RuX=QS)UPvd(v7!aX9_AhF;=XU3k|IL>Vx7PgLw+9gG8lc9IZ(s^d-Jg~J7j{CY~ zarNgqm6?9(toWzgss&+K4)q8ZO#s3;v}NXjq7G=gxu3R;lFa@r@zXFxJl^LbE!lcD zsav-8lG+|^)VC~`{++6Xn=ooI0|ZK=l@GXz64J_fsFtv>CKs8{i;%7S1%S(-_}vWDs!8$d;i4A#_ydz`jpp z6E_K6ah-!32L`3}CN2(oOe;5s+QB7UOqAtiY;aMA%O);j#~Ud#&Y7wksfA@WE9-P7 z)k(ACoYhg`%D8B4N|YWodbb*#XSL3QR{Q^SBExdy^D?9(5!3egLJ@Vzafq~6L)NBD zRg3=aZ~x@6ZO`$to3kyg>)D?{o7#TOk42#0=p)-yB9}ngaWvILi>`I7I5cupkJ__J z&P z1XY74tI1k&CtoR487$amw2=&(P9N^ zU+$+Uv8}9X!?j?I@3T0Z4-Ht{(jD5uzEHZC+VNcXB#fxD%-8@-2pA$#HNMW$dancO zOd~c6FXMBxudz~bUP%y@4IQ|yk0L>1fLL%qDYpaHT9&BlZOfGckj-xaxK1^%v~LA) zjbw;=^$*t(b!YAXu1WeUQvTr@P(A%`t}74E*%1#}p=YfG?xdo$&II)Cqw-%x6?|u< zmFE9NP${3{ATBD)6;mXEO+RCsaKfAZhFOYIqP+EtQ%Mebl3OgjwG^EJR-2nq&d9Bb z)230ZCb8Li&gv+vd2qR*r@B?QH)5OChlm<9_>BlZ*V&eOTDoo0)`xh9zJ#3YR54nE zs!QwLox2fQWs3InI=gKdWND!-dz|<>7PXhAFY7uGv`VIVp=zW7Ep5X6U8qPG-QkVw z{(QP$!Rv8-EW!7=_kyxwRARQ)VS!QeaEc4<{ZIpK#3(RtgU7ql4N`5|fP3*3Z6Lbi zlQ9c9^vSSxL%`8t*Kp~$G`LnR{uJC*^U5KBfO}fMd`q(}M^~x*(G3207vfe&rMN+u zLv#rj2>sM($^R~-(s*m2>gQ_z(qRsv_GunxJMsk6M+JU4f5!;Afs6I860}ViF3K2G zLQFs1Jb#w?UHrUuZ#Pqu$y~pqZ=r5?5MdfkBn! zjKlaSHL^4pcY^6wZORrt-B+JXBc+4a0Npk>NVRq&$gRx-PVLA7K6{%7yz;CGTqZ6& zNY#BEt~9`U$#cG`UU%_&Jjqu*_u(NYGBjdaE@@j%`O<2X#U^A-Wy36a8vBm{>hBif zYOyBw=EYh=iEGL-*Q_qUV}s>aJg@FEw8O00Uvy<)jZuY`YH|QAU}sn9l-*{Ny-dto zrdQgT*=Euh`nPhL{-d0Mwy32oL8pw4by7+j1uXonR7#y{GU0#xTW&IMIn^+I~`+d0XYq+PlOR!U|nx2^$OF2Z=xMtU9~ zUPJ7MHdps5Tsdy$c}3&rQ&vtD#tgZ(U#0bTEs9c`0Zm{t4nj#Q(<`vWJ$mg@wsq1+ zp8DI?J2`?FN0Yb!?2_2VvM$qJTP)JJaaG4Ci;H6A?S@|BAcnJYZi6M{7|%00&=r_| zirvc}+RP9|?yVqFfXuPgE;y`tt!(R3@gg43Qm1YQ?i4|^;~F0=YY+pK*|qS5G-j}3 z>pBpC>BZ`CcnuZeEi0&9R?r)%sEMVfkG5eV6x3w=KsqKt^o;y5(Ie9d*hR%k|Fv-f zXPf_+^ECd9*RkWh2JQHq{&G!xw5G15$60XGlX=@(w5X~)||@1dLQFTZL$de-^- z+Tq;2Wge%hdv38MtxiLNHBKVy04UVUI@Za`IapupLK0&5EbKyF;fa*p$08Y>C$T}bA%DT8tJiml(d`zZyY%h!I4T|%jvnntSh*D@x!I%_#!{6RS~MHj44^z1tn*;c zc5icq7D>QzyWft`yiew=@GaC$M^jbQ+I z;ls}r4nIzu#rYk+jE?1Q4q)K1=T=YY-%Ahf@brFuFwX?QE)@fT+ zf8F9i6{c%zT|4Ue*Q@q(W{uzHpmK(5r@iB!URm<1|?WDDV0L_-N z9!x7n8Md#w$S8>WG#MjaIqTSG zYymZNDS2=S>ijWHt;|Ih3E|RT!$qAyn1C!aa_s@Nb5p_D?$MG%z_2%d-23f!M9t6V zcT{dD3O_(w60jyxRh@uY#9E0wf5``4w$tQ-RV*<2H#OgInt z3(S%`{mJNf1Ts3N_jUF7JUx0du?C;UCx^h^+Ve>M?foRaV!Q2q$(;8whVE+##3zX0 z^LDKFxz`xC>1!m&Gjw(r6lEw*t|M{s6M78U&-?MBPOt=da5CxJ>svlo@~<6+Z1 zFrV{o!Rg>?bgrU)E;{|JmF?wPlf5jm4XHewv>uLHcZZ2Y=crNAkyWGubd(L- zJ%FVTE%<{(B6HE{$SQVYIlJNP?P$(!Oeas~zYk`Ur&H+*S&ZyUa9*u5@Rlwz+TuPF z8LOT!O?X~abS7o2&G?U>L)_P!-mPzyL*uya9Bdz(8@X@nAx*#9F&$yaqQZa21POPy zID6Whmae0YG_jT_Uka4Ja`!8q=TwpAKW_|AQ{^~5+F{@CEG_`q%Bmj0NcLp;+8Mt*nAA z73ZFR^uC`KdVSwLK$~95F>fM?0KXLPE^g*2tIfpa>e?wl$Z+ zE&+2lOv8imXT`#~ddr7K=h-Hk>`}!6doQRC$V!EewYbu8V+HO;!ZuIuT_uzmgEIW8 zqEHJJo|iwoxYTro*F^xg-_7VVVxXYjue~#O8_)Zr&7^V$1|*At-t?1YN$$ z%T}bZE6>F4yty}Tq*}EulzRFoyN`$c^w)Lff(eh*!+O0&7yo4P{av-HW!8y3l_r=Q zYxLDo4VFALAT`z*KEG;Hd8AtA3UlFhS>KzXY`y*wYRa|W9y!1brlYm7LT^J*{k!R8 zWW|cB8_&;^_v^v)<5bvm=E`U7CPN`-eVMl~_g-VD_EN$wmp}ivr5cbmcluv?wK)s! zM7exbmos4>fLlm84(#@4k)$Kh;{gdR}pl(cz1$pcY-5mbB1tS9a2;{nU?Z zd`i4G*lte@`@ezYe?ABPx2A3uCKitWAKN0cGW}OmHvkB`wnkDBj0Fp}V=13x2ab^O z`4V4sD<)VZn-C$Cd!Rcgm;r_vCasSlmN_TVP zY=77t{&Dg3jF2BR{p4b)AsVETB_V*)f+!G-3`1b& znUO4pf|w4Am-vn#86x=!CIbdhqLRRBbMZ>_*V5q%rzcCcVFj@ryW)WU>Q}-xv)k zSvEbSX$)V(ya147Chr{jMNM`{%!|o72U}+KP=9aa$B+b-(iRmUz@r#|QU3 zQhGs=D6Z(e4Pg4K-sD6u{h-E(>gfo#&-C;a)30(q#8*SS_(7z0rgbBH&V- zV+Z4@So6c@RF?I|oYgHb5_!~@LIfWgXirXxoeZP0_!J2GnUxfMm6FU&g_}LsXZ)Lt`AUNaC=8INo0M zue?gfve$H3yU~)w5Czdt{kHs7t@cklifU-KKi&g38WjW()Jej3OnwHH$eqJB%y%;nJFLiUGZ4?-!1k`^|RCBg8rbg4DQ0;BYvGME~P21A>jT1Z$X zaqa@yw}sE={5(cbqnR2^R@HHxNE3$H+`KhD>h7UCr`vk}_V{VSCv;T}^Ih#D@7 zSPb9p@57iJzogABV{NN&7J6C=i$cvtZZsRBPQ$LCqB`Lx^79iaINV+s@fK?x1#yBj zAhg5}!O&djn1~63A*uzq{L=_e^k>re0FhcV_#nvsQwM~#h65^%@z_=!->88o41*S< zD&h>Rn1;Y$X<^f_xx{IUuoWyI10n%C^+)4J5n`{iU zHV{2*BJL!}NUZ`;%Pb@dHPXpVrzV8+$n_Nzh^wUm=og%}D5x+qNMbTT1=+5E_56h^ z34cPPB|eBWiK?(sFl1VyU-2TCj18uwOnN>!LUPyFN)RN|4P?15HdJ2qORVtM~t-dcIHKQi7 zPjqC3M?^g7jmSca7(s3>Ri&L zTKBjMIRhUOsnk6JCIFbRs(9Zo*>3GsC6Pzl#N2T#T$L(>W^N|v2IcuH19CztA*)qA z1few;-=ugw@qkP4fS)4C%i#~Kj09_ZInU~zWPbC2+bq=>!wbR6{P;#z6c5;fvO7bc zSJrf|XOggnK_n4d4YyxZG$8Mn6c31jGPXcQoH1*rxP@4=wr~rUHu;CCw9!zhcLFf_ z!c4~jE35+|n&@LeI#z2hHX4@(e*qQlAn7PifXpIs(*ch~lFM$tuOUk@%F;d6nJzfS zguRq&z)ZRf0|!(Xg-Q+40M$CUIv;}-Y-Sf65){%X5Mta!L19TFYj2*_$T*NgR78d_ zNPwzP8}YUZ3{^YqVfq(j00k3WyA}$49KMHyKoXi3^87 z$NV|(oM3<;QJF3a7SL3>4g^Sw5P%WKLqzKl!Y_VG`VFzE8pCP?+5<=bNT{_2V-(K5 zp-!cqi?>!~*Tod@zaz0gc2{!~&vyO|{VZ-0sv(~zYIUZm@f>1R7-$+^hl>WBAWK|H zQB4Sks<=qGA=Xwdf{5S}>mY*Iz$W7^ORPlZU;m{3MN()PrqQ~A!0fuMDl7a&tUX>h zV}#yA8Hf;W4X*05uh}53@@Tk#TQnUqnSR0GvOUNUf*r&sjo=bT^O<%@wrp(16Xg&^ z!5bw?D}^B@@-t!Il1L2!8NI1Ai+#-q&*Z(lg?O8o2n~N2QGja&ZW^>0`3>v5K7ehL zfXznZE$iVy#I+p31(+%pWBO8kQ{bue#`x9&@$n}pP*}JnX*AynQ6K7W1Z|}R2bO}; z=mi+a!2p7%?1yj(k<;3%SW?$@g1~eQc@~78aEi7L%kXe>P1Sk-d@nB?#!=70oWc4~ z5z)6FT2_`%`#3}uZz4Mwq#`)Q@nFBB7f|QJRZb*DtW`vlmmw0W!{@P1J19Gy!_(O> zDn#YEMo?={ZNB)7v#EglQt9Ldspr zjldq&)ASV}S7Y&%z0n$|GtGgknT;p}-h^k45@G5g}BYdvM8TSKYm-g4wpWM&-Ubjo!Grq^o z;BHsFU)Ah-o}XjtcptAh{Jy7;?0ViuYv4P+pYV9OK94iGURU?@-Y0!l^xd9MU+n## zfCV3Nzn2|5zQ>Z*Yu~qvPkg@3*U7ZH^0&LaX=Zx-$bR3CgWkui^UKZeycynCFy8y= zosaLV@3$az*tTrAkKHl+?}e>69{0=6yqFm1caUa(a;@GdszZCmpb`j5Gw6&R*$ zD36u+{c?%E`1#X;&24joSCzUT!h|pp@nUnlfQ08e53ap!51~CBghXDbzTjU%OoUki zWOa~GCl6v02VLwle0$DepREpuj^6Lv2RpHBp(b17Y@trP99#P~o>$vn6JFO{g7kii z4_Az1z=tlC&m1OLj;)??Y^4c>gdX0EF(#o}Jem2+>?$o8CXHfjR)FM6mMO;i+M8xu zV1h^yC9bP9!8OI>aEM1ileN1k>0$o<1c^-iK7yX_y%=`%VAi5emlZD{wYoObNK9p( zvV!xvZUsCOC+5}aR>B)=?4#Mei6!RY=Hf~mW|FuVw9cwYlgCWU|ECRBwzxdBz4^7@ zD?6U|Sw`IFR_(`Wr#U_cuzFl|X#l(M-HAa-)C_j|2*iD;kQvpI%P{H|uJ6vSPF(i# zYAe5mh@!k}c$j$@e9vplwf6({wO5bc)zR1&VfROIz*e9i4&N_>)?G}Do1kycn{wXi zD)RR*L|Z3ZB6x_E{Q)B&^ZKQ7jYT!uyQ-FD^`cWtE1%}(9<6Y7*mf*O)RhUN=-ZWl@OP69WS|7t=!yKE0ztOc}@St%afVjPraarGIOB2?dz^r*r5AW zb1d6dnK<_+4+omY3_{kdRI|`UOFOOoKU!MmN(pRP*!nHEoH`tF0HN^m`@{`lO&R|2 z;LsMx6!yE9ct`QvTzT^X!J$77F2FgXAVKcPWlirtK0fxxtz+a;;fZry zRNKU%5`$IO*B)|qiuAzezIY&<#6CKZJK6usumW|X$`qY7phF!Wmpe5yWmu=|d8V*t zDNhfFy8pL+gym3I_%7TSixMkQ90z)!34z=e&TTU2()Y;o#6KjkUV#A(YFOysuiM|4 zg3Q^CYWOWCX9_EvB9^E|E#|~JJi8p*0xYNXo$t$SZhF6mOdqhg$gF1WQgoiMfb~xW&l(|~-!kgK*q4LDGU@{DOIVJ}kTZ0y!)FzBY0~Ea1baIi z`vt?UkoN$|r-p{jSMeKm64Y zGi=;rQ9SWmoY7;F9oD?k?e3QymPH;z-0_pKu6h3|&xq3Gn9^hf_gY^B<;7*X`!WH2 z4&Knk+%-$41+~}a1jv2M!1CM9cDrr&{CR7YNJlQ>}${#Cumd4e&dsYB|+!?(R?Ry&dn@ zD^*E6L|LJ%=Ol5gr3u!}q;YhBVI}Aeqwon@C(%?c>0hLLsc2^bf?b{iV_6ZUs+7Rf zktw#$dQ`7+6i_;;Wo6fncV5zp)AC4FezJr#6LeW|JK6vnU6z5^tcSy*B1?aoyKa=C z@?e>EoG>Zdop|Iyl8u8&j#)M$4xJyzn|EuOejIS=JzQz1cUG7)$e%icCXlqFiZ?DU83$@J(a9a0*r+6+?9X)H^ z0NOYNZw!i8sgd>1yk6T6Pn1B&xnL6^wg4Fe0a$>lS{kv6 zcgvGmK1Izj?e(;-m{pSLbYVZO!CsHd^p^vBOP_zY-8s&mqivTIV=$f@VMl0)m%lOZI313gl$-G)p)X3` z=O|#Iv8Yma-l+lMgwjrcB2BD>C9+r%KW6^qMD_WvNv7Y~Lfncsd8HF$=N#(AJ{p`0PgS9&^&gV@+SgPqb+$S++eMi`*CPEJuCfHW$k)ni6 z`u6G6)yeMjT;%c7^(pJqxq(Ldue0;sr{zxlZyW4X^q;cAS5S9>`J1(7yT}mf1K^h~mRZjF9nUpXOGTjhC=NjOZ62wS3Qe$KOX2y)WbMJ#RC1KA#uQFdM`#78g_TJh8fGN9_8ZJy`kT z%?tjWobE8KEN>3iLu z{l34B`M)w_b`Fk0z-Z27^irO9R{3c0uY-7q+ti z*s5#s#%_LbXulI=_Vg58{tDh01=!H>8XwQyDSJk#XRzWgM5(Xx#{KN~2-5S|yUc^+ z)GH9JUG&1+_%%oUsCRxus`X%5c?{&{bn~ko=40k=YVF@d=kRx)b33~(Iai$SFs!rb zB*mzfrEZ~6J%$&)MLtSADhS1{BrTJEh~#hTVocP zq<)YCp0RJ8vA;Qiax6v6`;1n5dRQ}8sIJEc=oKSF960(Pfb|Ifv)Kpx%xmYfUmNTa z@c`;U{;Jm{q_tb>-b3NQrgqX>HYUw|qmnfbeDiXtC{yr_Z(~il<-Xk7Tw{%n*L)(x zbRy(vyX3gu;o0%?CHAJ8ptSEE?-y4taF?M7>#5xmtTv@N0Nm8b=97g-@A&Mb=^2 zJ9?~}=)%}g=g4V;}K8+98K(O@dRl5IGjRv)0_MJ;a$DU z_slms#y0hPKKNg*XG>iR1gP?s5QvaWrZG{_=q^TMg}rFwiG{bgQ?>q5TG6T5nQ zUs534L0&2E_Xh7G9>Gi!+li!hV~MSc@A>!cNFC+tghAR+~avNCx} z0rL^+l_JV>6#1y_v*I-!{Vf5uboT{iwXZ9;2Rgyugc`;+H&7$c8>Aqw6@0Z_JWifl zTcODytRSz)H>O_0EAw78b>KF{N-u;Q*7H=)hkAj7drai1UlSY_cY+Y zeROd0T~e-2&vXIQU%BH)W#jo^j1NhE%PRIgt?y$4pYyV$z>Dxi54It%;rch5*G+FNp;w$-=Xt^< z&Pm~W*Ob_Y_(%&Uo%#|(obk^2ll|K}7#r(t%EKu!MbJ`CZSg3+hu0%=>F<3VEhpQb z&AkrC%wgU|ttVo3x3J(|I=$oNp4>Hhk8BTq^{&mzdy|8@`loqV^`x5|)?`1C3n9mT zIzdS9HI&%DNxBs0iT!S@xhyO3riw4m;>q`2{gc{#wAy&D*bq`{Vh89UqgQ7d z1n6exjL*66`^b#mcFfN>c6F!rxq~U}QpX~3 zrpk=X6UIq`go)HqQ|ZG7QpYueNkTDYmmk90V{T-Wadua`^)k3>NKa@)BdaMOR8P4jbTF8fm1 z>_)n0Bio-3NEWA`7&C~|18X;E(P7Z0OTT3Cr$wJkg*KZ2ZT!R@lljdl85ecpsupy} zI1cLm_4zUjf+dqi8n|YA%k-=kvd4V-c5G-bVF z-v037XFb%tCD4xhHG>yHvy1(mG(x371{4aA}1zGoT_ou)#v@8Dk}7vnf3HD^DatySX`i>iaO;hKXj zYZv2{Z!~pw*H62;4o%A(+UDt0E%Q#vN!O(GOEN}nsB~J;$tP7^+wKypi=Ld;2{u38 z&eogygC**hmVdH)4Yq(--el#v^teH<~Gk+K40d-`l%p z&>(TalW@jUAh`5ojlvrD0wW&OaIz_%fsO2Toq@3l(vZ5cuvY>2+*e(^pP+w>a-)RP z?*-7I^&L2zK+o}ZyudLNqK*nUCfgTTywEc^Y)7)KpIqM~NMo zSku~euo6*L0*@LFSSLoXWq6EF^>pa;sy%B~N1?p@HLy#>krp(So_88Xk~wMPxjYT& z!`xbS_a7EFcCzRbA^)Z{6BL7Q9?Sa7aU+Y3M0uviMQxJ1E-9}lk$VUupdGY`zYc>e zsj_LusU8fvsYw)1cGIbwlqHxm0#}oN+!Mk(Lsp4yUoJlBfjUr*3jTXjFFPy4|COni zmEnJiYG!BrKlVJ9t+G|uq{9#`9$)Q!arqg2rDt{dxWYn0ib6>H`?6hK!s%VutG%0* zwj^0^K9Ck|u8C4ICqFd6Fc5)!S|&aHB}@z%6T`HkFv1WRLHp1lX{i_%so(fszA z0RC7O=S7AlE1f8$L$WB$AH0!R4%mL`W1)?5O%WnX>rnn+7?!v|ak)L=2c=E+b07kN zst246RED4E#3n{~YFJE^90_<6@;Hw?EDb-4(6jWE-RgP<9LBAHDJag8G)a|zL7$07k_>?0w+|~ zao(&=)G>qfM@rWNYy}8I^WN!6(4r!N>ON@x;OI_%X>wrxpzOGcIV3TOr-NWC5`I81 zmu0#Nu&QJPz?2@*kFl>`Fnuu>!q}I}S%;4ZCScvL&1j9#8ptGMG>IZYYZPG<>bToX zG$f0*faVN_M};J%V!`|s(5#83^J5}1nY|AAJ<2a%oic$<)TqE8VOC5E!ZE+`F6nq~phtlRK>&KBJ94JBCDNgxGASK2mj>=y5;yS0l6q8i9pCmao1V$Zoioy2!gEFsw- z!eD?=!uqV9IN5OR&!Q+{6PuLF>N+ZGrqS92q77|9e7H$UTI}GVq0=8=S0-U%XwWLY zxG(47y#c^4RSF(1#UB!XgQf^eNr*D9_8w3)qq8~tQ3>XqUsBpquGE1J0!17}FA+}3 zQ0w;sCzPU%Bgh%md5(na0nXzFbp!PTu_hsr2KIs%YJ25yT~ds7=`hT!*5G9Og&uVA z9yit{5}~Ekg5S=8SRBrs}`!~O7ZHp;zg+2_@lZ@)xvBlx=T@Ghq+#}0i zdq~?-KwRU^BjN`)nG_rqf+QHk*hR`jRDL83QI)rd!Igj<#BvRmjVln4x1C^mpaOv< zfS`-Rbih4AhdMZ*K!frj3<)Lll93yDyKdl=Er`Ze5vKr&8BVI*fQ#88xf~(Diy1p{ zT>Tcb-;=)it%xT;oa9DC7O9ylYC{=*lp#d$nD3jR?w?4>B>RKB@pKYdNlGMMMDPw| z9=1!5$iG~Os&Gf*rz9d32WYPFSCu_#8ZKo6(FuVgN_j@F=Nx@>n0@hv`dQ(LmL^dlqzsa-`OlualDO_Vlm|jUo;VQjqr~nZXktr3&mg7A zK_SLe;q>49kwD}M3}4UZh&?C4=E&pOrL=X>jK+aOG)T!H9h^k5i8uORjD2H_Zb9?s z*tYSEZQHi**!CUUwr$&U$F^u|o^u_x67b%H$aF{R*i$|&vK*>@kg^LogiNc{vGOp8oPLO&b zl>`RzT4VrNffUU7S@K;K?25uS@RA6^%cmW?LdJ}k#_GUijQokXY)ri*R#Ho_rjB$I zjKZl*US#9`ZsbPhb1Em72FVyuD?$GS(&3)~&A@gShZ7-izpwn?WL4q^9NO)+3&xZ-mK$kGv6Ut1|grlMnGyA*A z-WTDs*N%uB11H5x-JppgM;^!|5;nFG$s6UDzTN~+1TC--1Q>)ZRE47#3M)z2LqV)f zh3r!FHfPL1SxiTB1bD%I2iZy$mM z6-Q{+<7II&8La18vK@g_;vUM7e%v&l*NfKk_J4@sln(coA%dZAKJJgX^xg-RiXP68 znM#b%SP|a{{SjwK%oH7unYb~s2D+cDoPDARNJP2+L{&b#OB2d7KvF;KK?Lr$e@u0= zOsUZ)9%P8Rx3Cp0Dx5a8kXNuC??_{Xd=Xq;lc>-KX})c!TJG_HrJIg>#2X_HAo5O| z5MmPnpn{@85sm#$cwl(FY{jyg#9JbAVjiuA|LMW8)>0}LeDK&t`knYm<(Q)Qw=_K! zDdz7UWwcd6RRD%~WHx0&Q<2RvAh{_q`ZkGC@6( zGujvpqZAY=Kv8N333QmUw=gRuG60K;{0-e8MTB1&NJ6QSNX;NPO+NxD@>4okiCbZ( zUz5squSv{|sB-E=JJTg(*9dN+w@hR3_lhCdgO_smTGCopu0>{F6WgvUPE0h&u9- zt^r;R2}z_MnuZhD6j6MC&#F4jz9`))z+SqWAR>hoMhB9~&wNjE1_bDg5JE4c7&K5U2}ck-;*JufT_q-}AH}LBj$|9R zHuN7r$V$VLOtfDtmWb%6~PmwpF9mcz`A$ku~sfD+~31BdhZ zsmRI*<%A9_QY3;m{;Wd@rBr>kABY75&Jmt|taV^!#{P&HZUPVIi^PQ(i*-lDTr*ln zf|)$#gXKajEohWpG*%rL1w!ocUTi)Tcn}eFJK866YIK6MAi3M04$YLf!I&tFCLeOB zH07PTpVyeCA5^W69NnKaG}}ch>{N@!^%*)9jbLEmi6L;qK39Ubn+p$!V=#`|H5NYF(GZEQ^MK%l3_kQ=aPZsQp z-p7Y z73-?+Yx&vhQh}|dVuls9B*d}=Gf~NC;|KY6q-;63w8abTx-0eL6EA-%s=G`&HYjs6 z?)gd$PsO;2y`$eD19FVKF%fKnnR}YX>TzYUceO|xr8#(_Z)gE$5ZT@HfW)r8SFJY^ znnQE=lj`Wal)I=7J4>eT!qF3q!!+%o*O`N8bo8m8?>GKJsEBw(u}^>;Vw!#yV$jfy z=(7rQVIG#Pb0pu%L=8!E9GWjD4U23mHjU7sP{WGqE^k^-ZcY{!YJci@eM!8nYfBk% zEFcJYMd553l)bKxg5nl@UecjlLa6vYV?fs~O#g=_(gAEv(F|2gAMCSSUx)Lo7STbi zqyay|jqI{Ma#KK{jl0yRLeUnS7bFyHO`ZP6g=I63SQ-igq3wWLWlEX5obg+r5^2^j zxZ*hM-Z?{ez}gyzbmUO*!h#Zubt$fx2?Gw{Tq0A>yx5%gV2M2^GzM4Ig4)`UnXP4u z@@j?Im+|!{Lu=SwkWhkk-;VeFbQrPlJEI z8L{cq&mb22O`0i9Q#RZnlID$4?F3yO=VAC)31r;|?C)=W;;H_V(0Z=?76<)XM`{08 zQDm=})<27e@kp2xCDUf85?q_sbo4>rej`1)lVzixgeL~v{yd6{yzt+y&40esPdl2v zD?JQSIKA`3% zTrGbP`n~bHx~|{(om;M(fop2gJ-|k zDeSG^t1Rj`FR$fhw=|lEf=ARPr>g_~rAQAS^GU0&wpfDb{hq{-1Dz3Cp+Bx5<%d(I zDJvDi4DB%&k){0Fj3dCXFx3>0(Pv(gy)J%pLeBp^#G(;HF3-}(BJIPOt2Q)#ra*vv zysk*l&6`yr?=aKclN40IJ2c)@n94Bof&8K+?doKaJSyc8n>;GuVLt?zRq{CQiNm5_ zFPiy?ZmdD(c%D_S<99n=U(NMAn}PIuxo;~&n#53Y9ki-9i=sRn=u&I5k+vp`E&JYN z+Sq8KXL6xAtJ3#=RIvNJJdX1_li}E@&~5tLfa|KBcpS$>PbqcC(pu^|DQ)GUjHB+c zFf&D?ExHZQMKd;`V$AaQICWfIs3xY@6ue}lLrvR`xj3OKDAZI8D|{ah(WozY~=ML=JpIj4c*>CH{W{_Zr%6Z{c0?~ zFs3PO)k-y(=Tnb$!-#)~&uZaEEm>P>gUkncNz3*xr<8d|bJvvJK&4A*|k_bNw2;FCw-sezEDe7I{npBtJ1;U$Cu9Uk3VQRl~YNW+rKGm=w^={Eb-|z3HBH8 znl{3CIhs>+m%c^6NfFd$G1OdwW-yw{E9r~x)Zo>$--L80=&fjcs=h2gZLeEzwq1*J z8YwGr9n|B76&~8^PCyZLJ0y5a>Wu}~j*BGFCig~ttF@*v zxK^H7^Unt)@w;GZnhm6ORVHifp6cZI@3MYUTj1uoBZ@qaT&-?Dh3$Tb`aVI{ZI5#9 zJg*}{gw$1tQ*CLNMSt|ON17Juv>r85{aW7#&}ihi{bdR*G)FIu z)$Ge$+N^S1OtvJ@I+?-ia%Xory&P_GP)FX;+DFIO@x2?l+Y1R{(M#Ch#?p)2uR*uZ z8LEDu&->#(;>D7yaq4a!n|ACjGs6wg%t4?VInc7b+@hn)m5XVyy52qzu)dqprucj} zWE-nK^jZF~vZE46ZHi~QLMPZxCzYU>g&qN>b$#B46^}t*O|?W9K;65mm9k4h*SmNz z15HKCvZjxpm6PWWmr>9UVsmh8ErX}4qhe1}i{1MmQ}C9=q`BS>_lJXwwpi%r9|L7( z?mx#wf!%(rE*Phfp@x}eRAa#o(sXIcX=dvvQPIvScNr!>Kkd2Q`&U^Tt>Xs2wcOV8 zZXD9|*Y7H~x=mheC4_Ii(yNiJ9H7nP$QDdy&90ot{rqmWV5b!%$dO1r#=W6A7?^h9 zYiWCxMt@;yjWyLk~Oq{H6F&N%8J zZ1tZqkJ;Tnn7P$lWyUDyRXvBNm4-34=}Zdt6~iIl`gM3({$K43+D|<;%U!+arQ$K% z7tt|V=3!y1Vf!UY!g1KL^ZY!|TWp~2!{lc(l!iGSI-oBVoq5;{-*F(nSleCOmbJ~9 zE(YwZm$z3c-Q=xp;OicqG$(UwY*#ZqG1aUQ=#^)QG0{39)&+_qNJ$Ss-t5!M)HZ~+r$MWHGMewn zfr-fLndOBE%gKcWyNh!(x>K&sQ?eG`3Ib2pNeUXj zi_mOz6EZtf!<7o_hnAMNw(9(m!p^S2MhzjaNrjb)Szkr91edMGm5_?E>t9PJ5POZ0 zzYH@mpB2g3)A@W`FSmTFoWHb&it@T&NlGbsF2q;*7kt;WK31Rp{DDH}X#UohgpzWGEcX3s*{oCwZr`z=2 zZH3~gw(QOhSr^FD^24V_2bfcPtx%|qk=Grcpwt%I1KM81G3wbS2DOLdbnZuX#p55@ zf6VFK>>0o?nEPn-;Sr{Xv)OjvaoMwTN&6!jw}$_8Wy4?l9&NLVQu#{DOStFqk=$Qn zJndd07k_o@ypy=rmgo*O@BdaDdDFzbLe4N(ql&N{Alo*(f!>@)Bolr{VX99HLZwA&4`H?)CAjkjQ2ub~_xf5QeIj$Enkm6X+fZOrE& z@_q7)^?)lCo@p`epmu=R6iJ$CvZmD^1;L;KTdQOIZidN(lp6&X`CFmgtf;csT5DrI}qQIGDX zCCWSadoevMGfK$y{!}3|qOG9OSK$TUedD8W@j>o=?&;4*$$ddsZm z(*fgoVric6y@81Ud1zXGuba}kk|LUvXmN;R9rm8Zvl^9*y$o5%PD35RPERz6X}GfX zbnm9;okcx^v$tYb1#wtXcOR2k3f<`1;TWLSL(!D~mU{T2Z-C#-gYi+<-H#Lfuyrq# z+iJ7~jl-eIgSOb-Prdue6--})oR2G?Vw`=L%7E08QWP-XKDbeoy&~CskXp2GEN_7g zIjPYU@R8#d*6($yH@h`N3jV8Ak&=b7vmmf`5xXo>I$Np{yJJ64&sMu4raSpN(@{$LcL@;f_1eXaF+-M#m=vAtjO zR{K|R$|(YJ^S#KA70=D7>wUFV5w7?|8FZQbM!yWo<9T0AdNGGMbSbJLI*nd)Kl}^B zh_4;9>N(-YqHRngKuv92m9>LNM2MCXoIg&`eOcNUnfuDQ@`lINeLba`$#sdtZlyL1{HvHdhNny|&3rSy7c>Xq9nmfJ~YfgME7 z>|Ah;fPJw$7b@9u&}4GcWb)H&aL{bHt2c5i)6}FrDKqlrtzHXgDY-T(L+sdb{3nt3 z??IvK>O1bbE7Sz1sC6%FZ$<(@OOz9wHTrUFJ*a)XsLNK?X(jKrymies@g;r217eCC>nNQUYOUtmYqHAjg$fV$ z{p6Jkp<(&q*GQw<&NV6${m=cN7oE6wA#~jl?(&B+2L>Op)1A}N_YL!(rI1f=alPx9 zj|Rz2hdUQj?v!F2#A#AK=eN#cjC>hx4*i*pP?KSq^*Mb-@2T_K$93+p-r3ewK>8}4 zCOx5M?eDZs4_0pvoZr90Y)sn}@*p?=VI|n53F{pb*5^NYE4XwT{uZ;xBrFV@OY zb>r1lr&yN5Pg|C9Ih?(+`>c7H-nyTDF2#`<8rK;4uc{h4*Yq-5y9>YG+`50C9^QPf zDU|LjSCG{z_hBE}wfcveK3t3x(pY^Lo~(v|v~`-cXp9#&xV7QDWUc8iY^eE3g`rP| zq0?*+%VU4s6YBd!4bIIE&hp>IWYN`9OlVjc?jCrCdJ&e>YU`x==qxX3x!vlh(XVx@ zy891X7ybVDs-`U1Xm?Bc3OF7JI3D@YY*S5d$NN^xKhHw}yVf?nfh;RdFmL8|Js%#> z3S)wjpTw1(0%nhc`GG2iVTmu+TC8u^_$%E9zM7d!*=zYHppz+}lgrmmUhIB{3_Y)x z)9h}Zp58SWGgzxnm#^50lPc78);oHuUEQ@FUb+v@ovT=@+S@lhW-h%6S0C=Q%(@Bu z-5~9iTkZyb)^zO{#=Vbk1pW=u?DEQcrLJOauQ*A9KfDOa79S&TUzmlg^1;-3q3OL) zc5PzMzgTIteq3J-vEck+oyc7y9s!*!0i8Vl>C~lsmVnFXa+_VN2W=RY;qFO8rsSk? z$7&2&-70#w2E!aIknc<7HZgl0T~?dE4YgQfY~x_S@m9dGx7(lYB4A9v_Y}XL=VKXt zE-l$rOa$52Fu{v-{{J$$z|*tGeb47dT7q<~i@#iMw7J{P%K3(LVMY^tQA%;TRy`N5K|oqbDu70+k%Ptn7t>cwC`sAhV63{*=Gt&xk| z^i^0hJ%)6TZH&84%&F&}F?qd)HGIM4(r^CN+E>xi;{rZe0B-HpzgYY|kfo5gme+C7 z;C8d{-qqfwj%-dh%C&g=6M@)KaQsA`6?YEf$uxYUH^AL@>u7<~<5Bo4(t;bkkf%8x zZsH$eUtBHC|J4)C#>~$A|L2Kj{(pI*Ik`Cg&!A(uYEucvP%v;jC`m1oe)2GQX36pF z?;COU0SGXX+bQ=Q)YRkt*D!OQdq%7S>|u6y5GO!U1glAa5!Z#kn-&>LmLzc`nwF#u z3RRROu~NKZaX2j?9A%i{X_?Pur~9S*{Mz}(ovGIC`t1Dr{p}|Pxl^Hj9_|py=C_FC zYcFB6IRI{j85xp;IRQfBFPjX*d%DmNG1_PH2(fs*l&d6ppo2)EK=`dlt2`qPOcW2}dm=Lt+(;tp)ApG!~#Nv^4t<5BA=W4Z}%xuhy{ zet9fjMqwj3U90LJe0%|xAT|P9Dp4&vy#jsPBXhg@88pS*^5Djhp@R&Wh#>f?M$3GV z^0uP@6JYEVVO62H^8F!__dzUnVCdn6p{QagZF_Kkcurv`Svc+S z${ij06=iaO#R^FxePa+l;T&V*(H-%1?)M}Bj_XB3l5~`zZ^!*{7`9Bs(@f4e z8@&nDQwa&@eos|{OQ>PYel8$MT$4r6<7g%oort4m=wla#lig*8BglXi@`1QUDC zW`7~Av+La6!aW8BW06ey4y=d&ibu(CV=T_#6+R7ZPRVF0 z@M|>ni3c_x^$ZfJBbA??2Ad+80%=%t zj%@Mbm>kdn1wZsH_mjPT+CCq?mORO5zg)(^PY8MiMoE){6L*I(oh-;OomcQK?Y;Wk zN>&pzqVXnZ3&W}0tsKeMSPIaR{&1R7zi789+I+08U`eABA3=_BE+_(g=e~IS3@yQju@zc`!18r?Zz7-nYxYjV>cq`6O#&nysNC`vY`Rs?##a&}1&*pdf@#Hc&EE*02*#4Rgt`1slZ@ z$kHv^6Na`#Cl3ze`x-o)WcSurkkLfNsp7%`q0x4Lsc=WN=ywc_zzO$ECi1sHH`q=| zA=?n$VA;*N@DX$hFx*REJli!jreGo{(eNRa(FqVr(h*t2aw1Y;CL&oF>>%`dQb%VH zZ+H%QGz{6kRZUe;K0bG&!hB%HybEvT<5(X$?NTLJe$d<~oSZ`EfuEr)AO29m| znTJZ+fkmjKC>f|pU^y~_6G3z_VdEiuC+&xHoNkyz(m1_9h! z$HB~aF@Yd7g-sp$&Jns<6&+i=r8lEEs*)v-|Bs$P zAWy?xrD*2J$;DEQ{r#ExLsO_8Y{(o=nL>!!OuGbI682tod8ez4+&Kn$55qTLRI$dn zBsU$#BsGN}#3x(0;zM>zp2bH2{n-=%diZ!CIcmNnRc$b7u<{5=QJC<^OKQeg2-FIb zH{GSw0%MqNA#z{om}p)zu>q7u&S*HagdniL;`gXBcTADUxS6rszG;zUvN7S&MEmv$ z9db=7>3A;wlIidX?l{0O$eO}UlH^L-Ao1c+ZtImr!@uhcDg(zc6MkgaV4l4O^pQUd zv4aFcrDRMI?Q4Z6Q#t7;EWJ(1V*m?r)#Cvfan;lB0cQ*%>>*84dANesk7Hkh)sr%_ zL>LV}bgJSG|M^RvU+5r<8$ia2`k$%LP)+ME*WVSSm%kBd-qV+evL@|M1|D-@i5{Y3 z!`O!s+4HJvC9)qO3&xo`07Dcq*Oiiy0F4siknkAy#%P^eoj@CQm4+j1vhQXo&1)o$ zjI=nXgH6mU8#014NgCOY83EQbhHhM7bW~w~G#nMZk0AplY6RDp?O!@@2@L&klR#72 zh`_`y3CheQHB9M!?8b6(?!PK2fD9ccb}7qZ6D@+pLG=UuF^!(|N*LZR(g0PE}1|ma^ zt42QYnYepphM`(XIMHZhZaagORM*Gw2UI!_qRP@cA*VVlk@_tnfwLuP^$mw3L41jT zLmWj1Mk9(hD1*h&F%%Gt=>QQV>7Xn-2f0Ve_9nQr{;oPTILx;jPJ##%JWAI#x&C?ym}(hQ?6Am%w6 zLg%rg23A%lj&U4If7vZOD^g-o$%P`4auMgTuLv-lelMtArdHB}zkmZIKt&`}GbswJ zTtwD&`jI4@uTbd#DHDl|Ajz3MVBbDsE<8~275C&&w6PxvXP$DFkDQhmw_f%@Hx?N+ zA8G|EiDd_RNFZ_{FAR##7;0Hzbc`WLEuF?g+@w$Jzh|}leeeXWK@x6H6 zGe`8j_iTS0x%szS<>&RV5f!%xhz{`UcFnY)sqC&eK?Em52>7I8L`UO15`TA+3n zE0BzZ3g`??%W5QO;V*V<@~mjfQ74a4r;^UCP-5PXqLh^d${d6!?(Y7}fiMcQ69h&c z$C-$VZctgmKaVZ{eBSaXLaz}Rc^w~*Zh&}R-&*ZU>p2d`*;G`yj$6&i9VH`s$c6Ek zBSgp@$4C(}5|IwpDr7|oRtq-^zdf1-+%TDN;d7UOkcH*{e;t^=o$C(l#c{2y9q z2t6M=%`@_ruj;bCh^kdVr%G-8wWjEz{+Keg(VnCZZ#nHuwyWzd{?ZAjS!?^Z+hdPDXVecKtb0H^2m zS>D$B>R_GzYn#dnQy7V50?`aJLdxP=Gki5nqf^7KAs`S*DLjUei*D&-iMS=>8)lZ;N^?`a9o?Y~Np&lO$49@FZ&W>ioWY1 zqHL_-O^3Bc)i1khv{CjRJyXT_UhDp5>;5fW2a&I~ypFG@w13ybd3dRm{=ZwI>?hHUT!<1ccA3Im(Ab-q2_?yuhBSj>ME!&Jd+Kfs%*Fx_LeuF(* zJ21ffYS;;>$?sO;dTXuh7VJ!$-;TNuq7s|!U+$mJh$^b4Nj>%2bc4@O3_w`5X*%TER2-gW|IU$ zQImwh2M#BFs(&&7kfJ19<|h3QHpi~%+Lkz1A~!uEz0E8S$^DpotHYX+* z>MSWU0!&wqqz$Uj?_4}ug5XF}uqf{1$53%$$%Tt4iOUm^ma4<@X(DP@cNN>T@_KDj zTFqLON^BOaT`L4?Kp4EAGBEk?_Xv6Kzo6$1dvr`qBymWz7`lG9;!1AJkm+9(0}bp` zps}~Aclh}n@o*4+UEg&Qc>K-U=n5yeib;aWtP^8tHzu=Hs2Dt!Na_9N(etFhPV_B&78PAh{N zvy9de5d>-bV+L#syo~pI=9^GYy;`A}RLg^+;-%v2*XcWEt9{qw-apyG`wwTH_cyn1 z8Q}t-KhuVhBb2LbnkG;wIvF%<+OriU6UtP&o>$Bh!V5joC#Iw^N`rl5nT{nej&v2t znE`1#;0Ygu{GUf8cMmFQtW!5;6c;o&Q>jbw(?_Ph)d$)t>?sAKaj?Qj_tdh$Jx{AD zy=Y7N0}spQ$5&MY~KrJ-_CA&Kd&R@1cn~acil&d|HYSP6r=VClZ)wU zf)F{G{KB`q(}yn~hgaopselO=Hg+~hzf8N&Sx?pX9ww>)_~-jugeUs#gE8OdT_WMr z4@#MH-??5YsTNfo2C{Zagn6E%`+I%Glia%k{dQhM-G_$chKG2HQLL2DVn7Kx+}}`D z{8+axPRIi11!tFxVHhe@?XBfkFl5P+Z($pg9OhGub=_|;5putPzI<4}zFO^Ne-1qz zJcRfHC-nx8Oa3U-?cZm=Euj%P(8AK~s1KyIlH1Tl+ZEh`u{4QHL$JDu8uN0<=<6p< zchJxF?KI&yy}qJq#3c_mji0X!ci&q0uZjl_N6!#|$iLrNR#DnuI829SW*k@^UYLhA zbS_~>p_!sv{$t=0G5I{?-HnjHOc$Op8bDQ{F@n%kmTN@?7o zQ}h5bj2JSEZsfvNW`+##c8AwrfhhuJTM2q1it3_X{>?mCTCcRR)Lm)qQi(ULF!ZRJ zpvarfZ_P&N+e+S5D04qvgOT+Q)vWK#bcNPb>`;jt>oO+F-Cjft90x*oOyA+tYzMCA zI$lp%TX$YripOwjF>~=R{ocWdMkw$ukZm*gT&Z?5*Be|Jx*PD$%2<6)en2aNc$KDK8tv}m zSixhW1JZ9|``;)NT6}rWAiJv0L(M?mqcxVt|B;mIm`WV}HU%`2{|daP;yio-!WQE1 zDY77v!t=!|qjs|e;|yJD8pnOxW(tEOL(lWXQ&`+?kYC%$s@HATYu^)%o4wqJs~Lx# z+{d#8X5t+x2Yg6csZuwDUkqzy8f)cjp)uFN-{bJ2SvF9a8t}*%xQhzk=UK&8I#LV4 zZvWHF5HNU(&UHBq6xM8=xmr|MXdG!5=PX)M3(6GS!%W&Gul0>XGKRAsaNH+O(iKDa zZJ!NUcERE}{ATgIczyjsWq@bHxb=LmUF~&xi<$J}c3j#swp?Mg7@Hr<^c>IhM!sWw?0?Rw}7rIyXnANU8>;p5s*+SJGe8f9(59Osx*`p zHX*k1{40+oZ-PITEm98hz}!Qr@tZ z7-TE3tlgQG2`-I&qbrc|CoyLWtdhkHyof0xW@SO-&=QHW^Hs4bv9Q~j{ww+_=KNr% z=fp8mYe%m&OJwdCm>mc2n>Q{T@1u-5H9N=Fd1zzg?3P0Rn~;JmgoR*q`a{^&yuFkX z{#Rx^wexMNNS_eG3B>v-%7Bgt+v$ch!h|7R-s!MWKOvVo($rSu z#(N|t)djA2_v0vqE-l}OXm_)T?qm(_%1X118|`Vwr0wq7eVE_t2048Z1A+^Wn$_meSHC7Krr~>B!k1k0MaA=(0ewF^~2qyn^ zNS)_Cu3jXPa<2_xH=P7~bgM*VUe3>D^c*y09~Gr|)HmYQ!ctbkuD9mG%wKr99}UQ% zZDzkW^eTK)oBUa)O*fI^{iW%Sw=fiI{q;2CLw#OzH2kP_7sOjiJT!~0RAFL7ZQ@03 z#@N2vLR7|EL*IUPG6RBHIfUnkRtIJlaIq9*GQ~H2muD?|ViaK;WWJ7c(!<)7wb3){ zCYQxef64PE@2H&zJs?W@HA{A7t!j@##=)}CA>5d`ejgGFFf_Ecf?6ppf|a%82z*VM z!Oj8Iu%>F0{%oLrvb&vH-o3>~lnc3Ua#G8qj*218v~}~{HAHco*3gP?7B9_uOkbky zX)jEmHmTIt<*j$DTk`m)vR<9o<9!MvgkjqvDhh}&@!6s(;$4s!B-e9G_b^GDas3zS zER%G&(Ji(RR{?_0-@(ir%^>dcj{|M!(9}>r-B}3Ec)3vBKzi3*bd?^;wfOb5{K2Sf_ zNs@UXo^6XI>|hHE-C(ctb!~EBJ#vn@(~^%FnW&gY*l(X$!^Ong976lvcjJFJE6Nl2 zIpO(tOL63Kuz`a|&p%LMixE}JZyVzj%CfQ$hL5sozJYVK1=WwK0gAkfYwVs78+yB) zeS-VM`uiouVi?w7WyP?_zQ3qWjHbtrKWw{ob#Rf;<1x)%&+G3B<_^X7;z*|5%4u*C z`wFea(J4;iZtkE>TcM8AjUmd#xWpTkXieXnOon)7?mup7SD0@afK z*TBlY=aL5&Bde_QcdZ%Md}1>T_r@dlqW^(0-RNd(kfL^?l~MU6Uxcrn9G&1_{z@9h znpWrfyhZPA|ImUMm0l&~+6LEij?fiNHJ&G2Oj3qC-_47E?jIHQ|4M($49=8<`Ng#E zGP2A66PL0Y**M#6^|#U*U-L0~sWHxvhq8P&ux&CDIMkBGN#vLTk>G;;*}Vkt3^i*^ zp`M6KPN7==o%eaMM?16N?0v;Bv@J8S(ILS*#@W~83B}QN%N|kfA8y~n*-!iT>FNF^ zpx1jhD&T#$y})M6spTD4-|Ec5PLC4nl;BWZyzCXM%90CSrcdSWpRMn>*E`dK^s@2I~w{@ZG>Kd__rd+<(|h2BD2_yc=JgWFngi_;eOxY;)OQmeXzdXu{% zr^XgE*yOoLX9m|g<2c=mjy)p1S*bK@k=6lz!Fllc_`D(Ax>aCzyn5n~*#~UTq{k~l z_WK`B9pA(C-n3dv!%|rx{%QY*_1#Oj~^Zd}SZeTiNQ{;asF;lX7)sS!uO!=FzJkWm{<- zH}AP$J~=!!z!LI%Uhhp0y6LD)G~o#FB?&sCXJY8RUgSNy^rd zYr#{`OzO|fmK*j@MCYc8)IM3~4J{Sjt6-;J4y8|uJ!5#e5C_oh%7 z>UpM6I1La_#2%QK70+d@IG>p> ztt1GvqKHV&o@kfol2t@`GD=0|P#Vl#iLwlpBw9&AB}@f6U5OGEH7Owy)W%TGZZ^y5 zw%6`9-)WcgBqUbt?99~6Oz(Ea_Gw@p`SK9>DY@R_zjsch5glw8w1A@3d)Y4)BVUqPXAl!0p;n(SHmJ;ekLuz^tJUXLwVE{N;xP|B>Nw~O? zRj38&FQCA1#gLY0MKx2b!Z`HF*@aPVX3=WwS)_=egMW=M+|^di!NW{1q5OAK-ImGe zM0Eeahh^tP%_zYYn+y+VPU2EWS7}5lPu0Nqe}UJ5ilkolGZmBXH2%h453jmQNZ>@& zldRu);MK`|Rb=8XMAp>Dn+Nj592fUd?mL(!RYey`CDOC3BNyU|CVPU$S&S?;>sL)j zh6Z4rM5Y2XCymFsa{`B?viln@$Gd_~|0K*O#IT5_H{60DpN#1sADUqI3A4*@gnRUj z(WLT4M6e<*jZH~m0wp*|Vlp}l3pS2GH9G%-CP*MBB@3k$Lgrm;4o8S1!wEQ+a3wG_ z0GoG94~#N7WwLaThRv*e?-W)1(hVLp9Ft9~?Ik%?nGCt)#ePkbbd^U9B#jt9LK6ud z23HTTO60v{PB<><&c0|JDc(jwU?k^kL}8zlZ5&kZrcE7;=qEWfT;LIg~S{d_F>7Y@X} zkD1&FTe`oW6N@b%dJ0GjU`#yMcQZ&vzJOhV9yLUSQ{G2JyqKv~_6H>{GmOh$^Gb9_ z$TI3URGjjverFscV>D0%1PlVMsSlSHaj?z7`H?1Gh!7gAMb$6r@1P-d0;f+0>c8{-Xi7IlIMBKT^{ zseTOi;2(cO+vQtWfi%XA&Dqx^4^VK(y#sg<-AogegSTe}`_GI62jr`N*pb3kyCXz| z9rzXf16bu6`wEK-T4aDJJw(dI(Z<|Ek3_K!SYx;dryAm=hHQo0r>^fu4>p!AFwc8q zGhp`scKeCS9h;?~mq4151mSmbRne9>Lsiks`!o4;_yR~lKq7vXhvS3L_(up3Sp`Uf zl3EE_L!b&~(gujqVNH^T|Efpl_xB?L1=9P=V2T9y%O6?^p(j`sg*1OT`N9yI@8=Y` znSih__zVVibzWYGD^EJ?n+X%xo?P#{7k0$8Bi68GYvRg)Fed80)Y3_$emfip{|iAq9P z(TSSI_~4_0;gZvtKCK1G<%4;%<7#;ss>OWev4H}HpaOsco$}SqDG-+S*uq)7f$)p< z6DF)k3DDQOLN7opfXKzgm&VWq6-^>DA1r=;Zi&fHBo7@;B-9gpZ)BF^9iblKGD;#* zo6cw%L5?_+4>|@T4_inTbz&cpNiG2$Uv8<5CI`jb1>0cPo>~O_T z`Jvko;C@7d7(;+el)v?p4_*%r=?FtQd@rojL67v-j(w`z2nH*`RAJQxa_ zlaio#W)&4I1e;N?34$2uF(i~&@sj^fzFlH{VQuEgU86x6IM8Sy)I)H#OI^O_GBiUz zPL}-8TWKP82lUwi0}V)F0=+nPcXY=1P{X=WtZ>S=ad^J$kss!NFX~Cf*&srJ3$#cAkjBc9qtGcfi7{kBk(er&XfiK z7@469Lmwjr@5w?*fx3=lqMfSAex3t6Q9la*2WCYo;uJIG`C}^`sDgA2--$XFAG0gM zDojT(SfEOaYyMuv!C=gY32x^BcPL4u6xFzID2+lG)HQ+gY;4SyDm+a0)y*|^CGx`T z5Tz%M2to#6JjG}uOUe{J?2m-rf<3{3n01Z=bvE*>z#s|#P{I#E2(d`5$eDYgM|sq~ zfaQ^`*Z}%Q)s+ck*>psDeM#8T05-uwvj#Ol{QqL?9e{fYzID;;*yfIH+qP}nwr$(l zak69Ezu2~ICp&!kpHuJFtNTvfx>aBG^vvp6Gt<-EGt={}waVx6@_I@AXyGw=gsA>; za4_bO@5vNq)p0ZAa3V+8C!%|jfFJtp;TSSCIS%`!149OKSQ9Gz5-};D5Fa!+}0Wv z@Q^g`&m0bf&ZY#T1YYA;NFZXk4~alxdg${Yj?=N~;_wZ$^Y$P|;dmqNEw-gFE&yZf zAE2w7`xDC)9m3KdVt_?r^^4mzev2lQ@Hj?hcqmF9UjjwOB((oINubp1f*8+13_3m( z0$fXc-BRERiA>f-0S;Om)C--eg4$LbEllu=Se!RX5Okj=P(b0YXPu8*abS#3q~xtD zUN+Hse_cTYSKzEZoPH7qr1fi3h%;9FP+x+v5iH(_{3N`N!hqlC!AC5U2n8)z9@H7l z1<2T}8jcyoEf5Jv*Kw9F#QETqRYQZl>&`m&OZArf%5l%q!b=XP*IQU}w)=JG9ikjH z8lT6v$xfg#N`q+7&eya4yDFBg>ErnR+z_cR5R2+Bxc0K&Q|G#^`RjqF9Ytj`n|@BV z55#4)YW8_R_P?Xel@1hD;S=osxsr z!4J3}sDij0@E-><*c$7=l^1GT5w8=Z_9qda`CQ*{C?opg%UwWnG2K22-; z$QWe{vD5e9&@4>jAiWf{4rG+$`WHN^~MaEqW@YFs1GIE8X#zo50Qr9aLkSN{xPT!Fz>o$N z*>H`OCJh?OL4eWz$@k$bfZ49uv6>KrBMcoycv{>Y6{QFj#keA4l{W7`8efVtFr@)Q ztDWGOzcUXDCufa}MnyB@jEqu@jDEBjt2fj99LrIFQSk#GetGBnV=5dW-_f0Iqctq zs+bZZ)5+W1xm1)rbQLaD+yUF4E`wJy-EI1&=GUnDBi8MUy^<>d5CRHj6Co2Y2Qzg!fe z6e?3HVP5NjK&-4lEzv1Bik?Ce}i6*KL_+F;Q)6S;;$O zFR!#=T>Y%^XDg;z(=j`vssB&&j2ahA`}G3i%BXnmZX9#>h-tKN zp>O*rGtkxIR1_74KX}2$x9{1zqU!Vxf`I$!{+BycN6req7PR`3k$QVI^UWfbb+XEO z6f~Ej6uA{u=mtsXh6>dt*$RJ^&@U5kPJCXVt!bg6@h$ZW0q&x0sVk?P?IVuH2}if| zgH7PjF%HiPYBKV(TGX6m6uA3BK|Pky^a1ns!|;h`s+GS5COa(5}S)CPWI?`pUK^8g{ z*O@|^2e&*aW2SNbA;H0k_0fvZ-3(aldsg zYVyFc15g3PpRJY!)Z4FCu=&R}T2~mqlDJ70bpAfwxbWu)Z9Dra0r{~P83KA6ub=J6 zM=}`R|Eq+dP--)rJ%^ds_P@2Rdr%e0jq#URl~V2=geArGQjv|QX6hZ^Z-y~RnPkhB zcI9lirn~B=)oKK*>dK{Lx8qK}B*}R&F51aI@(3o^zc0ZT_Ejp3cSh_P+4d>0Okdyz zvv#n_Rc{nw$mSH>Gby{2O^*|@I8V<=U7ek00mvhdI4(azx~OX>_$fn@Uh~XsVGML` zM-+ajWR8C7eiWX?$aJGtef2fMr0GUbq_IWCfda#xm%6owq8mJ4uTP8@f9vatc*75j zytmPD*FDSMy@cyka)<=b3D8{TZ|E%N#ElTWp5HLm>XW>?jNm7|o8Rlk;WwK9_P-|Z zU-!?{t9Lrz_BV_0h!cLCp`V81zsF|xhBtG4Hzu#>uREKdefwV=iR&jkX?K3i<7KYC zEtC7T-N(qt)3ZGXE@XR)8iUs46rgWB2nh1Jt*t@8+wB8Ch5GxwZsmVIeA4HBcc$(T z&Ry!{KUE9!^S?co>Q_wXZFF6nQo?fhEdj*obR)m>ypfGTmC57$4i}H*>8~=q752YH z*FHQBFSIef?s#{$JU+*jFSHS=bUU1`6T^`*2$#vXJ-$LI4QZZ{rICk`C%t?xMy1W! z*lR-b1jMsO8$q+tkupd%PRR4am+;?hPnpndk76&hTXA|i2pQH*vb7pCg92;1tS%~= zC-A?YkJFt?*JrhYwp43Dzqo5VznfeA8u~jxH@s&i;A-Yd-99%5u{y&_nYY-Oa2u28T3^rKJMY2~V8aqi1}7HxOUxUP7&kZ2)(5RYn^9*}8?^j? zGEipZ8C#EEj(S0M2K2>KSPs#rx@B^Hw~NQ;cNn!MjK&=**)8D<@8X?kB$U(_wVxK zl>fn|^~@&kGm^(jYu@WvvbOWfsyqLl)iu$($I(L(-2RdDD#+B&uhJB$gFpS{PhA! zfz|>Dhs?k9m;PTL`n)buL_PSns!j)P^}MblSdn#dL{-U<7o-DTk#%}Tt?8>*q%xk7 zTR&NhNj(PdejHF|#7RL0h<+Yr`TTS_ZMAEjKX-oagF-ik%+hf~zmc4;e?KyOVN_ik zCExI^LN)vrR3jd>3YqVh`!|2pR4mloX;}Hw?^upuviU<3)>@9~KH#&5g1AF-!?Up{ z#T5@ItZ+nQjU{8fcY3z?_=1@O5rS8AceviNK5NUJ>zfZowwv4lLAIPQ=P}xz$A=@3 zBm6uc*XK+;%xbzV-yg?t)sW717C_-AH>)0l$ND*r*6A}_MO&*s3v9r3w>wiybLgF&=K>IUTcLVL1 z+^`(TAjR&I6uV4N>9I)7NFy~RV|XL$$r+zX<``1hY%4I=%W_RzqQjmxJca>eTa?ix zL>ZRctBm1a>R0hqcKDwsA1E}C5Gf$=F`}MIa!3&MkTFgsT?u2}@Ll$ACTvG4ZValN z*}5E0?SJ)%BXB+NAq}CO*-ONBQ$Bc(cs_x2Fs?H?Gg-jZmE%1>HvY~eUi*5j>(|=n zdbnMm&qGRWY;>kLzjW7+@s8)z-o%#2x^1Lei^(X)#h%!&#pIS%rWpv4*K{9YY4}BfdwcWW~Wj%hr9x_rsyT5%!@wnVeZIY@u4nz39 z>jbX@=J?eGBp5&;fI^&#JUNeElwzP3qzPrF8j&dVv}5`bPj{g`vM{F{-gA2h*XsMZ ztB?2QNjowurh-#(qZ9c;)`GU-#*VQR0lOACGmYdN8>a&rm+6;?QP9hZ33bcRq7|PwJ zAMJv*?C`R}_I(<|?WrBx_9d*PEQD!K^q++6a+S~bK^7r5Yu~YJE?~qspTl7ab>1{X zj{DA3V`_Neh!bIsp)#8URbV8 zZ#F;apsB`q#h|_3H}jga-N((KzBr%wKar<#=J48W$oyBrnxXw2gzlHLLG=f+Ywjjp zUz3BUsot4qh}eBmiCo^5I))8G7T}l7oxseI#Y{P>F^JT|;Q89hro&*ZdqGPO)t{91 zUqPCTau!l53o(_Yn2Htyc0Ua+j1(7oiVF?JnTEoAeL=CYpyWtUQXClR|6j0Hf#Y_9 zylP5bZ$2}oc=NV=4io<|e|)x9`d5)grBO@8kN%=gI~^X1NzmfR8D zgD`e2@2}$Ntq7Ry`f@AY5Anc$vc({_&$`mvT)Rr_jv`!v?BWodfV+WfLyJ`{mY;XS zfE9YHUw`x6^xp$KSvL5in8&9cni+Prm?Jqh827e7mN7&pY_P|zbR(`zQGz4Yu(o;2 z?i6<3$HCIcbQctx&x*RbU6NM(&tS%qF^nCtnxz}&*%aLc`;f5JhHH=0fQxUB=53B$ zJxsIr7`91EY=iUj^}N2Mzx{gCcmj{-e+e<_&*#7Mr7w=-`GGw$*mB`M!GLqx=0Z+8 z=thy?QYX1rl30ir!vPRNsdX<@YX>00q>^bMP zbGvwD&vwW4{`&Z7HMMZL13`YdG2f>)q>A%-ML{%@DWs8=BuA?+nNzsw>}J^8Vum$; z)0!eJTbRp#Ci<-Lh2?K<{7s+o3GOh1j&#ARp)^j%rOE=p-QBjv_y(QJ+W47L@QNQTn zJR}|FkXu)n*GqeyiFlhn$F5*M+|I_qFkhpFZ3b5;$ja=!t5mLpt_8zIw{T{u_EME; zzZmV4K)xUKCQA=M%pCWr9H^5%o+s`d~O`Mo#>ke*qO%Li@qpC*K;d;$X@2>9}7y( z1f>^(^Ze#be?^0QpwD82^6AkQ z`XWddl z{p9g{gD}QP$BURFXKuRePHt*f6&%Ter{=rQ1LVAIz6l*SK2tOov-OpexEh2J4sr;R803jn+j(~@By02xt242HYEQ>3`gCwpi!4AeI7h{XF z;o14vKPA}0*-A4ZY`W>jCL8<@s2d6Yc?k(M^LUh|_G=Mt9qz)eYP7GMB0k@)lfaHn zV?(F4rSW=`cfUNGg?|-Z^<|?$lB{I{(c(MfFMBAoz|&_S&v?UZT*9jXsV_U;&ir=2Lq?ft zK0%hwf1G!h&&@o!k<}m(742$ATgF!O$7i{7=ifksVIpGxBZ-5JiH-UHip0Uj_J1OA zFmbT_?~V|3e@GmzY=TE+jvy+KXiAGHgw2#v`@G-GWJ0ln@c9-LE zlEJ=`Mhrx(6p6yRB0`o_srLfd6+-(Z2weqV3IYYFqO@1S+68Fk@?a!|3PD*LPt}bp zJ^UxrGkh!bWB2XmJ(-$~%dN|kS>5p~dh#8SE8B8PL*H8>U~v8NE)0kRL!dr@(8y-= z{NhNMK+rc1%y{{I5-OkYtl`n1Op-5Uf?d!)fl=Jga1vI3;E_zXXJEFXh^hdO&O@r z$mQHI8`Ve*d&DjxnSbvrgcbMYD{4=8bYydwzt;NgcY#O2oAAz7%-IQ99iuObhmc7c zqofOPmxgIp;ho&Xe1K`^lckdktd%ht}#HNgD|nl0arjKkw&U7C0cu|W;uo+ zYa}nyh=5#iofH2s|27HSes?&*^M&|Haw>Y-VUsD!e@o$kgAk4-q0ZJ8wJd{#rr_oR zAV+hHjAgx;Be#Er`f!6sYre&8DjS0q=VY|814l%96VuBr)X&b70lMe#llHeqt&@+p zM>CW5noa0WSi`fm5xNm6Nk;yPF35{uCjd((_KN$0>LFN(m4tHNmog?|Kp4#Y>3!imA8f141v-l0ujv#j~g?W4ABm3Q6eN2#X7+AkCilhO%v-T*y}2#i=f`0QW^`^ta=npxH_OKjo57>d5NM{ zOh%Ykv#|#Z$QYS4QL`6H;3u=vK|*x`qd+K)1}h!q77mzp4j zE=?cxk#TWWwE{frxEX0g@_1Y;Xx=+qEzk|~mVeQ>(iFB1{gp%&xEMq!1RtlYOCt4G zm7^Y(MMOE655c*N3X7AlSTl&gNpV&N_2~~D?dv2Nc-xH|uWy`FKoVee|Fs!W2YsSI zcjsXVNV(}tOFIqHiK)(dBV@eCD9>DRkdWSuTFd4uiTiXhDJDV&alq}%c(Db_fWX%# z0b*frK?=auFmLJ@7smr}0xC4V2JUv;Hb?{unRaGAC!&~`;<-mnNC4vp1@%F$Xf;^< z;f2**dhH?;S%8BC8FLre2!P$ZDxb#!)0_QTfl2IzQG!gw08&9hW|z)!gwviZZ)>4+W6A>>?M6U1xw} zacXcnNSd%Y{j8S*Az3Cc#Rvq;;B@RtkO+~&>m(um#9z}PO$Z3rQ!(-vh9zdacmH)P z$Of+?loLxQ08KgIiCa!Y+Li~BUd&>fmJra-L#a%l=*WCexutjxmgw|14uw}dTHI*= zM#*id{)!_Ob=q;{H)oFcrrTl~69ND-1>l-^QaYy{!rf0z$q2N;-M}C_Idd+2UADHI z*he|&h`E-(ud+-)N9IFXa@a@Vh@7Ln=yg(Ev_7Ybhtw)1@M_ zsf)aFmd6UYf}P)0FQYsEH*5>_P*W8J0ZAxh0+dqCe0@2An8~C`Y?G|8(&d>v3E5YQ z7<$tIoriZsn`99g(psdrKrx)!$e<)*y-=P+kekx33N$rhVqjmI#do+3n13N0!DGN= zj9WmKs89s1ix^cv#BLmcxEg};^KQ~;bZ9PEBor~on#f@TiP8uXCs3pXxEU#uL0rE8 z+XBg2a1fC>o8I0+zz3|EXdGDZoq~#tggN50#X>+})mBri?yHq(iWfaV#I_k{7bbCKO;hQ&5y zCk&cuFHwzPupmXiiRu2xWRi>Q5P8*Y!oI6-7yfYFM26*?1Pc#L34IsT78!uA3_F23 z3U%O!K7wH5hag)Z6zAAb{$!$K5g{bCg|Jl=c-WL%rb7!Ck0q8HMudnFncqn~NPw0N zL{K`K=-`M@fHEQzi?DekFtCtCx=lsd;z~;qVcP;}fHwD2TXIiK$gUTP&V5k2{^4W< zt;nQQ_ufY?uD}%D?+&C;#EbhNBYb}6O}6Mrc06-LRi0aqbZCI`1r-` zyFScR1A%Ej;3HxmNiOmwUK|sG!c7*umMr1V6ow*vuN`3{Cc@UVVyaEvKsa#NT);n9 zlW~%EONkRI0+P5A)E26fp1s!<Aejs)e@ zR}p$N<=9sr3UJF1IUg|U+y%k+A|Ty6j2D@iDrbjTAWift;!*fB5Hf@ff*@NUO@OG1Yc~XN_XW%eJf-{KUco++eJns{Tv~<_I4o}o)FO78)Uq|| zLYW!@c{Q7sFOmW0n+`4sJFPFk%x4e$^OzWXV>Ha|-XTW-RRo)A5ci-7fm=U5CcfpVidjKafNR$= zPM{QotZzEV?YXOtAhb^gQ2r6nRU^p_{{+?!&*X?ON2_^};) zp}ErZ+TCXMPQ&*zFrArSgP6hPf1h5Uez|EsdR=pV$CRt96zHs8YvM0Fd0G>>lWWhFG1j`-4 z4dJ1&0et;1*MC|>7vn3!MBW8(my`}M)u z?k;lyzujl&9N>KYICpI-|NXVke6ktYExptAZ9Y+}lf{G<3|S`i!_6=!1rh3UwIrahr0)um7MR7HFcb?-Ff-a z(;rS-(QJC&`(b!NqBK8uxBb<+?Hkv}VShgU_dwLm`y|(01RqA{QZzLJ4*wvmzDB=lyQi_3vKAxt(XS z-TrOVd&whyCwtw`GfM?zNcOe&ql3qAT@d+&UpSu0|;rgoUcRmD|u~ui>?ts@8s$HvPcCZ1R*26 zUbknd7(eg(O>?{ang8|(4c_@QGMO-P+?W6Oe$SyBAG&IskwHndGkF}gf9@$_VFbC z6U)QV_Qk7C({GDS5&VQ^f?WP%cy5R5{w4o|^0#vGl^e$Art=4*Sp*}fDPEzJIcM7@o1FocnN?k-%J1;& z$~bf{@rz^Y(yZ*Mwv%y;_{;N|SCbFJ#n)C&!Zp0|8ZlbF3IKWYY;g-VD-W{}`wgDs zxhDC-9WxBSPTSilKhCQ)g~{G;$M~h__Z^or{(|ym_9yKETm+%}^Qk@n4H_Zc)Ste1Y9<56_dpJ80%=MMRYb3LKXL=K}Nvsk+7Fv~3$ zTOGJ1-Cu1-TQHqpx3%bB_D@7>kL2*ZucNIL58=Ewd@iSG()lRxh-lQ!2YlYRe_{Wq zq}q^>G70^dZn|#h9jq2?z)Hsk6;NvEi6xSZSkYvHc< z#-Nx&)ne0`8lFzSIA&C{#*q2VimKJFkbM$VE~rpBpHVd5Q8ZgC z<)FWxIafme8MSU#q3>R{MSGjO`0SuJDB80;4xNn2{q~0Fb6!KKUqwxy9MZusGRfK+ zgQDqCHrdBY?RtErjrMb{lzw)n4x?I|r`LTgL%~ST=XiY_xYCC4KPYADbZU{ZXirVv zfD*GK-{s8Hqqw&6aTKnXv{sv0n`UXvo_j7ix#={szO&mzZJBdQ4QbFqZ@t||*F(n5 zm(qg2YO(z96U3tRfBo1>Z&AA&mNmqtotY96w9&j~!fc-~5b95(rV&9J+PfQsLo3Xr zM2~l7x7=9c+-N^`%Z`61OXvT5Rma#_y*Y2Byga2cVGW(CNoos7A&gBSl%+X*YgUay zNtfj+15A?fzS~W^RkrJrG1<^M$7T;*LjP-*?=lIMcgYed6N_N@41;c03g|luFR6`LTt{z zb>qg9e4WsqA|A^2-70GG<}+dP5OV0FLUVp*W}c~{);8%>yF|tvTd1=IL`6R%8Xqio z>~OxHkMe1JAMWCFuWQ$aHVJjO%PAE2Dmk<-p+byr53CrY_Z3+E>2WQ#6(;~v#k{5;_A`QKB1TAbJR z5U5~BZ97!DiV0|7?YNW@XtLtXc>p?E`t6?EYd@u*65%LMhd(9s73EQ%P~i${@{~-+ z&Yw~CI`xWo+gkJ0(*9-tve)c*dgfa!#d(j%+V1ZJv?-@KiucL-*{*22b(_xLfGVYV zi3`+R@wuPA;n7Pg7>B)Hbav<>T+Me*)?mBIisW^*-w;jc5O;W79u9s+mC+c-1SBU< zr;x^)F;IS)^17D0n14v0&#V0ahR_l!T`8}bD}q3ea!J^FJW_ay*Oih>Mc4{I6ubr3 z!&E`Q&R_~nV2hed+Qis8l5zsK6q&I&!$OwP2vc>gol-0YiB*_Kr}?o5q*W@CeZshi zI!HHdP=?e_LUuOGXtSKU0+U@#1^G{La&6ZmYo=zJl=uo*Cr$Kv)PuC?EIM_tE9sh% zow>HW%*$5ehg_y|T1)mE_>&2xcRA4UA~B9pw3vdr{G7aa%KXq=9k2g~y{X?C1rzW; z=AU1SeEk!{NoHNXz^SGaX64zFvSPChF`Pz)gUz9rvMFvX^>vqEDAv@S#FQ*r{|)lH zRj94b+_`SrW!Or><SYGRtS#_ER>B)v7Ysb&Von$4v=B!ys3lYgtKa*o9{ zX1-^rWrazGqiayj`TGTQ{`aZkU#ezlrblLzf|8nQ&#B5do?mNk`;L0Lk|oMG8y_0? zVFjt(7YlK0<>b->d@75?Se_KW1$B7=jmuQJ5|lgR!q;kePOVDREj^fO!q!5zqULKB z3$U!^{Mc4Pq@3v|`L+}duI2Tv6lNxWSQC4s>FCUgbeR{gV1iQN+@~}KeaC4o!leA@ zT?MFJ1+*(cS}3|KVy0Z1Vo%Jey!|V2?Ppceyo}r1OiCEcjT3Jxp`!p9sM>!8-K#eg z*+5x>_gExn8xFaFWB0f0>3XFQ8*w&cUax(j>D!U|Xar+1_s>OnvM3`o^L__%6c3R} zBTrcv)sRgf)$3d~-fPsQjs#WnpxYeP8&IR6W|L6Lns!vJyVVn@z_~fO7MoZ1Q-+@% zgD>j{FG-QT2t%u-T%(i zjbqqwgb+22tW-*K{Q;oCMpf}Mp%KMOwuk+&l1x1P$|BgyiRX+8Op_c;Q=Mj%t-K6% zO=l&`#~s0ysRT#4H5d*)yXe%7BFf1=D3l(@3sH=ZXC$aq*Q> z|MmH(7RD;BW?c~@-43aahr0>k?gKynYx)>$3<;0{hE&xR%x0X~4V>ma_k3oj8=w0= zN_(SISS)c{6H;{9Yiy>tIo%B^Dkiwmp6!CrEv>Pt3nib~h4nN>tiFd7{p5JPscn&S zgWN>DN2ipeYIkDCR6SPXb+TTd%X#LnrRghvkpDM4W`X;H88Wpk+6d~sel%g zgpz{W|HAb3Y1NvB zMI|xkA7n>&F?-ZB#MHFK_#F;PT(tH!xGchQKJ)}*6*~b2B?IUF6_%*0Hnl_ZGe^~* zRBOsT9v!HiL`$pYi_KMkyj~t3PKoO=`;vIJg`^yvBHdoNC%B}QXYW1R`dSWdO0e7d z)(&h++<$%0FgC}J+4`yvz(4YJc@Rp=O&Q$F1@J(rBrOr#Nw;vxSy9X3O}HuwFyK#Qt7rd7yQA>Cs>O&B zmv^nDywR|B)U1i%^dt^UrY5CBleb&GQ8AyfegC{q(1l%C!)rWpXvv=)9@N}f(x+ks z+y2~Q#(OQPp_&}8$F?~E?=>~7miV2E>x*L$S`!` zN-5^OkX3o>1X)|)p-pVJPHYL$u({3|;asSmXO}jE-tg?+drLC?+WG{scwWAYw z#J=eV%)hj_65vE>#(g+hD?dq~Dx*``(W9|stDrbHB(CC$0iWx6+5K77(8S-8huxr~ zO?m1?dHQA~*eb|>^r~D0)Sj|eQn-M?1F`ziqgxoz6N1v9XmUB>Eu7Y2>p6IEbk*IX zk$W#(!C7XjeJH-+mwURKU)>de;gGC~_b$ZWX~SVTjK*uK>0WMi{77=V8#Sr2qC6L& z^WI}pv|>gqz`Q+)U^PjQ8m*)^UfZMLSA8*(3SV%pXKjjk4}b2ryJpF%Pn6Jy+em0~ zi2yfZ1Bs>W9;>-tv#8tsU+}HJKDTBC*RSe)LvmH`n)QT17_yPU()NkfJg!F7??%=4 zt$)>8QLcYI&yKFm_8>4T>RUCkn`E*p$+$Q&?dj1#wwje^#8LGWuu|2tQvK|64w&kt z?3xByQ<#rg+UbqZe8YQ}9$}Q;`O`p~%2b@rRE!|{jwe;x{sbesj{DL%n14zyWt!;@ zigN{~ce&T_$o<^iODWy^XP@?dTc3XB{Y_y~>%xk@L)M0$bxnYpQo3`>qp+OjYB*!Y@iJ_Kzw% zaiNQ_w?)|4UoW#q%?ifTW4b>UYvlnz+DmHnDo4uJVm?JO6cb}p3XSvx*@2ZND@sg{ z6{F{_TPX;_)>Wjgg7h=V?xwulA2BxUXyYYj+DyuA&@CUKQeaZ5nSq&g459u3sI3AT4Qnm2Tir+q2O#vhrM0F z#=d(yorALmt~#w7#N&>x61ji=n<^Fef(G%>@RNXFeG`F)yBeuIn&&Xf*Qx;5cGf+v zFPJL)nIQfeOm#g7T-9*PZ?yfG*smF_x32XI`+~~bxfj=2K`G6Rx|bX2a1BrpJ*1+V zM7N$`*9+D(XWKY~)C23_8a{leHJ##nwZs`}SJ z3LV9z97~50F*{K_devpLsxgHJ`0>9cL*U{I%=6JLMsj?^S7B91jW8oI?`PF0&=D@A z4Wd#Q_nXJDH5uQ5gt>PMDrul?1HYVcuC6!>A+ZW&1w=!L?UVNb}bvO;4QY>`#rXs4U1mVsdj(8#C2O z6xFfTA*;pJlSqZ5n6e1&Y5WCHE$m9CxRGYT(1|70WrY3rOgtp5|L-&t?nhZ^;eTqM zE#>uo%<$H!DT#9GlOHAtr|EyDF|f0-{a?`-SlIs$GzNAyMz;T*#vnUsCaH?90~xeG z&?XSMf{Q7%^Hv*-f(#yJ(D(1mj?iS?Fr(g_jEQ-2mt_xMki|Hwm^4vDGa~_VTFRZQ zDl(Fh>{Kk>YJVJ#hOCW2&O6Wfrv2vK_cOP{b>}^EHK%>2x=b#Y>!d=Nzja%u0e_e< zT}BxGqt_HiCnVuwS2t7vT>oD!X(}N}y!$?4a92*)Ax;QGaTek2&OcuCa|NCN7`P?L z3Z_LW|8Wpf0(JhjxgsC9VCN!N| z97S;)pZh97+`-)k4Z^!d*yP2;3kr|gIGsW*a?(x2Va09YwAsROSvdG^MJ%ca za95LqaK*3R8bP^#qlF_ZLk{Oyp^y(qk$J?%BBf2b)b}AHCz2oxF}FnlgfSo$yL44K&IZllUJYuL*+WYf_cR#-d0gQ^MAJ zHAQ?!Cc^;}qX^!!6f3Ty^K_!m4ZkP_AmC?##H z?6Tg1v-6q$;Ofwz)!Q95>?5V}@+E>W4U$uN&^tYd$8!;VAC&gqPt-u;eIEe)kP?F( z${CaqWl^$8)KRKC(x(c2gBBvxUI!1cDdow`BBf^`O;`d!LkCM@3ekFXbWkZB zLnQT`jH58#9U0OS0w=#yD&kj5q@0ur>r@oAr-jD~Roy^FFr*yBQ;E>`U+FwDNr#Id zQR4)kRac=Md7|tcVjBG=)_5Fd#`PEaKB1~LbX!=n0FKk>!`?V2HrOdgW{!UisNrB@P=gz6nPTILNkOk_FqlSg*G@i#)?3wxXHjGq3zIr zlCSW>g1UYRQO`8+aU*Xt@bUebs^4fXs6T`ZolMf2;90i))j~?I46IF#0qh8nTuxXY zi7Kw>s^mu!YmGz72Y7L1KE{Gzt| z#fJ*mo`Mks^NMU%i*p>3Jdo*ej|3e~kieQk7oG>5l#u(SOjG(nfE1vI5Z zkbP5?6jq8u2W0g=MW0=B52fsUV$y<}z}=rAt3{G2zthC_2#T`vL#@i+9#jI(A)1}tET!WJqt@kKFYYa8JaI_jHo ziC_c5I7W0R0I=RI7MgP%H@url&aF_2-QKmiA`KR!5UC|kIQ_=vuRz=}FepzcAnN_! zlaO=-=$aw`R8a^u-nb%E;)wZ{7)Q;ZSPm22$6w9$a+~UMeGCL)R0dVTF`|0A>qgK@ zux)D~>5@YLqP)q?expS2y-+GTa!sH}cub#6wZI9R-Fe7!5;yAfkZ=w?jmH9Uqj^Wk z!u(ZLjg%J0oLeB;ym7Aflpzuw(r6M$4?^mV`fNfv$Th*r{c#BdaF- z8n1}?PzkDH+8F6kPKMuzgl7&$u0mw{5>fSLn=F*WXc$<1kYf^=n~*w6$j&fjbnryj ziK5GaW9x#TLVMR{NRX~SktqW_eyZfQfGJ3tYO_3`Mf31n(}V%<4lL06z>s^F62o{o z%?OL=7lzJ&F}_3Yp`4(eK)pyGSo*C1F?}5u53~fMT7!|!F;xJN(vdr1;fu!MdIlk5 zaiH{QB>XON5O%!=A^Ir{9id4s4HF$iBhM5f6l4Lk8^j_wouPl2nE~9hPC#H#c|;Nc z*udx%QC}YgjZ+%C&{7a%sHl+b<$7Lr5I7^HLlOmms9}fzpkX;xMFX;EOYogm=LgWHUYFFjORhSpa{Q>k4QW$*CHYavr9FpZ2{b zMj;&vsR$l26RfA{Q3euIiuo5C&Q88J1DVqkc}hsc#;AUu1cN~?4kR7+!iWvz$hbin!q?(N zV9cRt-YDeTTX-yw6=>q_g+d{P`@=A6+rxK{_{{r57o_|q3&TfCO&Lc+pbhYv4XezM zlK(1J?)rCOh5>-^r z_(d6EU7z)l<^)YBFNTRki!PF3b%CU-AnQ0l1(s^MAWG)RG0Xily1fwiw;<^Zy+9bc zKNOXN?;PEfh%h1*a36h=pjII13A0fk>4?YqG=*$Q@IaJ^VJktNH1ZC~U40L;NLzhI zMstJbITZq^wQ(NoVD&eUiN(;Fn88K6hK4ju5L6i)#{0wcw=|w2KO=KsM8J><0mmLQ zOLk)i0iX?f%pXQf5WS>RbD_^)9PqId_%0TO7C{2==(r4-Nzl5z+ywapTqS{_wy0JN zLv5AK^ZzjR7C>+LH=ts(m<_VSPs7(3haT1l_hBZaBQXJE%y}+ za4#{XpFFvv9r1^9-8NiBi?lV?Qp(=XDv$|{{x_LkC2!G^=z@bZ-0wXts)8B_(8eJW z@F;FQd7+3J@Ozy3lxKc8PGga|fx|rbINd|WA=1~-__U76UvnbS}b4`(uvtU>P8wR#C@*n%GmEP!?hr0It%;*^S0Xf>=TCm3aES?O6qN5q~$iEMWb+bXzq3B zKf&6&)eLhyN+LW%A{` z{ra-OgAZM^TCd&l?M<`F_dGa5v#Rs{1eN$T^ZmEwIz z=;En0Co8f(K=sOo)oc676AJvYE7vT1b$XYP<9&2iz3zRf!0P)*0`tw?aw!@>r^cPz zV1_}2mIyt!0?ZFSfiH{R)-uhEH0W$`E35xfx9#PuvNl^Bq=*uoQ>h;<-aa!AcHqkO zS_MO74^`{EfrH?}qqxG~0=rqo=UzwSAD2&LLQjjDH+QT;j|MB>n~6NxlL5Kc5w-J- z$Y-1yb17vfJPRtfLp4P9?8;oZLrwl=l5%$K{ptQRNlG!`JZpK9HaZ{=qpMC>KAi3=+uGR9g)aM6BrN7cn%ewga})U3WDcuGjaa9@o6K zMh_Y*X(;S_UbjDdo*=&LC(XhD?dd=(W-5g6q*dq{AFBKvwWIF zC~Hh-9RXlt`a~3~;4K;Nq;%A7;9vayYMdopUAZZi2qHf%H;CldL$oz@48gya_$2rF%w2{K6fX6;U4Cy4wL(q978Iq^=h3xiBu-Ql+i%M+Cf&Yg!x-=X z5|zB}y2;+|1H0YD%|`Rid~OH*@EMIWy6t+>E*vBcQ(ARe1(TgTK-{m}YelxTjw0#l zw7L~hb7%@Tym!^%=!!hCHtur_ixWmQzYp?SkV+C0Pab@aT}50e%w3%R8tz$PZ)YYL z4Lf1~E=nm!msOS1{`EXygN}74GfOLxR?9N8e92MKGNI$U9d2R&MQ7^m*IH=$uHGSf zXhfA4V!LZGKI%H$l;lT__trC~$jz0nsuCAcCy|qbbsz}C{Piy&lf4{=8LXymiCT)V zi4q7mZmGpGcub$HRofIHq0SU(oP>K)Tvfr~&CcWE8_6zPkvup_!$W9RK3msqKY1|4 zFLw9e%5a+j<#?U^Yh5ir5?yyY7QHjYxc)MwFQr>8yho({Pe;iJA{h&0y?3cCja=6| zT~D_wingPnI@7avvP6%|D-RR`|jKf6R6$xVHHX%v7QqoL@Qx_l1Xc#3Bk`i7~k?GRuJAWoOE3D?TI9 z=-TRe!)D8|E;wl;@Y-|wH)~8&ux4KaIxiOwuSFv(&rN;3;+YZ?0YpUcC|By^dS8k3 zd1_eXZZrBI=dp@~K64=^a`sYHfWaEp5J_hMsXIbTtZfs}?yca_ z{OTR|Y+uebo4=x0!ejspRZ7D#TG#MkXgxz+Iqywl{XA7hcC4eotk84_E-@9!1kB|3 zz7zG#kC>m=)E#L%&($x`q~YnXUtbj+!0FspqjSHs{!Mc{1xwc^~o7HYjg$>fdh1YiL5X}sEaiUjGu({$s<~&-_0uNz^#%soW zcj?iFGiYS~*8bRah`{&P4J)wFf=t9w)(P_{M4dCOLV;0H=J?M%al<00VTwS;J6@+N zEVi%Bn|-pVHYC@Fjk|7iWqzV^?}f*TBpM`|VZAz1KV7%yuB~3WDk;Gw)iQ-F0cbb% zoL+gf{@qYyqTZ_FQwf=TwC#0cxgFJuJ8G|HbLB!Mao^X)^tag%p|axc=Syz{s)i@8bhi$K)Bon&ssl2^p z^u6?D{pgID93yGtg(%FIjp__%FVqV(vvyff||4OayrV!^yb4|DeH_#(^B zbk-BFv?b`oJ^DM)+~=J%KV5AFt;ol)&Qb@duRt`FyLpO#c>*-gUdb=c$>j85s*1sB z;CXnzK@1l!^RPZ`EtrUMGgiQPQc&L-+XO7@u%9ipFeq)ck!|&zCESnwG(!CVtPwkn zlPM(PVstu<8_iuebb3e}&2=~9*W)t%O%{0z!^v=#t(Eb{-5gqa%NkDB#Y>q8-@Y|( zNQ01k|AY6)!c{FD>7vYiWi`0`DzE)=evQ0nrCUpjTVG!C0$w13AaNdGS(GywCzG;n zxbjBxGdam@qRc2yAYY9e#R?dwqOIFl4nlS84^3$XcD#jicyn`-J$_L@UV8=YT4HaG z0TK&d(&pEuPHX0>-kw_n3A~vz8*I(e3X&6Xff{dzK>fy_$)IHCNHgRn z>XLEG_URGh6*?!W-+=>z{z^i& zvTZNY&s&s7=j`0BvEk{XYkW?7swm0l0nvd%kZ#aKn;p=6tr}vs~57>Vm!nY9^!gC`q7s#;!*1dDpIw*HhY5=S#eZ$L&k~ z5AX;(VAJ}gVyD${)u`QH^Mt+zL~{p_u5yu-yaaZx^u^lK&!na}E0G4iTZso%812O4+GIa$>(^Q%+g<#!y1~Z@ z&zl^^+woI^kJSe(Ei1vHmb|z9W&{0~j0eDkm4D@-tG`&NNJE;97H+Nm7zh}k~H~Kpb_Q5GOls5)xJkJ6SP}=KUuw2D5A|&e&on`puIe(whrkZ z8-K04Oi7$Jpc?gbO|Z>gJT;#7{FZqXLVq?Y*jQ-z{4{iGvdfa&@M}sPHa)z5sJJ#k zs&`LMjCa1_qNN%~$RVn;JCT2eZ^#!MK|1F}e%hi^y`c|DsVcBpekch3m17CK?N<~gBHB}`v8Ob6;B_$i$roJ^e3G!*;3 zl@L_a*JogU-CEe4u$w4#fd-ji;@{5!)qEtwg__aq*~b9g8o^a@n1mfLtbfZViacTqDZvpCLV zQze;7)-u$WRRfIi3+C{jYs=N47yedc)hfZD{afG-h_7M;?e}|Aho|34d$8g|&e}3) z=gcKsq5XE!9iu*{g-EWO7vR5AZ&57ZINA8z(a+UrmVT}q%Dp%G62I0yW$?+3W5LF? z`?WcgUbeGAW7dZ{>o9e*2&=Id|E}&i;5^)Glw(B!w^8KZ|~C6&Q6TjzU{Pg}8Wics)l0L>@1_eIFru2)53y;atf~Y6dkWn{$?HJ$@kLnhzifU^Hcq|d-nDu`l|gUC&@Ny zL%F)z&#OOd`>St!j>gU|CQ!6!0Cs*a`y$@&3Y?jvin_ihfV9*3_fmtW(%Vrz34uhk~X-qr4Tr=>zLw*GF6jZDMZo zJE=VfVcVNi$KVlZB4g#KOQ^GK`C~}VPA9-!X+Lbnclj>L6djugcOI83ZATpNS>Xe~ zDwT2CAkmutDwOd$Z{MsW3@}MUbq?688$~8!et<+H<2*8o^wD_E27Nngrk;Hu1fedV zwONA`9}tFx9(cil^Wb%tHiWa^7%NAl4Rb=4#h%lO4}Y`HMFrLa|L2K(nkV2Rse(>M#Y80h$>U0chc(}{-bw1t~S(GP}8LpL2w;uTV zDgI6y9fIZFx0~sVtyDK0$g>)anAnIuo8evE7VKJ*sw=>YCL*KdT`H$~YqE4XdEoGlqaE8w#} z1eQ85{kZRXBS~L;v$@E4>jGvj90y&zldMP5Oq)jIywyCcEjNkvdS2KH5Kctr1AQSu zc5Q#0Sw73t(BA2uQF%CynHJ)ly8H&Lu=F#JEf3U)^4Xl9N(xA~pFOfP)7c;#!<%7* zE)C0tMfX$OL&iXdYVN(%@QfzZj+yxIC6B}^*%zsUA@nSxY&OAC&WZluRe^p%J-2k@ z7pm<(C6WoTkhhj`5)&6KE>;g_eG!)Xw&tC{A`FkN5d=_<^Xl-);+nqFirZWI>Pbx8 zVs&dLDeu)Yck_QzKAoP_QU}U{sVJYyV%VVgLsZ-~TVFn49naNfq<){hw6v#)X}f zx|Fnh{FiMUN$Ta)xW9%M*#s@2a(#uQYXY*ev4E)tM zyNEjvNeDSOmtiWhJ%V(K*#buNe93Ouubo`(uP|~0^-+(DV;hkiQ(v?g4)YgAiw8!G zvS}gi57lPIW-|6^U!%At*7AZ7!&S;|;!mKlS7Fpxdt|}zdL@yW)%x~4%~4jKUyrLH?>4W!HtlzugFyH{QHZnam>Ow!fD9D zCp%A$vX%bXuk|X0ir<31CP*DoSdsm^ravu7Vw80e{f{`pD~n)po3$p7C;hD1Q*w68 zbjHgi>IAB&G-i7DL%z$r0&@P#;KHBw?y|MAx>{=A$<|aTKN})i$+{;`QOdE^Vz$Oo zy1mk>Baqbi7qp4djJ;4WlTbB91DE z!+ngE<;!1J8|5X4Zwr^DT(Hlho}l`r8)Z@SiEpX&&H-~DJzw|eO#H=a7_JC0IL_h- z4FOKu=F~RmCZHMz_BiZ4&UZ&V!EJmzQi7$7ai(Xg^oZ5mg3?DqD#LvG?CF5$+;3P+ z!KLCen5?cc_0nsUqh8BSC`K**KRZmdy-6kMjd#=_K_Kn6FaAZ$^n(s46LOA$cWE$7 z(rg~2rorwv7U5rO*|L42jR!mExWl;}4xBB{P7jg>JeLX>9>S-_)W*@j)~m7M-|Ogm zH5lUjx?w`;Lj<}Qui9ENjV075wGa*r8-F4Qr*tb^C*Bf^Uta}r#Endzm;GXOuIjAz=e?u=1jw3Oz}N3N1_`i$GGz<&SSS$ zniLOyCQcunts$zHp3Q)Xjj*PyMtNXE`JA4_1cK5q1Cn;Cr}~;Qy846=NbE!5v!l?! z;gjs+Yr{->GCAM=HBtbsElL|dS2pdXAafP@z|Nto<^sYd-o6;j}>vemTHZMw`ht$aa(xyUFF3#I6X75j2yrl!Eb>Am$`<)C7yc zyoz9PSVD*`tjB68HNvuw#4dPAtUx=U#b&58KJkjfnHMYw$wQo&qI{67+ZhTXA&w)2 zpUP;J7lx|%yNnuJ_(j50?`gav{0xl5VjOWgc#5Sq(lkXGE8mhO0gppv<{O1UB zn>^?5kdXijBngU!5$*kuCCZCa(SS(GPj z=Cp1a+^3&mH#C>%^&FU~Rj*9R$TT;c^;8fFhi0l{$0A(1r-P{vF+qycR+!1Lf=8-% zZ|jK<4vViW18FiN)pZFL6Q#h9WsHfkOF1I!ureWREh90D11=mBCZyjt^}NQQCjT4L zHT}cBX}RKr09eK{Sx`J2giS=_q>^x<0h`7Vi5L{ct%!vvVU9HeF{FVzPdx}{z5J+0 z)KElJZO!uX@9iLXtHbh)on{SG(8jF^`i9RGZ^R8Y2_dB5Q{_e#{K`eeYcC5mKFhAO z>hzh#fIQ4t1q)GKtxvCykpe#OF)bQ4(3HgpoU^Ao0MbA87wSU@&%>l~*8qB;$YCgx3|d?LXLXZNR+?!l|>s1yZEsZXNc z?75MkW8mr^F>hdU7%_oPn8;WHFfm_+Wx__c7$Vy@V+gTya}QBLL1#FM4(a#11M0Bm&Mb_0j`>-opEA_Gk*jeg9OA^GG3A6Us?Q#@^{QhA0u^ z0qaAj$RCn{Pi(AR!COS}nc`AR^fKN7Xa+#=CqFdDQWM=opz#bC>BMs&h8C%D&NI0b zTP}t5(kSyvSqz!-HP+*m)QJ7bFOdoN)mYXx-)kPWKhEom+4onk-S!|G1SE_a`g%Ia9=*O|Fm(=eifISzT9jSpZWCqX4+n!S z)6fCnV5p%A11pYGY6a0M$swxA2yblrx~l{E0|$VHNEqj$YuxDuvD6*^TiEueiLYMY zl5^{+u8<=!*i$a!c_I*lDC_CI(bc8Hbs{xl5$X0If7{)vDcV&H2;)ssN@f}({bsA; z<%J)*8*Hx9!%&5m$Lk?cPt8yYMMcP(2pg%6Ii0bly@^b9+NVp$0{P%2$*Ht>;)0C1 zpV>q?fL+yWA~Gz52;`J75jignM)D0I4F*Ok3ARl##vz@r~FB z7l|gOp!(k={~R*x1O%pi7w=O*^zAVY(Sl3ylym&k-!V-q{nRo= z*+&5Bu?cUfQy-XcQ7K5e6z0U!@-g$gMmIV}9bPTLsc`4x#LQ4ym!rcV96A}>b8cTf+~3X?cjSjrkitwsF&kX&zxt=20ac@-d62m6aq?+3Nc zC^;N@729uRiFn;cR2C!P08`Ibi7H`^j>66PQR5K0Kv;U*&;%gf5y zfyhmvO>Ecg>vH2NL}q)ElhtdtwVqYq^D>R#+-tv8YD(X0tMi6cX;Ju{SFt`PyK3DF zKbKC&=Q2>_g)?8-J~Kz8)bhxZ%e=;&ggDFi*TZ_p)$GZJrvm%UOlpkO&AF81RFg(=<`<*$M+L`Asj@5&Tqacasv6@Kn;6_Lugt#pnmyLlrL@sX4S5-bsV z|H)9_Wf)1;NYHJ#J`@Yl`H|1Z!kMz)Tm{Vj&2Ob%jc(gazR**~vbZp|(RF3?(=&0X ztyhDSgRdf_iBSD#fh_3g39WOn$wl@8ScSup)<7idoGAb0XJzj!$T)N47ja?6tYr)* za3e_gHY4_UA&v?9LQ40aNi87ZfcY+nuf$`;raL#=Y-FFVB%>_H`($|-sQ(ez>~-wB z@Qp^BNLHk3=y`NSK{BKTK4o-8?0II5pTh=+&oBX>7ER&0t8?yqU5S<9uL~8XNIY~M z=(;c?Q>2C3__G8UllHLli-?g?m$~y^hWRVuTE%B-VF#?#*(?28f^TzIKDCh_xEGKp zGD>WUw7=paQ&SYTC^ZKENN88H^S0_6cTCqEB9Pl8+O^?|Asbl_^aSeC3~|U2 zr~g^2YnoP%=*M?bdP>|^Ue=p1+QJ#F&e;6>+)))V%RW`=gRR{oY)EgABlD8Wf+Bkrs} zBj3d@?M!2a_BZS8vJ`L@EHGFQ;{Q}dq0hc-WEi9gj>n&qk{T*C(&k?O?UtF+SLe}D zPM4^*s`Lt9TcDvptK+aQIgz zTCF=NTU8FM@GpG-yOZX+@fcJUB*V7uc9NHVn!pnfQ*a_)IbI#tq?VH&4|k`pc?4Eg z2sjDRaW<(VvRm_tU$MARbkhaQbCDhkX`M;n|1(_#HmD2HG|mpOD{x}_r`vQ_W-nOL zoz@ZecbUyTLxFch+#VOZZ?AO3!ae4*X>sWK)0fiEh3 zNzPHwKbFkE9WxJFX;M{)h5O$Tye6n%`^6bJb9ArqM`L5QH!&tI-QVrVpwRwxbzH=k z*82md%rKF*oqe&9B!0N2&|UNq_TAJ$9(f$UMCE%l#upv;adC@-!WCJ{|Ht9kMkYay1=tG#&EWvfsSdyIjzXMI&OnS%B(Q*m^y_dgQuZak9{$G>7i3dQnouC5OYJs$Oa z&19PP_!EvR@f6mdiU5q(FEeW%M@IPb=xBByv;9ovB-5*&xZ|jq7(X&cVW=W>hs}Rd zqDF%{F4)Fx5#Yf}kws=f%M~3$BTy3_t12)(dG!1A9SB5G+awo4DU`zXn7=35@y>8r8qy=Q z5&4bBbeeV{lBw*vtwJUj^w)cN`tjo-=cKPa2v-;sU;Skh|x8X9&+q1yDGmX)IO2g@evk*e3>jq!a;b=3^4R2jSKOXl>t z6>n+%Y*V2;zah1(TtD;eyemtPXJed~#AL0YNL9Cfb4&bA7}98mmR=YGL7SWe=*NzC ze(0A^*?WD7#@C8Y^-%PUIk4R}%qRY6>uc+mvk9Hot(xzTL$vw}W}K0pl57tXJxrxX z!>XJ$5mr1Pax3ciTMl864BU@g2hk2QcsFe#@NCFpeVRI>6}c;^9$$me8PM&C?Fp2z z_>c@@5;+>4H>)30YD!VSd6LPYR!HZUsocF;uy%LPrO-DBd%)MZ`3(bm3K{&Z?OTQa zo=-Qt9-Lmv&3-5W_Sjn&b4$FcFo9q{mUqSW~|BeLvU zLYc}6P1O63003KG_LuBT4B705D_|Z5+3dxbRqY4eD%#7x!e*yldCrIYQXHKNhm}nO z_uSXX?pu9Yt4fJkoydYld>OKFInj01i_9O>b`%ZR4OX|UCx&J@0NRBLI%$W~`M}73 zQtyXXzH4aKBtQ6Z)pOoB{8#q=%H+JrUtu$XVd_=h>QI+#!DNQ-gX2o?XHVX-BKMW{ zS>4Wjdt}jNG=|WPj1q~$kLd{liZYG=1cNE$o}Vv-nohB^3}q0SFGd0CTVNNi-?t^H-RJi%Hp4!@Em;kF*A}jCGqVNXM%k*x!R+-Dy zbgO5V^TM6C2T4-2qA_-QHv71C8WvSx!b_2(W8#_+-B5RG_+e8zij<4a};&mP}r(5WfVEy(nL z!kbO-h^Qq|*dvw8B<+8|+S3ATdbndO$6PMcz#wXoo>99e&2n9}#E}}r302l>#)f18 zbqd*D9_54i<*DgUbAZ~j{x~{kpuZ`dyu%?j*CO-Z@u=M* z@r!{gs|ZLkV$H~q*ZNS1ZJ+ruJH>p{+~NRxiZUKMCmx9-)x#wpfzKS{SBk(Im;4d) zje$^n+*hjSKCs!+{St9A7(r@0P-+wSYWk`E)fHBLlw1ca03A}^4matz<Hv3l)&18&kZ^-~Lx z?_IE~$jw1B*ukfJ)`ug_97@AVBu(CFqAxYd>98*~lM~9V_unXW_NSJ}4#qSfKNMtC zqAbx1?okGoQxKC}FnX-}bR-IXLz$;17FP=U-J#3WAXlVhTYw{zV@J!ZL;k9hude+6h_QPy8;o!RAV^42+m zm)bas{a+tDOE+v2E-eE(QeWHSDQH1)s+?lr+@gIx2XZkVz9-#@PD3ssefj5&k=Vrj z;nFm+3uTq@J1cS#AJ^yR)R!5{h>q0N8OfMTE-6J2@l#VmI-jGCuUL zIy;kpocxiR_arvB78s=71r|tX{-ZWs;a##3&M3j_v$Y9HKlpWA#i^Ir_^)6a$fh16 zGuU7x{VdI}r{TAdn_0BH`+gkeSs(q6dIz{P_@!KC2nJ@})`VN8jA(-OU)H~ZVqnQv zJ8cEvm(<*BpE4OGh>2zn4ce}YJMz)abb&ftL9AG!FuKAsFP9ZZw4(sq636~p!8C|j zm5iRYvXVkaY6;bCjDlO0pm7(-!{-R~v@P2Yq7il8vEm-J zv@>Nc<-Inlr^bn&;F#y-MqBZ0u)IQ)V^FW`d{k| zg@x$+a^W<&%0TZ>59>K^gzLYWuW@I#kg2`uedxp%`=JMO(Lg0j;rd*1ZdRz?$I&q6 zM0Yw%TbPdv^p;b{Jx({g*u$iH8v6X_ds@S<5y$ke0SHXRXK^s&t%^5p2$pYMRoe9c zwk3o${-vF)?L7IZG-nSg%M*-@v^{qi&dx_8&U*1`va>g2=MJUQH;zp0JU~H1xewuF z#e#sm|FGw1Sr5)BmM4tBAdkbK(n8vx$6HI=UdC6C29;GYo&Sl%ga2C7Qj}o+*OFE{ zI4^+G#{1jCvtPyCC+!oMx&;JN?`V~UOvweQta{<&%na?An4UuK&=%*snIGZ|@^4dEG6kgoYg-7ETfgCZ9n@vl5~6pEJ_jXN!`Y_S*dL~gSTu@mw7%Is6;app$A=DW0)#vqzg#ubpuN2myZ;cwW$wN+y?HL+`M!Ub0nqn& z^Q@+?lF@kUeg);Lec?VK4h?bt0FRC{IiE_+8)N`bNH8+X&+k;I-8_zRS1D1Zo_iq1k#DUAS$qq4S#;f{l;` z!KxZ?q!%c;=x1W83n|v7-ldWYWD_ILfrQ#9 z&~;s>+%`ta3*XBs8lkl}U@{sRXihHZO_`}io;|m;$G_KgO>&>q?`v=DLQYYhWO&tg zZF1YJd_Bp((XQW3(-{TDkRO%IsV*RQooe14%ZuIIiofA8HH*0KG_iD#n!vcZi5$+6 zX_2V`${AkRHBY#fqgL?PwR4oG(r2bXQ9k-)-pvb1RWVr~WQG&A{r`9N8317W|HnT6 zKOL0;?418Q``r0+quNYH{oIct$|_Je9#e*=i=211-DfcYDV$1-+{DA9jcBAG_i0Ao z0y`wv#5@j39Ci>@^z`qg@phmv<_Fxyytu-xk-oee+m(gal>`|g4Yt){^Q8Vu=ITS$ ziu%Lah*cUYVA78@HS7=HszkX*@|01}(C&fJMMMe(3Qt6% zh}>_Ns_BeD{DqVTMfAfH-_4@@H2x|4%U8cs{n}7wM0pgcvYxqXZ{;{SPIj~V%9Mkb zTqZ486vtj7n;7}Y&T8MG4Qf<@W$G|fP)-m!xo=k%n$&@e8!EU3!|@x%d7$(VC@~X7 zS$6+u?6NMS<5uIq{hB>|Ag7Q#A;-(bQf>fy6!CaB7x&%9_-8O`_|*uXbcIOSwRUP> zl*+%fqMkX^a0Fc9puy%)-3xZk558Joj6by|3XgS0h<$K#N7S|(6_?0K%`nZ|!E zlzd`*5G8e6Q^_o3W~J-Lq85=znGWd27UEJP@x^!Pym~OkR!F9&sW8~v3ttXbE7mf% z%FtffDx}w#QQvF|O7dT!QjZE&PuXWO$qi3IvvVg*3>?a>*)_sS`iSd8|0+-4_inw3 zuL2Pu|B4qy-bTL{))cQ$GxA8;OcyX*Cf5kf>PSXyGb)*O;8**r{?yc~f<|99O1N(M znaN~p>N_l{u3HRseUz$V9gtq$eUY`Cg@E-(x?@t3x$30427fEPDt>r-rqHt=|2irX zj3Wn?-!sIGnl+T%cQ04M?b~30B=3*l)xsdcT4#Jx25Ml2*{wUfS+XSn)rAx&X z(G9fhEi+;4pIDw)^6(o5gKqi{-9NdVLG^U?=%jQfhu;Oq*Y#d~-mB)i8X>=${wza% zo*z*eo)U@XAe98sk5*5YAnKso9jMb**fm>UnsWd9nLYycx5BmRL`z@hp_DJ^u3IM+41d*{O}3~vJ7!Uzv>6ifUv>oY%ZUn`;~ zwF}F^z-X^36TawXOx9--J}ySZ^iQa#LzJqSiG`u7d{P|n`ZDLP@GLJPk?LJN{Z}k< zoR{UpT;ZX^lv-he1IiG_v!g=Ty~Z)XC|;_bSR7W%ff7CpFK}1F3>@-;rRskJ%R>p= z>_f%zSFdY1iAEsIM9o6j4!Fc*Cu_kpO_xVxI`3(T#N49Aoo4FQVAsT#z_1ro;mFennGSR_R;UUphNL7id4@0NyNY!GE zK?ngang5(RO z3a42S z%d?C?%+%QN6n_>qM^YRAUeg>qjeTltkBO7z5eW9qHP!@sf9f=v+hoS+j9*feu&0(A zIo}&&S}9%X%c8?`_2pju{9#K1=CPNI^`UiISmEwbi!iPLVvIEg zClpKgbHn&v;-O(C@x$O)0`>G zmF*A8yk-Uxsatq_bF7W8*zvt?Jppov>1NkhSM@0|?zx??DNK|TsCjDoKQ8j+v%SYW zi=e|ujU|SWTHF!f*C!=i{hj5fuG>x`cWAPD-Y zY2N$Ou>=ch=(UHaKJ-~@Ll|B=IY_CNOf$%uNp=`<)`W#}Ta{RwAy12}yn@~MK{`)H zA0oIKIs&uv=CQ%aO477; zj9_{d$QuMMdxXmc*tS6w1G17XMTAYjOy-#s*h4)i+iC0&ifvppN-hTflg;Tb_)+6h z^ia$gF^e$XU~*K`c+c^$A?+wkEPp;?aSn)yUVWx2gZO!6*v_w4NLAq9udrh|{yEqW znnd$?8DYh5!OOHTDqoHYybzg1nHtGP@B2&xZ=Xjt*%GxKouzU<*=E=O{u)rOr2;H?!V*r7AW4vbWPrg!)VmMOY#kO&igj{{g-OX*DT;2&7oz?ooL zKY)(Qkw=g3kU@Dq^}-lgqlr(&(iz}2wlZ30Py;7AAd?DE$9FG=SRU+Admuhm@e)(nm(vxn5Qx5q2Z5Gk6k!HH5Ir50=V7zl!)ROG3)^M+BDFCWE_esN&<(5ZBx z_|RD;t9FLc=^Xg{5(&COhDNu81fyR^MoQH~ar+gXnZ&?EA?-Js7fJ^Y!-6%ue{t<* zrt@kQfQWIqiM@{@8rBk%@DDacO)pP9=$fhTO{gVE>@(JkZag0JvKsM<_CPRxjKV9N zpN!hpmNs=ZTsjggf8|okqZh(|F3{J_v`vX>emj7(2t2LgJviEL)LjIwf6U7*0=qpv zc2_D>e9w|RSw-%)M=h$~wmVY{p5`N)mAWpkr=DFE7PGqo11E-yZ2;L)OA*Nzo*PQ9 zk96||%7Z}+DD%;rylXY5=T`RTKx=I_qcv!!mp6DtJ8lb7dPHyJ+2a)gRT~dv3A*^ESd}}1oe+yRX*aH6iSwVV%9915v68jJm zI9M|5+28hg_@!^FC&m@wXH=rE@YCii{GbpR@H@7&Nq09|sOXq9-;;gVG;&kzk^vU) zw?&yuaK(2OQHV=L;{S4}3IlH#qxYpVnLzema)V>oPC6odex$=t9dFc=cE2e8lunF? zzwCNCnR+8}S!~AuzC8rU(Fi&3lvT!$Din+fZY;!k;)Av>>kzw2`|4_&+G~kG@dc!j z;iJaG|FziUrZfU;{P?ln0J0GSjK_ggZsDI0*x9vV*JGQ?hAA!A!W;h8$00?a#{5t^ z8SyLg*SB~XU~XZBUOu@HZv6mN3q2BL&YR*Il#Mx*ui(ecjGda?(g&d%sY|H6}&f2GQwVF-a2j3Xsgy>%xu7r2oer>U?I=ulMpXg53^4G8*8(}1VWKNY@Q zm3CI?TbyLzp6v{AJTAP4uO0kFNE2K@D{OP1@piX9p(&{hNshKVp=sbzQmeRAI2=6y ziWE9=xHk~lsmIFX8Bw_)%R=S0-me~84JqaVR+e9u{bTtO=i41LCDIBY6smSG2Vp1C zaVN4F$8*^Bq~4CZMS?1z>bZ_{)XFgIr0Bxd{BuXr1K2bKNQXw)*(_A&n{bkRVOLDd zt!wYuBY8>o`zEH%UFz#VNrvnEPflws%p|F_r;6Y7rSqFBTi&iR@Tj1 zb-%ooS$V$mRoi6A*Le@!*S}|1Z_0SLeHt5|fx-c*`emS zEXrJ;Nvi;t-C(K8P0NZ>!+Ps2=BcXT3}9`ExAQ#ZeJ{z*1tFen>hO`T}u?%IBbIu%MMefB_B@= z*MeF1@xVxdr<=2T4S}b+7T^8&p2xRpB*Ax?N`pa}8#uxmop2wTDJQk~8>n|jzAdGM zKZ0%FLr8kx{6l<{>`t?V>e(mr?+!KM<7@nQ+__Q~k*P6gB6@0`CPY*68hs*if?)+KRdIA$*MEdzU z4x5+qAyZFTN0bN0GMaU5_8;?e71|FcY7dT@=vpfKYX5(b5*LG~<6*pRu}}mq!;@W= zd$2@8R~!DeuHb*Aq0f)ScityJ=c!+b{E*Xq&}3=YMXayj6$>8j;a%$eu9EA2J9rYn z-9vPeyZ|!Ybm%9soHNXZjP)OyEJtAC`?x`&E6M)j)Zy#eCgAZf4b$Qu&qJNZ z<|iX+FsyaGBcP1OhttxdM}$cBc%86(tXigrQp&OppRae#2>iGnhSuSB^hZvrxEDi( zO_$c8q>G@BNaD`4MUVUOG$unjhSO(jg<=DC+!3cwlKJnlj31?5`&-eO9Iyx8Xk>!k zFWF4npH~Bb?bj0xpi-G7JG%++uwPLyvB#IW4uj(ZtE+{MoYftRHY=(DL`~H;4{wUjR1;1_$lzlR$)+HumVkf4~C792p;b@L_x7>X?1}Il+3Ex4VW#dJe z4~>yo8occ~d?fE>{Nei*{d}3IuhgQzYf9{+I1OKxM@sYi)o43?c zkIJ`Le_^{psEiu-1pn8 zZzjgXK}3Gjj7_m2=J(_NWo=K8)(3#K{k{gc0msEYg+#q(_VVw+U91EC+w)tc zvn*L;wWbc`>y!Y>>K5$(31UF0WK|l+Z**>smNUC{0mh?+Iq5ktC?KpcxfIU?ZLVL{ z_XiD^E@F4CfH~$>?9Ld%INSH#*fdR<)KBm{!1mi3T$II}A|MC1L#tBUYCnjiJki<> z#L5lCjAL%2!o0ZA4PJT@g!~!mO7LEVM=u#0YDVC`y&z+d>sJYh6m#X7_{@SB`h3w4 zs4V{;WJ8%f#N`x1YgTJSf2IH*h|i9_dE_QEVNE6awClD#pBtcIIPPv(i|O1w(XC^& zUC**0gk4D_lbS^CcLJq!8T&L}7Gp+MmMjimF+_u6!oOjf;@94fCqC(WN7kE#V{r6S2VCPqU z?RTiLK^R=MQj!+LQv6eq2*EYmwNlysW-;x8ndhi?5UWhj+wAYf$$w01p6c1`Bnp`c zRFkO5lm}91cSSH>b0K_IB02x(Zn@F_Wp^gbYn)z**WD_gO0iqza-fwPI7hB*sa7xy zFG&wM$x`wzPj>Y2N*WBo(Tdeh##2Mag5i?z<9>x)rkLV_e~I2!H0Y@s1YySAT{GuOCqj)YI;42cW<$bhDctU!Mrnc^Pgxe*V?qe^THoxMPRPMdB zN%iQX1FiHJ2&V1*mVr!5$7pq`_2kNOxT1nsiaN^H3Kz%T^bb0uyz)TP;FQD72|I9_ zcEom3nLE9$%{A?er;_UAznSlS$9KVM4ICg%*5gIy+rF$~`VL(uC3PbnC&K`HqyvnV zY^TS}J9zc66_B+G!sOrUNJZm#U09FojD0=}^X%DI=+9m_9fn;~P9Po}Qpu@MVXfvD zRai8Ld9Ov9luB+UR#(#%S9qBR-aPeNy1<@lZrxR-sDHM_K40u8tWVJceYPxYQcK~( zRGVNB;$HOEZ2poqJeEM!%eS~}5oh^S8$9~6($yTQNq>(kgtmqH`Ric4mZoe>6+G`9 zwcx*B?7%^jg~usNdsZRJQC>!gk)<=#ow@NhYh@$UgsIIZyI@RfQ?asHwm%B*U39^Z zl=C?fc#nCGTG#e)uX^-azq!HD`k#k)Vobto4Fon7N{BV{PNJtmtDIE%S8WoFwyQrr@Poy!DDYnsLYw7kGDK^>(6C@MGyb$_7;&V6XdV!8+WFy?rYVoZFh14r@F+ z>_)109fIsl)BQdz#Zt^VEyGe?zx{GF98@9>If@PqvZm*L_cHQFs>cg*W0_d_CnjROe?aRtPOE=#(m4Q@U*Z$ALXuKL3@#6CagUo?zW@=NF z)o&`zJSE9)z2^6G&1$8G7*mx2R^rpyHLzU4e_CwSpaKL$!iCRnkl>xqLz$1P*=72k z)ZDEgR5{-VY zM;BxKkf(#!otflvZM$u=8KmzMnBe;>SjWdRB@jaM&vQ z*ZK`+B3gIbffM*L2(g3rJ&IkVM!71C%c-oWD+TKwTAcZ)Jy;HTus;vYdX!2ezpT~D zANSC4AC{2V($chaxzUX-vacFHp93js=3~GO`fk0(pN}2h?dAQK*}v@|=DvTsTNkxK z5c*g-jx(Au^BObLwxXkeP9qyI?TM;vNAikm077|}_8N91#jH#kIdJX!SpK#WUM)7! zBg~oD(l?ftUyrDRbMaw;#cdDkZ&rj353~ zpKH*j584|_^K$@>pOZpi!*pm#V?mpS(a!Z!f#*$)#rOy%WtVnY|Evf&KYS-#I9H7i z{Z}Y@G8m)@aq>#^8;1UU8onjSHB(!GSPR!vTUmzL;4|G$TaT>BQ~}-cC+-oA1P6|qXPyXtR;FCq7bbbcbW|*=^${fXYCk{Sl4n9tjUo}T%O6dwF4EnJ@9+|TZ zV^8k3uWrl$l3MkXTe~v`FBMhh4VE=fG|8(d;Pi8~?=$1>%Tv5>%L7JyZ=Q)>CCImt zp)S!J?K#QPfolyo6Dt&U7kO`a1r8;+pP3GvY588J7ycGbOE4Ka{U_(~o3>Xlm&-)= z+it;AlLM8Nx&97f@eRfv!?B#zM*Wd#18&%xo9rd|F2v$>;Fy(&B4v_ zf90a9^<=8dN0bnSMObk9QE5o`{tAJan#uQ6bo51`9}AO?4(4PGin{*^Z_CKziZkOK zMS_+lRwkumm4p$wsE0NlOQrZLY+G=FqEII+5-EBhhDmr<=~8hWz~A!r&}Knuru+Q; zeedSswz#~g=Qc%?bH}bQgeJFo8{m?z8-cucz~4P)b=M@6M{~1Ge?UX|T9=zd1#M3n zU#Xl^W{Me=<&4><5PGxEs=zTCe|~VJ)h$DB#;$DX%t$HePFnNCP{wv@Z9w*uN@Y<`39?V^Y%*gu(MkDtTu)__2 zg!;W>o?4-+RJxPqu#etU<`S*66@hV6WLVEw)kA0=R*TXM) z7T$+=3_Q3aYKRe3SE7Etq0Br4&M^whkCtqcLDu|8<_? z^us9D;t+0&Sh+~%`6>b+NxvaJMe2k{u^Tlyx(H4Y7I}n&8X(*dlZ249GI(QWR_Ojh zkC_VV-779xVP0E4ri&&A@hnTLJ; zQKO}Hsvrlx{-;a^X=U2R+$?Q0U(_orS6m~ zt4|F3)C+@cnwX+?h=Zs)ar6wzm%B^*QJFN)jhszdd;Bl_l3Y`yI^j~UBHfnXAQzxwu??R~b6?)xuZd7JU z_>qrDsmE4XOLFgDDX_ZnOBD}kc1y1~0w}BFZE&E_vS1T5hF#db`-b+I!dLy2ClMy3 zkl1K}_tEa2D^m^2!Us#fIbv1FMu98^O+l-%DAj5Sg!8CO$PEtwV^B2K$e2;m7;aWF zE=p+EaGJhQ;_QR5{UO8>e13sgHSw73BVrzK`yc1VeJ-~4qgoIN7U6$R9^0Z zbu>&yiY@{74IW=Viv(zz#1JVpnQ;@7 z2GUB$k~-VCg%zN-hOBakh?0|+Bnn|n)CP@LFaCu`%N1u4jm(tvg%-v|LO2I&)E}oO z%`9VU-xmu(Ar^YLhZm5UpzwDmmS`4^sMlo1dtDu8UKQ~4H%yLXh!LA*N{-D9T~C(i zOfC4pgk~RiKof11gasMbR9bD;qKCs~q%#zaBhsvbzRr`M)`jQ)8-@ItpA2k~WNt8^ z0s1^xgEb4|m)D0-I-Qs=tfbZ*pAHwUxr~{Twu!-`d@9}zo&?(5W5`m4D?ezJ^s?3? z{tIb{H2l;7%$@=tdOXq3gQZTac|+wJLKKkbjm(iZ#lI(Ytskk6y|Y|zLJ`JN{^MwS z-1QPfmZ>Z1nL;*VVK`1&80$uixA-Vgqt#A%QQOYv^igjsWPH;Y;uAa%phg-}wl^Cr zEI~1O-2Yb6ZMpnY>sTOfQHf0kmV$z@9sq?+vUfDSgB=+bnpN`1j#`j-EQgNJ0%*g- zxw#s=wPD^Vo#xC0;V<>nIM&S^<7T;K zwV|7I#C>5%oK(=aR@fNUU(Ev*`&fR2YM>7GN~nE6w_he6it!0bs^1e)MvEK=4>XXO zv;QbKg#_4ZMUg@-p;{Q_R3li4ZS^G|ddbHNW?=b(G!(J1f{ZXxXpoy3LUvNc#EC^w zI+?Oaj0Z#XV2w(&4sx6xv~R$5DWjR8hUlXT2mWb=+!uW349MyZllPrBQKvwS`T#?_ z59;=l9%26((h-O7*WC5WML&dEVXfZmGuv4Y+9baggh+dw^iindf~Xk9k%8)i^9rXi z6r#Qnf$-_IBP3RR_K>PG6Rjv_(DEQ}!$u3_u|&HpQ?0!uAerUHru zrM2h>3s{0&>>RUYv2h=r=I>RbK5+;}qo85xoiC1P{$J=7Rdc^h>5ycGI_P#r$js=}ARNN?;LddMSVx$!mCua~^ss95$ADxLKt;Y+z(Umu3_%QxCV} z?)fvu1cbCvy?`Bb<4lZVBQseoGZwvonqz#cHDa{7jrggzsQ2ACqa}u+;i4Gb`;-k-Y&jj|vu z1BjQ0tU=Ud=8U^9h|$aR>r!o}hKu}q50HJXKb$ZeTwh`=bgxOVhKL0P>k#i2siclX=a zhIZa1f{1{QV2$J8DkG1uEbx3?5DpnJ|6?n^L-)K)Bq!(GsfjuD$oG!_wvA&YcYqgp+XDSL3A-dp7Bi^2%5W>D~YO_HD}wt%55 zcO_L(8;UXJTdSdFawZGD9RWuY0I^>LvY8J2+kmthVVB5wrM?TF9Ty^CN+e^F=uhJR0BvQJurFn!v#_Y>Rn2KdcZss78IMNYTh z(;i&U+wf)sP)xZ$@_jZO-t+q0!wH%h`z?^?zkeU4j#OBT@o(}u-{f8gW0K%MI?61? z8V3Z~gJ_Pw5>+C~fvP7{4<-5Zg>{TbYD_&|uZQKUl^XeO6ORdAt9aSTI^T{@7o`FB z7g}l-TrsQWl$mTQ?`$d?Jpbkv4S*s_gLYd)YDC1*(_eE&WhHp5`Tb$`3*vAD^Kutw zCGdzRy9yg$zv5_!#BI)4to|M#JT9p5H}O8NpV4%UpK~2=dhJ~NjOTlx68yX++V(4r zn<_dq+VBd!qhz=~+1sSTm;{#DYvIBGUS9Yz`LgnBY@;^wOtd*tviolVx53s=Tc)7| zU0GX?vC};dc)8x^$%l=(lu{R`$50rPuG5>JeDBWrC0P}*6EDi z31<=0%>bEOd#5RrxNz(M>q7R#b+EgeH2>@3ajd{rME6aHX0y`Rb7#kE?OMV6)zWFk z5yW$;(a0KK!sfIFi(Ky?pnZh~0Uy^(r*S0#@cR{qqVs~j-+53?vXyENh~A3I3oWqQ z>tBVVhN?>2ulrQrZ&&Mt0T(s~9q+&5rd(HyHcF`Hbp|#xR2AtE^t88BRZkf0NiOTl z1bq+dG=Ma$7raSDQWDN$NNYa1GKJfuk&YBUC3LJwbIQZQv%&jpb->q=M($Dyd4|yv zv$aYx&JKG8>p~HGsOu65$D>~}OPlae_hjl5l_*Agz9~Ncpq7aFVj7Ml!oJ_jXOx*Z z?SQ5ot{iUMCpcx%>#~)q*l^9NL=Q`D9Uk+r68NGJRYqk$v#LsxRG-tpQu3Pm+1gvP zeS33`c;Q>JN~>uN%#f!g)R3AEb+m+O--*J91|4Ue*BU1Clt5g1U_(XIlA*pA&FUO zHB97FFQ1b5oKQ+Y!T*(k&a##DzDj8d*Um?7e}pa*&ITYRFI{TrsH{2G_=hzjp7i4= z<7`jlgqRPxh8uNaW@5|}h1D&bM4cj-nPe$0E>%pK0*KJH-Ph}@<~ErY{CHQIak1uw z!%?zoWTC3@-sR<1Lf6c`1H2ViwQYhNeDSnW*>rU+v#yK5m}r9 zvd7iw;BiN6rRyaHr~KSw?ah`~_m4q0 zUFAmN?P-bLPvl}FDw=$OO+0FjYdm~1SU;SRbqhZ0oSl~V6cQ1xbnOX0n{dTjA0^F6sdSi4Z&|+!rV~q>f!Ht(%GEZdGOW zWP-)}NUQ8P0k|($K5r6F^`hK2C>&`$>gpG>+}8HX&Vx=Tc`i@h#S~{P^q8Yzag8j4 zcz=`zg%t>=X-)c#6SkM|5QJXahtFBEP><~EQ!Sa688w=dQT`nAx3ZGu>0XG9(pFwq_B{)$vT_Zc zbu~#kU{;-v9gDIusZCno>4>ZXT3~;Xy}gesex%E5zn4o}C4Ov@0o=M=LNBM?1*p@% zat|H8W`5~cQAllAzSe&~3GjWB0pxfdHEf^%I5?6+*q7r0{^SV+T@J#)}c|C7J`2Y4fho$ zR)Kc|zAyeY`4bym|Hv#mb?gZLK6wT3y!s=~_MM2hl??f=$m9Y?2o4%eo(K4^x<)3l zQg-%R+f_5omJ>PSY8ox6tY_uPV;LTrFUrc-%i?h(v`tYPh8HpyW3^1IyU_Tpa{y=A zCra0%@mh&ZPlDFsy#Eua`eLI`74o09hw9{nP2o~d>6!lR5>IBn65LSPn#qM>vzf8! z5Ne$6Hr^ik7h8h(>z{k6dmR9e5uWy@8Oe<*KcaBu&Gh1V6_);=s9C4_agfy;P@u!c)bRFbOq4z-fhQ!9lZ8@UOel6Zg{fWFV)5aDoJX! zdb%wj7|PgfhDT=Q<3}djZ}qZyV@D<{E(tPiEZWUCpHf^cmSnCM^v-j%*t;ryj&57E zg!vHz{Q2xL6|c|Ez3?s7m9Eo|?;XeK3kfCWxn`>Jw-#9tiY8gl_^mtG6FA|*YfF0f za|&SDE8S}sSQ^~jbHOk!TV-^eflbuL6`oaCVb%`EI+v9lDtby^({f>S!v5z!51jRk z(xPLaYmLh$shD=P08W)$oTI9WQO0QMb3~}RqPg4jyT*FK0?M4}&cH(o1Zb(OVKgpM z9YZnS-792tJ}MiK^JdI| zSEnJGR9BFol?9aQ&wUuSd%E1&FY0y%eB|gK^zC*!JvRk-yS!UsC1#EIwjg@@-8~vpQs+k-*IJCK}$nN@b3;y1w}{CYv9?q%#-d_&*W# zmEQG$Q;}k;-yK20Kqk}ernY<_#DBo0J+O`mYjJnSoGO4eY1(g8^fU;CR&JmPR50ci zgi+MFK4NfEvbuYjaKT*Ze?)A#`C?aY;#eY91SqGl=jJFcj{@9WSaSWRJ;nFItFYqF z5fsB`Ri`Q65yCi@?t+x`EljF&mHH$Pl{T`s2ETr)e;|Vc}|0~laIz!_*=!@$OW(V)_cYu`D2S`<`lHq?CE6Yw_%0N@=24l*l&N_#BOUt zncZG<{izt)Gst|85zWjXTaN8(S)4Zkao)#>W)kq*2M*V+$9)~k5B7692Jb6SJ#T{B zF9dwNmIO6TA{SPY%b}*#aQlWla7*n%H3o=MneXJ2eo5!{n1IrOM}^-l&|0}?3Yk|F zlc!1N#eSAAM>pc5W26U8RrRD=A$)N~IKobG%DZL=|$~wY9M$M2Y8bDMWI1 z)giK!BuHfCQL-XWpY%9h98TM8w|0{+`l&HtLy&c+iU47r zyd3=w!)n#}{kim2BQC>F`xFqxTIcH6hdK79K4M zWvcYx;I;nA0>xw98k+z-&Zw%L6!=-dXQrIUmSj&-iGkF7E&BW0nr8v0TXHqJ#LuwD zbjqv{zL+9fRv2!32qJGR)}fk62kVlop%ShS@p$#wL^+=Sc28IQcK}I3iFB_xDmG2o*&tpJ~|BZ#98AgIda?;5CDA2YeyzSM;HLFDjM$AMSCI2W@E>Bma zPCYqcY;-Mbnp{|L&S9nLC}l2`GM^^MqxkvflWsBQ9~KDG9cl!aO6IR}WA~>Vy>a#9 zdnDb>*5_I0ywt5R?%}Oj0mv#Ih4I_au_xQ3TFat>KB);C~SfuHPpKdjYV5>n=>5YwD8 z|2W6$J#{~QMSjMf>-(j`x>Bp&TG9$0zi^ z^MS2;Tg>|S{p`-J6?$_b2_MD$IG^FM^W4dTE8qzB@HlOr_`Hbjxgi0Za`BE@ja-Fm z=-%mDH}rbpw`@upbqtH|37wNj{=dS&Rqn08#+rtuONMp20rtN^#`1lNn%h#-^dZ^^ z#%;JEPa71e0iO?u=Xag&3o99qNBf3v_8<-3KKK<~kehp@e9uU64O<@aoWe{xW09i#*9mbC7dRv#~XbpV^LZiN1FcVpn~lkL||jU|N9 z1^2@;X$|If&VXp%yqv9Fr>A`==CapK*wbt+B!R7@;RkbqryTi~nT4t$LJ8jZ)5(Hk zJw4vo(@E)L0`GZ{xhl8rq`e8_W4$OFKekh2g>A;j)5)`A{$4C&?*`a6S{3IF2FAe$ zbBqVBEvW+wb&>c&D@p4m;p?+>VB07UgiU(5^I1(Z8p?TE@P3exgdPd=ahpiBt>h(u z^ZXH~b%Mqcn??ymY`P)=IUXCmpGzHy4NEX)e&j!wlESN|Z>O~BlOMUaw1^ZE{*%dY z*QYw0b}0y+yL2`XdRzF#c`UUDR=I&|Ijm3Bc?h(0rN93?zSqjFPkMx*)gnqxViJHm zoFu^WzkKwPb-fCK&HM|L0q-Ot9rTp-psuBp7GYXRC#bj@t_s;#r$=c6z zjj!inaDx{!f{ZrZi{6i54MKhuK9`J@z-O4}YJ;}VxQ=D}DZrNhKEtse$8s#))8auT zzh}T(HqrM`OC7JZOtF?$C)9INcB+;IUtHyak^0lssqs2<`eN6eL7VEEG!YVxS_uyNhm22e}@d}FFW{IT>8|q+2nQ|zk5>Kbo)#>)OI~- z_cT4_y2#6qRh6j8k5w!8f4`@|5Q=DO84wSqGQZOFa zEzVPg96`=ocMfP$3u;LxTgZ7dZZl%XB!?+75DKVGpd6B<2ly|59%slmGaDA?-Ujpc8X+v@<@X6?tykn*4T)v3W{lV-g|O>ba*Qv!rbO^7 zVhTSa8J-SUG@Ya`9LiR0cPosTa7GA>D$ty0@@l1i+SUj{ul9j~a^_JIN5M{=Q0VV} z2a+V_d7kZLka-Cx;2@F(oKd?y%6Js_4rUmo&@>6_xbaJ)!h71mhEjVu$x>y>Grzz{ z-y)RqKqCU(N)U%Fu_+Pl)R`5&JU~L7RAJAd96Ez|;gVAYB`MBJ<|D1)v&L{oM%0Vo z8}Xq@!1S6O5hH~|J2N=#H9`@P_Og29#0WF;-ZqU!heC0ZN+|ICg|H6_d?snZ#iewO zSP2qcN@J&>GNOpIp?=VmA|*x(IwT|;|KWZUVqjv9vP@ZYF7n$LOfp?mnHAlAEo2GS zGK4g-{vJJ#=`-siVNsX_1tFeA398tEP!pFAEqlN)aXX_Gk#+93C}DKWdeHGY$Z%wh zBWQ0%4e_Y)o~3X(vT!~*7`(nP3V*&RvzMFzc|D>oBQ__;S*U5Eb_BDeD=3fYN4tAr zj1Bk*BsZiQ4mBg}OW;3p%v&3MKq7IyPmnF@k8tQPozkpfCf+egtfTbyh3zzPo7WiP!;zq%t27(|`qO{)Rfl-wtl$MdP z6o``IqoNJ=lQxO>O+YW;hLYlnPpCZXa596oak_$}B>-?Oe?UNZ3)`0lMT0}h>Ij+P z0OK0=lP+p3U^I#s8A|a}OYNktfu@V)i;H23RuoYF79m&O<;ce*WpSZ^f`AI?wHg8? zA+41~TqX^ZEc#~$wwe&Jo{$GCGA!?CiK+xIzX6dkj|y5i!ahT8z9FtFGBIKz-0hGN zB~_%a_;C-}I!P8QM`#!*Cs$>J@F!JNIcWpw$VEsJ#Sadd&4VqEgUe3%K~c;ayGrPQ z1inlKAs!NEbuR!}sX^HeCh>QpeQfkP@}}qx2$_0sEspV#jg*wJnJ%7_j&Y&*uY6ku z>PX3n89)JtBWk2;R+greF~T`KofJNBJcWHTkKhA=>8bey>}pS&$Wqob-e{7eujV_V z71c(=kUB-WMP^A_GPt0n|KgHAXD=bI(aRVPn?qhFDU_FB8E=A&v|&~r(IM3QeYhp( zi)YT3<1}U?DH<5q{|gnT!*AGb_8t;MM<={9Y_K0ob0VAzjI@Tq7q62?79Vu5B=uZN z`U{k_0zDxfVf2JNiKnm52UC`cFAL&6a%YvFB|i@}x0Yg3r06ew7!?W1tvNGWJ}MRm zdI=q9G8pPCE-eQzv5XfLiNNMZE#Do@#F;QD&;!_#)ZkHWoUEkK2#uZYo?`=T;HESd zD1{VD6S<%rb0ek7#95zD?JGGr5jhBtA}=%q89B-fxod_7luba_-9RpAv!>(VLhxV= zwPJn%qBL7Kc{*uQ=LA6qs)_wYC$z4y6MDH($5074g80j)}B+4DRG+J$m zYTp_;aqmPt6M@oDH#r(M<=m*qNIC0t%yx8@j*a;ezOS4vI+xauM_QXMiuj|RJ~D!y zT)RMOiN3IDLVVH^Avrk+ciCu%SlK470j*dY72!FZG*uK?@X!vGfftKL&c(f%2`8A` zHA0y|lu)w(GOX4na2mb@S}|K`3SwCyJCx zhD3_uVuK1Ndc_Ax4T*LR_f4@Jcmg`?HQL`tCP8n;hsteCLt#u20hRg{41BGCxb7b6 zUbHAZhLiSS1)&qaWtK9Q=pgbT9qMwhm`0oEJa|OW&!~-85Z$WBga`w)b^0A`dR}yT zk~Y;=nD#Wutj|E`c+n*+-6UZ$NH|2s!foxnA%!bWEB*P}cD7&m08fA)O zN`1JPx=Ab)#3)#X(!KDfo_@u7t}cA9WDxDCHdpHof-de|=-ky)xvTsQ@1x;F0u9eE zWKb6FL|soShZ5WGGkp@%AH#(@_!IX&e3Hpy=-Vz5j|vLKAU$Q?0gQc;v7=pcMZZWw3O z1M*)|Aju$-!n7#DBv{WZ!s1G8?C zqS%pNk*Lbapt3G0wo%Uh={w75lcL5TViA7T8lb=`s_xZ^SDh(Aal3~#L3W^|?X+Q( zGsCWMO8M=Al>HtICD8r8|Ndp-@V(}e>%W6HC&tm>8hj1?J5&D0L)92fB~CG(Tv%Nh zgqpJpcHQR0Kmv`M{7e#!}TUwr?r#mEl*nIk6)5}gHA z2~=Yt>uk0O7s!96i&-N4a9_>hw#e|a?ZFvU8?Sf|6}nQ zMpNNu^0rh}iLwh|MGvgl*$ALYAd>=T=Mxn*pw>`)jYa_YoYF4 zf@%y<4XpwEb-n#dY&Q&$P60bZ`%2;JUCy<9;{E+iAZYs`@3XSn09N6A?!7;sa1I zBqC(?l#2FQV`#9FBsD-f8g)uldwz^i`31A_-*$QHSZS0`6Rl;bYNg-?yvs|l;vdHm z#`mg2YjzNWx1r{Jn|$cd8b1c?2@yvAo3diN-)nN!tQ_qU2Rr#fZmdn3{8SEJWGk)G zpf2wlUkgghR8n_vQntY?^1WBy^^u~k?~g{Cw;_>?#DdVm45%~Ug$u%m;D&ZaKKQFu zaaT=h)n?pO5TC5irL@R#%v#s-FSllVrJQ^r){I=>7soqv;zvxL%HpqQ^)MjdYP*JuxT zrs%fU#USTtlo#Wjui&p&{$`ySCkI7Ql|B_m8$qDWJqYy^UUeH6^Hq8pa6vJkpNyg_ zg`Bp@;T|<>l!c|8P6=f#Zwv0)h@XS+o$b=aQ+3x{86Ub2!Fw#^AT&GEv-Eqt37r;j z?{O6;dq(M$J{2x}@Ncv>i=7)^(Eq_WfAM8@N6PLImwF*^?mrGsZ{cYUsP$LwC~#-@ zShH=j=+UXqhatI`STfdA%bBdY8mTNJ_4=^ zu$tVB!l||@NX3n^T4pb9Q)=Hpn*WD86l3o#7W))!<=~Am&wk=8sI67F@}<@>^0e(u z<98&1)!lwCaGIP7CT>V)Bc2l6FXqBm9PKn!z?ot48h8p_Cb2e}r{zrN25R-%Z$*QV~$0v*xDM zFhy7-Q9Z!Z95=re&cyGUS0-2*yPq>xlvWrvwGmu;Xa6|7;I|K>u>F}8ERpkCsUS!% zUtykNKXG;~EzQlkzbWiAO>EC-Z)WCe8|-1r8)v%*g>f4~nj$WJes7P}+Q70>J>M$tV z42_R==om;!^lkWk-=(sghQt(byh?QawLhQhb%J=BrETa@sLoc-ce0MQN6PCk1jsg8 zmAy>Y`#0;nQ_@-yuZq&G6&&`(=9a{ZsPBD6Wejj5oQ&NFu<@W zArrp8Upk@cU+Phfd3b21jL58;dqfgwC>AA`!gteyoE#K*S#A%lrM38pcruy%S>P$k z(AkCzKig)!dY5oz;n10~LMZ?v{;Pa)QPZD)JofW7O9|Jn zMzSHKiw{9D%yw7Hp-SFoZIS6y(%$RLsUIWm6sgX|yHt=UkXCwXYIz@Z?;_j*J}iMM z)6__(UTJydyWCaBx8RWS{B;}gbVju~nv7l~Y2(s{iJ#v|p;YH!c^A+r^fqAiSvsbG zmQj2A#LbPljY++u#G`ePR5}nZC*j0p^xvvJvsEC{-{&NXpq)1_uLddgv*l1-?@H$p z6*0<~%_L{`dOqJx^!VZo)q1x6pZkX4=-T_s5}Z7POxc}}{t+eC`@u5t^1O#r4sEv2 zg<4$n&xI%oAOHT@xA-|-KlI6H7PWpO@H*)5*15`)ypVhh%wu^>`xze-@RsWTAB?>P zR2<#XHVOp7;1Eb4NO0Fcg1fuB6WraM;O?%$-CZWQJHd5ucW2tA=x zUNzM;o9kK(UMd$@ zdxR&yHBYxOrHctB2?eX9^B`aR?LxU&XM}!jGKkKn;;&GuL5ct?D>7a1ix{lje$4AW zy(!&k>H|R4)*4TMxE(>33zsKQHR98$(+yNQQ>hDVnqJ$A$&7RO zwi=*1=+Oxl$m{m00w4>^D2PrR9xIG)L^`AzTS>Hna;@O_)T+LSHSPYPpW;yex)QOU z-uBxh?S->{(Y$%1UUL@`_x;Ht0}~Lob&Hvj)iEog*~9Rt>oRbZ?|HL6$Kz^6uEXsy zV?W=mV#Iu{<Wr_A}XUbRi%nW|foKgb* zqVHx|LeU54GkYU&lrmMK_t1E6JsUFMj_woYs~196Y_Ts2pIc@|cxfzMKOYaK22twV~x)`|gghDeq@ zdqb8>HH1C&9-z__&zGNj;B#$Q;^P~z<Nti!?@%nHc| zV?8e#CUuo!B3$@&jciMC8NYUl$T*A3)LD*~+Qy3Lw}$6RO%|84v~*h&P_xuqX~A+Z zM13_mbzcnZJ8n5oD~U$9_*8O(G=ZVMnQjwzqN#V!dm;w6chd<^G*f~GK0i|KKY7IA zHD_$;=zc8e^!g!ta*2B1lexIvS$pYY+NsIF*HQ0leg3V(r4w!FjoWLx`~D*4XNt@E zV;|=6#1nl3B|a|eK)M)F8UJYw)3wl#m&!&B@r)=5F5cy5{0{@)p~}$yRFw=xJmdd* z8OLa&Qqb-39s6D6%k26M@}kA+eeZBbcS-jG3i(`_N0a#^Qq)P~=Os%P$}eY~7`B>r zB(os6S+L!C)M%b)RLeAFa1^e?DSYr_L_mFG)9cVMTSYp=YOA z8}D$$#q5CoZSMz~nE)wGdx;egsbNZ_RYtbiGvRdGRTJcbf%Cr13i|wjejV?p1pTi+ zsspTqHy||iiGfcGO)?9*O&+u4*g9HwC#L=1@!2+)uLcN{TH3phkoJ`e;1*m!%T@x>oJw^LVl3>j`RX2j1?m?S2>4@~dDJR(WHn$U_#OS6nHqxq!^brmgvO1jal+Uv zu-q?iAG1_G0xrnPV%MQjm+#Wn730~<#cQ#PuIG*N?@#<{ww$-GO8au)XnfT*m>us~ z@AJ5NOT6Arspu#xfm*4z#^+hFuEu8H)5}MdJz11F1MnOb!CcISfg+#l*4!e#s(e(p z0372B_*QKw{m*=&66s~~>V)kVocbR=c$a=^Ym;_b5%9n$H@A~KCD{dwf&mmRQC|6&CYjP_zxxw?gCIyK(wj~M0)e7U{GRJ=(7TkZqz z{LA4Pd3;?4Hbi5GOa*&c9R{}!i(jut_^Rb*2))ivPD6?(fnUmnovlsrrb!DSLH-Zd z!C-iwyD`00&xMKC#{J1NuglRl`F%`->)&mbwoQ(UqGkkM$DreE@WSMz`FE=?s*Hqn z&}HV$7`p7TE?#I{$6BC>D+^QLz9ie!`|{!3iX@2vPv*GO3sZVs{fG!~U9+Bi3bR zF*&&O3@ss$no>$*?NANXnHt3tjiPqO5qKoPYTPABTgZ80QwIMDuKc*LYu<7q@p)m6 z^7bqZzjKzWEOG&bqO15y$Kw2VDw$A1vB~+eZ7yb1)oUsvdj5V5}RB#B<>2~)8fQy zZ%UWX&1^pq-1D~CF+0sD=i|wmn2au}>;>wF@46c9Wb``QB>#3X{AgV-A%#Xd^&Pzt zc^ZwPD(|wy8kn2$de-;710WE%ch?tRzqnT#vKyM1WxvAxs!uB`COiS(7oSY?)ZKT2 zOTSX;?J+e%nl<0xx!^<4W>&Fg^L{VP*XI&zFI>-~w{zodWK_@1eyzKrFdW0o{=ULE z!l!2GOk-PFOYWVS3pQiQppmtso|`+=m}mk#E!y+)o`zq(5*~pr?_@xIGCjo0GEG)G z9Hd@ErL{+^#y5xAQQNKB1t~q>z3x^=nmpIu>@>(~3ZXn?CZ*c1EJ^5>00velu0!EO zQtT55d{naqKD2f6adfpLs_^2QiGE56AK_WkzXmBPgcai*vL=|&C3(DWql#3Y!y)kA zX)F0^fsLb(-O_Z`*O)|ERf+Fba&G$#34Rv|MajL1+sKttILo#HP_l!)@+mh3?eZybJdsUid-{q&VzoLZe zX4J+Nsvt{g1J{()qgiU2@;ZUHZA+Rm$QsC&`N}{{C3UZwd{#-fwld{_b&L(J_?&1^ zKo=LIoF=i`4b6>U!BVQd7^NyENZwl$3uBDkH^LB#V$G^m=ebB1@>nLy65Xxv`7Z7QHJDk4Tu6W z0$a8)#YK6{_+YdQ6?rT-VpInOj0YEep5{hu@{-mB@iHP}BKBg&Z+H7s6LsYAY<=)= z-=J7GYViKoo9`U|x8&3;tluE-VLLcF5wWm;WBQ;Jknr zEB~%&O@)N+sfI*Z{5sR|jr+dzSNMygp<+zdXG#iOwr-d8MOFNm-AGc(ys&s&1*6zr zk`m=LBaAws6M0n~qFlj%aFnB8h?R&Rjsjr)6~jb=9na6b&bV0aytDV&wgJSUSQ{+B zd-n;)9M4If`_*fneeUwH<8N~1W5)4`#VRS^O2?9h5~>P6*dt1n2&evtOj0j~^eoX` z(y|PgNX>X!WvuyYjxVTm>=yK)Sdk{@*K9g%4oSjT9@zS;g>~l1qvEpOHCg&Hig|pl zrT`C8{}qqya~KP_}pyFb7Q@ihD2W$UtE+i+FhaQED2 zT6xCTRmdBTlO-DOc*eR*I+ST-agU&amflm%ANN?1CjKZ(T#IW2n`kF%&@|$z9O+rJ zpH?;~{xD3Z`F?ElW}9h@7{3x*sU%-H*(e)K6g8q0>e#+eM9grDil8K3d#8I6O?PXq z7)jIUTes-c2rT2tZvbj82x{ON%V{}w%I;H7|MrKIHSR z_0;tY3!Vw~8s&3v8HuYqL=Hd#|T9x>0xj4@RFSYsS3Rq8!FU_2frIm@g@jmpXsVdfpo<1pRIONyPp4A7uV zNgyj~o1ecEwbxWkVA7(NviV?A?yMo0Z=@-yQ{DaEidyE(nkKn1Dm^SwMq$@f3OFVs zY4tn&n@>|ugav`UryD{gW7CTiyFIm1n6>mVjoi!`6NARltTeuqO`XAU2c313RaVN( zz@#p)!usWCE_)|ewtuytfWhAUzKMK&=lok zfxNOIB|@vUh&dJO9mU~Bo<(-?itUfqhY+IC!n43a@~>HY_zDPXCS?LEkY?z{YTo`Gjh>A$tEO+PI?GgTc24S8X-<{sYH<& zb4FS8jnXdrPz7$Tthr@V>ypV+#`tD$?r0ECWNy>Egd-V$m(E%k-(X*|Vo|1*8|Tc1 zQhD6;K;D=|u_swH_r-qMW<^}JOUeSjY-wuVQTwP`rZ9D+8O446uAwkq=9iVu3a^@) zYlE}RQDInJXYNkZ!XN9kp}S9o)`zWk70ZC4>9R}}QsVtqo?pzPlqRfX?qQ9<@efpg zyu$v-4;5BMSWxdYrZcHXjWQRHBNxpzomYr1B0Iz0jxLh$OrfgEk@TQ!it8wfgsX|@hxOS?j z!g}1nikg=$*h)QVf%N@ST7~-Cb&n5yQiY3F8E(<=rlzUD2{6f41<|sKl^@(3r{Y>% zW}r$KGLokplv1XszR((XYi_w_6iahiuU_TsE`XA@E~ zWC)(b;ihM$S{0_3hpX{U+Uzbd%l`siF+y+%;!?Zf&``coBFgxN-@` zG~zOU8j&iviiq(-o*GO>iyL_>mFZLFpF$O*LM@=mOQW9zn~bbz(i2nH6|~H6gRG`Y zQG~W@lDV21TDG-y&#;iXskP0W!w0F+2$f|6yda86Ze4%yt z2!xz?Eb?{t*CO5UX03Wn9E~^)OGCosRh)P}FIg}jGUk23Pj^VGS zqBrkBF-0TrN4U|xEMnv)N#oKPI=C#rD(?^Bo<+{S#Z}r7Yl3?fnAO^N4dIXQR}>?Y zS`GI@qa9N`q57BORz}8gFSaOyWP6^@C9|dK@iAFv$&}W!>mvU$d+o*;@J7YD{OwN( zu%?W`u@T-2qq66$$5q$8j{A!fVs^2mT9uwV z=p7p*$uO;)*Wqpi?SgIR)loep@-nD@zuN02SjVv1asej~9l8g0Qy44s4}ubpEFchN zJ1i8v6nv;wOdG?emLv-vWCYLXyAX6;4^0Luy1<(hST9}iR3 z9TuK*I&Y6=8F}q5mn-jIAMW}`C%bO&CVdaF`R-h8z0Rv;lJYx2EBAZIU6>U{Lp1E= zgBy31N3b~q``@p=Vc2+0wC`w&%_f<27@y%pntym!X$He@$Vfy!_My-8UC{S+n$yfwPl< z_SgL+LGX!!IIH&h? zSSAR6sN57*&l`h8z$g^%7k`Zj2MX+xa=mB7!p1|_{jjr1{U%fG9j~o(1?T$25~ZH^ zQRgIGlM3?*X`ySTZBBPyK-}rI0Nkh-ViRj>xURLFzt!i!kNRBKLBW!;O>BWg$3Hs< z_jq%~=u_KSN7(0bLmqGKKbOg^HHFe-yQiD2)rH={i?gW(erXInTc$c+jZh~`3AZWP zM&ql9S(k4-ZhBNv&?W3dVe5^XUTNsP`<7I_*M<1haLlgRvj~H$A#!_s$GN8Q-r%gm^*Ha8x0_<+7#=0~G1j1#r7W?`Jvfc+etqS>ujFi(>5;k4i{bV0|!IWXLmz}*7k|} z$mMxW?D?MFt~vt|H>*m!%(?|!#Up)9$+gWa;q>*FCN3S3EM)Z!ES~x%OG42kH+X6wv}c6m%^>)fGq zqq8M6Mn~n%2~7SBnv~QgXKm9jdaARU`ubY2#(W?3xCqMjI#~{JE6$te9;-x~l(%$A zb!XxZ647Jb_wjXOMf*;U@0HiybkH_t!*{dV&d7W9@azMg^a1&<`=C{GDIbIo1`6s0 zBZ4D&H(nDRX@!D(Qnh5Me_NN#qB&x!qx_lsT)lK($vmeV(n{QFTtdou#}}{N^ebS1 zvW)^tF{CH0z2xrp>&cOtI$5FBm&KopYL z``fQMYQC>WuGPNV+h}^=?S=K*Bc=0QII-ntcFP?Hg3>cbkH^LQjfZn=-}40DTf?`V zfrOlA*U6XF{!zBq zO$C_#;Hbm6UyLLC{ma9hC8I8QnwHT0tU5oRP3enHk& zN@ao~H}VOEu31UvM1QFB7mI3wNcI4XL77N5$Vm7Ka>HrrbflegFJYMs@^}7nG)92K z4)bnM2It{7lywyok4If+gW<8u!!6%qlwPZn#1Z%0UP#VVh*inB^YaOQ*m;bB64hOZ zm1XN$8J8N8H_A=xnJv@NqJjLwU^Cw(@J!XUm*+@3S7U$cOXIt5gRwZTm8T@ZS-*o? z!q^Ax=CsAuhV5wC$HHslwp1Z8WZrNVE7{_P-x=0Z^I!VV`6g}o*rnzACm=sU9) z*hgTS0qm>M+t-}$t!PYjp*xn5KKkuxm9G3~Hq@h|>6FUmnyE*ak|!v;LX3`4z_e%m zy@ie6-7#fk@HDYX#2VW7@)qxH6ZGkveU%#35vV=T8`$XB=7GrHa=8-YUCHI)l-lKz zdD$z+jar|#$C;n9K%x8@KBQpg# zk*KyhcyltYp`#^D$&;VZYG3~>4G{K)LN|m6o-j;dFnJp_H@(%ty6at~(TCvhJ>)%X zzNbN*_aSW%$QTHPPU}uid~=oKltC?NlK3f|q~{bkglNTJ8e3Ln=Y13OW2SSOGb3~{ z=Fn}E_m{7WRwVwHc2(!b1^%elUD1xmy)1LpTxq=+@~k!{sDwD#7@zZ}LB6|gsjv7|3%w`1(G#dCCmdPc2gAkd z5zgQfSB5L|b5GDh6(kUb&t63JLgUTqB;Pesj_2NTelsDcP412MyoZTrFO%VAPi_6> zm9h(M(DjFV=Oet{E0Xnn*At80<6R}+eJP{%8}SLDSEtUv=mC@Y4m;b;ITJBya#sCv zc;kESPim}m0n{YjSaDC^YK6>bDWC-48bN88kpDC^iJ+ASj7!q1$>KzpfH=B-fut~* z*-(h}P}<{X>{o>fy;A{Es9)LfGx@rnfeP>fl$-u%?8kfdgvrh4D{(!1lKlI-c;p-m zKwJ8k{9gXYZLhc;gG{gVW^xW_Zpx*)#2Mr1qGoKG^Q()XXu`{N%?Gg%=;y)5JAYj3 zU~|fMoWz>uEbpFv^ILyzIJA3z(;4Io8P0Z@ri2C4(U~rFWeK^HPD=Q9@aCfxG9Dci zJ%3jK+GRq9SR8#U=O;cK3XwuvlkGFK1e@5aZb;$*^3f06zZ8yrlc|%2R?{m3h%apf}gt$RK2Z&p9L1* z6MeulMbw(e0@p4DUMjKUMkrujx)pCR*rjR_W^XZ|H<5CE&?qx!aeVlFVU=b_jUUyX z`liYkjLJe3?O2~M?8)$Yk#^p3xwPW@xvIBv(~VFsqdgEU>0HA*a7F&@%bw_9aO?Yh zr<3xHPRoPf&+w)w1iGyTGu_@OYfOim?9J=7mvtR)d3e>6EWr%FPqQ>W+j(AmoaVZd zr3;yz>b~qw5xy1SxmN<-K09xBfzNo!d(p2Qi*~j`w(d49w+kv5cooO>Te>hU3%GED zr`HBsYd4VJwJI&5Mo3`*q3l10!JvkfWc0bS$5o|~v||}iQ2)5H`y#u^88-JUd?G+{ zM}*-SE6ywD$4}kujyb_sgJF}d-%e3+2Tonc+p%3nZkBv>ixk(bPXG!Ce0dT z>o5eTQ=g}r8(|j9AT!*i{+zzpmtmpLu~bLNe^grkT5+{k>hZ?IdRpprhTxXU7JD^; zz-~Ko{px9jc>3A=5AgA+Jf{OZbxWxI2DbH%O2M^3HHD;{2{uT4wYR~n2yX9|t#j$o zNrf7nrBLtUd(F^Tc|HnlcoVkqg?YYL`|Pkc#CaE0)Jmw;&bkiSOT03^dF@{5D~CI$ z0+ez~V#SZIHVM7WYvk@ZuPJXAzAa?e16#WG97=jY&8AKqPK49`sWqoidz8OloY+$N ze!MhY;TM1NsJA` zoJiJCTynm%+#)eCdo_=*?b=K)Ja;hScb%X0wL~1hbzd)lz|L>h%v_P1IwFs&a6vE_K*USl(^d2pE=nX?^zakxFfyQP;~TVF^xZhX&^ zt;ttfLa6-0La20?yL=+zT6aFW)O%WohrVt#D;vrW1$@PYj zw<{v%9JJm3(87?7T?6}i`U2K&d#H7~R5}EO%=AwdL=Z|~^Z7FPENZ`0v_2>}UOsAh zy?!FR?;muxZ`8#{Vdw$oHy8VSO388PVLUSZ*m-*l_KpDBUf$C)$o12Pe!9Q@#F#G-3 z2^9>xqDBCqx3d7-u^KUqaG*GP6+I4Nf5tC2B`n);5Z5$v@5!)#+kos}b8gpi9Q!s} zx5@%Z$W~Z_iZbv+BR>`Z_}-6~-OeiTI6PK|1GYhnzw>h(ku3J$5}3sPZc%{rAz@TK zQ^!&SmWcWMYwM5j3IHjIaDZKKxhpR-mna}`qp@wV&-oBY7K)ie*5wfeKt}-0)$V+p zDcWgm5^bTSy8IN@EMCAtF894WP;VRFm9&Eo;j1haW(?CP0dhAC@tnvNO6OHUW`vzM`b-uMUN#nHZgMx^$X8Xbh^ z_@`jQzOS8I{#dqq>0hMBd}(rD1v zN?IEv?0>|hfMY#K~LNx%_-;#@lP2%_ojk8p^nYjh29ggoQ zzSas6O~?IUL`dyT$pGWyTr@`4wAAkp2&p8m6(n6tc+featy_1>2sjkV>ogrA8nr5_ zIU{wToooc6Gz8;R>Jo0prVpzklFrY zBmMo28Em#XtuEL75n?$$?pKT#kVTzCwXvCM-)wE2Z?tc9MWS!dDqK@uHz27Zk4$jy zzqL0%LZk^DZg2obMhe2~+3X-)E-~ThHwf^mHw^ zpgT{v{uXcV_B4;grg!BFgamJ&??x9ILs(kF;pOi`DXK?T2<< z$e~8BGsaXZu(3CK0d`8`$&T{ba$G(>+w!dTsdj} ziYIekLxE4XGL(>7^XxI>u^Ew@=pAoQNrjCHU$c%`E}$+mO_!- z)Op+SoYX(nGA=8YFnV>j0~1!XsP2_|wN8Zw4l!p9f8x9^aVAfYSPl|NDb$~hOi2x& zO-xCZp3O{29iJ^sNmK5YCPb5GUAgd13D2yqH}%e}8Zvp;mdtp(+v}&dZZ0Q82WJXI)SeN;>mQ1{Py?g7Y9iBo>^KWlHPKt)insVcvZd^ht z57kdU-jGj;Fub1Y+FMHc9mO?qm^sN&%>rZqNITfxX3syi z{QgDw$GP+=R=dfe<`0Y3Is5?MUPq4B#`00FvG#@OX7vGl%q&xrz=>?c&q}WEbH*Ah z@K}T|W6rNNW13e2r${4Q`=1yIq5EGzo_?>DBVo3BH0ABpqURx_lpEh~CUIr^9Im0s z-!#|z7Vc}V8W_#bLsn2<$D63_*6qQ89ak#Uyfpo6g@Ox6T`n9-j~?BDJKLhr^qUi~ zbW(4sQ-S`68%=2ss;GM2moZ(^saak-zxgPcFWo=Pe^M>=Ei$~S_LhECh!_S2G8vR# zoxXo_>J2=UOfz|7`rYSodyXuvtK|F3jpaQ9`7Q^_Hr;PEKXAmO6#cQCd&|xI`6#|L zm^X!yF^F41bBUH#4(sd$-f*IyK~H@KoO*I4(x@m+VUhI1Gy#?Wl$lYCLt#IjIR*Mr zvlWeC(tYvvBC^ZX`}9i0fbFPFVLqzN2Fp3W2#MP?{Rt3kQ;~z3}OIOTV zn1!JobaTRK)TkZ0oaQ|-vvQXwY7BZ{dBVl~4nhXk%rH_)FVqJHJuJqs%zN;Ta9K@C zdMcHPz+O#?O=<^BpD;MRkjpyZo_m ztX0p-hZ>2OuF#(Y&(m377irzh?M^q-FQ2u$0O};VVRq!dk`$XAUm*Afm~y+Az*O85 z#Sa@QP}DrnS(A0}`VeOSI-|OJ{&d!By9561OgKmZW!yX8@^0N zz=0nm>T2WO{&J*CVS7on<#e>3uSDDO^MzOYgBzJ$=Vs~wL(!Q!XQLZsoA1zhFJFF( z`vt5$p$~Q@otyGS{R1SNxQC<+3ZQ*MYrM3U~mF&y`6O`fuQ{Zgtq)7)y4tbj2^%{ zUD*PyY?0sj0Il1K$I%u&fZi)QUMo5Q_bcO_{gBeU4Jo7Q{`uCe#X2#KJa?PxIDwt2 z=jOe#cpF=vI(i3b$4#5Ad|Up&TR}vwngip5U!l)hv|G}EMnqqHtF*);G9F<`(Ifb& zVB!FCC@e#y|utwA*{#x*T){1tt5 z0XpuK-SeCyyDwhF+Fr$4UhuT_`Ep(FK5n>&pHua_`hjq}S{MsjiWsX7N09;V-|xM6 zKuwmQ@NIp9LKC;)nZNt}>rG4R(YN8x+eR0uHTxn2X&(IySIq@J{XLtp#LaVPZ8S~7 zGx>FLbhrbycbn~S0v)hBeH^}RQ?BDk=^&sY0h<$uvK}NZMa8pEr#1x_4@~a22Vj5X2hCvY_p&Y6KYt+2j?z{q*0-`U;Bz0?(m8ZE^B9T1kooYmq}0L?Mhv zK^GjoaK}gDDLZoHU@Dw=@gZX3puiXq2ANPKau`bBi2S?A2M@M7BNu@Wp!`U2^v^x- zzM2pl3AISINa<5)SG)`x4+C&$YWtvh{UgH8vmoUiwpg}o&ESdVB;YK3^-pgSDM5r& z1$;Ky16g+V0gb{4i^7QG9%!szxNEopbpEd&q_7(&;M)J8v~6XwLLg;7o89J(_d}0bzPj)b1-yNDVHrk0>A)gTySbhMJf{4v>v;`qh~V4P+F^|Cv~E zAk^n;jQ;zuc#Ix{KsOxHr~aD|4supeKs3fL!$3P6;xD2ll~lrg0OGHL(vj#`q{7_* z117Gs&2T36A zU@AEFzV>A&?CR5xHA?g$L)gq|tH3pYJ=;t#@30Jppmm@+9&w9kXjR-OKtVXs5106t zC?F5xhuIEuDwaQ)m1t-N28?l__-A5e(FdXw^lzlX%^v zT^~2%4jTjt-U)O3D0qoCaJn3;vEoCnojss#UZ{E0r+72(h0P~CdQKl zcwvy}1j=I*&x-(D;>f^1zWDp0Z;}W%0Ep=Y*KmlDHA?&_1DgT%bb>??=&dAyRe(m< zK%!5?79s#G43KKz?~i$z(dcJ%ePw`d8bPOU^lXwaa#^DWGKv)WB(B+O)=)dYQ25YY z;sg10;R50Dn3`vIz8% zAF!EAU&8*@e)vf2Clab37x{n@O;Uh`NF5|79EJgA;Edt_Ku!|q4!Fb<+zdv~BMvkH zY+(v|G@^?W3o8QhFa=Kn(aDH~g#ZRK_^r%(nC8`1(J9R zlr_>~W2`5A?jpf!TgZ9HjXM=XYWMYLj!jxx)mc_JV zBoA0iokw35ai6;RKV6g3Xszg&&0q#44~8zC;a5K7f`Q#k&JZa z(9K8B{ws(~m^g18P$htm*am-JyUE8I5Ru~Z6|oc#yWFu0?WNc1zK((P(`7;fKKvsztmqk<@cH-RsSYcF6J7G%Bs4{G-)-e^1y$tvwdAA__ggs$O z+QNPX2xr>#Qq7zUzt*^9!s|Yc6$&a}0x(cySy0#_qSb>8AczcS_4HP_rGETtQ}tyy zd{*5amsZxW-@HQEP@dU6YO^Zo_2$rJ?U!5ZU)cQcr$S$K*5DB$x`Dz#MZbiq%@l|= z7cVLRRjPLUp$Yx8ZbAAX5RFmRqFbw69>Ly^kRF(w(}Mgu5ARq=aNuHg{Q zx<%61?C@5vZ{mh5BG; zi4oZ|;6W2IKNOfg#`lJ7Xk2p9ekV^{Rq4m9bo=#Dt^otG;sIGa7(z!1bW}C^Xxm70 zgk;;OyJzxikfC19lhd-A?GGk9MpHIu*-_im!E(~#)VvaRGm<|(vz%wQalhlPKuvo^ z)DAmdf(PaFb8!lF@enbuP5)q}(d)A1wCPR_Wx}1+Ot$RuwS=dj`6SgTbsdspSNT%WT?p$9~T$x5*@F zCGtPYm<NPW?Gm1!*+V&i%DjM(OFoztQ0$CUq`bX?b1y_p2UOlcbeVIvo_`GQ~WG zMO9`LJ_0*HDQYyo%nsZk(IMJRbxD8a?d#u@St*l$E|Ekm1LdmW--Q3vwV$GOFOdxU z*l%3ja*~9vLN1|V53=0sU7%OvBuX;Mp*buLKi0DMriEygl`2+?NN}pu$$ngDjyEl9 z=}HNzRwXyOix@(fBy@%T*O*)3g?c?`vOX(jFE=7H9+^G2`QinEPN9 zh&CgYMTKKH(ZXGIBqUWCv4sjI~+C()_&|EY@0!dGjL&nqh^q2VZ%R4fBIs(GDB z5+m<=0>iO&FMDV?$|YyYAZWZ?N&1vs6{?dfsYs}(I4C9UD3jxaU7*iV*lHjF5pH@o{AjyZ>NWC!h39nH){Ue#w#%Uhoo+TnvH~t*jThjEWNDYv|lo zyPDU%BtsdfPpwYwBjszB$YwP97Cy-a+H{0?E|OBU+F&xOZ0;s3;$LJLsTc~4>cS7y zuI8YYWTZl_rBaGe&~oxIr8B;@te}cU)~nyO`r#}IMsQiGDYmA}sDg&NJX}l9{+L$d zU*9&~^g~ETSeDF*h`$FC*CV+~y!w(^De_QPFVc>!?Oyp(Bw*D5`PqGVBl!w z>zosv2;g|+r{0kpib*UfT)@J~$^VNKURA|QVzLlVFTE?WM_Ly|=iB7Ytf7KFgw(8fJW z4s{0TP%Q?1psx<>r!@;EFx!d@{Ao*76Z`?cT6hqz`xIa;VAjo+DDO_+X{J=YJU7aE z+a_bCiH-($Ipv@+8&*s@3FrPESpjm0Kvp0*NkaF8`vN(qinIHLeekV@-K0eihsmR= zy_dY`46rq;Mv=zvQuJ00>r)fFbQU2}gZ*$I$SnZf8t7n=}}0=F2l~uT19HNF9m%K(O+e{CoUFjzm!Fu*~oDR3@gMBaEGF?v4}DI8m$#I==^ucnpSJ(b4BcIjCm@EIH%?64QqojaQb;rbXejd0Emj;FDQ8|sBjp51)`6{vEk@` z(J-X|f8{`a8bK%(#9T>eWokbfI7JK?72rE|B`8y7m*AybEB=*CBL(k{Gz2N`6K@|FR zN+CDW7K#&*UrclgMHma2+K(8Hser#!kMv2$>5B&WHgW7F`af1p5jUlwGpYSp;OvT^ zCb0fP$V45Bj3FKj_;)+y3!%!e_>*7|5d8%U|96%DvJ=ZtUZVYPv-*mV9{5T4>maK8&6$`H)?6`-gPu}D#fM4|hrAVAH~Mdn`r zWsK;OM85KyeDXIaiXh!ktOKf>_<~Lfb4J1C^4L4(f=*O(M&V`JxBxV`i##Y^ERUoa z-J*sC>eC=ePmvkj_=W{CxGRKC&O>948Qqu$Y@*YT;+_bKiC@g*p->Pzxy`HPmXQ+g z5DPo8%&SF~BjR9S;P`W)09YP*GrDOFDHNxT;+|g>N7Uib^QMmD?|KS4navqxmMs(R zKn0zg=8SU7#|d{Zg}I{sV_{stBl|!qQ}ocwG}uYONY9UuNCk}xJf}f&-RUsuN>K8M zP?FI6)KG+QoB907qEKRpM&__~1yIpgB{sNJwz&MsFhh#DJ?6Q6=1`W1`ZCZJ)KFAG zZ)+QI4o$EyQZ9vox!ud+nXJ$9F#zcQFhU81Mj_!>^72|v&5Cd+<778UtL!x~@uvZ+`6!oT~T5|rw zME92rn#|>YabTM3mLGJP%V%=s z4egJJn zTN>AQ&7aqKu`vX5&@O- z`d4(o5mCvzOjJ8g8Va%g9rZMKzgcKm)Ah>TQ<=OSNC%Wa(6aFC1e;EO$^Vlnb1Iq1 zlMJCDD09M@F%TEa+o4LtcXFDVu7$Z+JayHd@t1%YEJ?-xnDBQyln><#g_2N(IFS$0 z^MxW&2|2wEF7kRq{>3CT|9-Wu@ufZ4Kz(lRd$q2;zv~Z)jvjinF9~&J{mZU^SO^XQ z*G6=qZ3rKEf5HKpG9ce(t`H*kClJvJmT*-LQO(*!|&r~I@(Qia}Z*8xv>?%j1u#N+~ChR zicd~kbF|U_W$M~~D{U?I+rd$Z& zx5K)5{6c%q@~DC*hXrFuA%3#MoNn~;XdZ1y6~0trwCLCteMqyHHYCspnLh{Y-gXa5 zJ8`24Y9@#D9#fhj-0XBZN8+A*x*^JJ-nWpM%;)GQH&gG9UY_;CFMQGW57!DJ`r*H^ z{HbA-V2fVeihVn_`7*B!1e6Y^v23X;(?Ilvyh~Y-C%#>F%tO^W zZYKLdHOW8d`!&NGu`Ek%`DAmbv0pE^>;}dGQi#b|xYW1liq+Jkxqq7v%BcHN#x7}} z2%_p&;AP;hPcU7dHZ>}&h^3(|_{#wGykbS6x>W7|vG})O{nGNkq*>WBhtyfwJxoCc zh`ovZN`%;7&i3}d5@NsBXk~R_dH)YPh$VB)t^;fB#GOB3l3-{Gie#00{Q4vHAG{#&AXPvd)NhV}NQsRflvF9^7pLCC0#JX?=tgll>;c`ZekHq%HlE2<7qM2lv31MwO)n z7TaCYTIao_V7dJ1alXqPvYQimclYkb#>0gL9~)GT<8Uu`)8@wZ#RWQ>-FPmzl2zuk zL?h+mLW0fiaE47?42I&?+XvJCsN1=3QihJoxqOF0xSZl9q>@&2F`FMw+naC;^uV9pY zDMf8KFiO-nc;U39O57PmMf4N#Kc4<0k+)&!uE9^EeXaB%!&Yqh<3&-I#|@@wjhav3 zs3+Z?;!?VUeqU5!#cDyk7)y}7nKnz%+ozo`rCYO;$JJ4p)~w)n*Wc*oeKi~&OCrL09 zo-S>j;wEGEpz=k7n`YH3$ zWq)=HOr-R`PyNxsqoI{oOirtkKqbUurZrG(N&6uD(C-lvB04Hzbc~v@irS2-+A8pR z89Uz}2;zvrsCf9a#h>&W^7Lza7QgO+a^EVWQe`XN+c5&mK)dVrzM)I`F2_o`kntu_ z`$f91yYchR^1mbe>;)ny0*Id@qD$Xqeh^;vX?qNNf{KKPPD`k0K_S#=D|$2Ad9F>M z*DqK2M%nGsx_vAqy;%o44b326)cgXzMj}-V=!w$QRox+^?!jBZt?<(6|7$11v z9H_mvF%?SEG=U*`(aziZMo1rx3PI;VbOJWx8+p7)<87O|6=a4xdHci zdWrM92DX)f4LHwt&X~Czd+np~j9gB;Ie@s$N!V!r7{4b^?_|C?Aol9Ce7SjeM?RH2 zvera%(a%mpwdaHz$IiuUGcm##M9 zL|Yyp{W=v9;75XIxV0n zHQ)quVQrZ)NaAEgWdxe{G#L}0wR5_i7U`$IkIJ656(+5bgtNF6V+}dbcDaflj zuTWeUylJ7NAyW%!n~<=aGlQbRXP-eyvY<*DA$5?HiG!k8(`x?0H7%$W98nuQZPBU@ z6uPSSR|18jg`D8I(ZX20YS=cELFQhT>KcpXbfZFIYPNz7O->JEtBNn|)PWYk!zSRL zW;j`a*N<^Nn|o0NG6Z}%&;U=qBmcNrCL2QkCr}#`3#VUa32bOHF^sFyLph^I0$67i zB}n?raMVM}AO;hU3W0DW7p$p5%c_AOm027X0g)Oj{2ML^(u8jgXXC7TeAUK8EOj+| zK1>oCS`84hYK&rd?>J||>`%oxQVSAJsJXJX!02ceh^V?JFDw>5GRmnms~%7lbjM$0 za&pc_Wq@v~4017o;E3x`$*OEFaFuzw92yj2Hdc|y`CwKg2{c<(lw-^RE;QGYOS-iB zTX-**8U{4pCWBV02y-z$z#k5SwyT1jz1@X+uP>~6@?x5Vm^}Da1>WD-+udHbHSVvq z5$q;3>$pTGt@gv-eAR9OlBxLmO#FRA!LJCs1uedLdNtA#oCAfPmp5@}5f>f`XSHX| zZ?4ag_;X&rsbdrQ!h*@$C44)ArvKg9J9j9_?WL4akwa)qxTLw5G^H}}r_qnm5?Smo zvT^9b{uj~41WO}tj>el=_8g6Fe#<%K;jWcgdtZ2R8pgLUGYj?fhfJk;O9Ls6N@B5hnm*8~Rza`f$j4=Anh0rF57xUh1cEwd-2%hxNSxvE`l1D-c6$M1muTpMtUya z&?2b7Mz${Km=R&OM&A8|ROFZ1T?qT}bk?_3JNi`vDyI?NBd0doBqO!^gk0n+T}GMx z)9b99b)gSeSw8Up4DX)bpWbH$ewOuc)lR-8UX1*6&x*^3@?+!Bml2;E-RM<(CEx9K!Ywn`uU97P^~P(y;39XuO<;D7P7IhH0erAV7cIzNbT-8?1uf? zd;D$vkQ@DMrS0&Hslvy^DvhXp*oe>#T{MDdo#n9rptpWV=t-VAWc0&TmOmosm)%9n z^lLVgv~Khx?t16#tn<=W9yAHrDDORxZF8l8}YnKP%VJNjg=3=r^Jg}%kYA}KWMLP`X(NTuCN$J~Z4F4KEu zbFVY}79uQk96E?23CTG+lO+*J{XTk4megVivo#oXWPGa`JX%}eW+(7;@&eA zS-Py#{x#ser_NdX!Z(9S<|0GQ_Obv&31bJ=Ar3S$b^O`XDor0Yl@WmYbH8{;qS#dvo|u;5h**FrkCq8 zyO}iQ#C+xfni%E8f*f5p*LgmQD=tecHUdC_MA*hAi>`oEP3XoZAU6BpqiB@p zhOQQozRAKPK%LvYdK8~b4rd2agE1`30#@dsZ?Lq1BM1J)E3?$T>0H(-^KBKPl@Y%$ zUCc)Yxxhf3HZL2~%w(N52SzUb1u%jijK=$rvkm)2Bhg*s5-i@>?_U!zdF=mYso(&e zMNlz$pZ?JIDoXf4t}<_Q+rL-AGAJ}3H0E5NmpC9@Ixa; z55h%4G>R&&oYug73}ES8qS$S|DLa+v;=<%1_nR@I{Kni=j$=~fNo1)XupdVC%81;Z z(Uqu+E4Cq&>Xqnqz+J5jE-{;JCn7JdE91=Crl+@8GFCzJ%5?<>*hv$;i#InC}u;uZep}J)*5lO z)8C2F7NsY|b^Aj@T76pmQdN96rZ}278b1K~|38GZZl_gT?ca3Hws+rwg52AsVckWw zL99WHpPn3F-@{{npMRf!Yhns`TC?6zLyD`wq1(^nZ(9jdZ2ChU0*A-n?i0-jjjhV9 z%07xdio){3@(*Pn%CfYQ;m-TW{(s@Q)N`q2iDij&v2-y*k@8?FTiX8@Akf~Sfu?~b z9>8lJ2bzC}D!T(vqRPHXy-JNhi%jz`$g5%YqnQ1Jag=csSLAUN-;a&pRyLflH|*p{ zxLW#R9yt5r9Rx>DkDdbWyUy?1=d-=SByE1&z1vdGC`3vv$O3Ecc$0g!jz{t3N2bcQ z6G~uEDvzts``6O1rOTwYp(tP!a8M&SovNHzX!_+JsMq1=0ck1eC=NM}jGpgv-{&F}A{39v{y+o-Xkm-)36DF8V2EJ6^^rMpez6)6DRjX% zvKTZi^!C>5NGy})8Sr;-OM8IuZ-0Q8SU~vke}~dpP@4i{8x{vLBQoPhPI&`is}3y2 zk03b%;eX-CB0~dLM(=H}3_DZ=1eh=f{J^n|xQ+Pjj!c!^oq2C5Wslxxq~bE;Fyn-M zG22E^Lr@b7q~YZ^1Q~)xmV=7V?o$hgdjPdI7utPfKFECNA9~eLw=0c+`v(V4W3n{A;C|0qrxxd;Q~%{Z z>_03fMOT`2be+vD;f$eEg@q1X$^du-_Vqc=k3(m?>YO|d#NP`qKKW-+S1%%Q&Q;->TmN@ z*|{g9Cq7l4&!7~N|CYS;eC8-CjQ7PiEMpzd3Xj1DNK66=E8rTqqn}yGl|P`pCs(vr zw3F8zb*b3dcleOSJ;V!o0HcM`im%X3UW={J5i2~Gel8uPJ(mj^$0Nq8F5&+2 z%tF^t$nN0vw8FGP5k_#e@W>e|2o)48E&>Q&E4@}K0tJ80&m;#0;056I2fUq8%u~Jt zBXAo`%c1vC@}uOpSV||~0~ZXyFN2%3js_lO1A7x9Jy_A)M;cA_;xftYaLPx!e3Les z;SUn6T6NQP(sk^93p>nZ%z@B?GFCyR#A3bRc?L|bk8o{C%|AF={PDn_whb`~F-jbV zDcH8vM9Dp5R=Z5=4$1b|Rv;@7BAk2e8LHKDi#yomv~~lZ$;!#Bv8zCT5J=*obLKWb zefk~6+G-PYk5~$aZ$c*3wX8>G zAzoFf6ScREdx6d8?Td{a&faJBac94k76mrXSi|GE$sPGO&)Nic7dmbFr(?^(M%rC) zF=){2K_&21t^UcknG@vNJLQPxXrrL4;*)8M2rxvu3O;L8C;%^m8qQfn0Ky+N|5VQS zr*i*LY%Cb84S~BESUQhx`V^Z?cX$3eq;&_YX&1oz^a{7Dlc79w^zOwKQ&BZkHbA~l zum4AE=T38UBPa_jsT~QwvO1rkH!qHwq&EjcF3(|cwyAf06~d3l(6~4Lvyt--{+XAka0n76@XI zvEy9{I8ysr&1>uZISWOwqxL2|6iPJ53OBa;#})q)6!urv@%K-wU%w-o+vK_T(!YxS z=+1GO1{)H$vj>=?j}e;OwL)bfGd{`PP5etU5?&z1K%o6~I=Ee(6u78~aCPqTwjg}@ zBHOnx+>j1XQ1OuE*5T}rl1|_?wp$k)bzXHPsU<+t%lkB<=JiuJ@#`%<>zV1M-B&N+ zH*2TYeC@?BZL}JYHFzFUJ3;;z`s8Z~z+<#avUOy>#@d6|Abr)gmdlfE#o}5a6(AjO zEd-WKr72UUUTvag8EXz6gLF%jz8K&wHBg%LEu8Szl8e7D#x>zEOvK})vjXQ&Mr~5u z##_JC-kglByKncdnr}6NCCcQ}B18>K{O`Vnh#DdJAQf;lgtl7IB7IyWAEu0!4U#UV z?}rfA3|i{xRX13;PRx?mY%BC9_%Ntnzi64yd20qn|w$o@yg|IyN4!Lezq zJopQQw>p>|EgAF*tOv1N1(AViA&OP(X2+%=G%yIlSfy!JK3tJHQLcqtg>CkHSR{=~ zpwOtY$Mn-cnl)&iu~bX1(!pfoTSdHMS*u9GM5%T6+)xR6tbUsK! z>P3hE$er;-v!^`L=u_8=Ftui_hG(VIVuod!FUapoUxfW>?_T+T)Q)~j z*Qp$wjVD6~l4lIoG^H%c{H!5lQrx6nxEo8#$fIdr=Co7{`Nm7@3U1f7FIzR}ZH@9% z`vQDG=cJkOva=$2e$l|Ov@oOCu|RPfLUS~TL=NI-G|~K4#%QS3Hk)0eXo%DH!vF5; z;h^V?BAWhX$p#!Y>W3yAHnF&jG@A5fss{Nj%0AhTWDsLf8S(!LEEbAb2dXcAqwbet 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
    44ozOk1@I*Zssq!qPNQalog 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 )X?_R?&?dN3YjW-_72gbp@ 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≪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
    }1!7TD`sdgvjy^y2dJ^PHQTEND@GnFV#n z(u%Zz$Xq~LEMd@FN~`xjPWy^@?h$kM-MFSpN(dGU&ACDIfdFkkm zo}hg;uR463o@1W1(LC3=hhl^0V+ARq{pIBd`lQ&lysfGoUKhbuy$P=>Vgc6|y4@|r z$)If6FLlK%CY7fwuWN_9iJITL5u^OBnDBzBw)g+y(aMkl$^O?sQLs4}HtyHi`zz0f z2dn9PnFYTCxKWDWW}qP*A@g7AEVy&E4qI<6iq1DpH4QtxyB>55w?#xRN?J}cjLErL zt<{Pqqp|Zms8^h=(H?Y>j<&>#c$P-GVbccxP#XsTCK!Ni%X?_8g`T`apt{k3<+dOb zYhon@aE=)au(rX1-s`D|D*SpxkI%bnE<0m2;+3JJ?N?zH1Sf!q#9OD=ote2vdb4 z-}J#N=^&wqI8DY){Wa*s;t_7SoIcBnWjHPngFvS)4p6(Mm5zm=K*Y{Y{C>V^j zI zmw-5^*Y%QP$dDUAoh@sfP1sdTnXVxCoO=Q=hOw$_QvY}C1TlIVT0j*lWA-yyi^xp# z0mloLSD#Ruz|ZNjyIk&OP|akD@m$>tS9NT2XXj>CslTweSw@-8^IyiMsbZ7$)#Cn~ z$K-4}8+%21Ld-PYsxHmX&hD;dGP{mq1jtxj-96acJUVLZHpcCkD5Qnf)D^ zqDz~rE>r&R6XRZo^tj!^-bwlZ$e4JJqm@;`ge`9g(A8U!U25BCKDV$6+S8t`6Z`^U z@AW*Nc30oxDO)p8q~RUve;p8?i34@V=VXkzhT=FJuh^ECCn{00dFn$M=_Jf*vYz*H z5OV@Yy*scxhz{z$CathtXJq5-Ft?17=aDZ%;JMUWMr_{Z*fJ`dEH4wPPZTuz8ld&8VXw@Y&JhPf^8|NG1TTu8^B8XzF`nn|ZGxjAWH@*0 z_4?Bbu|=UOtAs-)+jEhVL^&9{;ViL~EwKVd*(BCPq-!LXu1d2#HF8rfolEI-3~|7C zMCT#QH=IH`iX@%wX8jJ2;I*`41Vk0nugG$-Eafv9R%FBcJn-Ft^76=5%i(0gC@^|v zi*fj{1tz9ZxdIwI3Lgqhh=0n0wUQnFtD~bkH$7J^1lOk5A{4C5B61{@ZjT0A@P5qm z?tkQ&QB1&b1mUTY8RkseuFP~gmCeb?N@-&wuAJYAJN7x#T&ay6su8>}Dw0032{qM` zNY#8So;G6)HpFNj3-+J`Hnn50t<@}}#9m5OQa?;z)fC86ByryG>4(>2M^}@`)wP%f z%dT3S-E>g2jJ`s2OR4Zo5?R0vXeb+QBL^#$Uf<47q`yd&eX{&nr@P)vRB_iGOV`&s zg=G$x=GHG=0V(+C`4#gc8KpNMU4UW$QVm7aor8_^`|6G8;X2rC%e0ggEtGdGZ}E}m zh;)Kf+Xr)Ycu)2cAIS<(Wd3lqN{AlvZ+snqB(h}BQ$1BZ5f#PfG=qw;s6>t(E_Djn z&ovSiS+(6ZBKf=_rJG-WBK>FS`#fJRh(36&j z|L&o;$#SK?#9_$3znh9!@cgi$-@uNpm@Wg^z{R?9q%n*UTkr7C{y?+8`47E7D~5kB zGFg)<`ixH1;*5i+=x5NYMM6;_Z_M$mZ>9)VWceR(|7-BfC-e^{ND;PncVR(sgm@G@ zTwH|Bqod)3*QooQG1bv7@Bv&KR55$~lxf;;udFoAym9o2TjLM>1`iBZse6*`xXQBW z)L~tv9Pjc6{5i2jxDZvpwk4|Iw5C+}(?i8_K^!_5RK*+zuxdric;-Q9XpcF<7j=i} zt%n}UF-J9TBl5sSuIH<6-8D>Y!dHwLPG*afzGCuhxR}QV%9QXN>haS*PF2NwyE^x! z4@#%q<{?{B$SSE+*zNbchixTtf-(ERty?SvJ3H*!3okHBk{+-Jmo4wO7{|{#kOxz! zBc!t*etU;Ejp+rLBBa5A7-y^Frd8Gcn)gxLYHwdXsaf?W=}y|t&2j5Xt71KI)q z-vcOjTUamGYn-&&)hq=sZlvE_QK~!|C^Uj0Fm0mBHO*9I&SQ)s`DOG7I6Gc;{e12-SUawYzso+8-W4w=3?N6-t zs=1ec^0VvOsl1B}4y68eU@)5^BdP)WSNP}t?KcHI|Dsqw2r|YYalT=>wrVp^vlYfn zWKA5N#1Vfhga@-0BC~nhB>M(UyQ_N}u7tYgzF_A2JL}6cQPXeE%+$F3Yux^3&AiBY zhMV;I!Jyw<-MU0(ZP3CMq}L!L*Uj3B=xS57?*&19#)q33n7~3}pNPjvTd&{Czr4X? zVZee9nP%R!|1m79ShO*;&E=U!OL;=4^J0poeB)tYP$DDqStM4m3eH2dZ5AjwC-8h5 zaR*f_o$VqoRD%K*C%?Dd>pN*rZcZvm8{*b^sZL`uUzk~$m|9t1kK6gh`T1Ci2eU)t z~(2MT27V7?nzHehuw*t>1O3HX&A1{Bvm~Q^dIP;(qOvI8WkDc z*=Rk!io_0urKJ(A_F-LVso#kG4x3q`v6h(>k!WX3P)Kg{vp;u^pJh#JlM{g=^><>! z5&dlQIm@uB)))A?X@g3+%QO)9mUnIQ0$R&%>xnJtIzXFl_p+o+L|E zP@0+hXAv{hfchM5B9cS1qU4s-RZUaPCa6?aEG#1e#2OxI>9WIA4;TezeZ_@A=pt{f zmvvs3T!o-`k1ZyoM! zuicxP$}T+dL>BYIYG##+=Vc4!ayH55U(UXKX^I(1(XGK9hBMUs3&XQ1mzBmjndh@& z==thrx@lx!_1+mxOUc5C-}$wyth@#2Jd>5W>Aox`wSW#;~KBTlD{_!Av zOL^dd5IBS}21&A(*N}>kX|xX?dED(oXqS8!XR`^+)LfgRfK~)bTVJp76r&l67<0i) zQT-`V7)3#l^}K%ofj+G$Ue*$`FM+G*^E}6^)0WIQ^~Gh~NQF*Rs;?9Zuh}pN7-G#O z8zhN=+^LoWJwL}`-je8t64P>jk?Q#;oZpG9qK`yG*n4pM_@K6vv0Iz-n>#!9#%j9_ zZFot6b+B=Dpg)&x8dDo*mJ+QfQBdiT;4L`p%s=1jtBFFw*qab1Wt##PK__-(4|Dp1 zG0xf0Pu}J0Q?pcU7AgzSsc@^%ig*t~RsP6y=f#``2tXoxy5l&IMkf;%!m5^i`3+nQ z_FNBn)uPE{_aT*aqiCRTl3BQgWiy`dDhqP+O zRD*`hSU$Djpjz6~`#6*yid(v=`mO79f3-_Nvq?84iLPyI(8Ao@>MHe;MegPu+8HMX z=Vs4GYB9abcd_|G*s~(UZC4u6s94O{gsU)YC@?|4Wq9bm+E6EO3;uHVZxTlVbh)P4 zMsu?*L{LE4bhkk^RcsY%n3NmQARHv+4!hm%mt5U;66ohGPv?0BI6P(-{d}R&wik}` zd0VWq^%PR(peVf{A*OOOWU6Z^@%tz)A>28semtMA=5k-OWtYDNf4a$JkA6al?PK3U z`c$=dGyngjKa;-2IY%Ll*-@7)&P*glmXSocyeHN+z!HOI#XyZlNMOCwUe=aIZ;uDx z`{7tLy(zL9WD)|{n!#z3V&LFL1j~wSsfZBkS5d)~v6u<@7WD&-y4j|MKi~sU%48GS z2Kl=gr&Q1pQgl{>S>IQvo6gVwRQeO?KS&j6PFm%errf2Qx%Gu+uDamla;1gR5El*5 zP!HyaGb4@*=ds~PuYXE2mcp@z=pBpb>{quyOx#9Z4vD)|F}oUp{CO~mt_RsVm}N3R z9C#|&=%6B1h@u{&oQ@1x4xh|%9`Ty9w&z>Q-2FrYWIE3j7Xro+h5dT1fEB3d)+;b@ zRcHXlmSNsi5r466Pdn|W>Fl=XUTbr&KM-}aYfC$HW2r+6OEH~HXBA@>g&YDsvBj26In{ZeGG3p zMSlY>xFt8{xh3J8nrD=&Fg8_)6zv-1hgY<=e*uI%k?k0Y@GYD{!y7ZLDLf0Pe;El%}JomVb?>yO%CuEGDl%5=!X@3XnjY)Kb;l-8ksR{eX zN*GZ+)4*59P1qWm@YReC(FqHl54JKMoSY_#PTh$6k4n7a3T(7=;cf4hH#N?;HEik~ z=y|=K(k&FbNE4p+N$!M@woRG;cRKbraz8XV%V$BYB&xD(F)p|oWvWhT&XSF3o`3S6 zce4tDNF!OE!z{{ifCeqcR!uuM8Jb$d0vs6okOG)i-b6j>M^cl=&Gbyd{aHG>&%)62 z_H#Oa#dN=^6#1F#?sqR1rpBYwC2dKgG-r_CMYb6av_jBgC};E=Akp*G-_BUxdV~6( z;#N8d_?MMQ%SNURBch)onINd~@KqA+9oO1HPj)tvRjpP!%OhPhok0(0V`OZr)*nJC6cHM0NZkL zpgXjgO2vQZLmQ@AGyw2J--XPdjF6e?G*f^uWv0%8qAS7~*8hdh9mt^JXXCj5mvaoo zo^EO_{(Javef?~P<%g+)b6eWs5lhr3_BU55^^o&yj`nT){{H>*!nXYmr;&D)lmtd2 z4ifbbqPPf6xpBQWXwXPATK{_nGFXENpwydgFUZ^_lzq$lfV+Zb?5w#CG%U;fXef6k zEcG5-F3T}DjvVR^Zs1(Nz6BJ9R4j7{j(tOHxmCnw>E8-`U5hR6!5ZAS3um%{*hqm% z@@C=q<^;+=S~0XJZ$yC&DsdId;Ma7^WN9M6*^j*yWYVpIVj#nE-f{z+mpIc5{_@YI zKb8JK3OPs-ilQ^p3daEVg|f`Xy$)TvH%BM$P0$;sB}z|o#o}q4&7R7>A4R9qX+NZ0 z1_4KXuQ%E}4)<2Z!~qBX7Y+Sx1O`RtGefniz7lAre2r%YV80*~&$77sg7|R&Z5N1M zZ{-kr4!|Og{g$lqB*5WBQ{+;1MU2TpU5T%Te?VkiH7sq@_qh)#;jIkul5Dn|$&_-r zDWsW#I}571F5fmDHyxYS)|a;JCfcbJ?c)q`yRZS z>;;YMPd<5d|M2kY$==@8+2!TM#jEShPIG4FYPY&sZ8Y4g?mI=bOThIY#+})!7q!OX z+0TlAy(Wya*}Q>Rg(&^5^jD?KbC(S;5dCqA&Vw4``II9v1?1(;&^ZPekO{ zH6vM{Q)K{E7*wdAK^a@s!P8bAg;;C=7DBsV!SZfFPB9gqxVG~Z8R%5O`XTHo=BwK% zuV_4rE1w1tf$oYS0=le`cq95H-G=$ZP<5g@B^zTC77EW0EJID!iTO(tdP(DG3Hh$e zVBi^(@!U!d78WacJ!C}HnB~>7o^Mi=bHYN=M*NRaG>#1n85x?PBUhJItg~#1XUm8| z;5n?1tRRo%LXlG3;M@=~JXE^UYJO&BwY`zs=yvB;=Pt0&{uE;WNDS}&hu7GBfSRs8 zAAr(S`|GmT^v?q-b|XRLbOaaSLA0I}YoN(r(=9MZ7(qJa`G?7H9yMSF0rv#Kj0QW@ zf21;Os$<)y z{%+Q+$F?$=NqW^!(MR@!RLLgKQRXdyGFQA>_13YiD4xNXx3spfwz)awO`NQBv@QU_o(@{-Ri)O@F09;&rM08mu*r2~*bI6U6TP}oh zwo<-@NDv!1L;ZuC*+^`4CJEi?Eb!z!5z%+h)6}~e5$$jkY1`4NkyIG20$)cyVU?(UoBqoX^AcTP`VfAspfK)f^*%f9%xenfW0 z^geqXaN`@0#-njx@9PXGUnzUZ97yGP$vHP{dWrsbSVtR^s^t>IX=Tdr=; zIJ!NZF{oiMx#HSL<#1lHei^tf^zxc7Do-h>a}6igb`8~?u)Ni>SLNrUmhtpDu!~rxNzvrz%8~fFXMII1B3Y@nUy@EgJ|+FC z^x5v=VfNv}X*ue(0Y3?p<RtYG zy@YHBimoJuHh>`#$!%D$gAq_FO269Y08b-jP8pFLQCW<@aM%-NWLN=8 zgvCxzOoK%@?bs%;Y|go`acb0U?yHL&%vy#sIRWc12E~}#w(@x~(7S*PQ}beb)tLpx zgb6uUB8{4#$)Jf0cVDDyhPhrqm32dv71f)7GTDTzg-Dg7qG`jKWpZ#kCZ^C2G7d0N z2TFcIA&u~umF_W{Rj5?VRWdyJ6m?3SqH|GcVhp=z!w%n`VJ_H39opsBVgsc3%?@r^ z-phbCQJ$ffSUGEAf#OZmovOhG2w*9(D&TvjZmB%1Pg~ypjS9*gpFTup3*vHOXid|d zqq6tFCZ-{l0Ug-d+1{W3BsDqmnC}MND9Ebia%;Xl+g@91N%gwbvMy_r8%!gr>SzN* z>s)0}YqZiH;!7XXA3o-bcMaDp*6(tcR`eL;fahV=%iV>CN#t$8Wt)F!y0u^#FY1l7 z5%KJmhoBg-3iyWM!d#s{{LQJfwIqA%U1co?TpQ@Nm|sRVrJJOZtz`kKgs~ z!_{RKl2gHa_Y?LuAaY$$lVSMW|SkEUtS#4W;Zt3b{1sM^#kjw=Vn7O<+f z#f80P8_Spw-L6`?-H1%Lom#3!@JeXlNoL^JpO{6RBc9(W#YybgC_gc;$`A*4{i-Qs z^$j296gjKg1>G*2?lj{TQ)FD_c7(V9aT<$%k}9Q%e%@oz({wf0CKJBvHyZ7v7!|A4 zc58KEbz{S7TbEMcA-)<)W(=aoCCRWaDc|}lPR)NOPDE>B_93Y)!TG9h5Zo#lcv0w* zo9=Fg99w>D{2Q&&KSq6L>K&uJ@;+rgGHoH-VrFut%!;`gh^HW)2n<~3CyuaYoUM6m z8RBBrRGVUVs_Z|qc|hSOi(IE#I`#WUY1I>@`x~x_-V`&A=Rvhf$ox!eiu=^ORH#%o z=Ql5zw{S+XSajA=IH>&gTMCDl$$wQG`RW7(?3CRQQXUG{9P=au&Rw0pH1T(=O5X@# z|5OD|Kpzw{W={R;-k!Wg72^?&lE{%Q6n{Z17^84+?sRjuRi-s;Mgq#79=T zKd+}F@B_|n7Nj<3@jS9Lwu_5YlD0Q?*LSa7+pbyG$@a-5k)_|G)k`1zfmbeW_!(7o zR(qVU=NIASR#+Zb;nR`h3U^mj2)6bJ$!Xcpo2G7oH}$8dfk{ZF2SdpNk~FZ!bb$U}A;1Ga+`e5%yYxHlN}@zf^=}5R?*D1k|X; zI-cVHUId-byyST*v($-?L}bR(@htb74gOm}kjsONXg@b4t;%REC4Q`5_ zX50Ts(Z-szB|R-|ck>V9{W)EET8b;{>v1k91(p?yruL_wZpE#ivFxbjj5C_EPDRdT zhXX)EIn{F$;hmnq-FVu*V6|^L>E)8FIIpZaInBZ<3^2*-JnkwAN22#+d6`h+*lyN# zK`yLKfvFR#c+1LT@8pJiv(CWg|MArY~ zw9`cF*EvdeyEhbNuVNVW`ks@g`Eq%0=V0sL=xAYY;b)X^FW>Pw*l1rF84(?vwev8*_7Y~#-g>(Urvgw&j0#7>rF0D{_d2njZl@ipVW1Qi zVmyOL3vyb|h_5bN-V8C(>S~tB{vDTCx-27o*HJuVtaG@_?J+Uf()Q!j9?kTo;DNwF z2WwfQH94D|a%bh>kmRF~i_x#LmjOG8&*q6sha-H{8 zy;sMpdPm(=-PQL!Jy*|6Pxo9qJ3ZH4GrQd3?sAuVaJgKO)Ka8qk|MbRtx%9;MNVW$ zfQ2N06DvLd2MG{cN#ICI5GjAfupKQ7Bmd>EAaN2K0TmcBufBCXEv$WJ|d3MOcIQCZRo%;0jp?COm^lNa0lYM2!dUG}kIu~4c6t?>4aL4U8Z@o3s5mbgvVRt9`Ba({S3o;#NSmU0~!E*9-0D$8?@W0lVR9}^-*OEibdT;CSaTJ7xxYbB_zc15d?_#ONSCB1qT=I~ptDt~ZI*U- zgC`FkJP0fRo(rCv3|1W1$dApU8_h30K9!?H>S#pKHm~aj=!+;WGJ!X6<2AnX)E{jw~ z@`>9>8JcMqzn1oNMFxt7Eo?d4nboy) z(X^G|frBVgp?T;cTM=iw7!zGM=7FUYdr6AiFJF?bOBZ{k)#&Qd`hce8F+c_@I`z`cr^!8Tn@aGp8N z9-ADj_iLXz)@JNH_dUm%Nl;B}i8$S!x70lzMLtA2 zd9cz*R_r3P>{Q=CKaWeX}Rl*GhlKxor2^?Huj9Ek!zFT1J(Eq)s+5oZ~u-LznE&*lbVP&Xl1 zI#)o>#@3~KVy@85*7iz+v%Mf_k+#HbVe9DjbGPr^Yv~IM#a8iCZ-8*?!(Dh{9B99N zdemBQ+(ACEx1+xZPGqjTVn^-I$C1A)!>Jhq*SJAQ*AYLb*tzP~xYwC>73-qB=G3?V zizbDBPJ@_{QqYZ|d?OEv9F2!G!=+zL8-k4_BL3x4ym3Ww3|quNSFlr#AqRxf%0^mA ztwQ4x!653m4ckG@)<(9F;qUvrhlm*VNcKdjIb8*)!eM7ELFGm=d6%gd4TbMCMDhBf0^tsca#0m7r8&*>gBm-xwut`mY!U_`ub7qQLuCb zz|j)CvOnLvb0ry{HpqH8V_h=CZEgM0EaP)g8~t&A$J74wQyXQDlf{E0ehM3PPhumz z@eIETR0E0P68SN5ad6Q$OCHEAyCs;ECwPw7E>oNb^MM$zu`fogiJ=)a%Y$|D5!cQE zSoh>OFnyI9*c?as-wg%M6w}QkW;&6P80uqO zHKa@f#uN?qm5t^4f1z>x%o|+ms9}X_GH8|5*(KskTc!wO37LAXJ=X~4f+%B}RRgV< z+N^ zR))AmqFY#js%8?ze5gRjHB`p|NHmul92Enf12nv=s7_@4rfEXOM8tZVK=+nyh=9F} zF4>x+;#FZxl6cy%LO?`}?4wcY8=Qhcx`hopuR9v}oV!ZBPe)Dl1fPNwnc?Pa~a)-DpbJR;nRvy<28BlQ@ z3i6?dsAxztUIhjKnuehADzb@t5d@Wg#JjMEGelF-8Z(-K89#?Si#D zgW003LSormo#spmOUW-Dh7xaiMVh$aG@6Xh}c!pZq^*734{l0wAs92c6P zygS^mSNWGk+&P(5nd}mjo}!KW{EN!$zUV)>tJx1YM7ZuI^Z?Lpiwlc%pfM9*G>08u z82D_4o0%MOnXOieTEJC&v&kQ=MgEw*>ucN-re3y?N%~vo`uYpGl@}d554cq43)R$I zD-5sd# z*#4!7ezUO>max6F5@hG;xO~nhZO*;LuTt+y1v^`= zX9sS#={N;o#~?ruxX}#7Q@!um99m)SD)Sye4Q~7eHR1f2-p zAZ2NWX?ZAKslz~o=&sr!-wR}BjXCaqYOnh!H|xgJn#=0Xq_RD)r2 z4gj4I;}!)04jk;Zb0qOxBLECuPGjpOq<{`gLIJ}es$ z*EmDP%oMSdAQCs%%!e!X2N=2!91SCZ%&v0}1Wk^o@;R(G`_XnUiHrU@XHcR)E7QX> zYKj91j_TFcXdLMvPZ1?swTDOI^nBU@knj6|L57CQ|4bxE*Mz-_PJ_)XP2m6nnTXn8c<~FWlIk9YYXe8H0WO4^aHHlB~Pjt z{TXU5I?PfBS0Uzjq@@D)gSDQ$?5JvOt{KO*(DzqstEWnUM%jVEfT~WP;w6EIXt*T3 zaSZAvuJ>)3tv)YbWu_wAE9Wq}hFA^~&r{Y7A7=!nawKOTV7_NtEy~wYjd;2N+*-n<5ju|A|13;oV=((%k9`?!zdJ+s{4KA<4yTG zgZ#CB54pB6tJto}APmjO(6tV6VhClf>gL?s_O{wDc~FIBr_|J^Cink; zHT`5}X`&z^iB$il;!4$$1W5>j- zg8rcxtDO&r)iaZ2W_=GhTC#VB5TjM@fuP4U&Oz=_oc4^I2Q!NxWU44^-f(PAF?7eK zs@Py^)zS&m*$=^En}HRfzS?<$dBS^!HK8xjgLzh(FgV2l4|;zE@wO$G`T zXcA3?^JlTTwknbvQ{`#!jpUaozzLH@^ht@-Do*X-+c`%>x zWYdGGO-eXb5C1kHP&G}?c?dwr2Klt9&tu0>4Y5-in+2o+YEU&Y6oV;9%No#1=}?OV z0|m{31LHh2s?+O@^430uF6U2Is##ruh8q49~#WYU&d?|5K(pA)kpy?EG9K-~Wdb^Ct%3hL2>LN{V73vc&}HDY@o*l;gov z$ZyUHKSvqt@Lxxd(lpgFET%RCL(%wA%&1-}Yk|lwHsxlT>X^dd8h?F#vA^-*`6=RF z*kk?=`O{ywEmc=_(X>iJqI-Vq!YHSzG)F{|$8=4d6J!q&5c3Dkcwu&yC?=;D#Y_kl z(N~=^AWNmIHHfKIcEm`xOtSO<8p&=g5kdHgWOA$45VhN3Y*lcvI2fWZ2o0Rr z$#$m51R)hdeTQ-X4gtjXe`ts2X<=&HgPVocuDm^+BN>X zszBnFXDIY$W_l2=HyY~$cKCm|z3p4uUFjCL2W!3fx_#lr=>FE)h2?1eLNlsdpdR+3 z2woUp0DDaRMi^*JKTc@H@pX+^hp`ag*bDl7kt=~k`ifu$x}u06ySFQB2K?*IhMxFt!9b*9y7dY*_JOO5+X_=)eO@zT3d}AtaH*l zH)raXiECp+2Sg{%i#AonmZVVTq(GqtbIu{6L5ir`z0))GuW|E=S}DCzr7HR|cK{&F zbAROz@Uh9QYMbMumqh0NsJv}8aan6h<@NLD%lB^Y?Uf6)T&>x>SiX41NgPRZLK_BI zP5l?jcP!Aai=7Nf#dR{9>|{ZhA1zy^E_#>oMOWikiTPZ-I0MtBClW|pyrw-L(*!2m z;ZHZP^8#Z3Qm%na0S3M*(;$az!!YKTiCdKa08>S5WAtwBh+ZKkOvv3qYu%UJ)X+&7DEV-XJ_``H~%ll?2B zOv-Tb(0F^<3HcsZJo(X9T|Ab5_u=xwnI>yPxUu7Sf7_Gpp*e# zsL5>Fw*1Z#cJlIfGnrC8U(My-H}$liGDW@fE7$?(6@dJ2TEKA`%vC5()}GpXT#y|X zI*r+bl7X0OtnvGW^P?Esvt*m?B0uAAN;i6qH_YzRT;uifE7>c($LAlv{`!%W()FVY zFYUhc$}2}NT)lhs$`$2kvcze^Weml*bX@0jQXJY?ZhrE_4>B*&ahdm6h-VxU4_*H8 zzOX+Nxf4)J17D27n5GVki;L9zV}=ujChH#20N^&D?V-?z0d}eA>k-Yom$A}HdjI8N(#5qOMV|E%s5GhN zFU{yX{3Tzg<;4)&aL$Q7!+m~`o%4Y6xuva8No~%HiXSL-fA+sPiv6R+K15%;FK9zK8j_7^uEZ;ItsznV*- z*h``WVn)}qWP;tiv?(@skthPQ%gbPDW(Ek_632i$*F4Cdv2tT=)TK4@a{b9B{W&_i z7?vcW|AJqL^D+Fe6LI{uoHDbz=!1HIx%7akvv+(sr?g;AVcd@XO1ImkGV6+{{jzR- zYbr(bSzr?VOO~9ooIoM69DW6)?kE2I00N)kb45QV4V_6#~eoTyJ|~e#wa?dE*vgaIKS_>HkXRs!P3A zC>JwqnX85$^Iyq*ynWDYHkoWSMG<=)+34GinCf%X`?`lLopXBpC1?~}rl^#-!73-@ zy`I0okRPDpmpx=sU2yp__uKzf`V+}YcCm@r>RM8BSIh;fnp>Hh%Q?QE<5nt%Z9A7= zTwKU4OzPDs9h;ic*4;b<_ya0k3Lm=4CRUFWkhn-TYHn%~>inVFd=Rp57ut%%iJ zsdC!efOGlh*4O2Y7x{&{zMg+6C&HM@BDWdsU$d(9d-G!M*B`nZ@~En8ZdP0DKXp1A znN&Ne-{{Ym>+mbwln$l)y`_!amG#Tr7=V=KEES7Vezsm;s9NjKt%IMf%g^%i?t0hB zSF3rayS}S6_qUrO$3DupjJ1^>H&Ai5jZ238P1|5T z6}Exsr6`p%mAC|uVFN@sWpg+c^r%M&aW58&^D3G}SU>jlSDbhKB*|2T z{2c7}8rfndT9uhEsouAk{IT>c349{`OajGKCV8I3R*S0MnA67v_TZh-Zt3V^l zPjq1{GZhZkZYM!SZJ3w3b^A5@rePbhb=#8J?cex;6}-Aky_@cC;DU|C0`Y2K7KfUq zIEBj;|3*H)WN?LFwGlP6^diaS9$7(|dQDY^szQL3vZq~G+NFw#1A28ZnfU)opGbe4 zbm)gtiSx}?FUUpm{6?o^%VM^@W$2!_WGqdbhdy}}=ip@SAgOU44d3h|f{rLo?Z*j5 zY39UM{Nd+B5$TiU8b8M$e?R5xVhm5}3z1QBJ=3)616Q` zUn~@+%H=n#04WSRg@cDg-z?P~;CdkvOSTVvqFobL7!7h`)6x~`n)JN%NJN{KHaAn( zA3R94wr4J1P8~k?TM1!l5B+eK{w8{PB(9+!PEl+!@ zoNKk~(#FBc`cC)DIr)>e{AK>1BtJX&n<-!p7eW*@xAQK$(`$}tthT5)QFa`0(G+%8pZ=*+K_ z4dSkZ*khanz&CwA_Win7_jP_Sn^-e7j$@2LY))VrF}+B1L|v1PdQFweiYKk6wzdw- z=k{jX?Tb6vXvI=wsz~QnR?bNZRjpP7TgjN*q14_PRzJ1J>Y>J`flpZ4L@tvPT=aH# zuWqwbb;T}MCmI90& zIW2a3{p>HLzmoprz*fb{rmudr#@*hcPj}kqx3_IivDP+I#hIu+{mhepKl)h&XxpFmwrE%eM{4|G z+~v6KuE<&3Htcuqwq&*{o^%ZB+Un|Rm_L^@xWhkS$R>}f)#+O8y<9H8Y3kI}Qp*v? zG%%Ze`?9M$L@%xs`D;P)Dy~ga+=Km>M6>b(G&e6TiGABChUA6W**Hd1#i}$@ZMBf2 z;zj{iDp{00Rk||#W@O|0#_D8I%1M9Lo~TOs?dI>;qU@o`VQeEY@ZaH1Y>GB%^E>>{ z>z6J)Z`6FOH=4HS2&4K}BHgp~)`RS3^4Pt^AJU*(LQD_ua*upo+LbPe8tYuSOfK|N zy-tUmTisjT+9HazVr^^?X6pJBnfjDK_VHiC{P)SNgo7CcVZT;HuTN~a77@|T@pX1P zZ2{;OIr><;#P$8(F#olVX^APP3Qa9|w(rPO-HNJ($*bqW zwX~5F3(ayS)7D)bmtC>vUd1wXnX4)e;&lY3$PMu9(Sr4Tc(Bqoa_)dIt~i7Pb* zRZF*}$GvkRPBulhB2TJcxUsW;W&6up|9I;Uwtl*${Io9r{*52qcy{BH8_KhN87W)! z8~fYa`#0)a3et0jYq`l}5)f6DiWAw>YWE+-aryN_sA@NHv=Nnj|>u-Oz{dzcFG3f*Rd? z^hkbXKI6RtlL!YE7L}iov|Rqd($Z=aFMdgp$>!8NgLZLdzh`2m{|k4pn@_}g{(9g& z7DfL{OQkf=KTUp0lJezDJ(EgVdFy0YXY#Z4_eY;xYdPE)cLJ-6%BL6a-@jjR-20ZC zEx~Nl6B|PAVwg5{^_CNTjL8egF+w@}#Y&}FDqV+0WzBHj$@?&DRiTl#nZgVw@@;;| zKd{9{xCB9nNJ*d97q|yJkgkhKk!vZ>#wtSg;iY0xbgA1b7cbh5B(?4Kr*4lPOeY>* zSF#Oj)IdIBqW!+>=wz?;;8hItsoJn1TcC)xC~31pn3b)%ShWb^*jS1j*Vv;h72654ueW^+W$L8>Ca9?Nrf%EFFokZ-t}2` zVRou$l*`2;+rE66aRIV(TU(6Fk2Rc3jmwYS*w{E^hvW49Q>ueLHEudlk~khYjfNZ5 z$D-NCM
    >?u!}@s^Npb48AlmORKYyv*?yLnSWkS(o!bV5O&1NJ}F9hl`8yQq^;o zKq(EJ(3Gu~7}XbLzZ=KJe7;&JTuD1ri`*2^QrLMcVyKS+de4SM7qX&FO+_^k_5Kb& zUfHPq%TUJWDzg@3rPjgQTC94eTLMndrq1@O8o0ScIc3zv|e;mrOIm`TOJ67b^{;?S}391zI?nSURx}SwHom#_}NiLw^^3TO_$U4`G%ym`oKkLz=v;C)BL(33YhQQK^4+U-IDZow z$y}KKmegt$rJZETs*}qt&M(g{udXhdl2l!+o(^*Lqu-+fr^K)v)4Tr2ieH?#`Nk@o zldX9}3~pBynLBumYJu~v7*m-8c`KD}_AhkS>+YGJg8*hQ zyS>8`A-%>1%4zbwQ;v$6ZF3wL63?D9wjPhLQzUb4&%^YRuY@`k$4LMW#ZmX$4K zaWlU)Ik`U5&hVyQF1uD?rWqCrVRJ@9^XZI%uD?IU6n5S)rjvV;q#Q4&C|<&&;ll78 z_W-tEBh2_ zXZw>`p(9NvSW~UrOEo-vKO&V9GskTI*M#NJd!G!=$xF=!9^Xf#TC&Ng?FSAwv19xF zx|zRK{;N`e*nSyw_kP&fLVb96082o$zdRF$J1dJ;B`;9iGK%ByX&h`$8ELPKzqFL@ z@bt{gz`^U)Kp&N7ESzAcN$d7b$)TUlJ?D<>k)(k$4J{_*5)Ey?FuWe3)#~sibYUo< zD2TKC?FYb^j5@)kq@1@W9aauV7*%oS=9H@;Ozh^jDp?#Xc@DCfXO!YB(!dSovrOZK zuz|{MRCvRS9W!u(RZlW0>4;8a%~gZI!}UT&SeoMmC1qd=a0jp=ece-sw$-(|ob~kTk_?hy(QBane!?Oe z1|1RIs%`t)a#mNb#L@+VnKLynQqHZ4UN= zGdJkjSDCLL?amk-HtdS;uP$vYZ0zi;=8|M`b@HtJn(6U$_3xODNvw|BMtiqW7$Uw8 z*ZJQJJYpDL@haLaCfQju8u^yZw#H%ASE#G;wFgblQVr8}Q1K=ldq0P2cCUZgwf%{N zrF`46D6e$kkt%LJu$aMxj%6ELV8mUOtU|OH>|`8lvya`$lIg>C0ne^TrWxC8+kVb= zbj_in+eP+QDI<@(b(;embV=qgc^GDjCdTul6{ch|-fE}5@AaC;Qh}VnGbioZ83$<& z&}k2ZIOjf;ezbKF?bMc1ADXqco#19|+rullYpP=+N^GkU5-NM?*E%3CJtG zD%YPL6}_=b1CVd#=Mb=V zBkd2C+ILlPN2caqwA|0%xpQX{1k=8tu9hW!xC4UO7tu5Ibsw+9|86ZZm}w+yS;?jf zFrr?+cEKTyxs@0fr?KU6+i*lQLo{UPX1XZoM=4ibTNzU>%X2W)@Rm0>7k54QoGPuc zC^Dy%RR1TtCa`~hF*r}GUvkr*9elEaqH6{}^eFsyFDSHpT*y|Z&{QEXm=P9mk%Obp zQeg+-_(dwNT7gf$0Ox>baNbTx8uQBTg@o#|gyqeJTm#?(6JGclMj` zT}Gg3W}6Civ{D)uuSVwh)uAGqT1TV-_F|ZMImb418x8|bk%G)E)&L`1m2yd3b!237 zZD(a?e}A)HE*57tXU@rvr1$%MXK=cN>!^I$G@@R+`TR$b%zrE9sY^vBU-ED!uC-9B zkTcK{NVfoZeHW})~L|O$^`?gg_AFH99EiR zH^*^qX7-{i{l-J#SR$0a92iFpyAKgO=B z*44Zq7(cyam^f=NgfJ3K!dMnZR~>2h$$KP?AneAO|0PKEKQqOWnP z8!E|s>P6()Cs@Z>EgO8yGd@L2x8$Dx>o4hEZc@F2QI=LrE zdv>m%Rl%GTtsdKDKZCX{bELH)C!F)IrR8*^K7lRYv&_KvEi>fdvM?<8$r)Qyo#O0fH>dj7DatJ? zb!8en#F~th7OKO;Xtq*8uqLFJt#YBTf>wTh9_hot!TA~TUJ1M$9CQJ|Wqr*Xy_ zTf%f8c3LQymW^w*7e$17bx{)OQN>lN%8+s>`NzwxnxPajalACYI=i~Lxx|DRTN*nz zXzF*o9n~+SNslBAg?oA9bZ$^L|MB66{tsJ(0PZt0mv7kq3zm_m9gEy^f;}}VsWHtK za6wiv3W$Ze=e*0`7&s|M`HhyZo3@v zk~R*9yH&8wN#3^-aD-+Ps`iT%j(o@>v}bx_~O#lgR9rCt00ms zb?b~FOw&Tsr;&TVslhmt1nNF_(x1pg`iHxY_PaP@78pQn>0?gtx=YF-b-fTd+0M8r zX8_RxiwS_;#;~W3W}1^|CeMZDy1~b!oy>y#Qp&!b{Fv;0FxU?f15aR6o^=AOa~uQ^ zUp84NG#O51S^`CpUxbrSom`1xzYtBT!$H2ntLD;1t&GPv5XsllMiI|shO7}|O3DHA zKhyI>m09IcS-rSd=jNzd4?A9mv3hPS8Jn7_SK7t)=xDu5LcCaCJcC8gmffG0b<$1e zoTkV*b$8dl%Q!bU8<6qGP}xZ5j0}U!QbIO4;d)w@#c%lrKo+ZL`&$LuUmkLTe*@p+ zCAa_wjafrI;>e)eV`6^~19X#}%&0>nN*dRR;U6mMkg_{GysHdpWNoTRWOz4u zJSz0K9k^$V2V2tm{wBSoKi0|m+^IPB=Sb?a7caI^jZ>_OeJaxQqum6Dl*{BMe7wnn zi@yxwaX~!1$3)~(cOw%F;g zEIYqaYx>An9bHwPXqY(~hj!f3mfjhEv{1PH`85ZLHGxz=QOBCXbYooR5z)u2@%t>-wLhbZRBBFaks;Z=$h?tA9#-9Ciwg_dP{{~{QC+u)=C_hUE=0`* zTeloAX_YV!2apy$K`iH$Y#3(0B=u{1xF^0kGsoaeQ;2K;P|}of^2fbXx}{uHZiDo4 z58k-(+|?&WU3WO#UU4R$*q)r+egZ#x|ACSMqCU&WU}o%uKS&npM}%R${s}3vxf8S& z;VJ3_!!)~HH0Mh5*qgo_b&H4DP3tk6I8X3!fjh-K-~~*xLG5t88nvl4X^|a{ml-IU zNp7ht)qdElnrHrpuwFc{~i5VY^6o zj6~N+2sYA2B$n(%Qh`HCUB27AH!e z3_}5NH>*f~Fdh)XlU_ z02IHjMP2(Rbn0hR*V7H$&}(CAJFis%;1F>|vfS^g%T1Y{ehv2EP(-)O`##p8W|eSdlPaQpD`r#BYSDvJ;CSU*SE~5JdesW zNu@}c?GGm`Rdq+b3wB!e<)j5>*={3AMOFDhO0-p1(XDn(Azw7zT^lYCIJrkHVM}&ve@IR4nWaP~38L&}~ z>jYjFX^4r5MGKQ^1nM4;opiEGn*Hsw<jgdTSP zx9+Et0&Yba%dp?1k(ZfsR~nv?pKupt4~19p)Rb5q|JFmE1xv zXj(Rk5!Jvoct($RSbBge#KL=$31Mxb(HJQe3Oge^12IW=et>b(WVZ(y4M7OW!qL#n zBcY_y=++x|<@2JU>r@UK1WzT*LI%}>AcG14rK=XXkBJ-nq7!1z$FZf8UFKOn9gj_; zRuD!$)JSJY9U1w}!1a=BMmMPAXZdi5o0H`{&L$afbGBG47jvk*722jq>;Snup~lQQ z#Wc4xvbyMK{$6@B`~T96WFmY2XySRgkF2}#f07Zn znx**bGtAuK;TsS_S+CV?@)>vy@w0zWeyV&=DazaFNG}w?syKoXl6A5t8cYUX?fk~e znN4_+={0z@--fWD!@~#H-1tApnIo^t!m_)+Cp$B)@bF3b`rUh#pQhXi8Sojf!lBvO ze3;1`b0i;E$)?3WJqn9cj7I$`;NIow5#Rr_W9mKCHexU>Rg|$p=1t9l-Kk|Td`qt3tUzOzT|MCU&F9wWav>d#4p3fyekK^ zsv{!0tQBLbeVci>1SS_dmI%J%Xl1Gg2(7trst?VjdY+jza9K&+LaK)J`o;pbDhexx;12GS{jzZkHMm`Zf<@b9Z0Z%s2QokuB55; zpibSj8g*A66m<{MV5YzRWQRx=%gkt+Z}3^@G_!8N<=h0}R8J2mEN znX+qT3cBX&k935EY~n#c3(>(4bzhZ*m_t;>h(c^UTWYyj6sr$=c}h!sgO+1CV#IK( z%<~{7p*RTK$k5DOcf#_ImH(Ue)jXlRsr-`io^pG$^BY$;_a56jJVeXI^)eG$6Qx+GR$pcQP}!j?ok7RL*EkE^09SKGSdbQXPS)2ZJSs^H60XC9bPnaxUl6t zTV!c=juAJ9;dyK%rv2+Z4ME6N`E0)JW4BO!*v+$4L23cS=0dmYjx8s!T1Lc0Y++hZ z%=9MWA5t_LDS732<+I8cl=mgq@>e(B^O`L8xnKR#m)?8rlb?L=R`r=`v-w_}%e{B| zt+(C-Md}T&J&W!K5+LUiUv$%#$E5T|uW7*;q-vB8x-1y7iteZYw)fzOt*e`M8q|gM zfG(s|4(5>}98-R!KfUT1M8ztiLP*98c;H{7?jz2IXTUdr^`9YtJzZUx+|iN%%etRP z*Aq1f9i-A$f|3S9IxB_9FH+OY3Dqy^B~G^K8ZR2c>?wrrmq8(rPZ8qaCu@Ko$bCc* z?m0zApls|71t_fjDWyn`%n~D1qGFSJu-8as$g(WvQ35fi!x3fFvWd+3c@Z~CCZ)1W z&jJN5Q0&w^fNdFy?SjjQZ(vrWh$J2O1?I3+4^Ks6w$yYn2LsX;VQnMdK=PWRk+^{> z$-;_WR>xM)c64i=>q;9u*1WPMxjSm*wRmA#A8Q$-bTp33?V>u8S9QHyX|;HnD<@0) zPUu4qNvC_B{?WU>>h9x0FWQnjC5K1D*Ii3#aecsQsir@A(K3De_g0)w&IQK6fJ)skCd8?6XidrA&Us2TtWgR(PB3{^LY z?y%YTOXD#kJGTE!qUBvrqrY5=ZSs)Yk^!_ET(YaCXu==8|sVb3k z4Vv?OcLgoMnN(HrxwL0tUd0!9_yTPvn*Il)qn)uZ^HxrEt3(9|YD~>Pkkjgpr$=9o zJvmZw%rxyx$irrv^(KhPIS(?|GA`>i>&<9%w8?$HsS(mtnx_nTZ{B_1G=oMay=+SQ z=HsrdPUmV)APL#`GL-0ViRhnY$~adlvd7p^%( zM8kk&)@H%m#h_8)IGC}b%Bw`P3Y{U`w>^@5CZmifOAr@6t{iR-m72|3F*_6o?)7WS z>ytB|b^p2hJ1(vbx!2ZbX4bE{Lp5Xc;J(q5oudPNkVnb9cmV7j)gGB=8jm2*PEH)o zo+xBtwnV(?k()q$Y$K&}cQ}^C0#3WoDyo&mNMf6(iEBldZ`$t6wB!F{$siayj74xx zKQDrB>RAu_S?3P-Uvk)2ktv(hVPi9S9sX_o3fMIyt}y1Cw(mLymb_XP@l-AumSvh~ z1b^GsEW$%lb5PW&!*jo3RcH&`jjCF$izRF^+f0`0@#5?FNi_gnRPti(M9 z=x+OQ|6tcu9crT)9ID^dE0EC zeEKS5!6jEy+4%GCW(_WLg8w4;BRqW>Tehp;NSSN?BAu#1l`+_Hz;@#c3u@rHBkIU0 zY&XDH*6Z!=m&u%Xx;;Q%KKu=?o zp$ysM;cbT(g~x0=Q=9UPeP7L~#n@BZ&^-8tvFq8k7;i3QdZ6w??vqKxq0=_I9cTO2`22U;BKik>KcvG@RB;$ z;=CALT z-YAGW*@g*-Sf|94X=~Mc*7p;_odC8H6o@*0id+8{aO-~r@77i(KxgHg^)YKmq(s=0 zk_bC9d4inqIL~&*&YZL6`SX^1?H6tDV3r5pfbF{h$4o1ytF$q;gm1CxUb@JrzsP@S z`|nF;sP{2`w!b85m_*Z%>fIFGH7yLRiT>>WfbRZ4Sy1j*9#Wo?+PnIgxL&#@E|)YR z)=TRra)!Ohl7o{I(=KD#lRQ8ly0FkA80oI6%iz-fh)e!zl|BfIQsHffPXwN37SzbB zN{%feVDYe?Fk(m@23)>TIU$YOekzSjUmBk#&+$x>aNSp<7eE$9+hIn9ta#L1pn$eI z1zUFv4Xa{}ZVKOwqi64&ky>99R*$46qr<<1I7jl?hoA|2$`$3N%wp6Y)*dh~jqT6v ziD+!hROBtJ5G}KHmM!FzZVWmedD^?Tq1{6hCdq)?;V3$DRMI=BGbN#~A3E-y!%##_ z{Q(|)+>db()dAxYHRQShkpxmPjyBea%iOv z>f<0#KbNuArK*||1bW%LkxpX}7q_+^58anMpUhg+9HWl$Y0k|^d}9+n_O`Yj27P0I z{Umhksaqf-)3U=QTZ)S~UN>X*zm>mFx%m!2N{`9Av*sh#Rdjje&{{q~bexZmjG!5P z3S#YJdYHkYjuWyunW;&=j?93coITYa?UQQVM?Je=lYO_$-JY|(C&Ag$#!CQr{m4nD z8t9A58g)jA95r(&iS|(Y#PJ=zoF3n7|M-4=l-sn`l za9odOB%MfIB>jQ&Cqm0!JMTx8+@s^mE-$y*+3AR-5Z1XlIhA)!o7rr5Og8$yM16ZN zmub1hqW9(M3CYzjrjpAa`8tDJ5eQtx)X_8w@y>T>OdZEG^yV}~HNLhBvIGVF`pq+< zyN0`>TR9S)jLv@o{{H(A;k1=Ckla32!e{+93TyI8&LN+^5bIWcRP6*a)PAC&c z*S9B+XipK(@)4dpbgFCb&XeBR^ZB`iOTe#t%{ScOz6uYv#@*mu*%*-4GUnuXBLdI_ z^RCS7#$mlX2;9r%WnKux_I+j>x)lxOGNea;o}CGG>MuCq-HQ)Nl@|A#qh^!iGj4WMaJ?yp?av_flH4gwjF!(_%c=!% z!ZnpF$IB+A7!zXqp$@P;G4Io;0PN$W9DaY9-mg$N%|ptA%1g={%4eh(eyjcQ3$L~B z+kS?N~rjRB0>po)~XCmtL+QJE~8w8Xm zsO7)_OrQ1Dy_0`hci&aPt*G-cQG+Hb_*Ws;Q7Nditb#FIhIW`_k}M8Klz&QS1)ENH0la2OiD^5whC+}+P*5myo#hYu& zHHZaXko^SDwjQ5-N7zKReNCE|)KD-8R_rX%UYTBuGPUCkHKQe2f|!68Jffkb0i>k{?d z=*_I2##02MaaP!xpL=S3)fVY8q@*W4wF; zlo(ta4Vj%~%ZbKsmr@lWBwxLDU>rPyRx0x zOqQQb9)IM)-D~dT=)!{Q1T;vJV`6}TOR&_Oq?Ox8v(C;=Dv8s=Z7I~&0A*@A$Tv*Y z`y3gcE8>enM4%B_i&;g`Pez?uNiC~k<^e~P91gx%OgnA^P*SOsM{$hq&%wgMIxyzj z<&`1^qkuHw7rKEy#I$T1uHPban!ySElL z0vxYC?A^&UZ8&U%>ZS$1(RnGueT&$^xN>&jD` z<5YQh`T6|Q+mGM(*i>yKzp-QQ5RzY5Szp=S&inQG`tou8_Mj`)MEd+r&=?qJ*yzy93CrR>Z^ID|H%MwHE7#>D;qjD2ZvT-kNr zyZ65JE^l9Y??yM!jlCNUpc^|H07(EO2ohXp;WFfGlxE?KG}0(DqczeCq8xoFF<3{Ph)Kiab%jtK57b zl+t|I>ih{hr$ss@M&6)jG1HM4KZ(*`z9teQ3WlyK&IX!BmyD98 zsEhAn8@f+RZrodzk6C-}!Oq1JLi?87SK>e&t-#`J2G)WA&Q+^WNvCIL`*<~vtFb)BU+nUl zL>&$n{{3Sp`MX3_LXrJ?r{-HA(hIt+hcFjl1`q3@+^Vty=6v`Xf236M>DRg`-w!P68C+ckzSVG5GUkg z+aD^w(0cajGyAF(l$?SS1m(uo(pIZguAQDcy}ex?KbSt)-%pei@2j`!11F?U7xj7T zose)l2Hrfvno~MF6UTT2!9y(fF|EWOPih=$LRrI`+%Q|ro`ex7WSA(Y3_0jGAgr10 z0Yc-<*;SMc+j}O_fRmx9s+i+=pMNULPx%%ypL8D4>`-*^AvRq1oz`pC3_I zPo1PnL~G$fsM@Lwjag(om!?#~)bz!;c$=7hV+4(I(*vX>lY zaI>!we~qg+5j){)r^WU6zmR?`IZ{Si;FjkqKON7BS%%LxZh69G(;N@a z7Hh?F*&E6>8lGfXnx`F;{Oe<@Ib5%{3F$`cOYJ#@{hdlj{M*Con0VIG{G9gd`U>CJ zR4oouLCUj4^Gp-^y5Odk(FV1~2(dnip~235cB8cd7>`9~-ZjhMKGHi$DyoGDSHGKGv$RKwaoRvqUHp8IH0n3vcI z&;+vtbj~B&RdwPnng$Yd?T(N=j&nRel6)@vinPN$VsUoXTUr0v+Z#9buN$R}tdZIX zjOOax>c+;iz%VACJ=zm@L?wx|RU3@PLsxqp@(&V3bmGp(SKSNX`w%$$pwOQO&looK z5PlYj2n+WNehdrS>Gf*~`6gQsYTf+;hTp?qL6RmRx#cQ+hI^A1)*lwCkZ#v)qM^cR@u zFs+|^IyX#?#J+4_#nfHl?ilx#1&fC+v3LKPTl#3OjhyElW!&pakI zH#cu*hlXw|L2#Q}vfEUW%C{egTn|LBkK?@V6oStm&F}XrF+0iqMjuJ(uNxzK^~#ki zPjO&%37ZBmuee-2ImDV61tm(9H4e0L#KKtz=Trm~Mc2JycXN0kP*!dyg|gj1*+0)_ zONBx$noV$Mq^wXK%jd^EN_2<1lcwxgwtT`NR4cfy3v8`KfgdKQVgdiV&plwDpLCDr zS1{K;|6BTh9mX*dGeH;QDDDM*7Pt;tNM778oR*iWyAs!4wVF+bb^W4JC|u-Aa50jk zsf$y`l|aU|w*5zAkTt7Ii&!396s^+^+tosI&0iPS-aU8j+!I-vq1g`t^K3kg@|@|I zFK}IisP$dGjETCE;8;}E zM8VRI&@-F(_KQNWhXDpI*k=+Vg@FAf(P~6A(J`*aQ=Gdy+LBVzX|C5r3`k6Icv1Rb z_37PP2TznDhv{p|?rxReT6I33m!#^i-RWN;Gi8p=RYt?0P z=+xnAhf;4_1;IuZJj9LgkAx)N-rU?g?GnmTaCi$23CpPrG(A)bP++PGx4)yQ>4YB9 zvAvZv%5U6EaiwE$JIj&On$B^6ubOea%DVD_7gAz%`ZE^KWQ1eEyjAM3j?HcNWBr6{ z{Q}oAPfM>$pOxMg(;ZM3 zj>VBmt`<-n$+MGPr~Ug$X6QkJ>SAV%p{tRna@=ZMIvA?G6kZJU9QRM$9H!bu&#=T2 z8W&AhHx$Lt2}?(CXqF#5jN(yJmB{^4E|+~QFCcNpw2M%Q^(e7CY_vT`Rq)w$_<#f= zHl$(6{DGR4ilX181BOi4w#nL&zE`y!;E1#nGDizCwe|s4Mwm~snejPC*-RLVO|V>r zFm1d&EF3D;AZrT=qn!6pHatmWKqoFPFFSH{_ExFQ=or2G3+ZRE2b-0~q&aDw>w%e8 zxV^E`n3>>bg?KuDA2D6GNQwhBXihKU*gwN}Fe=a;=i7rB{ipx|Q~ZvQu! z`^0Qa%=%&=t4u+J5_FFHl;JtmDtL2Zx9u) zU%jxmy*0D3QS>n`F4Ss8+w+RRvWih5o4r%KGoTTVTAWT~wu8Qou*wV41$CfYsNDz| zmvJ0}4!YsRd0c~|3KUIu{0zy|4gup*mDiR}^8>ad?2@&7 z1DKvlam6Nr?Ff>kP>)AinajEMbO--OqOS2}Q?VT_e38u0FE<*kMiLdrk*KRKM6#*M zNG_ir9bF8D#GE^An7b*eR}A^L6`Fyjt7wFIf#BBVC&x+hHI!A8xEYDBM4RK|FQq_A z@?AM3O-W6@7FVPXx993!{TO>CwSHld&0d&hJY#p$x!m0-mAWf?UKrk$ z?sg0HjwQ=s0eEqPdGM+aj&(GLa`12Jss&}G^X;_6~FGBP8g12 zaX*~@{fM;3MrxBV%REKK5UPrX{*oYD!A#wERE8D%F;fpS$z(p2DuiK+B{1S|(^LG{ z+8h8oAv6WWf?<}hmLo;W1}1=Zmi`{qT1m&^UJlE-gsE}PqFI8alV_Zq6Q{6nO5~N^ z{VCTig0*K>IvYn-o|8T*eUV$TuZc>9)Q7LXuzTnF?cGb)_t$rKuYdK8H?F_+m9Jd? z%$L4&eWY5w&Jo^`O3MeGn)b?xWYLklQm6l{E&RS^DaFg8^?nl0#)Q!Ef~gh$6RxX@ z<>veAsY-Yl#F^~|rn>~m8{A=Mlr1Y5`HXw*96X)WiJmzK^$IcRoL&_((U7KDO$+kx zVP;i9AGgsxcy6B;VujJ zWC3>>l{o#%A-j$-zC=!{KTL?sk64q=^|fwfXd-8Bp8g@uhQDlRtiXyQzo8u8FL>pLI{ zxUP{^9JxL}=Lx`-0^bY7j;N~${*~ZgV7-;FEtsEX94N_{d(8DT%xym6ctkZg6hkUk z2eDrNS)9etxP{HeY$3b+jDJ-6oODlM+xcqBw+vPKz$>p*KmX}ZS3maVo7F29FILAl zHmddAg|Sl$D=Q<_k>en!)Fi{>#z$%8<+(VZ;Y4TJd6pmymcxXaBW?C8Re ztjkdMRf2EBkO*5UkHUl%+tN=Xj~Ix<7&6}ETVq_jEdMMp$4xNF?erwbb8TD{15m|e zMeuB(ClqQYv8V7=L7Hl(9dOYs#a4cpTlwJ}NF{Y$Pa)e9EQ3xGS&pJG!qvIu$|A7w z+nJ0SQeE{UY-o-Zq=~F+GAkhC^8s_PC9^CtYzM2m>=~@)L&HH%np(DvRRY|GIQwNo z#hPq#x1+;tE>3<{+K^6(Z0gJy_B^<`CL-n8tYHK&7>JY~b|4-D7vrN98`G#S!`fl| z>+=2eM%cx5zb8S!JvnQqWQe%NFigoz6N%@4YWnFRswblCC@7^pjZSfY4VW#i^}^OJ zwhoBnlE1ykRqHCYCk#`a^R$brqi|&H37=>aw3*6@dR{v_WXnM@xA5#V%I~cgHD>vU zRIojp$W6ukwLgtH$;uo%kF@M9GIhGuXiRx&d1z=#nmU{#+R1Ys_6ulnmr66N_wijE zDy)Xq>?gkHh^DRKdV;$b>Y9E@vo*!e!YoL+RP}IU3|1zMz-hoLaXlNk!BQR+fCWl2 zafVXFM1)2BgeL)4_*7aEHWWZXG1SP{O6+?8 zi0%_)Tbc-A+A~6>Mz(FxTwlWxTuKwnMF`Zm@!F*_cEyH%$@{E47Olek5${h z(O0bC$rN`wYu7$FhGsaFU5FTW1{7wP{(p(Cj#{o-ewq>OTLtHzA-_EhuEUu8tGF`B zkrGl#dP&RAU|xXhcxN@@rh z1*ACzDr+_<8pv>L$hWi@Ay4AzlYhhY+dCo$(Q>fkbXuKdDU~{;N*H8Se6Nzk%s;Ta zCmPhbxMzAlSxiPqq6wNU&SoXSZFs&?31XLXVUs7w8I7^mXVy9Zm+N@G}P%K@kyu=-g({Yq~PxPUUFTqC1_B9fzBA$kIHe!SC ztTB6x61!3cn}mj3SsFuJGPCYo&%jiD($?Pi{9 zwOZl)-o)O z5B@;gfcVUEL=m}e!A!VLoeJi!F;Fh+R{>xb1)ycaS?Vl}GxrOkUN#;rKQcmxFY{NV z%Uj4;6+&9&yUCOtPv0u66bueds|8o36Vx3O-F{pTPT)JE{F)x;mlW%>U&Z!iG@hoL zN)^nk>AEU1N(N(*8a-H<*6}8^BOv5~XN4saD_FCF9YNNz!M0_ zd8{l+$&qMi$Voa!)Ndcg47!7i+V-k1!_p4mSGw!LpKJaLTcas+i~j?gS_ye3u-+E2 zs0ilnwAj{H0NDJ*{QPSWu-O*X7E|+Om~Xx0VnX$QlGGgKPn!H%9Vher-p9DL_^;er zAj#*}TVyb*t-t}foad1+mF1owNg6>Q6Zx21KfLppgytblC!gQ^E&%qI#q3q4dC~R_J}0-|?zQ3tMaSqyd4VX)-1I!HS@Q2P@{<7x?R2*#yx_*woJZ z+?OK2%O-6_*Zi8P6?bxmI>E1*DSnB}f*se!{e?8fb$PdHvDr$FPese?Y;-+})Y;}z zb8Ss+$n|vBskP zsimkK&S;-YHn&)?9_F||bXi(#rBEi3P?QDVPTNtm z;1SDOIJo=Md!-Ap4mGYU4F5GCd%Dt5d>(q7K)D+pUKq^fXG^wt`IRqHlJCRULs)Rc!E-WZ& zC&z`dJ?~ovvc*7lq;Y@QuoN;&HEf!S4i!wbDgHC6%L(5JP zq;t|G!5gt~c4_G}GmKpB^vvGgX$4TA>!$^k`RSqa=T}azJgm^D=S$Y#gVVfQA%B!f ze;|-Ef5^AmXfHubuUhn}KgVAi+-S{eCO2AhA2a6pP77&j%!1tK3h{ajr8Z0uflA6& zxmhBf5~Md4ux-)bS?1Tq8owSNe!9r*mv%kv$)@uIk#Q`VDGm~dm5i9MY&Bo8n36bw zQIt<5lL55r0eQRGd^zR%36aO=T13o3W^sqDIB}0iPl|3g*StCTD7mn=xw*cwV$QCu zncN{aB|@&4SB|NVsQ1q!o$Z3iW=y2jt9>8nx9+qd6}2O&s+gH@w%l`zy3=b0Ik`qo zU<=#TGFVHN7`Z0Xh}uTrVG>bCwQU@XF>QF9PBn;KVVosZu7x^2H)^WjU9N z$=z6Q^4LHQs@@$=dgyMnCDLzA#O_)Gz{z1CYSI3fc`q^DKZW`BvY+QynEFssFEZgm z=%CE+`&N!AzV#K3|5jJIHOce)`$gv@W#Z{7%dK=av{qQ7rZ5%)Sq=-eTA1`aNg54D zr3dO*yG42rok-hV$nG?vj)JN8VpoT&%Wc;j1Lm9Qw6^!+PN8)$7wn(WOcWTMJAXCYJV*JXLI6QHn2ReORdP zx{k~j(D&j@#9zm85){vkPfI0kQx~~6#m~XQc*|d^=l$Y11<8OveM0m7@#%Wq9=H2d zNV`60FnS=Q(9^$d;MdhCF@?MstJV1&_19NNTAwfZTf<-HYL{FW2I=tvjTwhPo;4R z#hsl$lYSnvpSLajsyJzu3>+t$kff|1McLKDQek*Ft9xEOTR)QQ>VKM}ay~M;_;?1{ z{`$%LR3 zFVt#@(G=Ee%uA@0!#`}1+evM0VX6d|SCAp$xLW@I#8XV$Pg|76xevO{JXi-(UiyHG;3^lvRW5?7{MoPGymaZ{;Ob*HZd~1Jt*yyd2jKtv=jq=^I)~M( zaW`gjuxKWxLKDiiP2k;*sy9my_Oa|f|C$(m6dMkfmkqH3Mo_-#eEYyb~wb; zq-PdG;Fd7Qzxp;_*13`ToUZ{<9p4U(hPscfsbOrnT)`w4Qi5`DF;Xab#P?I4catIm zQc=*f3g-r!};rL&B##EwajP7;dE;XKN|f%oU4UdNR3{-CkYY z;()WhRX_UFM@}5K!kucvK4&S`8L?8Y1a1BO-p}7n<9r-gJH&pfWiXw?ytCB#fX@7l z08&7$zt1FyJqzHFCPKh6qe?OjQa%!M8fJZFinLsy6K$H<=sQJ&I5mT*){I8isf+1n z78i31#KA1GRPJJtkS51nl$a}WiFh_lOa)X4n5g80apwL1O8<__D4uf+3I#WkB-ghY zlidf7S&!RcfXk^gfOc=*dI{u5;Bd`s>BM-)_FwUQ{YI=y^spX7AqIsqyh8cj>r=>Id(? z&iyt!D!*Gk)kP$SY?$_ol;{Xx`F$A$hBikU{3lm*%OI|1 z>6(ri7y=`jqF>gie?XnHjC}aQh1Z97TpJ~qasq=DlhB~b1zT~lx65U$wyq;FF5*#x zDZ8#ATUgc*-qG0@bx~*1o2a~PqsJ{FBU)-SU_Pl>p@^(c6^n4Zys-i0oRm(xuovPy zvYopr@BxM#wH@tgC%QT@%Vy6u9+h0bT<<yJF*Dr}DBa<0LhJy~|oUAyK6OZrm0 zdsbK7v%9BGjk#kF^_w3Eko05OX8c*}NMvA&`~zcen*Du`J#wr{uVttwN%1>BJu)&3 z*zhnIdHy+Ow@PAbnIni5XP7*h&DX(9f>G`mV>B!@=Lpw}XHALX&T63M(K&9a>Mg_-N-4!1?Q@pO zQcC*9rAr(c<8K))P;t%j?7Ig+ezlb9Hl5wNL}q>+0o${e2Zn zQbn!2ho^l+-f`s)-)mi?(s6Uz#l!8it>`LkSGBgcVb3+!I>7qy=pQTeuM)zF{)BHR z%9!C;iL&stJ&>T>Gbqtv6a~-|Tx!Tl%SBo`03}-b`&-jpRjeN$N;M2%i3l%Y(j5*KgBR~tBWOmjfv@H&i3|;g;rp~$rB)_+KLax>_ZIFiz=c-o zUU)x*q1U)qzh8&->OF8DKyc&U<@=Z6W$EI*bNA1|b9?_v0>;Qn%d6gy_V%P3Rj;)& zVi@G0Qqo9=JFLs+*QWHl&KKo+`~TU2Kk@%Nr(!@M3`#qCkDv8|Q17u!w+>oyMT_ad zl4}Y=KWbMkZA`bG0p>JUB_15%q9(>=>MRjs+%W%OyfytPkSl}5;<#WtH{BO(`*Ivd zIrzJo%-s=_ubw*2Oo9q!A@`7ovJ_LqqLL{}(a@;)Za^_ZVq*DHI!u@-Hzm}yV{?6! z#VW&xvrh#3TwOXRy)1o6`ik^wYwP*P?mq?|d+NshFM}_?`7)P2xcuUy_fLUSv-dv^ zKK@qS=F0@as+xNJ{;S~CS8lxd=sskp;i6Qyy{+t(3OR z3~`tbIY`!%c9hUE>5`AaEHbX(c;bqR4NBn92nM2<_GEx^D{|7ns%u!ITpJo{CRDM^ zd_xZT=0M}CLP?I1MN;J}i#RQShM zfEKLa#gOAJ83Fnfax8f!i^i&%j7<%5IP6W|`j1jh8k45ld`xIIC!wbyrZFeyQ#1E{Mf$M=t1wRFS($J>e+T5kC;0eoa8%$3(Ffb+T4~7rmQnP zcmz78Xr_^OcCT_1xD)GM3kNy`3NlnX?*a$^f4;u_Ij-zF@7;IbyZe3n())r&FF+Fu zu`dJ&5F|ko+-A5BNsUO(a8}Kt(P-q6JhCL)V<)yHCl$*nJBdnOR4F@_tD=%n6=R4bXnl(_YF!#YjA1$0H zxG-3YG}(@&^y*edU^T6TUnb^L*I}&la6vz3YaT^m2A3;j`MR&gc^9uKz2Z z$oj)&VTt&Ru#{SQE>hhp;HRcr6Dn}{7ITs2Mvt(xCY@-)iMnpQ#&D+rX*a8yj)%;# zY(ZqQ#9gGPp^(j(%O8h$btMU)H(P?#ue(G=4@HR>`nk$xMOOgM zCMXR4LQfnq?l~_%_Hpsqq66b&RjB7IQ%;zzX~}dJz{xhhe$ViXTgF>@9`*90x#yT) zt%BX@o73$!IK4JE2auGEjZnirq0ZFbXtUqGl(jRp@X^|m&hhpWo%21WiDB<-q7mH? z>GgTD8mQM(b;woepZp3)1v>A9|5-`usTyG<6eo0M)HzA4MdTpm zhd!>^o}5)r*jF^!Z?0PA*MovXB=uvW^xh&tzNA8JHwEc;)p1x#_d`9J^7}wiC>>;J z{s(BI$Pf28s&}hYGQv^5LCJK|VR(ijt|#LqT2HVZ>bAGi$DBJRSKb5?of2YDst6I{ zUf<(i8h@Y7az#eVxT^c|Hq%Lu*=LP(>D2tGvuEpR{a|0-psR5>M{?Nn?=R=TxZOUi zo{PPjXNnGI1V0R1)6S-&fiE(;0C@uBm|)SQ65=jpiTV)NmDkkS1G-#u-GxB9X(0TW zTu(#4PPl^z_g9r))CCW3_MIxnKMft1oaK0I-m%Ht{RGeMv`R3)sN{Ow0+uOc0PJ}M z!?aaq%o(t`{YW7zY&b^V7&9*D{;_IdJ9d?1=Qd_GwzpfTH7b~1*MHwDEwuZN^T3SF zprPr>G8q*~PqN?8%9A@5CZ-bV;k8+96YbR#F)Yd1}(?QjA%C=DQm<`j_Sc;q@ z{B9rt(%txf^l6U9p0BXgwSw7Q6(aPM+mS(fGPEW;47&5nALFEPy-|k#LLSm-C3Na} zz@8OBgqXyH_hj)`*%m|Js2LsQf9~tvljZ4&X8qdEm8~n+t|`o97D(X<%Ug#Fum<2x z?;_l1>&p0A`l4FeSE$z0m1gX)()_7&AqdSNvSaM+a-aCJqiz`yws1hru0$lQm_f2u z4NR=5$vyWIN@!u(bPHJbrOzg5I^{`TqQY7>fmXS_A?0v5Is9qtDF#w+idj zm2df&-jcpMb-$^Q^DBFg_pV&I4}JgO%+P@f`mju;XGR)_XF1a0^NUYB>k@ zHlxJSC~EGFbSk0LLDP;c_?d-;+uZMRe_FTTO>1;?Uwsesa9}GbhEz$BGFQGF;MV2o zDy*)>Fsr#G$}9YBHWjdA>=`$7UFd4@;>2!oZ$hN$YRhie#p3Gb!otKvWwmn1FCQp> zXqheclbRSU>nW$f#M*EHT~?0VC!Wig=N3kqk#(roHz7$9^(@3AGa=>_ausL^a}3%_ zOX5lyn$z|~7u4EzN!aYm;-3PSDc!ZK^OUG>z`NAv>2p-YDM^(sbaLvlvLydPnK7I& zflrs90{$|w0{|O)-POp7RyvSP6haQwC9pk@kpM%&Eu=!b5`!hBmPLWN8(Fl+A{YGl z|0v1z7-xc=IYP6WzUeLNIjg9@D?g)X4|J2dvUX5kiW_`WeeDHsMxXVJ=FoaIYcQNS z;|7nx=IgJ&{?O|UW71y;LrtQT?R~{g#LR;3S^up%gS4omw&YR;aKi^etMp7E--u^W$GzI{DD_sl8M*q&eBUSB75B{IToJC4bv zXP)Vj?$HtaVWZp#UKkx_>ovq-o%l%)r|t%HpNj*6zbEjL4K+%AzO10efT%1>BfSYC z-0Z&rd6S5)t!sUs;ob#XY*I-vv!c|_iA2FF0OZvOYxXM0b8FmNkR16I(+fOkqYb=b zk#aUBVT8Y23K*$O=rOFQz_5yrIOW@Aow2cVRO0iLS^`;?h0g8T7cZmhcX4T@L_Xta zXQyJO1r=I}(m3+M`VC=W&S?PHhJt_n`I7(JpG!V#Ja4?B`x37k^~WE*`uxRv7q4Ee zhY;g>XS`5Ysjob-z%an6{YHb4GGX5yr~7U9hhNZVRazWY5J%R)jvk2CZ4KwL?>ZR~ zah!Pz9wNX55TTg5K8&K5smHC*#}VZV+?|&w8z4dGE#=bE*eupq$RuSigBT~mpJ&`I z)ycYMA`4xh+4&g$uCP7k>46Sv6N!bZ0?lS5Lk82a)UkZ^oL;nLA&=akp8bAU2;bU$Y4`T+ zaQU@2UORg>e05`OEu1RX>!C;t<6e0031UB#c^S#MEDi;Kuq|mI_*Q%W1$y*nzl(in zDX;^xXjQw?!3m+k&a~x0oAhPRRwh6RPLR+sodSxG>}Z$ zGnCL1K~tLQy)rjSw-I7g0lm2Pvq6DXvV1^U#j923puA5txr2oPFCQ1u4vV$Y7`NG2 zo{F=T$~cdz#{PVj-W4=!oHw3Pw&%)L(|B~{!rJ+zvrB7hj~bJc?e?SD%2Z`+>`|cZ z!lU~yJ^SqDqs^xl+YVfeeYD*eI-$X}c-oS~BR3!!xdHom$o`M&pL6zqY$_Z(E&NRv z+XMgwg?Z?kL2ef$IGZsN{vRuCe^bxWej22tDEt-VkX+)uslKulxSChYdF*1y6gaUx z1(KO(MNq;8?)|x|qUb=g>``^&BIZOAh7&vLM(Huyf(C>INO!m^5D~!?6ADXN5a46# zRG9BUM|xjiTu@4bNYl0EL;-v76kxLAa-U=EIDz<%JjSB00^yO(HFAuN2^+gwRlhKj?Q843D?9rONnTtz#x_I*M$`*qXSKWoHPz5TuU^{Ys!v42)nU@*r==I%QvT{e%%bY zCU&uH91zC22jgzcp;UMhI}#z{Dsh`$U1scsJzcl@?4E1N+rDnx*_yn*p5rw$R#B|G z&Wx4u@%4q}<@MS5`Sr<}ne}_OUwm=>(#f5jVEyQP(E+3~ti<=EudT%LDE=5$NM=<~={T`;i%+W!MH9U;WlnqbmHDDg^0@s~#0-Xn*!&3!NE~Bo}>`NvHl)DA) zYskaz>sF@M-%~dW9TVPsY^$ip1SauW;FvV`Mqo2*Nw|x73``sGocie)ig?3ws|g7Z ztH@A!&PtTzEXZO{VIQB^)oo0aH8O)B7l!#b{@KhfMy|-McTm7g9FO++~g+ zftu<~CZ-d=98jh%h~|H$5F$%`Ka}!?)YC;16?|BFWv)X^?pY#&Sf1x8v8A-eZmTCG ziYT#piOKC!>2$UFO=&yKBg}#b3Wb@x0J`J8)X<&?hB#yDW}051H*5L*tpA=G%f>A| zBXb%RfxNbM?y8FMWxA};N3pmk_lC&*0Ql@@k9+o_PbWV=Bw3Azs}qL1Y5O(Ifvwq} z>o{;<(q%IVK$cfJ4hlR>dBl0)xD&j3GN&wv&D|xX{lUy6XihkKLG9b7dD97A_UBrm zU7sRMEF1^p3w%dC^`Z&#e+;Qqn8>MgF=b?;n)s09Wr%!-DfJg_TE$3q(lG(OngaS) zknKddie&*QkA>|zfwbOGcfb`{56fR>Tt&M2F`4aSzGA#%yt%db_*ve)l%C_K@9c8> zu0LR{;ZpJ62 zQ_`Wnk6`~^@S9Pxp<(g4pGVEkEIyoV`K9gc?dRq}LMq%_%`x|QlY5W#st`@tb}W$G z)Xk{WA%w$xo^F?jCrmeMh~fZ<7D%bXy@_hZ`;3I%5?waE7*pL9{cIE!lH^l)reF~F z^UNzKKcJm$9{#7wB?VR;`-)I8sb>90rP==oQ)!lC%XM_hCiQBP>7DU`6C>{MbFoj- zXWM0%n=SWpGdjPe?GG^)GiJTJTiw^DYX7kCb4ymiSV|V%Ik!-F_w?tsKex2>?u8d0 zzIgfayKle$!OQRO?sneo47;(Oa7@F})L_r$K!QiF#8Bbm!&hQ?gnPAjbWcB!{tJ@* z3BQAzG%|EM4Ck&D&5ZEYnO7&E@QzPi-V(u8?^wY>g}Okxayy*|>5Qi*7QdiAb*rH0 z*ql}`MvQW|kwRNHcQW1XnllrfId4q@p_g|9=ep^bF+!(hxD)e8c(YA)M$kg(b?y<& z+_3`H?CVkDLdj(q0Uq2uwSui4~}B+p^>E^Tc}53*(V4XY<)s4+^_Ao+u8bb^(}w0Z04&47nB-| z@tMg^jWK6JuBRt17F>5eAIT&YS3(wY$KCbcbD3Gp14?IRQgt{sVpFFBlF!%8Wa6@Q zDu+D{A7kQyVsWlvgdUtO_ zb2LRi{%hlZ8hJRk0E~G;hAN0Pm7yny5PK@N91ZVz!$g`SE3lvy#O@m=7ZS>nFs18K z=B3KMI7;7sWPDr2yfx#Dl5u^@S*w*AmoCY(r%s-nlXC||I@jxz8xcU-7n%N``wCG; zRWU!9P8*@b=^^}K4^P&wnEj}ix42+QPL?odV|Gl#!n8yoE45BNSKOQ|qi=>dr@~Pg zClv>!L3P?ZR*a}M-ZYyz0@Rg$o_Tx9ghv4{*^-3#l*d9-=_EqIMj}wX214JpT3xg5Ma4?h>deAQ3`>h=rgbxWrM#3YOcBTI^y@i4F#bB@4QeU` zcyg=UtaqLK>BVQ}p6PZ^uU}kIA}QZX&H*uA$HzbWmg_H51#xTEcBV_$5eH2%nNB1fp}s z@o?oLqzZ71DevcToGLIbf`$q<=M(@WN{*VFT6eH5saXD&&Sl+dT#wfqXUyCz>##UB zm+X0)(s6VAhz$30<_Ec$`V27r9v@8_kqifj_v`(c(6G|Zp0Eemmwo$~odo`8AjVBM z+#S>ORnC}~lO|A38`}a;Bz59Yy`8cPIbB>?6r6=g2UA>w5v44dQSu<3H}9VV=h5$s zF@}pybZ5b*>L*Gh=G2jdvB~UcjYvG}bG(#PBiSP0Xq!VX|Nl*kXNR_I7Oj-N9N z))>(UKq}q)8|4x7miFh3m-G#dUrHa%+(=K&Y^EzS>CiD`_4=$Pt!wf_N>;5;dZqDT zHb9S<;P(Fej3aD$ltrg~NI3BUCo|B{-!x}rbOqu>1~d$VoMV~FuEmWYXqG^CjKT@R z*8wOadJKS+fUb--KowbfWZZNNFHZ~)?ETJFNttgUK zKHo5@vZXAB&A`Mez+e0=3lU4YcE(fjcgEiv-^uQ`?oK{Y@IVjxfl-qYAcuzmRe!GOCtJXQ@V+afdegPX@{Nw!uDC>ngnB(3A=Vu6F1Dy9 z(WfPEE(ZaysGRdn;f3_KrzrKgl=WN(1RY0^eBdR@6xLWM7)xcYJPy24`76a@*|dOg z^Hm2%m9$t?K4Uf&I%su1qPN9jMEy4BT!DTnlV7yTem>ta-B`Jpumv$xp=k`t1?6Vq z;}uC=_iLf@;E+hi$QW||PRaCJ%CmN}4V3LIfA_*fwbiN=CMHfKVc0m)I8qesNp*lF zMuSTw&?9jYKB93$Z=ChlqCaumdI{Qk#YdYps-Qj@Xbz(*%zboQ_-ix5{~6tgr*QGL zZ4kDlU}e|MNh<2f50|1ji6dT{W5=gKu==mVPoAjg`*KUEa|4JDo9dclbg zfOk5p*zfLs?TlCunutRr%BdDfth{VTSu$FhW=?woT=>liDY0^WgMAv`NY?ZMFEo)* z6nR%xSGfgU8bD`UMe)IEh1;Df2t8>xKNgx)Smj>jO=n%dmobZJ2FkO>MO|HPUOYcB z*FND-w%dMrV!{uSB=6^stR3E$3{HCdqWju(ILA6VBs&z%Vad$t3;8c;&{7aAU*}FE zXT}X$-DKVg!gbTzPBw{n=FGp?^1I6W`MEJcX$eI%jHfsWXmMGd184AmMYRTp%%3dO zrvn(RwO>*)`b=&ir>73*sDoqw>Z*<(pi(^$joBnvElJcWn?WS)&i&BN)878J-ex?V zD_u6Oso0>q6?Wb8cW=$iPEDO}%*>n*=jYG&C)-Cxg#F%Jdw*@)pc5UBVAc^vg%R0g z6betUS3?o?E&L~eNWoOaBS8R5PDGKluIzN=6(^+COye9oNpu_W_Vx)8xb9b@HYk>f zWO?BQ$|aU@$it{ZuuseLwA}*vX*CE)im6OvaY7HBJT5Uz9Mrv6rK4UX0Gf!xpIe^Xfi9Yo7R)2jL8tvFW0@M0x z*|Yh}PrscX)^v~ zGhXmz4KY5BBbLj}avt*9Cuh|IO*%0?Z!3ZNX5y>Bewt&s?D{2T;)I#}lq4`+OTGo9 zZ#CFOqGcMG)q+V-xRIy}TY_%Ev1a=$GFl^vF{ym0&aSry1P3z8$PtL5096m?Rr%c@W8R+SldcBvj*SMThfe0 zh28Ed{Gy_?2U{O0T;94Jih>(j1tUDMwG}QVb4j7F9c~}-DhK|IX2bd!$Hmc9<9?C+ z(B`3$SK;N#B7;YtcY$*j0KPw9b# zepL9MA?}$jM?T4qZ7d5$!ljCqMBH()9dR?4Y)y_u0$*aD@9P$A z#3DB13sRr0=iqo3(l`Hk!C~~@ihzpGsvuce*cP=U3(UBNu)h-s)cr8BaxBBhKghUa z^?s*0Q``iK$LsBs+z$*JnxS zM)y*zIfjTMOO`HuAb%a2F#_g<49=Nw*3tCYqRCoq&}mwF!l529#=H(i0hO{CkAt{L zIf=qZfhSrTk6Hzg@1oaokd`0~bdE6=L5YQ_V-wrPSbywkS)6-veTu!9_KMJ4q@@9EUnI2JaqDl2vhSN?I{O+V-(5!a*!@eKo z*^Z)P#$ML1aDHp-{E7Q>FL}>go};#0T9b2go*>(n<$;L_uVxs>ykm#Z+eoFhFWb41 z%E@5k)97{3t5)_sY?eDZ)F-uJj#N_GeNTJDIQ359-RCYxA{M#x1uJVI6u0xS37p$h zV&FUna`m6d;hcIwB!Rq11-3bkzV2;+RgPK;g5YXyXkv%@i zm}z4o`1h+jcU?jyDpEw|_9`g^=vWhVkD<8@R7|#oiloqUgxS)nn##=6_e>c`t6R1$ z4aWU(WuCxPO<48bywqQ1@vE&O*H0QB=!)P6?-!qc)w!MCP=TFeQoXC%n0Mw6i+Ut0 zhcxFC9wa5x5#%zuNO^#!^-2u9q}xB1mWIq7hyT&&4KBugF!K`<17Ec4In#D6IHw?n zusmc>6Nhjs=LDCry2p9XVL8UivBWG-Z33hR^3tUc1k**c)pSkMYbL;-5SHa85r_*+ z$1O{8rdCov&#$-%;j}~|BBa+PEFiA8F3863b%}HXRw-*pqJL z17YWCHso}yVpdv-Er}*+$wgs_!?>bcx{NTyB}Xqv!;l3W(lt|=FNPv#QUy3ZSSSh@ z#W_>trIjRiOp96y>8smP`SA~p9~l2lML5S*d{f-wXU3;_yt%TnwzamOZkQj`rVh>H z^wL}Dlb=u$)w{tD?Y(Gy)FGzcENT%qhQ1>_=u2XUnC%2qKcP%$q#{no^I+sKE3_40 zalmiu9?P#{pMV5>Eb@F?dTyRXSpdl2 z{V$~bRZT&OzwYZvC~6ReyLH!k&t4Rb>OMc}hg(;UmseI6FP6^jo!&jRv9Z&t)h4&7 zLgJ|syz!k9Fm}OT?EYxiGLzqTUQT^4Z5cj($zo*cOuBP2ovCT(}Up3y^I`PY& z{p>&3{=x_MR=#xm#QfbtnmYUwj~?B6<;^!AzjG}7@Z??K-aUEuJ9pt1zI2!0dgq;6 z{O*^&fNsu?efav#Y*czU@Avk{gVog$5zcF`I6v~Hg3V+QZSME-#iRcoR=3rQ_M6JG z>h`=+`v~Jc}h(slE6eEZs1BoLgvLN{af*=Tt*p?Gm zMq(&I{#4$p?w;wH9WrjRdxqUJ+^w&^_ub#`H_qe7;(cGH>tJHIXR?hDek_B_(gshd zJ@&TDpmr^0UBDofF*0;SKD`sKH1>UB@5&IpY^UW4`svv~$l>TNyHxt5bnqv#7~&f{8Pn zWm!)>pE|cSy>)iZak@dDu|=_Nnk)3D0hklu^Wq2Mrvjc01f?Q4tHbJ(P^scb5r+rb z?M=R@ve*Gb7GY5vYE-bllRw#EiqXS~y6P4TUv?w5WcyFPY=B6nYNE^4wzs_XzAa;B zX-;k60LynZCu^#Wl?5nY>Z%R@m#26cL7)R<8W!mUd4o>pbUNi`L4A=6RZ-a5j?+T*zShs_0qiAY(&f1WTrlNY^wFoij|G zr7a&YV;*yEcZr>Jn1}fcN+M!~-+3{0CH0!jX{JvFlXv+wWcZ z_7&~D^WQ$NtzS9+()RXC=dY|A?umKmDhP`m`+93+RmZkqXi0Yq+7s!bTHT6=pp+v? zN;$X25aO7}s>`sOGA$0Q2C#$ALJgam(<#(k=yfZMaHB>#%{-Nd zLZiebo-fUGB+4x%GEmRN%`3CBOS7|I3a6KDFconB6J{Bx`urcQ1(YUc4nMa!x_a@5OYM4`L&=T%@Hsva$DZ557I51D4PbU50x`hLsp?TaN; zD{^wg;-I{KB)kJgy~f#MfO`E`rF~UK)>G8uiy%!x%UyJ+eR5Hq6d(pfB@=o+Bi8j| z?(pg=Si8}6DDv|-x1LjGH}6;$YFOSZwY8O%*WKpAlW8@o`%f)yI)(u?bg0&K{-obu zPv?3{*MfvAN9|q==W#EjUQBIlmG8W;G&A43bnJX_?w#H@d)f;#z4LQ(=X*0R7>(>I zNu}l%7w39ogD@eIG5_1{U`bz{-BAr<67GDsh`xgu_%NSxe?$;xqVY#HGa*e_9xM$F zHmX%mc+7F=S%Nj!1B#y)x`hIvw5o6bdRSmB0vX@#%f}puNl6G&h(%Q~DeAC%FKp;ETAicWtTwe3Ix@b0YrZ1KZUOdOgXyeC*z@z&-=MBG=~N-Z_h*vY(c^o^fOZVF}mQY@l+vC25!cOlsp0jk>D(u&8Vp zcD_`y(=wOy7>i?Q!vm3E_ajg*?-ZXW=a;_+oHyj3>J(*qMST4N_USLZp&klsH*^g5 zw;Ym*nUO)3jrDGKadB~FZZ5&&|G!cv{xCIA-MohUpgdQu*QvsAnxgvvzD6@gYDIS( zIhaGDmYw$AK`pDEvieQSEm(c2>hC$>bLn)vx#i@v*+@4P?IEfK^GIR6wF`TTyGI=+{dTD>(=n-#Km)p+@kz+to>u`&W#3@Mv?|=+|ex8!J2MqIyDR2pMyxZ zDKdQBSb*ewY0(K5bET!c=cz$J5(qa#Agegp`E)+kawlVMiirC%q96$>kD9gZ$gJ)| zspnF!q~1`qpsN>Oe^7pD`9A1qEv;N;ckbS0)62_hA%PuTTdT3!{!ZRxp<;JZQz}iZ zzXNVO3Q0|K$9N2#n2H>fZ(B^p!pH8-98&X+`dd5qVxMKZH)hu&O2)LJaLj+mu78)9+P9@Hi))^6|#hjCl#WhO9G4_Ls1E)t@ z7k3pt>?N}>HD#_WEoHOjv8T45T3n&K+~k={bXedEv^rD>rVKwA(#w9)7qd zcHo!s>&^__$TpdKP_=`2mj?mWnu=~$-R523n7{8t*v~KvdIcjy*z6MDa%lyf9cOPt8iJN(y9Re0jnG4ZY)V@ojGsy*mc5hZAuqn_n;4$7M32 z)~mk41jZbdEg49Zyzn>>s{$n4P9Vk&9Ur^p2@;1lAYnE-CNI-9e=+hbGjbwZx~&}c zTu5Svp-`T1S6+oe6;fPh$JKo5vK>^{^ZL0tWd3)*egtD4Iw z<=nlWI~*Ak=vG4j^C4X5K4YvD>7VhVyr95*K$;$v&$I|Wu4NPtuLdttL^(IpASa0V zDD6yXvvsH($iMW$pV8nM@@^WH@~Eb8LF9>ZES z??v<5U9&*uZsn=9e2~CK8gTul4a7|WvJEXg?XO@)o?v=JO!KzzKV#XqkoyK`%6GTr zjbD+^vIvd%dY^A!Xh16L!-LkcowmYr4e0|RI)(D(|Q&VdjgCl2_j+{7AS}VPe9ESCy#qqr3 z1eMGCE}$r{usl%Va_c~mBjF|%;x%;l2i+an!WUv#tCykgU%vYI?R$^ie){S8 z%k%rCv$t0}lFeY^OdP-@(^8YPG7yQQnj%?kwk9SW2NU1EztCO<&MFQV(1_F?;BmOg zPoji+265|5@Py5HSpIA`pS3_32cAy#C73$d16s5jH_w zgs~}wtnY9VmXdDYQN>BJFd%s|pGpmK4a*u->h*zFE)T|M5f0#iQl9Mj^~U&n?+;+G zvy1jY=kYDzg0qTZYEec!-itC{0Al`x6Mp@{g9mrOP(%@0P-fT-P6M$}skun8{^byI z4uxuKtmh$`6XCr=p_b2gg)KErNF{RwMN-q$K}<^vq^2?cSF*FZmb$DMicTHrHs_nQ z+7W4yjvU`QduikB`Sa-`>4PqPCtn-(od>lE3g&q4#`PZ8yL&1>-~i5ncZg?)kpGs{ zAE=uqHf~9&g#98Y&Isk6Zh{Qi6#f(5AoR6hh8y!$`R)<XW%n4@}<=ZMd;#;pGxNcf0T62 z6e{DRGESBL>(-U!*}F|rzFRbM1|e6jU%K<$1sSMDSE7S#)wqQ=Ifb{=CEJVnGC@h6 zG;h?Pz{mKkS}0ZUy_&q9Ufa76*XsPp!lj&PBx zT~XkAS_Whs6UJ?=5zADvtmPcy9=BB$>omyF1^H|`!D)Npdc|X?Vh5cm89Cas-A+#i zIP z>mSR^wvcM3j-}3|?y3FW>z8WFC7xT3`NHz@rKvM#E=ljp`K3trW%_pM%7t6coV)eZ zQ)`#j-p^$oY#I9pk3^0ESI5&vJCkp{#ldLytv@jsmsAx$K%L)+$-M9`oOJwuJ7&n7 z5wu+eb62(5TtW$QQPMqSxw7ImG6>exBcGM>DSs9KEPG5y#n}4cyF7Y;wo~aFo9C`>Zg1b6NvH2V z`_zjcz5U{Y2gmOoKhQ?()BW+*e`^<~bTH&s*@w?H7~vzCL$oNg4^Lp&eAxXRb#UHa z`e96EAP?OQ8N0VMn{`1vrx3{!C|u@95k-bi&1LGcc&<~M6;!Ia!@W67jRMG%&A>n^ zFjQ4vI6Mv!>dR-<4NuzrH$ARv1PePhf6gKCN)9_(e8T7QC?G5i3t~*>Xnxfe5K|Xs z^D=!aM#rQrHU;+=EvnlxQ3S?<#dJd&Zy7e7uPD8365H~=D5l7tNIO(Z z&B}9eJN0tvm95Pxd6xk4LZjqz{M$Ov!ISSk3Ld?D=GfA^OJHgH@W%SeI~=@o8E|lU zWkr6`GYcm}JO6GT2p70T0QH(XzuP^UHY8YA1;K=FgiwtJDYoTnm! zj|g`YWUC8mQzWao1tdTmX<>`HgcJ5H?!$LA>z-QmZmD%5AaK*1{TGT3Beq31+Ds<}0)Z!mSmK7i!)Y*G+vqF_-fd zwf0ia8=67wJh-X*(h9m(WSI&}!c$;*HLWyD5)=gLFs~_nb27e^&V92$(YBu^&~oGG zg~BHTEp!C52vtONmct(lQTkRGz)+zLQ%BEUwk*S>)b>@!lZ<9jkIDGDGV6JZB0V(~ z&J^qQT>JAg;Juk|&uC(%-JTIpujo4y0=FLQYa&WH3`uAElUMn{Wry0%-qB8t58YhS zx}Ig^TcAGgJt8mnxt2n@TahoplVR;NI-=u!^C0t|fsoT^M9+SEnd)~%@=5x;_MlN&C! ztPm{vS3Co=IkNW-fMie&9cAPPxMy+g~>uw?2X|JSVG`Nwb zRcV^5YFXFQvV9_!yf}a|1<@@_#yTd#%s(BY^wmhfU@@y8eOA?e%#dwvi19t-Lsy7B zPp3Av3Qw=DK6!4VK0R%=Go{jOZKGYQwKrgV=5k)T;JJguJszBx#HkK<+M!Cu`_yg+ zF<=sRZE|93q=qB*=+^GIZa#;GFnL=x2y?)8XF;rQfSA;6|5(oU)JW)Jo--MUr;%~g z4WDOGhPc=dtXAwAg_+P6OWLw2k94^M*4rEM=YNk;s54}F`m-cc7LCSRZdY+ zhL&n6qyrzbTn)<~G-WIvMUD$7mtmzY!<$BnTO9|cxoen(z=D}3BuPf|H>n?|zLPM+ zt|*31ec3bvsl-9fif3frT@0eECj<`gUJ{ncs3qA5dAKDv6r{@F;xJg;>4px*g=D46 z{lCD#ep{_H&n#g^{#wW%r(`R}s<85C1#_u@6qZP$KY`-OC<5Z?8JArsqv)N{d4fCEG9%fFfAk#1;=Dooxc)VIK zAu%gO_$c|^VzJ&x9vu-DnT1L=V%YR+E-u2?s1FT0Zdy7bB8+?~M$>XE-_&^q>sT5> zM{UZBpZ?Vjs(CBHHasOWxeKXJY@M(x71k&lF3{uDV`omBm^sqh+&q2!-1f%m@|Fv} zt^ZG5+tR^%^4}Er{LFhZ+I#0f!_`wW=a!ex&7^d{iC15JbqhQurS^PJ$@yihV9FMb3IADe2Jm0w0Ep&-$;da{;B*6v zmzvkxR?}*K$oDh8|4bZ1k~UCS_c5`|nvH4N4np@E9>WNlc3$8BNkF#0SgC1D?OYre z^2sAXEdNn5Ib^4D!zicN5zOewe|W@`Q?@!XTn}@UB{*%ZO&E{nXm*K>mGSz0AnpnP z+$Wj1lA!>F-L_V0MWk#mA{j53$YA)WCw=3;c+w?revFhZ=Li!%N5%ehN$9-T*?7eg z)pVdyGdd((jwa_|r&DXG6RDeOmuEY-zHt7=ja*HQd|A0E*n|k>1`7*0#jdy?fp|Bg z9O83L@B$n>TR#p1;`h7yVYfe#L4UNRQeBI%%bae&oH?z6bqyLg(6Tw0i9kXQkn|qD z3hWb#hw~_LjztLIP#&VF>6={fdsqY-kYKCe{*a<+=FTYSCf_zpY&k)?6T*0@o~+*0 z$%P2N_5c@8Rxq^#hT8pRj_4%cUVEvZ7Ti2RO59ExJlTT%tJI$-oI@nV6W+k1N`eh1 zs0R7`JYqKU{du@%u2DL_luB*SZ+}qdXNOg2r*#vIxPnlE8Mh0#C1Zy@-Oq7DC{jVj zEy|1y_tBoc_6e|LueH)*4Y*n#7IT^xOJkaa*^Utr-B@KF=H4=M2w>FAeciUrYCA#j z_l11D4FO&pNKbtz*W!>Dr_Q)G^fyCWLG^hWiI|N(Z8Ke%%Bl=;m47I`%|`0Osh3i( z%1rs=sh>?fvo(L`e0fk9tgmmr@xs*?Zr|SQF4h*8mp5N~HW+xt$k7q%pGw59e(*q z7N^z(EXZ*2?=Jzw9AtnHI%tEa03**PKD7}y}z%9?98^?ozl!qC(h+MP7rhm=bcn% z!aD55bDG$V8Y8X_zl{DX+{;NjP+M-PF90gLlXq$m!ux&Uur?@1cAX47Rgvvs2Ab9$5w(_=#z)>9SA%w0&nJFu zFj_m^w_Lx6{%s`brGBknM7R-~IwKdb4XJ#dA`!7E z8&l-^g(fDt;}KhH=U|4Ei4qI z(V6(0?!qgLhhX<-*1Hf)dz$^jnU}Xhswo0)$RoeNLlhD0d-b+s7AnmpZIG|9ZB2NJ zUtNQiNu}!7lPk(GW`5tXIc82&I@^pMX$h1cl&93xn%n63u+$hF&uQ3kOlp12w64kf z!O^v8ShYAGBC`CQyt?nmtIH?2%u`$5VB^Tzk)7PVaHwOlClu!l-d=hloCW0!Go8i32}KP0#F-qL-iBn! zp{Qqh3AgP}tMRmE=?2o(OnF!SKn+$2KjwJ!xT;Q=TLRgS zdqII04Es-^G*Z~Mf5)^G?Wv7!RbW?)Cl}N_Trt&_k+VWsu*x3EnYmQTTkiF|Y-Cx` zg9k-25BEkNUT19Wx$S%J`g_0ueR**gs%`|mSW9D!PT0YEQ+R)(d9J1`rfzqjA0ip{ zBSu=LOD>$s;ME04juj%9*$~{Yg$R$?!ADio15y6v28^=YHOY0w-K>zr?W7_&w0h+P zF+pDC1<=h`mX|>s1Ymu88$5RW`0jf8o<8v6&W8G)%h*-YI7ypFous|hF*FENMM2r1 zq@)lf0vb8TDd=z&8c0=cLMOz+1YD^y^5`Mk^R4UGut!B$=mwyc?W}^WY={hUDWb$Q z(vAnZAgXC)2ApYmw*R9><*}yrm#fgrbKAr>6Lwpt>!q6}b&Pf{)}cYIOcg`FSZZnW z@~kjjic&D{zv81)rM{3Bs4=t##^0L8odw9M{_XQnEiDg0TVc3JUdC)Cu zdHLaIE%ychi$px0Mv%%#>p#d4)zxkOanT@-VctlN!xQz=+>!?!D$U|xcKT4P=T9bN zptMMx=WWZ-F|@fm^fd~<<_{7px2obq#|%NKwKO{`ty4)Fx>BVu7)+O@_sd{$R1P@U z-tDT)U~kztIXwL_T31E$Y=yT8Age$J6Vo;Tk=kEHF3G>{_{WRF-{Kdl#QUxIXb(5qo9v=-Pjx)m`vK?uv!`t!PODk=lTu^PhOAP7?PZwB znM5-T{B_Ouv;=2Vv|#8U*O1PaC|CU6#f#@6)8#mKv_z$rzU;wP2Eazk?zAr^(sBnR-_B_&4%*ySM9R7t~K(zh0N;zg}sdYV(B%Zzo`cK&G&w$5tVnnA%Ckb|N$}Akc=6t(lsFNY88Qp92|F zF=ZLwRl?E<`%)UZ0p$j|rVNGgRs*E+8nMXD^ zGj72yR4OQg4ixuT(JTGLtjQWkHOM09c)xo_H?q?+5c z!AWSlYP&&~Ca9tekS&c(+jQ`uZnyj8!#4xdF%2MdrukqNF{#x$H~m3I-9GhBdJS@!FB88IyiIkBq-+x zgBZkn4D3!eGQ=N=hn=PIBAAT!lcD?_+=WCBCWmmwl6fe~`n;CbRl8Y3M?K=Z=@<|b zW#^Vbu0Q>WPh0La8%O>oF#;OPOkt@gQw+i~ILFzlCL9H1Vkn?jX^ttEQCQvhfefdK zf$jT_s-O6dX^VJ@>A2dvCjE_`cH|l7W}|EWKgPZ^NRH$>uQIc$vNE$W>+bu$XZo0) z>FMdYFf#xKH!#59SR4!N0$l8pyIgW9Qp;P`(k5kE4vEx?Hf@SlkfFm2SqhUX+zJB@M_g=pDJw4Nh*z{pJ zKy7GAl$i4kovDDu%?Cx)<}v5~%XmkXibfZ-UA2Q|r+z7K)-Po(!ZAc-gYG4=2a`|O_NH)b6`x0{w*?v%b4>UMd-RNaSZyh zr0S0OE@7m(6SYLxaiVSl&y-b*5>cC^OIQYe`PVt|2~=CXRg^1fHR+8`Y1ftRM{6}T zG{Nkw+1E5vRHDfD&2wkYm?T@yrc$P*7YZG-Gi`Q<+|x0(vv;L(co2NF|DMECt-Rxy zxjzMS)DBJj@tvP}zwma98SG$(yT2%Nm^sX$Oy6{mu|8hN0%u1B&}-QF*T`ezBv3ao zmh4}i-4b#=ygC~FUE42HYR#zoHfdL7RBN~IcN3<|O?Net#`Re!X#yeQw{ zz1-ghpwImaslH=$nc~SYIR`V8{VBA$+Hd&9B1}rx?;7N8eEN1eosIa;gqfwl zBskW&#xlNV*CXBlZ`Qn|3a(?1J_{JH1ud);5KNQxs-qM79ftL-E|L zTf$#k)3mweGGI`d zlG?&jby{n3i(b(hGIDOpxcNZoz7Nie)3CL+25ZeG>{P4JNhE>*UcPq^=4hHq%2P3l z1|g^5sl$HnIJY=*e&11wzqzO7us@}!D(Z>mLAW*Jnet=xCDp1qBfN`w?yk-W?-wOp zmy_~spgdju>eZ_c_!UzIk7U*h!BqjSBA>}9q665d$&aLla*j~lN?(sS#nSR3+fS`1;CYfxhE4|%7ejGXgG&PS4=YI~W-B5^$6`B6sm(e@xn${x}Zxs3;l9T*9 zd9pLwRqc80MHNXZAp7XS1F-w>Az0qrTwezgS8#4{W-uCoe(BPs7HB=w86KWlIH5|o zM?97mPndB%u|-odXw@He!urVu^i)kz=CDOnwCqW^71S;Q=OGamrZ`_DLk~0R)Ewrj zc7&Ev$gY5v-dzKL7c3hCUMzkaS&pkS{^cBMsZ9BWn6!Pi{k~e>BEtNJ^d*jA=n#M1 zG9&8dRM9qvNa)#RXeXHh_gChTw7Z{JvX;8 zsLjr9_evamcNy^IL9aJh=6cjD7o+L<`NQo;dqO0KQ5fwlK27dZpr2xu#Fjk3?~%j} zI zPR(L5sUs`fs_0at4BP)*<`UDk{lvkZr39Mv9;tmkd3la|+J<&f%~?9z!RBx#sHzYf ztPO{O7EE8lPCfRt^w2Sl-MHFW!6L08?jb zo4MBDo+C*x#WX=bah(VJtxu8S%K@gsC-}m-yq;gXGMS3`uiE!CSLMCs&cl7u(VFlN7_sU4ZD z27y>V&%9Elx`O(jkRN)Vdpk2R-+!#~qt>h>8zR~E<9(xxFLn!R?~`HW3f}7Vg^(By z_LQu}nXAL!avfs2O1I6%10P?%W)|i`OCFK0S=nlpGu-R&C0h+nVS@xh>Cueu-+mxb zC~0l!*LS2}e_5%Zzx3qc;9AFOUMpMaYaZObc1`VAzGK}vyhHKeSRCH89A7G#O0#3y zR+gr>#wUe@VQ-IUTz-m9I#=<9{UqQv{*`;#OF%a?1B+g;e35EL7^Ihdz3X|`K)?RJ ztfjX8V(&Ccg@yrS28<%$U@n1t%~Y7acP>r!M6U_bec!Yj9ZTlknAg6TO$g|eWe#97 z7v@5u>vfDEOO)FdVzrS$QQTULVLlzG0bE^|ZXC&6`xnyH&?F33e7v!-$Z;|4NoN=& zvJKs&oExem25RL0{k6;$exRka8>()#@qK7d;OA&wmUphdXLBkZxcvJ%gHww1=`j|Ce1LPa1RQ zOY)2fD@(;7{Zh#X)f?@Qhsfm#j3lwENME$(WA-hf&lJLZK;1;HQ%22xCkrdtf|CxI znYH~k4=f`?oIxzUVgo&su0=4roR-d9X7Tq`2XL%7Gv;b#?Y^20+K5V}nhbTdTb&!7 ze!qr%zjnX*T(jM-5!2kRZ69xPI6AMV>ifsb`-c|*H=hAr;s|u+Vd$D~1F=w3P0xxa zhz01(QfTPJ3g(mNBzP`&I!W7G7M91|UhaO~2l*>&z8xT+Bn$(&PE~?;N~KrbQ=zq> z7T)2$j9eQ&05$3-k#u>V&w1360Lom!!e!f^w9l`VTHb=cZ~}`s;ZAv? zto{ydwi3DV#6}P9K{u8Kl*D*x>>DP_HpXSPBQ=sadEkUnejTb>PZy-9>SG9OuNT?w zeC}*2=R0Wb@|=$o$RUX*t2u5Wc?f6WGHfeK@qlm)b&DB(*Bw!Z0%Y_C)33&%&p|;{CpR}RDM0D7nb5&qnNS{+e}mDm~GFL8El~~ zZB@h>tWrvwSMf7lC{RCUNsmNzzvRl_K5k49e6J9cY0-D;q& zOyi{~$0lRnLC3l|(F{7N2`^^rlSLW7LVbrX!XXD@D1gX-nIfl-hw?16pgF)Uae`cp z8~L;yk$KbkMsGg!RNU;ygcER2y7Oio)Vi3uAhNz11!3;JYioMDfCI-WZBc@vMv%$} zus>j?&ZSe&rBasU6+)h1)3QIbxcFPfE0&S!xLA7mwque8^F~CseZ$dDMe$DD@r3Pn zG%EA*P3| zVTp+g!!pfmj+?TYW3|lD;)I9%L}-;IzUC1U-(_3^p{XiB8QZOz(rRc5a2F%B#DW(C zP)}H(R)=e_Y;dRcDJFl~cNM^G1+Z`g5%mhlx2V(kH6e6E()>txAvI4YA`eC+Z8%BG zaW1(4WIZx)VXJOz0DY)EeEj_BtM%T^t?z1JL)RW(?QL!Ku0GcE4Q#E? zTZeRk#ws{dtbS?>oWgUjPkU^!`wBRV6HAWWSn^0Sjr#rX41M#{H*VaRlZ2n8v4bNw z$_u@2!w4jO`&3z)b69}|>HcKiD0bq5f8+Vyh72mPRXd%S#JHS^Mgz1O7C)2CmJ5a7 zqcZJr6Wj&N&S3;dvU5t(8=17S>q&Jr5Ta}oeIqfF1ZFg@|De*a%=C8@b>ypQNX zE5d~0Mqto6>JF(cEx=VdlozYW{&1qkK2$ffyxFYPdWLQHfUfss+M;QRUgB8gWWO(K zzwcJzGFBLk!g03Q98Oh__N7A}2Ts8C zz)GCATc@#XBsZGYECroM8%9zPa`r<9kZT7q(t^v(hs$+_E@{N zRof09ukP$T&R0pb*?c^_yK?vT?Z^E`i;o^Wc%1Tbo)C{G%LaZ@f8*Zvfywnu{69a@ zG%$}pYi!aZjQ47*)k$09e&#FV!DtGBJsh?`Q)h)#nlPm$E3L_;=XA@_CA-bX7|w#Uyl7xdn^sL|2l14XS!_XE93G3&QkGvu!yj zE3=&_Ns;hue=SH-oVNWIMQ$hh&$z&YSsqk~kBTl6+@L9eOgbO}gd(N~xx{`i*7voJ zYlGy-;q7=aj;~6ACLW}ok{Y57B-EDRM;ctR! zm5|MBu~O8u%?sBC*LHU|ohTC(3Y%xvmeDJ;s+>W07 zC8aTN9Ogf)*?`}YxiqgzE>$%h>&2>FyH$M%1SnW0%jWWygv(-#lyx11nB;74Es>W$ z%x$Mt1D7{8Hf&}l1Eci*yw&w7?x`inYw~q^V=|U%;Cey2KPRNN%tJSF z5i{klO^Wt3iN&yMOc1&Fj}|jx`7dGJ9M> zO9DleL9A{@iIa8xAbWb3^gVVUdn z*MZvgHtz&CksZ>&azZCZ)Pgekk6Yu#25hB-{;KqSTk&MVtDy%?nOqzGuZwOOGt0N6 ze{EaHmn5#v-E&b%ayB*Cp!}-?y2ke`95OG{%2HhRc+jqusvMW{Iky9{8Otzo$y$JN zwdzV-ISimB^B;@(zXNdA0HctS1ak*r79^?8yiykAq~+I) zucqtjP^6Wb`NxH~lvm6+0;RNDgee#gvs0WyMVJPVnkIw*7$kzJrfzjCVaQxF?=YNU z-dYe5l9XnSk==K#R!&$y({7t@TZPw3mYS6I+tMR_G#c|8_NEs_E@6(-%)-`LmLRrn zT2!z`1Y1kviHWh=v|?|GSWRq8dqexMIx(+^-OB2)Gxw%3J1ZoS6PMq3@IYiWO?=?Z zH^pl&z5k`hkCP&KLMzDOtVKzoFHG~iJsq)0RGZrAbRZ)+8c#Bh!{i>hZvHeL{eY9i zty<0Go5f<$EzLnmf~sK~32&H;5q z5?RT=WDJFvk)mk?hH2((XC-<(piVzyzMRRFve}P~WEQ=UwvBmJagM$gz7#3-?^hke zMmn%+qq5j=sB!)@3kZJ2HMu0-){~5&I7ysYnMw71b2dY%9+KZWxQ>;0?za&XpsEK( z%4IbwhdXh;0>B&<7-PpVQ=q^tJde-9Wi#Hy2|(&oFc83kIx^%b*j!47b88L8tOyGy zt@cf~n4o{+b1&BHv+s;3xAp%f0C~`QD7$?r{xT`#V z_Wv#I^m{TIwzZ4u*;}*d^4cj3$fy%%%T5q z;4_j=@gVSNPMA8SqRe_})VP4imXa|rZ1>AFR2z(R8$byFiagGpU5S8W#dfnr(t${=)XQU2%#|5RB|2OU@U(gB^YQ4?Erae;~hj6c2S*Ee%2;a>O2< z!bm-_uAA#1X(hNnB-G4h?Q_z-%|YLdzh^`&-w#AJ6?WGuwk;2z@FOdRLs)Mi5eUrv zcE@2>iwCbWTUT3{t#`)*aMI3f_e)f$)in|h#28Bm!JV69dr`1Y zkt@Sa-#T^c#tln!I%lo3Q;6)_MBM#2rgLLu*Xf!6BQo7bfEkQ8U!DZb)zAVWv1mI{ zGF7@l9pW%GGAR#^&+~oIPUQqlq+EOrG@Hm(J+tyqIaXyR2x>_Xz|LyT<~U(-o{jwn z6(pNm8Kx}6NPu)WLmgapBUjfg14pdp>4|bgkW4m`mrB2fm@5q1`DfCR%D+-#B`5_6 zn-lkb+)o_W!v8kjbD4^}XfG?YHi{Y2KY#XYT}jC<)t9Er-g}##jtC&cEo7s8hrswQc(9P6lGlhbEGYPp^(4?)i|5WYSsqubP3ec0o6FOF=y2U@a4=jaA9|2D zm?}{N2QlHSJj)jy@yL(v3-1wT*|$Wn0tL6BBy1GB*(5BWQ?M`p6PrgaM_Ba&`J}Y5 zq}*~#Eh$i5DBh`oUGj$_L@g1X_W{UwA_YY1>eT`&T|k$htwNOZwz4H@pbT+I)FmwJ z%A{i(O(N6}WB+qUS<-N)Pu6B~ja;cjyksYt&63@n9b%iViO^|sbeY}JL~F90>)B-f zcvbBgr|$?@F9{J+Mi!|VQG3<#ALW}M&9dBI?K=LBw5U14$kiql>&PF^dFV+?LY59- zcU4Aql7KGF#h%-8tq+6+NfIMrq(nIKBO>Er&=6rNGSf0QY7qAUWR7jw&Oai)DpN^D z=-4cSh2`IjCq1-Sds*?D@9yhK)FcnTtorvZ=w}K|PDm6Ll$5z`7uHu+3fZzI^XE>X z^Gpa25S+^9)vu!ybN&L{yU;v(?hcx7-)QogNAimU@ko{Tn@SUsir=uxcSk;$Td8w_ zeJX-UkauVSdak7!e}1(mUyCd9HTN)!U74uw)T~O#%sLfs(g@*kkaq+KT)~_hG3R-1 zLIdKn_E#}FMB3J6>7~>ZPi}UV{__u+%P=2H#y)w$0s4D4QPrI9$JVvhU3J8 zc3@#&mrrLX_wbQRAVuaxaHCj3F*i&lfD6(Ka1fRS^GW5orKl&Jw@nju4%)pd7rQo(_<#*?+XnqO3W`)SqEvgHfg_H{#TaS4U zO7e)gJD&gfoA}<5#LEBAt9!e9{w-?yfQH>vcXpOuyL_20ih1Gtr|79?)Y+ynGY_>f zR>OTReUwR0S~~|zIKCSnV`n}KyVsHMK+K3X62!6d!rz7x!CipzB_#)B;G7%u;mB;N zF5`Gq=mP&x;ps5!uT(gv3Egp1YUHYkT`P}#0pme!H9f>Jz4BvsaP$zZl-}g@BtjxA`F+T_Ehz^VV^^=vr6A0WZ#Dl)+S}M`UD96G-q0>@)s5w4_rjAG5&*oe zk9v(B?{zCKDfxuvPvhNwv-zCS1R&W#T_4ObpYEGxuifB=5%-uTDRn zSnPk;k0I;QZr`PDfzIk1olAtLc@%ZVL8SA-^UrsR18pFK$ZWH@)7kln?K01fCU{(~ zIk4f$_0AaH2A;?ZjB7Yk6E5*5l03;tyxQS&?~z|6*PY`XY0zF9s)cohv(l_zA->xL zMi)DAwdM;@&ALc064LT>OCgw(d6i8g!*Sxgs%g5`=L+&wQ*W1nRqUFBzL|ZPq^^w+ zO(8-8kdkypmxuo~4-5UbAr?Y@3ZJt*1vu9w%>hZu9d>)dbCmY}|Jsk$%i0<31;uqX z_0`fjU;?tSYq46RB+^~(U8%}&1Vp`D0NA$wIdc4QF(<{!cZLDztu-i9 zvCB=x$(a1%zgLy7t!mmVbm}3hREe>Tb1HKss&1T5nFu`dVLG)$U2|QwE-;m0PX)?a^)`m+Q7mo>t#2m3HfzXQVf` z7t--syS*#>soj8wZtRa-!XFo3nHqpTlHWd~AoTHz&q?}oA?|LtXhoJ)1PLp{{gs~Z zKdt0Q%eI4T=N57%lh8gbwY=(r2jjXJi*(mZs^xWc3FS_&x9!e_3W!>OIsG~_+@ed( zA`e<>`m+_dFq~!3;;xKrpSR;yP8&d*+em&M9=RUVfS5!6XrdC!HBXCVK6hGsQE8F| z^N!EN^Y@yg5zSUA8!59f?~8l1(V+K)KW`S!Us(Z#LoRs?=b`@1!<1leE6sR_?`Y|D zT+TfRZ71Mro^bYGhmNELif@A}>NP}FiPAmK46$c7xPOE9sGvYxQE~2pbggmOP4}(h zypx}I;oL@9rpPJ8y_kc_Y>)nXBpHB7`DQ9r$Y#sw^f`Iz3xPnc%YzgZxSLCvf|=E| zqIE&(zw5U6r!D8Dty`}WkezN1+Z-{;gVrr`k+bd!=@kwrewJ&&y4 zxwA}`k9*X^FrG-Pj+R~yQLClocunQ_HhoVbNZtP~Hk5MXEX=Cu5CE4$i`xm?TZRP@ zY(X^3RpNmYBJ9IuGcPlkQ$RHpTr|l5hz{{tk`@dWWH_-HN~XHshJh!oD$VuK=RqdP z1HnVb(j_@cvV{z4Zy@3;x%|w3`h71V)I+mTA!XU2Axs@0+sx+9{)rav-~R8~-^g>V zwuSa&M7km|+uX|Uu3YWpFJG>o&gJUEb(zuBTcuK`Q`a+@`lKP@&`{}A3sir84+V@> zI2o5O_aJD-Q(a?nuqX3#aI2Bm^hob z@NaG}EWBd7MEV8G_R*;Ba)T#S`~An#ebv;22OdsFzbQSViHWObx>R?~kG1c|x<_l; zruIbo#rnNFr&j0J8->Cw%D#2yD|fzr2feip?yRoQX0x;Ft9Oiibvr+`XED|gK6H!^ z0Ng78A6OAShTpM?j^tE-jyaLH+>80~9;MM&0Pn~rC>Jc|qqHrvZ4#t9;+MC8Wx^~X zRoi>rVybU{&EnFHuB{ap0$4~3uwas)2WWCb!uya2yJ+GC{;PSJ4VkBl1xy0TiVTP` zuBI-06aSgon!_bi3eXv+;9$aWWSi7%<#I35SqG~X1;{Gcx(^MysNo9LvG>k;T8El9B7a9%8H`$~s#*inyr43}p zq!ieGR{MGF)7odX`&->#c=cnS`QRr8_u zOTVRk@-weubNBoiQ}yCaHRB!LPCJz;C}q&G_T@nxe_HTwVF~ZaH6N5{@t%CuNmD8( z-3U*n4Px}o(I8d>QSdin_{aEv)wG;|mK^N3?oG^4L2b|g2xS*3r4`8)gcCwHYL=Dbv;l z)y3vDlJHWgPYWTKZrd<*nop+~R%6!O$If}}hgR=ChE$QYrbGh;qW@p26u^VgDH*n6q zm&pX)+Fu6!aforqZ5K$Kk?$F~RLnD)^5X2uY_oZU#5QP}7{EM{yU5Nib7N)n@m(hU z&Pgzliy7Zk(Qowmfo{&ttv7p}-%g0sy%LQTJ{D~B6SrIxwfRVcHQDSru9u;sWRF-M zoY(9JxfKo0MSftjB2madF4#IJkucVSoPjw>QH_Wu6-++Yz3PGG>T*^n^mv2~6(Y?! zuLvwoY%{^k=-&i5O2a6tO|x{_a=DSHB{CTg;TDd^y?&VG2@b;)y~%aG$6IomC&pDL zbcY`Ks{MEObQ^a*L|$(Y*?uu_;X56}zP@b)JNDkwB5zmP|Ef%$Muj|Usr3XkGUuWa zF6(Zcf~(hgR91pW{(lD>fcOy>>wd4)=ysPX3zg|+sCCq+&^^k~5(dA-qmjaZPG9y#bUZGYy1uB%u}t?BlI znZP-3cCfWi$nhPzyd@yV40&+#W;9w0RecJd8lo-!7+NgLQRqIOxZ&k6P@~vRQ2Hcz z6qU{fCH_<-LbssHLB-_zWA{H7_%)Z9`@L!6U6;JP7l~|PqYV+%AwWK zj4Qjk`5!3zw8`84EVUcIEa{(k{P;2Xz+>uozgIHe{xi*d2P$`6@##FYur zkcD{Io+LU&8$#vKG=0l*^Ta44$(WG=QqB@%O+9IqND|c|n3*9IZ1w#4Y|BrE3WaRd zp0a&EYiZNdStdJDBu6gvQ1|=e8hjk_V8Z7co2eXIh47+_rq5{NYA(Ocp3&|o#=@d% z9$22>DY`L_t#4Aq_i6HW!NiKgr$kzf6MvwIr-_lpp|vw{A#EvUUbWV%hAy}c9r0b~ zQJ(McHWq-a1an>eZqauboA?-~dht}Z8(*Y}ZM)OVRvU|Uw$aGiizM18h4}G!c5uwG zeAv^FYFRLW6f!%}8<`!0fcQh!U?aq#>aZb{*UZ!JukbZG2~GhX%C;i&N!5o?NMoai z(3ECQd73$^s5)%k;L&tJ0qdG`Kgt(%LqZ)Alj;gLyM!e}#dy*;zHwb3n#V$MXp(|cWX`ZVh930yU-B~Nkvo%9pOZ5ZYcT|Tx znFi8RH28<)>=oTw(b^iU3DEJCVC9YxD|4B$pwn)wDWGOQ{_ zDXi$NPvi;+&0+gdSWp`G>UFG&0!D^aGI@7xg-5^a2}#osaf^^ileyZODw>2$4pyy_ zV)pTlDZl$ zx`JxVKC{~^EL^b1{C4KBH$3eZZ&RZ!8Kcvy4J8N|EIt?wlG!YYp< zu~$NTM%VjfRAerqId2?|ubw;qo<^z9f8&u0G4Mz1H^GU6rby}M zSG2pMQHwq?$8k|R-Ps`qjW-$*EEcjf_4_WB;#XxkLN3Bo&ANxpHj#C@ytoww7y*S2 zVL;A>f>>0I&pHw!0S%|xj*1i%Bbq?XSzb=BSFDa z%~^zss-DG)Ui@Mt7Sd(tDrS)cleXk%cvOU%Xry@*(^XN?Rm(S3UZQx^{|1AL6y#Id z!6c3s6kQfflri|QruDBANnEa{3WlsXmadreI;8>L>^qFiSi}}JiQTTvC6Z{eU^og?a^xZrkk^$}MHXqmsKts?j>M>%&VnM0zoW_=kJXB`cMi$|=CJ@D< zlpYuPilmA(a4gwC2#X2m1ongIO=4s|)xqOj1ZQk-g^qika%tx8P z(VLza#%rBq^2SSr?Swaq%bD9LJ}Ik1Z4VSaV$du%aiqF3oKlRuuIN)AE0vZ?tqdea@Vp1KBDN`HIGrpi z%8#nms#GeyK;l=Euy>nMTIHtJ6#?sGI$sF*o6hIPf^B>+Fo(VA`D(S(SSc1yRSq6M z-hbgtCbL#}|D&4^9tsvX*-YmROX@C}U_d;vJmFV;M(_|ge_Jr}=QMSZC zVZ{4Hq_KXM=G|I}Pdmgw%*q9@cbK%Gon~3YO;_{IU>%oL#%!d`DT~p01cCcNH zI&ck}yrp?;8tTWBTnZ=w)=a@Bw-MG|BneqrUqAcdCtqY?|7Qt7k7%emEoE>cWN`SOtRe-rG1 zNC$Y9H4w3bo_pd|oi#u%Q!H#`h6tCMm4>Hos#Y!>T zZm-Vtl~8L;4ILht@iB4fQ+bsB9_BH;@xE$*Y$=RCxQJm6{#}M}Z?anaRFDufu@x#| zbAxr+h=UX4YY7{h8Zq4XHS(7@tL*_D{NoH)5J-qO(nnFlB~(wS`eHwrF%-T0Ylcd< ze#$m&6~%ViM6`Y*C3v*E*?%a6(n1U(I@HG(WL@gpU8>c#`qx1YiK1+$ds@-S z@)=1^N{$oZBV%2oL)JWD@v7Sy=e$8au(QsF;D=4?d26L;z~r2kPb9gvg+-qn?aMaq z7PwUH;ezie@QUu{s-mDN&GH?L#i(Tfc)+6vme{ZauS&INmi&}I4k{{zV4q&TXT(J@ zf*Sc6BEK>VbD5xYGFp;zT#yC@6L0n?MOh3&ExI_fKQ=V<%7Tv`ISQX)LvlnWfwe4p zrWNb5Ehj~epc1c(4Sg^rZIZt{r|1ak(x<~90H@Sm_Cl%@Q_MpWKVTs6tbDPX5=spo z6+Ua2u>HZT`ei}n;a6xo2>1&5*4lr;iXh3dPCImpKm9TDUk5u6>1O3FQ|wv!S}UhB zYAvU#xf(BoJ>jTWG@e@t_Fx2CUq)^w-G4ZS&~rSHq8&A1WU#;Dd0o2KmIBm8G7lU~ zyEH_--X*b8AFT-(@b@+8pMc7*e4OLZ?*%Y&Z$?B5{3k^hMT~U+=Hg;QHY6QOiD_R} z`l!GOa-P2$oM(yIq;1CK{`{9@}y^cG!du zb@nrkagOGN$iX}|PIr=LRkJsZf^|S4ncQTo#Q72oYtlEnRb--W_2Fjl8JFr5w@s4qo@8M(Pvt)-zQDv zYl)fex%pDVAz_MP+rFUdeyh<~^iKk^A9VUZRmKZy_ha$IfQfe}Ra zmgsMKGK~*d7rmT<7s!Dh%Nj%)^xf1-k|vthlF(2d7K^oV`6XQyBt_Bfe`}B{{j#n~ zzb*3M%RxK-m#`|C0e*S>r$1!=TY&rth$G&j_~q$@2w{R1goMU0iDWn&O+-&T;uwy= zm_4J1Ego@aP&hxTcovGEI7c3u-PjBg#xGR@+zMzaj%?V5#o0(EY!&`zo15o%b}kV& z_)QYXtKbtURf>M~J23vwEk)MFyo3F?FJjs>7LU;?L6q43zhk}~*!~>xi07DYFIt$I z>SXeXbS>_ioqRfOn(=hLBihqpdmMY>Sgn>;sDna(XH=mbo#yDs)p6GU$ z$Zzue(;mM7rZ$tsfCNhR175)*T?tuzO#`8qJ(&+}626T1;B|qMK1_U) z1OoH2@d|>XCi34fDd#SRiAnW{%{&eM^T!34OA%R(1=v5|WfUT-`2eAPgXXr|QEg_b zCNaL9&CaNXG1FXItIyPrL!^CUbS^>H<;{(4+qRt>+qQFK+qP{dH@0otd1Bk<S^PxtCoU90+>U8h%7pR@PztYA8)HVc!Yi z>E|`mAZXqk=T=uf#!5btrwA*1{+NgnRrrV{Mb5!StzD{}Sd2WWse!7C`-sz>!Kdna zXhCI${X5_7@AdM>-{FI-Bw;ij(lnkc`vcbd15?`%694F&X=Ysgz?%75UYKqh%zd`u zT}b9PP%cgME<}bRsuS^NY1M-rI1yz+Po6TK)-{ReINCH!i#otGE&X2hzcx&&FBxCq zxsv~Q4w7f{KZhmh6_?qBIZEh*Aj}?4YW>JVJC*pv(9jn*jcI)tQH*d*veDn+|LKWDR#xQMHE%<^`-Tz7tdBca&OaHIlua> zMV5M)NtGy8*+`TbNNy1|p|Ys2eM31$rkE3{PuV(fh@?M}fG{n&xZwfw-+X)WXQI)Cj4QmG89x zzo*#TgSy$6081$5V4(?)n9{9~K{hVr`HEkBv|*nV6-VZdAZIbgfInKHc@L`wz5H#E zdK95lJ#J`2L1}9kxj@XZ@H=&oQ;?Vf)o)IjeQQucl2+YYfFcQyyXR2F1NOUr zZ+~7^xG`~!CUyjog}#UxuGsElK<|PwxI9rsm7Li>+AQeTYRWkT5eYF_?^OSw41U8e z@cS+Tcx*WN{Nr@qqprz6HStoX1U{k$YLbo%Lld!9{7wDcUHxZ{7r7{FFXP&`@z?xR z(^Z3{&s`lA?^nh{hHp3S5$)UwXFd@w{Z$xeqkFZX*1|5OdT_Z9A@2&)t=r-fx?bfo zc9l|B&TvZPX@})x$|kxXfM?Ww1Zvb2LHI{w?}#ZUIb;jh-#2?UoAbHFUY>fx5ak8; zplFG9SSH>oOYV^yzk^&_^KftK$Dv8K*ihSz#TogAQ>qP?~R<=xP68g+$tMVY@o^u zNdIw43E=WRbf%rBgma}tlu*2{#!x3?r$_n^RV#ZMwnHHpA~OramCMjf4QmNsF!#h*v7U##fwb zjdkz)6ur(&=bBtn93RF7KB%eO|0L*HEuk1W%Ch>F8ch7*6DpgTnTZ_SY|qPhz7Ul> zCN_~GdfuNtTGK|RT?nDwu1U?v83HsQM#Gn>iZk*Kk;n$VaUxt;kBlo*B;9mMaWRGj zb?Sy4yJHR;$-tIG_&`Am8{LMPLWTu@=nZn`v4%%Z@;PR68t0rlCn-`QjuF_GYw~t_ ze7+U`)Mv};q^+s{q*jE=FCYu(XmzU}saS+%SI(DAgOQbxDZ+L!v zy5~IRhjwh(71Yow&Kq<7*j~`!nN_4fVwSU+Nr?Z2(oBCrq%_Z1le1m5G#YBdipAUC zv&x2`*cU)&_(%Uw3;1-kzaw|APM@9ZUa`91G9^+GgtqZQETEn=J!?BmD#3Ve4Vd+2 z<}2Xktr7nW%`w%zm&aK5T1VR4p=}Fg?qXx(ZomwWr?tcJa{X*?Ny<}hT#u$l#ixUY z$K0pxRrUU|v>yP!)^c5MOJ9FT=HcdQZ)*uFi>$)Q7{}O$Ji%2iTmOa{1rundx`Plk=>2VoHkm*Le{dfA}yA>hu49^;DcWt z&Z+7t)X@Eao$<=I1SO<1&4G&K9V*F)LD^tJ5os0Vyb&z6>IO$`87-={Nu1R)1wLfMnk{qYgpsbl+tP?vJL~;p-MpKDc<{&lf z8NQXC?8Q$XC@iiNSj;giLe@o(oQ|TN#jy?~d8EhWE7mAOcA|aG*fZWa+&SGjckVpC zae1hC`qUO{I;17S-e|$51m*r&qs{>1#ZTnJ+Qz3|59<>2m;Fy0Af=>MN%UB23Owi49O-fr%}M z$;Z_*!IBz2n02f|pcn9D>>_ev(^2h66~8itFn?2!1ZG*tP#;BE z#EXkTKfG`cGS?u}-@#1zJfT?(Vl7!nAWt|j;l>KDk3AWeUVh9q$L#}#>Obp3rj;by zx6gQK){$+vB?q$T>?hz#sUXK;&sQnf!;E&IX)fPi{Y;-ooI|REmg$`GHAd|h5PLCA z0;>Z9W0*sODbR3bzkXR4`tmxE0U1Fh%W%qSw~#IbT)W#qyObC*T_2f7s_{k%!zkyv zusqCNbW8D=w+p$(uj$h%fP8H>K>Jjd{}l-@W2;{a4z}phrm0p{8!1CT(0}O2m9qbB z{`dV;3wvY7s`kN0AZ1O@kH#Eb5^-yHA4NP;rXK6FMn-%e%>3rPyzSSb=C7dcr4X<7 zel%hnOO(1^hXCW&?SwxTMx0Y7*Qi@*>ypH$DHlU$WQ&fjW-JUns71<&NcB+d zreSdw;6Xu(GTxNdwZORWO1O2l!w5vVcAZ=w(aIq!FSITa|-X^#Nn{@ks`P{7FZKKLqS4;jAJlN_W0Jb!5&Q(*G>Bd3F-Ize2g zvl<~KM|aw@`g}7;T-rU|gc`nC8YG03-+l+tWQxot%~+@!T?G+1BjFwgawdVqf0D=u zmv;S&|nQJ*N=VX>1ju9OzZf#}KhaN$bAzdUP?+hdDc zdbN$N7_W_TE?GCa#2&%t0FQxN%3a-JNte1Iac**hZr69iqBSieQ2kp&n^jf3{e72R zPfA5OldDm312WR>IkS7bAP4xAM|@lZe;t9^Qp0^6K2&=Z-sct6X9<1KO`FWI(3kwW zE7DlTeDbiEn+sO5%t|gkRr}>-bcZT|w+)*>-z12xP#V88EGD5^e>u(G<>AEIL*}Zr z5+RHh_xj=zu$G?l2_&4b>rhnm6jO35KQ%;X2s<%z8vbyguVryxk9mQ;?d&i)rF#9d z^pCg+2O5N;C=DlKWnyOTEQSgovix+f>4D-(awi+RU{XsvPESpjn}>Y$oIbB%xR{P< zm}Zc@zcV>*J73|y0V1zFPdMOd^>lk7JQf;389LD4L$ELQMMf!B9Y=YSH8P;dA&vPU zdHH0304DZ20DAlU%g6X@>+PFC)sZx;S%U)lSD0W9Dq=9bxS&vieJm+XZurknGsloA z4Wv;4nD~n{6aaWIiWm);L?n_R9th_x6C|aye{qYK9P<%r8&1-1IrjJ*nV%xl(Y;Q@ zUe7hz3f-PdeGmzKf(fc4djU{l%*Fi==^obKge zz;Bz-STh(xi?#N_i3r5>%v95T34|Tv z6Q)X(I>v=o`FVPqn@fqts>-dn4BLOR}qMyCSIgUW8U*%9VA=%_^JZf?aSa^#`V zYW}3T^K*ah%JJ#xVGM^*f&KN(<3(c)BziLAGzy7SZ>E_>-t2{>^TSLk zMP<<^2nx$aCADhE=dUHeNi8u4OlQ5i}kh&K6$m($2Ksnt&2LE z78dOCJ_5Tn63<#_5TV15T!O2a(gOf0Sf6aK^tvVUDV?Y5p2Q^w^AFl=DtG}7VV&a*e4dg?%aBuf0$Dr|;f3f1sB!@xFoOjIb#%g% zX+z`3obV)+v*9Tz`znDm!b&pDhv$B2DsjaDsY9m)G!P{pWSg8)FBi(frYb5sq&%p_ z6fdx9sGgynO0~xFB#qev1@YlxApS~r6!|x*JUB*1?YRO8+qLQe_s13cA&hI=kAQo>?6W`lWMd?v-j%^aMF%mjoemnGU(3{I$%T z5rTB?(G^$VXgJS$| zluI8VswF7Uc9^rfR7kcGbaEav#Xt5$Z-1ta${E9W8%>c*MOnU zwh=W7v{+^;xzY);HeV1poGDz$3h-}pw*~*Y2s)HW^>kims;&_rVe{-P#kyW%W8$H2xk@5sA*vu3+O?Xg1d^GvO$ z{JDfXiONr(CJrt+sOr}z>cm|7zf>BR#LPc+4N^V~jA-nWe?iF1$R;EDX7!&KS}js~Jw^AfoW}(5N!pVE?gU)7(Q5xe%mZAYaV!nzr-euq$)= z@lo@-Qe(XLr{Si{8-fy!i03&OPr}7YXvW`03gyCN?1FSG#Ou3q`AER*#ZxgQMEPq> zvpU5nvD`;c|5lqxLp;${$MAcV$@7(w>2=!N^525};4kqFMe+Sq+ejbvU7(n8RDH~? z-$b~vN}CK4++E^tM&=LN(zWspemZ@ZSqW3$t#|hKW5=hV+o98j2pw5)G=epoe%U8X zs}(j<1zUWGmM37d6lt{5#_R%SpJ?p2ajH^7D`k(0%=%5e-s1U8DnePi^0&Z@$bzE} zkN4&JptDa^Nji;Cj=W)w{_T>Cne1_+)8$~iOlkpIOm~Hti#0CC{<4YJqD1ymK`~AC z`5z9agJEnq>^Bpl&47c=VEEqvZ!vZ)7exL&B0Q?+u5*enJ+&M6pckCWz>xAIGFvcM z*T4(xr_;Skx0GE=1`9vM!2F6l6KI+S3@&SYV%>FW1T$51Rw`I_EU0u-0_;2^ z6hUaYar<0D*NW%w0;xQZXh$+wqV}|HO+y5I}o- ziK!NWjxt7M^4gd`OD|4ZQ@~*|KCU8$1S#>;l7!uP-T8bSO`|%ZQ>ITfX3QKtY*{|= zS*@9$Ktv7UA_=e;+vX}FB1TJzj zUE6CRel}ZOyN715;^22+*WWSY5uzEI*q!mChwr)3=^ zVyT@Pkc)4;SlS-7X-rXZqQqd|pvUIw3M&GVji}$?1Kceh z7;jAG>=+zW%)g~;y_8K%ws!{zbMOarD{O0xATb^mLQpAXNiO7JJiK~CzK(;$oErqu zoL+W%@f7a4LVXr8bu2ydxq5;GzH9hF>t3XpVtueD{mR$yGqUWm#u6)_U;sCr5DMVThDB0f(ZkwVCnbSYs^N z{<}41u;j=R+Y)1Sq{QXWJT>k0-#a;8NVeH7JAxJv&~Xxsc5Hbny$_LUT{i$Sn0zf zz$f=5kQ+F`^yCruWwjZVKcO%oq#9*5iOWT03Wz^pPsk5w&5j}aCyPgvOF%w`XqYHy z$YZ|qi?$aGHqxSp|7rl$zkleB;iYj4!TaDulqfE6*LPUIi_y>B zpU->;Uy%PG#xPdPz@?i(5tI>l6~`Xaizy5< zadx{My(pgA*CM_6-P&D~{=?LHX3hEAxSj1MO!F+AJ9e5>WmW z4T!kYm#p44jd~YXrw_0vEXI#KMo94*bytoRi7(kq{rMF6j)#qVq5fitp0A`{189Y+ z2wNq+dJ|XuEv3OSL~81yCq2D9+xfQ2rkS;g?}M9seso7RHLyiTT_;RcO_XZUvB zHR!dNzhF}Sp7}r{NJ#bm9gvXx@O?O0k)3dVUN9jDkAQ6v3{w&IF065CGU5gq1r<>f z9xMcz$RUBwiPyLtDYxh-+vMgyO#~%#t#D0hmP$X;wLJtjJ(iE=Eolk6w&z%q$A>cD z_Mf=C1jLufcBbAQ)(YpeMR~-;AM1It$cnDBv7(O@OSkC+-}s3zcbemxdf%~(cbuX{ z3(bx6yLoTir}GelEgGsD_)c)Wgy zo~oe&%M2hhmZgC&y?5L^PiTX8#XHKrP(SJ!XzPwvPrJyVad2$Q2c_vQajY}8UC}=s zupWsv%a{&Lbx7e~ebwOXy01M85?x@d+#1$skgj)`71>!xa85k8E9X0hcVxRrwhFT3 zudg$)I@@B%oPby}WKVSE)RJq#2gpi%9q~@XbsR2Ep9)MjN9l~2-U!~enya0+xdRcl zND~^nN(_8ljziziNo4hCY3FLO9~~RL17ZO=bB#fSjQ1;^jKb2 znd8I}dfY3XSr0x?@9%|p+24a_xf&b4r^T;42vdDI1V=YV?(Wx+vplzY7;-zJ43{a$@GT35L9?k^*t(GC2xZlQ6$>!}my*HtsAPj**;6__pSH2Q6o+;`94 z^z4&r0x{qvY_T<}OF$PdeY^=ZeOVX3l00`n$5w174)eEOUx&j=92rtVTO3}G79QPS z#-4Vy*ju8+N?KxkbJm}DxGE2MO!Wdf0X%SB#iuYl@mBoZU7Ce+f9dGHe=?6T;iTZ` z_js0+Zd3s`o8rKogq(L&gxlNzR<}3xeXnow1hXghT>MGfG&6K(m~RiQm7O+Vtb^Br z<@>@8mW-17evRl7@7G(%F*(`8<5F9_G<5ibyM~U;nYpwb|KZmblk7yJXD3{rZmGh% zA8qqgGMzL%x>g2qI17p%u2OS3pDwk+xkmDuB$i3r{@&9N_s6D?*X&J5x9IzKHBxe| zQT!MOUC*TZ5jBU`XMMok8_L*sg*BhA13lq1Yc9Xu0*KFuyQS31A^(x%R?dRU$8i;w z%*f3&WwhwR(#tnJ>tu%t`VoENkFYc(=cn=UUQJDN&i9!sCQ#j5`e)X+?ixsrby|a~ zZrXQQ)y(?@#OwC@@_pYQ87&7JMc~eH&Tp><$T;9vNE#cizD-5Q)4UGr1zb ztDquN_QH+s2p1jxJUFJ0^6XcP<3=eU(a z18@xolkUSilDS-OXajW{&sStejy+qlWIP#0_t5t;!)I1>RW=TG)W{ch~Ohrw-pKjh=}e9-0CW(EexZav-_4<7Gztp(;Z z5-kDD)-%z}fE*LoqvL@Yw&^>ijEQj6P)_Ptw%DaY`d(;~+M{aiWju52^&3q1=1zP*-!qxEZ*V z8@vUCJ7hBw5ZdGIcRzPb(vom=Tep)!mg}bT6EI^o>cT;Oa_R0o zQ*@4cHx3e!{Ts66NLgtQ7ql{FbX=vbd|K`KJkuO!0FSR5==A(gHi=%{1{rAYZ4#BlpD*heO^Q`c+g{ywF8f~2@g+?>ZYAb8 zX#fsAUlRUp2=?5-=cb&{)TSI;-ffqUduy5FId~SC!`Yk<9-piE9Y7>Yb~5?)LSxDR zy(J6Q4@w#2XcroTt}9pmw&QK{Zr*xbi~Dr@-pn6^(>KF@zvUe%?zYMH!sFZE@-xv3 z$^Lk7`DPz*ZddJio7f_;Q);W@^Fg!rg_B(|_m|~z)D!(^8;*6lZmbu(?c;;{a^#jg zbB2D)0b!ep@LPETk75n$+!Q zE27xus6_fETwgbpg`AIDtm(2+R@|}ki4Hv5MT%w$4&>)^BG$fa8(y1Sj|N{Go5o)Xewvr5m4H z@o84(zdzHtf06onMSsk(1#-PYu=|Q0I+_$I_K+bJxtjQ6rNTB8M%}8|B_%`pzqr%! zZ>`QZ8zB-8KXZ(lzf30K0huI?b7btwCaWOvm2z#HVRP#mC+X@a3BKc@>}2Kkp;fGq zuuu1$J7tSiru0q$cHXSGgs{BF(S6}rePrIZT(t01F{_;c7>}&)&r$Kz&Ok;823HYk z*6AAzc`6uj|CEvOOmn_zEE#^`)ft8;f0?<|u&Op*6nEZSBR}%6C09VWt@(f4`Gdlr z(~+U$P~hcEk(^ZZM#Z0MC>I!|6mynTKD8CQlB+#w&(k~Ox61^~ndYc;F|8R^cqg6} zoB5vyWy}npgi_%hRVAyLGebqd?chNZ~`2Wp}pC+h)XN;V||7RF`wabT<)bW)yC+ch)YmaMgarf~p zn!`u0I*GJn6OmPxj%zNBMr(W$g{K@YMXVVUK}N4@oKX#y#E@H!Eqv@fUIs2Jx@kSU zD_&n$yVGM;@Jq)=;PjK$q4bj@dXtAw%xzwKSqIl*i90#zS#nqAc&>P(Go>-jIWs8n zJ39MXBmAAK9J#ovxt@8txt_Q4yZ1sYsl@LoP564PJ$%nTd2MMIxm(UJdk`{Guk&$l z8|Z|6^zgeIJ(>5(hyP>Fsp39hWUBy!K|VDRhm zUdrug_|yJedIs8kbqV#|sss|x!(X~s{2dw#yh>4nG+wr$^1dmlTom7TT3RPY%Y>ookFE7PZpT=2^~9RfvC2;Wd<(3{@I%a5a0fMa%%?38`?Pm3`y`M)_7VDk z-QA)=Mh;PhPMxE z4JDflg?nc*ma0oO+wCR;R2-cD3|Hd_g^sOPd2EkU+ANnZHyRD!16TpBR;%sBh6j21 z#K*RJbskekr~rP&I%jKOOqhRZou&iBuv*LUwh2%UsB4nV@mixU_5PK_=MuCyTGAzn zYw`o;2VhLVe0s3XIzp;_ZI^K#&_&v$)i@y61EUD5AxNQVQfSjTEvA5STJF&4iD?hx z<=&<;4d4|q*BEUkvnTviTOD^md*6AK=dpH+`PQ1sh?%dsc-&-QC)>ce?5HzcQXSkA zTRf`#^zWC(>cCiUP-mgG6ZfU>!fR{?6j87q*njxtghG{m8m?^(un;#d8c}y zzJ#?*r!_=fgSx#9ncG~q0!3D3C6xm&(*~)?Vp`Z3TpiSl| z)qI{l-4b7C%wWY{XN_PPseO(;IKBa6Zjp~up6M*j4Il{B+M!tX9Z8s(%xSTDD9x!^ z0FTdvRgwXE*GM}9b5KHay9G`_*VX_n5yHKaXo4`c`=$+FdjUO9i`BCVCHL*u=bElud;xnD zIjS)srT?>!G28{cUlqFsh!!f27DVCv@7lPwiv^nAAnkHYr*j~8wShW|I+c1yl|3bX zex0V)5+q&p{##28NS5pKU2l{FDgGM$>w5zpZQ*KQ%+orJIMevm>QH9reA~;AH&Y2l zBj81T;9O6Cuy^3smcu;aaSrj7$fY~5xcUT?Ka|P~8XPO&&Hj0bA_SWt9GnO zjSHDkK+sh^$p==!&U_P90TOe_cW1CQsCn#fWiU@Cbvd=k4MrV#v2#6M*HlxVfT7`A z>n~5oZ>m36hX2IaSKV|HYEk{SAYZvSf3=^%HI-`87@D1=1XRJqpwxG@*|{*MLsV%= zrU+ou1mcukstU$nj<&&g^&+@*jj$-et-u&(Py`Jw4{2cU_v9R%g+Gx>>3>}Mh3kmcf9^!#&FG~I z%x90-sphYoOyJB;QONI=Gi*!&6nO&V%`9Y*_pnP~P5%$5sjTUS684K8+I``R1OB3d zU7Q(wKDCZd3$`m&^U^rZ?({Dz$+o{qHgAQDA~+gVk%`rW(_xngC5$)0`cn z1GZ6n&RMCKNU9tC7o=&v*s0O7OaEyn}6@6VSr?2g15j6^ggqfiK7KhS;w|` zF_?AX6h=Uutwyaxg%LC>)AO@@?kff4;ac_y*7!XyF^GEMShT=Z_xs<&AE-D);E;so z!Q1*tZi1Ol3W%Qb5hq@O zIdNqA+Y$7r{`)>bx02qpFXB|aA?U)ti=yZZ?ver2;e`>9JEPknWG?(S=4+1n{OfV% zRk`Y7VR19RGlyUb=fryvSUF7X%rmv2i-rMV8cAF#&A03qEskN~xL}+S-=aC`#Yi8P z_ADJPD9vm)Fs%}lm^A^M`TQ(iW6PW}UeWzd)Ywx3O)@a{V+fn}Wv_i;G0Pe%oRrke zD#LVifLf}KW!(c^Bj06Vqc6aY1x}3SFK$MNZhR2ap52DYYx{*8=fxwi+t%i881Rac zh74IG^t{%^k7D^{s#zTmZpyH+xlFTJomU$ej<{`YgHvabv#bE56U=C>kHmc;+D;wL zKEbwr>#(7_1`WGU)7UukAi1W4^u@hmnRADoxmA*R+`{{9N^eVrGee?v!aM3GIYt^e`x;PU*%A)_*s<4YxmYy9oZlCyZxh--CXHB~AX%E{}0e$_;J^ zeI2o;q$brf@88!Fy%J#83#}HHz6T6^@mo-68BJdkT*@3LL`|)AT9Pa3PZ-8zhPSfBN*+u zzabR`!A7pVV@~+V8n#&$Smfdn6mJ+qV%a2IrVYj93&AERhfGt+I`8TV!$cD7kIu`4 z?Frpo*`P=?lq~`df~|tW2E=#kx==bIO6bg9Q+hG69U9bghNM+BI|K=Xg^FgFRxv^f z)ioFA42}K`jZ+(%>!ifAP$zRvu>57#IQiF4n%_3}mNr>qs%~yXP&FaC&S8njb%tdX zvs;T*nvQQbgmD3(w~vTJi(`7OEgE?m7dujo5sp+zlfgAK-AcnR)|gfi1%x(%&TA$3 zAg-pKx&Q2vdODAiv1ig&5W6NA$|m(QuH%>HVR1cy{2f_0hJOiIq&%GE{5-qc59rmhrMDoAPeQ> zZ=kl6YEgQvLn2i;L?djKN?H1Y29VZ(Zr6nPkf9H$vRv?*9#XGt!fHC>22Y3vB0IMYB`cLa0y2G;Y-r1_kNbadzNDxt7+*^716(B&?_cE z;H>G}_d#Dgs)~dQ)GP7lyPVqj zBP>z|%)UALd;miCYf9$e#PtpuMc<7e`&S?h6&}*n<_gtHV zHHK_wgCd^8kTLFfefE6K=#RI=$5MGQK|$_lm4CgsCr7O zV+d_KVQG$lX}ex9v)V5=Zm&Ge`a+GLm@xTf%q6y%A+`UuA0B0|AOvX$IWts+BE}TB zQ;D`ODc6`PWM|Br{MxjCPEUIf?_y4Ms3oYh+Tw1GmpFiowqNgMG8nr|bUf<->mMr? za?*o=tIsRV7*XK}aV2NpO)BQ8CEQyQ7RoR@z=^(HZ@4JVaIGg!Q+Kro5u1pTFsDM< z$koY3cp(=f8B>do7u+TdY|n^fR21+jF->UCW(r+HScsZ>+IPE;r#7fxy&cFKupdaR z8edaE`}-RVLZz!=&Hmle)2 zh=#XKhU`Tf==OU&ZekFjZtE^Kgio#Tp6_TcKrLbyq?UV86Neu$auj-zdVoAR ze(?f(x1W5LaaguN)IMYmCO_oN=uW;{gQ&jN|A^-O#T%ZyuLh%F`(VN25Z&-~i1c8d zVhhd`GSb;L;yTnu5~&MQxA(^+>h*C16<#}XpYuPoT|4bz)G;?FgE!~VmhknUCfPR@ z4~MPQq3-){k=pkTgNP%~-5G60zB-Ya zxVdm4yJAQ+cjMR*4fH3L0lEBYFRbolDgKi%_1|Vu9NfLmR#NUQAFj?SvQLg}a6wfR z=ix?-G0q&o@zlu}wujB-7x@mdl*Igecly46bSe%`^&a!F#J-z2SK1oBRJwL~K0+`gNI=Nz0Yt5_^PlhD!Abf&9#l4wH;JLf9dey;mbtG0RfTrFsg*@alMkFkC$ z-ocr8vW(~&P~c%tNz!+Oik)KOHBILby-Vk*mdM9Xwb$854PK!*ea`~vW5vwczp?*Q z?O{i&Um2;XqGoNcE#Fn%$zRwlDW%v{IXu{kM9IIKM)sK&mCN`fkQ=VJn~l&pPnc4F z{N{1kRMIx9-iw^DkJU3s!eM*xNg*_wSaD8^5xWa9d7N$XC0SwN?bMxZLY)uUJcmYW zeD8&)HP?vgG7~$If%5!qwd{cxX~ILAA%H#H3Nw-L88u=M>?2$;4Eo`imA9Q_lyTmI zd?w%AP5H(yicr!6N9u{Q#T^#;g0<3rNI_SKMrRwK{*^&A9Xm^9@-bs+`cj++-zTmq zDv05UX-JwjBLY*m9CR<?c?#l?ne7$WWMRZ!t}|b0-d^_e8y@g zf>ecVHlbb079Zb|PE0;rp{|w$>&WC4TO%q||2^6r^$8>f9z~snbNb4GN&NGMxJ9js z)t3{I%=~;@27|_L$1ufY^~d`f9&{z6rU{D6^llxcgEZ+|j(L{Gn_)xCr>#kJo*u<< zxRhkoY0MdhL$KDrx~dLWRtQ=x%BrCqHH^P&QM1SY!M%q@K{=Y$Grb+T1a~dmTH@YU z0mz)M%O{>1yxoiS1g8wQ4`(1qAhz`S=Ag}1OYkpu%iAt8OL7w{Q)vr`qgZJ5)G(-} zA`?AWKOO%{m4B;Vjz67KRVmt>F@sr5QY;wO*nfPqqI`sxR-S-v3Se)h!QQ9{Hq8`n zX@93pDBdBGOqd2NJ|*;T?`u-%l@>ndBwTc3ZtdptG04268Pzt+)}lbFR!zR!OT@Wu zRsXW6h>|*h*hAu+CPUdKvF?8~yLNEu&$2UFH{ngJ*+hxppdeM9wWqBLfq6C-fJ5d!a z#n9Ue3sA!luHH5Nuq=x0n?pZF+>lffjDgwhPMKLXlDvFjCW8!k&amTMN37^L<7L$V z*Q!LGXfoBU7Gv;f{?MKH8(KlryZ3e%c^5Oq~}q%t3B0IxA=H@S5d7u_iuD2%jEikUnWR%LqIXz2d+x z%TGhw2V*TvD4+tQmyuSmi`g8_SX1;;)>98(E?*oZ+?Raz-VXCXoXxmeBckt#UTKo{ zkN;A=8d+il)ewx?Pu|5&aHHk4l3fB)vo`gM{HO~!%J;gwR#wPF^BuC1sehfU9G(Sg z11HoKPVoo@jJBoX?3<7TW(dw)zrC(?A1yvACyp-RU>1&|)jF<28R=@zDHifo#%p6M zR!LQJl)0MTSg2_{%bV=#s&C!04><TtCn8_S+PCX0Jesu{d38x_|9Y+`F^;!fh(q>%Uu~x$OAh~=$0Ph zn|;3W`xrdNw*~XM3QKrPFV0eB{^;BTqAekSOa2|pLM@wh;sxfIkm;!*c=*MFpohKh z8roC1I%^+G)Fvy~h`hOpOH7pB-~i{)D35Dvd|9xq4Rmk4ty3$|6?T@%Jxj{dUND^d z^$gAeVS)%Gk^Es`e*JKhH%jIdhIB0F;#BlS zXTt{Mn#JtX3?w0FBmvGdL2T=Je`a%JXyLb8X&fJYB0-5B@@oBc*b{NXBs`j#JBk;qJmXC>fs%Cte@!u(|Jg$MPR zuhY9#+W}OD4}0W1mC#~inij#V@IzNeaB&qyE_#P5r+89ZB{rL&HOJ4Ji7vbx6kh?Y9}p9i`D)BCWkj)Mzp) zC%fPjM?cg7^BR-ygVEV~%Adt}desKTUpGz4Lsk66u zZPvY+y|Ijm<9QiU@`rXs-};!C1SBzipXHmG3C~Q2Y>sCQ7qKzm>u4>>>9Y@+s=znn zE-QmZmohb+=oe?ND97-mw(gVztg`i-Gp;Ng&bf@m(vL%Lc%eS!@uY@~4b>@Y0~|;d z=$T~UoQAOcqC~YaTI0pU(aO{on(gvLUGHw*XAgj_N+YpW{LJKh#qs3v2H?pMx_OS$hqk!(_xg;2(dvIZ&izt z-AYBnXR@ZE>VO^qOv|zIwR9r+B#$|uJ~$pfGf~H`Vnc)aZeRW3#YeGEvS!I3GI=fX z&(nvkWRau=e)UmOfmU=WFPYf!O2zrH^r)(6Ywqi+w*k(@Q8|4SQka?lASXY`NU<1dn`*Yu& zM(=Estc?$)$tZku_$C)C!BJDFO)&edG53~Hadh3EXpDrQ3GNcy zgF6IwcMsCIyETLmXx!Zzcb9IQ;10nZ65JaaZFKUyGw+>w|98z=cRtNsb*gIH`JLKp zoe!r@?b=%{?@a=MLcgwBqr9EYFjSKeX(odGEl#bJy_jUJ&MfAg%$XcB<$y62wdNCg z0t(TM{^aerRWvbCr$y5_GoC0xjQUip!4(N=_s7*VF&SP>cYMs{6_$un#LqV;2QZ&z}SW|2$8 zZ(k&e7)bk8?rxgakyt6Ht2B-J*CNs<WtZGo_vd2-i{ zF-_+&qs=JtMv?I>`*6n=ZBAd~fekC1xc!?nynS|-R7wCjlL*WGpEyC8EfLnoh04$4Oabl}o+GbQE7`d)_u9 zX?+M~6y0}x%!9y_X$&Fl#o$V$?a)on{_A3Y_6QN+^ z_~bbyDW{HlfLx9PJ&Qi;Z~he-9qB-nf|^{P8?jx>=r$;l>HY{ac?15gf;$kyb<@Gh zZh;A0G`X?p0xEm}PFHKY(-*wenw`KgT6{-mXeNtt$QdD*(&fLfss6Zmzm9M0h`GYB zN1RlzVi0H|T~6OjTeHYU^5w}#28M1{A`23NaPQu}7|5g_Pef{t9P zR~Qe0I+me!#kkNnADfKB$+63^Hiacq&51cc`AlFHgiu1vR*%vYU1r}7*GOQ8HFEBN zG3kfOYFR>8t=}sY71yMjn4c%!lkw4H^KFF)o1J9Z(qF&wsAFS-HLp|FF9tNH_* z@cl+QY(y1KfC6vWwR4XKT-y8(A(dg?boBR{!uKsgd5>~uJ!OZni~3d^v&~FX8yC1Y z{3efB;h`k$;RLKSK6l;HEKYOVt zM;8S~yEOl;Myy~Elg0tO6bnG$m|x{|vP_<16CCg|MwLu5b+IM!Ch>z(S1M`*b7^FT zKZs4!mDG0)$G<9;_6`tkWj*vp&6g(8$|aG9Av5D0)4RliN;y%Y?DtzTKe5G`E*6aO zsRz3RwZq%5oLZGo4x(*{q z4%Qt3#z{nB@Uh(Asy;*vj-89rW{0ic1Ub9_C(-(m-;)SxU$d@YfxLB}1`Uu(ByU`V zv7&W#$a*pq60@erg!w9Mzc2BMB)9Nwcka7AHYw+KgNo*6>Y09q{zkLt^UPaZF`vze z;(g0eW28^u7CWC)3_Sf*PYhVgu75?r{sdzDAzMmyB}n?I&|zdj(tECQBzL=Gp~?7^_v!Qel-pxc9A?t^C& z>k51P*6HBxvU;qHoox4>viewn-2OP4Gx1!nMloeUYi6vMtrr=(B~bj{I&Db&-E_R~-eZqE~K+y?ztAMd+tM?`24osETtRY~d3n$J;ddUP&ek>|(}!g-W|%Wwb2u zZA=)ome|@`J^k(z~%9->dumi4aD=vvsj@-FSo0ILrPI4dv;QdW3B`tBkYd7d=n7Zcw`;Wl6F{EJ77QTK;4MHw#scMp zxi0Q#Y+b4GBx@5ZGt4%#xV2*1GG%|KHvyIq;Cn*hOUekS<-2lPKRXn^#V>LL&QdrF zLm53yjLkJF{0Mg&qZW^zO-tq5xP*|ykGa5d&q8gol!}<}T8~l22{&2E8;Jo#Tri!H zxB%G_sfH>{?xxJ~SA${r{g>+H9D;hl!CR1FxM^Nl^-u|=2W$R*QZk-GT!3Q@}}x@w8|!%Kgnn02ly5(ia}5dv`_c7Zo>w3 zsWGh-8RlCo6vDbY*5u`KVb4{&hsVtfEDG&;l1?WVHovOetG4k8o2}2!{3~xogt>KD zRPbBXVS7b$8dH`-X=c#u5#~}~nB&wb?Dg6pZpJbND_S`XhO0d4NvCzY{(;1%Yrf2& z<)^0Qkbobrham2(A$*UNmAJBWtN5KI){-2Jk|ty5x9GaW?U}7)%cj-uB`#ad;Ze5V z>WM}3Vu_?qMmFq38@bsVh5S#NDk0aP&bgXp{riTK1=!zm1Fp2^O17A?AQFvt0x~9+ zSj;Lp-z8+X=2{!vq9h7NGj8Um@xnE%V8P2+QdCNX*QmsaF0(8|csg7`blFSg1ry8G zqr47t@*3B6RXfvGY6V`#Z3F zzTc0Lu-Q2OSWM_?lt@%P=w5M=f6zuZDSp2%XB)kCE>pbmv}>d@-feD_BbXTnvnFoK z40y=YpYKFHDL?I{@)O!z$%*IeY%Q~txs8lh@(gE^_K=c}Qrl{g$h%|bwx1#I#HsgO zq>N!}5Jf9rc%DH1IuGLbbR1kKOMi`Zv=zp7Q5n#`@$=&`&2+=x5~fKBv(l0yb90Ph zTqU{2M6SzxkE~zF`v5EQR?_R_X64c^iNcO+I8l!7YHlSBCGr(u?G~|AztgGa(zCxa zW;^(XMs)-m#RBGrUHkp&QEd~hafIguj&-v5LrO&Kt zMZjt{H^z~Tovb{5F~1BwL?C7hTOk8Kww#+Y+94p0aJq;T$#I^Rw7>*Q%#5Lx?Pz8v z>UVZSWu~j})J5oQ#vlnHgbT-hyIG-7M~&iE>aLc%7EJUj&8h|YWAK1o?C_FLM{!AiE8tH@H|N5hx!G3$zyI_SLX*V5&BYPV2%@J&9F z?_+dv{qn^7PXcD8)spX~%E2kV#ciu%<)2$8n{uu+4hMhM-PH#dbKIYKc%v^hoFVF> z`gPH@^&1+4yt4Uxj=On=#39g;EHCoD?{gWE%PlHG!%yXiHTIVNx+s#-C7;YD5T@Tf zWtiuzqxxm4=OBC8GC>0$ezGT^th=nqcW)awwBT}O_zP@sJc7~CSO3H}!(InUz#bGw zY9fy&p{Yk;ig{>Ia*e&1qLHNk^-fLD;*%aWNJG!ltBi2gS3C8wLi&uMGBb*STS}wc z%fBnv4wJvw%9M*V=X8;({yrOoOeSR%DdHks8h-p!Lb?-OZcyV6L#qjspBhzuvP0oV zD0e!{ul2X0(d7ZS0gq#cAVVCR!Y+r+`U576pS`1Z^UqD$XVo_)%}7i{3Vh?15H46)-8 zO1>H~_q8{-HOz+^-A9sFSySw|={(e;DajM8*?8r3vu&}}V_u0=J@fo@M)G&zrkY=g(C5BDK&Pd~=w_KW`tgrFwS(U3@OisSqVn@vx zK@fw7j-yK%?{qBC#{T8J6nwVR$VL)o{jyQGqowb$q!2g*+Qafa9VLif=4;48 zCd^4JaXpR#la!YPN^_+=FI2<=wiRbP4+rB}#$q}`jkpgx4flj>x4GJuF7`Zam9ofp zr|L%qC&50-9ieuk^IHz)rKfK8lfHoC$x4|+;pyb{)y@-o_i2ypktXTgz00&vS1I9! z-f@2)>ItT^J#MXe_X!M_qM&7g8lAG@zSb&VhgRD@ZkpSPWlkSKTG5~;3Uk-qqo_Xr zmea2FNQd;u&ciAQMlR!MaX{0AP``lNB3_J#-BF6f?ZJY|z(n8f&4@3~@!{1fksq

    bf1j-RUu+%!is6xL9zBwXVH_v8F(TOj>b8E4GBt;|&sK$MgT zS$(mj>i3$)MZJf?uG6OW)hfks`fH6%mY9J=_zxvQ1p^}ks-UQ8<7?k!hYpO2JBQf@ zwW8KM$zQVxVL$~%M%nK>jII-|uJUYWAM6ffj-#4lGwg`R3mlkPI7pZqIsz3BbZeWg z%+M!tFk&P8?#g*r%A%SfmiLmpX}F#qRV6E1Ja1eHvQ+sgr;B!ZFp4nq(YnPgoTl;O z`x1JaVkZ;c_}HY&C8G>>FQ3q$L>dknaFicd!j=kV(prCXqCDW1-W2-P=m`PaAgU(~&Ta5MVRN7!L(n3@{ z)l)D%)@T#-%Rt9Wy0l>yDIV?jgake20Q<48x^SXOz!`SWxb>(|L5{@bi!ICb%=x|I z&$!LB*nE^})mObllvL0GZi>6(x)>BVBcaBL0_N^}V|4oXk!J^P4tJtjn$bjrlTHGu zo3Wiw*Suh52V%xRL@f})7C85izxzcqlyv06@0|~>Fz;1_z$de(Vg|ptW$A2&y&U1E zrearJv#(zL&~oDtA+W1)nZ^vD@rMPd_Knm8eQ3u0WQe5-pbITe_BZ89rl?@b96#Hh z!XMNrVa%eAJk^1W9=ocgtf7im_4a}G~&Jr%q9&ffTo7uO#ay*3Zm$Dp^J z`(I58&B=jxmj1;_9sx2tZrWk@Fa;BHOQzsFUWEHsbC0(%e1hw-Mt3A79ONkhx=rFf zb?!npjkBpPk$#kRzcB7zJeO{TM(t>X4>gl{Nc{l<%^2%KLHN$`p+5??zDdY@)w}nNs+t8tRdVYv^ zoz2YK8vQx?qu)c!br=07+)k&YaO+vh!SK^^?nT;=hHK#6e$pI-DMORoyXyGnl^{PG zyBu60=R%VM!DQ9x47T2@{n~#MZhTF6U)cg|_4n(_>JA@ND)D4=)fd5_uBSJ-@K&_3 zNnZi*xVFvCk#iO_4oB&XN5f}fGo$ZbjiPLr>HEs>q|XX!t4!@VjhhmKj!VYrn_a@^ z3VF1;8aY|^VzEssY~Qs$o0b{Sb{Kc^ZtCwYT!dpWIi5FClqwDEf>ZQn57+l15(`|0 zhVTvW7aO9 zgo3JHcQbrVgo}-h_7zkSNHpoI>Awkb?r051HLJHZZ#+i z0b%Br+F`sTy*@u9glO2T41FmD;Rl<-0)_834L=#hx0QdOluvvI#IJ5$z&Cbbbjg}+ zN}>LP!*4HsC=@5k6Dq{fC&4$9IU}RhwSxoiJT@Qc0woDnF5Hd1kN&bw+}|l@o=96C z$#V_B4fi{U(3<+2NKbkhDVZqT_e&!6w8Sh+jR0MJd8Y!Y&d#BvhlOt2o$xkq@vH!E zTU9{Yy8K!-RsDl)`OQRTse!JE9Ql=(($(67qruZg%F(V01W?$iK&8Kd1ljqBK9$rp zPgS-uJ^A+&il%MTC|TTvblsO+dzfCL+qF{1sGa|)_PJ)0C3W#IeFH?YF(`1Z^jPAM zRD%Aka?)tixD10^76$^^ln! zL$-An2&X>an9az2RqT1d0MmiwA7i=Hxb2>J40qqyC|%W!mh9t^Gw;3mIhUbY80#*e ztZE9AZO{l<_Pr(Ey8HruN ze?R}@a0GG?F8Z!BlgIByj$l7*Gs40B4`*^lRPDh`K=$5Q`$H*5h~YQvZkoc(+e)!aGNtZe?uN zluKPbS5A3?Ohih@qINsY&SjKmxyaByB_+m1{tgImNT=kX?WlMv{??B8xWT==aND}xUDZV$qQlIYGILEfZQAg zIAYj`Zsv|uPi4Nd&*YHao856ZwZYpPwX9sc75f&d9+S-V)Xsb=t<6&h&gU=*=ub*j zN{5#yCPs+Lm-+`@Qzfu~Vy39I%=^s*&0bzGU*p@*bSvd@yO-sS1rLfycT69@FC|bK zITyRqj{+DTY2Uf>KBbmjzI|)#S94tS0==ndz?+8Bfz&M97bizdX7QWE6r_^&fIEu> z6&(%W_05G?)Wva@^(QhPx_BF;v$r(T9B!8xHPk~nl*wuG5&-b5UA2qEoA|TOH2TI? zqV}~I=gl8)(;RDh;WpaoG?f{x$*(31+{t`{A?#iuj7NBe5>-rG_F%9ba%08%ul;lD zD6#D(Dw-dkzt)JnL7;8p%6hHik7_=|TDLKDEonVW2FTkxE2k`nl$#%OHUzD{vAcw$uzm}pZC4At>CxmSm2*LEs(1s!`k)sE@3 zw!qvEB`vJ?S*CDW<+2*Jt^{8`ja|xow)b0w5t|Q7Es}FND7(R(KxO@O^_qB*D(<(G zmvSd?20n=1MZRri#3xa@!6ajaP7vmf=+XvrPW=T>WOx!#MrGryX%t-4(0G~t) zq~%DegrlV?eph)EDVl)r-kgp+^{c$;F@H0sBXC-@OKyIe?WSkCZBCl$wwc#>)J&k?P4|WDg&rw)%omjyih3ODaywhcep2rFQk4u2HoK(dq1PnKWX@a4 z79`gp82#o+2tgW|k2*`B@@7V-{WL#Aybd>}KA)lT#dkUA{`u97%t!HdV&Yz6IZLee zEoHt`+>}#NUc*&&^?XK>i%3g2;tvD4%ddgmq_ImxQ$M%gM|Tra&WYW16+>`1TmY_TB=|i7{t!`59)kMf&~i>p>b$OvS}`$-V`BHuoR$ zCq*Fbsqlr3JWkmheyWMpQ5S;w^D{pEPsgq)t%rVry#3W!4i4iD?yrEMYiQ0Z_Hi~a7;EueI7R$q0nk8trD-U zc_e5O?G=gcjItr6u~tFBT8q~%_f~$81Oq=t_AKdJ=gxLo6~@GcJb&gS<0r~vQF{h5 zsg>#ORGiP(O3bT!p6E<9os757azIm_pdID~m^%ma#wg{UHYu5hIfD-KSFaCC_3mYl zavnRZO4*+8iEozIFST?8tRp(zDpK9UvAJCYdv6ix!3!c*B^?Lphh}5p8&xnGltL4K zN!2iJ`42y7`I<<>#ih}BD)psD-8Wi$)C7Tga(1PEYm~FV`!rd5AduJy?oVdA>pIj# zVK_vj=hD})Vhf=5Um7MvQ2#bXg+l1QEk=B%AwNAK4RoU(}5QsTD(8k?S z>6KM|$w1@$M(2lLm5$Qf*!qcMRl(Mgi;Mio@{0b9{>?twLaE<_(c|S+dE?wOG-qug zBV>ipNr%a$;`v6a&719rKEjurv|Nzf0E-NDb)bFJZ7tOkF?;>Nmqh2n#^5@rYVC?- z`N&MZ%;0O+)!Y^L&5OO`it}#7#10}2*JO+S!$hB6&6?`f8w@KI*XMnkLyxAl!?dK9 zE;s&gi2(XICkyhH?7pTkFJYW<>^JXXzMlB$eCi2Zm6=Vk#TP_ciE4A?e@ik!zbY~` zlYBDx9@%2!`;QYR&&iWvDG!TR7uH7g5Wxp@l1_+J*hC%a%NVDKv~tKJGbD}~KP0|J zrJ1K%*yMZ@F6z>il~BD%e!zY~(m}vhB>6xU>EUg-dlDH5|HKRr}2%2{?1Bq z_^qm`0+{yyx?`&;_P|hr0{T^XuEPqe=h4 z*uKmf$f?Qz)4AZ5)fO?;=HTYLEmHPpzniNGufD(UH^w!OC+GKD9p^dTR|30E(O&)c zn{8WALs1{Ehwj^$+liwRSj+NM4Koka{{}OJ_V+Jw@l1+|Zz_X^0GT~{Gbw_|+wKQH zVvnnJh^man>42St`!Ub)!MCUQW~Hfc+ty3{ZVx_S+4~iwK1_iLy^{^NJoTgQEfM?Q zXoB(db2|Ii(V;_RQq7Eh#``rNSfnIKdOAl+&QmJAyp`7_*7olnUPNECk9FIhui+zB zlRl;y?5~Tk0PGpeviC~gF157vg%1=-YbLg#F)6xrqdDq4lrUWI)ZFug8I^LQnHri~ zqGgHW_Iu^|3)|yn2Iei!?0e zEexB^XnzaA1GwJtK`p-ZNE_E9v0+kCg?xre7WMQ66irGS>Nt&|BIltgS( zOILWUcH1AZNeYSWC3HUGdu>*Jj&g}uU&sE#lv}3j92G`Ku2TisVU06uhO*aM-F0nyq?R{XS!#e*=`o4UF z;peC2r1+~!7q3j=#v;HtHD54w*SU=JuWIcdY6N1X+;6nU0?zYn2Z_o>{i65~R|ISN zTe3Abt^FDEH?vP<`}(cmN!xg#PmY+rUseu-7iBhWTN{l?+lJ0SZ z)?s0$83Rj(yrOe$~1RESSvI zG?|?i)O7N$>zkY}YP?EOr+SDr8Mh12-ikgMAKgO*R+Y5%$B~=oU5##eTp!y(Z=0b8 z{@wv<8+e#k)xS_(Tw}g)jc{p-M&+|4Nxs1*$xGJ@EtjN^0evZF&%i*1P7;p1rEqLJb24QiDBP1Jk`Y zSu?YAY}O6D$`ZmBhDd4=-mT};Xkxg5m$+tPNlcW{l3NK;i7NPhs`NV|U@vg`PmQ@d z*5kx7Ca&+@D32AB3>tlD`^lW)9#0nxA3sn>nzy%Y(@yu4MX%5#kJtC(a@L6~Y4o^% zc`hXijPkgo6VW+DCTDE1VAwBb?Q3&Jc}r4L{d}CZM|)QuB2g`R4I_XEl)C)f2ESrU5>uJ*@vh*V>zh!&!mF5f zt;RgY&d0r*#-6MOz6a&K!mPJ}W;C!eyzU6Um=VIWe)|KO5)N?;EmW48%1XEn`;@c; z9i_`Cw^~M;<6l`!w$kw9{N#zDeBxc|PSdD!`-=VRu||^{TDfomwwXrT1c*v7zIZ#! zQ%nS@WH3V%i(L};!YDn{Xo~61uz)sfX0I1^?|VW>eDhnFISV1n&b-Kq2(t>9dBc9Rfonkpl6;E!X^MG) zYjiSkhgcQO3UoM9{WWcMDTT?$A-GaL4*kR-o3o3lU~a_P{?G~129_kZ$T++($~WIM zp6{=lpIZs2ThfC54#?Z*wKPop=sA&`6(~Af($KiFn+8=dSOGk>l5i5zW*Gs|R0U4< zPQEiWEN3|gTL{hKNNV4<11k4?@F1w@Tt$xvxg2IurQZr)Y1|=aJU61=)4KDgaw5Uk zgXecQADocAkJ+uhrrHT;LVW5T=lBxmLy)YHZom|5FSokE|!Fbub!aRgRX`;e?A|{Ye8Mi zn#fKWf5xzvH$dACSE?IlSuS`h^%F>YTAZXqJRW&$);#;ptZ7M0v=joKwYIFb%jY`n z4>9N9c}BwFeDK7>vN62{xAVnMWeuyO!D@kicEexxc86=MU_w5#M0Q^TtG%{a4LqK( z*kAj?CvsYzjaPftM(B?EOf5D?Bl;iRz zd!|~?h}8n+gymrYLs=9L&JvbZ4PJL+;U7WE`1&H$T8#IWsr8DhzFn0OT*W*?_%J+- z@5Y65+wE-*Lm~wUQ8_Qyyk*gq&KdG0z^N6SG}kT9J5BxA3%F%{oe6#FEO!pSg9pHC z#kAFK+O>I?eFIvUNMb$LJt=D?jaz8%?Z~}*&XLx9@pc!8FQA3+#D|rVe0BET^(g#a z?*>k!fh2e?+P0CwbkNA$Q2h?l6aNu3mdg7#88nPd8$VrlADVJ+H$HcrUv>#m8Hb==Z^fHEL)#(xK`bCw@;Kl0o3rrd;3l^(+Szu<8 z{WzE@?6i6>av3Ko6C3YLX7O17x3v!al>o&_PfjXp?Tfb6$Ya$@iyR|@*X*Y;x^QL( z)a<&-{KPIoKD<==4=8kgse8Gg&&0^DO=qb;gBy_NId|c~ zp45I*vvapfY@tS;{TEsm1r$oZ{~h zgM`e!ffd4)GJhx=MwyZ6B6yk~OJo;768=jY>|Ws%YOC}m21N0^TdQf`M;qGHc4*xG z&}FABL&;nNz9?k3|q|LeO%4J1*7bdDeU4V3be| zMS%8~nLl|R9g-IoI!xPr#(CW?+R>!M*n2pv*x0<594`%EZ+u+|A&ro;;TY{7tQHWOYtU2eI5bLi>D!I*y$EL7?U+ zCrJ>hwL&xw(OkB#sGHOIZL*XS0A@DMK7l_cPWSK#zf_TDI{o-O;u+2XbIc)>H0|KM z%QSAA&S7r8f-2pqWXz3TXiRq88;N=VQ?E@r_HtG)dsZEE0@>{xu2BY8QnWMMD_Fc7 zM%^k0RiWiwIh6xKt6wF~bZQ%b#%03L(UeeorN%a;@Of(dOGDqLXu6FFOg^C_bfckuf7c(v#LcmTH=0`q)2tzIyE)?Yt( z1|3b(o{F3cgKsad8hE#P@7DVLwt#hrbgK9R|j!*a7auEX~1BrlkG6gb&WEOOf&>(60)I}7T3 zPCSRh?^#A+P#yCQgU5A4E5DJh>rpx{FHzse%aX0f)L282zxx3@lg1)IM9}CUjO;XL zUAV2S>tTG9yv=_f{+KEXL~QR&Cc&N`c0jD6{)oOE6{CPN_#SKE5gf`ExCh@#1vfve zjj9+wt?!hu20p`QQo$_`!K0$>4+W#5oeznlq8$$nqoQ38k)xBwPq#ZVtbu>vbgY5r z@Qzei+r#i^SF8UmJe4&N0oP#-yn+{`c6lQ9cLrGlAK|>L&v1cqXkX2m5%}`-=~T}i zX58tAX#M_tdb(F*cTIjOfwb%QYUWm8*Tx{<4-wup3W$J9QWI`R+i8dO2R(2iEGkIZ)?NsZnJ{}jg{ujGO znO^-Xe=FSjQ>=F)&USmxW5#88$)mj-MLH4Q?X8H}U5#^=O!DOi=XSdbw@h&4Mk@j_ z-W9t6L4=H3ZLaWuR~}%u*W+wX0sNhA7a`;Ln`@aSYY(>VRu?H>e;#DYil=JU&*A8~KcQ2>q>ZtkuCQ-^<%PG+{~DEvI^pD8|5{EIfA_-sJI z{`vD~BMJ&m0}Aec5$rtwqWF&L;S2-T1$mSl4?IL)tGD zFuyInIQaWg`IWPXrlIDtgXMfb+fO~OnylLJ+sN1FMZGIS{w>HBSRMGJUiOFj&c%Z- ze}L(d@arnDkAfO}2)>L)c324fx4eAPu!JqOUTGCFfuo9Ekj0Z;%Wm}_ ziIKNpQs|-J!KmApwm%#%z=G;K=R`iM-nVJ*6D=q&w)KgAHh^Sei8b+Z@Mss-jTvG& z2a=Q+yF?azz+-_%yMBWy(S)wY^Fc~K*&os>VNGzCEjrEYV9AyTM!WWQGg-t_1Z3ky z``2u@pOQWzgK{#hB%)ulo$`fD-u!g*@t+qU*If`0@<^vE?%pOgZ!`QR%k<*x@cT#X zs;BXBy2SETEpjp3tT<5F?;p06jo4ar%rY^7l!K(sEFD#{us7B?85+W;O`Y0%^ebN7 zXoLR9?~p8IUtXeK(NPdZ;D9)%tEBmQajg~^oVBA@y>=2_#eG%cYV$fmLYgistrnZG zHvIkLZ_32fCvWe0kywqK+9~q6v04L&{nXhrD(BA&QO6TP)f-X6_unay|F5=ZKbK)V zRs-P!{ufz(09gDV`mlJJqitE;sI8lAPxhENSQg2abLeV6JBH4X5yQCmFHXt#`!x~j zpX;SmKF^0y{Mzby;i;cSQIGUmjhZh^o$|{uR!5#n_5SNev?=^M^bZT&=v`q)dCz83 zn0H_t-k;HI;ZL8x<~DyE!C`;#7B7N>VAcTbhZwHe2ef6Xx4pj^%#hKlaU@L7O3Vn* zNEt(WKQl`I3=1-&j!I*a?hcbO2t96mI^j0e_}}-o-hy zS5a%Mj-haB7t|!)4~e-#5{jX45i^t|6>N<^#ktM6j;&g&Z*hVB?gSggW8X5j`cIrM zFxasj^_Wh-4TqTFDbkwpC|2Wm^=wb}8+`_}HWL>!nqepcrB`2TKqLH24@F$a6(40+ zF=ycYLTR3vX_zxzxTs(6^y)r1f?3JFrN>4}Im>XKQ;fA5Gl%MJgwuQ$G!}Z(aM7op z)?v&XyfhgOGQDOq<6lKo=)UxFIY%D*Xht(u#irf8I5fYM9J6n)J>Z-69b{w7aIKypGB2~-d(hX z$)}mn^Za}WFryk{!=CK@o??d0O#dt;7zWAEyZCJ-Ox?D-5@n?85J;>$2 z>{HZz6_nWVkhOSN?A_Vb_hk$tHh1LLN@RWCUMV{(un|q?(xHV$lr!EJN4X%=#QWdd5;E1-x_~n-oPjP`HP+2K~iT$v&5);s(X^^XT`g6T(neWZ~Fa| z&J`4CSvn3J%|dbjlGed229of!O5OMPX*o2cf$0%;1u5<+3%m5mG7)AB&?cFXNgTFATR~ zRkAPUa$zY9pG=_5E8=R>mrGpK&!7f#4LcN;WwR*B$8eQ76qO}1DXn?-!wu}|6>OZ6 zr@I-83bf?uDv~C&8H_5`;eT6|daHgBj&2do->Y7cdxQMBK@pBY(VI&3Fs%s7U zLA{m;H;|DuIp+S}am)dgg~t*aU+lqTQ?D;O)1J;ns8lGYk~pxUnsIlP+wW=p;wsPB z#rOQhv!^vRI4G2|i@i`nf5-`i@E7&AmAIQV9^612uMrs`VvSxU9)MvXkr}sv;hI-< z;xy|w+;GjDTrA^dBm0FYkkX>6jD zTILH&I4N<3i;h9qYgj|jtJG(Idh5)}uM^SNt%-9@S3fYUm;Sto)(zN*q_8jZD7KOsl#7kEZYxyJHD6 zn=o&Qr?Ou~&t=$UF*;U<+yu2#J|_(;V5&!WrUa(f?3|}n(`4LY$fmrW&QhTh6%`W^ z6CD!?(O%sS3-{pXMBSvA_@hRZJzVc4U^uayo_;btlVQ0HaoAv`YxAu3$caMsQa3x< z&N{w6ab4jVmt|9miVm;A#Rh*!Ok30pkBJJ?oZH_=j^(87)GN?uuUosJ#!$~c;*E$> zEg+tcjHwM|KOFH5{ps&>Wve%5?+KFn!4IIE1hr*PjCnRCa(t?cP0>7dzyhc7i3-u= zYsKgR!`0v zTMm3j{qsj0><0lKHry!gWvWDgZgiHjtV>~tKQA@k-`R_thBXqNdXyP69S8eHFi1tY z@!oVIEe`gb%SwC*vm*W?_G-(nlALXU4eBPueRwllx}i}Jpb~v2{QrTOgk42a;%>=A z5g&~^%niId>PB7q9n3TBYx+59_##z|?sJ}?d$4itur)8iyDAD)`JKhD2R;B%!Vyzs zwB(jx3#Xl%P)ZC$LkX|8r}^DLRpFP)`k_yK>x$$K}tuH#T~;s&_ox9)|Q ztWG0AjCd`1^+5-2>YOTI{2M&hJVMwRI!%=$FKe6a|3$K~KXI~Q>jzVh-)D=D(yPR;TYHzGdX^C>R_kyD|Tw|;%{8=)GYnUo#aVi~YKx3B7S?|D|D8sOfT z471hd6g{+6{0GRJHHjSCe6PU{UZab^SX%juQdj5s6qSKs*Vz*9I9cg#$a`q!6MR1S zYtgrKG-z1&Xb0cgI(abXzA&$${^7d<4Ym?TynnA9pu5P8NX0(BGH3e-rpa~*(V2lb z*TItb2L>jN2oLuR=(2;e(Jdbhnn3=-Bqkg`wABoE;f!=y+%Ig?(T(~q89?!WC=3tR zOL_N*z3)OHJ80YSP9r&g8Sk^O3qjc_KdtzHaB{uZg<2#L3!O53xN>rZK^=qlk;9^z z&4e)n)BQ9d`)SBw2`%h_YzrTY41*TCCOo)O0t~ZWv5x@4?)_R~2(CE#o-$TA5>cA{8BER7rUiNJ^Hbo9(MGOBXi zMo_M^adnUv)ScTn730^?zD=Q`J|PQYabSq7##Hg@=xwC)o_)y(RL<18R=w?TS^vKB zr^}p;+OA&Ow)!%*XdZ9T?r(7AA8_Jl`lOHPpYYv!z#nb|UobGdI3coIh?&enWNga~ z_k^HUsn}03eAd9)zw}dQ0x#@NDu2!U!B6_R;3pI8>u~kU6w}A$L)R;kq1S&|0wf&oa{>sj3X*hp6SS8`BN_jXayqL{ws3kUEAg z9RhLh~ zlGXNQ6wZT8D;yv(MC1{~m8*t`lZG&n95Qw!i>Ru!D7Rn`(LW{y{8BxrL3>rhp-G5B zID!m#!=)w7RFId>p-lL0JQ-z&bx3oU-YxG-ddXPuCqPjBi&h3qdd%-8dij+v@+&p9 z#Z`SYeQLNrPv>Nt^+}^)G+G+u=f&+B{5b zU&sL!^|;j<5T=zsz5q}?@g0L-a>r-YrIO=dv5^3PD4I3~<2%zZS6RfXP(~Am64MNL z0i0@;w#Kc#An1yb(u)=0zz14!XtiszOFiOVFkN`L;As`O_jO5nM4NR*a;cc#YGkW> zqOIsObwvdGJQyMi7%fcI5bJ0((Mb$A46mRDV1or_PPs zD<>5gxF?0rNAA_avVprJ?;PV^)J(KU#j!EBIN}9p^{@7vo9y2T?d)H(ZpVu@LpU+JnAOXkNr9GC0=#@bs3*AWBj zf`*vwI6lUh>BP*;OvlX3%!!$qnVFel$IKKnGsVozOvmf{cJJN0Tl;F?kN0Or8r76k zYE4h8^?lu?)zyo}OUm!xchBU}OtnvSe%owX4|z9jQnS*GR+IPY%^4p`YB%;}s5Y+W z;90+B+P*Fld4eHAZ!`G1Yda6&z{?Z52A#XqzGtr^`DG$qb7dr(mcGy*Y1>fG^oWXq zu+dbZgiWSR=k}}HWMe!pd4HP{Z9Fq=&1LbGm>>`SxpdiLuexygd3T-Yr*%KzyL9@R zZOhvPy-V*VyGl2`@kdnn8Vh&3#w@q*mnPsDh`Wdj-Am3ryY$Z#=-=C`M*d#7#QcolI@b|lI@b^lH)M_ zb^_)=_!iPJ{yDF8S;vUqgq|%m9W`@o0`q|E0OXkbT-N%}Yh1^;nvIAfmvJfmGlSDa z>3Hdc`MCMS;W)@Kuyw|3W6i{lU7xWzO?$%jz~xrVG4wg%xvq6?&Gdp@pXo8pb3%P$ zY25qZ^Z@U`=T`RC&wbi!%xjg`q|2C)JvQw!<1$@h;&H~DbO{q{%HWJ!hM@I~XohH_rEP|=vL%65 z>SE!&CG@{I4@UaB3YuHi@!Id!BXbqp38Yo1GGS%v&83O&Q=PEN~4se0S_hK7Wy6p zojDJM9Mj-Cac;kq8Gom51Tvx1`A#?lauU<=P8bBTL({cR_;jTlGl<+da(Ntc;dfZv z;c_L)M=IRW8zB3_xnpDxo@}Yhy!s;>4~ovn4cWu#);mHEhR)ax`NNrKCrDjMqP)nX zKqn?$VWI-*W9An|T{-n>i6c;31V2c9Zs-WNErnk$H}CYw7 zF?)FYqb`5Io)>$@15Eu5j*>M%=7~J9!FS8aU^kP?2(XDkUK4ZY<}nX0`s2WBld}0J?6O6 zej#|z$;lU(uD_G;hyjaE%^}^HzaYIQgT)zUrSF(Kg==Q1VaI#3#s~gta5~n*moO%~ zYmi48&CrGn)Nx`>0W|1Rh7&ZHD<_mRs4M$U>O89Wt$T1kaozp18r;_78Gn!$lh}+W zG=Et4)@evDMl_o7FNT=b<1eP{*SS>jo%V3o$yKqO{=L!QKOIHXAfYi@Ps_!uhai(N zUe`ob`wP;P$1>?mn?yEa9>v*}`Lj`ldOB8Zyj1UV2D_NMJ`DSZnLQ50C~)N)dlHIq z-wHf?LbOrg3J!ZpwDJDg54GOGdW17G_W1R`KWay85Hm+hYPDBvZMZXg?aWzL2yC!2 zCoW7}Yui`w*;Cj1E=(V5Usrh9bLk9_R-kRrJmcuhq?bi(sI&(O&Bj&`AK;h-Mc5t8Uzt6Xfy^F~PA;t-DQy79_yHM)_??~HV? z%Ws!=e61Nfy?*I{v>)#5K$G7+?qIV2%i^ZI5%sB;I$)-D=-g>GGG5*AawZ(zv~$K* zuOB*~FNb-qCp(a)cAq4gAm#Q`+){Z_=MLKMH(wun!FFd)?YUlQda-vWZVp@?I6mq4pmwKj4qYDJK6!Qd zG3=pUiM)yUKe|P_f2CsqeCI}4V9!y=C zKRb3L*l*!I-+G~Mj_Yq}KdE&^+aEIA5WE4w%$p;w*XwVXU7?rzH8)F7{9tb1z{^AX z8|XJ;-^lI>)N9u#L!Y4TvCUnH=hru!4?o}G*jw~BGBA_Ci2SwuQ|TKEnCm0Jccgo# z^m*wG4@~zF<2yBVMf9WzCKDL)-8p=Q0keDre~kL>Vm||Y()G91-IF1!VgH58@_246k7nbka8`^>cnC!XdJ|NX7oN4o*2c9rW2YwslF2?Oo0MMxZPid z2rGT~VU>xMm0n|)2PgZpeWZN_e+&AI{$6}KWp>vQF0?K@E<)^Tc-GgK z*XP&A*Z*ek+Z{i&I zKm`d!JqxITem^U+@M z2aR%a`XWz9-L!{&s{Ofia_D~Mqvz=BDBLL3sjU`QVV1Iw(j*ObQf{(-l78}LQsgLb zzs^CdwL+ylk9!{E9N4Dlt+!rer>3vaT&BHXd*X60<{bKx@KV>Nyk2~vrmy%|=DDE0 zur%*|a(aSy;&U&1@8_Z5E$yw%SJYKVs1{pxS#en|vGBNHd;)h8dh+W&=q3K8_LGZS z>{H-V;Zx#M?UV0Q?o*tj)~(j9*sa>F+^s%UCQxR+K(yd^B7fq3AMjHD68Td3lIfw? zS)`{{Q>MN^jp~L^SUR+ia2w>gmJ`;I>|S|8IH8^WsIigJ(dZs~$QR#CZ|}X4=q|s{ zm#r_*E#xEaGxs+BR`vGvIs5kYKtI=41ibe~^{jkby1$v>s|~)W=F)9&WNf24m%e=LFR-!@;To_z0eZ}qP}&ORo5XMBrsA{Kg5dKP<{ddB>-{dN7< z{nep7p**2$VD*sBkUG$oiQHIz68~hplH2;ZCAqcUMNDQNvKUC3UToCHtYSF&5HJ&*n@{zOpb&;qcNFrUdMQFY+o@SlKJ z)?3Xz13kq(`#s4$3xb%CJH&E3q=w{KEmA>m=1M?9fK-7bYdA?Qc}#!e7o|w@1o8wK ztIxR0pp&?^B7RH3SxIh7VP;9k1SzdJ+S!PPAN;c+CW-j7DSHVnMSRDC+zE0;EXTq( zlKjUYL`f1V()F-hj6?_$QL=R@R3%}MlstgEGfWQI2mr#_7LD5|LOqtQCRUC%ug8+k<}&@&fUbWO&JTk!&Vh4nOW8 zw+dHV8EUnZSP;^IA{UJsH0b0amjDe~S_LUZK9g4Sj3jpAAvDb;L?85ZBQ zlz*o$>Ws~6S!ilW|4v@iy0x@zu%VN%uH3geZ+Os>_?@_@d0Mb<`K+b>TaacxqQNdh zpJovXcZT(M`=SWV45%?jOX0NmI3qtDcOh+%E&qP;#lpMswBfYT=ilgs)nm~`qqH6a zmxe>xUUq`3^ac%=+KteN1du3ok|tZfEziktYI&<*E3kK<7uc)XI}~6YApd0vCKr*P zz>8kv``WiPj5WX-?i$t_+S>Ou%r*2iyfthuz9+LQ!K?GF{H^${nJuYz{&yemoGwXU z1^p_98KT+Lld-$d`~4S0@8T|yuZsR}ff9lInz^M@`4jnL`O~SRsgtSW<>^V8IxN*^GB-@dMKdm+MckFldcaBf1O|yo%=O>OF**HAMns`t?PQ^A8 zX*4C-zc41FXil;XNk*&B{HY1?HuLOCQ7VNoic4kY^BlN1C@3{E!;&ns(SzdXWPKJ^ ztSlLL>EcLaV-}VT%vyN-h0*c0dpNeyI)&LY8%G9CtlZzTqv=1~KOX6#_p+AZ}k6JG0a>*)oG;kn|lox_9uKaT(J% zj$IU^IBsFo%pi_&71JaohaEF@)b$|quAy;j?O(Tk4a+KKLN+zL*zeX+jf1;`=!4wG zowWnYRyHh6cnr8aQK?a>(bK(bH@);FSR@uxp{I|ly8u@#O31W_V8N;>Ep-t5L-dj@wvm!4uNar z&BL}1p=)H_aiK*~yrkU;4mWu2GCmZ(aRMXyd*R4+a*JcIE0mcg^)~S^E7Yqd?Pqb> zb=nWZs(Td0~pL}M(FnrPQPU({TDCny33G-?A{M~fsjg9Z`ku2cUeP9t4ap}ClhR`ni`t#nxmRz8f5Cnn`oPD z8oaEZHynB$=59i_ZZ@HdkS4zTZ~}NB1qLbp{qm9Qlk!Jp`3F8`^;6CHzFH^SFs}JT z6PEU>wxFx!B0vQ^qu zs8x^Eud70o7WLHwm81 zpJbY}oLrgQpCp(xp0u8HNS(mkt2n-Co7Iih;a3l@Y_<-vuCtD@j<;^KjjDgwsdO!!+X(!O->99wKa@1i8e)l&w0Vb&8*5QueqG-mL zz$EmbEj=&rt60BVsS6^YLtRo`*8u3@0f^87``!ne@=AzMo`jYG@mx4mRT!2b@vS(P zI#fG-!@NeT8{mDnvF;gw&@eyRo8J()G} zsCk1tNRNw2I^*wV;(R1utcM}Uq1_*q-5RZv51iP27N4IqGgUK!xp1DL$R=pS4h&5o z#l4bAZ<%g@;{Bj_7#z*8c+!mp;f)2-YAp2=d#adM#G%Hm-IUw2WS!sDtab!3+CjS! z&n+m)4(x`LOKEdTEUr91FI=pK>B-yKOc)ZpaUCE=gixLdM@aPEVK480s5iH?O)R`4 zCO5YjRk@+w1smrOs&^1g?@WQ;SEq(W$W3u?;74k3Zy-h(B%vhUCTcc>RWH=Z6bAzI zqI@$hdC;eK#6C7&qq0OCq(tb6TdPP?<8)@?Xe6siua~ZL`*0zhvnw-o)(=kT)gN0u zdQ`uwcRSp#-d>3HRJ0v>hryDIqcD-xT7+SS%ZlJjW?`;JN?|VaGi6w{ra8;9RHG&2 zvXT>xIHORBI*5p55Vww0vh6N->0nx(U>SqyayBE(8@EBTdo*djnTN`&rcGjfKmM}R z5sg5iaE0yhpsYli5PdF;_e1kvJSyPZMhyhYW2U2Gf!qUW8t9CtCG_JOa}&0Nd)zv# z5m*$>Sd+pK8__xhsk}iB4YItZpN-L#1UlYh3wG5H4$gj+mo3~y$6z->X#uMj&hWjY zkSrkwHIYfO*1Z3iu|58XH&I#I@5VfA>n5b-H6$o;Cf1Vl2_d)%q2&4^ zi1Dlks`@*fjfWM`>IAJPe;1lDjg!^hM)Nf5lZzpLfA#XYpc;!$vKj`F*H{i)kq5Gx z&?WR@nrGK_?Z~LGJKRtB>eVOz*0DXbqEK&d{WrGYBl{=;rNvcoyo;0IYcU)vDQ7WE zMPACbN1Nc-@hXA2Fu!xSpwdBDCXy4YEGwzE^bIK?A2&c9-Z?E~IUc#Dtc2U0jYD0P z)w$9TjK}-w2va?zR!JoCeKs<+$H&!;uzv+r{jVpyJq8R;%UL^OA2kVmHN2^zwxnNIHsLvoFTKT zODBDY3Np(x5yaQ&KjuoMnx{Tkt*{(|b^gG^2t&djQP&kz4u@2e^0Y}2^?Vu?33WvjC(Pi2N=js1vF#)Bh;L@_ zEW}DrDo+?z1)*hF9$^L3A{fzx1vq!#7Q|5%X+6SD;6Hyw-$LaI+?)XJGg2z*8Z=eG zUz`BIf<#KGWqO-X6DL4%USb&`cev7wRMHO}jB3HQpYknmnt$#8+E3>h81xDb(<9R$ z2WI`%3RaBMrov3~E3zwc zdj9hO_It*2^-Hy|$mdRze+>wRu;_uotAb?}8$^ciiGhP@y*pnaxZ2=lDDDWlft0JdWi1<&?EWSr z?IYUJ0%ToLRh9h8VNuxi{;x)GNAxZL9wF4)%4JNC2)=&8Z*EIq#17IJ3<(kYYrbo~ zBL+F_(!NSCbR=Wmz&qBXp+RIFR%~9wvi4c9&_KjTwd2oUJRiD8-z;kzaP(5uBxwxF z8bDeweMM}uxH$Bm8-!lGSy)Txs5o}1OoO5Z&40*+MZag&+?4;(+lJ1}%2`r$0M8R# zdT}gc8{jrl9;9CltDF?L;V&gOr8oOHkBMdl3_6^IxgpVu=4LJ3)VV3L1H)&L%jRFc$-wJ*Ofzg%&m%4ee-U4hnAX|{mO zqauW9P->-Yd-eN7f#8p{b2d z;uY~%*`ND4n9a&xR`~czrxniIe=>@G^rC(4)=*BHY5l`Ss+8D28y z)FQ)T`9mjPlNG6Y7C!@e$wK7cH=hD`R@CY_vxe_$d8C-zKj=yZenvKx{N83yF8ktY zA@C3NVz$?bajmfL_-Bi}#5CF!3xSwKFEn>XOo;vw4mT{}Y^@V*(#$U>eBJ`|qc2a% zaz*I*S#kx5TO8c!av?)bh}lrxbLCFdZ$hPW$5#~ZK|WBvGb>K)y!npDIt0_tPF->m z-9`E*cTQ-!MJ&^FPVJMDkF%ZJ{)G7wPHIjC142Eqa7WTePb0ijYi2*5d?}8*r=(tR zLh4hhFs{b`ah3L23yq%MrAb*T#K?9nq}sC}q{J{{IG%hoVo40bkM*Kl17yf^7w zrm5mM?R%;7sS@x^Mlpcar1FgZAI|CrZoJWu+U+wG_JC-U!sSdGR?o<2L;L#LGXnOQ zXmj-Cb{kU9SXraX`jIne_F(T}ek0`-R`zV~aehOV<;`W82Uqq;?@_b*spU_{^@MS5 z{qq@8lb`np)~9=z^np!#JmpWZ&3EldwLi%=nA#JIe;WV8Q_cP-+6MhK#MVrES*@>kni zeF{KP?1I1wk}=^Fp~e%#g+Ai4m!uPA;ui5o+;tN2`EH^6Y3_XIJaLPm>6mZ0Z9pfa zpx_)7KBVpZ?ojYowC%;8G@>C4$olYXWZ9n)qcPJl(!VIiTgF>{JJif4!4XRI^rNIP9to{tgQHB{297AH7q*TTjB^WA&yo94qkM z7l61!G$arrT(eObI;dB=gdp2txWQ;~LU)}a$a1N1sbP_$kfDIs6-0s{A}Di~v5Xm! zFq<%&CpH^YgrJa>m68QZmpDWb8>%)^pI|O@x_3WvnlBqcG5}$#Tt%f8r!=cH>sNFp zwF2$HC&C9!Sstx~=bzEW>1PWt!s@6$Anng`5oKu3v#uSdGwRX*SEMwIF>jxrlbtfBA5VuoY< zz+kC}Q1yCylTAO!mB;$`N9+~T6;p+b#I!{Cn*Gl%kpdlt_G1EjTRj~fd*aq^TMxJI zLq5ICIRF0qF(g6uY|?_oA1Cy zt@A*PzEG&3u zdsBY4YOFrb)W^vOln^9{I&8G#L=_g$iV zU$1lPz87bIWw4LP75q!-c6F?luL!CYDeJH!X?~+{Y_yM+2Qf(r9ku2AIxB7o`v(4r)Q2uM8# zAfXsfN~a#v!d3wCXOlm9(>Yzkl_%7}(Ifg^NIM}g)A4W+-H8(|mr%@c(Q#dM*0?-j zel;=6(ZkeC8^_scNAa>GcPyR6Lo@+S3bO$;o@Ru`+xIryx26e*uQBJV~ixgk|-%*vF8KH^CkTx!$>EV6$%Y5QQFlNIFLYp3b5Oa`Zlgk z8g&K`^%xMJbGa_%;KL%8F2Uf(*GJi$Ti)BraZj^@@$q!kI`S||G;DNL>?G`1n$L(^ zNa-K7_4e>(yqY72c>6PGW6AT5nFS1+HMSKS%%t_c!EP5$bHP&rwNzpl?DaLdkBbgRUhv1DtA}BU9*njoi=0Nm07W z9kCV}bUXI9n#`pd*Z^WRmKsXhjvjVkNkHijG?bjSGBbDM5+L*=Kt8|ATk*Xv!`1GB z>HGUJ$!d3tnc{me>N$>>(d&|EAOJU&mG1P}t8oI_P0 zY`?l^Tj*pAVP|aHn2{)|SJw&GNPB>nx6hhy9`G(Q&QLNWLENScTNF;}41V=$7yOPV z+|5YpT>FVWJ>H0=Cqbg4V6oDojGPpoWL#61sJ3&6K#E5<>Kf(!LZY00k8Sx|oTNxB zlTDW8IKz_T{&ZQcQw^8SPnNcMbwn%)jNeT1KI|$?W*Yj{n#bYW{#KjB zg*7K%($(D@(-e-#eZOhxRoWHsVwtNLShnvb6p6%;G1BLa+4<<^sT|DUBNRRFOIAhSf%14U<7nL_J{TT69s|~;F zwpgEO(&G?9ZeZx&*G@cybMLzMDim22%3tH}nxf{AG!3SA@CpmZt~2R9h_+Df{_fo> zav$N<9|P>*{#>Cu8}3Vu)!SUghQd@6W#r;C`r?&;^?TFZd0c|!BeHz=OTB#-(HDg_ zKj|2ub3=!qU7@A@-Tes4;INTyeXG=3-??ws4)%IJX5Y z+4bSO=ayJw&lsFTKW&ymofCP<&FK|c?g&-D^79%`0byXrQCLYK%2HC%w(M8`vGzr@6EF4)%6X-Z(bqn!nF@exWt^(4M$Ex_q@K zl3=kVx}WCHzxVj2dJJsPYlr3}I)?Ho4O_M7RJ7xGo3re+M7znTwP#mOeh3k`#LXya zR6u)xO(aBpCjn+6)~c^XE*}zrH|73^kZ27cku-Y#ui}TNtj{3p6FRt~jNcshh2!&L zn$a5d)?w+jc;`Erl*{xH1^yh&e|zP>icykpvF|jGelY*v&1b%Y4dM|nA<)NyuyKfY zkZsUv#Cj(JPO+`5uxT(d55^D*h!_yN5UKMLHS52Ih+19$b5Q@)!`X-S-1|}QreEbf zQ`c&WOu5M{=~kK;Ju}{>0 zKbh4dXC_J@i>csfUJ?n_3Kf~McHbaUdz27cz1;Yf9JvdER*0AKKauC%lhG|3z@pb1+%4O_Z zj!6;QM>Dnc-RY0E2UvNdpm^Rzy?DzifywQ6vQenEhlH53((Cu8qTGW|h`(ICjZQS$ zmVGnU#16U66igJ_b*j&f0^Hr0&(Pd!x0y$Atxvm$*>U|nb>}T!hU~}UqjE&6KLPO{ zXr~K^y^7CMtGbe63U+$Ujxxh-1f+whOl^G*^M|T*mJ)SUk2TDF(_-Zx@)>SkBSAaY zyAg;;H@Aar4nz1H9`m0$uKGev%~6hWS18CjQwvM!sQ3o~1m6j#tt^eH-)@Ain8SMEqozOF|KO)&{CRNm0`Hln~s9zeUe5E;VIM*dCE`8Jk)f zx_)l9+lJ3EZ!dqq>GN1vU%!9dKi{>mLH8bn7qRe0lxVWV08rVVF3cfWpL8gL_<~^; z8yY};63^na_6l7b%VDPa#H%ugE33@S%{iNP`CJQ=VcZhB6UedZHA2N|ou8rJZS14w zR+kjF&f9ki$(>p^GLI=K@3;xz)sz<9p|{yewecSoy@LjM9=v|Fln!1ZfSS+9s7$eW zLJ^)p=>I~n3Vbas>*x*}+bzBVHd2&OOV3${PRP_#>Srre$Fk0;DSGJ~wN&KH@2w;< zyi|f0z?+E&6zLabf zn&o@RvT@f6eibPn%G|fO8|1lZfxvfpM#f6=jG_ixZ9I7~s4l%&yVoNkeV#4`|6YJu z6Z}4=c(Nic!tK(G(x}aWnxC&iwYn`kJ|r?iX>&h6asRL4w{u#q<2j*q#*%9IcBPZs z3C+H;gGPt9>#mFCAVIX&(JET8s^QA**4?*uiTab|8_QlphQ9;x9Jt@HcL~Np**jE4A8IRIP*VNA!I57Tkf#1B{mbWTX-86sP4e^&6I^7L+Py~f9Vgj( zI=-){OYNx3uuEgpV&irvd1+mvEY9C7U%g`2y87Ae>|Lj#&ppDF4N`tsL&S+!)dH!F zYfJ$yoSvuHZ?hf#B_%* zR@BQB$x{Az060uicW=@RJ3CZy^U9dMsl26L^&pw5pNPTs+g>PA=QjOIkIt<09Y7-P ze#(xm7#VNPj4$5Sq#L)5q{uJSc#iu@>s5%nD^8z9X1yw#wX0MtdlmerCRqCMI`yki zjM%}Nyjy%+{K%rXgP*RUnR*z_JZ8R_6^_#JO|PTCXb9pzd=k>&MiizoWY=~JOC)E_ z=I`;NZ$NDYr57!3Z;MviMc}uKAp%#T6aV(ndai(Pn=J*RIjrNKA6=S=TQ0`G)4exs zRj%&FHl+)~q_EZq{cX$`ro9DxA_W8 zq7@vwQ56oVCQJ>RZb(D3tCNb`Y)=3K@FrKMu(;zo2UMA|IhsMiFg76GG_2ZwR2y#2Mkw6T5|CA-}I8|-i zLoTJ5#&L#7MjFqP+QUrd6OLYwO@SO6lG*1B zexBML+ZKxAsrh&6Jx9cq8ci_W=7r^TM}^EXmORN^DaNNs(d?u!WUF}TdOCs4cuT0E zL-B>wp~JF_YedFyo`I%QWQ-TffPXx8u2~6-1AknHn{%^#P1Zw#LCL=*xI5;u-&sUr z?!<8~78m1&3)Q!-5yQ_+`pS!!Mw_8DTpv`15?Zr+darOTHM~vnNEnLSR3J;ptr5i7TP3cp@cTZ^RaG?)Xd2d45>E7vo|o ziF&t8X^y&-bGt~%@*Z|~fVH_~BR+&9O#MQT_oXiS$44EBwRL6Tf_2p@DTeqHGHtt& z)RV0fx7Eo$nnyLS&=Q_=z%afQ~OF)>vGtX0WA1H#ptW zSvs8kVG$|G?j6c1XvTa}zY69moyUYq&N?V7Z_2zusVf6Gde~;ks*6pYjfk-y*Esi) zrmB@520AE&y97J>`nJbASyZ~4vMe14K-0P3I2tAu>hT^@6O3=R`7g^3=Tzqiqa>LR zMY)^B_~&#E*7gDVaNXO7OFcCKuKxOv-N-%&Z}3lsS0Y<*Bhj&Y_Hzn(P}4lB1V54f zATiDfivloWUIKO+hsq$zpi>}Iq0ExJaon4U3y9lY4pOGcnIKW`k%4mRJFGyhBu>?E zHc*wCQ8IugStGxn3Ph{EV*->@D$oo!1+h~7DgZ=-XcdiW0g*`-3P$+=3d&l+@MqAt zvQahQ9yCv-SlN#cv`Q*dGin0VC#9A5BLhol6pKZR0ffL3DkfEgW&kEAiAJ$Rv>c$E zlvdSG4*Ws=s{;T8$tVKXa)F3_D~K{MbxsEB4p8X*+G zmn^CLw>n5RTm`6Y8g2q?qlqsP#uye2=T0t6_p<$+!5M!5jb zsU1K>b)66JRo;OHy3kyA z0X|U^)qqRN>n4Cb)pZHLp7Od4P(yWH0ccNh6AMpGc9ROnPIeOt&j#_<^gDuhEBjeM zyubUELA>St=pf$e{;xoAMZYnKx1!$#NT|4D2qaY65dsn_?DznisAj7B)qoeuJ9@w- z%34i?T!1aeExAP^ygj)^I$RcHqp~9h1Q+xZfYz$}Z9!`l{dAzUs(x9}T3J6V&`x2; z16ZxRqXn#1+_3^yEA4=Q)e1WfKzijJB_O@xjuDVvX-61HuVB;(I8EZz55G;aP~Fi1 zPAcx$02ir$r2tNr$>wV%%8X+bh6wXx_=E{OgSa2yozKwze;R02g|mHwxIf^X!DuIM zWb@AwWw)^kheY|40{=T3{_o*5kj-04l+DE`loI62@d#(T1aY6kJO4vFX&}4dke;c| zK9(jh?n_@u&4A#}RxW*3k_gb^bf%ue)wlX$!B(oq^xH(M*C~R*l-8n^0Z8P;JSQbH zk<}cfEGr|u;$FZ2M4caQ2(o0t*6g3}X_#Ir{N@BT2UYScbDMVX;(_6J!lgQ-GR7KO z9#WoR$v#J0&5@|gl2*c^%B0Hdo>synYwEzF%9zZO%#_S5YnU<48g5BCcP#^yLHAlT zQec{M%XNmbz*ml+(t@JC(~O0~WZY-j|Fxk}eP}eg?_r!4V~jfilwgY9J0VMRNDE;> zF&;fI5J?aDnba#2mOk6|YolSzBhImjo2NyC$0O59uqnFg2#e&z&IWn?SL#LJ*%VNYTV zVvM?z$n?qxbzLY6n(03(#f3|mmkv_(io%9}EeRAy`{k!B!1O-ckb4UrC?`q-kKFOi z@#`zWSky3-I4Z{F`vUWa%85bWPtd^vFD>qrdr4x?$k4zGRDg#HQa|o~NGfQhSS8lA5k(-`EFCXGOka$ zH#4n|Iw8$X$Z#;N4>$N9!+v$)lW2`O)|zY&IkqaPOsn2QRR4M6P%@TAzuzIvIXWPN z{c9sw2AlD>xz>;qe=2XR@^6_#=IlNv_BkzmVU#0U{lECr=`!X|fP(-zbMA@ME!f5< zz@P7t%H9Zlg4Stjj`ylM_P{wrePMzljOwneW0R7^w2br=CbnNk#X9ezCF1(RbQTD3 z+T%cMCN@So6K;P?$fe9F)>v)!)XF86gG>1Mx5%^k+f7-bLT)PEg; zy5d7EaXDP6H-{$_snbT&N7F#w*YwjE`W+p8Ci)#&8;M@m=6*k3c3uNx4oUxGg)(ab%3hxa`|}>` z6)N+C0`7EZJ~i;(@MI#XH|ZiWzHmNrq?gIlVVw1=*&!u+iAr_q|GA9E=hw~C5kNPZ zX8vu@wDm&aG6*u)GQVVMbx1;JG?u?lU<_c4VDtj}T1{|xMAC)R0@4FoiqfSsmdjZz zWdg!=2xc5@0C0uYwr~ZfHUdH)@Mmyfm`&8Hcl0O1Ww^8851uXUJ@3G~r5^R3)E>g8 zZU}yf4S_f?@)O3>mnTlEobyY?TU#PXA}C*Iem{GTC+{u3E#0C`$ZnJlm=1&vwASed zk3Hg5>n(LMJezh>7MmIf29a)zESA2Y?k?|f19sZ$B>@{r23GH4iO|FEMWxa%!5I{(ke8XY||@Q%1RUDd5_xDCG>^Z z2G*W!$P`=yq~2D}m9mX%Xw9E>%&yTUDz8Rc<0stJ>>hnT9=}!pRX^>%*Ht^nP2{y8 zZjJzsAclr@2yebS=bpytCE~?}FlS67U4%`{Pt?5WCw3m#Gf?J=wxf%~KH?&@Y_j_D z$Gyqfu=(F4YWuoNN{$_m@>Fd$iGxjPyY^bCt53 zoft)1Y6~6*{V#@`_F-=*+!kW<98d6|`$a34^TkWF^Tod`u8WK;tH-mf^b1Wq^EUH6 zmDW=GDhKrgx3xJ1A1k@w)y~?#x;e@x^s5>tIaw?tdjjNuyo!N$t!?lgV#;C)ap^)^?5A z_xTHKr=`L~J;_-eDQ}j@g7(ho^LIOaXO@B1wtE5v(eM;e&w?D{*A&j$g5j+-r~{!D zaedp*fFK1&U(m7*q^A+PLEo&Uuw6(bH3U82M{Up$nn>SCNIrz}(t}#!sI!3QBr0k< zDCx}WG?UjQsbiIu4WoEYF{x{+b9}bKRW+<&-2e_fa36rod$G+qDM5Wlmg@284h2u? zhFZFVPN`idG1!+75%6fN26e$O1Ck`2VP#gjY+tnS6Li$dpq?d~FW4zF4~&+0LNh<>(&9-0(G+nR;o} zd`P@J(_u$S)s!8}8e~vPF3|juJxk-8nL=W8s2slkPzOdaw0SllfGS zceQE%R^vYCVqvb7?E?Fg`zP)+R2E_tq#AUkUvdxP7KQ=WCpQEzjZ6!j;fL-oZh-Iy zN8<}C1d>0xA54#z0U;xH8Qh7VmcdsAXcg#sf6X2l16W3aGAL?DR7R9E#1?3--(aCa~-5S{+W!~{n$oPIfixCU@Gg4A(PcMw&A%+myt zP;HRmf>7U3`yp!ukqsaV{3(e6VGw`)8Hq9AAp`u0dyvxxUA8Kqafu-cxFK^n;qf7S zpnSeOKz~5_K!6dvP|u(rpf({lp*A7(q4gp4p*;N<{HFYUA#x#ep>iRJpnc)^q4>Xm zVZ9*EU^n14pf_MPAU9w)pfV|Kz^zrXa-0ICezaf8u9)15$t>b^*L)O#RBhnMt1Kso9!=6X5jJO83hGYZD4$Ys3as~AW;R@{v zndukZ^WT(bslLHGQkAd&J;NU`%wl)X3%16`|NLQOF~kFeKcAoh>bGU+ zFaKMM|Jk>-zQH+CmA8K*!=DG3#jc)z*cz`;|MBo+<@L(9RTaE~^2ESlWp(1fn3%$< z4W5y{x)+6G;5w^OFPTP|7o~gBls-KIcm!>feA;F6stx{jh23$fgvF)D_nYsoOx;Fs z@=Oc`@ZK~qiuj5{;21tQd+2`?in_e1#F*xFL^n_E*yhGAU!e>>AD9jW(dCO9P|+2daU%%aFyv>vQKh>U#M_l5Ue~WZWi}@_ zM5w*iP~hdL4r>|~r9xXDsqm)H9XW|mnN59n`jH)6>cXBaatO9MS(94;eG;18CQpw8 z^yg2%O6!K@MjjcBhkkOJ+p? zAZK*eN<8HC1z&IT6%_Fov$kNnf%=wpg@_eLyt{KJxW$L;hIsbv!?=rzw9AtAD2l^k z_L~{Ae*qNkM7G5)eIM5D61Vmbuin`?woU?ke&25xZHMG;aeR^vNuCI=Jza75*X(rkm=7Qb#N-u3i` zZJF|K9=}E1n7_tMueD}BW-v~jqe>H>{$$ImyG=QG5Ee;hDFG~tS{^ro0s`W|y{Nli z3gQXO)4l;nH!*EzNt=IA9CA0A!Tp{yj#w4@plj_t{mDJWceUZ4tqHfWiq1o79Xsst z82HHl$E%*jOms00)y139B5wgR7r(U;Qnj8y8?Wv@&l$x-rn>49(RSF`viB7C8#&cy zoY}cnma*2kaK=ghKx#1=ttkH|N;*zX{^1!%zm#iG{mlf#X`SMa$sAIlBG1ISEurgvG!> zCr8Pjx!dl1(zuA8U6}ZZ32|uvJOCgwAuSGo?zLs>I1jd;P!u@G#P#V=LB(YwYfkks z)d({~M6{BZJ=w5V(Yl^L;*6x?odhJ}0Pyk9XG#qQ{TmVZOri)85b#fXM3GAijk_(Q zxpMvw0Bt~$zj{&zy(7nc8};h=bW4Oq9PeoNi;VV;ueXFlgewsS;YyY&-6F4%#x=sY zy84K8nVlIfCiZeTlvRJp-O}V+b zu_H!gWzC!!(``fyWXurDp20Cg1`QO>nf|xG(`j|3B4kAR-iEF*yT(aUfjYxD5d{w~ zJuxxJcw)WvG(Adp^vuW@HeB#y>K5$K(@f1F!jf*YrN@PumK^x_1U)Ry(IX{2T7(Fn zDYAR-RC~YVDAP*uzIg1oL~E2u4~_^GqYgeD8X0ObM_Ch1zVN6JT@Q&4Uw2?7X7eT@ zlfb-AB`4+3KjqlEl5I&AG0|p;mX}Cek|J=iW2EH&d3W_mPH-VV!G-*Ugz!vRI8zqR zl!Y^8;Y?XL^L~7YrjG6RLQ}>NJg3bl3I2L>w4Wml{(f_W!Jlppm#kS_ksHFdhil=< zz4s0p6x_@BZ~di1+eLU=a0TV=$u(BY2$41O17pYs?{cz`E5JE|wOl}9$%A_DZ9oW% zjIpJ`QW`9y_!9E6P;uGWzQ(=bMQcNPSph8j{LG=-6y4wTUfet|0qv> zhG;r&UfGy_3AWy*(|g-8D^^S#P%$niHY~eru_m&P&+c(>reoCP1MiOXjJ6%j=`p-a zeiuMZ$JeF^b1L>5W3SoVgGM?1qv-UHBKQkAir_EhC_4S4=zLfkf?YOAWKkMrh)iEx zx#YgAlvYo3$@&%a7v1FqwjhUR1 znuG}`xlu`J74yvVEUINqJacH$%? z&hBg|BxzU*q-+WFhtdLt(n9IN)0R?tFEoimdxwqo=<%dOWxHvD=+}P=jvpR8FLvDY zW2h-`<-|d_@>=Y1wC*F0sH!JXbyZPyRZ(?SQFT?(8C)!AwUSTr!Noad08`N46amjf zzzY!&iBNX5`KBL@!nY$-#r7ume+n);C3`SIwLzpk5%~ga5o(-;fV1^>83z3*g)VTk zNX?tw#toCI0^t`JUeGTtIwTH~Iz2OcF9HVN&gdD92ELRiaFl_5jnu;9WjJ8;Iy&LnmX}F?el4Pf5KrrIL(q%)Xx5i*3%jdd?6o4 zV7TGB*Uh~{ep=(hIhGj^J8tNH_G@kD|aAEkrj(i72n4 z#H%Rrs`uel@58Go@#wjNR1V~z8R(`1fqbgtB+x+?r33M!`Ee8UEk{xlNSmMs5*5%? zU;g6c0*97g|2k8n3_=3>Qq9kMs$H6YHSjb znjt?W<18YNKkvCn^3x>4(OmO}Lzi9t*wvj&ulwFq+w~2l7e$fO!U+0R)?(BdTh?#c zmcIWl-E@m}VFn{QVN9kbAeESRLC55iP|3?v!!vbq-#(Hk;3B`tJNMmFmsRU#4+O2+x!An_!HM>n_kBP-geE>RGyA7&It#hZZK4jB^Yc$|C*5QrV z_wY`Mo41%9=kB9*G(kev0#ls_D(4@m32}~?^ZaV!5l@Ys)k?$q@|(&83~b zE*FmDjO4Z1yhf(hG3JhX-6CjlH)hf{03)~LwOf2frq2YEKh~Xz;;-jF)7pRk;Q7B- z4Dzv9Kxd76XMdX8x@j`C{QJxCAHv)Q27iu5&z#PkAx~@km;__`*OV;AgfKE8)ifdE zXF|l!WK&?w7PLOB9=i!ss%9@FV1h6XGmib>5UnWlv-uv2pU;OpwdIs-%?Y_HS{QT16(RE+h zx9=+(#B)QL?DTw4=bYm>azCbae@q%BQQTTaAL#vAVx5n%90 zQ~?xK07Vr*Q3X&$ZQZUq0n*_o_Z2{)U~Px%KJFj2s%}qa(I|zzAcM_Az>d}x(A48a zIMI)0K3P)$3U}#E+?H^-5H#H zxq>(3nHoahJh694*N){aCPSiaS-SLxkej%5c$bx?O2huv70?6r&7C2(LR%dGF(q<# z&7Gd+1u)#D!r7(5+@<=zE)~|U8N9ZT$P^?K7|s+#FcQjyGMv+f<~mXMaS8%@fm57F zbk3*nG>SpToHBEI=9pc5H!1H=@*)_x_u9phRL)I3pnCM^dcF5`8f56q@Q#A7)X6^fhv#C|@;> zE_d4We<~?Pp~Qm;xD^jnMD0Y&L8h96GeEW=9k1_IlGkes&ikjX!#aKbV-s@orLRr z3`U-7ArDMo$CaFFQxf=)_wXTa;zMfbL#)V$G!i{OToADWr0rNiLN8E@F^dX?MTNqm zLSa#%u&7X2p27vh`j44qX8lY-3ooIIj0t1Td7VUgfU0yaCK6RC!ErPY6(r`Btg|{E z&2vjN5>-X>YnR?MbJgWPxS>bMIwhud{HlSWt40$tYJN!zUc36=ZtCv1_Q`9AU>T|N zf4l6qwaMBwx2z_tm81e97JwdjCqyiS-KHQGLe4i90uJ;Zae$Z=MD(E64r*<{KBGDZ z`2e|QTlow+C>YTxyUlKkguP=njZw*kjQLK{2o&={tH&lM!Q|v*Vlv@8Q5j~F$00{5 zQ$-T-o5sc}f|#*bC>lRO8thTG#cvZinkcPhfH4+u`Hfl<8~|V!!9Z8)4e1HSgDR^4 z<_RoA9+hjaj9z!{M`R~Dk7}=I?d_0(zYf>lhTS2vzepR%%*>rFEJ2PY3>BI|2eA|g z3-XyT2-wg;JOFGybdXGgWIafRKr#rL#%jib^(;|co`#&Q6G|KYK_zMRud~1}9$O}O z^Xo@cnJ6DEMq(i*nnR@EihH~Xmw_z3hkryE9C4q&*2NR0$0#61eBO{m;~)rtiO`zD z9+%&wB|sbpZi146Zck7EnutL}>`gQf|8YK5J|@3!bs*pj?73593(Lbm!|?36=g3wa zJk}T-R)}Fe^ei7h3{%)LMStQc5U&Ff8;Dpz)B<7{h>rzX(LE+sC7S7aqyg{`%rUD! zuFf$l)pr0Y#rG;GD(eTtAN3kzJm3qNS+ewc=~WG94te|$UZV$_OHXjL0R3geqN9KX zm^3;m;PysEk}Lh7!{XpI1jA}^Vs;iXS3<)(EciIyX>sx-K|?>}0)J%mw5%_)&mxWS z$UM%3)nNOTXo>v~N(3Bq7FICAA!h*%WrPclIVs~9rP>Inj!!2lpH&Q_D%YNe0kR2e zwz08Es#wT?l75<*(id3`AN6~}7FKi2o^jyTw0)XjrGO(87c?yRN2%f*z^nLgk*rBt zuPfczaAj+Le-pS`XP}XkEs&=PaNU0-BhX94l$9U1S)*J;KZApU)*A7_^K3*ngSWzT zClYqoM9*@Z(LH6{soANBJcUY2z@DnPz`B; zDn$MC!i^YG-iRT7OA|uG?+=?8Vs)u7M(U)H%N;b}4A@0-wy4J*v>923_zeC8*wJP| zT1|1jlY?nEEBzqDkFc$OTmSbqM zWd?7D>t!9$->1`b_)TG3_I-Ls^`6L!Z@_~$gf}wE4q`g`dwByS{AN+uMrYqI!qJ~- z>w~!H)(7z|S9a;DtzD)6Z3=}2YR-4MSEJ32Bv1o8t`DSC1xr8$eC;_4JEqkY^85o5*{3}`q)??T%e zlV{`?NUdK)TN+QgE$51EQJ$bHirdrp*=npbDMcLrD9YOdXN&r66tqIBN-6&by8;H1W~tf#LTkW{ zG)qHg!qTNpU`($3>@$feEq%jubXYl5Nt|9ooT?7Bt>(HZCE!$1d9*)%SQYD0BqgV{| zkf`ilVC}*8i}HjPG-JBhvb`S7Ia;jWRhAWESC=Zb2j?j^suxkTLM+OrMZBY-wIrV& z-ZMOL$L3tq*4szxHb?(bmLb?^@d=`T#mb5JjX%3{VENrYyMD>1RyCP)#GR5;VBBGM z+wL!|-E#kq28$VZp!kqTX}7nu)#Rp)4v8JU^SSG8{MFsd&0eqMRb>l_egtxc%VgOG zIrKQTHN>JX{lTm?Qz7Iav=@XBxn!16Bz&x9iVG=zVFjjwozy4klwCf%TeO@d)-fbW0Q-5z7dF z9{&DjY=iRqgZQTDWYW@{{UP2Cvp^O%VHQk>Z!PFCOKd8@imoZKg0xbb6yD3ckwWoH zR>A5)7wiNelJ;>s0WgRG0K?el87XLYg{*o_>4poi-b?WopUn}Fv@i+Qmc9$Fp%{v= z(a;JB$Wd@{_OHwZ5R@9g&*51DouycV$d(S2w2VQoQ@tbpGDN|KC(IUBHzR*uyq|T4OEbt|-DKvlIC=Xp4g0@fmvV2?TZ>B}3A_vVB z^kU7}ZgdCJwdQ2hHUs7g+JHW#OC|$39r{Vc0u9@e7M5^Fwz+o-svs&=;*2rhjy8Cq z=8JMos=zNEUaq_^uDh2h^vBfQEoRLWE%~f=pOMB(pCf}Yv&*O@N)O<)(Py`Njda9T z^w#=qS`a5ehO_(QuI={FygpuY{xHW8l$Ih6p1-|v_9p=!D!9+)@Sl2W9IP);*2{OH zL|U<7+4B8H^gzCgte*kDfU4+foMLON$X^pXs#Q_7`2t)0`2sDZP-sLHaA-BLD6XvnNngEOp@Rq%ILD%+#=N-@Ok|> zJM`diU~p(KF#E&mwKBX-D1}BJ9*eD7H4y{v%XK*h<=8m3eeNBy2gUFcgKez0)VKltf2Ypxv%`i9{9b;Ew#bIbp^XIal@e}3By&)>bQ=eFnWT61r)t+4mr z(RKIlY3<(kCB%TB#CAgsc0(S#Oku&)N$5R9c=K&gWMcGvjfM+T+SIMSE@ps0?$cwsntk-~6=;aja1 zOBKr}9%9U4m(y?75fgltI+kVZ z5)I9e&>Gmk)%2tsrR5boe&KgVdUtdo{x7ip`a1EZG2|t8J z?q!leO3C5lNM4?+FXj^+9~R~55}JRkC~45-vSOc7O0B1=^qcymRaX3b>-H~bX(%Bk$8J5T$5e8xvQ{iFfLf^JS*A+3FZtAx5FIHx)U9B z(O9Nu1M+Y#s9zmWzkFDWY%LfG)#;dpCj}T?jybpKcB;kFQ|~>CYNVtSeK1{=KbfzQ zQkO1~cJPMM8(O2^?(mwJ(i>%Zjh{xcCVm(8pTDId^&1!wGHjBfA1Lyw=4d^WdNqQQe#etB1OcIrzv;`_?MbM~RlQ#}Egovy+6R;UiZ{1Dk!;JpX1 zAvq7ltWb6SdOTqD>8xf*uM0ZX>YlP_RF;J*DssI+DK^NJh7*;~7nK_?Qe=~hDOZp> zYs4FdE7WoE#;1zj6wwdupbAQzayEd|lWHBHq_)dx|5aGn68 z`e1oQ_7tj}*aYR+m^Z4lVDY?*6&8t$6c(kdq!t#{BeZ(*O;YcOc>)O=OT13JNOHEA z*OzcWDE^ZM#)!}3muTX@@xKs^(eHKzj5Ki;{DEL3go**DR8#~G|7cdj@rw}BoqvoN zXZ7f;UVHxg$}wqhqMBkOl=Qt2tt@txqNO3c8FOGU94qKD_H4!h|6&I8rA;Joh50lORuw@DyA6Lx;nr*AS2* zfX`U1r9%v=Md@ue@GAI)T%m%rvh+=d18hK5D$3w6mGTf9REawvSK|5$~fG~UuQNAr<@Cv4V| z=kfoB1`u=w!UhcszErCC9B>1^T&b`@5?>EqU|5PIc{{2iuAe(YbP_+8WA6vD9r*Ze zZ+EJjV71m9$AOU?+M&sz&6%8lb`^3n;2#A87LD>4;4rj@gSDvpkS%IoMvGcvZ7J_W zw9Md4!6aIr#d1OpZ+#{QupG$c>bh!X05p*o0w53|-G8ndY=4CtA+c1sk5X=ao7}%) zvfM;)DzRZQul7`CpnYwabQbii738eX7SZ(r@&zqLEC4Jdyk?#I&&9eyuKkrFx~?r% z)mt-8N?xvn0W#>sNkpi1=2S$?}S3bJy zpe4O5-@bVu%`u#oq@CRp+jE=$*UHGZKi{*h+q-r}*WPv;$59l=UDnwf?%m!syl*hv zn_JP~gi(zV?7ZFW2)d=(RX40WWleU*d&j$bpo}&_89k)ggGI1B_OvV`Z)YoDop~gg zJlZ?UqkY{xl0_Z~Dt{7u2t!C}?sX)Ml)8JCQVZfKRaz-kN~sxKSAa&)%jTm_(oloG z9>z8Z#Y`SEjA({YY=qLV&bOkKdt~x*+mWt3(S~MD7j1(EGG1rVtj2t?$<;s(X`<5=j*>ac6?9%boZCef&dTyC&H`XjkV+7Vvx|g_} z_$k(bEyFefi;S&P27&HGI}3Vvkj&CfC!I}_7#f>H8xU*IuUO*~;IuV0;Q|Za!+bqWG!Ook z8$WF}Z)zsqYU{7@b-&y^xb9`&a!2c5E z7vxr>@^!hv-xfKudHi${UA&EWtB5Y&-2HN~dC<4+a*>M3ph9^h8Z{8=PsHsKe{;~4nY?LP(^jX^ z+ST~>l6_-!xyv8fzvqE1wSqtGOQ$knZz#9^=HYms7YL$QDost+_ob{;>(c!x>-fge zw|sG%_VCq1Qyos?%Ahy2I<@T6%Ku+r3Twvm07EamJpE$IM+z-1QwOJJ|&F<00rF+p!hc zXJwX?@vK1K3ym2JSTC|2eawxGH4kJuR(Qx@7y3S>7fhT9 zj*#qYa^R^$ePXyZzDKLjVQuLjvhj5;y=7aQF%FFn#?A*|?EI!? zKbBFt(xy9epk}_AeC}O2d{lLrt6k^dY{BYbk?UiT6J(LsW99I~qR(_#0qyj8YV457 zQzw#xpP-zBO7``YgWyEu6#z*T&`8j z{%L5Sef#a3ktW_c_YoM?q%bq)$G)p{MF*D$_XY`z8h01;LrC)b>x=p(ls<`*_iqUV^AP@qNv<)IEc8g#s;@f3Kle|Z>fpr;s1)>z?9(Ws%kDcJFQc1uw2bmZ{*_D z+zWnn)o+B#3Nv<=DQoCKsG&LRm>e6p&PEVVgLJ_NM$$8LFUU}(XOvhA-~WuTlz)a; zrcdHgm>qE{qMWM0oC-OoLe3$jby$)~41ujEuO+~0Vgs&Tv8=d|3Bwc>s%uJ+n|d;@ zm$64%_SK_p>hMoo3|8C155h#j;SAb%s&sfUI$$MZvU}RTT5(=M9j5k-xLbd1MH=nCars zV3&L~Lvi8d%7Rnti67^5F6D*HYPO(?R`Y`5j3mZX+f$c6uoPwaR+uGPYM0dIudMhc zR8Y0J1$y|df#$V6^+IxVs4uko>H+V(mkQ<=d8rG}%v;VitCl-bU9ogejRf7-aM{yC zsb{bmS?atZb@ZY-{l${K#1lqP(oPSH7_Z{{5sOknfgAQ*1OCS=X#mu_K20y8(hb;cd z$e?pTSe}Q6M>HdH>8Vqpo7K(BN&;{~nHE9wVDMCCYS7f9ML1WkO?6gir0R_>0-U0- z<&rrqS`?D_>+IGqZiw}C6+%@An#@ik9UmSUO>Vhkb?k9-cB0_xfSjb~x+NWJn;hV+ ztDip9Cj@fAQb*Yvk#Ci0$h9>car5xuC$3t0^R_k#@{rPl<7?Ws9aMA_|E`=3%bj=A z`x-!mS0SB$cuR$rS25=iw8pA-MT9ZtfQW|++QeXlH~R+6!l<2vi*+sB)slDX{UJ9;5`frZ@rYWjmNC^0 zH_iUwLTx+L*c0UmMyJyn(0Mxl@^uxfi&1YWO`dxFEc zx>uWYZ;LC$P2xV0P@0c_hdP7@EvFS_t+XAhO~;d{)w8PUxQewD!cBFr7De6LMNAY# zpGX)8wek3Oa@X;o#&Wu>#+8<1R6h9s={m-LmECa5vij9a>n%DGbrmN%Cz@+|GR|mW z#j4RlG(L9ySg5}xZl(zsHFcCW&^VB)Da6gO!q}?uLKGO57NM3{?WT}datO53=QIWz z!;xIf8%T6av^8uVsO5}if#WR#>fEr{EmE-F6>W(50yS+bF-4l1J(|7P7qP!9yI#%U z6>JJy4>9k;_JP-@LviW)TVbdywD5L*PuEnJ#Pd?u6ghG;HgbKx_smtj&Fgpf4!u3L zVrV1tZr;|_GgjZgR4*Xj@-(hvG?B&)Bht{SrTV5@FiOjhN#3!=tfQ%m5kMh zz9Qg_jPM%To>EGHx$FC{@}4Owzi4a-N@h$L^Nm5t$iHiM_Gd--+j>`@F2dhu=g$=F z{aW-Zj~2CxoDv!MlARZos6^E2=ds10`mkfA&V#a}#qu(Ltb8rja;l3gQ6*AnJIPXR zgwpK6Nt*ML#Zv-5W0q zx_q9CXh+M(>sHI=r4fp<^+&q*j@H#p+`MW7Ejpx-ucVvYsMTpT2AjMe@K<~f`L9?D zcDw99v?WfbS640s=IE3dr7j&&v%lzY{hVNknU%bxRyRG29L6O;K z$}IT5P+erx#yvkfm8++ z(@|kT?oZOgp$&;M#nwVYf7m3~EF>-+KkHHO5QhJGVbiSWq!Dpi6$DRXKvpF(NksZ>_^ni1ItsnFS# z%V7ONKh6^U4QmVtNC=}*g^^yrK+&MT$q1)5Zji6}|4921__&HQ&sXp2<8}8t`gpJJ z`%t&kt*cwNx^-Hz7j}9?XthWIGP0O+s{ILHvH=}RDilZ7 zZ}dzn0UCoK!vW5RrgJ_tiQ{UjEBaCRLBBXXWmBV7Qqz$ti3I*Xsz$_@7oW9cXelij z&bwe(wp`eyHAlUkpp~P4{VSTY23+p22y{Rxe5eKHsLvg==;`ynr1hfL=?+VzuJBQ% z$!uUKw6?aT@F-f=XAEW&I18RMnT<5X>NSNEU^69OERapmw|m z%Ynw)j5etvfYWe9#vAU}@2B<^*J03l7{pV^ zqo>@E9QK7D%)^J1)_e0rQb@j^qzn{%KIVKd9~;*<+@HtqC@#koSHNa`Zv$6c{hEO| z<%?e=oPzru;nC*Gfs8jckndVy^wveftyLbaQ8Kk`ZtEUxa@@Hr))J9wD=V`h^7jS< zXRHoa*ebK}WKWeX=#0CJl2r`4%odO0&MZnDGT3~!Xfzasebop1>PA*1LPT2iM?I;t zr_X^ka5$*~cjW}pyUVVlV#jUcwT~Lg`ldDfh(th=(b5z}Ynj6GWYSmP6%6&J{fRV+Zis^q$t{eXXe9=Tb;Q4` z=zIut6EQ*p%ZLQg1D2nyNx+}5vkh(Xp&=HX1FH!qu>!2h#lrLhO?zTP59F*%t)#W@ zUOuUzGEkrl24C*p%(!ngSbcBK2Ye{JP|NlCt|PLE(Tn4lqsHN|dGYnpi%ms0w+nB) zEf$m42yf#x>Re3|{XiamfpzGCJYf~!R~QU^_vZ1p{0@IhCU4eYFmwK`#rELb9Ya(in(!)w^wxv@h1-F{T{B7TMO6NcGh@Sw?xhxOhOcN5gb^P;|Z4-lT9ZQ!U%am3q%8e^&5%tbztx%8$HGm>&^bm>greUa0ak)x{cKjtAt;9priOQ9BPEz!2dkD4uaK>q6(Z}OeU&wgNz z?fNsgaqc-HscJa2mCV-Ed$1eKs5|P@zOER{JIq!muc=K3I;Yvy9t_I0>udT~D$cr8 zN@-qPW4SWR&OIj;1N_%nmuyJ6B6cp+wyd$pA2-3?sx11|hQt~9G!ZV-@#uI+_U$co zkZMat(uupq6g3^smz5~99Dhb{%(Na!=1qwFX4uqBV{k#N)Cu;W-(I`1u4P%xEUD~A zO?u$6CgH|FiOhrlBp-vVi4YCMCKY*C>rsQOL==2EXCbVW#{=W~>XeseoO^|9>rOEH zv8@*^G?zSvam#Fh%H#R+qDcjyQp;HGmN{_tRZX1RQl^t-0Yw$KM;hwV`ldRjfA_s`CoehYQA+By?uCrErNs9<{8FAul2kfQ_!H zPA%El9qvy167|niCB5}sDmB3?7;*LDn4f@yfHUdUIfFz0Ty#9ZTRnTN`)2I2A3Udq zJ{qI?=khc6KZi9|C7~Mp2wov+4Ws3FtH?u5V=L!YB_0WyO@5n(2K1|8Y(H(`k1`@5R=Ysi|+ zefB0MJg&d@S@D-5DcbHsf#J-3dHk%XzZV-qp#+MI?Y<(7 zpTp`Ky8(~RZ}-eVz*sshiwlH08TQDbE=I;#|p=MA#e2(h*x@qwtzFUzr9I@+4B=?q3aydxSI zqhPFXc!G*8jAJ8%u)mHm-zI8_Rha5T&>KZm7tB28-4lJ-&OL12llZ!3ziL>*;U>?0 z@Uz!YFl5f^-D8eEoVS~EX71stb+(T8U%3_o;rE0XW%Kh!B)#T~dKRgv=Sx2FEro#px%(S%@oqL9SwN zkYn6?#p!_yyJ7y!p&+9XcEg;;dsw%XmoJ-A0b9v&z8Ldc-(ZeLivA{YQMqX}$BkIeY^9qbFFv`y6(+sHOJrqqMx!>hSU;`#AY0lt7Gx&3*Aj$Od?o z1{exjBfQ9Jm}h=MBh;)h>OO&^8bO`KUJx8%AY!FRuJH+0M;ZEv>|5v6#s3K%m4oL> z*74T)nK_Q?bO44*P6mZz^!cJsLE!{?H|+xh%;DMZK7il7dCt2xU*X*Yl}(NDL{np; zaGD7>Bq|yj;k%zBNT8qkKi~~!Jsf;1h%jEcW5UiwLNBDpn=hk4Tqc~u=LiJ7`Et3^ zpd!=06SD(Mz%v?@3D_lRM4+_>UCy9YYtq?cUT=k>(}l4bbS{5slQUCAJjm~+nfRmMAjT~IRj>mPG<|bT@hKQlOry7$fg5nlroHh zf5<6=pl2A4H+<@IM-`4!qHcH0uGiaRP#)rix4=H)cW@Tu#u|>3U4(D}`MjOta`0FY zE;!B?mu@x1Aj<~xbXE4jF;={bF`DgWQPuO8Cl?x*LG!#=tQMNvsP z(Div@g~a(Q_!UG=Ukp(*=0ntEjk0)zF-mp`SNL{=9I`90!j#Wfi6~m>3sj-cs!NM- z6JFBlH8d6|ak>ex4dO=Z{;w|`AnFtcy>Rd%3Zzep1fxL;3 zVE^2Fmi1e8P9A+rUETTGT8x3Q^q++{a`=t{Z#m$-IbX_L;TUXBwKgZwFZh=vI%;XD z0?(DM<$F`f?sYtHUR;tmH#cv-eQ1!dAXUveYm!3eV&4d>=kvM~p&_JwhFzlDJL{ zL=dkou>o5xtQRB?k}x)qL#L1^kS6o@2h>eh=q2(fCkGWJVB?^C314CiP`(749xw%Y zo*P!!eG3j2>+bN>K`}dD>b}CoE8IX*m!J-iugq!#7nSQ~+Q3C~=yawH(>;t;ZK;cy zc{`qb1y%6~ZIl#LMd}W{9JI?p8&`Ow>;`NUjqji~F;Oq9qjDYv@lsky5fM@ZGlkP!}L25M|b5N+qZZuvO@M1EdrnK;d0V&-?`H5gALZzz`{HjfV$HDh1mm7wcYh zK+}AWHd@?PyI-PNa+EfjJyyF#CEl>p<2O=jWv9Ml(|G;;&MjZn_ih8_}h=&y&u%CL!Zus8wq=veNAc(KQ zZ#rCf68vxGZa50ns($AbGNMz+4T8dH_9&~DJ4|F#a3off(40i)N)|N|u%y)@$@F~H=uOAfPv$}DSid7GBi0iEEo$u5CpHvOWo_Vr&ChF>&@97D2N zX6q}zesg^MjemW08_lv5s~1pT+z4y&a>KXp_LZuEwDZWiUG=hua94k@f4>)1=^%tu9eZZo8JW zkH7irSGF=*mSi~5@ZZ3bzj_@!`3s$(hqYzt^Mxg_zCSHIOFEeYgjdCHr|m!I9WNl_ zr7q%(_q_e*d7gH>pi;1mhNbDMe|r55%aoHhO%Vp0#Bq|%00@@T(!A-3C(vi%Q@<10 z(b$C9DQZ|j7JaUdTUlO4{We^97Mv-ohwyVd{{lNx54-U5yq)D=$g|V+U`p9Yumyf* zg$aABp5Y|fFlFLVSm6^-sGm~?S>isnI0cQO>vcPIYry14Zjwv2IPftMZ zs#bNJ0KP@AgaiJCYLKK(Lgs>!182Ukzl|ejPr!3kfJ(e&nU}Zzd2r!iKW+3VlHJTv zm7P^KCD|E=Q(}izcJnm-_fHhQckD#r2mh!S^$bGrD}H$F)X3<>@gJ;!^FNBwn-FKg z+C7bHSF3WC7j5s&6X!??s$d45@D4q$9;}?8)(>7N*6+%UF$-$x8;V}Yw zVSnEsP4R9SPQ0{ayfdkQw?|GL`ys3hWF=U~?}Ak)j)65#=uCQsg0uG(KR7-CYj}Lh z2mU}Gg`;&^jcyd>N$}Dct?e9Ve@5jg*mvs0QP0`V^6F#@g z<9E{EfSQ~aY$nzjKpOh+6b(nKKf-bTARhN`gy-+#^IP%xPp8~a_AbElL->4n$_-@z zo!^G_!Z*opP^+0E5H&a8(az+Fd7~*=!wamQ3nEsOWYv&1=Z1ps;Q1%3VcBTZY(ucB z!K`6XRC+^06mdaDmZ=B=S{kY$V}s+OMnmZDjS$nM;s2}Yxm?h(KU@L%DRN8t5H zP_qN)tRp|6{)OHJb^2m;)|3Kh+Z;34oin&E~v#=Mfdx;-dyG(=_2 zp6v@l)<6PXb)<3;fkV0<;+DIqt>h!jwdHeHXJml@flC$xmotnrhtJX6bsD*-bJtQX zggIAiR`PZ@W=fL6v$EdemleN72MS-Bb*(x=-C3Hvg8yDRD%8xrCI|>yg>XJi3$YgR zni|pTUb<{mTmNr|)9i4)X5DW+anUmje?nc$LM!E&&1%Ftv`I~E@gs}K;zdsm_y0DJ zAN8#JZGO(PO4-8+{4D%C9fBRCuptN^l~9_0`e=G!HjMCi<1oRk`;7TflC;)cUIiMo6Y=@3ZM}^vf|?DIIu;5 zR`4jY+r)s_KHhxkL=0Ht)ODi5YYG#d0RDUmEn?#Z?N#N+Zgf`*20gx@2XtskDPDe3 ze(c>?m;i%_25v}$ztRz639+2`6{5iz$I#LaI_I1OFP;rYlBotd+z7|evpFL}57GlB zbX~D8Ti!`4T`1p3)j(Z1rwr(H14=Gj#}e`0ErSgm?<`HxOOwpdJC2Cau@wIJTDJJE zHeY=u62iey^)5sfEm3Zo^9NHHy?ANIJ9+$O$Iv_ZIj{Z<3bQ2w!-EnEZ}3kaO;fdW z&`^|9jlZF22-lRz`&Ws7AaEE_>>)2;MMPSO1)jpY@Jw*4XWlz`N#Y+4d>Ivc#N$sv z_y(^6yPNc;WVb*A*B!ic>5%|%_{p~duEP5+f5887iH0A~9QpkL7ZA|h_e>^~kL|tp zj6DAIZz#ME1W3URQy1wpT{k-~N-xff(u?z=*iP~z!AW2{!ECrZFN$p<&yS$ID7KS% zc>iCUBk)H;PuNF?OPHH4IWs&X#bKJY2Nk1KMXIs|AlMw z=<~i8Ze9KG-dyX>`&O@hXdHgtk1W4aACoK{gIKi{&kJO3l60IRL<2VoI#0RQG3z12 zTsn`l*PywqA?HGOCi3_e@(ch}K&!u)HWjIceW+{HW#@))Q9?;pZ}rJGpOyRAESne< zPz~a=L4-)iD{1UHJhSEW)Y=C)gpWkKXkdRzG9*BjETk(oLF9E8?B8}`zpBe^Gu?eYEuuvoY>+nM$hXmH5qLtEv@5?z`9_# z&aBi7)U|J3Sj{06Ofg!qW%V_kEALpBa&+%o^Cz-K%j=mw$tmgJ5TsarcC+ri){RSh z{Lx&};fwm<2xfx-$0&pXN_22fZ~dn2*Y*BXXU14(s8FD8fc-N}d{ynAIiPOW(0IK4Z`TEo9p{N^L zu6b%t&%V`-1`WiStd8aCm+k54ysA4;x%`H~8x6dUX0$xFw{v@c)RA7AZrQZ3Ru2&( zP10Ix%gSBZ;UmLUzV>y^+1<-i2Z!#yy4B|Oaz?AiX14RJKjI6tud2(e$p$on!)mq* zY%sevQ_-LC1}lP$;I#2J(PRk+<>ZQMd)u}zYc!Bd?Xula>H|}Hx)zSxaUw}<#8@}! z7bZz|B1+Rl)g;NBwIu}qx}-BY3FOnlsD2|oiqr4FArXaBLUu%^LL^mZ)q(;lc_v>h zinH(FvZc&W(`K0TG&h!_(J(*`0zX}AmR>?hN)*3Yb7_*3j4X{z5&y|p{8ipyjmPv7 zZ!26)7OKE&3j*QSH3pQH+MszIsYr|0UhYdi?PWESdazX^$T`Er?_Na_xAqmXuF4EXh)b7 zd_Jw&We&v(57Ja->DAYda@_wWI-NI;E89QP&6s3Klk_(i3^}S_YM$Q31w!Ug^+}cU09Z-GX>0J*B4(Lf-MJR^$Id2;|kLSXLG%$%Ur~nGq*J>nX=D zx3QQ|2)kq>J~_!a(7lN~zFD4kT{DwOS(Sw1j#dXLM#mOvSd$WT`Xd5h!5=R@sN;2v zMI!%fvT`i-y5x2^OrM-b!-fXA9WK_>wJ?cG_O)G?rit5xHT>K>Ej%n}Ph85|*D_s~ z$_U#uN8LB5mV@bG5RQV_0|KdjIg>%Db4wWkGSsVW*Bsq()#KxB6^nNDwvOifHCw*E zb<8+j_Dav)(TqzV%KpxbpVls*0iOm$YqN zH4yOjEFH;o9T=`l4ee^rY*^9f4K7@{fgD>gwrORwtF6gXcj(f$lLNVSzpt&czjAEH z4x9;vpeCW_sU|v!Em#8FHAn(bn12hDfHTotArR1HkN{L7RbxG&DIXPz-k6yG1GxfJ zC>k^*K>n{B(I}YzHDh!6pPoA;G|Ud-pN*6<)TE_m`AtvltytP!W7gBGL92_kFHeu% zGh9VFItEu&?|NW3TAx3pBO&0$y7F!^iL0+Nu}KX15c0 z(+Fl^;NZ~_-fc5BZ@p(|)i)0G4u9wO`wtzd81sc#wU3gdObKFCQg%*BK~7!&qj zOem|EP@WeP$|X!Fe<4gry-<_?(ZSokzqz7%*N+Y!`u>)R<4W81p@mm>xRqA?=^>?j z5fkou9$~_tA6)bBo^11u`_~jdp~U_eio)Y`1=MRps>D{1jvOy~L=OHCidhP-tALVe zDIu&kAv}Dp6cT{cnOVxgA}lI11E?Hto}UY*>iqM-MQlT)9%uDN&7~2Ifn!;ykbr3x zc9axJ@k#7^T9c0HMv*rt@2=z!b>yEgh?GS|gOijN5CG7MQ1=`sKb3&v5&(|K5wP0xakiLx;A*|;%s zS$3Qm*!T?wj#QV@CM&O@^t=Hm3x{ijv8&py8mKigoQ~1kvcvnbYY(rjw0G|xzDU+X z)H4?{X2+KHh87L`Vt%xrgu`zO1?{ncd}qU!9YweZh;0xp-Us_&O$ja5kPCCh#mgg$ zbCJc1BRR?hb@~p77F)y?(U!qzv445xl+VZXkJvspTEtKHXH>Mf+*di3$1j+z=&ytp zGyjiZ@xJ!`Pwng&U(?KMS;}P8rI+vN?%doRNG!i$(M_t^iMZJGr)R{z*Pb2&X zbz$ASKoOS{P{GrAwIo!Gbzc}KQvYU=?$et6I3v)dmpC}(LUDEAB5mVVrp5Y@CQ@AbqFd48*Kt&Vd%9 zl4yny5Y8Vehv!T{1a-1jh8HK`Ll`KY%m5_&_6{YZLpQH#TNA;cu?@}AEK7Gx*MuAIf-av5$+gB@uwkOs|ZkVpeKT6LY-O8_72bd)wj-Lv73 zXW?szY6CD#PJNuy!xKaERCP5IQ2}W8WdJlWdLfY*9eo?`VS%wIK7y<^nt&>~x_K%O zA4ePas*tq%b3jt|^FY#Ppu!y=x_P86EJ(>E2afIz59TUOaMA`eoQ{iR7S)a3xw3+C zbSzp`v;AvpBR`fi5R@+LfuJOP^lH7(ru>*D}n!^}1c<<8vW;2AOf4KrAjfEl@kW$1l$SqG}Jla4k0L2KR42q=}!C(dj z)uUy^^knf$x^$(!bfvy@1#gWl&TN(4kGHCVOxF*pOJ%A{QTpXqa7vcyNr-;!mKa`N zjp5WF#bdO6TWpe4at?%zJ!tnp3;u&paw9I&I63vpoYf5$HR1=07tw=_Pm^5)F?Hb- zV%r%up59BVxFyN6#jQx5oj^;aohTYgkKWN)4PV+>s&r?m(w)UhcOu~t^(Yu#pKfES zAVlw(1<}iPG@84?7DnweSsRc%5Q*YHl(xE!u~%Ur-}6O*e96W0awPwB>-f=~o4>ic zIX1Anr*#A(`KvIJuk`0eTNmsZi2mB$xICTT1F?MTmV7+Wv#mS3q0M{vjzhPD!4nzoOrVvHXU3ceNd%`PL?H{lQD$Ne*(SU9$B!8Dz8t~;@S)kr zzHH>47u=V}85OthD~G&CE!9IeeIKIx&Xh$9@u5~9&kog$-MyxgOh2$OfB)KO?T+tW zGjz*HE-D@ibZ*RcjI_AyaEcpQ*wqGJTJZxF-&-tx3)((IkobkS{^N+Zx@Frv%T_&l zT`!zgefitH5Z_m)mTazX-P9H4YE^vp?M0tIjxE^K+`XkM5M8jT1$~mrZ7+Xqu;Y#w@44;euMXz! zc~vzMMn!ycmI zNdq$ac1+}5#hkN==dWP%EyZepH}n>tB73dccLb|}p=iDKBhD;4CB-Xh6s_vaq@_0A zrYDVVS(HTs{isFeX_VmNNq7o#pTA{hR-(k+F&KMM(w2|CaaNU^pf$_?lpB5 zxvDE!w<4R+p-@JW){2?MW9e15EUR#I>|ggJcta9m|W>suMyM0jn%>bH&zcf=N}nsSl?g6L$z<YfT;Lxti9I2e=N%v%K`?{QgzU7Ol# zS9VqDDJI^z7D{zw>Qm}49fAP0x@gs{_VbfqY9h)oL~0UzKgZjm&gu{8D!(A;{af^E za@v&Cgz)+-u$~hjQm_6nUshBpl}*%^@?-xEwC*7>Tt6)Fk7+Dk8;Wi&RKn>YgXXp1 zF4p2rcmhd}@i8b)Y~gXT@DE@IXz>T$F1Z4~%?NHqaw)Qj9O6uREO(aI7{&8 ztLpBNM)HB#&3-macUM=Bd#Ya5``3H_dJpZDcz6o4H;I_NI00CmscU@)3Ps&|ON~5T z2CfSauw_r{J7wV!(lSGirsw-hAobA2=3O)#!>YLxNsa`3)cdh|k*&Wn$~(Z;4HCHp)W1SQhQPC6wYtvhx~vJ;v0Z zOD6TC+sy~g>&N*&NmD-jncT&uUURbkTv@8F51cRS$4UNAQ(8cJ=Bkaop&7t>Q+C}w z%lmeWH2GOB-n03hRj&4?IIG0vl#=phI$}fH=J;`ALuY?@@%{zj$JT6gwv_TlSHt{# zekf}J3+EnQ)!=Pgy8X8PId|W(eI%`*wXD@*fXiKqp_takCm0GZCc=<*ZXVR>@ zb-vG68bWfEn*2z};Z%dD+a*NZf)F(aXWWyP_4KAoAZm0EB5HK=QiwV~6H#jjxst40 zFVmR(R&SUi!8b0P=QtgUA3?M{;ukuH)y#bS1TD}LS(?}!3IvcZ2U&OrU|(*AeTfkD zKs7FR8pj0-FJ5EeEk2JMp)&74fy}95M@u0ck(2Q^tmAXukUCe0?R5z=uc2Do8h@i~ z9Vh3!G1a#-BYIuI)+=iKT8Jq1@8# z1h1`LpY2bZ4e5EMwPPM@!_tn9MHvX&Mq4_U6pVmw-_jomcCT(u4)^kIYa>5US6tU0 z4|Xgs#``-;4o7p(Jn-ALL1QrQc4h18tchh6HdM@{ZO(>#-r*@m%#OMOCZNUN5_4fo z=CK|z`pOA6T$Spd!dMn-6Ovp-jYeD3hslm}fq)`8Zac4-J!{mMe@(D%vgyNepyOOw zsIIV`mvX6H0wKyTZ~m=8h+;6bxteh2<}a>~4Yh_fayc$jP^2oD5P8_iP(!4szdgH4bO7Z{eMDXmUzv}t5&PTjEj zz#KIk3dse#dU|hJ+vsi{+ws}@wYM+u`%6Rd72}i^MG7B=Z8Q(I5#j)~R(FTgL$UJU zMqb6(&vWC!KhJ`PJj2<~m$`8=_~%O?L)m4K;k?-Ld$)9NUDy)hqRZ~RX4B`FMxU_N z&#!MAPU-B0`3-IJlN_$Q>D43i;1YVz7f1L0`)ji|lILt!p;o6sf+P@*neJJ5A#0@wtC_O|E(DQy&gQv={j-2~& z3SrgI1g5ONxyxFA>qr$XzwjJMD@mD(Cg1)QXJlk(4g*ROuJ3ggmrnko0^V&$Il2^D zl&gzX;D5SB1+;E6XVTK-gM^GgTbUIf?}W;3auVABe^nm#g`D`Y3NiE6LOhj(cuG#3 zRUvt9g7a7TVt+2ETIn7VG=K8EEPi#GKM6H|{C}YNMLS(tuRW;Jd(9TNUQubIS%{HI zl^pDq>s)ni#7;Y?HdZF^$|GPIhQ4?b|=4S^l` zJ0el5^Gi_Y$MP{DM@muD`Ave(PqzNb=OdHrEgyc8&QCUpI^Wm&tFrJ2Y5DM~b^fJ% zx`NV|>~o3IwfC=SUeyzWKMyBlQ0e=cm$f&o=m@wv#(UkFs2{0)3y$KZ3UO~M%WYke zl}A>k!NSp7m!&iYgNos72CYf0FhF&dpI=P%q^&X;Ve)_psP-Ws;Osh8<+nH%x`h1^@*pDH4bWhjMrjJ@ttIn41EYS{<*4BxJcXUlf`$SwkP$W4+Tws zZdw~$h5c;iF5s!Lo>+O$>i+Euie4=n?%Z@_O|ZKm#K=hig+rn66z0?oZJpyGVz-Sh zyJ@cHAw#0SzI$iPUO&IkFqAQYh5fgzY4o=(+i|F`^K*x;Ta;5WTAEVp8NE#_r`2@h z+5^4TSZdL(;bdRl5w@nsq#CMuUznAHhc3L2Le+B`{L5;Nno{(S(t~0-F!}ZxHsM zAIo6vRZSlQlb8+*pbQ)iwcDYCf0bF34Hd(haf0WR+2)2+EFdSsZGGlU}est7hFOHT9b2FAL+pgIHj7Prg?O5NLoZfR_vTUT1w8w+(UFO1%r zb~JYlfp^;m2YFX66*q_0R*e3}Y}#f{WlO$+`NNW~YL(=DRJ8u&P;L?p)`Fy66YhA& z=Tj8V)s0)uDQboNUte#OB;D8Xj^M&Ao-5alD=g=#3jFGwx3byJ+g3K(ea*dVqkW|i ztHR|pA_NX(=3m$A2ZnrGe{9wML2qKk-D~={FKqB>9<{^vDh_3g`qV(NXB*zwamOvY zMzbmntF}53xtC$pO=}KxBM_ThxU;A4pfC=$9nP$SnmgD$6iM{u9R4b&kMtr~i1mxI zW5iQXOx$?tNsO{h0P0EYNB|zB6zPafE^c-^p+$F}Dr-j&^Mjg7jD9Kmj`3nzU)#QK z-@NzK?qbWn7xv!_k59(t?do5AL%%mVymMgeh5;{r@V*a@t{VO3`M>|l`QxibA3Og5 zbL7>7rNO`X_73S-<@J$f%!}p3aq7i6US;NJhIWYQ%#qjjjG(ojW@z3aB`>e>(*95A ze2hxgb1zLWF0)Rc^TG8MK%p6{Y(Chst=LUf+s?9S+s=V!Xs*~Ons<_dd1nNfcOoa` z3v10gv;DEphz}tnPf{}Sj0i@a$dhIGWUZ0sQqEcZRgn6SceYq1^?kH>a|7pqtEQ&a zy(tR*W)hdHxTevqtt;IupPAyq}^o6l)?aTgVn$QQf%>UfRlELNCs0}WY-|n8x=hG^D{Ne`0=krCqD3E~K ztkdee0efoUt~o86=Qq+gnO!LI`96}#gq-L}F;BLL|I}6d_m`6k+GO8F&O7 zjJPXrS5vwGFQP>2-3~j4f2dTeL}K61z4)6K1|%ulLTVrm++DT(oSwpg*(uvk9j{8Z z{t&PTr5nez0h(V%?V0wgf}+P2I~Y0$R&JA;} z!13eUo}dM>{J?|PJh-iqHJfQh?=Y}dR%v!ve4ShRTUWNYWdhMp>-DH~g3tC#I1WHz z?PuXyL1Oyha^fXS?+j>#mpWSGDHe)_z%iH{ttqZA3<&$)stn#r8@QhBS#B=Zsh0#cW!K^S)%u9*YB7XbEdX2cjGZsX>SV zAteYZK#TxU93)UQ?I7}(CV;`hf7% zq?`*LDUdHbzKizc2T*i5Ji)6d59|;gBTx`}UWGDFQS&GqL#|VFMX{ob^d-Tok5L$v zsI!Y0Bv^w%P{c>Ue~AJ;vj4iGHeSEr#9Sf$fgr_R1u4N#0x7VwWHlH?M*JXk?Y9oz z_{exXUH;aAo8a+V8hgBXFuiC?i^QlJ6NF1z$8L z`JQXaUqWBO37rFOGKI9A^e>3A6F^agC&(HiwtDS#TP zyFknHyXl_e*9o9cu*G1PD*s(UW#*I6xSgI3)y@#e~d*aCXs1Vy}@# zk_L6R9(JClgi`I4c=RQy5)g)kG(1E`#-{+ILO5E~xrl!uaW4dHiqgR78mD;|cAn9e z08M;asJd$9ioXh4{cmy#ak*08v}hY&%P9ocExBjB!RT~r1X7{H6NG4WQMO?*UxQYc zG(^zulZZen)WYR~9!g4b;q?+)RTPF1S0fD($WKMI3WVLId{Zz9LJ1JA1A#CIgh0>% zLU!O6rq7@S1kE610wE&^832S4P)GtK2*^O(4urW>C(fk_lLelbJgk%nOU!`v%V$uA zEJrfQPE3BxJ7EnL4STfP7khB_$i`B8pLcM7ygrB)o^V`DZo!iy{pdU&A8Yv zaK_5ti(q@ZH&pq3H4rp_(8FcXBcDE@6Y`*W3+2W*yI!eN7*r45Bn4eA`8UUr7J)NtpkW`I+_FPs7gy4{Z8B+nPA5 zMZ<759ft^-PMfD|;}k)28th91O^BBzh9<>EP9HSo?@k^gxfUA;B`HVhNoOg{^G4lg%{Ki z;@%R7a7GE0)a0NOz7rmQc?v!wxLju}Lqu32^dLln!swC?amhaWi7S)PbOP_o5F1HK zqY|l&(<>9LN?Msj;L=R5l2#@XyAihZ4>8fB_#W}Fv@-d3VT9l;u!$dB3!d#8jS}-4t!xE6DfvIp^;ERx(_~D zLU>Mx9>2Z>>PtPP@e&azfzkvX=QXJy2=agMcog*?#L%KhDJ_a5tEWJkMOj4SDC_9f%>W!KaKx_{C8X-BOx;U z8Z7bSuYv2yU&9i)R5E~{#WxCd6jDh$d}@hMqLfO4_-ptn@@-h6l1jQ^orUBtU}lB27b>RSfZ6m#)y6R9@$&4 zgp*2E-w{>ML#%|@Fu)i zZUXP%DV6!H$Q~yD3UQ;j0jFy171~xEF~3!Yw~72KWmrL6Gf6R9W^(r~yWz%!Z)7x; z7>x)cN;UP>Y>SWLOTkjYqm-%Sgj^RZ^+wuOwYt^GvA!)}h|%8TbjcVK$C~sU{iXB} zUvEh@8%zed*36phI;&CRDGnw4-HSGLZE&GJXopg>1pdOIDqk-Fe!{DJ`T{-0Ku=Gg zNNB7R;E%iw)AYx9ldU&){Ku|ex^S0^OuyW|$MjuEpaf+cZ@dIw&))Bf9WP@p)`go0 z_*i@T<#PKT()8Uafs^!ZO-{>c99%ye2G`dxuck1t96?}wNqpYUp48k153<5ijNFs$ zjLuzP$@HdsQKU8$4WWBvadS(cE#rmDPMjc_x>9ebbw!JFXfV>9cNmL{n>`H2DQK-( zXL9HaTyujnsg#|W_UOPgOh3e6y7z< zHxC}K+GcM-zIjXXWeNS=K;3jD-@JuQzFe-UItwUX-5weuho559yOpdk2;`L~Q0y|C zRLFr7_H4lFh*+83w5D<_DeFyEYPSpRbocIiB zdAU|3;^|MfM@EW7jW0}$?CkNk7eb6oMxf~F3Tw126>d*j4DmU+P~N7~>P=uL#L6;_ zy7GVZNlVYBZeNC9+v!(oIEsSHXovw6TAfDgtB-inZjDlB0)u9QQe)OQJx}4lU0Q^2 zz%tkd_rsBs#CXvXeY@V-%KW^Z4)cAb%_tBYiJ%dzxRYX^j;- zX8GV_`D0~Rk@LM&=Jt@yFJ7iSs?&t*lYQ{`{ek{+@4~ITopI&oZ67eFVjf0`<~*e} zkaia5;b{lMf{ZUHU*nFkJ9ylMSopL>kt8b~wFZ&7$@osbmS;3*sJw6XB7 zGVwe(0aa)NIKexV@hpM)SwH@JKky$l?$I{6oAAjd&~%hQeY#=?`v0=Ks^P;%|IxB> z57BhAOicA_bvvAt5o4zN-DIkDzt8Z#<6rsu-5u8qM3yWLHiQhmu8mz?>)Kp%`xdQu zthJ4A)q1nRT~>X#B@!(7*uKHJec+mF&6)n}oOMwynksth1_~})raRWQCJNuma9;)fY`et6q} zt0C!yfF7qAxiwl0Hq6i42sV_+IeXSL+k<^u(Lk^r!41JeAXxBgHQszEKKJM8@zsmk z19DnJQH+-MnP`Rz@}pT5r&jvg$8rs;=R}L^KHJp3v}iSja}ICbuQv-`sh#-!;3$+K zF(!>eHUSRtDg)!N7>+9;1We`5TsV`fM%v{mhlMd5FDrR?%aY33&cx5;rsr`nQO|VT zaWU{b{J`($=+~THuj@5htD%4B@_L=GG3*hi?JPyp)LEO;=KK-OF!Ya{u&2f*e}(S> zEtnd!V|ih1LSY((kvb;RJz>F8@4bn#X$f(?cv?msE6Xev-4kUC3Ma>Y|DvduCT_+R zz6Va2dRnVigZI>$+H;_A+H6j%(^>flo!IQiH@v!X9IV6Ns73)qF$(tyF$xc$7=_^R zlSVgv7;8H{9i;F{RA6ZnVoi(rVyZ#OX9g);U21AB$^dOKYK>YI?l1T`T_EoTO0}8O zSs4O->vH&Zi{aZX7v3&kjZwHyh*1b9 z0TiR~z)7u*5?>DmDHPE1lc+>|zuQHm}IN$|G)2M@!ZEKa_UZ@|Aajplis z0drBT6n}@a{&-otpY$Iu!<$#TTL<6Z>$#@4Z&Qcc z+jULfylZ&dovgP$=+ApuJ^US!oLdd%4DMT!O^)3>+_!gWzJA$_eZ{dRr=vJh>{?bZ zx=JIkMKhBhfkXHgAet{$(fo0q65#yDj{W4p8CagD5SahCEZn$ae5NTRe@MyMjVK-` zLh>>ILCW(omDp2^MgaIpfbm3PNSzU^yk*3UC0R03ama3@0)hY|naZVHL^0 z{xm}J2?XV3JgZ{Ozuo96!UWRq1Fn#dTV0xRH zs;pzQD1atS-x|!i)b&YUA#8)|Pm&-t;X=C~wyd3t_BE^mPK~U=oovpE9!0Q983%gm_7kzhlRg&KKA)4ra#H6dj|u(O@ZFtKof!T zOT58*Fs4w5vT2ZhtkWF@(MR1{t-2rEUt2z?m8(=rN31#K&$zX^ zaBE$Mf)W#Lw9o0!H#+lCr(6aX0)QZ?Kz(PZxS+wV_vc)ZmZ<63M1S6?(r~QRX4h#s z4Quss=73F2G6s!SPs?%%e~Q&>WsF`!YZ)b_=hW7COVF8(I+Ze8MBtShCO^Vk@Vf-8 zzkV9)-&V!?`+1!yYFn}F%Dsp$zJ zfE}}tzbpSp8P<=cj+LV~lcvX}=Bq2lR0_=i{-znYUmqRV%*Wc34mb&ra=FSJYD&2C zAyaQ(veC?G^#-tzRx?cHzw{}#c}2VLY<{H4tx~Hgvjv5rX0(jj9&={%O05Cd^qj@% zwBG@MHQkE|evJ3=1RPNaEsDDcf;qIXqf90WcJ|}24Jb5_9*#sv)&o3`z$v;2ir>6c z8%&{~Nc!P2tgrPvg68bvH!sr$Qx>*MqWw@U*iZO6RyB96ZgIN$Zd_)Fhnx(eBcauC z)RyUt>4AGF(Xt?Cz9ZG_Zg*&135PRo*SwJ!X$!`Ocl8b4y0(#2&@{)g21ce($X)sF zkj~&P^k)i^n`q!af-A}M1g%GLLRee|$jT2;P+M?HrB)F5h}yvEs0xFof0>F= zf=QdxWg%!I#P~tjXIa>1h*{fN&8ZRthcN>d0Ef{Y8n3vACi^^i95HLbQC>$S8b~$_ z!r!p&cejJ~hif|UGPoW-U=4qxY~4?`KU~v;jk7xNvdA7K#+tExrXJ6FI@Wh}tl>Sz zP|@#7`*ilw%Fgy>MO$xk_sHKSo0~F)W<$`5a0ibrP)c`ed+G(B8~)kEn=%OR)~ z9-S@Y;9_+!)2=+5gM)kAy*Dg&PE>+{$$`m#Cbp6V z%vi->`pK7sumgH2E`eT&{A?^3R0^T>0SY4>pAlexD)u(0CQznOII9SD-aA;U}oztIi zFu0OZ5DJE)WJa``R?cv$KX+{G4aNG#n>#l3h9mu(+UmO_8dF_oU8W<-S(1E(Y``m5HaJT&F~C_LH%IYXUG&GIE`?vPCBOSU`tLpDHsvEU#JC0&!!^ zMUL#;j9khtoOoJJX_S=-CFd|2oE$t;sVTXfQY%59l5-kRh5&e`rb(RF+jYvyy>OvK zlE{x;R_g3}9V~-qY88pBJn*i-OJ0pVK>RK7FL34;uK>`6^>@UX28OK8(f2v8A$QvLQ@?X)!bAhAY+-)_}EP zbFg{X7`6)Ah;73@gY5@%g}&H2ys5mfT)bg#^WMnzU3I%WtJeqCD|_cMb1}Y4*2Sjt zhJ1PNuJv=f^7*d0>v!!fD;$fLTO9q@?H=0QapS(8ec3Hr>$lpLu5_)`Ef_V8;!Umc zRw|a%BzN!Ix^lENnQR?hxpm)eMQD7jFNCGeq|R{FpwTG!+nMZ@KLC0{_i3I$Q%v!{ z_kMgx$Q<=4Us32kzps$bWy8`@FCER&v3g%I>w5Na_I-tE=5=t^_gDWt;`MYspS~A; zoX=%)nE*PgG-TnQCvurg4qt#iFW684erT%hg=6V_HX8t$d_DtyfNoZnqtElG_Pywg z_)-=vcrv-l&vLoU+wcN>8J>)yZ@US8fNy70g$up#?7nn5k9(v_6$Lzb7d`g#d^(?m zCr}$X@H6=9Yq|Z zPnVUus=MWDs9thKtt}I!GZ4*x;c8B5VMnkKP~3z+4Hs~+w&A*=1Iu#x6}Jqv?~1W1 z4Xv`#wuQ~9;U@3)4bFyCkYSA~njsc?oQ%S3=5p)5xN7a!%8fq1#%J)jSOx0|_HOCE z<2IF=RZxscY>Tt_`$AhhgH^8=3)d4u+uRM?uATffoN0yyEJ&(RCUeH1!VKhTwVIQi zwoKr+p5TNO-I8SV*Wa1?jqSqqi_-N!5pSbB7s{7}>$~7@epk4DS-4(`{{vwse+#c)k?OyO?-SbS zd&2b?yxt>Re_v>`9w<*ub{a8a@NR3H<`sBIJAc3zFCwK;9h+<2ndMn;iP znWe<6B9m0y{qmjwU4&#ZV)ba_iL&&-DbhTZo>e4`)^2wR35HT6Q!YaCorb5^9fF`$ zvz@bQuSH|TDxiWYNS@Iwiat`t+r~d_IA-)3k5Ro z{h#x`GnoWvtKa?Z_ubzYo*(}?bLK41cAn>)nK$oi@EHvm;nKAF4tJ=+pVLvF6I{_> z<*zKxG3d1}qubzUDyph9efazbsuz$? z>f|Tf>U{8|lMcZ|9ZHZN?@AvC07c!0*2y2S?@pTK?~&z47GgukvbEc8k8kU^?`tyi z-03!ha89c>W(6D`vrc&UdxFO7bNkZ_Dh>al-AI3S=fCan7zJ9IyNmC_c0~}39w%YD z^fO|K>ILxpFylEDJbw~Am&i{6{|0+(rwfL=?i&VoNw#QBL-ADNS)FdiKQ$qfA@2&! zJW4w%OX7J@f)L-M+OH&-z<9gp>@siSEQD^qp16f9`UgTrHHS2U{E{(gx=}DpY?AQM4 zz|=F^6s-uW(PLUXE08P_l)t~YCucT0&1keSwc8R;QpGu6ex#EzXEwU1fhXRgV1Z@A znG4TPu!}~h;D4;*4Cx+EszoiXi2c5S$8Oi-$uMv#PS5MCey1zls^@#he$2mNvgmkD zrBRtXss#yGa8-9^P*mTYwRaPi03epP6|j3c^A{YkAx~75ISWL1bH zW+Db(X!g-^uDo0uoHbLS$lk53r zioY;>OC*r%ODVSHRORlvHoYLrE}Y0|Di8Tm>>mC>kKJF!ep~f$NABEnS*v!jjLI@BDOA2)@r7wdwdewNqVvr@8p7E^t?(I+ z^T{m5l{}M3t=16uBq10Ht>>8!&oi$WDRBt9>~;mt`A1ec*|%(c>;+ul3RU!LVT0La(#W!D%x;U>W!9Y58a*b9$E5zf#^Po>DwdJ4`)Btc zI+dDg_T0<$pCq|vy0bDaQ>pZx*{rM*{cJ5wfR5RcnZ)nJHv0PlwpH;z<}#ykmS;AK z=C%6SDD7}}iDNceGBf`rk}dga-Q;hIsw=aTt{8Ay<=>PO-=^}<&?i=Fvub;)>$WY= z3bqVXr8oHgRj(JY@Hm6oY*MG>)YLbo(|a=2j^ET*w5}=NqE_`9Y(~+Y>ds$1Uf(+2 zkz+F5o0VmD7}%ZFVh>wAHkVmjJ8|>C;3xJpSX|zmpuCr&`x30o{P=er{X|bJ2z@Nr za4g;57q38YN&ej~abo5?Y736M>GWBz={CFHqUN8ak6oiztMPBdOKd*2@4>#dTe&-! z{sC?)eL6gRD^qXrQy;S=KQg-HW%fJ7eV4aLen_;u%fN})dKqul_zw}--wFtbm_Nnm z^XJ%9v5PAC@*|Db?00}`)j74%k?MB)jH1>s$I&OrJ*U#r+wZBl;n=6*UdNw{OBB0@ z>!cOTVwRNH>DWJ+jYL^d*v}E4{44GW4M)Ya5Eq!|W>eS_f@qI0$xwNB3Wo(FuM?JO z<`)H9CCYP7-JDYu{~6>4vp>$Z>gRLQ@N%oN3Z7QjV|=iEB&nCL`#MqNy_f4dnO&-< zFC@E?F41DQK}gS~rS$3e4nRqZXJ0P2yezA~@=7f|%}Kj}gyxkm%)>&qecl>!ta97g zlc(fIPyJeB@jC5(lje_{4olYJGU>Qqa-7EEL9JPnZf|o3ycYGt!s8m7(`#F;w;Of* zZy_B1LFvoqzA4bl*+sR8^7j(;Pxzb|!aDaFpJMZw)heSkh2E7%-a(T_`FGY6hKhT+ z%_kdk9S?Aukg{rE7;~{Vi+Y!Lk^h}lRB&0F#U2>n(S@_IqqSZxd%EDR2rZj;P5uu$ z>~2FgzsF;K%#vg0L|$X^IUF97Cf!=&@H-ub&zMtvUMmu>+2^$Q%o_d@y-AnGn&II- zl(w>P+4|hrfQmkNS$W@|l!u%wlw*gVF&p3E&woR%eCg!6?0!oAqW*vCKlw$9cYftYM`=%e zLr=NWUb?QKVSTw%9FBZqciki3-F^%3`we4V`9*6+s~gAZ>ln+K>^wtKNuFY_95r1* zR+5HbttZx;S+&}jJ?o^KG-L8f@8$SOPmR41+Bmakqw!V?PI}Z{i)|z(=hAC~?K@ZX zYE3?;1B&-qzw9q-b6#Bxt@GpR<>IhB{%Y^9mjlUm$7OROBRTC4%JN%edFj&C%Sfv7 zk7>%T$?ayp2+^aNvSB(b8eKL^nmP<;N4@s`)Y5Ew?7@WMp7z)TPMcM^GB4a*nq$=Zb4t^{ z>axKWWd=bwYo$9WE`Bn@{>Xz zf9*BxEz#~mwI$VQLpE?1)Q7_Ld2W@}XLqFHQ23XQ{p-TP)%!ZQ7vtd|*05~NGM}%! zyPSJC5hCdCr}DXxG>|n^f(J-7ILilTrP&5y(J=&JznO7=l{WDcVv;0C35nz|e?E*q zj4)neN5y`Oy=7EfL9;fBy95Ys!QF!eOK^vv!5xAQ&fpN--QC?C26uNI7$8A|4Q>H0 z=e_5A=ez&zn*LFH_nO*ISJm#dyH@v8{l+RTf{SNJuL5tPh5!YZE*|K?_XM6Y?`~@X zUXLnAnmRrzd9Nv(SNC0lZx+fPi-!+r|1R9TytZNtM~6*)KvQCJ*j%9U#VL`Y)w$>W zg~{{7o>~6#<8GH0f~V-UNB`5hiuJ*<7rnOBg&a@}`Zaj?<#dC=oznDms-j#cpD)%{{Yi`6oN|976rTkeK9*{qcM=@5-qJYv90tqz!Q#S#SC_}GV z+I+&UyG`wIthSi;;ZXIr+SChN>;&9IrwMBF^$dc>;KIG}<2!!OJH#}K`TI+e44-41 z?G)2U&l>lnKQ1Zuo-4}+>oFGVU&=;`Cu?kH?bqkCcgv3eUAfQ5VuZI3ImE~7t+Uz= z4t4jd?tBYP99l!j^GqJ!$_ z>cnc{Y{)!1}W3g#js$!fkKTVMbEas_c4(pw{A z)zeNH@wP!qJuF@$C#j)n89Q&dZ*Hz@c@}opZvQf=oxJbpX~35UE?Mm)E4?gmw6%y$ zCFEhz`fj9j82J$M8-e?0z+U#%ItDCrCp&X-2L@7%f(u8By4*sDaJJd2v&!&K{Q>oQ z2cE|mNbXJZXQNXgq$aJ=lAjWfQl27^vPU8rp!WRX(LQ!wlZEBpkB+h$CW6IHx^p3} zyC?&8!X5DN+1l-44r4#k>(thKBCckA05M&;Gd)YMyNxI> z>>Bp-@-bCgUXpJ1mi88NM`u_1wRn>z0sBoiL%^w_ih=H-xmA4K53Uu0&*@Wz6G=91 z88rvD@LMOYd>m~nyR0nUv4>40ZC}F-tM8|OTp+IT9^{~;am@^xr0@ngIcP4k>p!9k zrq$Ic_{6-XY7YWt9JayBLLHm7igicpZJj1DR8mDeb#f(s|uu>?AMy<0p~ZT4P5~BlUXG?UZvv zLW6`%!wton-NUO#Lnj6&yhFMEz?!X$r0-$Bu^T6=cu!BpLuO4KFT20^o<`Hh|G6Uu zCY(f{eI`|6+C=QqWWBTyJaE^EIgKOiP5gWLKz`%UfW;oQjr8&;$F3uNm39K^bZoCz z4L?y~j2n#(-e+0EXU;wibL@Yp%hc{U7yf9O_Ge*|qjl@5p@@=_c)e4w$qi(`Jaf=I ztv4#|@T1$(K8$zi2e7*gFq+zu)p~n-mFbH-I@;w<7$T!h;J$2LoYJ=68oN&z=L|oY znN=zL*iZI+wHA@d>)D_CcVq=W`0-o(<2Q<#?1uaePY=NM{v7|fgM83!3-88_p<9Bw zBX1R@Xw3Q*Px`+5->eG#eoNl`(PlJ=z$XhN-AmLXXZ87Stp7l2A=hSsyU>E&#XL!_ zo31^UWDq|;sU1a_Qn$j#mW_@izE1X)1i+<(2?rDXZr2n@Xpw$4b6m_*ZIo~V01OHF zg_gPfE7l_&vRS)Ek+g0sqWdGu!p2U%ZcJ%WcNs}Z%i7mcQ>A28t-ka8)9R5I)%Ne} zlFWB{eg5PQd6F7s+uY>>b#S$`|M6A!)P9a{i+c289hBV!+nNraA??|bLgYH8e720a zV$wg}NoN$}C?6eM-I=CaUl`Dky%!HiSg5Sco?jv;riKnryo~suihkLsK*}vMvho?QHbc&QUMbI$UL(-i8AQ-!A*JaX zwZ329ywWLNL6Eg*>Arrd+p=Y|*61LW7qNQRFBUbv<sf9ZgB`o@7HDV!&@xP37F`OERc)$4FiLID|VPMSax`0USy? zc>5@tB5l6_6zAP-fhP^)W)`+gnx1O;lG7k2C@&Hg=g6-Gs8m@kAwo>qBInGEuGyMq zBWX$siaOlieqUtw#`mMm4x!D`v=l!`)`wq`V^mIAqpx`F|7!8mA^B66J3^p;kddJ! zHn+s5x_{F=WyfK^{FhfZXCl`y-ms%{ZMpZN`~e6L1Ijd zU`ZBsE63XoM3bOp!)_Qyp={isqh%^J!Ejk;er>m@W=;3(a*wUZ{U-y%BZf)5K=@&? zF|SXQlY>(GzyN~uqPDQN#e0@O?o0tYt2vw39a%s8IeVCv)ulxD(9dK+Psv9qa8T;4 zK7)1i3GJJ$VWPxYw|<$6mDS+^T59_W>s;p7$dvNrxebePTTu_T_IhWN12h9~^S>gb z-hivY+?Ep-5q|p(V7Zg0i@L-itju9&Z0cR@44;E3cxE~)vo%<^u>Zg`kD-^{7NGDG zkC5@5K%28pw4U|uf^K@9n2ipfce!z^H?!V*9M%8UXTVE!Tw7)0@vr%ktA)RBR&a_> z8kAXd5&Tr!|Bs+bu5P&^Cu5jcbF0Kb)ZRsN@QK?>*VkVi+{%1d&!wtPR5|r|*)7HW zayJ$G++Jbc=EM8>tGHrJP0)X8wF|tagt!Ck)X@i5Xmx`?jS6LrS0xsMW zRGK#!e-bsK$BPe{nb|3>hz*m3l20^xKPO}7Eo8(_TAmPG`T%s+@<_oluAmP-3N+FE@^CLhkby_A*niPpZ9u`p13PaHv{{$ zSXIu1^Idh)Hsn8nxu3sH7hM-`dEtBICZ3~nABzd9gj;g^?#i1_aplIU%bQL3MvZIu zX?Bk4XGpFx-E8ceA2$e-GMsoxQ|e3d504+f*vl7Mo%+h%#IPg<83>#`L@Lg-$lWl7 zup36`>lye*COzxka}XvS&h*BBz;7pGZT!lwO9(l|$+|AYXE|k`S54*HO1E#78O_q? zC%F-4X4F&j=SJ~vGKP~4k_Uqn)8=gfD*ait(kR-=q7qE+1W@cAG)FQWViTk@&n6J; zYwe&O-TKNM_6#(Zx4JpUT_uHP$v4!SEOTZX)g$Biq+!J2tf&iCzXIAO+PdQEy&^h5 z@{s(pc5&rG1HzWR7a0Gzq%p;UlKGjc#2N5-ekC+%#L}zUuYJT~7hP-cwN4AvS-tNc z$Ba04QK_ZjTm7Z%X_Yt2CbuT1*0ueF}ygfdd1qd|lUdFtc{R#8pznAv%xHVk#F1~;19`zD~JokF_ zu07vEp1*FNi7a}%tIwC(@^^PW{?WV9i@oi5b_eXYQh=X^Rv4btpYLzPICqGMC?1Ae zMNu$f8#qdQ#IK3v* zo$O2_Qh)xZ9la?$^{-)%cz?^=-beB!xLK+#qhC?p1cAGs@$$9ZdmGCo`jyc!wLJ7WQRJok*=X`jDMf#-USlDwzI^sf&q z{#Im_u`3fQZFBm@E<{ItAO2s3|3@?1{I>Y%g}i|Hlnlz~wjHe`gJguP-9lcVNvOdB zWgyK0)(-oz&Q9}<)K2t`+YXL3KTd~U2_??=HOZ+yQB#CF1%p1=V_L`f6q;(`5Uv`@ zWJ9=E**Lhe62btU1gfULxwJD}XA z`o3w!=4VGb86s?OiZcWA*AR1x6TdlTOg&z*SXV{V2L9a4>W zqICPA3rBNF96fW+g*l0DIvo?^!ih}=_^R>5JL8%P2M3qm*yDz>0$|FBXTIKX7C&I3 zN|@CpAWga95{1T)aIT8MBXgO-vp(qUU_96vZ}J(B7{QNzl7jaL*!>nNLrh@?%)See zR7pURyTy@RtV#)glR)sw?J0V7~Hz&)3E?ZDW> zZ!NqJ{Q;$VF~o@iNv-Z^0EGh%TVWfPqTn|E+-(7IS{Us#2xLHXhQ9-2w?&8XZmE4X zB6~OixM@2R-Hl&Pxv&%oukx*5@0z|+ny$P8pc`VAasb%=b}P zWJ=;-%Ze`&{45v!j&?1K3GG&~MZ~blO!loGY{ZK5?aSSs8ou0^e*nD@3&1I07Ge$) zYq|V*LWiO}VTrRx!r!Qy{Kwyj5z?FkSoum`a73v+*r$og@W@ohD2yr5U!|J^qrC$~ z=0;Ge{~7MUh9VPFSOBAUovg`#x=(P#ffSK}StXlLQ)#Nn zQbe(`2co?L&|Kk+L_DZ{+Ubyo)K}MvHa+FThhm@XF5bSaFQ=G_c4%(HjTvCc7dF zMtf4ig*NzMn;Ghsriw&&%)ogYuMhrUnoSs-IuFYj#xA;89m8?#LI0W5gNVU%nn;1L zaO8?0ObPo`><@D%tKN48{Y$g+uc;3n1DjiT0!lRaCtN{>^N-Gs^|7Bbg4(B2iO8Tn zwgsYkl}32vaBLzIq7(>rcB1IO<IF_7p{`(f7H+U zja85Hzpe2psQUF&mDt$dIMZPMfHwKVfU=T27n#fBsJ?GITvN9Qa||z7Yl9yyGlY-~ zZedJpjm4;6xf}&x{u5m)a!s2~(f?$2tfSq@XvDB3lXo13$2eeoA8xdoAK5%uMz~;* zWrhCV8H4VH)Pv+K4{1OzV6ShD5YOo4bW#+1j6GAr(4<>BkYSt8zXEKmj#%F{$T3Eu_kL*@In+|Q-ZN7etDla zd7E;7$|H5h&6q@SY0Z@ZUof@hYm}`fJvWlJSo(h^MCS(`FwHtrSt8fOmkX zHD$ewc=S#y9-E`07a8#X%m?~jV$jc)WPQ*f5!Nj(7CMi)4*Pgl5j|2Azr-g!2p!B?CsG9nxV! zr`H5`LWp$H2#)Og{gP=Ztqf=uN3?l3yHN6 zS4az20S9%eYZfP>XdZ5D2;p%EfAG&!@4oAW$v(kjon6f62=1o^XNOZyShG<>*crJx zv_n-DE^Qm!nW9(iKK^2tzNiJy9lB$;9Wpy7LydVaXpq7*Ksg#=mM7~wTUAXV?9P(T{WLk5GW3i3pMYlvrsWHtI&hyUfwB6x7y~sU*nNf3R zC+`>(`uKGosUJE}usC3k(+RogCwB183RIc{Pk(aQ=;)|`gIqR3XWVdH=HC(44bTl) z@j}UV0}cz46w>LCK#X$lW5E78aZUGK32bcM+&|VVtpYYyY&Q941o!4SK=vSp9d+eKX39mF!9nK?b>j-_fKoK(b683mO@;TQLEpH1HI zAxF$-1%6;rlJ}PV`};a}Ni_~`peO*! z_(@p)zvRExBDepM)i`ilmZBudZ^=G2BLt@e;^zqgnFT?PEL4jcHFSCE|W6G2Mi~kjf_`wi1SQ};+zb~p969k7+2Q$DD07aXZ?S?Ba^{vo` zX=&!TMfG_mC;P1SU;RTL|Dt{c!hOj7RH~{1Lt>42i!d))gB^SuSl{!ra0U7A zPt2B{^r-NAnH@oEWGE;Z%|~4q9`wN*ANXAt5%kd;M}Q#)#!L-{QO?bjotolF{O~9$ zVV>|`5y$(*FaH~FcIGL@w-Z3yJiO+nM7^YFCs%VXZ z=1|3W6K<(Hld^`mXp=IWPc@862`?C7syu-Uv`ixmY_w=-@tEbLqw97^-A7&DL9vCm z-*!-ROXIPU-3u*~$;6}Fi0nU_-I$KbD!?wpBw(9u!rP57%KwQ@f_c4=$QnE%&7+Cm z=}6X?^QNbRXeNpCy?u~gi))3Q5(@S_A{sQ0TX5%voQbo&+`v3!DUU5iJ)zP|+*JOH z<`O{|Lj!F`sQf(pus9%2yzg|!l!&y(iVQvGOyaNIk~k6?w!z9@Mp@K_x$XL~MY4dJ zTkcN(qgSqU)oaB+xrK<%o9mEKC`BEEk!%=@^yB4o(=h@tLJwew_4J6*p8j>#`e#9k z98W)OhU^$u@SFF%#DQw6RroZHs+~w6&T+ZKPnpp#(H>!{nI`V(p*}+2W4Uun)7i^G zczsS%>R07Fiu9f{a`f|@e_Ru%xu;K;#;dtQN&dzmrz4fEw--gS(WfgmvX^FPE1E4) z4I5Pq-?7|Y>fD)TGcL~hg-psVomwljS+lF zuV46ZjnfHt#~#l@;ThUL{vpf%M@DbDRGO0|%hUM&W#u_{1wTtJ-u*x3XfG)M%w7O({SBAgRc5)3Y%!eUD1@stL^U^Kc?TWZ4vETJLc(ojhd-t zJ`|tMrdaN{W-Pxx@2=D3OGHQNBpVzF4oCK;%w3RVyhZb17rF~Z>vo2Y5OFL42x3Vr zgjKN)^oB;e662J4#I|S6(^@`U$kcSESR2NE4$CODn4Y4oCI^tmp%2QRmR5n{j{{o4 zbwDZSY{=}*mx>`|&*bm>FY&lZGCGnO#<&xkXXZXbHJ~+99(n9_|5ElUVudl)hgj;s zY<%f-Wvk>?xF6szIJBC@PJihA_SvWm? z0Do8HJW;R0Du}iDnLSMN*o12_HCDKmQC1Ck{K?fz8t!u4W?trzjtN&0HF6Xiha;;1 zymnX`caQXr0SQGg>zG5jwwVWqyRZK0#Ol^EG&q+g-BCAoTW}C*9wZjs6`S6R(_bei z`u=`K^ECQgyC!%(`hJ)}`%dTz<9V&eJA9n@KADO;Z=Z0q=?%VB_UF&=aphaSf}tlI ziyABz?=!g(Tj!Z<q z)I3}5^Kf_3sRn0*?74u^wRioaCQn|(Z$4Jq%SB?5km2n(de1RG_t!{ne{f2&iAAjk zx1n^3qT45v$-N)c;Cji1htGO+^9b0NZryWfkPfXR9uTXAbE(eJ;&rCL&`taOMYEYO zDVcGZf(}oS99cAHvCGVf1-~u^!+vZ=TllE+9;FHH5Rc*3FGkT|oco4wmwkhK+DV*C z)+8U%sKW@B;pC9+Ql08}`xf*{Zcm#J@cR4mKt^SxMLl)P}Itd6Ia4D3mqQ~jZKpJk3AZ5 zozik#nug48C;`vDb;a1d63{nIo$1m`kcy^R=nM2Pqn8o5g0sQi8qqO6-$qFQ1$MR6fU(Yxwn&Ufps^)Al}bA=Xe>(`*}3)r~I zFV~{?kVmm36I?hY{7ZSJ5k6Atl2qzpR9dS3_+qMm_-6D;sKhx_(L8u41b^Z&?TT5Y zC8g5hq!nsf*`+Mzbrob9aCl3qTiD&$vN~6EtB5Mx7E>8~daofoNgafHux-A%7v2=- zA=}>D-mh;n&pn4^^mqbQ*+JaG90S{k#OHreTY8qPmP-#Eh)d%37e=(19Ywjc6*Boq zujOp>JAPM4nX;c)lZ|MlCD5g+*Q$|R{*DgOh`+VzR?#b4k+#=TX}){dw7jQ#Pa5m^ zNLJD1^hi$K9D^KJ6e92-b;B8{VilI>S^ib(M{AGHUxq1PIYKvjLHLWWpC-Ogez!{2 z_e|+zJc^V0ov;%8MGQAe+O$WK6PH~}L4vA9w7ruJNBac+@~fRveu+RC_Z%5|wWe)R zpyOcY&#yIiHBqkbmC?=J=IWkk=H0jq#rJvBeB^RQhP5M?yo?w(O0}K_Uc=w$-?IYN z=v67?Y)%km2|do*(8d$%n4u-l{sYyy3-pRYslAB&@4OVp;g9gl7w5W0toiQ2dT?C4 z^8~7dn!M%9-~2z3Ve8k7z{B#+LCAhuqc)dQoz-mMhCiG2Gg4Bt%;U;F{3-xm`WP{; zk)dDl`Pg_P;1`k`3B>A}_G`5)uTo&tsIMa7+1r4M1aZ*H8&EbdL$!<4$1+ixi_F^* z5r*E2>wtdL$d}i^8WqwIgjY4dNaHBxWBP++W7?kks z!twcNzimDf^xHupg?Flqjav5b9X9nMf3F#qjDILO*3R>M>?a!h$+A$%hpeqWtSb@m z)e0^oh#xUhlWS`7k4+Z`Ncq;oSiZ&2^he2h5Dzw+a~k{l+YiGXm;4+ETI z7_*ZltaR01q%8i^5l}N5yg=#}I-nq27Q^C@=ogqnuv7VEWD_QyHv* zLyRL}*MLBSuwuGL;E4p)idxDG7cGPhVPM$y-D^dGsrhvRxOyTvwu=eZNI2@XeK(0Z z@z_wC88U>~k03)F!Aj3qBgafYw6S6~$%7h7V9e1#>meK^`?Q`u&YA;eNnybICBga^eBjH+d4r<*eULVHo8G_Uz_U>Sv> zxOjZ!hh`uNMqq}xVUa`gUsIe4e<#G-@{qy;K5VNg8K>0V8SXY}69>@!dp(B_?v*Xm zCy~}H_`07Me$%l2ZV+CZun0hja5w&X$>;n-V{5ZNhkwQhjin!@%xvcp275l?r)pGr z-kRq0?tJmV=U>);hc;SepLIsYw(@BRq^;mIz9}GZB|kZv_2tQuuBDR?KIr}012*yE z-Sf(hc8ewO`f0@0t!2CRt6~|z#_)YP<<-Q;(?B+J} zeh7u#JK5*+OOG75DAc!tfjihgzB@uf__ywQ539PjjhBh^}5i>Poe0 zT}>qui`rz`>T()~VfD&Nm1fl6%BqRW+Dc2m+r-AoOvJy!$yF7@orZ`ik|mfe%m7sl zP0gNk?bdRTpC8@Qf~V%PCMHYdQe=bzh=hkcZ8q8sp#J6AOQBY${z*y^K^|vW)cF3VUq6Sp@0o=ox6m zrd?0WH!XNB@9U})E&cnuP$?y4qjOxifVi(wNzk&S&Nts>k+Oe-c9!yc_Frh_28n3& zV+$|lt!fnK#8ar^yoF}eWYp5aQ{`}WeB_g^#_X|Vgl;9mzRg67uE27ogLHhzi2^}d z+-*rUsF}o#?I5Iyhww0a5?B6Y|0eZo6}C{Csgdx8=Wb9GI;DO#avPy&`@^%Z<8FR0 zp(yC#?RpU1{*K)zz-bpg8lBQu{{d!@Xqv+(yx{Ev&GbwNHaELZywmP`zUN+a%(1u^ zKJY{X7CbcLDmP{#vw}M1_4gy`sQD!u96kQ;3)ZuHlI(r%40!`~RL{Lqm+8Eu!D|+w z8Q!;cC*j`Xv5td#AkZ)B>^;MOaP{xK_}u}e7{wzM@tami#8nowEs>LO=&&K`2dp@l zmM=+ZFjzmfB6FtpPcMJKw3H*8*;aa?(1#Is(*X(-mUX|CF)ZqK%5SkoG@RELYS#d+ zt~Is=D>|J_dIMSmre!&ol9Yv(h1&&ZCR)T|EGGJz1cwxdG_Ml2g)OzbC5|l)Un7$E zTnz&GStf1m+cxj@GDpXpe*0;3{Rp zzEN9lqGbZ9u8;i%JZHo6k`@WoK(p9cc+EveH9a-9&=HpjCUE)+DRncT4P35uP z@2`xM+M?e2cFzHV|0cB2J&D4Wqo?0z%C3Beo$N8uQtS;8dwl-P#X%c3m8cqXNp5MF6QrmX~+L&^u4)9UW8)4Pw&|zTG*ahqhvYUhsfjq#zMANG=i}tPgCv zbH)aB|J72^*}i9qdVIZOj|J32fusht5kLu$0b4s~%FsJDXe%;E1Rdgs1cAT+E&|%( zieaHms_rZbdkV=VSD!61gw=Jh^mM5>U;w;3_QXJ2IKVe30X#rt$DRiWMupr3wB-e~ z(d?YLLGS2+&+x$KrmG%P&DgIx>O6FBYhGeM;%9N8ny$_%oL;z_&n0)6< z6nbY5y~6+sAwUda0dEzD)9*V`+h%{Yi9_%3p<1wzROmJm9OU;-4Jl{~74p;%#_k7G zLGMVRd>9br1_J0Zy#X8~vQJMCXo~z zR}} z&V7gBip{EM?OY9drVA#ccVG7F;RE%sAX-6f?*^d(g0Uby@6m${ct@TW*k%;iw*9N^ z`ywxWSjAFuoTGb0F8}>(Be07IV2K_3_c-z>?io#$ z(`r?F!mv&5tG&U8w!(mR^I5jgAnpNe41M5lPb%O2AL$s^0c zne)3(U;v^}0wh4&ueRx(GowDR1N067s1Vpj*ayae-m(0TXJjM#V>^?{_WAa?=_-75 z)?pl4nOB3__@D%cfRjG(*?)Y30(_~!#^Iv(HwW`vJMp~~J zr9C#f=1-Xwm>{}*x_o%y*+TDN-y^J1(Wy@yEg65JWJ12=gCVbkwZ;J>KO?@pYy~`^ zW{2(FxB+e60o%v`CMX{Y#1IiM`tkMbUAzh|1hz#>TPe?^Cs+FD5YH1)dOV7Ewnys7wvBX@+DTux%KUDHJ3c#eoglU<@Y58bd-%kAB5+0*lr zmnU(PEK%z^ZPuTi{)?T?3(?A*$3u?`NRRI%f!Y`Eqrb&y zzDj&NO*+yVXN7293;56kcaO9bd;%|FK87Yi?LiiL%ooP`Cp z<`P&>dz7Uyoi564ZDk#-@KV3=P*nPZHOJRiX}nxIoi}6CJDkBf$jTH73LB@T^!3jh zrq>~RHUg0oYO-9ZqjzsSutu75VSfJ}g~}$GRDnAizdTe;e^F1U9Wgw`dSG}{Q_CM^ znW|w<&twQ#py!GDz3k)#%`QMu zp?KV=6M+Kk|&e{5uvUY4Vc3Ver6<5o%7zag<*gl&-i z>cnZ$s%la~o9Tz8?TLQv<`QcOgaV$b1Y|0?B zjQdgyQOlsS!Oqd_sY1aFgTzzoiP_P~)6L1#f&XcFrxwb|6F_26)J=2NF7Gk1m_q62 zt*kTayXZ0T%XGNiXIc?iLPJhG_ z`K%yFeg)2kZ^NTH*J=zbn!+&+zcHon_&6HcO`Rv7 z3T_JVqrZW;$6xS3>-MAvN;$fmZht|T8g-6Vaicb=)te|^ygix)SrZ2>xifI}=8D)* z%e79tMBC)5S8|dg>GQmDfBzaubX+VRWEE^jN6)QxAOGw13scQ*!GXo5DWWlU>iSxx zAby!vCuDYkC!245?-FCZg9EkhHdR%{Q@^6>#7(x5hWx`kB7*z+5cwGUb<2`M?upXC z_nLIs^G-RPM04Rut>n$tX<7v=JP(O8SWhLQcczHCI&6Zlu!)HtGISSRMfx0U1^R2% zSZcv3n^e52n^3Wzgo*n9rd3Bm6D2m#d0j9w1WoqFBN4CX8v$YC!4&!MzMsFTm$|MME9ac|^YW`8v>A2OSNag~eNC!_L|}Oa?5;=4d@##&jr0!YG|- z&5*9qI_$xeR+6?zA#=aNIv-*n=PP$n;FVT5bY6C9%=%{o zuh!w2tLS|r$2}Gu#K6Rp(rq?~l$^qOX0|9{;k{7B#;?L{dG_VlQ9d?XL%kB!sr60y zRvEZznc_{XIq{b)EN~dvQ&y*zEnV&D{ZrDK)f^I=nRo&?+S4~*25L;qWm;cCH zjqJ&9y#U&~FDFcbk8-em)~VS;>u;iPj$#+Zp9KDm9r#Zt4oz)Mm+{&cluyKfvzIxZK zY~hH?k%*tY)k_rxc}q?;baaSy5(U0o#@nTm^YP+gNiw@>-{wx0=cKFS^rCa)1TNhY zxoQ0+t4lXfnjS&`ly1%BD%vm*qmq!eGEZ6HcK2D2ggEx0W0vK4vWnR{)>|MV>wOXU zVJ|EoKt6TsxZ6H^@m`=Jsf})439xX|EU&yP^N})}?~614oMiu#Y0%%sjo-wD3;vOOY^Xxq z%vp5i-uxrSWXK}nee>eiMKc$9zI$wScpH@!C7FCz%o(nbLQHZW$!57oUzWHYlpY_# zo!i)-VEeOENf}~EvNYN*Zap!BvE5OVCT%hzN1u3eKV7qt{6DzgMpLl9)Vj_&l?25` z{fY%o=xgaHhmqsBO&t_CMehCn&Oq)CEnX1vk`E&XBtnjVE32_op$mWX)Gw2vNK28S zD8p=$O^`tsF?ZT1NNmk7PFuP~g(s!!ZbIg<{XK7`DD#kD@q_kWS=;Y*sj)Fv9Yrv? zc9=3_p`#Wtzu`#+5!IM#(JuqZ?4=?-Mq2tqu=B{T_++zj1gf(PWU8_xL4)rAz?FL| zv4_k=!Ivdrw@BU02`q`5O(mytA-r&X8(B&7;)J==~De43K8pfVN) zhOoc$&Q5<2JcEHds6AOKNr^SZjemE#WNJTNzxKQP2df8-m0P@yU*+j1_y{qK>MXYT>0z zeT$p&xBl#vl^|q;SatR;j$;MCh)Zv72knDWv$ZGKs1jS_9cU(5R`MTJM}zLCPrX`3 zF&n3MU}F^c6LkaNqLdDhV;lIK@4i2#7R!`k%E}W!R+Vgahz*}!yjv6$1n-_^PI*5v zcOj4D%1=(mgWu8E+_wGIX`_>p9Jgg*z46*px!hKPVLMMJGgCUV*_I#&)q2TiPOvdM z$%6MNQzyZkhr9GaXC#*4X(-k3lMi^>2PQf$aMbx{BfO^L$=80eCbXcr-11;z_+}+* zax$_Zv^+CgdwcTJ=@#y`WmBgP{d6#{zOa=%x18l&B1RZsYxmU+OIAAA8_s>Us3$>c07PoYwQ%UkXV@?Fes{C0@%WMod}b zH&fc>NN`xXQ7B~{FnKlj!K4P@;3t2M>8_t@*g5O-;`ZaWv!?EfR5GinhAZ`Fwl?`! zx@q8JxDvJbZ`eGVQwob#S2$#Y?N^;*o^FL1HL}Qa-EKEg|M{eS?0LyN4N51QM0V4X z;}fwkR)>D#B)O(H6D?%Mbn3JD{hCNe?xZk;O2}%Q#+3RO^&4`yt7>w!JHGA$`9?t37s}nkK-(_a8NOqq z8e&%sZQ3+>%@vrB#bpv>43}@0eH!0&>l!^dmzahEE9~H2 zetc&+G2J%zwWIP*7Ku2zCMcDhy!2F5A?}K;86kE>=c`(6^tVuHI9vy;YfHvjpYl{^ z*O=O`QN8l;H7(m=KT|z3d1p-*k_nzVYXyvV=OV5nwWe$h6Eu`@E%%xkmE1V(95mU% zR++|}O0P_x_qn%+z6894clSX|nljSHuWbmcrkdLQDBh@e3@za{S{hB=dAq{dEcxrR zeq_JUfQNhF9nLN48jPB{Pl?^bU$S2e_b9_K*IoILFY91!vEFjuK@fPO*qs}{^~W}h zuEniKouYVa@7;3>v6vXahbg4T`BiO6ugvyLn!8g9O}s{l%wW~0t@SpE%^d=CGxQIP zwN2lEsCpBbI_WhgS!?j8CZ+Yy>vd}%ynnp}cTX9bTu|j{v|#Y4f2C78MQabt+hS|x zs8?|M)Q&vGqUq+AVrSxeMxDaTzhqQZ*d~nqrU4nZtmkO7J(3B0h$Hn2dcixm#YwKS zcpEUZ>NR<;C%|ens=u#@CNBV^iy6-Jl1_Q)kP9hapsxXgFEgscXJ%P`1Z+*MG~9B& z0-7$kc%Hw2$6u4W$6sSaCjCYXEhxh&_RK7Mr(R*_cUK#t>5qV+@8MG z^RM|gy_^cVr@tg(HetW;{Pw#Z4E9FNqgKSBpcWPfE?uD0O-Te_;QC1lOw60oOwnZT z-4Da3S_ex7^PpJa%+RsY=pWs;$*{QvPX|*C@CM5b*ayD^d!yu0`9wfD$ZCu}WlR0| zwl;^0R@=&l1CdStbV_|nw<+Bo?{)brThh38aZB!!wW~lE9 z)j%8SHjVH%fd!K5$C@HCd%gkT0lDBs6a^G3yi~-&{@|L>`anlI6Fd`JP72g@@nA03 z6{{0kpLoXrs6nhd|1Rr5N3caOy2Ng*yEpl++JNReM*Cos0fb$)f^ac&60bM4BQ591$PJQp|;^cRMsf3!*FB6 zP_}oDKI0JJ@ImFA?{FO$P8ocXp-cmZs78^49@#P|akvyTK3Pza0lZ-U2;{*UcIlr~ zt~duc1Rs>?vq=E=G6wG^l@my-Ppr>%#5b`OHgtYp7q*8wikR@5aF|f;Vh#8WgrVN! ziXvw3%Hv>tz{2ej2PQ*_cEQ1;sABYF`{`y>uJFCdJPD4tyB`O{2TTVtf?ZH1QL0c$ zQM7SdVXO{OmKSugMK_@Kz zpas{OaLvBUxjP!nAlPXkPFj$~nFpm7PjMtM!P&*=G3)n=e`6~C)$(9ko%YtnfO@jj zQIO)7>>>IBwL!eY1`(W1XXGi*0!DPL4|*3#yo0Xi=}ouT1}3zl$r0Z$i+SoBXNGiv z_;->`^;H=$zT;%4qvHGQTS1-~I46rWE@GU1-k#s#Ym&%NKnx1?9rYkeq z*O_-Qe9$>9O*10|;acTt3N(HG?K&@MG;B)37wdt#{JT3#KI`YZUwBe@^c((G3!Do> zdyA7@B}o!PangkwnbXD$JCq#~i=5TYlb-jxo#jQTvBw9|)P*A}lNk+5l&#eqzEfx! zszbCy`E(TVlol1WGnf?>lxj+OOO;B!6|pn!vLKD^tVyg=se-9ClgtWZjgZFS#>K1h zed5XdeasV<75*9qJ0|c+tH)W)U48fDYyM04i@~!I*<)-)Y(^}%Oi?h#s-d#76Ms*Aj{so<6%*qLJ5 zlirH)ki#MDnfhMQBgQ-KIpDeeIsLia+cn#JbI)n`&p|9$`25wW4w!yOJxeGI7V#EF z77h~`6&@8S>NwvV?-=fIaj|tKbs6k1=+JZV*gqc^iT4(`|8ah~IWO!1*#rFsb$If6 z#(3_6YC$!gT~~K|mIi*knTNNT@wX86aW5T8 zaR1Z#pdE*IWXT$pdoa0$#3?!7l=NJ_(v>V?VtSG@SJ1w7x1?-c{H-`28Zqt_0Tn;G zXK#EQ+GBKlScs3om`5<#ym<&TAu>Wd(n)&?*SR2mGQ zu`;XUC#r>OCoNHa!)9GWT(z}>+X22h!q)YlIBIK_e>AA1{@G+R6hjlFxhkdmZOqc* zsS|n6KdP<${Ie(?>Gzoe|B9SMNz12u*G!3z>!m%bHSyd$sh)+^lxLkxEA(0ep1wE) zRmC2+hCf1fp;<I^n#DTI&)j!=8@$k&&CTikg z6`ko|y*K8H$Ta6IAU-VEM{|rn#-~}4pivb-$TqD3aG%){7p;whXA9-!+gXUC=A6t= zJHB1s@)BVrbuC>5D3aW;A^BE)6U!_K+9%zfUf{~j0wMXPh`uR_gx*kfQ&i6J5N^nH zDou^1Q(9LHNQ;|Fivv&|Q{$<7f~A%)qyFt`$6Vtmp{!ddcIW`-r>ky6V*2kbwoXSw zG>6fdhK;2n@Wf+n8YR|TMrEG7RCJDCd28!FH0q9RNjt2di>^`@;P?-)ZZZE}tTD}Vs|r_di);ZYNUpNuY<2Z8 z?D<)0KH$C8UVqG9u&Q6zmA?v)H|mjg!LLv+hpJ)B!CJ7Oyta=Xn;bc8J+XG8jF?uHd84{{xXgZok%C z{!4^P*sRQ+OG_8Fa=VCLo5iZJS*#A5#ZG0ju$#?d&HSdYly4gB{hLXH-fz&KGU)H= z^(`iY{*0c3e@2WnWqE$z)A~qor-f|HO8k$lDq5OD$b2EiI=^j(kYd-iO^kgTc0cB< zy6<{}EX~Y388)@|!mjQTc6HZDO>P(Ewx7|*?iIU(dj<3N3NN|$HKTp(5|*^9P|wY- zTPUX^%kJl~g{+>fWMYT3OW58nv5xE#J>4bt2;XWEQmnH5f`NWv9sOcN4@@%l=>f5) zdf<>g@;~sZK|i5isUG+!>$Hl8-5=}q?-c0sf9E2DZcHyS=*0%TM1LB5xJbVveZ*tX z-3HyI_sK^b23^h4QU4=_2K`aJ{=I5FKYQNL>pv*g=l?-cukYQKl^^pz{T*9Vpx^sRdR1)E+! zWN81zN`u~I(7O$KpF!WQ*I$}!(8GHDWmDFA0)Ai4K0$NcFuyM+4Ema^HG=EC`uTr( zvwr?weor4i2B+xL2a65qMFxGAL0@RlcNz3!di(g<4E}!!BgHS>&03|M^chAoZYFJ) zu&Xh9%(?V=SRS*us@wyvO84{bl>0fnu}JSr?<3!^ggtrq@ zDqZ!!D%WBlAKln$dlj4GeupWt2YvcI$pQDfkQ1Q)f#iVuH6}wl3LJ314m^ahI<&o^ zdkB3^A@w&T2i$K$J_r53Ne*P?d`sl~t;qSd$oV^wGu_{7slV0K-$m5lbV?1S)KW@a zc8dEY>fs>u@G$i-opOXyUZIp%DTPNH+>FUi*~ui?BYVK=1hA^ODqIV|ddi(*7W9e7 z6@~FnJtal^A*r49N)ai-HcK~4 z3HD8Cv$UDrA?=XvV|~(x(uZuD^s)3YyG#13^jEf>n+CgEw#XKCkL;3N>|Qxn&Sg8~ ziSk5tpM0@=F}q*>ocuZVf7v?^C@G3|?>|-5JvGyzwt)qf1rdJD5)} zy%mer+vsgr0lkyniN)xh_0FuI-bL@i3hBQE2C~9NE#m`r!x&&P_S|x;a{N=PzBP$2 zvYXp&`5n8LJ(xdMTm?l?rB~@in0iaSB^;GWWfDnL7L`RLRoPTFkxXS**+sa@sd9?s zD!0llQmDKtuSlt)RkVmuF)Bu+QiW6@5vhu(A|kaarizI)s)Q;b-cY4fDUnu{QDwxN zs;nw2(y5B7qDZeAsYW7$YOb1#w^U2jQe;%ERcnz+wO8#$X7#c9SY%NhRY#Fkbyl54 zHq}LS5mD+>^{L3NdZ}I_hw7vHh@9#R^@Yf#2C9J~xB60jDe|ZxYKX|IhN)p9pBkY? zi2Q1l8YQCD7&S%|P-E3t5u?VbaiXA_peBeyYLc2H3acq<;%&7;tq^6^Dz!?KQ)|>3QC@wo zz84kL2DL#{RGZaiQAur4TSR5`qxw-)QQOsaQC00!JHIy6T#`ChDo1>ZW*K-B!0n zth%f2iu&rldLSC$;QfH?7m(#*>@j*lx z2x(PeRT$n>MO9Hqr;4lMkY1HkCDB^B_V+EazZq2pRRJ=ohN>ZCR?Sp1$f8=P7LZl7 zQmr7HYNy&kl=?`01ld&w)d6y-I28vuRlJIaT&k<;3b|EJ)f4il-l{j`RsB>y$fpLV z0gzt}QiC8`4OWApfEucXLX7%aeGLWGNHr1)snKdQ6jtA;Z=i_!R(%Ua)p#`??e#=8 z5sItHYBH2iQ`J-`siv#xP)f~IGoiGat!6_RHCN4rx7B<#AIhrl)OS!$Emn)6yjrT3 zLIt&4Er*I~rCJG<)M~XFDyy|>EmTqK)q1E(_U|3Cf7Q@-ZiRQzc5Z{}Y6n`+8tNys zwl&o*w6?X>9<;W#)n2r+b<{qzvhS(=Xl3iF1L!mAsYB>9-d9J^XT+)#>IBqRSJhQ$ zpl+xe&`{k{x1f=_qwYXsbx+-cCf@ZTZZ;H6wsruHEuA%F4RLb|)&iKeN?Qdy@GS5Q zM6g7#1lr+$&FDiL_@B}DpV8-yzDOEMo(~z>)UF zvU6x4mhqvXSa$g*6d6q|?-Uy2Df5k|%vevEZ#`wkdCH9U6q(>DGSO3HvZu%tPm!sf zBGWuYrhAIa^c0!pDKgtrWR9oETu+hto+1l8MZWVCS?DRU$Wvq#w!c$o5-Bo+6q)x= zDzd~=WT~gfGEb4^o+2weMOJx=toFvr8c&(Eo-*Hi%B=I0S??*c!Bb?Tr^secksmxo zws?wc^%VKhQ)IiR$PQ1Dot`2;d5Zk(DYBRpSxF;h6DhLopHyVGr^p^pkzYJT_Iir^ z>M8P@r^tR!k>5Q<4tR)BL#gnb3Zp-l~fP1v3-29s4CtSEk!HQR(vQriO!;n_*C=|1H~ZmrT9t= z7DF%#J}u6Ov*MgMFD{6S;*z*5u86DRnz$}*h@0Y;xGnC8yW*Z5gU1bHd)Zz*!6ZHj zn5ZJE02kFnH4vh?XaSmNE!sdpd?G%9pokN35EAhs9wULSqAQr9yXX#<=qWx2TZ|Xu zA(>b!c0;&bz%Bq;@#v9YXlb=KwRBo~Era%!mQl;3W!AE|EyGWA58&0>gm=FKz#y05 zJ}IP{iVA$CE6hYEnIs8-D zzCJI9j1Z%|7KQEU>M#Sdc3pY<^veU_MoN1r3+;L#U|1$gvD z0tk!{^WszJUIi5e)?Q z7Z$#P!7v1d!Y~+4Km0NhM!{$p1K+?{_!j5M+DKHf+z;u`aGhr6YhB+`7 z?ZJFl0N=qvSOkk<30j9`upCyvN>~M}VGY`f?_nLRhYhe1Ho<1J99v*3{0Q4%JM4g+ zXh(jAU9cPWz%Q^DenqSD8|;VQ;Q$v%uzT7$KeE=gj4VbTAVX*7S6$WxBwU7 z655|Da22k>b-3Z)(TUdR4%~%%a33DPLwE#_;R!s2XYd?e;4o&q2CvC$@!Gr&e~;JY z_4xZdme=PEcthTZH|9|y6YZ(?Ona`q&}}`bEG^5(w`Eyb zPL`JyWJOs?R+d#{Rr!vrCf}9SWer(V){?bl9r>QDE9=SkWvr|(8_0&Tk!&oR$PZ*w z*$f{o@X-n%ZDd>dp=>AH%a7#8vV-g>Karheoa`*)Wf$31ek!}+qr2=Od&*w2x9lVP z%Fks#`GxE+2grePko;19B?rqPa;O|8hs&?!2su)YlB4Aq`HdVazm?jV}{wDX!-{k>$P#%(p&Q9+SuA33*bU zl7Gn4@{Bwy&&l)hg1jg%$;+*)YDR0T!@}YbrAIm55seC4%%NIciGC5Pu zlC$L;Iakh;cjR4pPu`agjIPF~MmOU#qr1_==xOvadK-O=zQ*T9KjRCdzxkQj-RxoZ zG<%u7%|2#d^K-MG+20&s4m1auUz%T;gUuo4P;;0$+#F|)Hz$}A%}M5DbBa0DoMuip zXP7h1S>|kWjyczyXU;djGZ&hR%*EysbE&z^TyCx~H=3Kw&E^m07IUllqq)ue$^6;e zZSFCDG54Ckn)}S(%>Cx?<^l7N`GtdSXZrU)^+QKbxB(Av$-v7&DQOJE$yHkvJKm`E!(!09cDXr5<97#%nrAc z+bQgnc7&bEj+7a$=h^= zN(RUQ)i44YhaO-9T4y|k`2}#blU^;LSMGN-4y1rI=63x1pt%Ff!{$ybkD9v@-g^*V zZH6Gm6A_RWBY>!v-yWHV-R+E-qQH51ULFLm#4CZu z-{tRuZj3ZWLcmyMtO9A=G;ZSYxIT~X`#1SmzlZ(5?O*@@cv#nm{#C#E@AR2TWpmk5 zww9lv2XZ}UIrN!T$(z+c&oq?$WIgneu6KOt8(aKEuh{+X_`|>ThTozeeC+`fdzb%h z-Y~)2p#BtJjPM`9*)$; z?znvAJ9&G*-?SVniIvn!W`$eHtrS*DE5b@;MOvw?G}aqdTI)?Kot56oV7+B!v@%(l ztt?hnE1MPdr{4C9KlisPqzq;LC9k{SZ~d+cQ;tgVH{N%R~P$|hLU;7`u^0UA7%PK;pQj!0VclP;a>)F4;J3Cwcj(>iaah4pD*+`ZZ z!dZ1z8{VRC-LtYrtOaCetyn9_&)Tpy5Y0Yh?V$ksn013ftUDVGW!X435gM@->=$Ut zKjCrEgU9n8(3|(-ePAH}oDYDn_#i$6hVx;31dOKd&%fnkINE#4@+S#PB!$T$T!h0c zkwQelY>`9chIJyZC<~j&=kF(<|2ySx2efJ0FK}5;q95AF@dOvHqB) z4xnHxO<+)92zw(iEHIK~2#gMlWmy8_0ux#Gz~sO@mOHQ@u$&bQtPE^pWdl0`ds&^p zfxrRQC~!D%lr;_<51eF8F*!5Fq9SU6ai?R39EV?PB;1xvA?gJptc*sfstV0pGXSSeVE?Fm*5R%O2gYX@ty zy}`P{y6o3rY%rGX3pNfmW4{I41lzDfAx!+)VWX+hj2$sr7_HecqpcCgP8sn=JiCIP zc09X^o^~UGK9^=gvm5`wnrxlsz3eJ>6~4tDVvplnUtZ^eS?NOh=A|KP z>1|n=XbXcPbX{@;UG?c+i^|dK&ZKf7Mh7#gcK=%M{k0zdI*t$bYMbmVJC2=PEEl@H zLaY!TvjVGtPNoX00va33#?sX`JKzI7Qcta?(cjS1>Tl}l^z?cL{VhGCo=MNFXVJ6j z+4LwqyPiYOspryj>v{CNdOkfr`;F~qzq14EAUnhkvm@*%JI0Q)6YL~A#r|NY*%@}0 zonzaQHVvEN{(>gtqTh9J|Er6; z1{8r00cqS{^FrmaU`um=dnJ_nuX%&0Y7vYVi{Zn767Fx8((hYXPz4_f>f$4e?hkjM z5k8XAzhzDat?>~~zdMkeesLfr^umYxzrZ*|vM}aAYMiN&@CHlE(n4CgO6EGdGzZiEg*oA&xaT}4m7@mpfT7O4yG~7m<^6G z*H{2ajRVF(NMRf{jzNTR(l~=Lb$7ETq(?8(4>GzQ0j)ugmqG2asa;8_UE#(;V==a9sj(7L zQybG!8#5Rej7yN&xMJMEcDXIhMJ>%sEsdd;7D7Kb6Z3@Gm~E7#R+pnzS3v)CAF7~t zdPesvaE#L1df&oU#rfHsS~SFMBdbaC9ul?yt!Z-Bnw3h}##wEa0~q~=%*$;!1O7yd zV!Rk?;VPn0AM2z`fOL_hOOSL4Vf@<=3{uCWUbjdi+j!r29~5TjJs}LUuo2kzqm1#8 z26M5MkkMFeY=juh$ZkLhMs2-rX)U?YuyP3Yloz-ILFx8Mi#^v_@m`t<r64L%j|_EJpN$E|3_SBDV+W45N2WV| zaVzE9FF#Lu`XqV0vvWL44ryeEbh1MMvO|*WP>}3Ui0qI- zcE}_`-#DLn+A)r2-~?aPM2;6~$Uq;_rPQ!l8MNEnrL8YPNxGWk0dKsLe6d z;Uc@v?y|>d`~%$LNqGcM%QNyQR5yl2Ve7M^7CEVoMepa<4OwBT8?ho(H)chtZo-OD z{Q)aZbyHS?>SnAY)y;9{xpfOxn(CIUEY)pTd8*r?M|JCu*}GJa(^I;&wo;FvdX=8a zt<{sD>DGF(pyk$q89|%s^+84ThF}=g&w>us&x1**ei2OS))>Piqq<}mqw}<7+0YV% zp`YjYR}L&=(c8PRLvAb^u{>BdW_ht}!t!DH0n3kNQ-*N_wygk`%~=eVEm%P;Te33l zH6ZM5EI(xBuxy9%0>@TW#Iggcgk?uo8Ou*tRV>G`cd(qms$n@^4+oCzO^)R%-CY5B z62y+dmg1&x1iMUj#$Iv27Ur1WQ7;g!?z+pgZ(KD?bV* zzzkRb%V8b*v0bnqj=@>X`R~5^UJpl71djELIF|CT7*-5NQ$gH@2)h*a^s#^x%aBFO1TDQhl zzDf1^U^=Qd1k+RfESQ1n=fStAei6***4W}qRF`CvsQ)HY&rPA8n@T-5je2f6_1O&S zvzgS3v#950Q_szzo}26TM=+cePfm)bAjMOX;t`~HDpEX>6i-cxry<1|XkkECcMGx= zKr@|%xj!f3-u+sQ?mWs0`Op`8D^e^4jkT0CMk3sEyGLQE5?-}-N0MuM(VmQ&*4$2J?t{ z%sgS9GEbXl&GY6(^Rju>yl&n!Z(Gs|S|Q7@Ov|!tOIcx7b}NUK)5>M#w(?kct$bE~ zE7~ex#aIQcLRMj`h*i`oW)-(eSS77eR%xq@^|n>kDyJ-EE2WaEWGY-eRFBkSm0G1y z?st)yDfa`)_jHULs-RD6%iE#Xj>Ejp;yp00v*{{mMOj=J{PE>|g28f2;@pE&cSWhYV5^NX-!Gn3Qx(O*&?zmgl6F$56{_P|Ismj8gYy z=BFFFO|!5vxa+%RAZ_CD|KCUP|2!UFjvYKg5*jH$BSlaT2dF<4^=BCMr$hajg!(fn z^=C5b&v5F`MGHvR~T=4GuEB+G){rWDWh@9 zX`BjgUrP^5v~L7PB?(sG)l4VLL9>8HvQ8se(nv-hf)P~mzq(H9Y;o^TdHIdAn~_7f zmsijoFV1G*j(a~XbDB^z_j7|@O6Yg4p0%-d3o?wArya?wraO*sLE>0lK{nvPWIM%_0ZJbx+*KuoieBOQi zPJRfVEwP^}@_dQjn}*I+9k=J>X%fBLJrBlsB2uIinP0!3UF9Q?pg$H|1nC~4S9^5D zX8_x9kzaRTC+xA7W52WONcQpr?(2j-u!YQ3p=zRS%W>N#{9My8PJX?05^lwqgAe8- ze4nyEZXbc$<9IjTJJC7I^N)~pbMJ~uv^5W}N0}k# zZE=hH?2u@$G?0dsz+9oamzBNev6rL1Dvo-0c4TvE;uw!|>Ns^EhZE~Ggq$=-@;ZZ^ z!H|!}`^%Y(F*7)NNDtwjP23spG<90wd}-yhg@Dt}X^%7IW2YlnPA8`mggKp^Pr-3M zb3Vga*Td-rDV#n|Ur6QjbNWGQr@u20(l|q$G4Q4{&Y6LqV;0&C_xgU0pQuj43{B#l z(LEQVbLn)h5S`1Ub16Dk7@aGe&Xt_b6+!2Uq;sWqzH+{TvMRNBmuERV{i| zJ>gXidWGZ3DeIIcOP7cCiGYmWKCTsgwb$#{HN4k}_GHuOljMiiXg%Yg8}!C;G8nVU zu`mhqfqAePR$@l*1MGl3m=hdD%rGK+)O+sTTSl%qY3!ZOOiY|F{+jvOZkT21cc zBnz9%!JNy=%w!y9AacoRV0UL~G5RQch_sOFNjaIb|Ho*U&SR#j>n} zS({T1_2W)?2Xi;4f`hr6Q_;cP&8dVv$(_pRL%37L9fwX;oGTn>Oidb}wcNhLQOa@N z#9|qX9)hEvXn$BKR@V#Z_6GZqeH$as%&OF@91OKbmE6*f zrZWF4;*Mq1FQZGSd`&VD$wVZRk7Op2nMh_I$wDLxkt{xvl}J`1S$!lMk!(b=`A8Iz zC?ZillATC)BH4W;2ay~^a`;G2A~}iV^pRXdauLbpBe{v>CX(An@({^GB#)2eC6bp& zULVOvBp;D{K9ZkEej@pOB$`Mxk!T+&K%@YX0zMK$B!);)#fcOrQrt&M5Gg^VgpZUYQj$nXA1Oto z6p>OsQkqCl6A{B{L^pQ$LDiNvVBbA9%CQ{i)st~C{q>7JJB~q10RUfHFq#BWGK2noNO(HdY zq!y7{L~8j+9U^sz)bWv6BC$kbeWX5-`b6scNCP4bh&1q#hC~_?Y3L)3i8LnC*hiWW zX+orlk2EFHlt@z_X-1?Ok!C*9oJeyb&3&W=krqT+_(&@vt%$Vpk=8_76KU-uZHTlX z(#A*H5@}1Mt&g-L(vCF(aNW719A<~6N7a!?Lq$`oGKJqD%Pl>LbI53?nkkM}`v_PGq=`d`;wQB47K+2qGhh zjPQ|>L`D)B=_8|vj3P41M@ADFO=Pr>j3F|H$QU0PM`Rq4aXvDh$ao^-ePjZW2}CCN z$V4I&iA?m7Nkk?QndBpriA*Lk*+-@jnL=cWk4z;pmB>^dnMPz9k!e0Moyc?|(|u$H zkr_m0_{c0Gvxv;{k=aCM6PfKJbBN3#GRH^e5}8Y6u8+(kGLOhSADK^NK9Tu8vVh0} zA`5)vJ0jl^`OZfc5?M%Op^q#gvWUncA6ZOfF_FbSvV_PIB1?Q^DUqc_miovtBFl&@ z^O5C5mJ?a-BP)okAhNT^Lpq3pXea>{p(Zqdme2wIi@ooFucFHS zKli1FWO(nEnMr^ULI^$djz|+wk=_MFKzc_4A|fKoD!Vph5hC^G8x{nl{LlTq$>YVgxcmG2*?%D)UMAKuJA4kafafG}dN67tgg!~^zr~%*zbpRY)!bg|j2vq?bA>X(4@^Blb5OVem5~3fu zSP$$(gJ@DP&K2|kZKJ2@Wja8I=_@+H`MN7|MKuu}$6{M$2r~R7s5mUOwT6JQ43xX# zD9b^)JC1S>C@Vm*Ev`h1_Gne?Xm#vpP3-9YxSk(C8xO|XSRXrUW9(>C>}YfBXiHoh zThYe0SR318XYGg`J&dEM-nrI7_=??>5bn*tALZYDVy*4LcYA|*W}*&xwl+9|lER}{ z-%_A&sjP1kC>{Ei5iVjplo`G%RwoRHXR_8spmhpbXLu}ySY;_2^u`v+V_@qG_X#Mw zL3tDuTdABK-dSQr%?(`615JLra6&eKFMu zxs(u^%Rgn~>Eft3O^Km#_(?4B%>nTtUpFCCRD$}Xc#+#_5$YGz=Zop<#WrrIfPY$t zI+XZkfmljO_IXqMQzcYb;=5^L7LUJ53EEIG#`Yf4C8(W5FG}FvpA@G{&>M;tl*ql? zpTi}-DJ$~%a|5d8XQ|1;!J7DE@l^YKdTxD$m?Kt|pxdX{Cx|X$j95^DZl7(RXm@ss z9-;()@TBiVyZc8p6@yEBV|P;|iA4P5iq<8*v(L{@qEGA(e_Nx(cXm&P5N-*YeGY6$ zq}tVkII4Y)s1Ug&X!fb4Av!2B0;<)r3E}m>dp$hF+MFM5%{|#Qu=3$c*|X=O0$DMr zm510e+r+At>F{^aRkFqSyceGu6`GcZDWJ*Q~@Bp{8;4xuJec zUxRhDv(yQpi?C)D5_>}#ar8dcvh9jhC_RqaI+Pb(u@dve!8m#~dzP^K91p}%wK&0) zhOo8`XX$iOydPbs66@orhpi2XR9M^WIQolNcd}0uoE1miA?(wg--dqnh@;;vtoA(? z?6NGT&O?hF8R%_M5J#VcH7K?^ksn9x&(Tj1=Yf31u{e5L*pub;(Ku?Mu#)e>M>FZ! zIJzsWWG%0s4yaZ;vGx(}!17IiPNhJD@N7q0f%r{pyCK$?A>pBXwv8eowx}`O!!?}S zCWWtM`$=Z|xsF?0#<4dwyqx20T6iVvb~@Y4Dz>^c96>X}J2;YM26l0Apy_ECmEEnL z6duc3NNk%^csw=|g~Io_5vfjn(*}fzABoKVB~nA=b_2>vx9jDr$(Y_*d<<;r1?K3{?v4WuQH~Yoja` zKifDl+#f3-iC8h|4`0_W+@GTBBuCjVPZOK?C%V$RFrXh~`XIaSO1xKs{v><;4&uci zQWR6|{ zJx@nP5CtUbmGmT|a+w`adV_L#9HkE^?1^QWfYKL~fpO>b17%Pgb55v%)hFj`IGTp_rF7Cmc>I)Xk8#w&5AAM`&2ZP`&P zv|?NJtncA#d?K06<4Waz3gCM(C?TP%edd5YBjKWii`Y%r`;!IkPqxpc6}Wq#b>K1X zfEVeAUD~zdXbJ3v8$(`YV^o$!5(~+n@C5tMYaRah{(s0GZcL? zNslS2SOqJj#O8+TOipLmYbmc~NL>`XpjO>a$64@PjG_ohMH?lAC zSmg1@{>T%NCnHZqo{l^dc{cJ~+rbVub zOpjd6XZ;~EH}XfG;f;}bkv~QL9JvYC*fH#g;MpZsAN3x+TR&`)RhmjyrBsH>RE{dG!YZPqQcA0QRY?`7%BqU0s;a5#s)nkm z3RNvtTh&o@)di}ZYOgw|3)MyHV%1SyqB^P0s*CEXx~cA}hw7<%scGseHCtGOXVpK{r|O^TGxfRp zLVc;eQb*L+I;0bHqE6Dux{YqDFVO9Dd)+}_s4vnN>yG*o-AQ-WU36F7O?THlbWhz& z_tO*fL_JAQ)>rB&x=3HI=jcD^xq5|OsqfYI=~a5QUZd~VYxQ=$LqDu{>b-iO-p~K{ z>euxf`c3_oep~-tAJFgUgZf?lo<5}C*B|H)^+)=!$u?z7SyRsBnDQpqR4{p_qRBUv zOo6Fvs+g*#nyF!GncAk2Y0NUrl2^!a>~*e^tReCRd63F0qbgDzRZq30ma4b9oVuy& z)eSU2Em3#T5VcNiqT%W}^(u{5->C2DDxIRUX{PS2d((Vs|U0TF08RhaQB!tk+NI*Jy+OSbtCZOm$P8UN$vNA-!Vin7Z^DYf}xnU9Ob( z%KPLhxmvD~_sg|%oqRw(DA&t}Zjzhj7P(b!liTGH`L+B;ek;F|N9FhOm^>~| z$RFfMc}kv^XA~)+LTaoUr^c%ZYNDE?CaWve6jh|As$0~p>NYiB-L4j>mFiw~pIW7M zsYldq^{CpT_NsmAG4-x`PaRV4s}IzN>LYbneXKrFr_^b6hT)pfwRCM=N7vQ$bbZ}G zH`I-EW8FkI)y;HseV%Ti&(|$=E8SZ6)g$#NJz9^^WA!*aUSF$c>Fe}teWzZmm*}N> znZ8Re*LUlC^k%(9Z`IpOnn^dMOoqucjwx-zCSs&fMjK;X;~C#%nFhu_w<-+z5#(ne zv&xWJUC694WY!!qYYUlmg3P)@X2T(~5s=w<$ZRrXHUqMm1$o>St%>-T*_fd>2hzD& z-Xd?6x5@ePcDX?QMJ|+g$VKu_xmYfdOXV_oms~FImiNdN@?H6!JS5+jAIJ~oNAj@z zSbie^AwQM>l%L7Z1hNz+H3N=g(S0mI&HA;V*11oz!J?SzS)& z=<+&OSI~L7qR!Wqbb+p{tLUn_ny#*E=$g7v_tAs(5It01p@-?=dW61O&(PQCnfg|J zo1U+4*9-Jt^g?}yUZfw=8_W#7Ngvb4^$GohKB-UX)B21dBTUF7m_(Cgl1++9HT6vW z-$^>`m;FKh<^EtqkWrCQ?DemS%%tf1NJ9P)e<-5W$jDg26TvJcvYq|=G&9jmVK1}Y zTxHMYGe+{YWBEM6ae-+Z4{qQ{GLPe4B9CJ#C9%i4o|4%+ZKo8z@?lCf%}g`ATZhLH zZ57eSUQf7Q;Cfp{8Hf?pkH<}>8^_=AraOO5Fg^HlqIZ%)-ZImZk0+Hlvlq_njWhe; z%u_hCFV5r{vJ^+s+5K91h^tExt%m3k9IZy_@r=WGBpj=z(b;FN72}d+cI4d3U_N&w zw{9P0>V*<*fpzMjGJOaw@ILcm7 z_63xm>$`Z26M`|`#kA7wxq1BTt$UG%EP6o}z1i39qdpuj4%21qS?khl_VrC@o@s8{ z(@mxm^Xg7Bocp@ZtTgLrop-ypn6~&k{paWr|I4gO^dhW$T~OE3sLn)dUCZ&!LqLue zk(S2Th!JX63MKST;M|I_>R3ujn8=7VIbnLjbV|Y9yQz`>k^a!GQB(@rl^GcynLrM- zG#r@~nME>kUt|?2zrR0#Oq>j(d+zRz^_nP?Wjf0tgE4ZFtSMVku58bUx_%&)Xx+lT z3p1KiAI7_l#soQb^jXw4XMHjF+rH_rW5C&af_|HFjxr&ju#~LtVP125U_C0M%djQX z(zVzUtgMnDs}x(~i;b)_k1Qf3rExxBs1#=$*_0{E$qIZa%lpDS|xJU>t5`BnXzqM~2RuOq7X z_5Avxy5GoeENc2q{br(;-@-p%)bU&UZA3l)0>7PT;9uzXwfE#v#P-Bv=bk9Z_C#s6 zCn{xoqDC(5xsQ3cx*RkS@(CEF8Kwmnf*+Y?o{JyA`_x*=p8 zqP38?rEHHawHc)> z%XwU$%wy?Sl9eb~)|GWBMK+WTIj3tPo3cKfC(mPjXvtceAup5{u~u9nFX7g@$gUhI zhOiz>$a92bA1=o*IM7o{Tx3>4D>7b6~twJp#POb zI(tcOoppg*=hnD&3ftD1S5b)-pju-kwvx`?eX=yDMxr8TX4p!N%}|)HlQ>$0I7hjX zrJJt`0((wj?AC}<8MU29X^iLFP&%Xdi>Vaj_pX#-@f&3_b{j{fErzEs!4$jZ%!?_$rIsSby)3cW}!K^aAF@LcNejwoz~7ksZ^=m@B9CX&xCnWgc0` zBvU$&jrIP5aoZTgi4bN>L0%D}0-oms-2PU%l{1SY@>}lls64@%ep<0~*q7c%zD3$p zhVk_wD#z&h1IlGweTFJP8u^T;>r*9`NmD9-OsX-4?n~7nhZ>MWA*1MPsTTNH8+@$8 zIC>e?Wh8wM)w4*N8d@ApjltQb;B0e7(H@-#^xWDV?OsW3-D})ysFOR(okgA9+3sxW z;?8mBP*-=ZJCC}#H@mk`Z}&F$HtOr%?%qND+{Nw^x(qzmsJ~cbV>xc0T#~{ZZ_QF` zt9nqFGtOQ-Bg_3faKA3>t~Gm-Cut~ToUO9?>Yr}VkkQEVdUbH?@ z4Ym3&0*@7~BfI=Fi>YtNsJ~w<3Q-fA2XIfE2e3_X9>CsfkzB;yZ;52RwwVBXzq{q# ztOqOP3LXJxb?p5(t0Tu|b?o;Zln;{RtZoA-o7a(s-ng9EZ6}X0_yY1-FPCw&vU07& z$onK!(P#7-s==sRP)&=vsnDWss?C;^$y^SbFw0&VNsVmA#2UaE6Kg;{mU?sZrg@WE zngiwlwK50IyVTmeZ$6~9=CC7p1BKXj499h7fE#fo4Rp2Z(I7Y5&8DGlj+;YQxVdfx8s_G^ z1srFqxK(JhTg|OOW87M9EgJ9Eb?eatw}IQ3EShDEdRFFiI*{(_c$w3uG3xIr^N4bl z$2yy>%CNSUW6e!e^%eVU)l9XZ4AqkLKg{~ymXvCzI&^E(;y(hp4VS}tWTWM19@#|B7(;T3oB|z=-YaHbjTc6|rf6mr{1v@# zQ3kVI%WdWT>@1gaANN=-Qa;CxOjTBuWm~JDDza5JR*l)#nyRK`)C=lGa@9-fC62Q% zt5+$@R!OL=dRx6sIqHD2wZ4<;B->q%&Y_ArSLbrf&C_|TU6plZj=42-O{%Pi>0wk= zU!$+#_-lDq9XzW6gis57strEX0iWtKpHis-5JC$x*-T~~o59$t6>@MJxe1Xw^fQi}PX7XmIh!L!>*wq2dFFNVI=5l=txkEnBzVL_&u^!~Bx>Q?@RkP`QbqnVYJsFv;rOO$aJw_uLnSDSL)ko?hDpDV- z&uOaqQhiA?ZRMM;Rj0I|>lmx`qPZ;DF|?i~TSS`}lg*`Fs7XAo_v-z$AL#cPGsR4! z7yL*4ee}1VNdI4P=OJ)sCEGQ@t_AEm0d}1TyDkMQ&V&^^jQP@7hpe?qSgV1xy1;z@ zCvqoRgXs`dF@hQeDjP%_Y>k3>vz>Kxu$s!Yf1_H!c6ujAibl+dEVe;A@2)`X7g#%j zr;6s0HeZxxCF^gtx5V3nb@P%MzR_L>n>&~5a*=1pOE5ahO{i{_(Tt4E!{$+{VxBdB z#m>E7v-^~2oeq3VXFe7&wznK?39fbUlvh9x7NrnCa{{W-+Q~oJxljUW3sQ&%h48lSE zg@?+%*esAEbu<8UGA5%Hn_2_+QAw zLzRon(=1cRylP&futy$is+Zs;aH~mP5*aVeODEUMWQqHp_6+5C{Eu>3;8|3`E9d1< zo>zhU&-c1`U8$Pa-Rn*@yq;c9mQ7!;9~F87yuno48|qy_4ZY#saBA$0^hQz>Z>%?t zntBtxB0Nds_mf;DjxE+hY-Zn8O}(Z*p{D8>`>0OfM<0E)o=w9TY2HKQ^h0_R&Cto58=W=2iDJZMXar?_2(f4=w-1N0xu$u;ril#PUyk zVfiP%viuWAEdRvUmVe?~%Rh0#@=u(!{1c}v|HNs_KNc4AFO+Qg7fQAK3#D29h0-nm zLK)t2@4b-pj(aCUJ)lR^pj-0-joBLW0Y!0?5FN#{CWgk-#6`m5%`S1Y1ko^*hi67K zk3TbXA|VkKr(&9*kbl2#@tS}=(ogbsTX*^eRLB_44=Mmj@CV7NvZ_oer~{;;4v>aw zLOQAm+3*L|fr4tlBR4laOb^9a)1y=7A+!Luif zGq}6k;O;(yySuyVK@aXQxVy^$gS*S%E`z(f9o*sJfA15!cO&)_Y(z$OR(5rFbX2{_ zO8I3WeV1Jeaf^D`ZL{2iNSht_hMY%pZ0>LBvk!Df+OzH^gSeymta^V@&OKf=_C!;x zW_n;OSK;>zGz-d-OjZlT67TWO4yBbb^MN~Tmt)Z>UUc50r}XL9(&uQE37kV)8TPFV zjQv*WY+b765Ax?VG1 zvGtFMolumiAK4$-ryRSTdWv%|xi4Gh*91>qcpPBiWYuHx)Auy>qjM)_o=z5nSkl+fw@1Z&+IB(sk7* zs`w5yFkC9mxLl@>w42;@Z{_&fiOS2@Rxacw*Q*^gZ7v@G-g7_)!xrY8D`Y2FC0P&d z>1HeoZ*Yq>LMUciXCq&5ljrU0FF(N3nXmfdqh{$>`+Ko{1hr$ImxjH5$l9(MBSC7-1MibtIub zygu_kZuxF4SYAT}@1T@A2JQEWZexw7bM0yKPA+iEvtvY?y>K?wa)WjR5K>Pqkqll#!prN>RXFWg?K-4ez8P*e7-o zcSlW&+r*GVI}!+{v==P&nM)w6PC4p_Y$WVtkGxuwV^BWgDo;>(Lz_cSH{N7Azm z`OhLkVeZLamk-dciBh1Z<(Ze1Ds+wc!(e+y?;pv8w``p zy5qsJNtcFvM?)Zd9h)-2{Vm@}^;uo%2eswPR@T)@g+(jgd_2{8gJadnV)2lJ>Z8De z>>`DDmlDE*2;>S*jQemMPCe%X!aDF$;6bFhdNWt>Uge>UU?b){3b`Hj#NsU_3Plb{o6es1m3zSLlo z>XP{FzlCJMin9jK^3CsnQ{2T z59xl3mos5v1X!~YLaQ`S(gfbKLjrE>Ezb0>-NpVzF}`c&Rl?Mfrv{~AdE)i31Q7IopgN?~?{wkUn)>$DOv-0b57&e*%TEpW z8D)OrzHhfPwd&`?_TpjhV3HAa5vbgN^&asJ^g+HqM5Zhmqf%_B zzG}d1*pHw(=9ywSh8LX2lu8$@$HXw5QN?A>gxZ-y(PSx2eu*&5Mo@+A4NCxc{p?B@ z&b8@mEz)giV1>B3znUhv(5LZcLm`Ko-fCe@?A7?>ej2jHP5u?ACp%eXl|~SNtA}vQ z_u~k6-oszapAIp-fppEs?+Rb5WHYdQ~k2=nbpXI~Rec_LX7 ze&KVU<$p!bfso0j&dJjO$cIV`2Myd)m z!UF^)wq2U5K=LmZoQNvdi&=uZEJmAqSXyQKW*GyjvctET^Vn2;{;VwTIXOVH)6ZTb46V}$xpVd4})sYYItl)U*XRyF_i@76>m77EI&5u02R zpIj21(-EM(FNXty$-7q9q|h}>pZ%c8i%^c&Gm3+0t?><_sBSt_zEC2NtZ z?e^?xhb7iY?S`POq)L$08p2{P9cx*tXzf6OUT{Xufue4dr>Pl-ENI0GwCA2jn=0M1u?Spg6^Nhi&Zm{vZU0ZwI#>L`3?K{d0}B8hb{&fT!G zDGrI4RydmhdDdy{^rPo=OZtfhq{9{P+w%~h`;b6~N$v*gv{3D%LUg<&6!t6MqU_=F ztGZ~{uC?^2G(!4fJCwaIfb%!jkQLeKD|-joIdXv5Iha%*ascN3M)|`O`9meixZ-l6 zP*Prl<-3aCsL=JdqdjA9OxClAY(w3-2fWROpEC-<3d+qR!h@-l$1sLP{?# z4=sHLnx~A0js|`%y^0Aln>$uopF%O>xfnnRk}+nZ-&l%2NYEYqH4N!AmzXjGTG~SL zF}_vkwj09J5U!IM?5ZN*alt6KskWsR`2m|-bfbay0Xy%jo~Czm_f{Fw9>Jg)l6lS4 z(OL4*$PVcbLF+#vy%V_VI~dIu@9v|yAC8c>L)9~l!Z_Z64y7V3b1ZAF$L_Sp?l6sf z7&;d}Dr7|~`e$q)tK9!0pmc%?77p?ku<}QNGy4!$0COvVx)s393Se&q5VZnOSOGAs z0PI!(S*He~))nIQPSo{I+{|_8%yr1jb%e}yg3NW;%ypd1b(BmWT8=hhj%R??jgr+3 z3&@v;0|>_ z#AUz)_D_o_vWAlQ6~XvLnJ86%d<@(sqJ3OHxg0Yhh9U%BS^P>#B~A$^_Htx$2}rT% zun623RMt#V6OPaxM;QMt^p9?=H(%SQS1!<_{m#|e->sHmhg9U~t60^~t!To#>4Ap7 zUf*n&oCLG)$8gUdFMXyyby)WO!#tR|z`X7l z_L|!MT<%AF|4{KVX`Q&$*OPoWGy}Ny6?fFB40Us7>pI_v`e5?MF`e7^a2WdS{U#VK z|HzsvU$FZ=b;Nbr!ZK9hys*U6jeT2t=Gx<)M(C{Ae6aYo5=hZJIq(AQTMQ+bJQ#bn zi`N+=v?)&V(9ILb3qSQ)NRRX2^4c>>ufc~ul+jO_DZ&2yNth|w@>M%1L%;i>6a3yw z5Tr~OT}9PY6#LdgU>X-#nO0;avTi8;=IeU*Ky=@?fwUPts8th}B`Q2&Kd_QPBTyyQ z?U7ov+FeQp6?bL+jrSeyOZFQ%1N&4E`CFHKZkDL+;7u;FXU=2P%3Etp%jKIQPx z;bF{KRM0IAW}m@3MRbwiG0nN260?kCUuYX^TWp(p4t5DG;Lr_ILM`S~Ar2+!TLU2ich>4kLLqVCb3JgZz zYBQ0N=SMM&1GJg4%(Vw_SuGOGS0ZrHuo|9f`1Ij6a<*;Xue!G0FFm@SbNDtE)HxUK zc{cQql6RE|N2;Od0!Ip3vAE%tT6F?1l|)jM=H-5=kteAB(kM(APc@Sj35-RQgn@`bwS?LKiQLmsySHib%9V zkHNK;TqTPch{xThuA{^bO^hw3u=#mO@mRnD5N;!BqSTkd!Bik7Mak!v=KhBy9#32h z+a7gCDX2^dj2DQ|Nf0VVi6noQ_MgubkRz6`A1T^bcID2H`d(<2C|<9mnVlo7|5X!2E7LlrebKL1NVK3C!~!E%J1CYFI> zeCFMZS38z3DaDNHR3w*Tx}e$&NjsRBbUNusS>PG#8S7NSfRs7^(hPDXz$;$f3`2%g zcLaMrsZ+{8tZPJepX!bjn9&*Sm83aBUxorq>5N1lwcY2OPdg{?P!g`sI+u7KHK@m- zO7Jx!!T<4)^iGpOND4di;|3duP!iw2&w?)&8&5%#LP#Y!YXpcU6t&*x;f)Q8$JmFG zp{5>{=19&aFP&jgQ5c(b0%T@OP>kRKUXr8gC2s`p zbp3PN@Qa;?j{BCeH|wTw@=b-44%RbC7&_iEKW3>X3*Tw|o3thL4Lgz}>CN8@NCti9-=V+0F}USy|C} zf-zu{z*?SElG;$}DxJ5uH<15n3KxDYS{d7buyy(m@}3Fr&m*$;IIMG*p{-zyG>w!G zoWl%EOjBiRQPAL}u06OZ_!aCq;xcqC{WfI4b$}pPI7BSk6UC!4!}eY zf0dpuSQ0_XHMMtTV|c%x&Q^;HfS76E_L{sCv!>V98E!MOWiQ91M%V|J1fv6d*X5~m znK9m$d&xPckY*CI@JgtxQQ0I}p)-XFlt7vh2S)LPn_6H-KX?C*k}_B;yvt`^R4>0$ zaIW$|8=g!t=6sM0kSJIBlN==zqf+5hvfj92&y=+$VQ<_lrQ683h;13wa$kSnP_q2b zp2B|5E~A;YSx+~xYNUCld7yc{d9``7c}2s9zTu+zqNU58--h3qz9pwgef5uhnw_TI zk{$Q?2(b}Lz4rya3gOgttdl=fGmIUiC$|xYk#4Vhzy-J|*cEs*^f}lv8pxnmA>a{w z2z(P|8Lt+>NV69fY!AEx+#U8DJRNKqqZVg7IG_vs73@321N1rUG6@Tu6POX(_K#l7 zUbSA=-hzOBa7oBma6*J9mTiiFJ#cm~66gu2>~HDN=}0_yPVh#^+Z?^aU@qX6U{g@p z&{|+mJVs>OcD-!@w_rz*Okh3`PGsBUy@LUYkWAouFi*0*2LaO%)nIyXt?(e;-h}{L zuqB9USRe3OC@08miC+1DEbtaE2WTHOCjukd?eTy$a6|BQ@FzMWylvY6esF)JRs@hR zm^_+r=h2ZbE9%qbA`Re1pRNaAKq46hHi1DwWm9fhNpm=w`wD%SKf3z z?6A?0N@7L8d@!uP$bZLYZ5oN~B}&Q=FC<=P*N$oU0>@^x)qee%rE(SD+9IYk7`z4F z2KJ(WSAYpaRKmQ`Z$tOSfipwoLcCEJ@owjVDMEhWfe^P1!KNX)UvrjnA$?G4ce$3j zQJ${K$-=8(Kp4IB0eKJx&~J#f*dUi)+W^CFcl+Q|P!q8KV?lTWTzYO-sRE|Kn7SsL zUct7WF7Y9l%U@Qx2crb~`l~0|bBi|E!L&!nzUwlG!DSY%aC98bKr~ups=8PQ_W7rR za!W$3gya{>4wpoluyxWGRXk_@g``!x>v%ItbV#1%yUkx>eKdqTGVJindQPGHHH0iN z*axImBF(Eb{?z%yr6oHTW_7ZvWS`EZM^GSG95XNRuDjGcK%R$P%(>Lr=a`ILy-_k~ zlM|9ga{*q}0YWOFg0E59#yI_Kajdqeg=pO@QscO(mA7MG>uavZS>bv>r#EMAxH!jZ z4(=KoTeh{Me&u{=dt*d1!=eBCq}b`$(RD-`{jHH~)2lO%Y$FaPS#L7YQQD2RV<%hc z8CnCvR;YVCDzq=}%&Wq3!DGSY)Y~TXrCX=mkMu3PO#M{Srs*psM&4JNQQc6ZrJ|;c ze_nHe?Vi)E^u^XK?S0PX=(2ve1zO3o==#=vw;lABV49oWN)8lyXRPyTTnlOhOUd@R0Q)OQ-Snk8h5J4BczWSXFl1iw!`3?vx_8V&=Ai#|Dq zfv7~EuEId(qE94YPBCO}zsP(BQ9tHUyKTPtTYOu>@5>|TTSDAnM%+0=+PS)+2y(`+ zBvcH(B3Y@$k!M{3*T{F%gItbvzkW76_fSRd*!lLEVt75Zg4pqVYYP>7s92-#%$#jz zg?A<6r*QK~Srh-}D~s^**%LcasHXuJA)nYUqiF6)|OhLH@9AbXp8EKU%9;0 zU)j8>rzZE-Zogil+tuDf+vVO%+Vy-j^b4xhG|7tc;<-kugi5P5mdb1wjuzZbYv%7y z5o~_7|J*2ZP~oo7RokJuP4$mIp^5dQ*H~u@T}jX3)dK4X=ER_P&?ZGN`5Rq(I58}{Eu0Zn$ta`t?+y1!Q?r6lnv>sy7)^W3##b8bpmj$z>LdqG&A5J<*V%Dp$Q!F_$7Lt+KF9Uw$7rJ^PXEn_FetV{)cUL8AD? z(JnQplXBSzE1xeom`S{v^-%Duk@-UMb&irR;QL%4M<{Y(xhvgd!Q8Ry3+^P=ndh-w zrlRF6#0w8&al;8AUmEr-&{atP2kaaNU&!yd0#{rjrp1^UlNTs`sl4K-V^vpH-%O?g ziW3^X%-90cP(eE1kl0cTD;VEoi+{gd(I*Q{5paFBZVY2=Qv#% zeRHdedeG@5245I-M7U>STroD}T4t*5#M@)K#bi&$UHN@OFY{|6Q$$^<-#Is^G*D&* zU73AzE{hyz-tXkS0*H&lk9F_tUkJR?yG7$>w*D2}dEDW?3koWHCgtY)=Ti@!lD|mm zD11_C;Lft&G2b!xrsWo0&BDJ3RTY!W+}?@mhzl1k+|}Gcyr&y{g(=dGw@%DoP;^9v z(dgrk)nD+cuqxt@aqjru6aB<{esdplH!4(0n1rwJpMQI0Tgh9tez0^8dk@Vqzp8gR7u5K>X_;~k>7MI5@@EF6Wq2#_pZ~zSozG+U z=Vrv~4f7J*3z_@*=cC~V^#=~(gO^Dh9fAL)HH9nmR7WpHqeBccgTg=uOeA@{z7-m( zEQ(t>iuL2ki(8WWE)uV&Y?GZm35a3pjIn7j)`5s|>#xS>0xSQIVQ&H=(b5?_HE^9x zEZ3Z~J$QG{V_+Q*vd57Z5PoSwGD5H%gqaD!BJ@S7#RaMJuqzHtCtjaYbs@ggEt$vO z)4~afddu8UZ=Vwc+-@(6;8E+|Xs_aLyrhKR118dpotw3Y*Q`d&Lj_t?Za7!7>Kkq4 zN?3+o{bYg0mS@4t2D>qe^#_c(tv%z2SD@s0MEgIrMRT@PVx8XT|K?$;Npg_COgh$W zy57(}TexBt`QI!^)!E-w)4vFYRxTLx_fPma(ok!1=%~hoev0YzAG$X+H_a z6Q3O%q@OpVl!!8a)Do`nzFZ6kpJ85?o16NF22f=FKWyWA`EN zUVIJN9mI6bcs7DH_O?Z@NH0=A7U?2Ggd0Y6HyoMsy*%(&tFt*UXOs8hM4LVs3*kyk+$H8|3SG8gY?oE?}OZ$>o~C$ z;DZwlV6hAV6bJ)oQ67n5E$7|(t0KkTB!6qq{(vI$pB^Gtv~pmNT$w45G(odY%zZbq zeJaGd6O%blb4^ya8jPZiz#V-u1Sd2j*6p(ZX0X)jhj~qJIhmCJRq#^CQ|oi^Y(yRu zCsw_*62V`U#L^)t4(zqO5kZ1A9jC9xug$-JQl#?}T&$3RcYIQ+C5%u;!A3L+JbL#;!5w3k{;gi>$iLy<#l2z_{=juGVg?-I>-SCUs zIzzdx@HL*>Mc@%XZ7_K7#XM zRHp`Q33md!S*2PIS^clsh6uyYH+8mx;%U?ze_|{;IS91(r_ozzyCpM=wlIQ?r#YAT zrQK+8LWYLO-3pGbjaw13uce{=ThcwyC4I0gPdgUuruJ9r)Y`y@K37d7xQS39`6*Vh zScAB-mo+hmp-3|<5&~OKO^_4AnQLF0&yuW75H6A_GtH0oVcy>qvpdK|R6~Z+r2#pQ zc(2TPhYqA0!PCt>8EkgWZ+5wSx5n{}M+>hvv$ zk0G~^d$&vAjqf8lyk>Vg9%*UUR^Sb~d#c}Xx8`r(HUV*Uf9JuTBUZFsWZ^QJ2B8cX z$`+ZsnI;6*CQh4-4@krcgg!feZpMvg+mTj#LR2iX`ouFwLxt zF0n)Dw~qY>8E@iPWfRXQ;savhSoOtQ5CZ%XYBG9BNH{l*kC-yo>I;S47aSof{{!EX z-X4he>C-KQ@8!lxSkm9jg_8F>$SKT}4Mov;VtYqmcuzp@SZknPk@qS6MC+VqqW_9# zb7zNQa-fv71n(+va+Rl){CvXskTlCh2Soo3_UhYapxxgZn4bQA$sqsX{$VNEkgsl9 zZsOgPomQ-H{^RI$vajr*}M;2mi+Z?+~v`aLBw|H+#lsG`#u5sPV=ppgZ zA>+3PujaPl4*$RX}erM|)x@Rj?=s^aNR48d(GV+9qO)$lvlfKRU$E(Do?NU%ihRrGD!6 z)hmQ-bk;h|96%Apll-)^UEHu1Xu<@4@x~?G;!Ck}U@01PGvi2l2 ze?^;-C1=`KNa~Pv@Z(EoI0^mwrSs>+Ij=bkdSB41?a4%V&~*8$gO78Sb?a$x+veaF z-W+fyg*>Bop5L~Nlo^$C#<#``|H;5rJ$Qa-T+p!GHg)s8UNo**)X{yXI@WZAmCR8D zCeqW@-2jtZT5Js~{yP;MkI_Rq7A-PNO!7gQv1G8-MlAdySxeAyCoPiK&@JFGm1|z zlb;=D7-9%~2=W2q95x*yUC7O3(^UYy777Hm&D(nhk;2pK*=rE+4yFo51`VyEGz4w| zp^(8v=J+QOp%uRt5kzA|we1$jg=55HMBVEbAPc4lmJ3^nWrdQ?<+iHkO=CpR8~f!q z{S{CiKmj%kt^kpZtOdqH;#8N->4z%;>X->!;;u1uCGkgY%M-?P-%;d40Z}J9l<}Yb z&65f%G`8aLX97kDr^JX9&EkH9+^{TF5&ztqh02wvrA4~M`dnQ}{oNqktcUwysw#!J z>`KV(SRwe#z7kxj|ZVZ=TU~-QS7x{ItA+4Iyhe15-$i;15Iv? zClRvJIE2^Vf;uwzhjI?5JZfA>0C}#O?Y8S~xulBe)I)O{NxOJwqt}?gRG^H2d9G+~ zQm%|*OlhnwyzwTkqlaUpM+!1*rJd;f8awJw89aHa*lazeVv)xT{Ir6TKQpi*(Wkr8J5q}SpeuI;zskos7Xe!$(4c@V7rUAtg_ z!B%ntVCC3AI5kp$dgZ2$tqGkG=rxWZ*%cAvZorbgl`Lp}zJ-W4zg7mg&mS>V6vb@OL&iIZ= zBVm;|fimstuZK}bHOsH#IhuZg=2hBT+5SOd-hW(1h9?4I3G0l&ko_FWD=J{-{vOqI zl25|^86|UyPu~78r7(5dgz*0FDPN{h&ZngW5V5Kce|pf(epaWtmEdZmdoTYh#W56L z3|(tAb@$qO05iaVFp|QVe;sakZ<7b_;S-H^NQRghn7B_tl}8B-+?S;^WPH{b^^o<` z0%#X?+lZM%Br_f)kYbV)Aj(W>7jh9-;7|=YMj4sJAtp$7ixKT|sjDO3VvJTiU zNNh>!?AW(HTwJE#Trbq78~HiIW}0Q?=2a=0IQ}}SAZtL?)>(7jKE>IwF9y)&`%o)z zS?O*J8G4tli2w4IEs-jacB;k()|~zVF?v6BUz?3->Cb<@y}(E=0|Kx$~2GGL8P2; zXlY`mwh3kiWx&dG-*4G&CBRB|znPG+Ix&v(acy&C-H`%WcYhAMZAbw2p`0!I2w+mFeMzv`N-Z&BKul&C z7crh{>(1GI$fW(z5hh(d9fn+&2E7C*O6c@YsLzoYlG;=UtoV`7Ik;?CjGDjxO1>we zwd@=LxCl1NSk+IY=5E*&?ekNnl{fP_bR)d8{YaWISExEgWcw6X4xTt#EQ5&h>1Ak9 znfa+I)`Yy=NM(WZg4oj_EP^AlU#g z-uSt+wQu}Tki_sP?dZ(fIG~vm)C6c3USf@C=Y`QI$7#8|KiFGZE5B)>FFZ#$^BFF! zImHBz%O*5rki{?#!er!axeu+A6R*S67hrqI+W&rh`@Tx3SkS0(R3moswgAZKNv*sU z)Y!fJ~yS5mzWJ@bQ)T`H_pqtK7UU#+&qArmJ{MYxLl0(Z72t z$?T01Pgr_Ne-vOKmAZsN7?aH;Y7Jw+K29sH#CJ_4kbGvlOHeaktFj*9;snxEu-<%OKt$o;uS0^sM;an7_%G}eteainVjE!S{ZbYg7iGJTCs&kAy zO=lkxyKSA^5Nc)qP{Oa|R=Hk3F4CINGPzT?Xup$}uI3jHpqiLUa!?sIGlF7mx=faj zglL9m%1ZubDiOt&Jhl_E=s=&itLYbyH&t5u$gyqAUUb4e+jpIt;_tg7-K@W*ZW6!m zO36D%+rgPuXG+jyw=6MCM{nL-wMPclxlr-yarCOH3)b#Xfvry+z7OYPqB{Q zG9Ks}(Hho0TVn|;8)Y(I_qkX+k7`AAj`+MMVOczvUmxPR+&TBl^;rfu{|xh^FdL8N z{YccFD&rcD)7h7c5>RH}AGS6@pU`Vb8`j?U>HkE#QTfkL*KkK4ifq~>IHn&+TVuD) zlP_f)RJulWZjf(rjIZ=F+#ojy;NcB2yl*&TWamUhrLeI zhHIHUK4yQ`)pH6uny!HSD(mDJ>n#%c833)~v9%-0*u1Oc!&>{X*lkJTu?-_RjDSvY zh1!uWMu2cswq1fMBcNTJ*f>E_ci$PCx1U_Geq;q37%4tgH!{Zvs1~1U7m|q0 z*(Z-;$Q9Pt>-ZJ+4h}~Gi9=rOs`hUN!aVDSY1MAcXf0rB`SwD0#*ok-Su4oCx}X~F zNq%WA5SIi60DG_g0u=+dS~h_^g_v8|iPcH!nE^?+!Gbz)ks{&KK zG`MHnaypIWl^9BSjH_0EtholUW8Yjn5IN?n+F+GN1(kKt-VCWQ$5_ZBHR2yjC8{zmx*RkL^)}-&Jx2x*@{QkfFi~KuCZBIXPb%w;N`U}UUQHh*L!{&u2s;H3J4QEE_Ss!QFR*~i*^pTISIC4}J0 z=*3aG-fYh^*jVN8iuI}9(zSBw`5F_ns?j1m{{eU%XMMm$-;3hyJ(076C~aJxC6b}+ z@^!rSwZH7ZZ299tSQ^5llQydl-*K7hT6R`fZMnPVe2U-Gs%stxmX9SE3${Szsz8&} zSR+Z4HjZ8$;XV0i!XOrMpiJz%VrxcHidkkoUiHo z{m`~a{=dJ}fm0~eU+%5on$i_m!=oeHe-fqYztvGjj~eL&gNEseeDsBrge*b#cL*n% zVWlxVT!8)l&G3F=ha~f9{2Vu?GYP@z*qui2c5g2;2jk1W_5QYnHqN#s%VkT~%mg0O zf6?oBE*11kJS{p#?=OZhomRDK64i7QBKV6Qqt6jXN zN3&J=j@H-RT`-PfvpO6uW7|?(YH;r9GF4rz40<~aZf_1RGCk}JHr{5QS8ipGmKY)o zm0FD~omcIL{`f9lj|i+{;;fUi#$q~li@$sWWOb+6T{s{56}H@cm&|!d82)isSM;>Z zh+ZUmouOMChhT!}b~O`Qx8OrqE!8?EDUTZ*czeB=G&H>AW>(>C?*!~mAKmk+ZW+T| zg1MqNbBM|DV!WQ^MH?~{)Sz6^G4$Ro!;K}i|~E(|URaH5^z3#T2{IHYX0 z%7dIi(Z*u-CfS^^6*0qVya1Bm#p1X^+WWmy+@)uJqnDSwZoO7Xg^; zY3=hcok87{7Qct`JoQfFc|Tj2f4U{76KwsGfkuGB& zn7ha`aV=plTVl?s;z9-%+cDrAi_2H!U=avtvUX=kV=t=dzI%H^67)#;?B!^Zk=;LF zUFa_3R33g)!1J>6B{~ySp8vh6qGNvZqJZt8IvN{}i#Uq(D1R2tiRBhDTX{H}^kGL)mWEFw2>U(Q#3uJ2lB`O5FOVC9 zKMFU_JYI&C_mX>hT!o|Wys+C5R#quUdFv(hokC~40yKIPzzR+R8p(N8j(^+`Qdj?U z!_rCeQ;U8lr~KAfl#s6tM|5h_r#=(c>OLSU=~W4KQQZ173Qdi#m8S{dR5xSvgD%A@ zmYC^4N2!U2oCDuk{}1Ji=gm*ZYNjh*|ngzuiJpUxD^hrxf zG$Ng)bN2=~TVnMzG4L~69auwn0&`OlUI^n5GIfHxxmc=7Avjg2Pfet{4KM^Dwm(7W zXH-O<5dW}w8N(V$^<5P0d?aHowl5pnLm)sqj`ViH-GXKWv6*WUAHoKgf&$g`=Z{Zg za+$@WP*$|V5LDR+5CDxxfRfG6@^3-kx>0$pd4B!iEelP>MCDaGP!oeL$+rJ#Vkl0D zP17h3M`@oJR?O9S5^ts@oy5VO(cIf0JgVEZdC~pQ^eF88d2Md7Sb0C;`|0=SD{L#y zB12V!06DsP+)A^P1(Wx$=xTSL*H~0-JFj24PdJTw8UaaJ`%dJEM+aSqsVv%^;mN^O zq5YSk-=V0=7kLR|eQut^0}ttn6>vox{}p{S>1}vzAFG$0KH% zu2S{X9N*a~K4an+2r9dU=W-S=w2{gvSrn z5G%mX5QuC%dD){jH>p_Qd?$n~(^Ie@y2ngI7kk9xk< z3ea_JJqYd%nZ9T=v|=X2qS1&`=UQlWCYJL_k^?w_T;MzbYkZI%S2&-({AzF)o+55q zI2ZNZTaYeO&$V)?Ld0}-p%q5BH+n|hmBLC@EQ@><^Zi>Rtru&i${qSg^p2Ix_21dz zT~Q}xj zTy;nR_xUqYg`VWk*}qv2K|PXKcaAT&lHXP>3F8t z@DZ~pyzny2`|4vWY;_^raY|z=YHMUyl zW2Kwt-%b#YX17h3$k_%>{diSd(a4`FcSV3NF1o&cy>82cSonALH9q%jb(ChC6!o}< z({hcC=}1^@`xJW3lTLOspn_VneV>m>VzgYUWYH1haAA zh$a|eq^W(1Xnm2ad;O8SNg~}(YT2Es zi?fou>1fr0)<@@4m+$yvt;u=u=ePbsV@0OB`dqcm>i<^eB=IU7@?`9p@Z+rUrtRtQ zFRiwv?5V}7Ug%jLN$@Y+s!gj3D&7lx2{GmPsvMHDgyJr0cBLkEHKu zI{(dX_+S3`-dg##E9AK!&$+!cQUIM@f83z!VNKS>Db)WLHF{_)^V6T_U_DI1ds~Kf zHS}$28(&q{{ttCm`eKyzf0(=%bH!2SonBI|?AjjPxii*73;e5YsBe`>P4R_2)hWwO(@7;$@u|+PP{t4Sw z-tnu4@7C@stw#G=3$ykKCjEl;Nkh?5kFZx>tNUGETUn#gtCh-^TL)fq54IIsc)+a{ zT)1H_j$VuKD?@p$%CEsKHExqY5bYw-hm-I7GvDWud z5PgmF0hFczyO((z{kXpN#X(m36$ay=jM zzgLmkA<}m_Z03)lDk83m%7jOTKSTJwXG9n@y{$wT)_<){7)ara89T(vgFU?C@+1&D zjF}z4ydA}ZM!jQ}9mKpd&V!|TjV0G_%|m23xa0)$d++QCi@3k$T2~;J8pQubtY{*X z+c)P4Grh|z5QhVTW#|>@j7S4Xc1GjB!9}fCF+RL_SV_SjtqzM@u4ItOnWS1!iB*SX6G zNwwT`eM7sxwagFwA3UTSI3y9lBD&XRWuYcbN)wN}6uv@Z9;$uegVdga^mYu=A@@|$ zm?nskO3~=i;;jCgTGWLN0Gw2=<6O{yEF1dq|n^aw%H5A^G_h#s*w zDy(JLR}(YZj5 zjx?^jKORIO90Yn-lV0_|;zEOSAIypjw1*c66t>4h={K`~`?8>yi=h9PIuU4W^_+`D z$?kQBAJdvhMo_#xLPrqOy0T2K8Z&ays*@MC9t1xnx^jKED(4p=7oypEI+yt3_L>$K z>Pp}6xRxEA4TOHU?-1NawCK-kkL!alMhGK_3W4eT&UE?*Nsq7_Ud%5w(+#5=cAtp; z)3-jLm37JCekC^om+t{)r$kx(h&OL=^gj&Gzpicg72}4_euIgVzFE1B+!jXRhA3T) zGz0))-(1r;$lX-XcxQR*d&WP%z5jky`24QW#V99C&3p|z%`KQx_rI2_LSxiSYZMRY zOnI=r-DD3G7caSXJJ(bmkSlX=eESXCqB&0}lfDT^VZYpOFOio$U^nF*;ANI#6`M;} z#Vi9YT?^+0&JylgZu7F>!DHLF$_1D|3@&f-;N8kV!4AzzShK{is39Y3L4+<6VYqJh zP)BLkkA!>q?dQ2_HLqv2w!QO}_oHO?(Q=&y0?=EVV5lomX@9F5Dl(wNJaOg9N^N7V z0^D0YZQ$ml*bp{yt_?`GyK;vpeT0OOnyj zN#>#%DFhiN=+jF9La+v_i5vP+@Ia@t=$N@b-wi0PZkpJn%Mopdi@Bx!TG)+2VJN1TVY+R`)8{3z|-b*4v zF#RSD+6rmu8RKi!Mk5CL5bq6R7np&Zb~)`CI|8My++jib4ldX-Adq3&Yt|GK$w^u+2)ilzRUa3r3S>T*_Y+p-%Bz;_GrO^McQGW+k~y zfSfpl#5Q%gU@0GZd6|f-gh!`2`IW2d8P5iOc~Y6+%wjt%Y?s1rz`~Q%gS9Th_O!!q z0?HKiIsFqUE9S)!Bu;_z#VFWmivf~RQow)3V#y?!EdCD!z{f5P{(xU({D6qRLcM;< z+NKi1DcU+={?0pTyfB!9!3j@3@U+AG^ON*Or^w%k;mkJHH{wBSV}$h$e@Oe*YHHEc zR8H18w!CVyOE=A!qD!|l8}hMTxEq&(W!e3uy1!EdKiyT?Y^~u~`2SFN8=Ge0E*D+# zV);Dku~~l1i{6pIpT6;FKL$0QvTzD)_4SV)+S!CT4mn8`%b-ru6x8rrEv)!0Z;kg8 z-Ki(B*ObMYMA2IsYmg5SXw3Ym5&Iul6D+L7TO+K?^wP?ro+oChNQ@J&(w#rcQ92se z3^jBPAF!S!i3?L0I&%3okw=gp_-H96t=?OG2>W%1V_Te|>@~EY^?Xu4Kz=Bw7&$}hU zOL2t6M7hOtyuBLO!*n9e=jXnPecP^>1b>`B0SING7GjZN#wnl{M%bK1Sulco*$ns~ z^dB)~6)$Co0KwHnRZNKYr-M8LQsjH{Sv40gbL#W}%#)W@UsP)!Ga{8vD7^5C3~4(8 zEVz)92iO0~IjH3dCDtZ|mx7xZPwns&q#e7z@!&^~ZU|)|x}#XFewPR?uU+3b;Nmvh z0B00AEWeEDK{gJUxp`$qe@k$7kx$m0``&i(e^Wf+?Ahl0d#WDVJaFJ{)gEO`uKhQe zXOq?9zPDAJUi8<+zgb}P(!L_*ndYf;xIM+wevPF6X#HZb$}ez<=wM5q z6RwqecBt!>^BDmyZ_{j7Z%PCUt`TFOc4RZ2Bg;tdC)`PGng6aR5MaEtFOHG+*4|rJ zBu&0+n(F_rtQ2XMWJAVY?eJzSN183g;Q|y!XS?D+X=3fYUy3h3+@`6nzhLE#IneYM zEShsZB_j%J}`q-rT$c;jivB-6bN z{w~lo#htwB;Dx#Z;+4=uo%BeVfz*){UNV=1Ecj*i4gO0@e5J^lv%FQ5D1vJS24Cmv z#Giye{hUqw`TDuwY^jS%?5XCov+~Nu$LTyTopkA8XG@$@u;q4QB<3$#2z^+QGWD^TUoBl7j?dB^vyzVt9vSMewduh-9cVMXlNbGo8a? zzQ2xif~(M8W<8~kCCZ4P|5LOD(d7TzaQoP=9$igj#NFo*xrsN#l4L~x9}R5|MMO3` za+?5;_?IWS=DX-epT_v}ejec;USy=E-3wlZdR`MQ<$179ThlD5<}b|b)@3?rT6}uc z!!!w5a-+1l)E4NsT_V0yap|l*_o!Vt;@c>JP-o?*`I-ywan``xV z>ONimblDgvjKVb9SrA>Rn7HMHZo~1D^KHmkWEsVTFt)f%)JIh4I34C%;_9^SXo^F5 zT4pBdqbr=9%JVFVb@u3P;>F(qd@39h*&8j1Uyum)__sBrwlXO+C+j+QK`^IklN?plWF0#!)@s zEygh{YlVU=8PDbpE`mZJ5d(R`cY4naXU!8R1Gb{Y*3WVVt|k0!L)Eu4I-#6S-_vtw zsznMqgA05dQ=!;7{HGOEOO%~62lX&Z6SE>Qaw9GMBh8oueM!t;BfH@SUfPhH^3Nd9 z=mxrh(wW4|Tq+?(Zi+H_l@Kmx4Y0h5_KM)Il(}S&o^AJ=diE{QNbz>rGnp>h|-4&W@R6}S>-1*lHT|n zCLZtf5^JwPC1-XO&*FLM>6qdc|B`ZL9ocEwKWTbu7~uFdEii#7f8#v`TyaxsxCX6h zaY}(c6U+j2Q(8bN&8%40G?rC?KHZBkduNN#0H{h1XvaGEyCc@6i*TiA$A0Ni43%!D zzpP2vFHlc1)Cf1BNi0xKQfyBk$e(&&087IzQT#;;SrPbEG4M}o&swS;P?D^uA@%1A zr}XRO*KSYNdvZ@O3V_qUR1x@P6JszeA=guLks|VL+RoYOcn(GF++Kjp_Dq=&ih*1^ zUsmi2z~RWiyBsoZnijrh-=;5{$MJmh8AA0`D$mO7*Xm~xt1}w|s{z)}E~?^ZTB~6j z?yKR(DsI*rD|&I$dL#MJLkwF(iyM&M!&g$vN5grh3-$Vy@cr^80F&&5wBCux4PU=5 zYE%WmZnCSGXFl)z&@`P+^I{@lPZEw+Dt4OK{p!y6#vl~ppys_(^6bv1;h14C((*ZeT`WmDEoy>kqrv>KB}hg_+4PJxc8Iw4-GSBTBqr+KPnx2?_yhAsUF%i zY9ttKLtw=hFr&79goaoOhuB2T=vXgJPq_OySJayA7LQH3Xqp=K=(ei&NR)x8Lr z3GfMc^D}C~33yUL9^~(f9p(grfHqJkLia52i7g6s)F>!FG?yM-ABunvB zcTXD=zOJ|M6GS=JJjJ{sOCQ>ylB*vO;1J+mL~v>XNO&m*SJqhuYNpjg}RL`U}3V+Ad19_%pq6&vvLk+1`4-2Kg5`{qrQBb~a6Y*DZb?h|Gp@4CeR}b!A&V!;>??eP)+emu~6-+qTQx?l9T}x2}pphP&)zhw|>+vU=Yf6<}jM{#0 z#kSOPfQ0MbvD_*IrW8YM^1-Fz+jCdCuHU&L`-7N8>*bWnCzbagxav~B@r8WTo*c7T0esp6Ziz@+1)>s zbbD^?lPap@d~k-TK-O{|P$fQ-CazIQ99E^MRT(&#vRYY*&R(;5K)M#=EjofcrtCj} zh&=IPZun4RG`Cr@efKX# z+9lFOi{w^@%OX2ZAo|m!dnMOZ$@$aPqb1ncD{M@fSkH8cr(g9KasSKsB|+JAXrlV& z!}O?i?|p}5;BI+~+5UWSas8?-k86p}1;-Mv?6sV%GfyF`5|H`w!<0b@A-{pW6mj8{ z0c{9@jP=VGBjfjFqiqZXRZOq$y_f&Cr3f5VPEn~i8l2W~XKho}-Eo#lF_0Q&rZ7!N zzTum>pbjRWmk;jU+?*Y~KdcU1UuP!aceGsz5g6^p=zb$w6 zxC}nIzvnXTP7|vgv_(Yb@p8GXYEd?wdZv_N{i%w;l1WK4GD%dEVxLVjcSz59?O)#8xZ<=}T1)Z+Q zbnbI2bZo{(_GUZxXXSL}z=ZowtClTxi50vmW;)<_-6@vq-IPWrnPf(HiR7}MF!d$R zG}F({)1B&fpMW2$mr^(M;M{+50ZP;yR(mD4mmyI=|Da>*LU)ZK?vY*d4}4+^utD?Y9}an%|Ca z!RIi*mVTd~#)1}#jr>;$l=bs@a>?MK+dYSlmW)aqHj`Uc(day2b}V5zuHxtiMk z^fZPiAU;jg5Tun~m{kiYuXC&e$K3AaiF=p1!ri9vfP43k#*;!iU$Vu-WMvS7f&@<(@v+0Sr-CIE$YH~hh&o2DTfFc8P>;HCr!$CYi8bRSZbEB zFwCSNS;~a4Q$h&j*zHREm5>X&H8J&(iwL-;znw$vfT^RlQ>Rfp9e{q$y&Ey&74Z7L zvuC@F?c||#OS^0%bpMKDsUsu;xy37rQ7>-@x^u>!71p{$9J8oKN-Pi^Jz+vVrlGb6 zcIN?RMSBE})jExsb_k3>2|8wWl92RxZ(F_=A-cCHh;KN5$-=F5W08j2xMh*<-B!8f zQxT9)99!?DSIJV_bpQQ8X(WBO{;M3K%DJf~L%s@CIgiSvsfTmP#>ho+jy$qAaA&eO zjk9!XwLLxClSl9{LF7gcXKtjJb!15>t4r z*<6-+oZjGF!S$o!JX67=A}6wV7J%?!{;Qtli6ycR!#u?gpO01vc}tUu$J5nj&%hYR21RY= zvUwkDWTKv|aNo@fMa26SAIIN{q3O|AbpfgAqZwcV#H6^0S_tQ%9yoI$Jr_v zLKj1boYia?({sa-&AW0R1>eRzZ}F_&Q6jZ*9||Z2dt+aQ_XO7csNtlB6>V?f&lY*R z!K#k8t%FsiNW6npc}RqVc=z;1*(L|Iky?XI1ux`Q5UDn(ZW8I3#3Cgs?y)j>@H{*$ zc>g5%vU8j-&6(AbCW+~l5vK3NdGZbL&^$Sm zHIti?tPf%S$-4)hvUbOH#}5d!jCUq3qnvtl?$;IT;Jn4nYp=eiW>_(sc2UA5>UlbJ zvw)zINszI*xdCA;Gk>&Foz5V%)d$>K6+yW#t~g4Y2oGJ*P%FL%(y0$Dx!J?W2lZpF zR_C^+uDE0&#Bh_CM2n}c15}+k2@c-Bz+?Z{iYQ8q%JK;9@ss6-xi~5DG{RO#raP%g zqAOLNxOAJY9~3bzTTB^Ev}78kOdxO@vFZj1he`m#9oUF?;_^y0tE-#Mt@#fo++Ulo z+qC_X5lag6h8>2;fypDUtU32kT#?hl^>5*PL0c}Az2UoZ)Z^ZRlqJ0r@ySc_KQmi@@9_`^) zxT1M#Idop1+_C4%;|^3MT}nk<)pA-(e^@0NH%K{w(uf#bDF#vpK^IGSB7Dl2~Ttx|z$6ci7eWGRTYe#D4)9%&0bsRE)Hx!^=rQ z4}aE-vX$~#Q+s)Qa`?eZ>y#7w?!##NMv>ou6!8j);f0}>=MHGPWjw__c!zrblvcD{ zgy9xUL0Z3IH0e2eL9$%(nI-XC3PyHCW|w>cHF-8R*aT*#_Vs=-IUzut@`;Bv$3sei zpJMisWUuh8vr<3{&6lNwZ>CZTY!tKSBvOTMvDaUYL>-QSCHgKr_boesjK0hr6x(Uk zLo4WZ~X%#CGu9D5REmP+_L`j3-zIC^}h%@p4r!_kin`@U)ahph4_ANX#$oMe z-NG}Cb{}Sm;oizo#%yJbx!uU2%>xfoJJN_~P`c=oI1U&wx>FBOpZ_o-aUah6%8kVe z;@kS#PjnXd98nWU^noXo!m!Rmg%2LJYaN)FQh^gnpYm#s88f<+V(l#!gnM4O^mgs_ z)Mo~5<@5RqVD=1h3&g~ZTlwS1-oFEB)m>J90}hi#R+T8mM^w26?*FUkoVuM$|A9bz%|kY+_Xq`OZ%QyB zAl4h4(QfW`frbPi{l8?oWuslxMgvucdj>utiwiYy%JQv>FP$s@%g2(&{NwG zW@e2!Hcy#VKjuCo&?xl*)?`s@)cK*yvGGf*BHkJX`X89|RUhC{g1*ao^?sZJb&#oO zqvomLc=AQ?XZ4a~50jAg{}ZSRCm>FV0|H~l*=99kY;)VGYtUSOH`uJ;ZGDah*enpP zK1v&Wx~OF}OmyjP*^ZN{6A#@TD_t5Se5)##yUZMgE{(jTs!7wNwq&jh$WfX$QetW; zt@588cgi2Zt|}eNN}t9( z!<5@m`#&|wn(Q`~_FwYqqiq{aE8TG=<_NyVr+qWx0wOA;%vm`4O3o#xsP|(4*ttmc z+M!b-)*Za;RdN>C9ow%G&F7CpgQK%48C`_2g4|QB{E3w^OgeIa879lGm>IcH7-!he zD&<|Re!<{JU1JO;)~GkR{;_ZdKR|(Y@O)fI=r2{f0{l(@@;4I!%lyr?^){u?x|GUCRrIoFW8TS*Az&Y=*YO9 z^X2<8r&?gHUj+7QeU~q!WDYx$15+2I>Q~OuFh2`4DqTXE7lzw`&(z2AjtYVMkba)Z zEWHWGo_LG|h~X9fpk=~U>$lyv+HCJ;LzG1b`YnAB9Sul^;kNew@dGtfe_h$Fs17@1 z0MiD0)2{(j2|3nhFGEKb(qMj5V?a6l+KaHeF{j9Xo44njczDlzk3wTCFHkdg=Am^PbfT#*WHK45mLnx6m?{h`{nUH7aEzsjsuhYpnWc2O=YcB zJZ(=NZA3daRr-@-m?`7LFa<|b4_UJL7UlDF;N7@{sKOuUSfAg*pnZ8I@*ic;4`|8{ zC>{lx=sOX>Ye2$htp7shZs7e5r&VXY>UeN5pJ9!=-Q2J!zl*pbk*FDkfiXk>8HKwd zlTNu7JQJTX&C}#QIfoXaOiX=9;}KB;neU;=66Y2R|2EyP;r|mtCA^kn-`I9*RcU;p zI^&>vzT42te%O!q^**bVf4Us#6u5={d$TS};y0+}07F?J7@9ykc775q`Y zr*s<&W)l2xe^`*UBA=N>l@_;|Ys^i93Rv;Y-hFM{5|WXzpRza6)! zU6Uc5OPtOCseZb$i=x>TMinupZFakgmGN4JW zb9T7KHN=iR>h60ZtJ?^hNfb!j;I&NtSihe;fR`x8<)oO{3kXLVy#CFO2ne4|(A}u} z?J;Ac5#bI@0`llC$~q~g^4fbxiv0;0d*fR`nv&WLb&dt~2he7hU|?j zcWpiK1)};9pLnS4m*BGiq5Rxm-CwqFtjZh$WrNli3!-^lA^glD$RcydPDksP%Gi#D zUvM5oclmqw%?ujs0A7sYW1^?c)B?D@+qpN|SlJP4j{lPz$>FzFBju>3&+`D_2wwmAe#GAN#E z`n9{}VH`znL8PK>V##@KR0`qBhndnhYw~-4w6CaNTUSBH?@caz@Y`4Nqg1vB%6lV) z*u_;i)GtjEd?HyjY?QP%K_>t{Nj%JayoBs%Q-L9N?>+_eeSIza9y?8l%^c--{NplC!t+^t=WS*V z;)6CeFz^?y9quCXOIyvfEB*wF54khM?3acciYdfazk{cY!7um(qYbI@JDGYJ%0B3z0W-J^hihnJtSI(^-T#wy5FA9JI-^+f#U zGd4lG0iso~^~ndXw+pLAH*W=kiBG7wzHtz^qlI{J!GpdBLeTtQzch4!M&HD&IVh@p zNBri29BZ?9JfB^6VAP6VpKC@ll?XYa)30W@r`evQ-KGrp8w*gLvvAs3lUoI7yODA@ zdjI(zeutC|>3S!3(l_DIRs+<7;6OVGVh-{)R|Kh5`i#KiZ$z};6tOigb6$N4lKz&; zW5o`_8OPB4LaL&#gNQjTw$I{*kre<_g`ulpV-+d!M)Z#Vu=Jy6K6QKG>v+1UR+&O(D2Tn(NF9#=Rnd;aeiDomg=w8w#sFpNya6 z_w%EZHeSACl(AOGZ`m2#%#6DA=86Y#mBI~Q*I%>>FX?+}6;@K0gZit=SB@{sE&tYF znCcrJQ~u}#cL93EjCK^14SD}Vku>P7BC~ z$!}zJh-Jz>d2d|%I$uQT7I%C|cQ>v!ai}yG?w}_NY?nZ8j3ltZXJ2FmHsP^rM-ngs z1VzEwWK;6!K+5d2fg;nh;#9A{Nij_^;<`v46MXgoR$!>f6q*3LP~`mgB9u@7Tu<2% zt)WiRgTN@>ykO2I`OX3SJWl{l^f!O<9X-;={XAdqi^`a%%ZW~bJE-{c-T%c@GuoNP z^OzRT>pK{@c>GuirRZRnz1@)|ZO2C}JmEtINROvTk0-dB5AtzpWxAMk(>u55hTj$H zVoC&ajN@dj-2pG7#s_FSsVeiQDpT9@+Zl(lUQCakZbo$|Pz9;Z%=-@%UqphhFewVFpP16{KR+nfX4 z9BbM`^qOK0%bEQ|cr9UkHVsFCHING_9Gkb1pWgnnteoNa#Tnh6K-^!arOsO zKx5IC-Lu$>_3`$?ky@AX{m3jw)jhVg<67i>$m2#Z{Czvs86<%hE)6y~^?YW5&1p9s z-|rR9$X8jGQ$MCTnxRF=9`TK3)-P(HYOTGiv^PVR@3prt<$BetFf!Wl2fG3yZWu~n zv33YJ>r5cG_t7wj=+6s~IwcTeFospHIjXNmk0EF%xaik^&0@H+F;ApG7nc`2{Uxgz zt0V)_*)zZK%ZeCL!VZS{N1OVPZe~2j7WD`NWGwq9WTH0*b%RZ+qo;fg6dlhmi;GH9 z9odtd&6cOtXENOz^_Rm!4cPQR%~^3E5y(cxLLYIqL;t%Y^DbhlraTzehtL`1>RV%n zZDIxXS)JZYsyNr+_)cl&gwv4fc#tDempv=sG{s!LhmO3DOQdg0q)$53bs?@39IxI9 zU;goBa0JxVMiA%4fw2;jOiOB}#{Z>*H*Bd0zgpyW;8C^5o2qFn$2C36`Q>wL8P@U8 zkpzK$*{k|8?{j0dhCqy6Omi>oT?Qoiyb;o+z)X1;ic(nzRB5mTCAI!|833~pq#r@2 zC)6?_G#6T(N1XnA9?|2DA_Ajjl$hr+)zSTPqEv1HakUgE*?r;^d!@z<%UJios!G^< z;GSvrjm#}|J5d2{rf!Rok+4T`ell14#}13-LXa&+r+APz`>a~vKhqili{=F`YPT@6 zPJYDi&S9l|Hp4e!Z5b)p-ywma8n4>;LR0EddqZDD?85jV)7L#-GLN)%4d_Ijn$skI;B%Bs>QR1lQphqn4uVn{i8oZkH6vY z2_LG7l)Hw^SNzx^U^1;V>G7^evM(6kDY40iM&1D2Nd%#Gt_;lTHFwBoLPx?As?YqII&&UE#3_1g&A8~~e2MtxD=n43A}O1*#sTnv+;()g$Gkr+i% z4E;4JWn1bHaQ(sX@o|~S6xXx`H%>oNdR`#*hYH^nB?dOIKGZ}3s)`V$JGet3#}rN? zN7c^}+_z-1NW5*+n_0APwUczB`o9B*Hof43bA%d&OwgUIN<*f_s9AniK&oqwHvJ9#?EaYxhp#x@`~fd8ai^GhJ87 z2U_lSEJI7fkd_G|Ev?YcI!s}$h}D8D!b>~fJ&E0q?^~U>(qGHYdKS+vM*3(mCnuHp z(Dk)^Vqsdgh1|a!jsMtnLz8Oxc=%o?g|t@RI&IvRRxMqK%^8hw&22knBD}sWfxEF# zmTfJTDEIf6ub`9%#i8bTf zDCpK0Z~l-yPk!OTUHim&{m#`DJNd!(8lhb~Ic37vw6h9B?<4edWu9$dQQ=r`iE{wF ztdbRzj3z0LVXuik4f_M}lr?u=-o%H=R@*-~10-}3Dmg~(K_NS>-l{VOqMT`}eR zV)StFFqynCpmi9~J1XJEozDS=WH&^-1%HIgwt}&r{Ia?PS9dp3jR(KHc{#+z+F}xA z-f`LAh1dcb=8|av4c5WTcW^9j{%O4C>g^KR2VTNTun|}8jTuMa1$ZLIbu!e65;9gm z#lp4f;ChG-)riCa&8VtFoBUH`oyuAYBt!9?%Fsh!^Ln;9+EYRaP@LMcL&IsM8BgMa z_e(*YcWnD)-$y#^1#%o>EAd&%1I;#wB$na&Thu%P?Je549Tb1wC7fS0V zJonn`Njyj&HOm{9)s9Pb@>ak_7SKJxiOKr?3#|*Sy8X+A3*I`yi&uAl98!9J^+~=m zdL;1f!^)M>?+mi?I>N&%`;tYU#@L&qy@nYGyIQD4RTBq()HTyGS8}>@`6QbK+>IQ zkEpBIiu`eb*p`HO6pI0mdAYgI96vkaXjnxTsFX|)tvju#7A)aR#OEZ>jdAB>BYq5R zVr%9ceDE~a8`7<5RQ-7W!?Mn(rm{_!F6nV2YM#bDzD<%YmUAe*9xRi*t65C+{&>pp z%!XTpE?#((x?J1zhYSNFX}tW7(CAk-C#BH@HfL$ZxCm#h4u+K(;@b)@HPnIY&HENQ z&LLtC{$_EKpXmceHOjV3W6}kBHg6;E?%2h`CsaBIcFZm&;Rxd+=kECX5c^0k?fc;R z#P7(A8mrax42YP+FID<1gm;f&GyL*=vF+YebnuQdk4vmz{y-#}xrU2=8}7tp$7iR_ zgp`4lF%<(V2Pbt=`KG|%_R{wC_Ur8nCI9fx1wK3q#NZj}H|+id{5VbKuorBFCNt2n zbmWl`k3}nEj)}y%VJn7$xzRQ$o~`Mv=`+Q1&2!bc`L;~rkfm+gVI8A(~yQcI0Pn&-6{*hV;qIXB!K+f(UC*;m*a z2a2~`&Xu@_9%`GVpXMJHhm*(BKr-@-pU6!`I;=?8JCiC?iiW}3R$1(po2=66E!t)p zs#>>6({p>|&&E1tm+|g1vNN(jYkdrtXi>EUYhefTX5L$V<+E9`USg0}zgEBgVQ2|v z7s{#=)5tR!Ca{c3?6?eNS4q;CEKfblz0AK%$k7_ps6~c&bfp=k%!aakGww+a3M8n^3Gg>;u);yU$c=^+1A*yip@3739^YTJRUyv;dy@Z zE;_C9PUfG*Gh4A-u{55~J7DQ^y9apYo;L9>W!cD7*B)A}zq^;bw=gQ6z0$?l!1v%*2+}0G;FRvpu-H@>qwtx4ySFiuN9T8g-kfsG^H(c1hGR z8AimoiM;(nZjOf-s5;J~i_p?l*te#CuJ35%TxC*KUrjm7F+20a{>UMOz?O$RGq#U+ z)ZDDDdcb`a-*ST|hARfo*NHXrw9j+&ty#dSKU;6Ap_NM^?_?CiD3V!H^>pgt!}9Er z*MXNQ7VqsSI>a@ar?=0&h-a!h6vPv>1vejp6QYYcjNFcUM}4AcZo8wcpJ~}V@>;JY zxmgeyt3`Mb^hV`~{OB#)j=+{2E8zpK^U5nK(OBQ-!@b?Ttkk`c@32W3U$lP)ynXkY z7OcM^*ITxj`o=Z=+B9oMB`ip-}c z{_NuNii%HcDwWIoCo|e+HsknPl#cNg#;_^8P9|(Ui$#_x=%zsxwj|7`d$6Vn?lPPl zG3ouzIjP23)8!+|=QT&hh3Tm+t?rPv9M81IVXZr^^XS{pq_zSqdb>&^&@D^v!c;Sy zoetAjjgm2zp%GZ`*g4~d{rzEE(;^am7f?Q2Fs<@k12YED+Y>J+r-Mxfk#66>z$K+z zJfzt`}1rCEinLhf!) zVo%T?g>KBbzI5WrA!YxZR8p1-_u;1?F9?5PreLDb^UbPZ*yMb~F2=jzjopn~PA5=6 z<`GHHG*Fu-1%Iy)dq{V1JZOlSEp~gf0vT2|^1m@LC2!zy_o{zHUp?Dco?+d*-HCBw z)9oZZAF?pRTt-!$T}eOn$x|N0s8dMQnp=n64E91iMCn0RJ!7Nkc^drbO@H@kv+g!b z#}v9C4}c(e&8uKv+LJc?Q6DWe^OUJ$X0~t~?@2gR*MW1~>r$L))VM%`)$0lk1txKI zwC465;5@wS7y~)x@4rUvi{rWr@(w^+?(JM9m3j`kzIH+1QxDKVM8I^%C~66ae>rk3IERpA;;;{>^X9(XU z8CsAUJ~W*w3|JhSe?Ez9I6idDmP&s17l(_PV-?>z61MPS^(^Wse6$x>IKRr_>kIYP zu(N#kY;)f_R|F%Q-FL0&f_PONl6{EuWyG5ZcXO)Bennh`yC5FkvXkfA&oODP?AZ0M zwhUJ}kE9rusZ0L$brzjk%%1$3q{s33iMiOe?rVvBy6qXP$fqUIY7oVCFV@@Z^Bzm4 z&GbYqf!jmF1glp()^kcA|7^iIJwN>+W4TlCGUG1!rXBlzgZHG*2;CjY;4wXVC&I_SJaaund`P$k5(Cm6&3Uyy`6fXSEZ`no^tv-RYrs^R`Mo*c*QWHO~uc$I%&M zfcsIDH&L6{J7)8l3xU6?$VZ}+)BWMiy+FrvOq{35>3t){X%2O!a6cwnPV_(>=Yb`7 zwVA)+S?6}Q^)!M-U>@`E-PP*_x>QfE)U(D{y;)Ubb4&+Nia6}cf&~ja!|Mq$$ z$P;Bh=5+rPa>RYqd?8`1X-rUEQvYdm=!A6#^D*2fSYX&nEF zgl{CKiR^ZMO`+pY^-1pCClTMc_<^+1;+~fRC)+r|jRYL!X@AXmlED`6K9t)GhEl?Q z6!~YM%yz&xMTOp7+znWj%!okX7v$}lZ!QYZW>zlNogM8r(=IuV3{!Jx+pyqT&x~|+ zUvAD?N(b1gm(ph&9b*Su`_o=5{Zstm@Tak~msDV;1#`cKP0Lq@4a!&PjmuZPhBP4> zgD<7;r3}0jh$dw2yC&o|xIUyd^`!(l2i3;ElnCb_MVg6(rZXkl0%bsT!XZs3O-CWK zo>(L~xtKvuWI!&S>~mj=PY~sY&3A2H9&uS+wCwonl0$Ak@#HIwX4?G$*zo{t2>1?d zmvjP(Rn+W86ItV~KaIGnRaM$s_uA2lqEA~^Q?ygR&$g!&$2!+qIlW{qpqnzww zBvLpOI-HEYI~e+jl?+*IqadM>5z_RDm645^5m(8iUW6e>re2w$h!z;E`DG4B(PycG zU+K=OTWiv$8h4sTx6ZDlVL~$r^H@Y5sTqt$sM0-qiQXS0&uxRX&L!PvPttdqsyucW zF`-SGcT3bW)|*nnWW+;iC3S` zdZ}cM1jgDQUGyz)3m7jeMY2jCN@E?Oe35B7JAD~6n@2lQ*!5ykar$Li@e5#Y-h34p zCk=gQ)ISPly(SXK4&W1hXq`AxcqSntK0*~Ag!g?bwRF4^!}pSXMgspZMohl%#^`In ztt3lUHrOmf5T&5+emz!TB0JQB@;3gfV~rp_&Dfoif=h`Y_RE~%r=c5D-BUebL9-jS zlg!}@vs|^~yV{9`>jzZ8cCvt2p&+zB0E8?C6LgIYM8kZRAlfi z??hSIkbm+hS${rwj>O@-A)MGAc{1HtT(j+(D1TDTw>@vnp;~y<13cS4*@E3cop0EW zFI*K1JBL<+XI$@F0W)_=&z-; zN+fdbCovocfBN(PMbtY$N7_7Zpc`(qv5k#wXX0dI+qSu}ZQHhOZfx7OH8j&E38``V^@MApaX0(6qM0`X zK+d>ftg?T0(S+4FgVp4hGAkfG2T|8{i3B)(WO}fw$3{3!)&PAwX##tgUS&dk_f2LQ zcg%i|6Mec`h|f9~j5rKLg8Z{9_2T99eV#QyFGoAD2@aQ$cx0gG`~m ze=KqQL^R3IrVZ-8zkF1Ywo*o2KdKh;tPE>Ou+v2i=k+|$8S&>65#CBjqS%1FnX%^! zri3&UdG1lz?(cFIp<;YG-C0t2stj9W>=fr)}!Nz9Q- z%aKaLkxI>BmZ?OZBzWv}>=c;7l|t0n--fO4Qv&GEo05a^izkmqPK*R4rRN`|z`{t3 zgpnwQA~Pv-w!kZbg*SnMH!0V)z$k(NEp9_%7sTrA{&Aa%Q{eRc{VG4pCUUK3d^&`E zQg)qom==6nb#TSm*X!QVbAIM|_8g-g?o&Ph1z}hGak2h~QJ_mppli%?3H`pIVH>NV zC1N{BtFN^efQ^X(GW;lP_E+p@6xm1vCO01(v*&5R)5f3C~p3=W0sL z&|;jpz+(Y!{ z1A4)BflzXWD>k6ZAl50U0GDbk-4VilNZvx%mzTKy0xbBW6!Ihf4FNxwp)TP{3U%o* z%$dlCnBQNUZ}B~Wkf%QYB=o5j$Z^LE-|ipKMrt>Mh70E#z>FxXw|)M_9olp5;}=^; z|Ko{PFjKxJK3B(Bper?32Th>MN}vn(zc0?aZ+pj{hia&6+}CZ@*S+!k=@NXn&EKBP z4m4x~Tw};4bW?nGWwI^<>HBA1=w^jjONozzixZj1ZQv@WWG6915DJizKqMdk0I&SR z+>;uWNc0jN>4a!*|Zs_0^v&0Jg6;9~I=jIzA9ecN41s0b|L-&Z6 zPdirvL?YdF?$$%%)n>`@>_r|W%~w@B zDjL>*GO<0}hgL5?KaTE&p3DdNq>d=D)p-J1R;DB7o-4AhjUuaGymF@fSBFR)HmHvS zUbJ2n!n{JZ5`{|-o;zxKYBHO2*_^!WM6c{XU*|qqacm!DPmnWw6ED5 z!H?GJcbYHBxZ2G$|8`6QM_j9SvO@9H^Ck6Dl~I;@XNnYk5d6}Wk8KgINtw1*I*;^d^t{%{e;*U20MIBhMenc$PrFrbp9mNQxY*;4A|I$;M1AR)1k&qPGmW?CB!DHg z7nI{{hzEr@Z9(u{n@*wU-IN z_w>0eaPj?2!K1yMX51pb$zuBFOwotYvXXM^eZR97mkxvMT92!QM}3K)25sy$X!`~> ztj~BX^Ks?^#xW9nhq>e3;`}4CL-;fXrHAURpX0^c!Pea2>$k_}^2G5Sn@3#NOt(!* zTzr?`6IbpdC$y+u$@=9Aj!8|u*veY(Z1U6J_vrvL>OKNXTqU1`)Wn&_p=TT!3aNv)SHEIR$MV*D|FRL( z+mh`QzS&b8z2~^`?(vI~xJ`!(|2s&)6FC;)OE`zixoo;Q{MR{s?bG4&ZT%HUz-6|ZFIo?8lYHUsOW=F=#d@063TBXF4X=UXiW`^T0|0)(&*<8p4MFGV3`eOssO?x$Tz z?59Mvgr`y`{qqk$+cU53xfTT3>&v`LhaBnZ+zAASrEpb;KG4;SptcnL;FYbHqDqP~ zr-N?Z8g-dhur{DRJiwOnx+=^II>B`EIU4@ov_jLvrzoaw&kFq^Z*sYBLiHaVX^rI6 zd8Q@0Env3F4JWedibSciM|9T~pYceTE&nYqZ*z&4J-{;i@tDEnYCn6uu4BBTJhL_b zC8SZ8syo~NSZ)mFMBRu>WDoEoNdOaDT{XEg zm5HgKqVk`sp43i-ywaC2^FKZ=^f^LTRkiH;S(4d)jaiqgZqJPv^2O4XeIoeSV7FH$ z>#K^^cmE*Y{XSK$l6SD)NtOx^|9q~cVt+Fit2tbLUWmSart6Utd$zmzKw7pbxT<8v zM`j9;%i~zk@i96divOF&U;j*sy51tAK0JRyen+m+e?s@*9+5krQJ5sV0_iyT@#az1 z1)2kdS%pUIP_nysQ7`Kmv~9C+-TU))x(B1s_>bMP=Fd_c0(Q+t^RiNAiBy`igV>LI za4c!2bW?o*amb|3D}0JX(bGv+J}27CN>=tmT}M>3bE0Ax9inSH&R~{JXzTnhXBpSG zIjG~DDZr_~ZIQ=l+Y*+9VW$3+S$hAK6HCD6sToIQHAl|8W;s7=!L*X3 zvbb(7H{PT?p0e0m`nOdzm&#+|>zRe~FIolmQk+vwR>7!3Qj7R>Vd@1%3z#aU`2{D9 z3LV9oS=lq^N3tr_h6SrP5wB9dLg_QbM=Wa<-BNnWT89dQ%5>6NhfIUAbo0EMyaWFnzW4Ah zRQC-IGHe~)oNX6xM=ViNF`cKGQ`NRp?iUY7TB9wA}WR|YZc$9zw2j-m}C8O+(dTbUy(`;%#d z__k9<-_k9kZ#C^V=$`R<%$o9a`H#K6qYSE}gED4wGjaOLhH56}G>j_9Yf}mGy}eB8 z$yrf(by<4RM3-Gz*QXStH2C7AI9gAPeNm-m&QjUK#Yr)1byga0I@+e3ox8m(;=8dq zIz3XR35rI+N-^VCWAkfIKx-4d5YqhYiI93!!4-T}AiAA-6ya_$HrGT>a&t1;@(kZa zOL1VuUiue8I>h9{=S4>`zTP$~YrNO;f{;K-ZUgpYx8<4sleVJas;e~bShuAG&wF** zNI-X!gkF(X7IZx<$0n zxiL$Vd5vgos0!l}`|^w}%q0eYA0Pb^>GBj^I40)FAw%q47YRI%S| z>dM~2+|kiCQm>h@9|H zj+0nN1Xqwx=8monvYNm9`K*z0yw->rpUfi$S4(KWx72II%HJSnmI*h8OOY8OgopNy+nLm zH?DC&a8Zxm7WmytDJo4QbZ`&<^CwIzBR(!3t<5iMk(j;2N^%vwjWkIiYm`u73MN+v z2341;C{F0qtu4{dnL}_@t%JAn%sEa6fj;NV5Dl&rT8;^Z$4U&+Z4`LCEB=ar8JSAy zpIXL0F)vq+pmW+g*)jLzshg;oO0sB~jQ;NJ9s%qRGX7f#B=v%wW zuO`GPb5bZ*%|n!VTnOTb0Cuz_CIMCgg2wSHOH|zUfS2X3%2Gz6X{*C(_hx zveazSRPRK+w_X2JYw-CB(D|I=voowmxWkHnXPo6_#Xk-$p~sey6Kg;ceeia1dfzPk zADzL^@BJQgh(F*$b-_Y`5dHLi?~CBi70_49kXJb6=fV8Izy4ppz(3EyJ{!v7GWL(; z4B3|_i>f=338RyIu?tILT#EB*FNaI&u5 zQ@WacIYW}Q(|wCa;_=Aio^p@&20cDR`B<}%<_Vp|IIJbm)3&T75!5!VC6da0yz3D4 zm^DmL=lh(ksU>~xn=lf4taq$28P_)J8AU_u`co=x3(^)$dAA9M*5`Ju zdW-GBx}yc}^L6nZ?RL9!8*zL0GMA)Fdy8(NQf-yjbR##w2E zLV3hFQ_H$B&kFA>mg@vH%b!OL;V$`~(w-W=pH(>((?Zn8=Mi$U0553gxs)^N(uQt@XR(Oov-8r3ZXGb*hp0EQ@=Dt! z_JS6$2ig~!fQPD5(^~v*$JY6lyx{f6OVnlH98i$j^@Kb2SjLN70|YFW$K;374@f&e z(*nZD&r*o{A7da0m{A3W513<%2$nm(-ogOE%SSa{^zA3ZH^xAehy8_2UcER1Ef7|z zr356t3Py=XR9*<uZardyF5O4QIUoh@RC!8m{%YW(Kcx0yw z1|B?lb2QE&{(_A87vwG=cY(9gFG|W=B%Xb|g1$X2X&KA8{mdk643U6|Qnu@XDbwFZLo!00vXuyXJ`q_m>JuNa4ysnjWh*Fkrbs`5Lbx9Y?!Ca+ zBf&v>CJZ7Uz&wKcm;RR@PfDsJV$Xt>sPn|(k;6e-zg8HJYDR_q1NMAQxu3dD%mdEE zFY%u}!W_MD{2VhIg36PGj?{}R0h89F?j3D-oIv9%{s^H7UU4jwE1}7qT(d)v`yc&r zM*;^}#-iU5Efbgv_qDI=doihEu|Abp5ibQt@(Y|(kpr{0COnJmF=->EM9iudKaRM< zurtUfP!<+>%(F#5u?$gu!64Ok` z6nb5e{I7{UM?O-{(p@KM4$`c*3jQ8>-Satk>+$hZ=cG=@eN*njIQ#!9_;NSF?|(G` zxuap)-Z^PG(p+d*p%L&bQ`k&QDBr!@?I~4p6AQJ4?sSJ@ zg}#Qr)eaiy*H_~0WbeeFd=5d7+qW~~h;L`g5ruhSVb$l8R-Y*O!nO>hN ztLVtkA=6*d0h^=JUx?D#7RtyBkU{d!6!aOP7D>Hj?2q(-VxB@Y@yuk{AM1~-_->$^ zWGeRejGzFs`9C~(Zm(|#d&tSYc(Y%g-S)Oo!hK@M|F4li^*>bj|H?+fePm^Cum7O} zv-gL3j=^uQPf5RcyM}tmF{F6mw{W@g>>h9}4iC86c4j6P)_u#K^PBsJWL;#OzoMOv z_AWCcG9!9xYn-b^+_D@|-0puc+%4%H2$h1hzHEkW?{5TrOpIy<#KjcL?CcqqF{{0d zL+RS<0+y~y|Xcfi12L!#^OSJ&FLfCF9ndSZ~jyl7rrW%YM27*LTy z@|^53B``wrB=#5*>2N=VcTdDhI~L`*;TIb=D=U`e{YC5SY-YG??;ky{ zh*=!j*`64(EU#Hm70-eF(pJ=pxLXu#!i@AR(+H2ni%bu#&?I@xdc&uzSfC2LxKxqO zj5;gLS@=7+x?P(6CSom|sU3}7if2K-cj2t0eW{XM9=0z}yKIlPcj2Kl{iuat?*b?% zy%KZYXvGnepjz=!-2I#kdTv48@`=wlRuSQ902bR=*8&GN|LOsygFZ1@qc%iC+3hn3 zt&W;=(d9s^kZkvx^9D9Za(||WdoO`Wi$G^mN$jx5gO-FNb4auB zyVe5b6G68C(M*B2rm*|dD8t)iOur&~@GyZiXNYNduYv7Q*Vh{T3I9-Ji0S$)gkYKHA$13L@ud zq32o0uzimuWAU=Jcja^njLsxup9Gu?9xk^$XgaB&{JWW7rgqH?T%TM4mCNGoTe?R_5i*=zT>Q>H+~&f z@`{G0O-{|>r~bdJKvGwfr3m}PS9^b~mbvo>$U+8y4arAq)y;mwqfHkMTvD37 zhyE~LZ)64GRAy^}-QJY9bPt^~lF5d0sy%`JuoyJL6%9{o%*{@9f5836lDB2xgl`8l z*wvZq19!fR3#ud;rpu&&{jk4Atk3CXESi}nS9`R9FMCx(QK~CV(s^kO;eVvj$ z(m5raLE0YilBU+6!?(HW|IF_PMJ7-%{!@?B>=Gwa(HSt>BT1rR)M?oxee3=kv=f=m zAiFiRC_EyS{C^`PQ~#ep%?28SQC91}%U8CvhPv+&93|Z#+z$e1P=U2dxBsC^S7;!E z?8kt@5MTo3|AhZPx=_1CIH&y5PunAK>3j*vBK*?t`tH>75^@;slS=-j&pQOT0ROhp z?*CtZt?xl|^_j|OQ6;s{Xx<7XT1tRxO&}=_5 znzCRm1Z^PyTB9>p~`qrWICPwhAFA|PWmgn>lDym z28X6ZnjV$ODaTswPr9tk$OzAo!Fo zi9z4)bemuCTgNGBFoI6I^JGrswKd;bCh~a5au(?p{}$zR^abKJ;{?^dJR)>0jDMFU zI9%`7yc&y?kRuU`l=Qu9@i<_tD7y+1VNL$2?hlQR_cOy-crcQkW3%!;M_iGP^o$!LYs8UvZ!p(*Jz zR8JU~C#8g@mM1L5O}!ji-SOTE%iQNX!wMR5rC@{;8d0Sk;$KHSBMZJC{IOGGg+XbC zM}*{-@yRB6>VXfoxqmeH6KF)OC3B_9!$KMLuP7($tCBUM|uY9EkV;bXL zXDfwZxFMF9&L<%USLtiBUm%u{dwR+CFw`VJKr12$%vl@8F9b)o{G=mU^?dgT)xLeh z;p9BQD6qI8`i}Dl5M-Cvi@>*LQ|jG%5G^5mNSZLFffvQMQyENutSXjN#HN zkYo+XIi_7K5n1(7n2KH@2C70iQ5r%|1dkY3QPyOvC#UQXTzT%N$F;}xcLquQ>soKX z0OOX5Z3W-#d(t!Yr+{rni*(Kz?Q`-u0<$;QK-w0%ZNwK?up{R*PqisgN%heScJQ=A z7Cpn4NU$cR&HkIKgD3m)J=Fj&oUY*u*T84Kcf5j9=y!Z;?4!Qc03=9R_}nnK7pfUZ4hE;V>ArChVXGN?NOw;g*?9 z@CJ^tY>ZmNc2$Dcv91hSQ+D}4PDQQveKlbIjp7h~t;hgyS4wO zN4a;idcBbCejAE1;$A*W4Mt!^8&0_Oq<}KdURQ+58gQgBDt>swPS6C$Fl3G-P8`30 z=KLh?PTfTgrcS*}g3B>@Q)SeeG+t)V8s}VQ-01akPd@d@X2ij=F=$QGe1#rJ@tlJG zV9W6#{$d)@y>+K#@W8r4i8U(1I8>;vSIRshQa+`j@U>!jOs5+9K7=!=-1{r%^K83w8JzEcJ%x`AP*-N;UfonEYg{}%P|~j z%HhX|pb%-W)A_%tE0!>1_NA)B5$JQI+)wYLA2XYP9BU2U4#A~-C#P~>`kr&DGaNg^ zk$bf5e+y?)eM}CgYtWi>yN=a8k*bP$WiX)={Em5rp%e$6t*^AFadCO$66?P$qJ!jJ z^`sKuA&qE<(aGM$ep`VIDj~-TE??iUoL0Wq&ixn@vn64t-D_~mhI!C33c4)vE`$kh z$`QXykuwj9{=R{BLlm6gsRP3iu}cDX{GN$o$2dUxeH*n#Zrp>vSMRQkc*i9N1@y|e zRhIvuW+f#F8!BISq|_N;~ z0HLM&8^fro6hU509`5%;EX>KPoG;4CEBFhF^kL+IKu{3~opgcDvZNB=`;9>nH1+>*;&0=F@)nCLEwUA=RF^qTayhKCSiox*)O3 zn%Ob@l%UTCr)^YL<_rt*x`+2R`^q85Y_Bom^#JmVS9HHkHB*ll$rGnERjhOS{W|A8 z$Opdzx4nztFFZD%AK3k9eYwkhjh7t_O8IZ8|i@D5oqA?t1+UT zAe<h z^JKH%&klE`>i#?f`_mW633Y#@H<3~9ox=y-a4aXM#{@w}feC_RP{;>UR`58RSQXP( zi9J8!&xDf5`-4Lu=f{Vz7qOWa;!;{AAdpjao;=_RcZN3#?o;3EJJ)L7G(Ru%BY~c1 zc2FBfHJ}_2j*UNWT97pI$a(ULFe?lD`GK^fH8ETNRrvM06pgv5e0uq~ARFQhw6PVj z!9_~9kY4at#N&{-Ht5x0AKvbA<&0{+tL$mTOvhsCys{TO<-5dt2vDJ-N4_ =V}A@?>!0FOHn<`yt`RFpb=U{#Q1n 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

    L|2$Jv#gd|GV(~$kQL0vnBLyEA$fRCDhtVtrDVN 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 Y`!8K7>EG_hpWdC0-5_%O-@pC;0H!6EWdHyG diff --git a/reference/GBT+32960.3-2025.pdf b/reference/GBT+32960.3-2025.pdf deleted file mode 100644 index c29abb890cc6e98437993a6b62649444553c09ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 734436 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

    w~h|uHgq;9hD^miu$HC6ta zlQGfUGlHs1(*QOozZG`DESJw*=@*`r??&S5ym`ZyfHzt{&t{q%Y{Wf{kZB011`SAT zmnEO5%9!#~JGREQ?aGnn6D zh18AGDgEthJdtNqTAEp%@_blbw>e!` z75@s!nE68A99NUlaTVp>oAgdLnRi=#X|J({g`32iExU~wu2528t0?g~Q9+H@b-{eZ zmzYyw-Rp$hLrao%+;u2qzg0OE9U-rh=uwk<+7=Tz`bo0{TRol4_YYFvk80(P{8AmL zB|Nrv`$9g0wqHT9Xoy~+5pYt|RMnH;Lkc|q(4{L%I&sE6r0IzWj05n~r;#zkND~#5 zeC<_VBvh@?lj$#%VI~Gb-jn?M=phpx#jYO;V7TpWZvGRSAD!#|Sc{;4hbA{umqZ>k ziGHeZ{wpc`k#vQueq)Q4C&Ma4IRWjKyZrp9?;4Xo7cG&93gAvLvzPc#>p?T`ywb!mQ2%xHsRgi!e>a`X6bJ$6Leg{wm}20shfW}tSZYNvsXh%*4Y9SWm`IXa{6$QyppAO zAZ(d^)ajw&xBN&&qrxI79%#m5bq)3oWx3A5=cUE_A98M?Po=$XiYoCNd|&>RK(}C> z;uQU1H;BI2;xXCcWWS*A+#a7p5euFI2aPyC<0wsEFeL{nFXQOjTXpEFB!&A|uQp2b zs|(-o{~3w$b!VFQVeP1pv8B0>&7T#VFQk;Z#id=oU-l@WPH{%O$d1jV_+Bjn8snn<-9#R za8gNQP#65ZiLLNEq`5A1bZ4YW7-SbM(kcm0Y$#+@UN#5iavbgq zBCY0!S;>**7DFfsx4!+5RRT6r0iKn~_ZLTg^^mk941O*y zFMC@ZZhU@5`2%%R5wgMmve_jW4(?_4L_NxeOI!=K0C`$itfu-Pj>|)tQHab=>d@0m z9Tbt^brqT^4I18mwZ=R=EsJ~gV-jSp@6-@lw460aIC!1v2Fn&_WR7sj$b$7HPe&F8>BaGq@k&@SPXK~fZF03~P(43~ zNDEp9yeW=2g-o}R-W&Zuvc%%w=sL4^2p+M0+)Hc7e|wNiWgOZ{mq~us;8?03I^l^? zH4D%7LgnStSuKX#KjNV8r(XTEN9r|`c0H=K_N0%(wOkRK@`t0)ZH4z9Li~Io6SO5> z6Bf0w8IY1EO4x%ND^RDi%E|@MXN4?yYRaK-X9SBWOuK-%--FW9_7^L;1Qn(c zKpvah>Kd|U;U!NDj3dSBOFfxsn3JINWfKiqaddovaz{Llr;sLciPK2~EdMM@n+6x^ zf(;I-L_JYhOkz?JfmNqjC=~85dROr?gX*5rKznR~Y02;&CpzlP>TsIxj?m6*;0H`- z%Ody@*LEjTjjvo%^krS`R(dWDK9+qthq@YQXcLO1ZaVIKUi>E2&FiiPcETo1cdp82 zo}YzXf~3beoYQk#)BsjUzI?)|t0;PWYV z_&Ngk8+KwdjY4^BDpbN*iRT7A5=isDACqSZYfooHlHeAKpo$Wx{<{meB%_=RCi=1q zrC3O2v>PuAvHYE~jy2$Y^F&*ZGp6XU(e>cUvYCFtKhH-f*MQ&OG0(TBb|g9Y2MK>x z$7wD!{QYA%d z8Mv-$`wM^RHh(!!0E49>*d+(BsHgOAk9(qcDeEJAnPeieCnR06;~N;HW%^)KQKViV zg@1Rhf-Ocf>3#+@q2mYi$dug8W6lL+tKnN#9Mu_f` z5#HFMxXFMilr4JmESf6z>uj{;4Y;i(t8T2PcxbhJ_xCsBb=EW9zh4=>>6}S@`$Si2 z_P%dzrW0PeVl}sqrWoi~fFzyHC|V{(*AFbGrf4kf_hSCe>N}l%rua?_Xv#gsqxJfH zX(6({1782Pz=<0F9|H4Ezj}5S^Ybdbwe=Lka8Lg~1pZrcF0sN}Jj3n!e&lEU?mnQi z80OJMIq&ax=4VLScle97(F~R&#>5^YUI#}|=e&e5o0Xu~Ao8V4mk~Ex(_$7e<<6r5 z%Di}z;MUyA@DvmHSc44xsM61fP1zd;i&%-FhM0)tH;n)JOf0xDVT7!Ky+$Yn%6vOv zEi>jzOz5)Kh-eeuIE#C;zt|to1O=tM2$WabA@JZ8(F0qbF!oWm=jCRP<1`#}0_w-D zBeJ7W;{{y~0`u(U`{Zl(bfaIPIzt7bSFpF+#PxPr%N zQKYU?$pe(77>QRS;;i|po+MaAByY%|62r`M9%9#EB15*jQdVoNg6*;;NR-zK0kdR8 zD#F5~#MEKmdM_JSPK}&!wYi!;k`gv9nAfV;-Mk2{Z=2+XzG*nDeX_mAzx2yJQK3YS zj4Cy?T@ZMMrv4hcN-^zjO|@YNsn}f6pm>i*MW{h)sKIzU{rn9u0Vb_@oysAt)s6b4 zBA!}WjsW7VQM@7oI6l_0L6Ta8d-d6PRvCxewv8~+H7-@JjCffEO~P1vt|rfBl5`+j z<-bgsedov9cUM>g^WVDolb0^IGDaWKlDkfuvGLc_2Ep*=4XqXTrKDk~3S8D3xeR~( zwPIQ+U{GB&klbP7aOX0`ew2?@0EzU-+`gc8JMgr`y|7)YCc?cVqgz{n zZrG7(jBc_E_IOc5&`dG)ARQmuD9CGJQq_U=Akoq3v=0K4w7@g zs$~yMn=)PS%DQx&LE%iVbKN1GOBL3e&m%ik`8rgApd3#t9WjRz562V4lRw`Zy)+40 zj3Gnq#3Q9W8Ou)o{8`hlRvJ#cZ(YQ4Pa$oB7?7N(uc_RI(90BV)2dXy6viXk@@%m` zg@zU3E!79aDXQ{6eT;)uOTO${`TTrS#I4i7ZCq5#py+l)QM*~VN9y+Yxjy7;)vZOX zjhIEZ=&OU;HAU(hMjbV6hQ2PzbC?N|H`>1PZ8l7po~5#wIsx8cu~;WJwK^M2#hu&; zSXpGMafYMl^OXb|TBa0>#Fh3Y->XkrXc(q7+Yh2IV$Q9;5FFXpn|+_$h~-3RhfQeH zd3Lx1%6*mz1ab_88?8-~<~6G2#j!;M7v&#h-0?Rxpol!lJWZsb>Q^+6gr&Y&lCw;R zuX{IQP-YEZZDz#7%8W4^+wl-y@-1> zB}i+_{PUPB$u*X(ny^z`(;}}cyZiIlDE=`!cHa>dBUat6^$<~@^A(;z;lJ3v7W?}l z*aon@22V-@WI{gEs^1X}F^MW(-#HY0a^L$X@cV%S%)<5+hG(Uuf(!7K^8&l;P_zpl|N4cmhFnba@ZWErnL_hY`~iFx%1wg+!N7c;jq zoQo&=#hZou%O579?j}_qB@572yy9SvNLm$V_NbiwxZqDRSL^Zj-~VnLFNiJ#{c>bb zzQ^XD$nK;on&jLB{~OauTYnsT{CFK>&8$je{}FRQ$Xw^4cV4UK+QInKndz9cRHvCF zNyzZ_*IJr0Fkp5g#z=r>y@EDQ!6yVVEZk`DwC)CGUM4D9wl%jpQF6%Ksd9uEAQ69)c$ z`uF@`D)w=|o+LH^iKZhk2VwtYjk(7l#se7qXz8UMoMI$K1swdj#@xHqxY5jnFGkefI;lU(UL)F^j{sqxUwbZk0B|yihyOjMhqB@annB5&pSKZFqfsVx|f#N z-@1X+xmj9Yj>GaDe!L{Hfu{v(_6RI*6eE)r)i>29hX$@DgW1l4Uwg(jHqT1}1l}w) z7*8B+$p_dSUun!KYB8DiPSAgr>!8*0Jck9^<*P}UCocus9Zyc1^qCcIQdnXTSb7v1 z2(*pUTJ!FpS{^zoFcHCRSiOF>fLE{sesTqWT?1PZ%Sr@1q=2XlhXaYnh-9zPZm^JZ7 z6-;JB;=4+tb$pnj{+;FhBmZo{{_)Tl7$3+5vPz`>hW*L_b1V#hNwLR3{Kw~;NeJcZ z8Bn~WAAe7xLNNseKEYpHrFAAH9)8TuyhRI(5Tjr4gZ{B{9gCDy&O=zp-$I*`5wQf# zIS(nN6zxpQ*FKG20Fk%}j9mu}i{k6DgO^SXAD^IylKzx8hi37*Xk~@+3)!4_X(mYE zgOz>q*!^rGi&Y75&}|QTA=~FXQ9#;AqL2rcMJ#8h`${rv#@?k61DO7tmBgMF zDUX-QFmua0X{oj1K#9^N53H~(xMVa?O4IP2?xsJUDHRH2iq%q9=JBd|R9Po*zp?nOdw=MSynpx4U-i{ZnOkE~>UTB6qD=fmLc= zl*Og^5uo0+Q`}Ymq723Rb~8gj9Zt3wg6MI(o%;SS76BRLxd}OTQ%7 zd`xPa&J(0%!8qEPT?ZjwbCA4j?}0UdGT zsq z!gIxVwhyj4;e`zm`5swlfK0>}vFFw17F36>>!532gC-?0LYw*tPl}dc0PHKt4TNoZ zyR<{`A?=tm#&J%42v!-GiuX-!K?%o2a?1e@c=%&K&o4HOD7@{@1AFo=WU_Owg#=00 zu!!IYk$)xV=%-HVfgMX41!9EtB&`o~lq4@5TO()Xf5Xf!XHzwoFQ$Z(_4#;UW#Llb z39BD%A;Eu_LuHeVd9Fv8tNv>VNhii>EioUWIASW?-TQdoWj4DZ|5Gl~#sWV{<)8(N zEoPxw`ZUB8(3R)VH5|ut#GZUc4A&Dhx;hinCb1OrOw2$p^!F%+qBT8re-ebUIXxB4 z8BPhhK#`#c_e81V!2vAj5lUU4Mo=%$6}y0PMH}@7Jfo;&Nm9qPz-lnRV?7sevocsNwaim!V;@H~qEvsw1w8d)t|)vL!}HM18MV%O!66 zvZyzRxIK&W4A$!YMY%IW(qp@LKiL313h{`yqx`n72eGBbB4KJl z7g%`N*t4bHX+ZgZCvy2c6?(!2!?j@qCuhA1K|)y9=4=NM9-q)v1v%>Q_p7IzlN|)w zm9XB)j}-`O?+{XBUY-zf*)6qCFTk?d%XkHxS`RdGur5!C-cS-eT{kxmL=>KwRjptn zUL=bnm?4aWCAOy8uQ=$`<;^^{5tgH)SJS3VeJN!UT<51;1n(2=eO|ky0Bz(7oDJ(8 zZ9(B$lOH{Lki8G%#JBo$OXwp()?0=#P_{VXbXjT0Y@$mSOsAK5w9c!J!bKPCyHPnP zd`sDxKKY|5PPDQI2GJT|b+w4`bIon+i$p19r?UDR3z{D{OossCJ!$YgjA16ltQ?gE zi$-;-$0cpPW@pA|?=OpMku(BJ76d+PWw-({YYT0Pg)6=B))qh9uk&yzyAm@Mlyie1 zl!Ekz8)BSYG6})NA$U~PvSCJ91JY~g5llApzqUB#9BmSCa)tJ#!AHUN4j*f1)4C6vw=0WJ9uzp z`mU>P4&3|t>80Xdw#_-E>oAd4xcuH@JP|QWM+uw=P1A-o&GU@OfyJ}G^s=?D?i9Wu z+Nstt+m}W>!>z*gRSf2}Rql=%8sI9Da&*Y^bnuTwT0_OU>!Ye(Dx2!z3&k; zJ23DGvP7(VbVW;nKdGFcHbJ#kzQ|qKh>*4&6F*TN8EzgyZ}X^z4Of{O^41e2Npu~J zxdIPoBU3>&#=T|>YlHr``B}&hk|g9Bn=nknp|Oc~40? z(mXC75WYv_`qa%r^uQx1vMk78zQUxb+o=r1k?>ZRt{^X8PPvv!#`5FOHy^^Kr{%0Q z2wb`{(yq3Pk}#qxGP)&0U$BO1D8L2H5R%HwH_{g`>2i9QxOuYkTdAltHuwtQ=W)>w z4ZV25?n{MKyI-YtPLksDhd*fV=J7lHT_O<}VoL^_T7<0%0VLjO|O+fOdMNH*IV5DN91&NvQ|zD zAtUP{HnwWBBFYVZ(ngEH@|+YGDckRh)koHrA`=eRL)!EkXGez?!%@=}!%i;aAXMJu z3?@A*)qu4dP^FfS;Xykl=Nszm9(8eA-;vyMAjUoaLoD!2`G@LPi-I>uG#pa1XhD|l<@b8DlS5}mz4g+zm;jk9g(TC zh#yRIQGPFAmZTVtf%{*BvPHOVW=I2?r(a3mc!+ga)J^i5ka-=LGbkI?m&)mq zD;U(}UDK8q0tCaWp6Z_AZxv0qk8jF{%U|JJB%KjwE}XB#vht7b@$vm;k=iEA2vkQC z%WpT-N+st96Eg`%I}w}cgU`IV3SlA$!N>GSs6WK{~03AIBLB2i4peW<77<%j; zKR4QE9<&{23Y*fC!h7Z#uDlXOePdbzf6s*iJ>YU`W$BA-ZHS|+FP0cV^%VJ`K|tr& zt9jn0#MTyN7RyDgC_y_gtcIAOx**{Et0$7Z_Ai)LfAS+I>=GuTtm-PBow+T#AciyN zs~uifBJp7Rr}h{}o^$8b@z)(F^88=UzsfwIQ1-NfXSgAskI>&mrM3;sQVV}~zzEtq z`I>xg-vZF$;80(&psfAU4itkX7g49Y&J~^&52rn_%7#WGQsh9~+S>Rmh?fy{tTzzC zN!E<|ABmIrwJHhA9ouKz*h)ULK~mPQ;^A}jV^jUf86mTN%gMzcnb0YXzsW1kZ@M+{ z+hK$cNTm*8PAZ^sP z0QY$oGc5VJUICqD5v^aC-t2l&MmR6#Ocer8C2esbq(T)~@UQwJdhI`#Y6({O1UxaD z0bhqvt}r-@nxMUt7wn#+C7z1o#x{Ro)h}y%%3(9P+R>sx=ae0k#L^(ayM` zFxSGh6D64TIJ}GTj$6tJ8na=q*)1M;PI}O=*EAE0JnJl(hnf3RNh@uWCP7P3+tq81 zqWkZqDm;bEhrnfIf(?VZ0GcKVg?ewvnb$>pm*xjq-x>1}lK_rtklRl)%7!{O&6CE2 z7BdO2z|zmPBSqQszG)3;1@lt%)UDCrSlY(eErZm$DcA4tqkr23UYPy?z=c`%rv8{R zXvBTW4UUWJ$PlptO$O%V9mFBe@usi&4SWzbg#UULsU0Ow14LbU7n2SZQm+C}l2}GY@fAJBMf_Sx*8@R$I?(wsf8%mJ= zi}0B`5#Sts^6vw~CsFrdqfp6b)JUS)FZ-k)2o8<>z)x{WPjK~zA%Q<--fYP#=|`61 zK@;e^oGt#W_kTWrBD>{mM*~WAS40#nRC?iCpbR-8EncI4+56AR%*v0aWs96pozQGF zoeDmgUGA0Y>Rsh1=o{Xfn6(jte%N6z)~?bO>DSD~3j){U=Ij8A6zhC7b4t-eIjlT2 z%HpMjTHAJj9&{Fy8=YK(FC0nT`i`HCkcZZ7*#h$DzZ2r;?A)cp^eg|v&ntQvDlMBR z?OCMLcADJB6SZHi2Gv(Vs09di(M1i%7e&x;#aXr1>O+YMaw@b==u7aXyAD;Nn=K3SjSrONPm{P#T9Nc%w zqJ=j-!A?~BLf_cmfnkEQn|h<~bO~ll;jpz;wXk!uEGq5;9U+HEPq`u1+s-68UIVTm zXY$3$p4SAao_HREU)Ow`lmZs{w>htKvkzu~((6p}fcw=i2|NI%t3Q|n*lFB`rpQRd z+QTr-6z-l>!d&yU5vH@|;)kVm;;|+0+<2AG9tESa7NMZ^r{b*VpeH?+#_bF>a3Q)w zzrDPwc1=Bax2=NS4LlysI?87;Sx2i=>}WsZFk}AO0mI}Yku~9ZPl}w8Lh1VFu2U%7 z_|j~nEQtwzuN1HqwD}^fd?n{GRA1+d7hy?}CgA9HAr*_?_!T#BJ)k_dZJqWb)*8Dc)N zN%rU%;S2QKOtQ$uREzb9*{am$`Oi*$5@g?t(knV63uJrvf08dem8^d@tMxt`ityGR zg9^&h%?;1$M#3VhoYBE0!uAgmRMn1etX0rOyyYEFrC&pKi9HLlYG0r<-v!#<1cc5~ z+24g0q8AOuUb@GZ_)atE8k|kA2C&Puw?=JA{PMKN#*Ahuc_P9cX>&599dMI1dosRC zm8NaB(E#@)WUmacAVm}^jV-D0g3?somwjS(vzyisj1{ShwuLf|m)mF#jPT%h-g#ML z_>n<6+UwsKs-c9`xmjZfo_<587+ZF`E?o01jj{*A*HphKb4%#*TCAGA)8!C!{2SC6 zI8NdOCwVJf**{yX$hT)nuG~#jVyAE>h^x4vB>hfL(idgD_>EQ{Rw`G-qEA{a&4>GL{?OKnh}JgY9bJ)~PfefO1fX{Fh)8%iQU2wxndx^M zqfl2Yn&0%Uz$fF=!bfE&e-)u@5z$dpMzFS#+zAi_t`udbaBzdrh;S)26bUUeFz@lZ zbEM;mj!Nt4Fx^pqEPV<9)Jt(sjtBjXNA@q3gI}JgqZ00kHL{l0n6**ct29u=t4E(P zB+=%|^)pJ|^+5}@K%HGP&rFdPqzNzdgeqc@7Q235FremSD@vp#mVl2S@-ka{v>vtv zt0t%HLh~1XR9)IsPK{JBa1#KT@IbJMkm zHhy*UN$PHSS+>ap?@a{ za6ehPwnMoU8e++=AIDfU%TW5f(A7(1Th@ggN|SNIcsH~s58MQrfkhugH<8qDGyYr|@*kYfs?)hL0}bWcjwE(JXvgx41IL#-D`2gr755{BopOTyj=N1G`At@mBQJiH#G5soUtlVH&L=m6GuZpa0B(e? z=xDVK1^Cl!>bBm>2;El6(7hC7&PGHGU_Fkl}iD#DaC7$&Nnz0P#rCs|z}2o8ScOYUCS zKNWt@YD6}E?rA4u7fL)bnvVLK&%63_t)@0Qg1o$)trMdLd-4zd!KE-%nUTj(M2IZx z@~07SAeV|m6Qmet=s|6N?I=RH1G6>o=MO%Vf2&eSzo=1sSEhjXP;`+>qY@B5KDzJ! zu=iF`aW>t;U~mWy!97?=u*TgbcyL1F4KxJT#!1i+T!K5nf?MMbjazVc3GOb_?|06c z|NOJgtT`8RF>B3t(Y>pm>Z)C}OP(!Nd!r{R90pAI46dT&-KDo<%3UbSf$oCP`y&MAFRC3~ZP#BXQHV>#*IXCOd<#kKa0LJt zRGE5a+TI=T3xc=>MYU+@>?ZZk@P8f7&^R`IUA{+Bg;WFm=l`m@yEw=BZuu4ae;tF! znqs&UXJySPL7n47xjutsrfjtvgXq)KL4Cdb?_@pL-bi{C(Mo#=_C|4k_2@a!=G!vQ z3&^D0(ALaU!&||WWFYX?8^@HwL%7{j4zRl8Baol*rrd($3LdkqhJ3wVlQkHs@60iE zwmQef5v9EFDjEJ$eY%#WiOB}MBCHb1o?R@-g5UP~T5oB3p(#Lo;vW0@CBv#=QTQ3J z885=!`^E|`aaJxGyU~SU&zjZg8h$rKnKk3R3+5`!S7i}e;^fta=XN;YLl^Lq zhSojFe~0^-{Utr$N9H+2t)}_WB@v8&2NtW>{@KS?{^a9aGOd>HXoI1Pe`O?eF>iO0 z&z-fruSvVGG;CkTI!LmXrQ?&&@-T<+_ zYFuZL*A%_lq&=FVRCbjxgmi<_C%uF(-6*e(g6eER5$yZXx-D#uLTPpH$1Ibu4Wy33 z_e^xtx~+48J9CGk^gogmN@M(b-mpoZKy6w2gMLB)Yn#BC2Ns;`oYxZF4AXn!kC42H zU=Gn;=_7E5MijbpIHLL6lc~vPiENrUkf~u^I{J6BZHA?F3X-YyoX8Z{Scu?{zjJ{` zFk4R1I{gQ5;2ZfquSshM2j8eIMwplYi*9gE14dYLdOu(x0{HN9r~OjJO{NB^woGa% zUmGc~T~g_)ha^;4n5d2JF46XMNd*v^4Rg4pz`n z0wlN#suPW0TD;zfASR^!8L+{La-L)v1L`G;-eOJV=k|Ik>_uwvChnzd?l^MxAnrX* z?3HKs*!bHqE1$bDBQJjOFn5oR(wG)$(i&!9WN2Pc+0v~RoxE)pnk++t79UuvI{~No z2b!5@^euM<>Mut0j#3R@EmGgp3epF8*9g|~*3CItV&=a-xVRtwP$Sjm6E$Mix-MZb zTwFsM_@|Bo#U(cnp;wvCfRE2ZDOou)*pZ6gK0^#`Gb%2D|JBn7wn&i60AZQZx4GP% zkI7O(#ACa2y-77)jep@vB9z5+x2ic{b)AFqR&u`x8jW}b*KTSpWS2FBDAEk`G@$-> zN|?w5?3*udoWOXn`P)J?-u2vBjaZ=WcdRWV7q)g?E~Tw>Fcr;sus#thrgcCm4Up2| z;!oNAXkD>Tfm`}b>2`XzkmY&{I7u8P74zF1=2~;J^lP|Gd$dP5n%!b!{Mi~-F4C0X zc{-x>OZgF!t7wqK6*djPsm4{QVnBU5XZ;)28>==gH6}b;ppbG~QJaPH!#dT>)7{0N zUU{aTwvm^n72%!d1<=7D)o_Tx?_H|j9HWw%cPM9K3aAGkQa@%k%N54Da4z3%nk!?y(hxz3Gwzgv>vMa>_|VXFAs5@^0`}Sr(R&>f zXW|K>>V@Cc0^Z0eNs{0SCQOs#+x4-V&6ekcc`zYAr9=8d1<5=YySh)(J91Q5 zzbY=V}ySGVIVY_Kb|;V=~3z-I28V! zmNY7IiD)orA)~Xm-1_{_RA@)<7rquvM#qL^P9tK&^pwyl^Y8EzF@!X%Ah3*> z9?H*n;k_O-(5E&^ztdp#_V-`u+Vs*s1c~@_>uwqg80@tHnq@Om#8&pz8D8@r`13Mf zVfsJc`hPgf7ys{0K?wd&!U%Em@cdug|EW08H{!QG=-%RDb(@_LqC4x+3T{C=mI5dl z<_E2Dy+$@I=HCG-vFl|NUXNcm0s`jg{)EQ)7|aEW#4um&AEPy&A@Y5 zCsEde|I^)hy??K6Sb5L&{e`Xg!|coa>V>X7?UuH_%lyzmG@fBp{>!X`xN*!i@dxR#DV)AM`Iwrf7(tu8UDV$Ny*ZRJ2?;?6j!zlr(>SK7W{i<)R_v`u5 z^=pRcoS>@7WgsU@hu2N0aQ`t*#Tw&Ab+)hf^JbyC8aP(Q+tqt2VitbnL0$>&8+HS{;N2}o}Or9bj*DoSQ?GGp$v z^ZiVh936lCcbEl?BP4rRJmSx&G2WJ$Gj6h2gu*jAqjBPFna=q0ODy~2pK-ile_k=_ z3nt_Mo>nq*+8j`bUe;X&V&Z+vOIsG*>wHn>qBpha^tA<3aP&Ar7&5t9Y&G;0oNygRo_bqq@;q<@-$T4J(SYYmBUd8qafBW${hXvHNG^j5IzdK zX(tro zUHkN?f`i{K;jy7_(Kk#R_h29jzF~b2hm@bWnN`=|(c7Sm?9z1}oLk%j3e~&2hhPT` zS$?H10S21iY=kRt6$2-uj+U+b?b24{~*^J@S;_%^i7|(=&>-T zVVt~9B0}7BFzAWE0ykVRhJ)O?zBa^w)~h2ZY^VAYPGE>Ao2abDYkk}~Rvo9f29q*o z+t2(?k@T1`1+#vPHnN?0@$AoFX zc!Kr}*Q!Bh%Sd77~m8*r#yUMzm9qU&`2mZOF@PA=*`?1cX=xezt_DpX2RB2Rcd z<{hb3OfWV}006zeOAZC)Vu}`%g7FRfylRlGi{!!N4>cla*Ye znfUD6N;}NH{lW;g;+Pp&axp0|PU_z4JXyi4ru2K7qA{kKBpm5&`82rn^1hT`^Q|p+$q^`{e%E9URDJ) z;@82!AzQ=cp;l%&olUX(8lW5iQ6bGH3ufz4t%#nF!KL`%?kaBDN3Wu7`iGToLLoX- zL-~^XMQsxzRNGPHtk{Hc>)*E2oArMTpS`{=Qct`N7TwFco}0Xow+s|zv2+N>OHl== z>XCg4V`p@}kP9wJHNcXHxIH)75XOGjJl@}E`(psBO+aDEAQxrF&?B~^%z=mZWH7v9 ziyZlCg}wE*N0k5lt?E&5)qo1hGU^%Q+#EGUb;w1*lTyo&Qplaj@Z!-y!(U#ui#PDF zxs)iv^n>iNOhFXZKQ6*gB}Lq@lE-xoeo(p!WYMis6TKZyMKttI_|6HHOS5L@eH$Su zF>UuIjC0)lCpriZsvP8L3snv3D8Q5@wT%%PQN$m#li0RP_`$h?qfz+zgH#z|M8L2a z{vZorpQl7NSq~BZl)Y=1@neSNXY`9DTDQ~@R^gu6`H`+t$y!az+L})X6t+Lfo>(JP zI(7o*$Im|q{;_p4{rG36E7wdtpf*>_(IPUF_zR(OavOK5{=3(xiHZ^B#P6FJOVv$k z>o|qAVS0ZLZF1g<5ieVUFt-Ox%0~BWbKb_-+w5nFa%d@5Ys=Cw_Y;$Zml@d(L;C6D z6wiJ3JOx^by;KnT1?v1bcd#)&LcExCwBX*xFXU#IbUC|pD9dy$m+Xe*<;YgJIut*PhaVxgC;m7GfqQ zqYnUWzxiz${Kq}zX+`{N&sw3X-xvFdmZFCCv+Hk^hAO5cZ+|fw*3zR=d|01K&ERte z$qCm0ourO+-+fLIMIO$SO1B`1;|!;+XS^6*`A~1YFpX?8)Htk`GPm!QHs#i9Tkl1(i7I#ekQt z;du-i$WME&QT62g5b^hRrcwx*xS>1TTGhPrllX74J`3=we8wMlvM~#L%$r^c#L`_)&Z=36Pzmo{W-z(d`**9}-b8R6_tGY@BVfim!;`9@UYp`O&$YDpWL17shg>ZH*y%QoEpWhZ9~r%2l?7X zpiDbg7sua@1=_QWIP(Nk1+>f)Awy2ZU>8qg)j1bP87ZArmK~x|2g%E#H2UfKp9XYKxiT{jlRE(B4dF4naBMA6;VK!@|V5HB?K92bFVlkcd}`@mf;MW*G39 zWH<_v^W~BGya2tWoNtu_jFbT%B9eE-KqTLWU(-`kSm4jTrbnbO{}*^*15p8Ly1sWr zHL2UD<;!dJtiG!sNo9CcQExn2;E*!OOYJ-)p%Xz&SS){fi_BM4Z}=elosIbDZ}QXI zp5L-pP%YM)-NQ(^Fu!BvKV}tt+7r%Nw1oAy7AR8o@eL6u?xt1A}0jX zGUH<{n>QGsb;fut&ziRYdkTvd?8EmIvymE@m@OY1miU_jYeq2TA&yYb&3AQHM9qP2 z27-gi`I%MsrB1u=K!lZ7g{y3-6jWb1>0}cu*H1H2=#2JnlXqA2I!%QmK1eaQQefH^ zm|yxhG%L#4wAx7%i;G2IdYa}RcbjLsgrqw%Y$Nk1?#gS7N~~7i^)>Q?@Wz|_e3OSY z14l*M-z$n<%k5^aQuy+~gO#ps=RQ;YYTu>D-F9F*PmDQxit*m_!wqSPeLAk~DvfzM zO)}ROr@XVyrK$RunSY1xfI+ZAcZWYzfn17e@y<_x$BR}JMVykHjXcYXrb?^V;elyy z3Zb~NM%|bEp4L>VcE+YZ5Sd4RpLa^K3x8a(A_sv>3{}SqJ3sm~s2-x+K_*30WiX~E zJ1}*JZq*Qf`t|}aE|29^!f|6(Owqhu^%yQSHyT*rZIlX zpU&3KANP?Ad3b^+JofEt@{@c_xgQOMa@)P+28-P!bywkgH;%n~4|W&c9%c!92@B1q zYMC90yw&papsC zknRctx>W?-!mU~pwRG3NLZI~R(dR#*w@=#~Ny+MJ|BdLz&GkPK-TvQo7thW6Kf%c> zAjtW@ADVBDq7;t1x+1zqaJoZp``Ss4c}RX>D6wJ^as1^<4;0aC+}`O^sr|`qTTsDr z6R`;|NXQ(OtfCwJe06mRQu0#fzw%u7bQSuLSyrt6!n|Uhwg8VIyXgNI?E^8Zo;nd< zy*jym5WgCGju%G1X|<#m^LbhCo%*!U{y014FuHIe{&G^U?l0irgY-CC4?i*A&Ty0U za(mal`uqToGn*+POcFtev$(_mtpq}H5y7F6+YE&yE zFJ3aqhR@!(NBG9BFR~hM&5ZF6P265Aa2B_dD1we$U$hAxh^st9Ow zhX__4MiRk$vFhgY@^;KeeSnGCL~9z#&`fzyL}{btA~y(742XzQ<^8_9Mas4(Qe73+ z!%oJ9)fQ|U+)extB3PiOER(QpzjBh?QbT$oXdBr~D}AWMtNrm%gD#~AgNwy!+yUc9 zbHFT{o`4lbMKqR~Ha+uq#ivh|+0BJagqwxZI=sfqq$|`ZI367Zox!9h+iW6S%Vb{ntk4c61>Os5(5@2^e6;4{RUfw^FtRX3E&7}%*pRBF z+iwkrHan@^Auf)*2&!;LxoEfw*cMyn0XuNd`Al#hr#Q!EIyG?DrGgDg_j&Lu;DQZ9 zcJh3zrFFf>8~VM_hZjTll^OFs@U)N|sQfnnMd?hb z!DlURPIjA}GRRauE{O2kcB3TLClVT_w>coQkceoVCKD-23I1xUh}+<5Mt4Ze|2;Vl ziQ#=robybAjMS>S#;de%(%t*5HDo=d>PIW{SnxpMgYY29fq)aYht6bhL%|mLvM0)P z5Pgj@VT3ZF3TL&<3#R0;2w}vbG5i(nnu?Jm=Mg+rQls3Ht`LWj#XSO8k1t?h@_~$F z5mue)1z?b%k8t3_0`rr;Q*~7<1$9+!5nzqDDBW`XnIaIgN&VcFV{I0uCzqgGT>#B< zH!}|XamKwdb1rB$Gi@50LmsQ6Bsp}D7ymxwPQh;oGb>u~=dFS#8&0xA2u0zReiao` zq#&;Gpg#RaD=CjqgG}8gLqYMbeSyj!zwL*qaWbgnSc;z6dqXdNvclmu*2PcdvPu16 zndEid7&P@K!{6k^q_2kJ-#8V=)0+FrV~T-7TF7}+XRO?HeLf_a_k5dZ&I!qX4oE(y zYV;1Nuj+iI{@2tJlDpAuU3tWFb!$7^oElwKvGhgkJ3d(3mlE4Em&>H$!6dsgE^d6V zhYRJ%tj8G?nHC&G{7p-A-$4W|a;LI*IxDsYbs)f9SxaEg8*1`Z>&y<*2 zeE2$$?PUD=;BpWJ*nIJU#I{4V(x4JaxEs!7mDgypV)5+>3-@)EGVX~84|JIXuGG}9 z4(kUT*G#fTw1B?ec8}I@av?;F@A;yyT7!QB@DBWqsC0TvYwQL8M6DuMe`vMZL0mqO5#UoaS#O01(`>BWJ9@VnMWC1B&zQWK@nAC+fXiMCJi6x?x$ zoO{DDBj9N$WdjFF{21JIgmqBg586`0vB%UnKS!9iwNVNcj zj?(?Cz{QLpck<>KWV@XaZ8LO5!rqEG@vGnWUDLH{oYS{AZ<}rbDO>%Xq5PFa?;KL- z04ExpP(CmrcPMsEUH;SeZYTn4$nV}B2{R>BhtK8Y)QA%uh8Kw@he4Aujm~q=BY{bd zI-1;}M?81E0=za1aa>jS@d{IvT#j?+_hS9ta3Lrmw&HO(e6p<<#^}!%uZ8EYGACaKBJe ztt;iHafA~666?)1bgNpDWy?*G@FyXUxSeW+A*a8yWsiO?LyS^b`^kjik%rHIdB|#| z%oZV7zPLG@K$FP~+HPKogrE%Y;P z(et3Nra%`}3=+md5D%}-`@E)lhrMh)Tf%LmMe0RB+NItSMEZ7N&2O`FV~W52kYGZ3 z|C;PW#S$VM9BBjLAmw$@=%^=cwi(~Zudn-7{e)A!)Sv#bURR-MVet&=A0ZG0iVnlU zG)cJ+ZV_CZ1g>9y{ek~hUl@v}h0HhTdinK7hg?b&^(O!q!v6_~`KNnK)j&dtX`rLs z3VSR8OH|L%`I(6rl5{(O@j!+he*e}yajpxod%vjwI8c@{1W)io& zohl_Q?udaXi+R|ryKf30L>dG2B5A`f9%|RUF%K5DWlT0j`o46M*?$evoOk&W& zKe+Qt+NOTC-!SQ4J+iw;af)45SsOpzMm6fFZpJWMI6W9@PNS_f?9^C0W@1l;R&G2b&Hywvwtq*!aJgaR!xyHOojV{oB*ja zgIq_>9qG4ZJ}P>=IS8POENeP2v;ue^ogJV@I|aVVM(oSnt#LNezv4LkjCIbCA z(I2lXl9~y$NISy=L)=i2%}QG%3PlWC0YTT#BY1)iJc5_9zR208%r=vX3{?}emQ$%H zJv)!=gqMwEbwa&PbEn?iU#NUgTyO2OveoKK?K8XNjMIzl?FimhPB4S@1?T=S_LMT- z?nFTRjtI|>dX}QASABP?btiXD+FR5`zH~Hi_W@E34%1&$FSg3k?6y>V!Ufi&tIe&x?4UQ+A zc7hD>p}`5GuMuPj9L|X3>i;A*=&7SWd8U=0Vh zBGYh_4(dZlK5xcKPn9$ZjoBU#Q&oc1mt|{Z%e1e%y-&6YF&O%>i_sEco_R!#Pet*` z{P#t2;``>;YYa&oNBYn)2Xvv6GcuFkDJW>DqU$R|U9U9DSg|;#2e=I1c++DdiNF+( zeOY_lQ>I|foEsS3jgYFf{-U&nG`;VFc|Kb!-2u|we%>cqrVQ9dtyTQLhDqE`b^hc21<(fS){>6G*aIDUE&RQhu){^{3k3KmG(Bw7blFdqa!KUP%o`q{BW8=B6dwQHI1;^wyPxa~24gMd|N9gozNZxE}{_ z;^h&k3g!Rm5->{N?@p zdyRbhQyDc?XIo98e}?8NaDmlZeNP|1c7uM?tlY{zXd8^M(8q$P!xuN@6gTO`uqcE5 ztxm1_xwRD+r2~;KeN7*0e4%1IA0-xl*sVY1NXpS(hu|Ms_*%uJ)WIrUzGv>MBr1*i zkZvhxjdjN8I*Rt_#+l~eJ6=p zr|LdlV-mIgU?Hy5=O${!m(}1U z7o|pr*_04d)I&5&DC~#NsB$pmXnOo^VdXnFP;U?5=76hWjz(Hhe4iPBuY}5(-L_k) zBWs*}vJcdm+26YMgW-kLmEOPJtHw8*49t)dW5<<=xQ7(DbqwA+_Dm#a?a+YVde+ZM zjo@y#mit%+h5|AiaJ?HUrf*>xmJI%DylqZ}`IWJN1YR%Rew(UMSI{4;d5IlIlc}Tl z#r?0Uzqegr_2`(9t?2Qx{Dhd1qHF1!?r~0kkTbtHX|$7njk!M7feVPz|cOPq6UO;W3;dianQGg-v^O5YOm* zbJ&D}Qa>J_G}k+2nVg&)J3l-~vi7$(Qyh6!2V;e;WVW?7D@ovrM>UK0d-Lq9Z#FRF zKyqjC`c0#wMg^BlXco z>GcI~tA%nfRka0bxwkPBI>S;!drTM4qYP2TNx9Z583Sgq7}}{rG|yp!Op6Ne<6eA& zzn~XgOR7Hvm7aU49p##H~CRB^ZSq9kTU zND@|;yNnm7tVqeXgo9t>1ouY&x7h@FX{_9S)yA$*Y(F?2CfKF zCmb@E&&J|7^RYRp*ai<*X~?|p@9S}jQ1~f#+*T*6R4jVrt~k>miGo;DG|(txcMB49 z$`BEwG2OjwX0(IU+_oaIDfZQHI$%(7=CvQ33k}epu?)NW^U0yw2+voCvhX`g{W!Kz z0n90&P69XHH1eT%3(|#aA)|BliK|rIW9yeBJ8Q*N?XB}!=lgr%oP!^)6Xdf_a9Z62dJR!CdIEd zC#@41L5-tf|5#_*7_0(0~-M_U8TJftVwx z>l6WuZ>kXB6+dPJI4%#DFm9RavZq`q=$y%-j;4tajJ=6%a@Ph(n_24_kRiMljF`Z$ zvOO#RuCRuKg{w>g_!R=M#yCo@2qFlXD%U`~<{RtJZwijU;_8vTeEhqWDlWOAe>8SH z8JjNlnJ`PM5ho!X9(`ee1!1K6aJW<vX<45hZ7Mz=v3mEI7BkwEPiuS z5!2I5`WUUJbU7pMBqtK;>wmPF;>shW$p+^zS1##```X_eg+F}U4r{c?<3L22Hb3}mJB(P@wlg=_Pj+~U+U8rNlj}IABcfBA z`}#vzI=|Png$9V00s0iYte4uV&ktV!{v0b>&Pgqxj1}JGj&0c+)+;j0o-Ye%L4Il& zU3@)4$`s9{74uYF?u(5|(8Nh+&*j}f-6UZ*<}6&*Q_Qk5qx`)Z3=Dc6%7UoI)={bT zD5qG-;MwwVQPQ)~{GBB6P;>Ly-jxq3QUa46WBf-N|- z{z>YonSHSUxpL@Qo4Z=ka&dC<|5t7O(bBqEiBKH-r}h`66YpJDMgZ-%4hxdCIzQJw zzK@7)8lKFe7akJ1{vGZ9)4}L5d0za%nnmm~Un!@XS<;$6Lo=|XbO3YC#*}_1WJwFo6j8NT-Rh!_MmE(Mn0quU4-6&T zM>K_=T^;W*>aBMpK70^VX~@yi2i9-5iMYH(Oz_u(f6qIoIn!NrP79>6IS2(W`bs_Q z98qK^P5M1vv$?q4XN1;sLjj5>1o8!k5%s?Nr;2MI@}XDVh1AJIiQYME^`djJ(C04O zX0&|tQBHiV7xoW-E?N&U+?n$$7!MOsu4eh%( z0ggSquk)KZX^b9SJP3#T$iA3%af_*{D_0W{>9HXb7Wf;rzJ{5RUAE}VA1%h$;|X0F z#oDz$@E4E9`P2C8Ov%o?)NYmNFw4W^I|&ybLA7sEc3JmJqZRPw!P6BFXzyQn-53V6 z2M6Fnubyrm&n`DXXUCa>utIzD!#$I%u(=_!#aq#-sPOR_y7U}48VHR~6L>stL5^&+;m;1#Xc-Gc~DNH}qMX1L)GRG7@;kwlU*Od}#)FVn4>yycH?b#;y^%^nA)<=L_fuwZoE+Na6#9 zjb+hhms`75ZIYE&{IA1NDx$>1%l*Y+_S2RySk~F?(_wl$hq9yb=U$VcBP>h!XC8QU z5h`!Sg}oFTjB8Qq;sXEjR*BAuh!pz)gI`NwzK9s+YNcOP1KYWkb=JAf+4;j~zFp%o zOJCX8>HzFT+-LQdJQts6c6`;&!mhRP9dy=c=dlAuXpF;mPb&W#ZkHdunH)H>rV^wi zw4;ine1{4=zN}-r`EF?Z;aSxRX1V?AT40e-1$^|G>i%Z>)`EIq?$nU-PUnGVeP*HH z#2v)@{5_3~LgXe3iqr1=qcd@ldN^^B#t9635%n>KU32&}mAikLQOhW-^wf9BhFF&J zn{tC9>2$gI$e2HX9>SZoUJit;ncVz{Y%QqwO=G_dSUf zx?FsZZy-Kyw#2=U3#nIALe7q|6stY;RtI=-{|vEt5o|5(L)&HIBDudY&|m1Wl`M_j z#>>$NCY0p&4ixA`2<3*lJv*Cb(Ca2PCwrUs8ZHP0MVM7?2-B4CL@TWU+IP|pL`Oygo}1a@iJ%drm3%c7u6`bQ8P+k*3r@X8D%*w*jRT{$!Q zv)DIo%fqVf{U4yr>(>`tGY>D5bIrE^3}Lb*X{mn==7Sr1lmBVEG}+>vFGHK)ljwpd zGuZMF;M`d2};L+b;lrAg^U?lGUDtFUJk zt{H>A8KK<@9P`8a)DSLcW5Rm(^N`*n56{|Ub2Q9*jJ`bM;R1FYWLRi_Al%mFEX(ZO z!NCDbv>6iJsDp}$J#0|h`?(x~?>t~kC*?Stf_1Tf>cNK&xv>bLTr(J-=|Oq?zeN{3 zq0`LK;?qj&cBw>o)quY#s)FL4pcg#bYF+> znVnXTG#6n1$L7uZu$sO(tnPj(u)Kps=|%p*>ee6$O6BoY#8tT^pyNa>Lz4PMh;C>^ z@5$)CwBE*1ZJAHKKAmJND-txceC`Xh_YUaX5*IIN$0RqB*!!j*Wk{W6u0tz2X1M{y4Uw)0wJVQN- z^c6TTn7$8uxvlZHlLMBs-DIyf)CbmqumcAuNxhvWg=3ScG7_tPtX1Kp3x^^DY6sraOL4NVvrIMc>4r zZ%39^z7s@tfcE8XW$hdc*0~zV$v#w_44?F|{I!T%=kP0uBy%g@GjlW}q6VZ4Jl@IY zBjMy)J86!-HuP%>;CE;|>WvI~yo>4O#bpN_3F+>p)3ow-Ty7RdxJ7>!elg|%QIy!T zyKN0Q-n`bAXV*H%KeCy*JXtlupS2nf$S>9IUa z3)(f3EI2QQQ+ywIt5bszvg>tUk&}P)U?MbjxMxUaBX-`$0Fo~8tDUX(`?H`w%Y9vY z-Ye9!%P;?UGD6?{=iz1$7Ia4d9-?@EM(tx#^h9rg^ON)7z=+h>dEYgiD%=mg-AuYO z*|zLQoYr=2u+_IV93|MU)US`Ts?lV)Z3Pa9T49ViG=ji|kFeC;=SLA^Pf)p= z!^*p~i&n`E*FwtoXMUOI_eNm?iK<3d(dleQCzGSw*X-~k zlXrs@E=PO>r*Z}4(hMpyS>T7j_A&m|rC~i#;zY!e-k06V=g&4|-eP6C8V6+F!`yt8 z*HtKOpKB70V+AH3%po}0 zV5vA+3GlGy5nyGKtFymt*U&q$MR^kDi~k-gnmSf3DE?m35r9;|C>kEy^c2CAf0@o+hv{%#a{{UfohZpqq(E%JWaI6NvV|FxEYEhrlhT}^( zPe)#Ltx1_?CEsHi3L%&o)N@F`rWCKK{{X)HTTUfF8P}~4)I9JW6+S&7NsziLLlBdt zt?k|nX&KsTO1|jYekhQPBGvSW;v$bc^1Cv zRLP7!ZhCL*6@$1dD7`L^OVh7clPi|4YAs9QMNHsD%Bj2#;6<*Vq0i81<=y$0P8&f_ zNhfCSmZtA56!~S+{Wb?=d04|WFG#@S34|3-1hfUA%YRGO|5hjbKdD(fB%u4Bx>>YH z-bzI6FTYXEX~IKr);cM0eEnawIc)}R2#$pRR?z(aKyekb32@Ln-WU2$g&eC0)Hd^M z6}X%u<}13pWq-I8zA0oHkWEVL4!!!DHk4O;UEt70C`>pbu$jdX0bz3s+pgu1~bvAKGjV{40E%luzAAuta~DnqF|EJw-DWmSLay zHXo-$pKk_9`jA)MTgUTBiRBjW_hF4~fBn|Wg>R;zh@{agO90~Om22mRn^HrUg|qKJZC`SUGnFWloAQ)>FmZ%bvF z&5bWo@z<-Q9ly7G3gw7mX*+bzQ3KctEY0YK-ZrT~B!MsM|EmHW{^dMrpXJQvUu7?+ zy@1~TM7MD7ZFL~dz}j1H$l(e{pK;_g25l73lAoM;u{vOBJMzj=0@!*O1IP51-K-D9 z;T?DSbMVx(ds;Vo`Ae-f!DVpOwcT;ZZhXf7b|ZNsh~yFPoa0fxs-0IMZ+4ZZr|q%i$psM|W$xCy}woH%nnO|KyG^ez-ZA@;J6oGuq=b zw`L#L9LBe zk?pHi0mHcWc?Z&#q%KJ7Pi}k;Yh!owvVpMs|<;U^GZB1+6EN;2DGn`3t@uh>K*R0R} zk~K!LpAasz3+MB_d`GcPs9(;T)Jz`dTb}5QCYZZ?v0E1_H=p6SR|Z~R-#6pJ8`}C; z@@7}*JXWE+^nsL*W*p>fe&5A@48jm^-Naa=-UxrQ6o7kj<4MnmQ)lzb1>V*YqYT!e zUCXAT=bAYW<>O<-hZ6rAz53O=Nf{+xB#+qKez@H`Y_kvU_Ihb22WEu#d6-u`U??__uZ8-&U9Zv8gEx&AbGM_kXiq;nT`r z*}pFQWcdA+;BSbgK=8iQ){r*ID}K6v+p+#_y!*eiSU$~z%Fhy;>jsa$Ws~AmVYd%b zrP>6^6~G#K@!oWsLf3U0gfSz9xqSQBfa|01-K}pv#5lW*gV+A!gZCyl$qoA$6S?;k zaq-k?;r@?%Ci3qgJ9te-CKO_-Kx6X$NpVP7_kVpd5HI1@@Bv;orfqkh^?}m)B_aeR zz|7;D-K~I~uk|}M#Oa*^I3A8lY?{I{j)6(cr8nq5)={AAo3&Sro?hS55G4UE9eh6N z&^87&4uLxr!Hb24w0M&4#`%!w$(f|BzbLz{EdK|4Zyglpw=9Z=AR!?@AR)K}4ek~! z2?+!b!DVnC+y)&IG4)VZ}^y<4x|t5@gT-#=5| ztkvDiy4TlheJwO(w9JnGK||S7jmEDysVgmG^F@|5XObzU#SdnzZH(G>2z>(3BfISR{R(WhyW9(iarBdbL_%c{H+nsG+|eB5KRH`? zsg&`^(t1-0cySPaX8hOk{$USzB-_IDMQxnx$wHm_us7bnP@c{g9?fMCzLJx@11KO? zv@9tl%f&BP{^0CJ5})il{?*D1iGH%(d85nEJN-X=)uw7`K*@^k^PNkqxqckF=IK<2 zbw8R%U&?2&ezw#dT17L$KbX+|*}vo>R=l+%5`7~1r&PoikT$CRO9If+^_v;rC5!H5 z@Um?;_ZV!QW=Lvz?s^rhY}0_c5--uKMV($!!l&9M zawsdE$1EVUWL|t@$CO&$LP>f&Ekj8p-BG(XR|{`}Y3n(0&oOHg!Ex(u;q~)eS*vbt zTy7$`5V(N*#HecYI&Eg(w4g&(Ng3(<8Zyk-2SVYn?Qqd;2`OukY|Ou^MJXhSLPtgAKnd)z7yNk#IN$AuJGJ_G=aPvSrq^p z!+$HUm<$D-H97I3owFyd8swKxVp!d|H~UjrT;_5Zz^Z!tG>5_HH8TTk3{aAKi@*t= zGNPe(Y`0~>(yvG9< znDTgp+be`XU(C0p%y5ecVEN;~LYOPKQ|UNi5>$_hVPGjZSvcQcXP0V9t3$12Io4l# zz17Q-`CaAV{p!L*!?Zrp<57~%%NAs0xUrF}t{5X|64@@r6_wTQv?~iLN*qUdV@K;S zP|FXD;ZI*@Y1jK@a#`>#+v_Qa2MuvNo8Rs2sjOqL+tF=GGq~!gt0A=wE5g+v@E(faD3@PKi}wM(;Fj_la9b zcpsj+9_>D#udVu7(|mr^y-zaCWwA4e!vOOuNek^$3E5W}yL1E-A(I?w3J50$lKLXk z+sLA>=JG7O_l? zrzK>1QuNr_A;ztIx!3xt?7J!T;aJAz2$58;R4#L$WQGL!OY=7cKgyzOQITmUv+W=m zpG)g)KCV+A<>u+78=Q$*5xe$;T1ehqCfISJSMly@(~=bY%Gq?6n|q^luWG8S4z;VG zr?l}wf4$7}Hash)iA9RLZ@T4cjmUxtq3tVucnZxy8;e8gN42NHUzw3B#S!-1cOnjSORu0(rv1|GwIJ9^9y_u-C8=$D!df* z45e1uRlM~!yzb{`)xYXn%|fPxEjBK&Cj$n5Pc+{hM=sD3j?6o3&+&V$u{XmeMTeZ> z4m=HR`Idlo|Aq?)BvJyjbFyGe@XN+$8o>>R^IV)?u$5m@PW(Wtb{asUv{nPLt0@Fa z1^5UiVa_z*$6&CxnQqi1+wf>_QIYT(J&Ta4zlgLuosw>!)U1;tM}3@5d^)zKThMwV zD~!JW1}|5u!cTNy-_I_B3Qw1czbr*TB7FvhcJ)evWbC-*7l4;*l^O~W4@?WB@M|Hu z1+)_F+Gy2t-Q(5nCGw|4eY3&OL_c0@z*ab1it@y-!lW3L<2RyDCHNPmYBVK6JxXl! z)L8ocMQQDUl*5U#;gyfskT~!j1P;4Y_A{G|LTj zJ`oZC%Y(P4hKwizrSHnkn+k7PEYFs(qa_@Zuhd^j@v3}j_ZETlBcQvp+ob!7rm=o1 zVu!GzvVdEeYi;rQ^|%*)Y4$&RGshNDAp2jVkkK46N=Vh#fFX#BymGYsJi(WGo@(lB z^yAw89(6O6dp-rRdFM^8Rz`T^@v7eU4Q+o9(wBMOK`2uDDlW1n+5xMK^(w2p;_~o% z?zJ9m7EzQpP+&pGqpY10wDO#A;dI3KE(5$0dh63HzH=6NCi9zrK66JUS*G-OycZ$8 zWE%mC=P97)#jsmvXsYT`)C(d_g+GF)8Y(akCS{*RCoiS_-K|2 zr7TLSDN}g;l0Tnl+o#s+^mqS~_e8qW?(upMib3?-m`ky~^Ic}tl(;YtnSb=prC#ea zdyQQcnUQ%(+eNMONjSUd9l=@4^`>>hG`)S*)D58H_a_{#a?14a1!6bk<;?!L@M(&Q z?JEb>_O(IZbmz3_y8zWjr0Q`yIa!DzIYsS$@UUu?p!N`i&sr{#DP`En2pr zj8HruhSA;dLt^PzrPUZF?Soi%j(xvB&DfYw$8xv%3@yty5}=j6TTuT-oJ~C{LAV*u zgJS2b;6m?g`USc(Cbk)rt`MCXNp%3(cRCM5+)=AD-Q*L7cLg=%^By1@7Z2~JWN21@dYRqt{-ZX zd3L^|!!(foi@_Zc~eZ5u8s4Pes?6tn%S_hfDE)+Jh{O0~F z3DGjh(Cn4m*!Yi=XvLDmrCqD%)xrVg#8fc2u#7v=aV`XuNi1SJA4p1Cw=vmTLn(Ww zXT7sa*z9So#E-vhyMhp`O>~qbC48y{%HQ32ryvIdZmWoC-3fn>lodec$y$H~=7EH8 z^3^k~Cat^f^$C3Bra$ zf!w!IlbeyC1{go3anl5hwT3WtNU{PL@{oA0o-EO>wWgC36(^0HlK`8v{YeFe5X(0Jq7u;5UKI%SRA(&o7OSx(LT!4+weEYvQOmcHMX z5tl_^(8dpI7jrnH06~iGlpCve*qeOx4Dl!T!|Mt}eW&)fDDQ^%eaFa~8^SEy^f&&Y znt(WidxGm`e^@{q_IDPfDfwH_92+ir7q!EEC0{#(*U0O2_ZP9&xM^|a{4S;yKls6~5SCIUADT)Bf8F6Cozu>>fw z6yV_J^Q`)cxON(ew%7Y^>ok-fvfmcGZph|}GcRftvt9aZey6;r(l&JT?pMZ*zblxO zg8RYU0irbnO8A^|-DC<{j!vMl~pMFCRn7F^e%B=REXzDu7StqUI@mNgrP-!9GV={z3tV^@;0y#=3%q=p`;~) z;uCwF$H=?SneE^$o^Z-)_mE!ug`8Uyd`b(Q26=MwFOLs2i0tj)^zK%`Iun(knC0Ry zo|MEtzcdfGF1t7O;LW@$3PfMcWCzwOv;Fzb{4%F3PCl6qeZaEX{!Yw{8}^0xZ;<~M z^Fr9KNp17zL(0Fo9Ql_GN_<%8g%GfeGsqiMkgg4)wOFoWhy4=nvB}Uj3eh(`&d5FhRnVivY%O@v; z6Q|RjIV?9Mb=+PdwSZ3FE&9H|wYp%XO#T&2>JoK=lo~GT_q&`tF1ap{ykWiD^IOK~ zOb2V5XGq+P4X*0nEmn-G$ZSs@-_0YgRpYOvMy0ZDQINL$r1tBpi!2pCKk%jLWdTKP zV`p|QG@{i+a)tqHqz?NiwU5dZjZUkP5#??lWlTvP<+S)A?a9BX-;#Dsg&~mgjqXRMgAZ891^%yppHRrV#c3@*DyXz)CuI&Dt$Ci0K?TIBK7OG;;`26|1#j~gD>S2rEqenps zNEJu3yb5kyXQUAB$}?(zs+HuSXVsH9;Z6m%-@my%(t7zEd)y_@sV7%_$iE@x_`*y2 zTX@#SPmE<-*N@C%xW=d`C=(8;T)D>h1h^hCJo?4q745kj7kg-t z-P+#rcI-vbH#8Xd;geb_;aAR>pJwey$9Y*x7H^870yoxquZ&4~?S`)+k9=rR%#R6{ z<^x+xL%!|5EFocD2o>dUS}|Vn?peWDNvqu8bG21iv6ja0wyO9lgFUqL_A&F|TM!l3 zJKCUw%Vr>7#~5oY8{H;4@1iB=i(OHp_!f~N&&SodrOs>Oy^VekW6Qq92?t+WB76s5 ztkBBJ$XO%nakJihn(3E{8jkhrwT!xcy8M_~C-VAfzftT||J}LM-U@9<{(uxO4aWsb zw$72tI~s*swDavXzo}BJpULasm^+d**>88T>H?dT15(D0xECxttJvOp@u21SN-vJZ z@E_knIG6X|fI_K5>XZ26Yif^UKf|1?CqiEWSvFIlUvw1s2I=CCJBH}GV$7g)n2lfZ zY)JH9#JtD~dLHlPq1I2f^bJrgLQLL_V5`O<77M_68+B~W2gZp^{BXu7uvR-RSD3+) zLps7>LPTz=E;qTinK0cWW3vF&`(evexs4a5VTu}#Dtd83>o*c?2A!s-WUHOa)o;&| z!V>s!dUb(LIb@6eBXYu!gc(I{^19=?&fGQvZgd~{sUEYpdG*ip+#8jd!VQSA9$;t& z*EYTuRU6sWtF1SR#I;Q`;MqxR9$)nmvH`}hUyrv$oTGG&8-87n$; zoFTgb!7m$G-(y3Kr3|QioaQ%j<<}}z1$zcBHW@^jFN2N6%ECa5r$cR23to9G*by`_ zE0o+2m#~H8RsQE8-!CFMZKWt?W7lOv$B&u@Bs?WCmDVmq1Txl_$6g_VDC#*qZ^NJr zXoCdCl=Y|mW|^aOE}oJB!>bpM`I6T8`TJMV*Hs6Z>x7`o%ys^;yrXNYc%@VCqw7LG z@{iMKt-6Me#hK(YoF!I5d(_MKLgb^~vp{u14J(EM60G~F8c!Upbl-I3SYv-7d!qS< zuEl~R@X&OQ1dD2BzC&-MoNjU2O2|p;39hZ~8>31c>@T>lPr91fanZ3;|3J|zyY}uS zK>S~yqjRaWxM)53szJ^dAG$z-WuwT$1YySwTrdjl0KCcmO_@3b@$cC2Pc}hA;kLi& zS>t;4M319*8ta82I{F<+n)brPH&cH$p@Cv5ot@S&CHJo2o#t`rXKD2Ot6d;Ap}-#A zFnnVkZnHhgXO!#@RUT)h`Aoy`YM}xK3a__KvgfJ}bEB14W!OxIFKFKYw$e*yB`CK@zn?kSiwtIz)S!-BOghu5v#*(hytz`Kp zz@RS1ofZZ+DxB0`PPkT@ea0;~4_`~c9IfK-2~dZ8X~S_H&2pt01S(G3ydvO-WmitQ zQT5t=L~Okp!B~R3rDOiC*cN$!WdwI3v#l*I*cADtd|wlVM(qd|Y|3B$IGxQ5ccr>d zS-xeB#-BIRP=Pxoi(r&^HAhKbL~5X1sY7N|UPl&giYTUMQ>aclCAU7B`kETm{gbYk zNRT>&{HZ#FjWd2ws}=*B^2QUR6WqxDR|w8Wzj&>_$^6u&$|d5(IqoX7AOGsFh48U= zj+P0Oo}?fmPy)USgy$7|!E9-Ah=Q_LKt2WU-d%rCrllIWj@xUXbp z=va~2a-mipzvh^7Bc)pv=)013?SnpWD6N=#ydz)AQ6(j&P*_S&^0b6^CiBs!*`Qvy z$5$Uc#efc~hzM{>5gF7RJta^YQo%aWkRAho>2X%lE9Hlo(G_{-&jH}BswKW~E-Bup zp{&lJi%>Fq_1NvBr%S?Y18x`A*Rd@_D$DQ{0GOiv<^Ivr8rP2MGA=2iX)A$R)&LZo{U(`vjd%bvZF5Cv^+jjVk%&?%O{#1ry3$VnS^Wx;xj9NTl-$@`-A3xp4?zWqM6 zBMgxtw|5YWmd?X!ILlzkenT)rIl4Ud5_>me{h`%GsH}Z_ty6wKa8ll$+e2+mEVgm;Uf-e zsXQlF=F&@ZsTs)a1TTrD)TjmDCTNF8h`V8n8x0<(E&iknpkB)}(V~($4T=?Wj!z;J z<*j0RDfNT&9S7%JgT35+#xy7fl5xPF>V7I`zj2GC>{p3X#?J>7JH!id_j-XGOIvdH zFz}i6B_0~(6P40f%iSZuZ!p`IyB8Ii|Ne~ea|e#xkiGIe?y{+Zs9@)5YX>-h zj?xC#nQ7f!@pcVwjCynCcHI^rD`1m~X{t0bL@4u9=Bl^Q#h_zK0`jX4wQrF*&u1fL zqcl%H(q($s7&U$v47(^h;nwwq@I_WFAuZyq-e;+s(@{DCEP8t;Drn z$^0Dr(3s&Y0@>q~(M#|(MtP4AH&%I%yxq4eHdbaY1^3<4I+;Cmv?^8LN@k}rj<*g{ zMEvQ(`sW@wb|SOu#K&=LL@O|Y+O!kmPE42&H<6ji6UF=9p>)e<^Nf{nf^u>R`iCd( zmnWNNtDgfqU$D|0W2QVlxWXBU-#o<@ZcOI3>lbMSp$h0NC#OEG(qUCQjTOP0=%aQ< zs5Am&0R3aiuoU`;QkPx>W})1#41MR#?DAq?u4h&{YTG1nDoE`E-*X`215~Z}u^*o@ zPuO3M2rTo$cGZW}6H(|WjMYR*EJC7Q@!M%#x`oht0c9;g-zm7BB}vW>i)*^dc}mBvIs@ENAdcuoKa+Wva4gt3+f=SAbPhWYd1Of^YmoF>?zGzVvw8ESceIJRd*!~4 z#1R@%?T?l)6R`Qh<49$%U0*buB}>*WW}!vPNn0tpR>=22e50&_@V)P0%G`s5H5~j; z1r}IGB=S3Af&nq;kZ04%P$M(%sE^4Yn_z}CMZnn8<#g&57fU0NYP>P`2@-KC(M(?A zF>G7?o0O9+Mu~8+%~+^u(~1lMz~fut3RNwTLmK{o4o~M72+Lf6A4J|X26Pnds$*| zpW@fs%RGZMj=nwT%QOQMiZ>F??X%SFMW<~}!x;5ygibSfyE+Y?UkmltaD=~!H0@go zX^=KFYGnvGpW1Y+V2)zI&C(SZaIKQ=YufNHpwz~=EjMC6M4=W%t$@G%nOk2>_}9ss zj=p>Fp2F0rANxM)DXC3m5$KZ|wwSS#6EE+@cdZwtdOp)GPhD}FvbQ#6=)8Tq2jBW1 zWW9YeM2@Ap@+9!iacnJ2HIyvq?%UX2SSuo`%TL5=7e5g#98w$t`DgJ05_Z}2SUzTY ztBy&cws8(dx+y^zPSdZ9RS35Fidj6@KclSlQ?N6k?9R*Gqnm0Fpv*!|Hc-dIOLoUv z<54ETJBxsWhu0gv@hwqQJ$NwrGVnPY-PK(pVzgS8?kTV9sd*^eVZ^y6i&WQ?XX@EZ z|BlNqtQ{xa_UKrShjvxNNURGnDx^oD;~d|o*jTvmgDX-Nb9E2>DdHmDz+dOZn2ak_ zRHDILqZ2Plf8%Y3(c8Inr(m8o%+w3qe{p!PtW){y4F*Qr(uaCh#nG>XA~8U%;>)C{ z={UR4WB6~v#eQXlHf!zQ!Kl?zB`Tj6Re(TOHOn9p=2T|s^^&-{tpO2U!q6kqX~~k` zAA~5yg_v_*I`p68;Kcx*X=?ZL%2{$?4(m4khrbH?&;vG(NQVrro)|t-MB`NuNu?f zOa7!c2|i9;xS>!HG0>SI|0{L#Ntev`)3cnq4<-g-A2*G`SUtn4#SKZ}_4Hso(4g5+kD?Nq5u4SmNMElUpI?Ht zqIS(HsgvD@aJB`|;VCyjoRnzyv0?q^2j#WTm);W~bv|-`P;~|nBY_;;1J+zW^I!zM z=5}%}3d+pNAGu0u(!-;=WgPewHF=$<9m_4kzaLZ8H#K3ys+aC_rX$!KaYnT+kRpENJurS|Kv7hEW)~&-troZq43%?d8j{yb1#S9duU~7Q;a0Jg8b%Fdx?;R*p!E+rZR>ZVCa^_ z=85_vqqb@QBd=A=S&-dtj3vS{`PeKewC#6deW#=ud-?N6?z9703(FPL-XEcpU4!N{ z2KjRK4B(tOEf|m~or+(LkorMe^Aj~mr$%l1Obxzg_Q8*CTGJ$b1{dE_HJze0Ko#$6!w;iQ*b{KD&vDMrH*bKpi)HfR*&<+^F0k@KH#(Z(6=P zw@Amr<-tqUMjbzK<>DZz%v-|Dj{KMZ?!txtKQ3JUUkOvg|3AajhzRit{3}Wg-~WA- z8eU$0zW)@ZCLQ6vBJ@Q1>dtRQ^sp9E_wAW*QAe@2p-BTH*S)VX5^@IIJj*Nsu&wSI zqedoJ!Q68oYDtG@mUw&8KD_SofP>6F6lLAeqwDLDXxLpQO-dXn`$c3-Fp*CD(a$gC zRai@^bOf%EOt{haow9)(Z#?8grsnX9G^JE&&?! z5uWDV_|EZccq%0{kpHkZfs1Ph^v{%SU#LUayaQm_+IW;<6?Jf;b`lkI(&DduXFL^z&Y;gvmk0abYeQWci?hkpl z(dz-0Ps{3r2Hf4ZBY9dsmiVxB1h+jOc9)Y=aCg-_Vsvpacng}UbyG5_Um=T=M&8ac z;xy+6o!}PL#oJFqGkiEqm!@QjEhNf}W`&w9TFd3c{HVX)mN!vx=*Vt{a2&&sf_55e zXT+|J+!czj`qc6!`^HC{%m^8{$YyGoc!aA`5A6_~O;-zQ3W&SfRqk^!1-OuuvPoMY zENt8Ejj~my?e3=?6U&FouUFF(BE~F^yglk0yDrBJ=3H6Yj~lpuyL~G6Sbq(F2EO(( z1a7DPqJ7AMEC%-^YOAut_K|xl!s+ zi%ls2z~yAKrTDrwrFfGss$8cGI1itR_5x3Yl{ew;qIBf!i}`EkPTZizz>6}?Y0b>< zCisznI1d&vq^7nevkstlTrk{CiOq#`zMVJ@=BlJ*waqN)d!Bv`0K}E21{g0aDdg!x zoik5gAEF8^7GcvXGJCvxh`9WI^dv`0$qKjUkNz-y6My&4ZoqbG^g)4xSMl0r^@VLK zDkr?PjTn_hv1*g}vjzVb$6ZT8SE~G}E7{ri$yD^R9mFwQP2QQpPkNdvmw* z*D0U9auDRyb($>!Fqd0GM2|hJS5`)K-O5Rp$8uL(BukFa3{)XS+t%JFlH6Cpt(5h2 z#NYPoRc~&mF5?2s`DX>S>KyCVCPe}?;x3?5Jsx#um+y*zU;w0B3z^8n8SiWheQS5UZc0qY5%G2!-?$Z2n0LZL9Yk{?S9Z`C4tbwYo*i$fYXyDvNj=E>N zObMJPHdoXh&$Vg@H?QhoyVux7jP{j^Yp(KUJez^`ZH{Hq+T!g_B|f;w$OUE2-Cr<- zSD;-US5TvtX`AkZ0uC!cWY3DAQ-3)`9Hfulr-YGfEzAqX+_GM~VR;ra`c_Ey!?=fb z_0r}1$fgJ(ndY_1wp7jxns$2BFVIpCN??b+q5i}RB16?O$^Mt5c^d$08oW1;r zluy!;c4qB`ZDXgLd65gOmusS26}{glkF~y&UdQdG<_vJ^@a$OA|`SNA9(z$?D7_2TlXTN7z<4y&x$qTHkiEf-C%GZDCpt!gTA zofcLzf2*gz=aN&_QjryH10c+lvLV~rnwLkqOI_0JLw#Ln#;}@Y^3hDB-U;X`_Q z6eATP)RcS1vRQFVsS`9`&Z@4oYc$zxv3xc~!9S^9cIFE@w7Gdr?c%+HX4$DuVO0oH z9@DmMBsya!b^k#KH$j7@?aF2O=-g_~)&q*Xb|5ikU7gZMcSGAdYa>d)Bzl|Ou~1l9^?srHyiY|1 z2!qs775)K4~WZv>D5J!aB3n>cT;CD^zyUTXVkd1(Gpw`pMUGAtJaHH zvD>eRZ7>KF619TL6z^q{qI<|fk;>UL8@B7ZS2bAg>u!T%nGkIjv0Kpo*`Ol>9TIc_h{0Vc|nqF-NW%F$=;P(lFzFTwX;D#XcV zmSGgl7RD#d`6~yf6PxuVYFACo5>Bp4sJuF}Od4&JJ;p*A6rWPZwKYN&W|$9#*6 zvSdgOyRg z8&Fdg8$RK=kd@|PG!DVuk@*gM{iQg0sMLBNcmmD|JWZdLU<{La>SS z+|*(-^%f(Q>l#2+l=@5D73$A1BN&S0Q-OsDRs9^yh?rG^uh7chdRAZgm+O3GA?Z&) zgZ2%4UJbU{Nj>?rkqY~R?CQWnj@4{o{=Io^5>HEEaFyqdU)`S7RcZU45-C?XKrxXv zyjhq*unbcGklZ4HroPj>=GH#|B>i3@#Tr*3h!P{;N$q|5tOe**z#eg`50Br_^VX(O zz8o!JYqP((T8FjdW>nl{K%&z~kM-fhcy_`#0DezUK)qXFV0lVkp4{DVqsz7nmI2b6 z-@PqXc+J3FK3p{T)N$$PFdHyg8-hp^LuiH#9(b+IoTiT!i|2Q^=eFvwuJc)BO75Y+ zJhjv{P&Czc{zyf5O9;XnIqhzswt5^1aA}H998aZj>@$7D$Zm411a1BVB0xu~%Wm$w z@P)1hZR)Xq_b-oGXT^@1wDlRrL5aUR8i^_<$L9p7xftJ$Jj!r~?+4?NZp+$aAsGl+ zzxhyG>%1+(nJLM46e)&Sc+{8Y$dK)(UgtI8Y@`>~`p>RwxYV_UDpGEtm9R>na+Yn< z$HVJcP{8mF%1=^gM98j?s27rdcst@bqqFpEQn2LZM}yT`&e9h$EZtJlo%jAeF8m*U zH1cW2iKtZm%=y1_@O-}Af?suwb8qm86A{n-bm^b(J8&Hdh)6LQIz->;V@2Fs|GVP9 z!IT(qm)?UvaU?PjE7ii}q4wwOVh=*)-dlT<`~HJFeg-C(Zh-!pLY0Fm-T%jnPWqjQ zZ08QzcUpn*L@jHd8NQa{GBFfVIo{JD$?3v1a4|CPFT}-{856CfH&Y8FKv!u$e9QW_ z9Sz(;{6vPRH!|D#HN~fwT>=aqPzClJT}Lfi>MH@4`G8;eVUMsuXZ`hdmB04qyBz67^V z8Mr?F8Ta&o9D}A0RFrFffiyQ9KghxJD<`Gh!wCHn*u_9%aS_1tQIBKFobDlPZ( zc2UZ54vr13E_s=kppnfa!7S3WIoaq6yu`3Zpr*jvDrbUY`+1;2h{~ezH$Y%eOQMFzYLK zloUfL+q&4beeQD05A1^@mj_mC{(20Ve$Wfb(=pDgfiQk4)T8Hk&qz93iALQ({fU`yEH+}@XnGjGo@<*QkV*fJ|Mv(y-E7MCS$SGub;@JsuqRVr z#n@PGPSyivI$6`2vh#5}Ba^REp?syAO^SUt%XKA${waycT^3yT{!vgs8~{Z;M3=Obkrp|mLs{^#HPS(AGV}q76pPaM zNvkpTakteuKxY3x|M60`8Vh6Ln@J*W$LP$*w_pAg5<*u4;>Mc}foi}@uUjrGnz z4jgrt(3P;}3f2Gi3DYOJUuM{6T_5&gcbib%|1XdclT2`a@z|9wcki%&1n@KAaesTb zt$|wcT{N)>KVJN=d$emYyQ%rKUkTcmzZie~Lq+zmF5mnKy4{!Pc85)6-~u-m4~k#Q z;U5^&Og66_kVJg>hHhS*z;Cs-eT?tjDQnBtmp`;8Ju(pb)@1Z|>gB`PSYFoXlIfd# zCM_{ILyP+YQ+Y!a1x?_@J=%PyZ$~lZIWBCk>CiOkSve_Z7ClUVH~Anxit2onL3iQ9 zr(vzK-h$?Sc;EZg#SMaI#Z< z-4qd5=82fiVy^RbPv>g>b1YisZ=x0W8k63hfjkMzK;dnF!sABC%3Z%H*SpTMyVPY< z((M4WS4$QwZknXvqUz~>gY$F4Hp+t~F18U{C@c7~RLlpe3oLc4* z-AeCC+#@$FoJY zOY?k4eSU z5cRk}y@2KX0Je29{~=K-Z3=IxQThus__X*UQur@AkP;SN#7zCEu{r znBV?cTe(Ve`mgAn=;hI62#Qbtb3EH(0IwWMWgRm*)gz( z^4YnO;?YaSVw|*3&#r>X$M4hH%4G%3v7{$SOx+x@P|l$DrFtrxm4&4J=<}Kj1z zmY}YXZN~#WWsNwtlxzaQQw7Z*jgM@;5Z3Py4qOetD^HQTai%)jGw&8W=gGoRx+74$ zeDdBpjq-9;B#=#N+u`H9P$NU*JSL;Z7r%rM&xXHMpgna|sr!hbuZg!i|3jN^+bvaCy zT)T1BA8zG;jeqWlSMW4mQo}FHhdX0Ww*dJ4a<#D%oqLb!+>uZW;9|LRe6ucpnQ56g zD{b07z4K15OJuQI;(8MGyV6&x9|Voge8YY+Z?abCBb&!VL(XWIV3 z?v0&SpTwullYvcA9!Z^acCSlSF=fhG<+bonZ#LA zIw^L}^WpA7Z0^GIa#L+~n!5zsqLT@NMT@g38Bp**$W+`Z-}h`Nu_tLC$A|3uDP#U^ z#?tj`Vd}$Puv6?~DKQ-i*-*Y*&CX@Jp5hAxtSg>GW;=;f5>!Vu&7&v{-+?eXNgTd2 zBnjM4;=BblVxddry8`n`3to9vl|84pVv5?&5h}qGdK~wShiOYmL%Lo6sr$07Q8!N! ze>!JSFfYL!*WublFZiJY55|;;-IlKCCoOpNoDCQ6XHg$W(eZb(`p^5c%*$(ml#v#9g!b*`Wqpy=CB_ zn*Z@5`Oo;vEfeO)uY9#Uss#jS@clpkX&{ZSKT)ZeCyc4f!D;QwUqyZLh&#U*ZH6o~ zSKubv-TsWBGr~XU5>yn6)7)3cs0Q0|ng@y`j{*N6TlZ{Uh;N^d6re_S?P|Ry^ZScG zv5|D>%C<}uaHRda^;YWa4_3%SygxKpxE|%_08jm--9#;qnF=wnnL2_Ex2~8_w^dhW z-reg%>?sOC{_pd$v$10A$hMM}Ux%m)8)ohGbN;Y!-MEL%^wpxf7A#8OmHhh(wU?D3 z{ma0g?c+y(Tp@u|1qjUH#SNi7`Q90iSLWUA0MUh&38PRfQA!F*M*U7(4?9C(DJkYd9vT1d_$LZ@S*^W@Ba^_e zJT6Phm&r2z_TDdYnbjf!&~^MT=?Ga9jDVOo6PPyr(k4vj(;X#>w1qi@F$s%V_)6~s zob;YZ)6%oQ0Ss-$Anyx)v7i-yYvc1SR4`p4tD;y3#Ll>caq3BUxR%Qq@9){Qtn zlwwWa+k3G>Y=qeu@=f70{vyp=E)1d1-ndPFSNuICe>1~>0WrYrcseF{?*Pjc|DHGo z0P~*8VlnU*lY98F<-ap15&ZwkphQ%ZPxM~~CH((S3`(|BkuRUTL)~)k-njFbx>%RT zzAqA8@9V99A^>>*ewopmsxkr&E8UZv#q+N)Ls=URZS8fp(w)m|W}Vjkuf^B6Z8NTV zZNTu1uy^u{FC9?xZ}k7}Q+GE^AYQeG(oYmzUuzt6I!j%2of;Ll);by{(APbIQftHa z6>2QNiC@x6mbt3!7Z?lNuQQi^VMVW}P?Kr430<~Cg4Z`o&2;)I(1s@>2|_o*S|NbR zff2cpp6!meQB4h)hq~?i5%(zsBlTMUfTt?xx6eE#1sl3_#wmdsV$Tja{J7io2M_yp zUYS8}bX?v!5I_PoRn>G{zSI7!xoM?I7N{t4nWl#Wk_ACQO0T$l9NHt2Hhz>CRIgDr zSSoQHRbH>5dL`NNInNvPk6SFcd3Ob*h79UXic+j z%BogG)GlEIi3KN>k84hbFW0DH;&X9l4KHX?Y&6o!pL_$9tTCl)+T|S3&mu7FUHlqP7ZI(haaK7t*T|58r{R{OL(kCb#k1|~9)N+6X!kQ%j+hy>_U3pSW5 zZ6Y&5uSU$3hO`0x`b^tT&P4*pOJK&h`g^pj# zSB7nUx0TS+qU&>%w;cztbb*);(cEUj&qiThce$-PoLY=Aj5)cKQf+-z&CKoO=sQ$% zmbxEXT4Y=2Y0YfoKVhh*6SozwrD0J2L{P0OMo?uabpg06+uC9!3lmU8O0uuzGb@Qf z^<)F(^_TS2f{!gC4;2%KG|^6VrL0T+=xiyGt4&0^gr?(}rJKBV3tfnivpPRE z;7*|&)|H}R+K)QpbYOrpJcw6#eqHBZoJnO^y(FDw4n%l$%z%-zci zI&c1`f}jwn_jaDJ;Hekcp#yY6w?`ZFIrHeaOx>nisC?cK^@ZkczQ(%U?nI-(@LJbyT?#a-?oz#E#`t^O*=Fe=y3snY-}zOp z6VD`EX}p5h&bC|tWRYjlTRW*}u9W_wr__2_G)C~yyd?SjbY@tsdg@HDlv2mV4`xOX zS)Fe&ciy!R;T>o6%LTP-vld&=_BX>*=iOrkO?1?p ze(d%t=gA76LuuT7R+QvFuWAlAwpW zaCu=x51e5lxW;MfsGYp#;B6m){4@%iZ`SJ`-d1g(^i$25==?tGv9&N-=FA1##re{M z-VDZVg563$x^d`4+&_tUw`sa-2c}PY9X#AKvzYSX57~U$x40mJBQ26#da4atBC0fk zn9VxNHe%7e?)a`lt<+yN@dCM#rIK3fysiDUoqhGz$CQ1F$eANKaAGomh3(+X&2Osg z%Npv;veS7Sznk)+AEw>XN6^!}4cz|+{z%EiV`Tc~SN}XqM67k6`ypF$eew&tQRPJ5 z-jq_gt!>07cXa0Ua+ReiFC2H(VrV(YrZ8UhS|n z*4jHbW2qpXwevVFm4duGq~Z=N!iFSi#@{LRrqf}F5ggDd(LEj|@64VtCQL;4QFqTb zf0s&l6arKeUZcfVoT5Oo>Yd~{ysAH8&T@Qc7O4npiDmy$EJ+hRY#?>iWDN}iOH~;r z2_HSPsL3Z5Y>fjX3VxX{qre+-ow^ch_Nw#C4Xu-KM)RlBfs?vGsnDB)X7Pp;!7Rd9 z(t)w4$XeM}S$M%#)&|wFj;W$Rq7b!?TH|&?zh+)ZM3ha*dnF}7mjE%dntW&P^Kx-- zuhrEaG$WL{b~^NaHtj# z=UP4`b%~)fnZdg-n0of;PvkBM;1S3P7V!@12vb)=3d*hP-sJVvFh|77Y+OfibNb|N z6n?gnsa|{Y0ligJYK-bj>pabPSe<{DsGWH%NB8Qn_J?fd4WK`buq^WQ-7;muc=>{Ds5s&qBwN;r8>ug+QTh#dv!xf zJ?RLc6sFosBj8C2CilISVXNwVpSX)FxfB0V&nF=FY{&V4Xr+LJe2QTT$g^q-SN|Nm zicS1f89HEQggUInB}e1aK*(xVj{J{rf_YCL1FZCc9GaQoXu|S3E-i%8dCV^FHo1;n zsx7?Gs4S;iULPV2z6lck?j`uHpaT}Y1(#`P_nP_J&d^TgNClkc!AT&ksM|MU>i%9Bbpc4k34=N!+gd>#JU zkP_kl!QNX2MG`!HgV^Hk4DRj>?(E|3?hcE)yDjdpI4th&?mM_H?(QxNd-H$ZxVZNs z&c0pT#q(t3F!Db{fVmk6dU6<(KBq2yWAs|p^xS9FV&C^8ftDeN3T*%vAGVe#*E z{@ir2*Z)48omzgE@(;aM6A+1XO!F%7j+jrW@hjKeH8Kkp6QtyP5+Evy{&(E)-!aqw zO}x093#^Y?Le>7iibDUV;Pkg`U+&&5Aw+rT$Tv|i$^;h=lDwZ!TOTG2;@H?3105>o zzXkt4SQ1{;5{MOJCye;0r6mY`)KT2sdToQnwwo?ay8S-p=T6s@Voo$ z$0{R(;bdL#(BgtjuE~v-wu|f7v6LrMLRsj0^hvJ0+!dGoRONJo-RN%#e4F3WgmM4C zaR3Z062k_i` zzjmBHr^r$S3y#waPKj50fe@^&w5b`7**>ZLeB_*KQ(A_+#{n3)2+n2)Upb#ZM%CrY zRu+=>ED#h?^_o$V2l^^Hf+{o3QjdJ42DY6WUw#H*6$HZr#Qj)Zliz)5-+O+S?|hW% z?Y=jQ%U7%NtTF ziLB2ROM2d*4e^xj?riK^mlf1 zf&bS|qE6aDO>yl=?pHeLgq;fN-`O`+nUbqioF1P^zJ|&GKEhLv_!_2Kkf!)U&ZDVx zcikwfY+#H*ZmEHIO2FQFo4SW;$50wH6ATws$)7kbR_oEHoQS?a(g8wr>BUq8znn zHsrNT#X4kOZa#J0DT5*dw298=8K6DP?rIv%?H-zi#t=kI64v~i+iurLlSL1%SGjfE zH!x#*Tfdz#QmJIAkZJKQ4#(x>@J+z=PUV+xN`@v>iEDsnwCm|{Urm18oh_q^#PWj} z9M`Hur0t%YARkNx#$WRkf4RRgEWSfUTjSf|tKMi19Cf%Vu~7!uNsM zKxZ6cYH*xFAj0;-esSPz;W0lD6*4(7#d-{Wn1s??nxLiFSXu_UnDT5IEhhB4I`h6L z3H84lOwsf5i&CY2Ykt}MPJ%hWI-(0AEcP^+@E34Mh#7lt3jM7hl$h&Qo<}8MC(IM! zJ(mJhAtK68v|dr2$}YUNizud?W@DXRo!FneD3KVO<1D86n{@h5*{9FeYD$1$i?r2_ zzC_MV%P1yi8bG@Izv>x=hDkTl#N}z_0Z)sSd-eS~R0n;VlYsvM67B3uT4Y}%u-bm? znG+^hQ4;&a$0yO%aq$i^(fuVAT_6mBoYDXDXYvnCf?8wA+gr2V{cXDq6%ncHDC7PVyE^Rm)sOIp$Bq&)rHcm;8#8ujt3J zdm5MI*G~qXVjtzmbeij^RSttn=heQA&4eB|PPFG%2YYkR@5WTKD;c>La*@U8n0=8Y z0S0FHaxb*(-bv^Nyi=xFGA771C7_1((PlK33Nq=A`(gc^OLLF8tfogkT~#nzhEekm zXmO;q+ktsMVQW_su(KT+OaRrT8JR5OKQA`XkS--p161gEve{q4tAl@w!u{l8v)zvs z_$REOWW`Rsm2WAdQCk6?Q*hd^CQw70rh!UIWcnqlTRj5mq4r|;VDm)02feMZgg{fN zFPcZZTP)a@?(6^!-!1V_Ml+D2SP%Bc0ld)s zt1t!)x{IJ}ae&7DsSufu4pPr3E^G_4#nknWGD#E?JV@f~RpLs^siedJeFLVM?4A0A zC4Wo59N^`oySMG;mS`ky4QX-=UdXjY3Q}4+?U0d;Ll5gCb^yO!BeT#vqf*T};R#!u z_uGY4)`9A;j?OQm>#APW>fC?!u=03mrSWpri8X3<?o!4(^Za<(cAN_nf;no|*lDUtLhdEJcs?nEJIA z-~4urN=5x=@YQtgCw{vn!F9nT*ae{)(FUT@M=Fr8|EfOK^XQ)JTc3Xp41Kdoh5V0n zn_8fD1WiQCu#hRd`c&V$e7L;)VZn6bO|aSCvtS61o@DmoBtnT@Div#cD%&L$CR|X+ zK2N7bWOnyQ>Wt+h{oo6d|DP%UAI}=ruoKr^f3L}fXIK)L*2qulVfP1)?q%LOoE^6j zDc!jmk2qU}D)9e@EUNnUwt^Lv(a&;vI~Vv`9Y{+1OK_eEjd`8;q&{Yl)PnZkHvYcf zGx6(|u~)*`Z-6iHceIL?Hl&jRi5D~VPo2lH%0ctus^d1=-?-rPXfEbR>&R}icb6CLaEK04}(t=QY$;ED?jU1qgQ1~mN4rLl2N4PyIzjO-8YC|<& zgR0{)mAj1O^cxtvHY)}=FToWr$P(;H(h*m zFEn-IrkfNx1{8&}k=;%#xo%4Fvsng74&#xyTK>+|Mr45{&WrGuRmwl~Sk7U3%^uI9 z|MH(jS1PX58WU^%qBI)es3fJ((Y_1HLZlO{h2-}tafwHQTN=;yYOK>-$>l5l^U!-5 z(zr`?vXdFS;AgeW^_#Jd7hUdFAp;jxtspeeXWE(FE+U_zmab0Q&Wh>~$*;=m1dh11 z&`12foG_&*jwp8pvi z#MXZ%1MLO(pSAyQ!T;+^LWWSJ4^OfJK};o!*t_?bp80z8Cg zt)nQ-+`ow~GmT-B36YjBijlCH$6=q;0Mp58G_S#$ji?B|qxSPM9PPdmXXx|aIXaPmjC=h|>Xfbv z@Md=jQyWWfJfA}3{w0?={}ldxPtAPP#VCq)a)Z-|Wu9NzrzD)ws4_DFsPL0P>T(3q z1*2}0pV^(p_H6gnt(slYdq)iT=`>+o;$A`X`$Y!&e}ayzN)27UW9F7eT(Gtdv$JfMgbUA~II z#u4>c%O?Wu`cOTRS;>_u76eY5l3(vL;{3M#pXo_J8D#`V!C>GWu_A{*Y!hJ!-xcpd z?>+Uw?qL@svEsZhW=$Wbe%^_klpmW>4)4&6B1s&j6mwBq=xMzz_zzyYy<~9nj(YeQ z3?t*nu_UwGV`8r*^aOnyczVU$3ye&FAW$@GnLV-DSO1AOW@^G_^fc#RHFXw(T*j_z z4SbYSk?3vy0|wra4cSz4>LZ5-^CkDm5p9TMvaTb^qfv=QEMPa5K6qOBl)*a^px}Lt zDwd<2d>C1+V3PHim%+YrAoV1H9oVazcMkh9H=}Um3Dp zO?5RnAPD!%3Qm^^LOupR!zJSW6}!WC_&%$Nkhm*2mmoSLqz z9oiJ%FfEK{PagdF#mLi0`XA+T6JZDSCYpcAmUMQq87zOMT4%)&63L4(*r0W={vl|% zPVLIg!V#xJ5Coje;GMrxzEKg~1pKjeWmZl+VRBOr$+h53V?F&kQ9W>O|Ft4D?~e%- zKiz->hkiQiX~_g;{>ofkuG)iP2n%=&0^@9%tot7?0n6r2IQl$8J4QS{L>Vy{LBI%h(S|(#UqElx*XSL{~pm%`@236PAn)z zsRBL{C&xzlPlw(T4er`ViJvBFy~N;iiV8n{_q`M4=tt0xx295kHw#uoJwGl0%bz@Q zf?lsq(hOGenvks(MuFM&G}4nAm=6y9z;EszU9lJ@f-DCz&A107mQz>)u<8dSbXZb< zYOo){ZqJH;>tD9k#$uKZWEc?HPltE0i!*t4OoH(YlanC9cJ&Zt*gFf@PI9#Pv>j&X z1P<3(6#x2an1AN@h=4GpLV`DA9Rx|Wi2C5MU^8kmFxrk+6RHt*Bh-w#-W=S;;nfU5 zB^U>Iaf1m20%g>^FY##*1pPvp`cUQ&&&DnYiI5wi6V#%xPG~e4|1J3MCHe1@;r|1d z0Yp1=BP_&KP&H(b0GS=c4ywMzRA8s%!rK24=8oro!`%I!XU*p2V&nav6D;)@{EjCQ zaM?U(e1Oi7fZYz-4L9-K-GuP8@|rW)6uQ|P%&h6#8URtr9agM+D{?fEiHb|ilc$&q z5;(s+lU|iA8J%CZiL{aix{5RbfoG&*@~ssgOWT2e54SgGDn%ml`vOlBo{qO4o^M4+ z&^Knbhods``*il@Yyxjt`~EpCfByXl3?#U3vwhh9`#F~I?=P+IZ=-*Ijr#tLB?P{I zj2UGZeHR^oi=~k<8Lhe^9itRxBXC|k_E+@JLmIU-?QTQ+m$3iMPY{7-wC6Uap`jK! zLitd5Cx8FVy1LA1n|uRZp*ao0wau3EJU6nggYHcSljhDtQ3(2F8bfV{eGDBC!wN9)D^<3Mx~U;>c5t0U_Yj9v+ zZ*v0n|5PkW{w8~$QL>)ist~3_cJZ1?bUoIvl_=7rOr>>(CT|he#yYP>`dh`w>MU#R zZ{L7#26gbUIqh$bIC`!3Eb>0?7}Uhms~Wsg=e+gY59C~TsZweU*D2nUzwM&@tnA$u z&O`Plqw|z;zho?SG3{3yyj$_#!Ohr*&cn{UM)nfqunO8P@M?}c;%$63QZ8RKbA=*^ zmG*Utr(7o@*Xolx=H&ofJl*7Ea^hb+y%0$SG?R%2DH3&AZP*Ktw{+UX5Vxy&(E#vwQxE(-IaFu{X%Ux#d?o7<+ z#@l;H<$DgeKf8g<=NAgTXx0XJHg z5z7E?;qu@*{Fet{ra8U2o`2?ojmK}j>)6*F=iuj>03bZ_T@LH9J`i)^DiIBjsqaWP zK9=5@zS~3?3x!D|OVSq75hyV(7TPCEA#odpB*Tmp9s4a!rFc@F!Ys*19m8oXDS;?E z{rk+j?KS&JX0_Dh`Y)*aEajN(Lwl$jAY8oK+&BRn8r+e7V+?cj->*v?U$9#;A?*7l zP#Kmh6!7%q-QNL88Gt!>Rx3<`JMs~WeVv}?VJEh{JaaXO7We)oETdSs=HHN3O5(wb zFOZ%}rs2((g)ateUlO}1wGKxr!&E-&ny$4EMcwUMpxMvW3k5J7fq?NGiPu-yt)U+X zNikgNwZ|8+cnig+38=)>rfghv}|m#ry5lHV`VJ17cv zsXEpj1hRBObzJ^92sm4Fu@YExT~b+I!mN!hUMD!6*bx5w@IdM0JjzNd>8Q4t09}Q} zE{QF+u)IRata6RuB=4vPWz3sft~rJr1`OAZOdz+8x%H`G7*-~-qMP8U3sIpG*6u2k z8zLjQLp7{_S(%@W*Sj8j55JzL3}BA1m^S_ZN_kPOs}1VXzRTk5RstR9uPHp>ABMga zQxVF1K>O_N<%eCRHwA;UhfmsQC(wO`a>yC77gZ&59ML)U7eKGvWW0QX3dF;qne2S#Jv=Vfl;^ zYw{CIj-)RV{jkd~*eAOrkf{GDP4m-{vkda^1En!g818XEu#E=69*S-b`?xmR&2}C? ztJ%`{T%lg?8~Smm*!ku>>_F1;1*L?}zjU6&!AlEeCobn5ijE9^at7Ca(0YJS2S7NH zfA#No>HASyYQ{zP&(cfJQNgA2b=i-o>#&*?&2uSca_jC^QxPbDVy*ih8eAAIgNEu4 z5GW)loiVMYFe@8)bQ8A-33*xn)R@FiRxse@T7n9H(iY}!ny?7@i?F`)XA*yZ!|qW+ z62I|siRnE_1+*n2V|qG%1vC*Mu#F{}e$b@&sPz|WH}%Q^rSWOio`T^#=5V&()Sc#H zeYjOvaCQ$dHlaM~Mn+K;-I-DpsCv({BDCF)f_eg=U|a#+vi5^g$~e$?o<@xLc-W-N zGplMSrF_5}0#+!^K+Ix0r3(ICSmzP3Bc}Ymn5FwnqGR|y=}dzZoRRaOHo040*l6dU zSD9{6{rxBrSzU6qRd|aCeybKU22mRgPrZ^TwNBWO=FjjNPk9E*gYJ8j>Of@+<#F#~ z->fEE9@O>qi7fyMl}1<6WR0**s#T1wdZG})O0XuU_7Xe?_b;kZAxi2 z3mFRZRn%AljhJ69s|(zmVeOiEJ@b}ZYV1&pu9%t zd|0A-@=Y%wLl^TM(o&Pl67is$RCllMcXdfxSsW%|qLFiG$udl7`8cB% zVK6f(s$Pn^W-GVsR)Y&v#_zS~QRC1<7#~fo(N-k;kSg~>`>uYvs2aV(NtG%mDhIqS zoXQJiA6v=s^-I}g-j*HrG8n56r_~epvhr(nWPFs|Jd}~Wm zCiN+wBA!HZ zf+TxpNuoHLVAJH$h7qjQ#~ws;vVkCkMwmTN^?(+w57{&bP*Z*#%o~c5!D}DP+eGCQ zJ%k&@c@JB+DM_X{MF_%q68#I6L=^n;g@X_37AedHW)Fs2zjhw_7KthVW^d6Dw_3Cp zZZ8$&TfI^^whl6cjhyO=Tw3HCSas6X&LyhoVb->uds!xw;T=IeD~{iBV9 z>~pqRhle$h63)rswXb6`M2};?TkotGZY-NBhg>}qR~M|2!&?L;!aNDgK~L;E7ti8$`o08p|PM6Ym|2 zfLWeS=e!;j%j(M(g_!7%j}T-n$0V`>75_y&D!P;PjnRay=pM0uv7}dB-65Q{P+d26 zgCwp?dXq)1NR)_xE{CV@%kg1(Rb{qQ0CqTs;|09*U=?>$%E-&De$4pAzq&44)|63_ zW**U*@P~2SA=r-MhxK#TpruQ4W=a1ttBp%+b_?ySQ@Rf!f~DqeSqiN=ylU;kJgq`S z-1K*41iZV&x1D-!=trcBMw*r6cnStNLamk`^>6R83UI30jNZ zxv%nznXQHtZ>syMk^b6ueZPmOQUs0DNW__tj!YU6aM?;Db%8O?CEPiz*Uuv*@P4yd zTlQwH`nvtK+9T87y@MD2j59YS0_>L0?VB?S4fotzt*G$;IEaAXks3M#Av2!V{!7Nv zK?_3y%>xdpGOTXaq4#d7Tnh6z!)CV&6(4w%BH#G1R;T0sF22T33W>Ncs_^CI+p z<1$pozX1U;y66`X&BtKlW;Ad^cM@ws?*oX@!Kf#&QTAfHhwVy&4TlRxSFlk)`IGUPxoM8Qe z*!0n$lY#9Z79fY?bmR(MMBvMOlyrs9O$;4`btMHiN$~aU#^^5k4K(1+pWiXKGxlLb zpzcdrNmZ~Pcze<_{gW*OKKw6SACn5-KX^*wy+UmpLuw)f8%|uwYJuDJk%(r18;@5d zExOk932nq3x(sy6pQKsHljad>igoLdgqotmO>_8}>)Y z$@=W0a5}Ov;m7fSy0hp|DH(-|??wD6i@!Y%(w^ z0}v5{=wdK^!d2jTV|;xmmE3_Ki6T?g-j21jMCo+A+$9_nE8fzxQL=WPEx%v3h2rG1 zTl8$2XijH23k&k}V#zvu13!oK38(#&NNfK2AzZg*xJi1y7^$xw#M~C<)Ygr7V|!l# z9EPet;3-xD5n6Dvu&*k$-{`+yQYV~^L=}BD;+HMi@VlHURkTU2lm#vSM^YE zp-cb8QwMVxp$Mrykxbjx7zfWGDzqie)v!=~vtxiTZ8#R}6(O`AhKrd?!!WTjstvF@ zX)|#prjJeP)#S$`A|l-;Qsvsuu>R7`f^%nJGMZhf*wg|SZ*e>{Caqr*cvoO*afI@| zLx{u3>9`1YtzHeotuQia47Nw+mK&L@=8+qDlRcbExt#0##rWht;bA$fmV5~cI;L7T zdWjbL^EsG+&0g`VAd0*!jXe8xmD9A;DER0 z+D|Kv?mr?K_e?X_-&=-O0H?-%+r#F{#AyN8QP>_xYcASU>pnmkL^SK=9PSX`>eBguA3 zjjz?=UED^v1((9OB>A0s+4oe>Cd0`tgrn^>l`H6Q8OFlFZc~Ok#STii z_*_qu(S`fwmUxr0jf1%+9Dj>bFF%BtrU{oIu0(%{?lPO$)|~No4QQA6jRoPm0{ljH z6!U8K11>e}`@W76@pch5u+zzSM80~BzX9%@$GNET+W${7>6oZC!khk=r(Wp zJgTNFPng!$+vGAGK^1e4o3hl~hJwka|03=FLk|6iOgtDsa1EyVA=_xjMT@t}jUuGp z<-EGks<5cN4ukB&5Ex!li8i4D(-Nj-_C0(k!eCo7x2XhfdIhsr;|?2@FhD~(v^|m6 z{zaqkU~zrFeivH<_wi{J2sz;$lgUqRw|)4nd$g`(ZTN~mWRt}=`cc%E-GD8RGpJk~ z6%)B)McJRId+l=aa=+OMCj{_-40fq^Ej^5G^yV}tk`#5j#d};pJ^&Sz z3fIM6pBEsjQ5 zVSa9FM*8@vKNnxWq-*pH#_G zQTi&j*TP@bXrXj$ox=r_>Tn`0S8Fx<>2xAi95W2Z)@oz- z$E4_f9J48;4*&A1Dk2$hj9+h0RX9pwD3|$<^nQL}!R>Te6`%tM%wC;J}m#|RKh_+6K5YJMv7(WiU(y7tH!nz zdd(TERPscD1N#=?{P946L!?{GRV)1kTm(v5DhUzt5>aw>`-i%dHJor5XN)?$btL`1 zN{P5=D63Ke1wxuvD|Ra=8>O8pko_(z5g&K zEvACbo~!7-tZef2L-=a5!mJARCB+>Fq9>X-b!4h5)oKL z7whNTya&}uyogIgVyu1})C#@$^nuZ>bn6XTaa{#xNZ(Q0<_+|`pX2(IO;(&S23uib z5y-e*fU7^qM1Us)AMVhgyjCcosRquY3dsW3{GILSwV(n2!3$JNXxL{2cDq>^`@vrwv@0y4wE!>a6_l%#`|yit|2uK|F4z2SWzf|GzG>(zUV#)xM8D($JSBm| z1-kX$YrYI5#pNAg@52c|30<9_4kbZoApP0VMhf_rw2bVnG>TrvZC<@^L1l)Y4_(bg zutAH*OAK}uUiPFIb2q5QcowDUfipM8kz77DVFLFq<-(9GTki2Ji9iURz*(Mw9^7 zZ^q*K@S?*d6>Z<4Q-C+!Wkspoa3QXQEW00bovL3P6PN79_?M@0Iv5<2uEiL(={v&F zlwyi*8oOw3sp{7AJRE+8K`foZY}`;;IqpQ9!9o@8%Or4P!f~1R*97-#{l_Jc1ytR@ zzpt_%B2kP|Ts#^c1iMZv8HWT8IH!*xn>b>OJVpgm(j3%B(6^&KreiDdEsqDLvs7?~ z>_6Z%jo2{RV{>rmx&oAj(O2CxKwl7DsZ29_U=SRnC}79Z+u)bmAN-Ig$H;!J>||4? zT6$>j$qnTn9*SFTJw?nVR_-@<>q}24!k#N%JmpNh9_uZBHpqoM!M13*5qE8T@iRBH z78%{D-}h%_AU_h?oGWd0M#)DI0B+7LJ4kYrr7l@O%js8=Kcazht(Kw<93HYl!%wG{ zebgDxi@k18NZRbTZ~_K;$`AbdjIq;lu5mYh$WD7AU45fIuA1^ynRxZNH&l1&)pKHH z;M-~R;Kw*T*k|h%{?`sE=3;6TCc}k`7A+R-0L4#YK}_qaFMH#!R#^) z$@sx4qM{$}DG z_(ri%eU}9>7y4Z^25zfkt@)3IZv{C0X+Ghr@i|2>HkL!*%MI?t=?ei8%lJ4ofZHWnJ3T2l(xSCV&E<4E)7&8 zNY@^%xl*JLwSM}yGP8eIe1E-DZdBQhPl1E%7;LknhvJ+10uv`LXmzE&tJPNtPuX@~ zE=R^N@+W1rTxmA1*qzAZ#cS-SMm2|l@A@vDnnokWfaX`bu~ZZF#xjyT5#h9tWgMSi zBuYM3+VX+)MWm|>-D$Bk4*r9_s#9O@p5!0kg<-vgR{F{s%ZMsrPwZ{DtR|Y;V=%A&PRVQscm2hyL0Vy=9ZGk>j%#H%3SsZJ2G}Y^v^Lo($?i7 zs!`?*yLjBxHJeYxjZ;YPG2LaYI_IH*;uMWT9_5u;EgM$?kv(z@+>RMa!xIc7oCb=- zJi8v-m4W)g4ipuRpvaC1az#$*LdbU4<_c9230F+V)F!a?rRbmI zKyV4S0GG}Ewu7iQ`11i)6#a;->?$Wc;I}s<3*~V9^8Rx-{E5D=xC3xd`^%j{W|0r^ zb2aPcE;PVT!b!7qh1&ze5Ht>0`t>;W1{&kb523EFIga%N7lx-JVa7)P>iio|Z#6P_ zerTjOa#0a1O5 zG|x$7ze=_0vY=G_G&+?NHFfFtMofHU*ss{9h$4Pl|*=HbJjG1e>`eyBbNm*RRRe z1zijR9|W5@&2g=NKc+tF-)3H}&+`|I{yB*xpWxlQo??k;9-OAr{T3-;$oGBbQ_ zr|3_PQ^0{2%XsV``Hxm{Ll-MZmJW$~>*yXHFjGT}@fbrJUV`WiR*33mZ0 zp(@~+B*ep1(9?LZYfSS}!(^=g`#E9fgj1%IYyb3e{iWCS?(};M=T~8KVJ_c^fqan#qeq=&~mTP=?vw_~j@e zh+dDr3cE~7>iu#GG!}{qp-{rB^bGFZL1?v^JJOrG<7_LJN%G%*?S7@oCnQO!LTp(BbYwJbh=wRwYQ|3yuAQ zpcD5~oP7PF&_K5`S_a?zVNyaY76rT6Pw*^*Sxgd#Y_4uWpQsbQC+yy+ioJ0%+5e>HU#QU(U*w)x{t?ezEADkE|_WgQR79#>N(wpd;>U+nTXKpfRm z%jP@4KJ&{e=~zx-K3UP?+hUR!@`LgbDmSDNubCPjlL|);wl#%M{wYe<@gB9hd^2() z*jw8mH9bW4&u(nBnN?-sldPg@S(E@ z^v8{1!+2zkWh6r&j+ts%)DQ8XBAO^-64L`nfGg73n^O^Ne8(d?R>RptFY9TU)sFn@ z_r&sG)#B2xgs^!0KPPx;iCB)jVk)v=MnM5WHF_rR^k;szm>i@tO+T|m$>=x5mJul*)!!T-{$~n~z zARc7#(g^mBJqso;vH7Qfh8QNjwc9qIh=CO*vP>qbOJyibVn~7jD<3kyZpwM5_-bPK zd9n|4R;c8?QS5LN@zTBY|=gg{<}%^z^b@dvB`Y8ubMOvepH53f9|k4!77M9&Q_dHbbOH491=yV01P)p1yL+&arteZNzA+&XtJZ2Te) zy&xrjH4vD9lFR|*#RnJn&>bOMG|A@(!n?mORre20(zna zE@kXKpefaRr>J#yDOfifJ%27*iP`QyN0Awmw}49=%%5%Yy5RWPiu#)xq(5leNOn|x zIGEsefSs#zD7Rp?Jy9{MJ~?Jau~xM57*o6^p3O8Y`= zifrEXK@154W-~wIQ!!WG5VlIZSF!aW47F?G1B<7lg(uz-#0kzf1q{b%%LT=6;xLU^ z@@7iJWlrXcS(nUZ_yQ z$af49edZ-a_^5^ZyU4T_UmYx5hAVfoQwtc%QuH*eac)%5gG4b}jZ!>ieIcutS$r@Z#h=5w}xqHT% z))SbBa$ilsFDPZxJUk$mD#;&)i|c^1=XjC6qT>}>L;#CV67TZ$cOYwP`!@5+MBT!M z@7ZoOf+=a{;!m|6q?RaB`xtn(A0i zH;>4e5VhTnqDxRbR1@8W_QWWenctlQXwxc@+y=vsfYnS<+Z9NkLhEU@QBE!DO2enS z#|Gyab>C9%yh^f>bv1I@)Ic&ckTkaznP(?XR{XucR3D3opvM2CgNBzw_kGq?Aqp7U zf+n;Qrp>AS2JXghy9@ma+kw@7ndOvA5Tfa!$d?_B#xn zNqLQ+ysq)XPu4!;pHPFPZ8!BB!1tOxfC<@SFUEood6hwT8M=VU*;^QZ3P$kFPCss! zbk6!jwh_1awu9Zsc=Fg5^D!xABwYVvqTDn;3^l=1$v1x9E%YHyD!RZ6q(T8t;+EQb zjdRRL1(Fl~!>ManV335bXuD47{~h@-kOa*bY!zh&zFbykJ28mOI7V`ci?7YOBAMy%Gu%p);z; z&vdpSlW5!`6wtk-<)vf_$L3GP!#-hho-zDn)G~_WvbhsW>sTX$p#1WCobsrL&q{x% zUS;mAX9zhBrBOqnmVMGe)juP1 zqqs6Jc7%qww)WN{-z;E=*s*5FmBqZ@pn%BmZGeB%)~2>ICdU17>dhi{{6ES3>;kV? zB?WZB@v_fetsH;bBkJcV)?9zvoC6c#N5lz~((Ma1tl0}cC8F%7ifD}{VIZo)`y!}P zCO_JS5X^;~dRv5%(9X}+241RR{VoNIem+%Hu*$qG4tEY1A8wY>ETc#= zUJF0o7Cm06k+SOjha3cx`>WkbQ}7JSWupz=`PuzoOG#FuBegnop~bR14g}u;%%gnc z;I@jhdzh2FU+n}wYT`{X)H-DoaHQ(2F|0Xc^v1R+WRCyUktElAtLMxR<9G?U_ox%=@ zRP^x@I+JC;L^>Vv4yn|g*=D+d!XD7C)I#v~l4KwAiuHI%-Yf zkNlTk+zh@c3)21ciQC%`_~^IQFSECWBG6D2`k5W%p;g@_KT~R{nY+*{Y_a*}TZ64|0)KrYh|AS)UfHNx%toenZiv*dA4*>VXFjlD!Gl4CaH%Zjfz zUCXQBLCMTxA@bAUz4xJ={Ffr+r>m!c9yhnRlR`rcM~KmGdaos6PYAR6fv%^HFZjW6 zTkumt{|jU96fIh;Y>TdG+qP}nwr$(CZQHiJrfq9Y+xDA#pRfDw`M4j|Y9ynLR8lP& zNlQ=l^q1#^Y=ckB^rIsH{HAsK%Uz60Pqsj#2SBk{XMFnGdBQpPdJxu*lRhUzGB^Vm zb`fzQ@021O>%m-$4GiWr-t@|jp!GW$)OY#AS^r(i?F9GzV&jTi8 z*oPjD>kwcbxH_(Z5Wj)lTmztZVQCI)W$?D8Z749&&PYv%;iX4FZQ6AGtjvkVMuGQTMrd}>_I+V z`d^JFuK*5~J2)78?7nup2Vhg34X{lwuK-S40|M4gpZ**i|A<>TeEp4z_TIfd|G5ur z?}G`k;0)VM$ywY55iAF2xDACOxT}~7&6Xg|bgS7M6OP+4vlg6Jd%|MH%9Qg;rEKk* zTNB#I!$7_DoV_C}V~V|J_i85Ll4a;h&^D#j84s#CO}zs{4pHSYi>nkr=2?fMQ>l)&gN$E!O)oegt0;{XQunXtEsXea(2 z-cBeM-}{3z1O?pu_TEKS9Y25G_npT8AWSsoo5LVLoy9z81_Fwm*)s6Is?zQx7$lG& ze>cQSenCo` z({CD19sKsUcJ=e@H4wyxIe5N0R2b@rd@9Vb-qh;Qcu?n1L__`N16 zjZ(HVMU%Zs_A}ER6c2}iCO(hXTglEy)o$81f2YyOpF?Z9;^>NG8B%A(i9ENh-&3a+wUOf zVUT7F=@|e>=ZeJQI>ZpmzVXSbIYC(1HBNI3P*9|d0Fj=K2DpSvjT*ow(_xyIM{;p$55hbr#dizg(zKzxlc^tt8}L} zfgG8_Rit>2m7``$kzZoUG7H?rDF}q@Px3=5lH>E;t=}3%KzrLx{>?aj6NrC(h6bQ+2osz)=!Ej1$yoPs#pRx2B*%C;^uTju@A}%q< zX_hOLkTgIsYh-gW#}McCY2{<2>A*U!%Xw`ne#|pmORw+k5B2%_4Hgbma2rhP zZ`At6iQQ*Kq!2 zA9XaRtFxddrk5=s&Uq0bKD2M3Wa`0GZ3#9$aY|hfs~sRjSLrT$W5bHmfIdp+HKx+T zbjysuR{O|2h%fe!yI(~nZCy!~Gcjz9>}Q7r;UkVrssm#7U@5x^`r9=LJiEJ)g;uF! zzf&#dLk|cNcMSxaB7mmIQ25~NNtFsSwiT4;BvBYYLLCm+6UNWO8#IzToWc)WuULYGsp3h~7jN~0F6}TUCBKH}p;f~ZbUA#p!d~gTUv&=1 z_PyrWskTVG-obrI$P0D4AbdW^u;n|!gbytL1vHtza z_ztSW!#?}$EDU3z#j~*lK!uZ`I&1}SPBM9E5uYgS+u(?q8IzMh&7aXKsTrJyc1DR&O|;wb4m{RPAdSD}O&y zkj1lOlW#@=J_KO3vsf+=_j7Ma6N~A5khc3*NCg9cb2*NBONt5RcS?7!kwdz0D6D~N zk@wmS{bbewDa%OdfGB($+ujxYmV*gVQ1Gkh(Bm!gi=NQd`g2goxA^nDKcE*KwX6C% zSyb(S0Oq7+Gw}JO{85rRva!(B(8LfDU1C>hXf;la%(x;f4&pIMY83yK)I3L%I&785 z1r3Fq-by8}zftWUFbVTs-J}BWkk0zcB(cZ}(rvm??7!}vEz%%*4;^H&zJ@tNtJ48j2`dzvK^?xddmG`E8*i=FX&#MLZ` zUo`7WdWJLr+SJL#3`wk&LwI7{crBq{bReloqFR$Xm2EXg29%SR=CLGudO|W=gyREs zlu-ToA$+U;)S>+H`>fb$*7;bJ4GY7F2-zSxip7i5VrA>+tYm=KL2FE{_-ld8oN_| zDK=Xbo%CkUEp@rjA;g6?m#wj65;H4cGwYHDJ4^y^olt06z5O-xkoewwwM#7qjU(wz zI#GfBJ)}+35g@KN@fNB|D0UTpOC1LfYHbeP3(1Ru8wxL|YsT$Bb%(H?CqT|%ht6HL z%I%dzOK|L*d{+_;Bq)1P)?g2km^$}RR?F$KP^u&(=1||=y9whavj;$}={16{y znI2X{?2jU!CzRNmmH~P0;#E+g+jL#5-Pg#TRnVtIIn58d(-whv?#k5>%yF0;7|%Kw zKujB(IWHUdxqFYncPE*Y1ZW}Dfnl5;*20bT+S~=}Ab-=Y-?!x;tD83TRkCd2Krg;n zz|DR7M6h0iFvm>3qSOvn=j{UgMM8^SlfM}|fT$W5JZ$LJ%_m-Vode$D?$H6+Fip11aB@5#Sz|7fT&;4B3=ZKs@M~)JthyB=zstGh z+?O>50wAJvUhArgkV6Pem%Ei<;d@|f`!g-ad;5gHY zjz&0jRekdU1mw5}`S8GQ8`s+c0~EMRYECix&%K$@jO%K@!CjjGFTI(-%p0)mB@Mo5 z24jUdx3GEm9vFcc;AQ>J0oO?Ka|qb?tna|U%$)pfAHdMe>D9YFfPk4er6M(%Sf#`K zMG&k>a{ZU^x9Kyy08Q49y*(emQ(@i0Q?@s}00B6kAWb!0a{ad-51}sbm%lk*D$fw} z&UTKdE8(w(mnan;vTP$cqWMzMyqZv-&LzHj$DG z>|W9@*JstNw`(jZk~ZMCTS{zyJ5WJ0Ov8%sXhzxO*iNVpm28|Oi0&43Pou1kU|M#-0nuTvTjH2a%FmW@~8=^rIH4=D3`|cIWd0-DI3zMwzR> z?rvf$_0R!k#;iMi=xpZ+fiX`%c42@9D2smb(18}>_gFH^VyXfyi=J+p&iJL6H0S;` z$4ksDX^?3%qkofELJz~EN7UJ_;^F8bJiny(^k+KlMI~lLJdDc1(MZ;3Vq7__lJjyvm=A+n^mh7402aaS^hL+kb6fVW zcZJmmWr_*?ExI-}jBbpg*dD6*76^uC0DXHpicU|d4}r}rXE;;pF5ZjM-wvALc0 zpjK@aF{E?Kfe_&yUJGOqdFz2o_1N7hn&nc*_V@$cA!H7N9vUAqrS{?PT3O7HzMe0> zVNo;r{|y6W`hN)#XJcV!`9Cqx$=Jho$L*J23>13$1&F?zUZ3tVWp5or*I32H35i_+UKz-_Iu_Ugvk+&&ld;sNdJ1H*t+*`Ois>Bl;TzTKZ%R_i7dV{(O zGt>UTCQqC=b=9(L2X?p#OWZvRoQJ=4_C$i<%n<;AR$aDPyo)QSpsTLQBtF;60}#PS zes-zq!G)607(sb5PZQ=5lh7CgB^HKpQ6FJ&cA;c$W|P$3TYgv^?jQD35f3B2Z+nH5 zN5}qhN-F0Aj}QB?wDjt3DfZg$ucUeuMP5wo)g_|Xf2!Pcm$CbukFPQRQVR|7?dOve z`=o}I!$EX$l8Jl_m7js>DPN>lD9ObB1R2(S^Q1_TKF@IHH zXiaKI+ItmBYV_@ZT%SokfCEeZO0!AT*G+;cS@WzHY+PQd;(8W)Tpn|#bEB;=To`jT z8C^4S)^9P3vV-*ygtFYNfsTzn7f`AZS3+<&v@}`gA;Q)rnTKX|VgOPq<{TE4S3JnGwo+!@j9vwu|e~{cB}7|6b@!+n=6< z=o1{3hhaP)Pmq@9;V@P1)t0e719+DiBarbI=daihtU-0Imi5Du=Sn=3ev;B~!r0vg zuJTEKQ+R~W&x9N(7KiC65QE*?i=qtiL#_{4jb1LC3q6~>7M4D} zD0ghZl@)|Vq8F(bQ)pUxKd~t}M@^g`V)+miRIFnD*`!iNVQ6`Tilql zO1f0%%|GfwLSPFQ=P9JBbtzJfBNvJ~7LgEJK#+D`NnU&yd7sgax42_~Ez#?Vc zRa+()q_^)_j`x+!@+%&qytVt{X3+=di{ZkunkHy+py}*h!x`U1Swg+_39&qx`5dQ{ zu$gK22{y4E#-nmTAU{J641}Yzf_eAajyDI2QttBtQLS*3`(d|@L8(&_Nsca4J9q@H zNG*#>*Dk~WF&k&=6gV4z`_VbMNsy%Rb7#LUZ39AdV|zCX;&S|H<+@5)v|D#AYl1wZ z*}RUvfI088-^{6ajxs9Q5c!$%G=U^q*8iF6@C;`nqEq;p+T4j7K8fZuHwf<>9IFpP zt&-C~M*?-7D-2HGBzp=DQUzf(2Aqf0OsSw%9Lrz6kW{8+3WII+bU968yA=Z4$@M(= zc#M%+slol;zo&F=$ME)0aThP*bIkKt?B>l85B0NKu(6doxmYAUI&O9D<>krO*309q z84tZ*da6LMTYY#x3GR=rmHgSlcBCl4oomr=+!jRa-{GHeVx^SiB!JCkN+wrpj&zg? z0vdgFvbc+p(2+gB6PbZ6#j9UtW34o13WBEXtGG6WOyy_Y{dZLQLRlh?@Ac*Mq}e^; zc91DlOQ+mGTxVSsPl4S$+KR6>JDSi0YPVHY74PE(w*qyKY_^#&zubx%WC9--U+g2m z@r@6G+2~<2OrHeH*DS%dn03-c!ZUg+npVjMPhhl#=1MLaJ6cfHH`u_SiZUcSFWK^9Brf9Q@1QM zC+b$)WGLIV|;|icC=9KJ8aF<-vEu9NK9{mzZSL74&7S06m^Tl>2LN24&OlI^K zn<{)1VXd3>&Ln<%Q0ZOgtV=zFJSdm!s%t4$fVe_^y2 zqHFJjivSuLpF_xY(bL3ckCCWB^K9~g7FxVMn`vhSZ=?ORVGt{bIdDWsfbG5jVW2zo zH^ux7rHu9&11_mmYZy#v59X}oAjNe^O>+QKQFPqY1Sx=Q>DTpL*GGE)nFrm+M8eTs1BXCP|`BZ?f4ls z!IoyqB?SFOcO-n=CzrR>HjU&bVNvUQW6cNdX@6-==fF1`wo}HVcWC%+Sluypc*lTX zG&-b6mwLa`CPw>_p))z8_;eWyhmPZ}O(!^geZj)^%-pcw6IZHbmTMGA9}CLQC^hLa ztyAa`QX&2MT_6KjKQ}nCMnzjNc1$hd>W8FdUMg#vn|J_Sea3vMxwMuMr^mvbHhY{c z#sdzo>r1q22Rd#z*+}7r>2Wd>LpqP;4_z#9|!CY*^7njvDC=IUaqv61qqmKuOjTvU7Q|P2vSKL0=4?4h&=nyPz2Hq#^T|7ZJO?kIL+X`t@X=smu%gG+ zl51fMMdH-wUr|F_VV5N%2C9&=3zLj);@kuGEFaQG1@3CRr+sl?^5*F#qtW@iyb?}$ zx*_u9}P{S$3afQd;Hbb42s6OotYGvs+#obqU8n16~yjAR^-f2kLlIvbzQBk_4(oG&@4X#T}-F=1%U{DNYaKzYg z(Dh9pAA3g3q>@o;mu%NaIAW(bc^vY3@YSsy&t)sn+n^CPv9vvm z2Mxmo$e@y9hGEXkNE|2m7>WlAle!}THBAeJtQY6L?1OylpWs9^!ceiYSKtG;dVB34~qAQ~=`f0>x2pcC5pLVJDH`*rA}@ z>heOMwKsArSbJ<)sLX?{M?M(D56q0NAK%Ke?Zxtw8o@;5y=+=cqADnL)(l zLk&_;-yrqffI%@zvKk28@sX zWb9q(Y6C z?4%X{d=u!L=B$;J^A+O)N@>zpPhDF%(HwU)SoE`jOC^`s%B) z@2CJlPc+kRpBQr>{(Gu2KPo>Zu6nCI{p`S{B|O2Wy=lEpd!St$!_&M?PcNiy4;XUo zYL%-BGQ^r&74iHleK1g{%RT1D8@)*jJ;1T0k&o4Yw@L1DB23u|1ft~;5u?U#LH zN_Dy6(eRg0!D#a-r5u@=)S@_B9)H#-@4mS*gG_qX=6g7>-gBz!)!0j3bs?N-DJAb< zuDm?KY$w`Sc5m)j$p}|)VglJ5&Gdvw=dk}g#Tk^L=|K%D-7a%6J zH-_iQ-ux$KIW1P^Dg5AlOm3?ztgpS&4&$6fSIwTfqVzpALefv&gJXyAi3Vx@Rw&C+ zYnpMjt=Xb&ncB%`65*<}_sGJfwYltEE0o)k(|?`Tjw8yuRBS^&XaPMDE;yQmoSj~C zlTkUpzu(+qQrSO{sHlX-=nEqH5>LJ7SlKs_uIZz9$6ZV6UnqMCT-X!(r0pC!G_x^P zv?wcBjEhcE8Y$#xE1XvY)L;89d#=riBS06$y<3wOcPGQr==+-H_ESuk?U}ZFYRCLw z^ocgsk=nz1>RsH7wEJg#x> z$=z`%QBkaH_N6>p5umxya}#hCRBicY#YCrTbnJ|G5O zV_YBd&@uvdv!9OoSdpA^r&iB6>qV~(hYR#$oWj-Kg7@e^F4>JCYpR-sXH(^}UnX@u zEY#y4!}vn1*Rtb>Ux*v;M<&!a%8gN~xNFTRAx}OH;s#UWWxYrbKVCCgN!Js=kwjcEr14zRg4T+iyg&QoDZX6qzEZf{e8=Q5KF-*3 z%Wnu#kFlAa$&~N_KUVrc{wFQ|He||^{0yH2>J=yf5=XX<<}*^7*^8SnGdYscSCHGI z$h({6%iavT>WElUJxumbzV~v}lX6t5Y4f1j*HO&PCSRbeZ{@y=bxydXD{JK+q&|?$ zf9JoN;ime4%x0}9GXo%;Ia}-eljZd}#|)NAaXW_&hZHNjAt#WagXNoZLpFNG>Xgtn z5Bozbb!%`E*3j;zgskoy0oxe4cpeyM_hQ=D?fS}rXH|hq1{{a**V?l zb+^_Y^bna=w4(a)XTz<`OJbZ{1_z@{#;5=zlmWzt@u84{RUBhE5>|{7lI3=mS zb>~uaAQ(tjtx8r#Y_MA3j|$KC!o)JS??N3axCYm@gLZCp6!h#$8i&CrfTserEZY({ z98|M|mO;luS6~(R`EkAQvFRL$WK#9raBRKp4r2=Qm!NHQH3^jR1IWG=sz6Dnxl^Z} z^=BF~aP!C9uJk=t%rMX(K1JJk6iLKFfbO&BPUsy(-)c49>3NW~TSRE5=YG=%)~a%j zSOQhW3Gx8hCf%UanxCJ@JPd{U?@x~w5W8nbtdS0WBvn4c0>Zv6OqQ;@IZ1`4FWTm9O*yAe z9jgIbp;Y_p!RmnhU{TkrbDc~x_j?>IiVrjKH7t-$em0bN0K%O0+gyCduas2i{#l3x zEAh2@fIN6D%4eB`*J{oALDEa|T)P)7QT6tT`m?2)9xJgeZ zMccTQ6Q@>|YgRXFSrg8@?ZSGlYMBgI-I!Hn--p?QLg%NQKh}5o6u#-poaM*zPTKo+ z68n13eTZ1KY8<$+VSghN{#`_anVWZa^TkB}fl3O|3=;3Z1Fy2vi31>25n>X^7yu_X z@piAdIhQeF{{by|JmvTg6+jGUCP8@;r?IXSI5TzfNSjZ!MtI(TJk-H)Zb(pHCV1*# z(E*!-&06}=Of^?g3wH>0kq~F&Y8ynk@kl!qdF;mW^1%hcC}N^|rV+hS+K6MmZ44Ip zt=jiY!FEoH>$_AHF%yX@fBB0VH=YOh0Wr6O^sCw?1ia?TlG6o<&0yYdEZk$G0zk3c z4n0vzHd&ft-oPByq+Ehoz^<-msYq?gH?PHQ2PH{uj*OEyteZ_0B~d#^ zSfQU^zMan6F%&hrsmLs0IveZUcI$^mqYG|0hwNW}-E2f?a4RX%iTZXOQv>+$WW+eV zY{DI(=%e+^Q&iMNY$KTiZeut#DFAlxj&>A2EHYfijOLI#1Ug*S966IZ7&=^rm+MJb zZ*YepSf`loQMAuAqFX|L(Sw*rwtrjrK(e^t1=H$wuTB%s{WIsl0~uY~qh7(9_R1R# zZRekyEal4+4UP6$Hg}@(&k2;q$s?uEs>Xk|qrI#Oi|2FLBz#kNwG+MiTt#!-eY{#)Y$L^X zGp{*PQTZ=_kj_WajaA7G!$sv8u}nZvuWE~J2>|FVM^u!E9{oBW1TNq^Qcx~29JCD| zp8E9VTy22=N`wBmaFX(1K~9kH9rcR-e`1YmpWH~6`uFNQzSAqeHFor?PB7!Gkd_~% zl3b~VQ5~=RU5Qq6`y7P4Bx_67cuHSjJ4E@L9Is%SaE~xSeC~AO$Ggm-I~%>~n?n_h z_s6QN8HJ7i&T*PgL1zn-@?OZ-yQ(CO4Vl4Au$=Ya5XZ_*arz`JygqBpE})0nN*pk7 zm?TTpku29T=a!%6MP1_t0Ttf~vwSTSZ%^!mF!SbbiyxfWZO!E-j+8*QJ6|w4Coz-e zYgl}SYQ!z12kt}^=Om8A2&7>LXs9GHhbGKVmT6!f%r!HI7I;`h&t|5MyjELScz;xz zSx9H|$Ya7ep0yNjq>Lb?e1mt|Wx$CaL|A^7$aJOjG%@oQ{u(Q@+giv?7#V1{bl81Y z-X)L0FjvIvGg=8^KF+UW!NnCZwVHzB52ZQgH=hUkJ+M4(VAct)_m2`<^+qax$H2r+b&s_TkFpl3t;B&WS;&+ zzDj-?Ri_%`Si^AK_zvME;FW4c-%JZ?4bC*df}WXX^;*5IP6{A1$%2lVY6TS5cl@T) z!K$##_d$Dy2ycZn|0osZOf`tAc*SxiTF&G5FY_^3`k_SAzwY9wZjAZYip77dEdE-_ zp%KeiL4Syp4l3}-02O**gvn#EsoI-s{>?DRSYdjVirq~yCzAOxYM#jWk~)tO#)>G# z%PhuvYk@ZhE5?d=e)Panlq?J>c+(88!1QyQ2yF-s;|B%N#EvAt$#Z)2*`$Yj+rYZ3 zn7~fzFzI|^IDYAHkt}Kb>dTp%T{3f z+rOdDuq44!IQ9y~Q)C*P5CLZIH5^V@bTc7mpoi}9Rm)xIpkS=K_ty+)xh?41qb9+X zuY(y};xvoOXcuP+sKjLU`IKHu$VH!+!(^ z!lUb&ris?a1D?go>k87=^SqjcoNlSOy~xWZ(_ge?qnKOY{B^xS!Rz1HX_ZmZdw;<| zC~MPqe;VfWaxU`N*IW{4K!MKyEo>RvQbddEZu(BtLVPHv7pYnGfXSmyBoK?0`~yi@ z`O%t^w{}$(IdHFq^bx@#q$tT94# z5jIRWYQ5DWjOf4)1GW?g0EX)}$U>|-3H4-N;9u2io(o#dNy`mz44gK}MOEaJ-Lc`Bf^*)rx9rWf0+N#MAwKgoeh9M;s4* z-OR#B$>63LSMlc)87M{*uGCL2I*VnfuEFo?vplrSB!$iBtZ+0-AypMADO$~m(wfIJwvwp-2D;@k{XRZr1Fkg$vTcY!Xr%Hf!(HDr-jw2bL|Z7-;5HW96LJ!!?cF z;~_TCqv6ZyeFO82h_D0?GTgAz9CvRX^Z$83&vg5Jv*X$Rr9QhdIR1Zltn&Tjd?_@1 zNpzHKE}M}j=R2;XI?cm0@8r|I}7MRK zFjTGZK&M%Pu>Lp8kM`_a=HR1Ep^+NVO>oY}S;gAqI{1=BajV4@wC867ajnIbwDR4o z0%ajgZr|EEE2L$yczc5t%Id`5)#!@b&nU_DyH-t^Fw^U9il_C+W!3RGj=aYVdSl1P zsFN0Kb;t;2zCxbYnzK10?MoZRGM8KQDehzvJTizQ13!Udkw&+yCYlPDl4P z7RI>`lvf)pmD~u`nRP>t<-K|q(t%ojE?mt)X;uXqAnV#*YZQ#4a8^lLP)09wJWXPx*a#}0J5r2w zfg3s^*R!qFZ`ckW^gmxfVX0Ob7HD1?28J$EB$5AfegL<36of^#|L!2@U#Bvv=)HXg zl({4HE~PMbdV>XToEJEo5(ttul$H<3!Xi&Y$4YtDt`cBnpsm!e82%a~(^93NsNvYL zXTWXhCZNdMlGUf0DxL{@IN8vgOuj)n2B52V*s zGEt;B9?#NtXaoN+c0}k6Uz+GbF}JQ>`HP0!{FOVqM^jZ9LdR~dgWp?k*w9jz|BG@5 zqK@fvn1i{7qubPQD1p3Vs7|%cVl$swtdZAJSm|#!TqU7oUHfRA3=&fDjb`^Js~mR9 zL-)fig*5ugJl@bxw1Ts^Lt?T_$gb2i{5GWMvKm$JApchbE1E%Sa)og`=#~>GUU$Di z>V>HWCWX}Sn4}1J-3a!1&a}PAaz_9|1K8c@EI_W6w@mR2K+>+_V4J{BrL~NXbT3Wo zXq{xxhEhdriF9njY`l2kkjHU@L6>dvCTW$hG!8u+Zq(cgZx z_VtE;Xa;-v?K*|+ka99V7~i;cPTqB%LcLN@Utd(CjkEKSdhP8Fe$mmA>;QqbW2( zx-?(mxZ0o)ox?ny!zz8u9T77+tE1d7hB$kcl||Z^83bl@dMFGk!-@ryEs7AC!<^Zc zl&ePmh59$ZzHxCOHIVnR5NkaUWP4@2>r_?M0*!qzK>%$Vv#8oGj`pVTm&NujREQTf z5$PEPZ%AR}sk~2nPr)q|FX&FIqE#8@cXN6R z$5AX!Ii9*kO_W+F12aw4+A3T+2DPY>XJK7&0>!ywO98&^XM#L7XYpo16J%y?9g`={ z=@!gwokId)Y5uyM`+`sBg6Opsk8?@%7tkSR7)-AFMUm`3kQ=&;%^0nXzC3eE5Azc0 z&i$MPp`N({{Cd6h+lkeBb{wZ*uG{xnG4PlmSACZa;RZZzSFS96`_3plU$vokx}PXc z`%ZmT2A%L*^haAamf@n)(w85oEZ$gt4~ZNLChHyTEE+5>kH52MqrJZd;rNmjfX)99Af%`x6Kx_2OSdz6pwfK$n9>Ha*VG(`RDL@F$&#)+C*E}Hqb z!MTgT4DY3MUs3yv&fK4%Am;^*32y_6bH!lJj~}G)`5$#bg`A8P1j^p%3enhMH0O^# zaARk_9V-9?_(o9QX~xGSiiF9DH-odlkTUZ0LW@P2b&)X^&A{=lW z{EccZbtW`3KLH{z&xlO6~RvDBqE&XfKMlw$FH zE{q4=I-c1E+-ECj0Mue}WNnlU{8DVcTWgc|1=7LP$nU0od_UR?3-RS3)T+_5F3nB(C zGciT9kcnyjY&_I@cjD+{YR8^HtL9H{{*Va5x}K>WVB40j8uG6d{Lso0l0lx@H-bbM#5HO6j#*glbS)XZB?!r(Tjj zD4ol4cw$MXW|=<-D(OgY!916r+sKd>4l`;qxWqa%eSdY8&;V(;JNPzl(kXmS1JDz( zY*Nv2ZV$>-Ftqb~rhZ$=;R$-Db73n)M(_xj%y`K_dPY3VdI*Gv?_kI!FH)kkOowk6 z$UuSykfUc7*~b7ro>OGZFZ{o@#6bjh%e`-#u5Vn&a5zp|F!sIQP}KsMZso`pYhCX} z#X9^Qdw%w1{SYte=gGHfWiZmLb^L~D~$_aZK9X11q!bmi;n`@HGdcY5v8^K7a5)hZ$BR-ktQ&}Nf}f!7S2 z#=9|P1oXx*%9(Tu%an*orH?R8rzX92g#G_A<%!Y)msQs5uh zPNRH@PA{PNPZUn>5J2g^ zyb9+d7_`QZc4gOYm-pTvDG2jasad4#+zs`p+V!!mI(-TX*+_&QrTQ-FhdU+mMUXTiSbzl(>wsyNw$GhKQ)%;k*pqYsd7s~n` z#Hicj$fUUuuQRvspW-_P?7t{FXj&3ZYM z1)-TGrCOzQ|K&HFi}6;e%b~KL2J!9;;lPwM{ef4Hy_vybXVz5ZgmV5PY9Lx_PEknM zoLm7bAcerequknGpy1be41^{|5Om#KWuh2VCW=5!PBK+2If2Zqo;lv}?gCON^slb* zm7Gh*^15JgBZMA!)9v{QHwpdXBt2inlP!X-F&(0$L+*-r#m;^$Co%b*bCbQf&(xa2 z_$)|zT8*8n8F!(*t;`iE9e>c$fQr&*D@?}Ou?*&kq>qPyg{Ifh>%X=&KJ0wQMInY82% z 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%0DjpR)XM3C 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

    w&@ivhh@#+1kT0DDdD%hI{Cg80D=`SMgEC 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�hR{$Ue8NC1QGogAksq zpwP)+le>gE1It84GlpV2!9zGaT+?V*?w2I`Dl_EbHrzGuJk#G#r@APz-!x~3rZZs;fW$|40Y~pWe{L29uDgHN5`QdG&qqNskktBb zED#tw848>tp*H?{rkn~O@F`vZfseJGL=$0r?a@ojx zm!-UQupS`zv5fu1r*^3j?+sW!q)137-%)n+Dv9e5_0Z9d6K+69?km-BXs2riaxdJS zr{9nRw*XB*vcD09zRG?FSG}~T8lJW-IXQ$d`DU}qb+VT#YwE3&eG^ZzcFJi5-^l|eTI{IjQgIn@vy*fIWb z(mO`~mO9m;E0XS^?Jo|35Z65vv(WI306le54|bAq)d%>-bLV0=mC-CH+GDX?Xai3o zBh)T?>S)6+1EpmWt1J5(htB?%gG({YkX_2|$y^{dqO0PJIo0G0Q1%!U`#_umHKdB; zA_pQkcZb6lFu0Mi3`}49ZNMX6fq}UyzE%ABl}+19UZM_tk_m8yTZ91)D$6˙j8O#c_tJ}Z?U#W$QVTbGR$me)z#5=$ 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

    }C!d*a12)O7xGAKQ6dKWL& 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%0AnUW;0la1ypB!DHi0m|c8Ejr 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 Izh2@04_+sF5dZ)H diff --git a/reference/广东燃料电池汽车示范应用城市群综合监管平台-燃料电池汽车数据接入技术规范v1.0.220822.pdf b/reference/广东燃料电池汽车示范应用城市群综合监管平台-燃料电池汽车数据接入技术规范v1.0.220822.pdf deleted file mode 100644 index ab50e8111813dabcdc752445a7cf5996cbde568c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5029401 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%0DMK!A!T4yDX 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%0AMZ(Bpo4s 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

    JkaBnh^Pg`un?&0k`2b4nENr%`HgzO7$Fw(x9 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%0Dc^ozA8ss;Ee)VM^`t=$*D!@O?!p0|X7`1Kf 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

    $z4RwXe$#T=O2ZcF+4rbl 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

    N0?=C*0(u-8 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Π6^hA+yz#&nBCl9|Ddd#Lu(H99a1cyRX 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 z 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�TLh`lem145Ik`JYqOQqks?VqnPGXMIV)&_)o& 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%0A>tW{5#D9Ah$k~O4rL+Q|Fq%a# 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%0Ad6kOJom@vH?z-$x#Vl>Fg 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 bytes: - text = path.read_text(encoding="utf-8") - return bytes.fromhex("".join(text.split())) - - -def parse_frame_info(frame: bytes) -> FrameInfo: - if len(frame) < 25: - raise ValueError(f"gb32960 frame too short: {len(frame)}") - if frame[:2] not in (b"##", b"$$"): - raise ValueError("gb32960 frame must start with ## or $$") - data_len = int.from_bytes(frame[22:24], "big") - expected_len = 24 + data_len + 1 - if len(frame) < expected_len: - raise ValueError(f"gb32960 frame incomplete: len={len(frame)} expected={expected_len}") - command = frame[2] - vin = frame[4:21].decode("ascii", errors="ignore").strip() - event_time = None - if command in (0x01, 0x02, 0x03, 0x04, 0x05) and data_len >= 6: - event_time = parse_collect_time(frame[24:30]) - return FrameInfo(vin=vin, command=command, event_time=event_time) - - -def parse_collect_time(raw: bytes) -> dt.datetime: - if len(raw) != 6: - raise ValueError("gb32960 collect time must be 6 bytes") - year, month, day, hour, minute, second = raw - return dt.datetime(2000 + year, month, day, hour, minute, second, tzinfo=SHANGHAI) - - -def query_range(event_time: dt.datetime | None, minutes: int) -> tuple[str, str]: - center = event_time or dt.datetime.now(tz=SHANGHAI) - center = center.astimezone(SHANGHAI) - start = center - dt.timedelta(minutes=minutes) - end = center + dt.timedelta(minutes=minutes) - return start.isoformat(timespec="seconds"), end.isoformat(timespec="seconds") - - -def send_frame(host: str, port: int, frame: bytes, timeout: float) -> bytes: - with socket.create_connection((host, port), timeout=timeout) as sock: - sock.settimeout(timeout) - sock.sendall(frame) - try: - return sock.recv(4096) - except socket.timeout: - return b"" - - -def valid_ack(ack: bytes) -> bool: - return len(ack) >= 2 and ack[:2] in (b"##", b"$$") - - -def query_json(base_url: str, path: str, params: dict[str, Any], timeout: float) -> Any: - url = base_url.rstrip("/") + path + "?" + urllib.parse.urlencode(params) - request = urllib.request.Request(url, headers={"Accept": "application/json"}) - with urllib.request.urlopen(request, timeout=timeout) as response: - return json.loads(response.read().decode("utf-8")) - - -def wait_for_records( - history_base_url: str, - vin: str, - date_from: str, - date_to: str, - minimum: int, - timeout_seconds: float, - request_timeout: float, -) -> list[dict[str, Any]]: - deadline = time.monotonic() + timeout_seconds - params = { - "vin": vin, - "dateFrom": date_from, - "dateTo": date_to, - "limit": max(minimum, 10), - "order": "ASC", - } - last_items: list[dict[str, Any]] = [] - while time.monotonic() < deadline: - response = query_json(history_base_url, "/api/event-history/gb32960/snapshots", params, request_timeout) - last_items = response if isinstance(response, list) else [] - if len(last_items) >= minimum: - return last_items - time.sleep(1) - return last_items - - -def wait_for_fields( - history_base_url: str, - vin: str, - field_key: str, - date_from: str, - date_to: str, - timeout_seconds: float, - request_timeout: float, -) -> list[dict[str, Any]]: - deadline = time.monotonic() + timeout_seconds - params = { - "vin": vin, - "fields": field_key, - "dateFrom": date_from, - "dateTo": date_to, - "limit": 10, - "order": "ASC", - } - last_items: list[dict[str, Any]] = [] - while time.monotonic() < deadline: - response = query_json(history_base_url, "/api/event-history/gb32960/snapshots/fields", params, request_timeout) - last_items = response if isinstance(response, list) else [] - if any((item.get("fields") or {}).get(field_key) is not None for item in last_items): - return last_items - time.sleep(1) - return last_items - - -def wait_for_tdengine_raw_frames( - rest_url: str, - username: str, - password: str, - sql: str, - minimum: int, - timeout_seconds: float, - request_timeout: float, -) -> int: - deadline = time.monotonic() + timeout_seconds - last_count = 0 - while time.monotonic() < deadline: - last_count = tdengine_smoke.query_count(rest_url, username, password, sql, request_timeout) - if last_count >= minimum: - return last_count - time.sleep(1) - return last_count - - -def parse_field_keys(value: str) -> list[str]: - return [item.strip() for item in value.split(",") if item.strip()] - - -def archive_path(root: pathlib.Path, uri: str) -> pathlib.Path: - prefix = "archive://" - if not uri.startswith(prefix): - raise ValueError(f"unsupported archive uri: {uri}") - return root / uri[len(prefix):] - - -def check_archive(root: pathlib.Path, records: list[dict[str, Any]], limit: int) -> int: - checked = 0 - seen: set[str] = set() - for uri in raw_uris(records): - if uri in seen: - continue - seen.add(uri) - path = archive_path(root, uri) - if not path.exists(): - raise RuntimeError(f"raw archive missing: {path}") - checked += 1 - if checked >= limit: - return checked - return checked - - -def raw_frame_count_sql(info: FrameInfo, records: list[dict[str, Any]]) -> str: - sqls = raw_frame_count_sqls(info, records) - if not sqls: - raise ValueError("no raw archive uri found in gb32960 records") - return sqls[0] - - -def raw_frame_count_sqls(info: FrameInfo, records: list[dict[str, Any]]) -> list[str]: - seen: set[str] = set() - sqls: list[str] = [] - for uri in raw_uris(records): - if uri in seen: - continue - seen.add(uri) - sqls.append(tdengine_smoke.raw_frame_count_sql( - protocol="GB32960", - vehicle_key=info.vin, - vin=info.vin, - raw_uri=uri, - )) - return sqls - - -def verify_tdengine_raw_frames( - rest_url: str, - username: str, - password: str, - sqls: list[str], - timeout_seconds: float, - request_timeout: float, -) -> int: - if not sqls: - raise RuntimeError("no raw archive uri available for tdengine raw_frames verification") - verified = 0 - for sql in sqls: - count = wait_for_tdengine_raw_frames( - rest_url, username, password, sql, 1, timeout_seconds, request_timeout) - if count < 1: - raise RuntimeError(f"tdengine raw_frames row not visible for query: {sql}") - verified += 1 - return verified - - -def run(args: argparse.Namespace) -> dict[str, Any]: - frame = read_hex_frame(pathlib.Path(args.frame_hex_file)) - info = parse_frame_info(frame) - query_center = dt.datetime.now(tz=SHANGHAI) - date_from, date_to = query_range(query_center, args.window_minutes) - - ack = send_frame(args.tcp_host, args.tcp_port, frame, args.tcp_timeout) - if args.require_ack and not valid_ack(ack): - raise RuntimeError(f"gb32960 ack missing or invalid: {ack.hex()}") - - records = wait_for_records( - args.history_base_url, - info.vin, - date_from, - date_to, - args.expect_record_count, - args.history_timeout, - args.http_timeout, - ) - if len(records) < args.expect_record_count: - raise RuntimeError(f"gb32960 records not visible for {info.vin}: got {len(records)}") - - field_counts: dict[str, int] = {} - for field_key in parse_field_keys(args.field_keys): - field_items = wait_for_fields( - args.history_base_url, - info.vin, - field_key, - date_from, - date_to, - args.history_timeout, - args.http_timeout, - ) - field_counts[field_key] = len(field_items) - if not field_items: - raise RuntimeError(f"gb32960 telemetry field not visible for {info.vin}: {field_key}") - - archive_checked = 0 - if args.archive_root: - archive_checked = check_archive(pathlib.Path(args.archive_root), records, args.archive_check_limit) - - tdengine_raw_frames = None - if args.tdengine_rest_url: - tdengine_raw_frames = verify_tdengine_raw_frames( - args.tdengine_rest_url, - args.tdengine_username, - args.tdengine_password, - raw_frame_count_sqls(info, records), - args.history_timeout, - args.http_timeout, - ) - - return { - "tcpHost": args.tcp_host, - "tcpPort": args.tcp_port, - "vin": info.vin, - "command": f"0x{info.command:02x}", - "eventTime": info.event_time.isoformat() if info.event_time else None, - "queryTimeSource": "received", - "queryDateFrom": date_from, - "queryDateTo": date_to, - "ackBytesHex": ack.hex(), - "records": len(records), - "fieldCounts": field_counts, - "tdengineRawFrames": tdengine_raw_frames, - "archiveChecked": archive_checked, - "firstRawUri": first_raw_uri(records), - } - - -def first_raw_uri(records: list[dict[str, Any]]) -> str | None: - uris = raw_uris(records) - if uris: - return uris[0] - return None - - -def raw_uris(records: list[dict[str, Any]]) -> list[str]: - out: list[str] = [] - for record in records: - uri = record.get("rawArchiveUri") - if isinstance(uri, str) and uri: - out.append(uri) - for frame in record.get("sourceFrames") or []: - frame_uri = frame.get("rawArchiveUri") if isinstance(frame, dict) else "" - if frame_uri: - out.append(frame_uri) - return out - - -def parser() -> argparse.ArgumentParser: - p = argparse.ArgumentParser(description=__doc__) - p.add_argument("--tcp-host", default="127.0.0.1") - p.add_argument("--tcp-port", type=int, default=32960) - p.add_argument("--history-base-url", default="http://127.0.0.1:20200") - p.add_argument("--frame-hex-file", default=DEFAULT_SAMPLE) - p.add_argument("--field-keys", default=DEFAULT_FIELD_KEYS) - p.add_argument("--window-minutes", type=int, default=10) - p.add_argument("--expect-record-count", type=int, default=1) - p.add_argument("--archive-root", default="") - p.add_argument("--archive-check-limit", type=int, default=1) - p.add_argument("--tdengine-rest-url", default=os.environ.get("TDENGINE_REST_URL", "")) - p.add_argument("--tdengine-username", default=os.environ.get("TDENGINE_USERNAME", "root")) - p.add_argument("--tdengine-password", default=os.environ.get("TDENGINE_PASSWORD", "taosdata")) - p.add_argument("--require-ack", action="store_true", default=True) - p.add_argument("--tcp-timeout", type=float, default=5) - p.add_argument("--http-timeout", type=float, default=5) - p.add_argument("--history-timeout", type=float, default=60) - return p - - -def main() -> None: - args = parser().parse_args() - summary = run(args) - print(json.dumps(summary, ensure_ascii=False, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/tools/go_kafka_prod_smoke.py b/tools/go_kafka_prod_smoke.py deleted file mode 100755 index 06e0c12a..00000000 --- a/tools/go_kafka_prod_smoke.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python3 -"""Kafka topic and consumer lag smoke checks for the Go native production stack.""" - -from __future__ import annotations - -import argparse -import json -import subprocess -import sys -from dataclasses import asdict, dataclass - - -DEFAULT_HOST = "114.55.58.251" -DEFAULT_USER = "root" -DEFAULT_BOOTSTRAP = "127.0.0.1:9092" -DEFAULT_KAFKA_BIN = "/opt/kafka/current/bin" -DEFAULT_TOPICS = [ - "vehicle.raw.go.gb32960.v1", - "vehicle.raw.go.jt808.v1", - "vehicle.raw.go.yutong-mqtt.v1", - "vehicle.event.go.unified.v1", -] -DEFAULT_GROUPS = [ - "go-history-writer", - "go-stat-writer", - "go-realtime-api", -] - - -@dataclass(frozen=True) -class Check: - name: str - status: str - message: str - - -@dataclass(frozen=True) -class ConsumerLag: - group: str - topic: str - partition: int - current_offset: int | None - log_end_offset: int - lag: int | None - - -def parse_topic_list(output: str) -> set[str]: - return {line.strip() for line in output.splitlines() if line.strip()} - - -def topic_checks(existing: set[str], required: list[str]) -> list[Check]: - checks: list[Check] = [] - for topic in required: - if topic in existing: - checks.append(Check("topic." + topic, "pass", "present")) - else: - checks.append(Check("topic." + topic, "fail", "missing")) - return checks - - -def parse_consumer_group_describe(output: str) -> list[ConsumerLag]: - rows: list[ConsumerLag] = [] - for line in output.splitlines(): - parts = line.split() - if len(parts) < 6 or parts[0] == "GROUP": - continue - group, topic = parts[0], parts[1] - try: - partition = int(parts[2]) - current = parse_optional_int(parts[3]) - log_end = int(parts[4]) - lag = parse_optional_int(parts[5]) - except ValueError: - continue - rows.append(ConsumerLag(group, topic, partition, current, log_end, lag)) - return rows - - -def parse_optional_int(value: str) -> int | None: - value = value.strip() - if value == "-": - return None - return int(value) - - -def group_lag_check(group: str, rows: list[ConsumerLag], max_lag: int) -> Check: - group_rows = [row for row in rows if row.group == group] - if not group_rows: - return Check("group." + group, "fail", "no rows") - concrete_lags = [row.lag for row in group_rows if row.lag is not None] - if not concrete_lags: - return Check("group." + group, "fail", "no concrete lag rows") - max_seen = max(concrete_lags) - topics = sorted({row.topic for row in group_rows}) - status = "pass" if max_seen <= max_lag else "fail" - return Check( - "group." + group, - status, - "max_lag=" + str(max_seen) + "; threshold=" + str(max_lag) + "; topics=" + ",".join(topics), - ) - - -def ssh(host: str, user: str, command: str, timeout: float) -> str: - target = user + "@" + host if user else host - completed = subprocess.run( - ["ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", target, command], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - timeout=timeout, - ) - return completed.stdout - - -def remote_topic_command(kafka_bin: str, bootstrap: str) -> str: - return kafka_bin.rstrip("/") + "/kafka-topics.sh --bootstrap-server " + bootstrap + " --list" - - -def remote_group_command(kafka_bin: str, bootstrap: str, group: str) -> str: - return kafka_bin.rstrip("/") + "/kafka-consumer-groups.sh --bootstrap-server " + bootstrap + " --describe --group " + group - - -def run(args: argparse.Namespace) -> tuple[str, list[Check]]: - topic_output = ssh(args.host, args.user, remote_topic_command(args.kafka_bin, args.bootstrap), args.timeout) - checks = topic_checks(parse_topic_list(topic_output), DEFAULT_TOPICS) - for group in DEFAULT_GROUPS: - group_output = ssh(args.host, args.user, remote_group_command(args.kafka_bin, args.bootstrap, group), args.timeout) - checks.append(group_lag_check(group, parse_consumer_group_describe(group_output), args.max_lag)) - status = "fail" if any(check.status == "fail" for check in checks) else "pass" - return status, checks - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--host", default=DEFAULT_HOST) - parser.add_argument("--user", default=DEFAULT_USER) - parser.add_argument("--bootstrap", default=DEFAULT_BOOTSTRAP) - parser.add_argument("--kafka-bin", default=DEFAULT_KAFKA_BIN) - parser.add_argument("--max-lag", type=int, default=100) - parser.add_argument("--timeout", type=float, default=20.0) - return parser.parse_args(argv) - - -def main(argv: list[str]) -> int: - args = parse_args(argv) - try: - status, checks = run(args) - except Exception as exc: - status = "fail" - checks = [Check("kafka.ssh", "fail", str(exc))] - print(json.dumps({ - "status": status, - "host": args.host, - "bootstrap": args.bootstrap, - "checks": [asdict(check) for check in checks], - }, ensure_ascii=False, indent=2)) - return 0 if status == "pass" else 1 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) diff --git a/tools/go_native_deploy.py b/tools/go_native_deploy.py deleted file mode 100644 index 8d7e5358..00000000 --- a/tools/go_native_deploy.py +++ /dev/null @@ -1,253 +0,0 @@ -#!/usr/bin/env python3 -"""Build and deploy the Go native vehicle gateway release to ECS.""" - -from __future__ import annotations - -import argparse -import os -import pathlib -import shutil -import subprocess -import sys -import tarfile -import tempfile -import time -from dataclasses import dataclass - - -DEFAULT_APP_HOST = "115.29.187.205" -DEFAULT_KAFKA_HOST = "114.55.58.251" -DEFAULT_USER = "root" -DEFAULT_RELEASE_ROOT = "/opt/lingniu-go-native" -DEFAULT_GO_DIR = "go/vehicle-gateway" -BINARIES = [ - ("gateway", "./cmd/gateway"), - ("history-writer", "./cmd/history-writer"), - ("stat-writer", "./cmd/stat-writer"), - ("realtime-api", "./cmd/realtime-api"), -] -SERVICES = [ - "lingniu-go-gateway", - "lingniu-go-history-writer", - "lingniu-go-stat-writer", - "lingniu-go-realtime-api", -] - - -@dataclass(frozen=True) -class BuildCommand: - output_name: str - argv: list[str] - env: list[str] - cwd: str - - -@dataclass(frozen=True) -class SpoolStatus: - files: int - recent: int - - -def git_short_sha(cwd: str) -> str: - return run_capture(["git", "rev-parse", "--short=7", "HEAD"], cwd=cwd).strip() - - -def build_commands(go_dir: str, output_dir: str) -> list[BuildCommand]: - commands: list[BuildCommand] = [] - env = ["CGO_ENABLED=0", "GOOS=linux", "GOARCH=amd64"] - for output_name, package in BINARIES: - commands.append(BuildCommand( - output_name=output_name, - argv=[ - "go", - "build", - "-trimpath", - "-ldflags=-s -w", - "-o", - str(pathlib.Path(output_dir) / output_name), - package, - ], - env=env, - cwd=go_dir, - )) - return commands - - -def build_release(go_dir: str, output_dir: str) -> None: - for command in build_commands(go_dir, output_dir): - env = os.environ.copy() - for item in command.env: - key, value = item.split("=", 1) - env[key] = value - run(command.argv, cwd=command.cwd, env=env) - - -def create_archive(source_dir: str, archive_path: str) -> None: - with tarfile.open(archive_path, "w:gz") as archive: - for binary, _ in BINARIES: - archive.add(pathlib.Path(source_dir) / binary, arcname=binary) - - -def remote_switch_command(release: str, release_root: str, remote_archive: str) -> str: - if not safe_release_name(release): - raise ValueError("release must contain only letters, numbers, dot, underscore, or dash") - release_dir = release_root.rstrip("/") + "/releases/" + release - root = release_root.rstrip("/") - commands = [ - "set -euo pipefail", - "mkdir -p " + shell_quote(release_dir), - "tar -xzf " + shell_quote(remote_archive) + " -C " + shell_quote(release_dir), - "chmod +x " + shell_quote(release_dir) + "/*", - "ln -sfn " + shell_quote(release_dir) + " " + shell_quote(root + "/current"), - "systemctl daemon-reload", - ] - commands.extend("systemctl restart " + service for service in SERVICES) - commands.append("rm -f " + shell_quote(remote_archive)) - return " && ".join(commands) - - -def acceptance_command(app_host: str, kafka_host: str, date: str, timeout: float) -> list[str]: - command = [ - "python3", - "tools/go_prod_acceptance.py", - "--app-host", - app_host, - "--kafka-host", - kafka_host, - "--timeout", - str(int(timeout) if timeout == int(timeout) else timeout), - ] - if date: - command.extend(["--date", date]) - return command - - -def deploy(args: argparse.Namespace) -> None: - repo = pathlib.Path(args.repo).resolve() - go_dir = str(repo / args.go_dir) - release = args.release or git_short_sha(str(repo)) - with tempfile.TemporaryDirectory(prefix="lingniu-go-native-") as tmp: - output_dir = pathlib.Path(tmp) / "out" - output_dir.mkdir() - archive_path = pathlib.Path(tmp) / ("lingniu-go-native-" + release + ".tar.gz") - build_release(go_dir, str(output_dir)) - create_archive(str(output_dir), str(archive_path)) - remote_archive = "/tmp/" + archive_path.name - target = ssh_target(args.user, args.app_host) - run(["scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", str(archive_path), target + ":" + remote_archive]) - run([ - "ssh", - "-o", - "StrictHostKeyChecking=no", - "-o", - "UserKnownHostsFile=/dev/null", - target, - remote_switch_command(release, args.release_root, remote_archive), - ]) - wait_for_spool_drain(target, args.release_root, args.spool_drain_timeout, args.spool_poll_interval) - if not args.skip_acceptance: - run(acceptance_command(args.app_host, args.kafka_host, args.date, args.timeout), cwd=str(repo)) - - -def wait_for_spool_drain(target: str, release_root: str, timeout: float, poll_interval: float) -> None: - deadline = time.monotonic() + timeout - last = SpoolStatus(files=-1, recent=-1) - while time.monotonic() <= deadline: - output = run_capture([ - "ssh", - "-o", - "StrictHostKeyChecking=no", - "-o", - "UserKnownHostsFile=/dev/null", - target, - spool_status_command(release_root), - ]) - last = parse_spool_status(output) - print(f"spool files={last.files} recent={last.recent}", flush=True) - if last.files == 0: - return - time.sleep(max(1.0, poll_interval)) - raise RuntimeError(f"gateway spool did not drain: files={last.files} recent={last.recent}") - - -def spool_status_command(release_root: str) -> str: - spool_dir = release_root.rstrip("/") + "/spool/gateway" - return ( - "spool=" + shell_quote(spool_dir) + "; " - "files=$(find \"$spool\" -type f 2>/dev/null | wc -l); " - "recent=$(find \"$spool\" -type f -mmin -5 2>/dev/null | wc -l); " - "printf 'files=%s recent=%s\\n' \"$files\" \"$recent\"" - ) - - -def parse_spool_status(output: str) -> SpoolStatus: - values: dict[str, int] = {} - for part in output.split(): - if "=" not in part: - continue - key, value = part.split("=", 1) - try: - values[key] = int(value) - except ValueError: - continue - return SpoolStatus(files=values.get("files", -1), recent=values.get("recent", -1)) - - -def ssh_target(user: str, host: str) -> str: - return user + "@" + host if user else host - - -def shell_quote(value: str) -> str: - return "'" + value.replace("'", "'\"'\"'") + "'" - - -def safe_release_name(value: str) -> bool: - value = value.strip() - if not value: - return False - return all(r.isalnum() or r in "._-" for r in value) - - -def run(argv: list[str], cwd: str | None = None, env: dict[str, str] | None = None) -> None: - subprocess.run(argv, cwd=cwd, env=env, check=True) - - -def run_capture(argv: list[str], cwd: str | None = None) -> str: - return subprocess.run(argv, cwd=cwd, check=True, stdout=subprocess.PIPE, text=True).stdout - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo", default=".") - parser.add_argument("--go-dir", default=DEFAULT_GO_DIR) - parser.add_argument("--app-host", default=DEFAULT_APP_HOST) - parser.add_argument("--kafka-host", default=DEFAULT_KAFKA_HOST) - parser.add_argument("--user", default=DEFAULT_USER) - parser.add_argument("--release-root", default=DEFAULT_RELEASE_ROOT) - parser.add_argument("--release", default="") - parser.add_argument("--date", default="") - parser.add_argument("--timeout", type=float, default=20.0) - parser.add_argument("--spool-drain-timeout", type=float, default=900.0) - parser.add_argument("--spool-poll-interval", type=float, default=10.0) - parser.add_argument("--skip-acceptance", action="store_true") - return parser.parse_args(argv) - - -def main(argv: list[str]) -> int: - args = parse_args(argv) - try: - deploy(args) - except subprocess.CalledProcessError as exc: - print("command failed: " + " ".join(exc.cmd), file=sys.stderr) - return exc.returncode or 1 - except (OSError, shutil.Error) as exc: - print("deploy failed: " + str(exc), file=sys.stderr) - return 1 - except RuntimeError as exc: - print("deploy failed: " + str(exc), file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) diff --git a/tools/go_native_prod_smoke.py b/tools/go_native_prod_smoke.py deleted file mode 100755 index 77c11999..00000000 --- a/tools/go_native_prod_smoke.py +++ /dev/null @@ -1,544 +0,0 @@ -#!/usr/bin/env python3 -"""HTTP smoke checks for the Go native production vehicle ingest stack.""" - -from __future__ import annotations - -import argparse -import datetime as dt -import json -import sys -import urllib.parse -import urllib.request -from dataclasses import asdict, dataclass -from typing import Any - - -SHANGHAI = dt.timezone(dt.timedelta(hours=8)) -DEFAULT_BASE_URL = "http://115.29.187.205:20210" -DEFAULT_REALTIME_MAX_AGE_MINUTES = 15.0 - - -@dataclass(frozen=True) -class CheckSpec: - name: str - path: str - params: dict[str, Any] - minimum: int - kind: str = "total" - max_age_minutes: float | None = None - require_parsed_json: bool = False - require_frame_id: bool = False - require_metric_formula: bool = False - - -@dataclass(frozen=True) -class Check: - name: str - status: str - count: int - minimum: int - message: str - - -@dataclass(frozen=True) -class CheckWindow: - date_from: str - date_to: str - stat_date: str - max_raw_age_minutes: float | None - - -def shanghai_day_window(now: dt.datetime | None = None) -> tuple[str, str]: - now = now or dt.datetime.now(tz=SHANGHAI) - local = now.astimezone(SHANGHAI) - start = dt.datetime(local.year, local.month, local.day, tzinfo=SHANGHAI) - end = start + dt.timedelta(days=1) - return start.isoformat(timespec="seconds"), end.isoformat(timespec="seconds") - - -def resolve_check_window( - raw_date: str | None, - max_raw_age_minutes: float, - now: dt.datetime | None = None, -) -> CheckWindow: - current = (now or dt.datetime.now(tz=SHANGHAI)).astimezone(SHANGHAI) - today = current.date() - if raw_date: - day = dt.date.fromisoformat(raw_date) - start = dt.datetime(day.year, day.month, day.day, tzinfo=SHANGHAI) - date_from, date_to = shanghai_day_window(start) - freshness = max_raw_age_minutes if day == today else None - return CheckWindow(date_from, date_to, raw_date, freshness) - date_from, date_to = shanghai_day_window(current) - return CheckWindow(date_from, date_to, date_from[:10], max_raw_age_minutes) - - -def api_url(base_url: str, path: str, params: dict[str, Any]) -> str: - url = base_url.rstrip("/") + path - if not params: - return url - return url + "?" + urllib.parse.urlencode(params) - - -def query_json(base_url: str, path: str, params: dict[str, Any], timeout: float) -> dict[str, Any]: - request = urllib.request.Request( - api_url(base_url, path, params), - headers={"Accept": "application/json"}, - ) - with urllib.request.urlopen(request, timeout=timeout) as response: - return json.loads(response.read().decode("utf-8")) - - -def check_total( - name: str, - payload: dict[str, Any], - minimum: int, - max_age_minutes: float | None = None, - now: dt.datetime | None = None, - require_parsed_json: bool = False, - require_frame_id: bool = False, - require_metric_formula: bool = False, -) -> Check: - count = int(payload.get("total") or 0) - items = payload.get("items") or [] - sample = sample_summary(items) - age_message = latest_age_message(items, now) - if count >= minimum: - parsed_message = "" - if max_age_minutes is not None: - latest_age = latest_age_minutes(items, now) - if latest_age is None: - return Check(name, "fail", count, minimum, f"count={count}; missing latest ts; sample={sample}") - if latest_age > max_age_minutes: - return Check( - name, - "fail", - count, - minimum, - f"count={count}; latest_age_minutes={latest_age:.1f}; " - f"expected <= {max_age_minutes:.1f}; sample={sample}", - ) - if require_parsed_json: - parsed_message = parsed_json_message(items) - if not parsed_message.startswith("parsed_json=ok"): - return Check(name, "fail", count, minimum, f"count={count}; {parsed_message}; sample={sample}") - frame_message = "" - if require_frame_id: - frame_message = frame_id_message(items) - if not frame_message.startswith("frame_id=ok"): - return Check(name, "fail", count, minimum, f"count={count}; {frame_message}; sample={sample}") - metric_message = "" - if require_metric_formula: - metric_message = metric_formula_message(items) - if not metric_message.startswith("metric_formula=ok"): - return Check(name, "fail", count, minimum, f"count={count}; {metric_message}; sample={sample}") - details = [f"count={count}", age_message] - if parsed_message: - details.append(parsed_message) - if frame_message: - details.append(frame_message) - if metric_message: - details.append(metric_message) - return Check(name, "pass", count, minimum, "; ".join(details) + f"; sample={sample}") - return Check(name, "fail", count, minimum, f"count={count}; expected >= {minimum}; {age_message}; sample={sample}") - - -def parse_tdengine_utc_timestamp(value: str) -> dt.datetime: - value = str(value or "").strip() - for layout in ("%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"): - try: - return dt.datetime.strptime(value[:19], layout).replace(tzinfo=dt.timezone.utc) - except ValueError: - continue - raise ValueError(f"unsupported timestamp: {value}") - - -def latest_age_minutes(items: list[Any], now: dt.datetime | None = None) -> float | None: - if not items or not isinstance(items[0], dict): - return None - timestamp = items[0].get("received_at") or items[0].get("ts") - if not timestamp: - return None - current = now or dt.datetime.now(tz=dt.timezone.utc) - if current.tzinfo is None: - current = current.replace(tzinfo=dt.timezone.utc) - latest = parse_tdengine_utc_timestamp(str(timestamp)) - return max(0.0, (current.astimezone(dt.timezone.utc) - latest).total_seconds() / 60) - - -def latest_age_message(items: list[Any], now: dt.datetime | None = None) -> str: - latest_age = latest_age_minutes(items, now) - if latest_age is None: - return "latest_age_minutes=unknown" - return f"latest_age_minutes={latest_age:.1f}" - - -def parsed_json_message(items: list[Any]) -> str: - if not items or not isinstance(items[0], dict): - return "missing parsed_json" - raw = items[0].get("parsed_json") - if isinstance(raw, dict) and raw: - return "parsed_json=ok" - if not isinstance(raw, str) or not raw.strip() or raw.strip().lower() == "null": - return "missing parsed_json" - try: - parsed = json.loads(raw) - except json.JSONDecodeError: - return "invalid parsed_json" - if isinstance(parsed, dict) and parsed: - return "parsed_json=ok" - return "invalid parsed_json" - - -def frame_id_message(items: list[Any]) -> str: - if not items or not isinstance(items[0], dict): - return "missing frame_id" - frame_id = str(items[0].get("frame_id") or "").strip() - if frame_id: - return "frame_id=ok" - return "missing frame_id" - - -def metric_formula_message(items: list[Any]) -> str: - if not items or not isinstance(items[0], dict): - return "missing metric row" - row = items[0] - try: - metric_value = float(row.get("metric_value")) - first_total = float(row.get("first_total_mileage_km")) - latest_total = float(row.get("latest_total_mileage_km")) - except (TypeError, ValueError): - return "missing metric formula fields" - metric_key = str(row.get("metric_key") or "") - if metric_key == "daily_mileage_km": - expected = max(0.0, latest_total - first_total) - elif metric_key == "daily_total_mileage_km": - expected = latest_total - else: - return "unsupported metric_key=" + metric_key - if abs(metric_value - expected) <= 0.001: - return "metric_formula=ok" - return f"metric_formula mismatch value={metric_value:.3f} expected={expected:.3f}" - - -def check_realtime( - name: str, - payload: dict[str, Any], - max_age_minutes: float | None = None, - now: dt.datetime | None = None, -) -> Check: - if payload.get("online") is False: - return Check(name, "fail", 0, 1, "online=false") - updated_message = "" - if max_age_minutes is not None: - updated_age = realtime_updated_age_minutes(payload, now) - if updated_age is None: - return Check(name, "fail", 0, 1, "missing updated_at_ms") - updated_message = f"updated_age_minutes={updated_age:.1f}" - if updated_age > max_age_minutes: - return Check( - name, - "fail", - 0, - 1, - updated_message + f"; expected <= {max_age_minutes:.1f}", - ) - identifier = payload.get("vin") or payload.get("vehicle_key") or "" - protocols = payload.get("protocols") or [] - protocol = payload.get("protocol") or "" - fields = payload.get("fields") or {} - message = json.dumps( - { - key: value - for key, value in { - "identifier": identifier, - "protocol": protocol, - "protocols": protocols, - "field_count": len(fields) if isinstance(fields, dict) else 0, - "online": payload.get("online"), - }.items() - if value not in (None, "", []) - }, - ensure_ascii=False, - sort_keys=True, - ) - if updated_message: - message = updated_message + "; " + message - return Check(name, "pass", 1, 1, message) - - -def realtime_updated_age_minutes(payload: dict[str, Any], now: dt.datetime | None = None) -> float | None: - try: - updated_ms = int(payload.get("updated_at_ms")) - except (TypeError, ValueError): - return None - current = now or dt.datetime.now(tz=dt.timezone.utc) - if current.tzinfo is None: - current = current.replace(tzinfo=dt.timezone.utc) - updated = dt.datetime.fromtimestamp(updated_ms / 1000, tz=dt.timezone.utc) - return max(0.0, (current.astimezone(dt.timezone.utc) - updated).total_seconds() / 60) - - -def sample_summary(items: list[Any]) -> str: - if not items: - return "{}" - first = items[0] - if not isinstance(first, dict): - return json.dumps(first, ensure_ascii=False, sort_keys=True) - keep = { - key: first.get(key) - for key in ( - "ts", - "stat_date", - "protocol", - "vehicle_key", - "vin", - "phone", - "message_id_hex", - "metric_key", - "metric_value", - "total_mileage_km", - "latest_total_mileage_km", - ) - if first.get(key) not in (None, "") - } - return json.dumps(keep, ensure_ascii=False, sort_keys=True) - - -def overall_status(checks: list[Check]) -> str: - return "fail" if any(check.status == "fail" for check in checks) else "pass" - - -def build_check_specs( - *, - date_from: str, - date_to: str, - stat_date: str, - min_raw: int, - min_history: int, - min_stat: int, - max_raw_age_minutes: float | None = None, -) -> list[CheckSpec]: - raw_params = {"limit": 1, "orderBy": "receivedAt", "includeTotal": "false"} - gb32960_history_params = {"protocol": "GB32960", "dateFrom": date_from, "dateTo": date_to, "limit": 1} - jt808_history_params = {"protocol": "JT808", "dateFrom": date_from, "dateTo": date_to, "limit": 1} - yutong_mqtt_history_params = {"protocol": "YUTONG_MQTT", "dateFrom": date_from, "dateTo": date_to, "limit": 1} - return [ - CheckSpec( - "gb32960.raw", - "/api/history/raw-frames", - {**raw_params, "protocol": "GB32960"}, - min_raw, - max_age_minutes=max_raw_age_minutes, - require_parsed_json=True, - ), - CheckSpec( - "jt808.raw", - "/api/history/raw-frames", - {**raw_params, "protocol": "JT808"}, - min_raw, - max_age_minutes=max_raw_age_minutes, - require_parsed_json=True, - ), - CheckSpec( - "yutong_mqtt.raw", - "/api/history/raw-frames", - {**raw_params, "protocol": "YUTONG_MQTT"}, - min_raw, - max_age_minutes=max_raw_age_minutes, - require_parsed_json=True, - ), - CheckSpec("gb32960.locations", "/api/history/locations", gb32960_history_params, min_history, require_frame_id=True), - CheckSpec("gb32960.mileage_points", "/api/history/mileage-points", gb32960_history_params, min_history, require_frame_id=True), - CheckSpec("jt808.locations", "/api/history/locations", jt808_history_params, min_history, require_frame_id=True), - CheckSpec("jt808.mileage_points", "/api/history/mileage-points", jt808_history_params, min_history, require_frame_id=True), - CheckSpec("yutong_mqtt.locations", "/api/history/locations", yutong_mqtt_history_params, min_history, require_frame_id=True), - CheckSpec("yutong_mqtt.mileage_points", "/api/history/mileage-points", yutong_mqtt_history_params, min_history, require_frame_id=True), - CheckSpec( - "gb32960.daily_mileage", - "/api/stats/daily-metrics", - { - "protocol": "GB32960", - "metricKey": "daily_mileage_km", - "dateFrom": stat_date, - "dateTo": stat_date, - "limit": 1, - }, - min_stat, - require_metric_formula=True, - ), - CheckSpec( - "gb32960.daily_total_mileage", - "/api/stats/daily-metrics", - { - "protocol": "GB32960", - "metricKey": "daily_total_mileage_km", - "dateFrom": stat_date, - "dateTo": stat_date, - "limit": 1, - }, - min_stat, - require_metric_formula=True, - ), - CheckSpec( - "jt808.daily_mileage", - "/api/stats/daily-metrics", - { - "protocol": "JT808", - "metricKey": "daily_mileage_km", - "dateFrom": stat_date, - "dateTo": stat_date, - "limit": 1, - }, - min_stat, - require_metric_formula=True, - ), - CheckSpec( - "jt808.daily_total_mileage", - "/api/stats/daily-metrics", - { - "protocol": "JT808", - "metricKey": "daily_total_mileage_km", - "dateFrom": stat_date, - "dateTo": stat_date, - "limit": 1, - }, - min_stat, - require_metric_formula=True, - ), - ] - - -def vehicle_identifier(item: dict[str, Any]) -> str: - for key in ("vin", "vehicle_key"): - value = str(item.get(key) or "").strip() - if value and value.lower() != "unknown": - return value - return "" - - -def build_realtime_specs(payloads: dict[str, dict[str, Any]]) -> list[CheckSpec]: - configs = [ - ("gb32960", "gb32960.raw", "GB32960"), - ("jt808", "jt808.raw", "JT808"), - ("yutong_mqtt", "yutong_mqtt.raw", "YUTONG_MQTT"), - ] - specs: list[CheckSpec] = [] - for prefix, source_name, protocol in configs: - items = payloads.get(source_name, {}).get("items") or [] - if not items or not isinstance(items[0], dict): - continue - identifier = vehicle_identifier(items[0]) - if not identifier: - continue - encoded = urllib.parse.quote(identifier, safe="") - base_path = "/api/realtime/vehicles/" + encoded - specs.extend([ - CheckSpec( - prefix + ".realtime", - base_path, - {}, - 1, - "realtime", - max_age_minutes=DEFAULT_REALTIME_MAX_AGE_MINUTES, - ), - CheckSpec(prefix + ".realtime_online", base_path + "/online", {}, 1, "realtime"), - CheckSpec( - prefix + ".realtime_protocol", - base_path + "/protocols/" + protocol, - {}, - 1, - "realtime", - max_age_minutes=DEFAULT_REALTIME_MAX_AGE_MINUTES, - ), - ]) - return specs - - -def run_checks(base_url: str, specs: list[CheckSpec], timeout: float) -> list[Check]: - checks: list[Check] = [] - for spec in specs: - try: - payload = query_json(base_url, spec.path, spec.params, timeout) - if spec.kind == "realtime": - checks.append(check_realtime(spec.name, payload, spec.max_age_minutes)) - else: - checks.append(check_total( - spec.name, - payload, - spec.minimum, - spec.max_age_minutes, - require_parsed_json=spec.require_parsed_json, - require_frame_id=spec.require_frame_id, - require_metric_formula=spec.require_metric_formula, - )) - except Exception as exc: # pragma: no cover - exercised by live failures. - checks.append(Check(spec.name, "fail", 0, spec.minimum, f"request failed: {exc}")) - return checks - - -def query_payloads(base_url: str, specs: list[CheckSpec], timeout: float) -> tuple[list[Check], dict[str, dict[str, Any]]]: - checks: list[Check] = [] - payloads: dict[str, dict[str, Any]] = {} - for spec in specs: - try: - payload = query_json(base_url, spec.path, spec.params, timeout) - payloads[spec.name] = payload - checks.append(check_total( - spec.name, - payload, - spec.minimum, - spec.max_age_minutes, - require_parsed_json=spec.require_parsed_json, - require_frame_id=spec.require_frame_id, - require_metric_formula=spec.require_metric_formula, - )) - except Exception as exc: - checks.append(Check(spec.name, "fail", 0, spec.minimum, f"request failed: {exc}")) - return checks, payloads - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--base-url", default=DEFAULT_BASE_URL) - parser.add_argument("--date", help="Shanghai local date, YYYY-MM-DD. Defaults to today.") - parser.add_argument("--timeout", type=float, default=5.0) - parser.add_argument("--min-raw", type=int, default=1) - parser.add_argument("--min-history", type=int, default=1) - parser.add_argument("--min-stat", type=int, default=1) - parser.add_argument( - "--max-raw-age-minutes", - type=float, - default=15.0, - help="Require latest RAW sample age to be at most this many minutes when checking today's date.", - ) - return parser.parse_args(argv) - - -def main(argv: list[str]) -> int: - args = parse_args(argv) - window = resolve_check_window(args.date, args.max_raw_age_minutes) - specs = build_check_specs( - date_from=window.date_from, - date_to=window.date_to, - stat_date=window.stat_date, - min_raw=args.min_raw, - min_history=args.min_history, - min_stat=args.min_stat, - max_raw_age_minutes=window.max_raw_age_minutes, - ) - checks, payloads = query_payloads(args.base_url, specs, args.timeout) - checks.extend(run_checks(args.base_url, build_realtime_specs(payloads), args.timeout)) - status = overall_status(checks) - print(json.dumps({ - "status": status, - "baseUrl": args.base_url, - "dateFrom": window.date_from, - "dateTo": window.date_to, - "checks": [asdict(check) for check in checks], - }, ensure_ascii=False, indent=2)) - return 0 if status == "pass" else 1 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) diff --git a/tools/go_prod_acceptance.py b/tools/go_prod_acceptance.py deleted file mode 100755 index d9a3c9de..00000000 --- a/tools/go_prod_acceptance.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -"""Run the Go native production acceptance smoke suite.""" - -from __future__ import annotations - -import argparse -import json -import subprocess -import sys -from dataclasses import asdict, dataclass -from typing import Any - - -DEFAULT_APP_HOST = "115.29.187.205" -DEFAULT_KAFKA_HOST = "114.55.58.251" -DEFAULT_USER = "root" -DEFAULT_BASE_URL = "http://115.29.187.205:20210" - - -@dataclass(frozen=True) -class ChildCommand: - name: str - argv: list[str] - - -@dataclass(frozen=True) -class ChildResult: - name: str - status: str - exit_code: int - payload: dict[str, Any] - - -def build_commands(args: argparse.Namespace) -> list[ChildCommand]: - timeout = str(args.timeout) - return [ - ChildCommand("systemd", [ - sys.executable, - "tools/go_systemd_prod_smoke.py", - "--host", - args.app_host, - "--user", - args.ssh_user, - "--timeout", - timeout, - ]), - ChildCommand("kafka", [ - sys.executable, - "tools/go_kafka_prod_smoke.py", - "--host", - args.kafka_host, - "--user", - args.ssh_user, - "--max-lag", - str(args.max_lag), - "--timeout", - timeout, - ]), - ChildCommand("http", [ - sys.executable, - "tools/go_native_prod_smoke.py", - "--base-url", - args.base_url, - "--timeout", - timeout, - ] + (["--date", args.date] if args.date else [])), - ] - - -def run_child(command: ChildCommand, timeout: float) -> ChildResult: - completed = subprocess.run( - command.argv, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - timeout=timeout, - ) - payload = parse_json_output(completed.stdout) - if completed.stderr.strip(): - payload.setdefault("stderr", completed.stderr.strip()) - status = payload.get("status") - if status not in {"pass", "fail"}: - status = "pass" if completed.returncode == 0 else "fail" - return ChildResult(command.name, status, completed.returncode, payload) - - -def parse_json_output(output: str) -> dict[str, Any]: - output = output.strip() - if not output: - return {} - start = output.find("{") - end = output.rfind("}") - if start < 0 or end < start: - return {"rawOutput": output} - try: - return json.loads(output[start:end + 1]) - except json.JSONDecodeError: - return {"rawOutput": output} - - -def overall_status(results: list[ChildResult]) -> str: - if not results: - return "fail" - return "fail" if any(result.status != "pass" or result.exit_code != 0 for result in results) else "pass" - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--app-host", default=DEFAULT_APP_HOST) - parser.add_argument("--kafka-host", default=DEFAULT_KAFKA_HOST) - parser.add_argument("--ssh-user", default=DEFAULT_USER) - parser.add_argument("--base-url", default=DEFAULT_BASE_URL) - parser.add_argument("--date", help="Shanghai local date, YYYY-MM-DD. Defaults to today in go_native_prod_smoke.py.") - parser.add_argument("--timeout", type=float, default=20.0) - parser.add_argument("--max-lag", type=int, default=100) - return parser.parse_args(argv) - - -def main(argv: list[str]) -> int: - args = parse_args(argv) - results: list[ChildResult] = [] - for command in build_commands(args): - try: - results.append(run_child(command, args.timeout + 10)) - except Exception as exc: - results.append(ChildResult(command.name, "fail", 1, {"status": "fail", "error": str(exc)})) - status = overall_status(results) - print(json.dumps({ - "status": status, - "checks": [asdict(result) for result in results], - }, ensure_ascii=False, indent=2)) - return 0 if status == "pass" else 1 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) diff --git a/tools/go_systemd_prod_smoke.py b/tools/go_systemd_prod_smoke.py deleted file mode 100755 index 9fe8f6cc..00000000 --- a/tools/go_systemd_prod_smoke.py +++ /dev/null @@ -1,210 +0,0 @@ -#!/usr/bin/env python3 -"""Systemd and port smoke checks for the Go native production ECS.""" - -from __future__ import annotations - -import argparse -import json -import subprocess -import sys -from dataclasses import asdict, dataclass - - -DEFAULT_HOST = "115.29.187.205" -DEFAULT_USER = "root" -DEFAULT_SERVICES = [ - "lingniu-go-gateway.service", - "lingniu-go-history-writer.service", - "lingniu-go-stat-writer.service", - "lingniu-go-realtime-api.service", -] -DEFAULT_PORTS = { - 808: "gateway", - 32960: "gateway", - 20210: "realtime-api", -} -DEFAULT_RELEASE_ROOT = "/opt/lingniu-go-native" -DEFAULT_SPOOL_DIR = "/opt/lingniu-go-native/spool/gateway" -DEFAULT_BINARIES = ["gateway", "history-writer", "stat-writer", "realtime-api"] - - -@dataclass(frozen=True) -class Check: - name: str - status: str - message: str - - -@dataclass(frozen=True) -class ServiceState: - active: str - enabled: str - - -def parse_service_states(output: str) -> dict[str, ServiceState]: - states: dict[str, ServiceState] = {} - for line in output.splitlines(): - parts = line.split() - if len(parts) >= 2: - enabled = parts[2] if len(parts) >= 3 else "enabled" - states[parts[0]] = ServiceState(parts[1], enabled) - return states - - -def service_checks(output: str, services: list[str]) -> list[Check]: - states = parse_service_states(output) - checks: list[Check] = [] - for service in services: - state = states.get(service, ServiceState("missing", "missing")) - status = "pass" if state.active == "active" and state.enabled == "enabled" else "fail" - checks.append(Check( - "service." + service, - status, - "state=" + state.active + "; enabled=" + state.enabled, - )) - return checks - - -def port_checks(output: str, ports: dict[int, str]) -> list[Check]: - lines = output.splitlines() - checks: list[Check] = [] - for port, process in sorted(ports.items()): - matching = [line.strip() for line in lines if has_port(line, port)] - if not matching: - checks.append(Check("port." + str(port), "fail", "not listening")) - continue - owned = [line for line in matching if process in line] - if owned: - checks.append(Check("port." + str(port), "pass", owned[0])) - else: - checks.append(Check("port." + str(port), "fail", matching[0])) - return checks - - -def has_port(line: str, port: int) -> bool: - needle = ":" + str(port) - return needle + " " in line or needle + "\t" in line - - -def spool_check(output: str) -> Check: - values = parse_key_values(output) - files = int(values.get("files", "-1")) - recent = int(values.get("recent", "-1")) - status = "pass" if files == 0 and recent == 0 else "fail" - return Check("spool.gateway", status, output.strip() or "missing") - - -def release_check(output: str) -> Check: - values = parse_key_values(output) - current = values.get("current", "") - binaries_present = [values.get(binary, "0") == "1" for binary in DEFAULT_BINARIES] - status = "pass" if current and all(binaries_present) else "fail" - return Check("release.current", status, output.strip() or "missing") - - -def parse_key_values(output: str) -> dict[str, str]: - values: dict[str, str] = {} - for part in output.split(): - if "=" not in part: - continue - key, value = part.split("=", 1) - values[key] = value - return values - - -def ssh(host: str, user: str, command: str, timeout: float) -> str: - target = user + "@" + host if user else host - completed = subprocess.run( - ["ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", target, command], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - timeout=timeout, - ) - return completed.stdout - - -def remote_command(services: list[str]) -> str: - quoted = " ".join(services) - return ( - "for svc in " + quoted + "; do " - "printf '%s ' \"$svc\"; " - "printf '%s ' \"$(systemctl is-active \"$svc\" 2>/dev/null || true)\"; " - "systemctl is-enabled \"$svc\" 2>/dev/null || true; " - "done; " - "printf '\\n--PORTS--\\n'; " - "ss -lntp; " - "printf '\\n--SPOOL--\\n'; " - "spool='" + DEFAULT_SPOOL_DIR + "'; " - "if test -d \"$spool\"; then " - "files=$(find \"$spool\" -type f | wc -l); " - "bytes=$(du -sb \"$spool\" 2>/dev/null | awk '{print $1}'); " - "recent=$(find \"$spool\" -type f -mmin -5 | wc -l); " - "printf 'files=%s bytes=%s recent=%s\\n' \"$files\" \"${bytes:-0}\" \"$recent\"; " - "else printf 'files=-1 bytes=0 recent=-1 missing=%s\\n' \"$spool\"; fi; " - "printf '\\n--RELEASE--\\n'; " - "root='" + DEFAULT_RELEASE_ROOT + "'; " - "current=$(readlink -f \"$root/current\" 2>/dev/null || true); " - "printf 'current=%s' \"$current\"; " - "for bin in " + " ".join(DEFAULT_BINARIES) + "; do " - "if test -x \"$root/current/$bin\"; then present=1; else present=0; fi; " - "printf ' %s=%s' \"$bin\" \"$present\"; " - "done; printf '\\n'" - ) - - -def split_remote_output(output: str) -> tuple[str, str, str, str]: - ports_marker = "\n--PORTS--\n" - spool_marker = "\n--SPOOL--\n" - release_marker = "\n--RELEASE--\n" - if ports_marker not in output: - return output, "", "", "" - service_output, rest = output.split(ports_marker, 1) - if spool_marker not in rest: - return service_output, rest, "", "" - port_output, spool_output = rest.split(spool_marker, 1) - if release_marker not in spool_output: - return service_output, port_output, spool_output, "" - spool_output, release_output = spool_output.split(release_marker, 1) - return service_output, port_output, spool_output, release_output - - -def run(args: argparse.Namespace) -> tuple[str, list[Check]]: - output = ssh(args.host, args.user, remote_command(DEFAULT_SERVICES), args.timeout) - service_output, port_output, spool_output, release_output = split_remote_output(output) - checks = ( - service_checks(service_output, DEFAULT_SERVICES) - + port_checks(port_output, DEFAULT_PORTS) - + [spool_check(spool_output)] - + [release_check(release_output)] - ) - status = "fail" if any(check.status == "fail" for check in checks) else "pass" - return status, checks - - -def parse_args(argv: list[str]) -> argparse.Namespace: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--host", default=DEFAULT_HOST) - parser.add_argument("--user", default=DEFAULT_USER) - parser.add_argument("--timeout", type=float, default=20.0) - return parser.parse_args(argv) - - -def main(argv: list[str]) -> int: - args = parse_args(argv) - try: - status, checks = run(args) - except Exception as exc: - status = "fail" - checks = [Check("systemd.ssh", "fail", str(exc))] - print(json.dumps({ - "status": status, - "host": args.host, - "checks": [asdict(check) for check in checks], - }, ensure_ascii=False, indent=2)) - return 0 if status == "pass" else 1 - - -if __name__ == "__main__": - raise SystemExit(main(sys.argv[1:])) diff --git a/tools/jt808_e2e_smoke.py b/tools/jt808_e2e_smoke.py deleted file mode 100755 index c7e7116d..00000000 --- a/tools/jt808_e2e_smoke.py +++ /dev/null @@ -1,543 +0,0 @@ -#!/usr/bin/env python3 -"""JT808 TCP ingest to TDengine history smoke and light-load verifier.""" - -from __future__ import annotations - -import argparse -import concurrent.futures -import datetime as dt -import json -import os -import pathlib -import socket -import time -import urllib.parse -import urllib.request -from dataclasses import dataclass -from typing import Any - -try: - from tools import tdengine_smoke -except ModuleNotFoundError: - import tdengine_smoke - - -def bcd(value: str) -> bytes: - digits = "".join(ch for ch in value if ch.isdigit()) - if len(digits) % 2: - digits = "0" + digits - return bytes((int(digits[i]) << 4) | int(digits[i + 1]) for i in range(0, len(digits), 2)) - - -def write_u16(value: int) -> bytes: - return bytes([(value >> 8) & 0xFF, value & 0xFF]) - - -def write_u32(value: int) -> bytes: - return bytes([ - (value >> 24) & 0xFF, - (value >> 16) & 0xFF, - (value >> 8) & 0xFF, - value & 0xFF, - ]) - - -def ascii_fixed(value: str, length: int) -> bytes: - raw = value.encode("ascii", errors="ignore")[:length] - return raw + b" " * (length - len(raw)) - - -def bcc(data: bytes) -> int: - result = 0 - for byte in data: - result ^= byte - return result - - -def escape_payload(data: bytes) -> bytes: - out = bytearray() - for byte in data: - if byte == 0x7E: - out += b"\x7D\x02" - elif byte == 0x7D: - out += b"\x7D\x01" - else: - out.append(byte) - return bytes(out) - - -def unescape_payload(data: bytes) -> bytes: - out = bytearray() - index = 0 - while index < len(data): - byte = data[index] - if byte == 0x7D and index + 1 < len(data): - marker = data[index + 1] - if marker == 0x02: - out.append(0x7E) - index += 2 - continue - if marker == 0x01: - out.append(0x7D) - index += 2 - continue - out.append(byte) - index += 1 - return bytes(out) - - -def build_frame(message_id: int, phone: str, serial: int, body: bytes) -> bytes: - payload = ( - write_u16(message_id) - + write_u16(len(body) & 0x03FF) - + bcd(phone)[-6:] - + write_u16(serial & 0xFFFF) - + body - ) - payload += bytes([bcc(payload)]) - return b"\x7E" + escape_payload(payload) + b"\x7E" - - -def build_register_body(device_id: str, plate: str) -> bytes: - return ( - write_u16(44) - + write_u16(4401) - + ascii_fixed("MAKER", 5) - + ascii_fixed("TYPE-A", 20) - + ascii_fixed(device_id, 7) - + bytes([1]) - + plate.encode("ascii", errors="ignore") - ) - - -def build_location_body(event_time: dt.datetime, sequence: int = 0) -> bytes: - timestamp = event_time.strftime("%y%m%d%H%M%S") - return ( - write_u32(0) - + write_u32(0x00030000) - + write_u32(39916527 + sequence) - + write_u32(116397128 + sequence) - + write_u16(50) - + write_u16(523) - + write_u16(90) - + bcd(timestamp) - ) - - -def parse_event_time(value: str) -> dt.datetime: - parsed = dt.datetime.fromisoformat(value.replace("Z", "+00:00")) - if parsed.tzinfo is not None: - parsed = parsed.astimezone(dt.timezone.utc).replace(tzinfo=None) - return parsed - - -def send_frame(host: str, port: int, frame: bytes, timeout: float, read_response: bool, linger_ms: int = 0) -> bytes: - with socket.create_connection((host, port), timeout=timeout) as sock: - sock.settimeout(timeout) - sock.sendall(frame) - if not read_response: - if linger_ms > 0: - time.sleep(linger_ms / 1000) - return b"" - try: - return sock.recv(1024) - except socket.timeout: - return b"" - - -def send_register(host: str, port: int, phone: str, serial: int, timeout: float) -> bytes: - device_id = "D" + phone[-6:] - plate = "B" + phone[-5:] - return send_frame( - host, - port, - build_frame(0x0100, phone, serial, build_register_body(device_id, plate)), - timeout, - True, - ) - - -def send_location(host: str, - port: int, - phone: str, - serial: int, - event_time: dt.datetime, - timeout: float, - linger_ms: int) -> bytes: - return send_frame( - host, - port, - build_frame(0x0200, phone, serial, build_location_body(event_time, serial)), - timeout, - False, - linger_ms, - ) - - -def recv_optional(sock: socket.socket, timeout: float) -> bytes: - previous_timeout = sock.gettimeout() - sock.settimeout(timeout) - try: - return sock.recv(1024) - except socket.timeout: - return b"" - finally: - sock.settimeout(previous_timeout) - - -def send_phone_sequence( - host: str, - port: int, - phone: str, - phone_index: int, - frames: int, - event_time: dt.datetime, - timeout: float, - rate: float, - linger_ms: int, - post_register_ms: int, -) -> tuple[bytes, int]: - with socket.create_connection((host, port), timeout=timeout) as sock: - sock.settimeout(timeout) - device_id = "D" + phone[-6:] - plate = "B" + phone[-5:] - sock.sendall(build_frame(0x0100, phone, 100 + phone_index, build_register_body(device_id, plate))) - ack = recv_optional(sock, timeout) - if post_register_ms > 0: - time.sleep(post_register_ms / 1000) - sent = 0 - for frame_index in range(frames): - serial = 1000 + phone_index * frames + frame_index - sock.sendall(build_frame( - 0x0200, - phone, - serial, - build_location_body(event_time + dt.timedelta(seconds=frame_index), serial), - )) - sent += 1 - if rate > 0: - time.sleep(1 / rate) - if linger_ms > 0: - time.sleep(linger_ms / 1000) - return ack, sent - - -@dataclass(frozen=True) -class SendWorkloadResult: - sent_registers: int - sent_locations: int - elapsed_seconds: float - - -def send_one_phone(args: argparse.Namespace, - phone: str, - phone_index: int, - event_time: dt.datetime) -> tuple[bytes, int]: - if args.connection_mode == "session": - return send_phone_sequence( - args.tcp_host, - args.tcp_port, - phone, - phone_index, - args.frames, - event_time, - args.tcp_timeout, - args.rate, - args.linger_ms, - args.post_register_ms, - ) - - ack = send_register(args.tcp_host, args.tcp_port, phone, 100 + phone_index, args.tcp_timeout) - if args.post_register_ms > 0: - time.sleep(args.post_register_ms / 1000) - sent = 0 - for frame_index in range(args.frames): - serial = 1000 + phone_index * args.frames + frame_index - send_location( - args.tcp_host, - args.tcp_port, - phone, - serial, - event_time + dt.timedelta(seconds=frame_index), - args.tcp_timeout, - args.linger_ms, - ) - sent += 1 - if args.rate > 0: - time.sleep(1 / args.rate) - return ack, sent - - -def send_phone_workload(args: argparse.Namespace, - phones: list[str], - event_time: dt.datetime) -> SendWorkloadResult: - sent_locations = 0 - start = time.monotonic() - workers = max(1, int(getattr(args, "workers", 1))) - - def run_one(index_and_phone: tuple[int, str]) -> tuple[bytes, int]: - phone_index, phone = index_and_phone - return send_one_phone(args, phone, phone_index, event_time) - - indexed_phones = list(enumerate(phones)) - if workers == 1 or len(indexed_phones) <= 1: - results = [run_one(item) for item in indexed_phones] - else: - with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: - results = list(executor.map(run_one, indexed_phones)) - - for phone, (ack, sent) in zip(phones, results): - if args.require_register_ack and not ack: - raise RuntimeError(f"register ack timeout for {phone}") - sent_locations += sent - - elapsed = max(time.monotonic() - start, 0.001) - return SendWorkloadResult( - sent_registers=len(phones), - sent_locations=sent_locations, - elapsed_seconds=elapsed, - ) - - -def query_json(base_url: str, path: str, params: dict[str, Any], timeout: float) -> dict[str, Any]: - url = base_url.rstrip("/") + path + "?" + urllib.parse.urlencode(params) - request = urllib.request.Request(url, headers={"Accept": "application/json"}) - with urllib.request.urlopen(request, timeout=timeout) as response: - return json.loads(response.read().decode("utf-8")) - - -def location_params(phone: str, date_from: str, date_to: str, limit: int) -> dict[str, Any]: - return { - "phone": phone, - "dateFrom": date_from, - "dateTo": date_to, - "limit": limit, - "order": "ASC", - } - - -def next_page_params(params: dict[str, Any], page: dict[str, Any]) -> dict[str, Any]: - cursor = page.get("nextCursor") or {} - next_params = dict(params) - if cursor: - next_params["cursorTs"] = cursor["ts"] - next_params["cursorId"] = cursor["id"] - return next_params - - -def archive_path(root: pathlib.Path, uri: str) -> pathlib.Path: - prefix = "archive://" - if not uri.startswith(prefix): - raise ValueError(f"unsupported archive uri: {uri}") - return root / uri[len(prefix):] - - -def raw_frame_count_sql(phone: str, items: list[dict[str, Any]]) -> str: - sqls = raw_frame_count_sqls(phone, items) - if not sqls: - raise ValueError("no raw uri found in jt808 location items") - return sqls[0] - - -def raw_frame_count_sqls(phone: str, items: list[dict[str, Any]]) -> list[str]: - seen: set[str] = set() - sqls: list[str] = [] - for item in items: - raw_uri = item.get("rawUri") or "" - if not raw_uri or raw_uri in seen: - continue - seen.add(raw_uri) - sqls.append(tdengine_smoke.raw_frame_count_sql( - protocol="JT808", - vehicle_key=item.get("vehicleKey") or ("jt808:" + phone), - phone=phone, - raw_uri=raw_uri, - )) - return sqls - - -def verify_tdengine_raw_frames( - rest_url: str, - username: str, - password: str, - sqls: list[str], - timeout_seconds: float, - request_timeout: float, -) -> int: - if not sqls: - return 0 - verified = 0 - for sql in sqls: - count = wait_for_tdengine_raw_frames( - rest_url, username, password, sql, 1, timeout_seconds, request_timeout) - if count < 1: - raise RuntimeError(f"tdengine raw_frames row not visible for query: {sql}") - verified += 1 - return verified - - -def wait_for_tdengine_raw_frames( - rest_url: str, - username: str, - password: str, - sql: str, - minimum: int, - timeout_seconds: float, - request_timeout: float, -) -> int: - deadline = time.monotonic() + timeout_seconds - last_count = 0 - while time.monotonic() < deadline: - last_count = tdengine_smoke.query_count(rest_url, username, password, sql, request_timeout) - if last_count >= minimum: - return last_count - time.sleep(1) - return last_count - - -def wait_for_locations( - history_base_url: str, - phone: str, - date_from: str, - date_to: str, - minimum: int, - timeout_seconds: float, - request_timeout: float, -) -> dict[str, Any]: - deadline = time.monotonic() + timeout_seconds - params = location_params(phone, date_from, date_to, max(minimum, 10)) - last_page: dict[str, Any] = {} - while time.monotonic() < deadline: - last_page = query_json(history_base_url, "/api/event-history/jt808/locations", params, request_timeout) - if len(last_page.get("items") or []) >= minimum: - return last_page - time.sleep(1) - return last_page - - -def verify_pagination(history_base_url: str, phone: str, date_from: str, date_to: str, request_timeout: float) -> bool: - first_params = location_params(phone, date_from, date_to, 1) - first = query_json(history_base_url, "/api/event-history/jt808/locations", first_params, request_timeout) - if not first.get("items") or not first.get("nextCursor"): - return False - second = query_json( - history_base_url, - "/api/event-history/jt808/locations", - next_page_params(first_params, first), - request_timeout, - ) - return bool(second.get("items")) - - -def generated_phone(start_phone: str, offset: int) -> str: - return str(int(start_phone) + offset) - - -def expected_history_count(args: argparse.Namespace) -> int: - minimum = min(args.frames, args.expect_history_count) - if args.verify_pagination and args.frames > 1: - minimum = max(minimum, 2) - return minimum - - -def run(args: argparse.Namespace) -> dict[str, Any]: - event_time = parse_event_time(args.event_time) - date_from = (event_time - dt.timedelta(minutes=5)).isoformat() - date_to = (event_time + dt.timedelta(minutes=args.frames + 5)).isoformat() - phones = [generated_phone(args.start_phone, index) for index in range(args.phones)] - workload = send_phone_workload(args, phones, event_time) - first_phone = phones[0] - page = wait_for_locations( - args.history_base_url, - first_phone, - date_from, - date_to, - expected_history_count(args), - args.history_timeout, - args.http_timeout, - ) - items = page.get("items") or [] - if len(items) < expected_history_count(args): - raise RuntimeError(f"history rows not visible for {first_phone}: got {len(items)}") - - archive_checked = 0 - if args.archive_root: - root = pathlib.Path(args.archive_root) - for item in items[: args.archive_check_limit]: - path = archive_path(root, item["rawUri"]) - if not path.exists(): - raise RuntimeError(f"raw archive missing: {path}") - archive_checked += 1 - - tdengine_raw_frames = None - if args.tdengine_rest_url: - tdengine_raw_frames = verify_tdengine_raw_frames( - args.tdengine_rest_url, - args.tdengine_username, - args.tdengine_password, - raw_frame_count_sqls(first_phone, items), - args.history_timeout, - args.http_timeout, - ) - - pagination_ok = None - if args.verify_pagination and args.frames > 1: - pagination_ok = verify_pagination(args.history_base_url, first_phone, date_from, date_to, args.http_timeout) - if not pagination_ok: - raise RuntimeError("cursor pagination verification failed") - - return { - "tcpHost": args.tcp_host, - "tcpPort": args.tcp_port, - "connectionMode": args.connection_mode, - "workers": max(1, int(args.workers)), - "phones": phones, - "sentRegisters": workload.sent_registers, - "sentLocations": workload.sent_locations, - "sendElapsedSeconds": round(workload.elapsed_seconds, 3), - "sendLocationsPerSecond": round(workload.sent_locations / workload.elapsed_seconds, 2), - "historyRowsForFirstPhone": len(items), - "tdengineRawFrames": tdengine_raw_frames, - "archiveChecked": archive_checked, - "paginationOk": pagination_ok, - "firstFrameId": items[0].get("frameId") if items else None, - "firstRawUri": items[0].get("rawUri") if items else None, - } - - -def parser() -> argparse.ArgumentParser: - p = argparse.ArgumentParser(description=__doc__) - p.add_argument("--tcp-host", default="127.0.0.1") - p.add_argument("--tcp-port", type=int, default=808) - p.add_argument("--history-base-url", default="http://127.0.0.1:20200") - p.add_argument("--connection-mode", choices=["per-frame", "session"], default="per-frame") - p.add_argument("--start-phone", default="13079962000") - p.add_argument("--phones", type=int, default=1) - p.add_argument("--workers", type=int, default=1, help="parallel phone sessions; 1 keeps legacy serial behavior") - p.add_argument("--frames", type=int, default=3, help="location frames per phone") - p.add_argument("--rate", type=float, default=0, help="location send rate per second; 0 means unlimited") - p.add_argument("--linger-ms", type=int, default=2500, help="time to keep each terminal connection open after writes") - p.add_argument("--post-register-ms", type=int, default=1000, help="pause after register ack before location frames") - p.add_argument("--event-time", default="2024-01-02T03:04:05") - p.add_argument("--expect-history-count", type=int, default=1) - p.add_argument("--verify-pagination", action="store_true") - p.add_argument("--archive-root", default="") - p.add_argument("--archive-check-limit", type=int, default=3) - p.add_argument("--tdengine-rest-url", default=os.environ.get("TDENGINE_REST_URL", "")) - p.add_argument("--tdengine-username", default=os.environ.get("TDENGINE_USERNAME", "root")) - p.add_argument("--tdengine-password", default=os.environ.get("TDENGINE_PASSWORD", "taosdata")) - p.add_argument("--require-register-ack", action="store_true", default=True) - p.add_argument("--tcp-timeout", type=float, default=2) - p.add_argument("--http-timeout", type=float, default=5) - p.add_argument("--history-timeout", type=float, default=30) - return p - - -def main() -> None: - args = parser().parse_args() - summary = run(args) - print(json.dumps(summary, ensure_ascii=False, indent=2)) - - -if __name__ == "__main__": - main() diff --git a/tools/tdengine_smoke.py b/tools/tdengine_smoke.py deleted file mode 100644 index 06ad0524..00000000 --- a/tools/tdengine_smoke.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -"""Small TDengine REST helpers for local smoke verifiers.""" - -from __future__ import annotations - -import base64 -import json -import urllib.request -from typing import Any - - -def sql_literal(value: str) -> str: - return "'" + (value or "").replace("'", "''") + "'" - - -def raw_frame_count_sql( - *, - protocol: str, - vehicle_key: str = "", - vin: str = "", - phone: str = "", - raw_uri: str = "", -) -> str: - if not protocol or not protocol.strip(): - raise ValueError("protocol is required") - filters = ["protocol = " + sql_literal(protocol.strip().upper())] - optional_filters = { - "vehicle_key": vehicle_key, - "vin": vin, - "phone": phone, - "raw_uri": raw_uri, - } - present = False - for column, value in optional_filters.items(): - if value and value.strip(): - filters.append(column + " = " + sql_literal(value.strip())) - present = True - if not present: - raise ValueError("at least one identity or raw_uri filter is required") - return "SELECT COUNT(*) FROM raw_frames WHERE " + " AND ".join(filters) - - -def count_from_response(response: dict[str, Any]) -> int: - if response.get("code") != 0: - raise RuntimeError(f"tdengine query failed: {response}") - data = response.get("data") or [] - if not data or not data[0]: - return 0 - return int(data[0][0]) - - -def query_count(rest_url: str, username: str, password: str, sql: str, timeout: float) -> int: - request = urllib.request.Request(rest_url, data=sql.encode("utf-8"), method="POST") - if username: - token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("ascii") - request.add_header("Authorization", "Basic " + token) - request.add_header("Content-Type", "text/plain; charset=utf-8") - request.add_header("Accept", "application/json") - with urllib.request.urlopen(request, timeout=timeout) as response: - payload = json.loads(response.read().decode("utf-8")) - return count_from_response(payload) diff --git a/tools/test_gb32960_e2e_smoke.py b/tools/test_gb32960_e2e_smoke.py deleted file mode 100644 index 66a16e0b..00000000 --- a/tools/test_gb32960_e2e_smoke.py +++ /dev/null @@ -1,107 +0,0 @@ -import datetime as dt -import pathlib -import tempfile -import unittest - -from tools import gb32960_e2e_smoke as smoke - - -class Gb32960E2ESmokeTest(unittest.TestCase): - def test_parse_frame_info_reads_vin_and_collect_time(self): - frame = bytes.fromhex( - "232302fe4c544553543230323630363239303030310100081a061d10060f" - "02002a" - ) - - info = smoke.parse_frame_info(frame) - - self.assertEqual(info.vin, "LTEST202606290001") - self.assertEqual(info.command, 0x02) - self.assertEqual(info.event_time, dt.datetime(2026, 6, 29, 16, 6, 15, tzinfo=smoke.SHANGHAI)) - - def test_query_range_surrounds_event_time_with_offset(self): - date_from, date_to = smoke.query_range( - dt.datetime(2026, 6, 29, 8, 6, 15, tzinfo=smoke.SHANGHAI), - minutes=5, - ) - - self.assertEqual(date_from, "2026-06-29T08:01:15+08:00") - self.assertEqual(date_to, "2026-06-29T08:11:15+08:00") - - def test_valid_ack_requires_frame_marker(self): - self.assertTrue(smoke.valid_ack(b"##\x02\x01")) - self.assertTrue(smoke.valid_ack(b"$$\x02\x01")) - self.assertFalse(smoke.valid_ack(b"")) - self.assertFalse(smoke.valid_ack(b"\x7e\x80")) - - def test_archive_path_maps_archive_uri_to_root(self): - path = smoke.archive_path( - pathlib.Path("/tmp/archive"), - "archive://2026/06/29/GB32960/LTEST202606290001/frame.bin", - ) - - self.assertEqual( - path, - pathlib.Path("/tmp/archive/2026/06/29/GB32960/LTEST202606290001/frame.bin"), - ) - - def test_parse_field_keys_trims_blank_items(self): - keys = smoke.parse_field_keys("speed_kmh, total_mileage_km, ,longitude") - - self.assertEqual(keys, ["speed_kmh", "total_mileage_km", "longitude"]) - - def test_first_raw_uri_reads_record_archive_uri(self): - uri = smoke.first_raw_uri([ - {"rawArchiveUri": ""}, - {"rawArchiveUri": "archive://2026/06/29/GB32960/LTEST/frame.bin"}, - ]) - - self.assertEqual(uri, "archive://2026/06/29/GB32960/LTEST/frame.bin") - - def test_raw_frame_count_sql_filters_by_vin_and_raw_uri(self): - info = smoke.FrameInfo("LTEST202606290001", 0x02, None) - sql = smoke.raw_frame_count_sql(info, [ - {"rawArchiveUri": "archive://2026/06/29/GB32960/LTEST202606290001/frame.bin"}, - ]) - - self.assertEqual( - sql, - "SELECT COUNT(*) FROM raw_frames WHERE protocol = 'GB32960' " - "AND vehicle_key = 'LTEST202606290001' " - "AND vin = 'LTEST202606290001' " - "AND raw_uri = 'archive://2026/06/29/GB32960/LTEST202606290001/frame.bin'", - ) - - def test_raw_frame_count_sqls_include_each_unique_record_raw_uri(self): - info = smoke.FrameInfo("LTEST202606290001", 0x02, None) - sqls = smoke.raw_frame_count_sqls(info, [ - {"rawArchiveUri": "archive://2026/06/29/GB32960/LTEST202606290001/frame-1.bin"}, - {"rawArchiveUri": "archive://2026/06/29/GB32960/LTEST202606290001/frame-2.bin"}, - {"rawArchiveUri": "archive://2026/06/29/GB32960/LTEST202606290001/frame-1.bin"}, - ]) - - self.assertEqual(len(sqls), 2) - self.assertTrue(sqls[0].endswith("frame-1.bin'")) - self.assertTrue(sqls[1].endswith("frame-2.bin'")) - - def test_check_archive_ignores_duplicate_uris(self): - with tempfile.TemporaryDirectory() as temp_dir: - root = pathlib.Path(temp_dir) - archive_file = root / "2026/06/29/GB32960/LTEST/frame.bin" - archive_file.parent.mkdir(parents=True) - archive_file.write_bytes(b"raw") - - checked = smoke.check_archive( - root, - [ - {"rawArchiveUri": "archive://2026/06/29/GB32960/LTEST/frame.bin"}, - {"rawArchiveUri": "archive://2026/06/29/GB32960/LTEST/frame.bin"}, - ], - limit=3, - ) - - self.assertEqual(checked, 1) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_go_kafka_prod_smoke.py b/tools/test_go_kafka_prod_smoke.py deleted file mode 100644 index 9b6d5b74..00000000 --- a/tools/test_go_kafka_prod_smoke.py +++ /dev/null @@ -1,62 +0,0 @@ -import unittest - -from tools import go_kafka_prod_smoke as smoke - - -class GoKafkaProdSmokeTest(unittest.TestCase): - def test_parse_topic_list_marks_required_topics_present(self): - topics = smoke.parse_topic_list(""" -vehicle.raw.go.gb32960.v1 -vehicle.raw.go.jt808.v1 -vehicle.raw.go.yutong-mqtt.v1 -vehicle.event.go.unified.v1 -""") - - checks = smoke.topic_checks(topics, smoke.DEFAULT_TOPICS) - - self.assertTrue(all(check.status == "pass" for check in checks)) - - def test_topic_checks_fail_when_required_topic_missing(self): - topics = {"vehicle.raw.go.gb32960.v1"} - - checks = smoke.topic_checks(topics, ["vehicle.raw.go.gb32960.v1", "vehicle.event.go.unified.v1"]) - - by_name = {check.name: check for check in checks} - self.assertEqual(by_name["topic.vehicle.event.go.unified.v1"].status, "fail") - - def test_parse_consumer_group_describe_reads_lag_rows(self): - rows = smoke.parse_consumer_group_describe(""" -GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG -go-realtime-api vehicle.event.go.unified.v1 0 15992 15992 0 -go-realtime-api vehicle.event.go.unified.v1 1 2036 2036 3 -""") - - self.assertEqual(rows[0].group, "go-realtime-api") - self.assertEqual(rows[1].topic, "vehicle.event.go.unified.v1") - self.assertEqual(rows[1].lag, 3) - - def test_lag_checks_fail_when_lag_exceeds_threshold(self): - rows = [ - smoke.ConsumerLag("go-history-writer", "vehicle.raw.go.jt808.v1", 0, 100, 100, 0), - smoke.ConsumerLag("go-history-writer", "vehicle.raw.go.jt808.v1", 1, 100, 110, 10), - ] - - check = smoke.group_lag_check("go-history-writer", rows, max_lag=5) - - self.assertEqual(check.status, "fail") - self.assertIn("max_lag=10", check.message) - - def test_lag_checks_pass_when_group_has_rows_and_lag_is_small(self): - rows = [ - smoke.ConsumerLag("go-stat-writer", "vehicle.raw.go.gb32960.v1", 0, 100, 100, 0), - smoke.ConsumerLag("go-stat-writer", "vehicle.raw.go.jt808.v1", 1, 100, 101, 1), - ] - - check = smoke.group_lag_check("go-stat-writer", rows, max_lag=5) - - self.assertEqual(check.status, "pass") - self.assertIn("max_lag=1", check.message) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_go_native_deploy.py b/tools/test_go_native_deploy.py deleted file mode 100644 index 24110410..00000000 --- a/tools/test_go_native_deploy.py +++ /dev/null @@ -1,63 +0,0 @@ -import unittest - -from tools import go_native_deploy as deploy - - -class GoNativeDeployTest(unittest.TestCase): - def test_build_commands_cover_four_production_binaries(self): - commands = deploy.build_commands("/repo/go/vehicle-gateway", "/tmp/out") - - self.assertEqual([command.output_name for command in commands], [ - "gateway", - "history-writer", - "stat-writer", - "realtime-api", - ]) - for command in commands: - self.assertEqual(command.argv[:3], ["go", "build", "-trimpath"]) - self.assertIn("GOOS=linux", command.env) - self.assertIn("GOARCH=amd64", command.env) - self.assertIn("CGO_ENABLED=0", command.env) - - def test_remote_switch_command_creates_release_and_restarts_all_services(self): - command = deploy.remote_switch_command("409f55b", "/opt/lingniu-go-native", "/tmp/lingniu-go-native-409f55b.tar.gz") - - self.assertIn("/opt/lingniu-go-native/releases/409f55b", command) - self.assertIn("ln -sfn", command) - self.assertIn("systemctl restart lingniu-go-gateway", command) - self.assertIn("systemctl restart lingniu-go-history-writer", command) - self.assertIn("systemctl restart lingniu-go-stat-writer", command) - self.assertIn("systemctl restart lingniu-go-realtime-api", command) - - def test_acceptance_command_uses_deployed_hosts_and_date(self): - command = deploy.acceptance_command( - app_host="115.29.187.205", - kafka_host="114.55.58.251", - date="2026-07-02", - timeout=20, - ) - - self.assertEqual(command[:2], ["python3", "tools/go_prod_acceptance.py"]) - self.assertIn("--app-host", command) - self.assertIn("115.29.187.205", command) - self.assertIn("--kafka-host", command) - self.assertIn("114.55.58.251", command) - self.assertIn("--date", command) - self.assertIn("2026-07-02", command) - - def test_parse_spool_status_reads_file_and_recent_counts(self): - status = deploy.parse_spool_status("files=42 recent=3") - - self.assertEqual(status.files, 42) - self.assertEqual(status.recent, 3) - - def test_spool_status_command_targets_gateway_spool_directory(self): - command = deploy.spool_status_command("/opt/lingniu-go-native") - - self.assertIn("/opt/lingniu-go-native/spool/gateway", command) - self.assertIn("files=", command) - self.assertIn("recent=", command) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_go_native_prod_smoke.py b/tools/test_go_native_prod_smoke.py deleted file mode 100644 index bbe968c9..00000000 --- a/tools/test_go_native_prod_smoke.py +++ /dev/null @@ -1,358 +0,0 @@ -import datetime as dt -import unittest - -from tools import go_native_prod_smoke as smoke - - -class GoNativeProdSmokeTest(unittest.TestCase): - def test_shanghai_day_window_uses_plus_eight_bounds(self): - now = dt.datetime(2026, 7, 2, 1, 35, tzinfo=smoke.SHANGHAI) - - date_from, date_to = smoke.shanghai_day_window(now) - - self.assertEqual(date_from, "2026-07-02T00:00:00+08:00") - self.assertEqual(date_to, "2026-07-03T00:00:00+08:00") - - def test_api_url_encodes_plus_eight_date_range(self): - url = smoke.api_url( - "http://example.test/base/", - "/api/history/raw-frames", - { - "protocol": "JT808", - "dateFrom": "2026-07-02T00:00:00+08:00", - "dateTo": "2026-07-03T00:00:00+08:00", - "limit": 1, - }, - ) - - self.assertEqual( - url, - "http://example.test/base/api/history/raw-frames?" - "protocol=JT808&dateFrom=2026-07-02T00%3A00%3A00%2B08%3A00" - "&dateTo=2026-07-03T00%3A00%3A00%2B08%3A00&limit=1", - ) - - def test_explicit_today_date_still_enforces_raw_freshness(self): - now = dt.datetime(2026, 7, 2, 9, 30, tzinfo=smoke.SHANGHAI) - - window = smoke.resolve_check_window("2026-07-02", 15.0, now) - - self.assertEqual(window.date_from, "2026-07-02T00:00:00+08:00") - self.assertEqual(window.date_to, "2026-07-03T00:00:00+08:00") - self.assertEqual(window.stat_date, "2026-07-02") - self.assertEqual(window.max_raw_age_minutes, 15.0) - - def test_explicit_historical_date_disables_raw_freshness(self): - now = dt.datetime(2026, 7, 2, 9, 30, tzinfo=smoke.SHANGHAI) - - window = smoke.resolve_check_window("2026-07-01", 15.0, now) - - self.assertEqual(window.date_from, "2026-07-01T00:00:00+08:00") - self.assertEqual(window.date_to, "2026-07-02T00:00:00+08:00") - self.assertEqual(window.stat_date, "2026-07-01") - self.assertIsNone(window.max_raw_age_minutes) - - def test_total_check_passes_when_total_reaches_minimum(self): - check = smoke.check_total( - "jt808.raw", - {"total": 2, "items": [{"vehicle_key": "JT808:013307811170"}]}, - minimum=1, - ) - - self.assertEqual(check.status, "pass") - self.assertEqual(check.count, 2) - self.assertIn("JT808:013307811170", check.message) - - def test_total_check_fails_when_total_is_below_minimum(self): - check = smoke.check_total("gb32960.raw", {"total": 0, "items": []}, minimum=1) - - self.assertEqual(check.status, "fail") - self.assertEqual(check.count, 0) - self.assertIn("expected >= 1", check.message) - - def test_overall_status_fails_if_any_check_fails(self): - checks = [ - smoke.Check("a", "pass", 1, 1, "ok"), - smoke.Check("b", "fail", 0, 1, "bad"), - ] - - self.assertEqual(smoke.overall_status(checks), "fail") - - def test_build_checks_cover_three_raw_protocols_and_two_daily_metric_protocols(self): - specs = smoke.build_check_specs( - date_from="2026-07-02T00:00:00+08:00", - date_to="2026-07-03T00:00:00+08:00", - stat_date="2026-07-02", - min_raw=1, - min_history=1, - min_stat=1, - ) - - names = [spec.name for spec in specs] - - self.assertIn("gb32960.raw", names) - self.assertIn("jt808.raw", names) - self.assertIn("yutong_mqtt.raw", names) - self.assertIn("gb32960.locations", names) - self.assertIn("gb32960.mileage_points", names) - self.assertIn("jt808.locations", names) - self.assertIn("jt808.mileage_points", names) - self.assertIn("yutong_mqtt.locations", names) - self.assertIn("yutong_mqtt.mileage_points", names) - self.assertIn("gb32960.daily_mileage", names) - self.assertIn("jt808.daily_total_mileage", names) - - def test_raw_checks_order_by_received_at_for_ingest_freshness(self): - specs = smoke.build_check_specs( - date_from="2026-07-02T00:00:00+08:00", - date_to="2026-07-03T00:00:00+08:00", - stat_date="2026-07-02", - min_raw=1, - min_history=1, - min_stat=1, - max_raw_age_minutes=15, - ) - - raw_specs = [spec for spec in specs if spec.name.endswith(".raw")] - - self.assertTrue(raw_specs) - for spec in raw_specs: - self.assertEqual(spec.params.get("orderBy"), "receivedAt") - self.assertEqual(spec.params.get("includeTotal"), "false") - self.assertNotIn("dateFrom", spec.params) - self.assertNotIn("dateTo", spec.params) - - def test_vehicle_identifier_prefers_vin_over_vehicle_key(self): - identifier = smoke.vehicle_identifier({ - "vin": "LB9A32A20R0LS1343", - "vehicle_key": "GB32960:ignored", - }) - - self.assertEqual(identifier, "LB9A32A20R0LS1343") - - def test_vehicle_identifier_falls_back_to_vehicle_key(self): - identifier = smoke.vehicle_identifier({ - "vin": "", - "vehicle_key": "JT808:013307811170", - }) - - self.assertEqual(identifier, "JT808:013307811170") - - def test_realtime_specs_are_built_from_raw_samples_with_vin(self): - payloads = { - "gb32960.raw": {"items": [{"vin": "LB9A32A20R0LS1343", "vehicle_key": "LB9A32A20R0LS1343"}]}, - "jt808.raw": {"items": [{"vin": "", "vehicle_key": "JT808:013307811170"}]}, - "yutong_mqtt.raw": {"items": [{"vin": "LMRKH9AC6R1004108"}]}, - } - - specs = smoke.build_realtime_specs(payloads) - - self.assertEqual( - [(spec.name, spec.path) for spec in specs], - [ - ("gb32960.realtime", "/api/realtime/vehicles/LB9A32A20R0LS1343"), - ("gb32960.realtime_online", "/api/realtime/vehicles/LB9A32A20R0LS1343/online"), - ("gb32960.realtime_protocol", "/api/realtime/vehicles/LB9A32A20R0LS1343/protocols/GB32960"), - ("jt808.realtime", "/api/realtime/vehicles/JT808%3A013307811170"), - ("jt808.realtime_online", "/api/realtime/vehicles/JT808%3A013307811170/online"), - ("jt808.realtime_protocol", "/api/realtime/vehicles/JT808%3A013307811170/protocols/JT808"), - ("yutong_mqtt.realtime", "/api/realtime/vehicles/LMRKH9AC6R1004108"), - ("yutong_mqtt.realtime_online", "/api/realtime/vehicles/LMRKH9AC6R1004108/online"), - ("yutong_mqtt.realtime_protocol", "/api/realtime/vehicles/LMRKH9AC6R1004108/protocols/YUTONG_MQTT"), - ], - ) - - def test_realtime_check_requires_online_true_when_field_exists(self): - check = smoke.check_realtime( - "gb32960.realtime_online", - {"vin": "LB9A32A20R0LS1343", "online": True, "protocols": ["GB32960"]}, - ) - - self.assertEqual(check.status, "pass") - self.assertEqual(check.count, 1) - - def test_realtime_check_fails_when_online_false(self): - check = smoke.check_realtime( - "gb32960.realtime_online", - {"vin": "LB9A32A20R0LS1343", "online": False}, - ) - - self.assertEqual(check.status, "fail") - self.assertIn("online=false", check.message) - - def test_realtime_check_fails_when_snapshot_is_stale(self): - now = dt.datetime(2026, 7, 2, 0, 30, 0, tzinfo=dt.timezone.utc) - - check = smoke.check_realtime( - "jt808.realtime", - {"vehicle_key": "JT808:013307811170", "updated_at_ms": 1782950400000}, - max_age_minutes=15, - now=now, - ) - - self.assertEqual(check.status, "fail") - self.assertIn("updated_age_minutes=30.0", check.message) - - def test_realtime_check_passes_when_snapshot_is_fresh(self): - now = dt.datetime(2026, 7, 2, 0, 30, 0, tzinfo=dt.timezone.utc) - - check = smoke.check_realtime( - "jt808.realtime", - {"vehicle_key": "JT808:013307811170", "updated_at_ms": 1782951600000}, - max_age_minutes=15, - now=now, - ) - - self.assertEqual(check.status, "pass") - self.assertIn("updated_age_minutes=10.0", check.message) - - def test_parse_tdengine_utc_timestamp_as_aware_utc(self): - parsed = smoke.parse_tdengine_utc_timestamp("2026-07-01 17:36:02") - - self.assertEqual(parsed, dt.datetime(2026, 7, 1, 17, 36, 2, tzinfo=dt.timezone.utc)) - - def test_total_check_fails_when_latest_sample_is_stale(self): - now = dt.datetime(2026, 7, 1, 18, 0, 0, tzinfo=dt.timezone.utc) - - check = smoke.check_total( - "gb32960.raw", - {"total": 1, "items": [{"ts": "2026-07-01 17:00:00"}]}, - minimum=1, - max_age_minutes=30, - now=now, - ) - - self.assertEqual(check.status, "fail") - self.assertIn("latest_age_minutes=60.0", check.message) - - def test_total_check_uses_received_at_for_raw_freshness_when_present(self): - now = dt.datetime(2026, 7, 1, 18, 0, 0, tzinfo=dt.timezone.utc) - - check = smoke.check_total( - "gb32960.raw", - { - "total": 1, - "items": [{ - "ts": "2026-07-01 17:00:00", - "received_at": "2026-07-01 17:58:00", - }], - }, - minimum=1, - max_age_minutes=15, - now=now, - ) - - self.assertEqual(check.status, "pass") - self.assertIn("latest_age_minutes=2.0", check.message) - - def test_total_check_passes_when_latest_sample_is_fresh(self): - now = dt.datetime(2026, 7, 1, 18, 0, 0, tzinfo=dt.timezone.utc) - - check = smoke.check_total( - "gb32960.raw", - {"total": 1, "items": [{"ts": "2026-07-01 17:45:00"}]}, - minimum=1, - max_age_minutes=30, - now=now, - ) - - self.assertEqual(check.status, "pass") - self.assertIn("latest_age_minutes=15.0", check.message) - - def test_raw_check_requires_structured_parsed_json(self): - check = smoke.check_total( - "jt808.raw", - { - "total": 1, - "items": [{ - "ts": "2026-07-01 17:45:00", - "parsed_json": "", - }], - }, - minimum=1, - require_parsed_json=True, - ) - - self.assertEqual(check.status, "fail") - self.assertIn("missing parsed_json", check.message) - - def test_raw_check_accepts_structured_parsed_json(self): - check = smoke.check_total( - "gb32960.raw", - { - "total": 1, - "items": [{ - "ts": "2026-07-01 17:45:00", - "parsed_json": "{\"header\":{\"vin\":\"LTEST\"}}", - }], - }, - minimum=1, - require_parsed_json=True, - ) - - self.assertEqual(check.status, "pass") - self.assertIn("parsed_json=ok", check.message) - - def test_history_check_requires_frame_id_backlink(self): - check = smoke.check_total( - "jt808.locations", - {"total": 1, "items": [{"ts": "2026-07-01 17:45:00", "frame_id": ""}]}, - minimum=1, - require_frame_id=True, - ) - - self.assertEqual(check.status, "fail") - self.assertIn("missing frame_id", check.message) - - def test_history_check_accepts_frame_id_backlink(self): - check = smoke.check_total( - "jt808.mileage_points", - {"total": 1, "items": [{"ts": "2026-07-01 17:45:00", "frame_id": "go_abc"}]}, - minimum=1, - require_frame_id=True, - ) - - self.assertEqual(check.status, "pass") - self.assertIn("frame_id=ok", check.message) - - def test_daily_mileage_formula_requires_latest_minus_first(self): - check = smoke.check_total( - "jt808.daily_mileage", - { - "total": 1, - "items": [{ - "metric_key": "daily_mileage_km", - "metric_value": 40.0, - "first_total_mileage_km": 4434.9, - "latest_total_mileage_km": 4481.0, - }], - }, - minimum=1, - require_metric_formula=True, - ) - - self.assertEqual(check.status, "fail") - self.assertIn("metric_formula mismatch", check.message) - - def test_daily_total_formula_requires_latest_total(self): - check = smoke.check_total( - "jt808.daily_total_mileage", - { - "total": 1, - "items": [{ - "metric_key": "daily_total_mileage_km", - "metric_value": 4481.0, - "first_total_mileage_km": 4434.9, - "latest_total_mileage_km": 4481.0, - }], - }, - minimum=1, - require_metric_formula=True, - ) - - self.assertEqual(check.status, "pass") - self.assertIn("metric_formula=ok", check.message) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_go_prod_acceptance.py b/tools/test_go_prod_acceptance.py deleted file mode 100644 index ac902d91..00000000 --- a/tools/test_go_prod_acceptance.py +++ /dev/null @@ -1,53 +0,0 @@ -import argparse -import sys -import unittest - -from tools import go_prod_acceptance as acceptance - - -class GoProdAcceptanceTest(unittest.TestCase): - def test_build_commands_runs_systemd_kafka_and_http_smokes(self): - args = argparse.Namespace( - app_host="115.29.187.205", - kafka_host="114.55.58.251", - ssh_user="root", - base_url="http://115.29.187.205:20210", - date="2026-07-02", - timeout=8.0, - max_lag=100, - ) - - commands = acceptance.build_commands(args) - - self.assertEqual(len(commands), 3) - self.assertEqual(commands[0].name, "systemd") - self.assertEqual(commands[0].argv[:2], [sys.executable, "tools/go_systemd_prod_smoke.py"]) - self.assertIn("--host", commands[0].argv) - self.assertIn("115.29.187.205", commands[0].argv) - self.assertEqual(commands[1].name, "kafka") - self.assertIn("tools/go_kafka_prod_smoke.py", commands[1].argv) - self.assertIn("--max-lag", commands[1].argv) - self.assertEqual(commands[2].name, "http") - self.assertIn("tools/go_native_prod_smoke.py", commands[2].argv) - self.assertIn("--base-url", commands[2].argv) - - def test_overall_status_fails_when_any_child_fails(self): - results = [ - acceptance.ChildResult("systemd", "pass", 0, {"status": "pass"}), - acceptance.ChildResult("kafka", "fail", 1, {"status": "fail"}), - ] - - self.assertEqual(acceptance.overall_status(results), "fail") - - def test_overall_status_passes_when_all_children_pass(self): - results = [ - acceptance.ChildResult("systemd", "pass", 0, {"status": "pass"}), - acceptance.ChildResult("kafka", "pass", 0, {"status": "pass"}), - acceptance.ChildResult("http", "pass", 0, {"status": "pass"}), - ] - - self.assertEqual(acceptance.overall_status(results), "pass") - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_go_systemd_prod_smoke.py b/tools/test_go_systemd_prod_smoke.py deleted file mode 100644 index 30e4962e..00000000 --- a/tools/test_go_systemd_prod_smoke.py +++ /dev/null @@ -1,101 +0,0 @@ -import unittest - -from tools import go_systemd_prod_smoke as smoke - - -class GoSystemdProdSmokeTest(unittest.TestCase): - def test_service_checks_require_all_go_units_active(self): - output = """ -lingniu-go-gateway.service active enabled -lingniu-go-history-writer.service active enabled -lingniu-go-stat-writer.service active enabled -lingniu-go-realtime-api.service active enabled -""" - - checks = smoke.service_checks(output, smoke.DEFAULT_SERVICES) - - self.assertTrue(all(check.status == "pass" for check in checks)) - - def test_service_checks_fail_when_unit_is_missing_or_inactive(self): - output = """ -lingniu-go-gateway.service active enabled -lingniu-go-history-writer.service failed enabled -""" - - checks = smoke.service_checks(output, smoke.DEFAULT_SERVICES) - by_name = {check.name: check for check in checks} - - self.assertEqual(by_name["service.lingniu-go-history-writer.service"].status, "fail") - self.assertEqual(by_name["service.lingniu-go-stat-writer.service"].status, "fail") - - def test_service_checks_fail_when_unit_is_not_enabled(self): - output = """ -lingniu-go-gateway.service active disabled -lingniu-go-history-writer.service active enabled -lingniu-go-stat-writer.service active enabled -lingniu-go-realtime-api.service active enabled -""" - - checks = smoke.service_checks(output, smoke.DEFAULT_SERVICES) - by_name = {check.name: check for check in checks} - - self.assertEqual(by_name["service.lingniu-go-gateway.service"].status, "fail") - self.assertIn("enabled=disabled", by_name["service.lingniu-go-gateway.service"].message) - - def test_port_checks_require_expected_go_processes(self): - output = """ -LISTEN 0 4096 *:32960 *:* users:(("gateway",pid=10,fd=3)) -LISTEN 0 4096 *:808 *:* users:(("gateway",pid=10,fd=4)) -LISTEN 0 4096 *:20210 *:* users:(("realtime-api",pid=11,fd=3)) -""" - - checks = smoke.port_checks(output, smoke.DEFAULT_PORTS) - - self.assertTrue(all(check.status == "pass" for check in checks)) - - def test_port_checks_fail_when_java_owns_ingest_port(self): - output = """ -LISTEN 0 4096 *:808 *:* users:(("java",pid=20,fd=4)) -LISTEN 0 4096 *:32960 *:* users:(("gateway",pid=10,fd=3)) -LISTEN 0 4096 *:20210 *:* users:(("realtime-api",pid=11,fd=3)) -""" - - checks = smoke.port_checks(output, smoke.DEFAULT_PORTS) - by_name = {check.name: check for check in checks} - - self.assertEqual(by_name["port.808"].status, "fail") - self.assertIn("java", by_name["port.808"].message) - - def test_spool_check_passes_when_no_pending_files(self): - check = smoke.spool_check("files=0 bytes=208896 recent=0") - - self.assertEqual(check.status, "pass") - self.assertIn("files=0", check.message) - - def test_spool_check_fails_when_pending_or_recent_files_exist(self): - for output in ["files=2 bytes=300 recent=0", "files=0 bytes=208896 recent=1"]: - check = smoke.spool_check(output) - - self.assertEqual(check.status, "fail") - - def test_release_check_passes_when_current_release_has_all_binaries(self): - check = smoke.release_check( - "current=/opt/lingniu-go-native/releases/8def635 " - "gateway=1 history-writer=1 stat-writer=1 realtime-api=1" - ) - - self.assertEqual(check.status, "pass") - self.assertIn("8def635", check.message) - - def test_release_check_fails_when_binary_is_missing(self): - check = smoke.release_check( - "current=/opt/lingniu-go-native/releases/8def635 " - "gateway=1 history-writer=0 stat-writer=1 realtime-api=1" - ) - - self.assertEqual(check.status, "fail") - self.assertIn("history-writer=0", check.message) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_jt808_e2e_smoke.py b/tools/test_jt808_e2e_smoke.py deleted file mode 100644 index 9ed59263..00000000 --- a/tools/test_jt808_e2e_smoke.py +++ /dev/null @@ -1,134 +0,0 @@ -import pathlib -import types -import unittest -from unittest import mock - -from tools import jt808_e2e_smoke as smoke - - -class Jt808E2ESmokeTest(unittest.TestCase): - def test_build_frame_adds_boundaries_and_valid_checksum(self): - body = smoke.build_location_body(smoke.parse_event_time("2024-01-02T03:04:05")) - - frame = smoke.build_frame(0x0200, "13079961234", 1, body) - - self.assertEqual(frame[0], 0x7E) - self.assertEqual(frame[-1], 0x7E) - unescaped = smoke.unescape_payload(frame[1:-1]) - self.assertEqual(smoke.bcc(unescaped), 0) - - def test_next_page_params_include_cursor(self): - params = smoke.next_page_params( - {"phone": "13079961234", "limit": 1}, - {"nextCursor": {"ts": "2024-01-01T19:04:05Z", "id": "frame-1"}}, - ) - - self.assertEqual(params["cursorTs"], "2024-01-01T19:04:05Z") - self.assertEqual(params["cursorId"], "frame-1") - self.assertEqual(params["phone"], "13079961234") - - def test_archive_path_maps_archive_uri_to_root(self): - path = smoke.archive_path( - pathlib.Path("/tmp/archive"), - "archive://2026/06/29/JT808/unknown/frame.bin", - ) - - self.assertEqual(path, pathlib.Path("/tmp/archive/2026/06/29/JT808/unknown/frame.bin")) - - def test_raw_frame_count_sql_filters_by_vehicle_key_phone_and_raw_uri(self): - sql = smoke.raw_frame_count_sql("13079961234", [ - { - "vehicleKey": "jt808:13079961234", - "rawUri": "archive://2026/06/29/JT808/13079961234/frame.bin", - }, - ]) - - self.assertEqual( - sql, - "SELECT COUNT(*) FROM raw_frames WHERE protocol = 'JT808' " - "AND vehicle_key = 'jt808:13079961234' " - "AND phone = '13079961234' " - "AND raw_uri = 'archive://2026/06/29/JT808/13079961234/frame.bin'", - ) - - def test_raw_frame_count_sqls_include_each_unique_visible_raw_uri(self): - sqls = smoke.raw_frame_count_sqls("13079961234", [ - { - "vehicleKey": "jt808:13079961234", - "rawUri": "archive://2026/06/29/JT808/13079961234/frame-1.bin", - }, - { - "vehicleKey": "jt808:13079961234", - "rawUri": "archive://2026/06/29/JT808/13079961234/frame-2.bin", - }, - { - "vehicleKey": "jt808:13079961234", - "rawUri": "archive://2026/06/29/JT808/13079961234/frame-1.bin", - }, - ]) - - self.assertEqual(len(sqls), 2) - self.assertTrue(sqls[0].endswith("frame-1.bin'")) - self.assertTrue(sqls[1].endswith("frame-2.bin'")) - - def test_verify_tdengine_raw_frames_allows_probe_without_visible_raw_uri(self): - verified = smoke.verify_tdengine_raw_frames( - "http://localhost:6041/rest/sql/vehicle_ts", - "root", - "taosdata", - [], - 0, - 1, - ) - - self.assertEqual(verified, 0) - - def test_expected_history_count_waits_for_pagination_sample(self): - args = types.SimpleNamespace(frames=3, expect_history_count=1, verify_pagination=True) - - self.assertEqual(smoke.expected_history_count(args), 2) - - def test_send_phone_workload_can_run_session_phones_with_workers(self): - event_time = smoke.parse_event_time("2024-01-02T03:04:05") - args = types.SimpleNamespace( - connection_mode="session", - tcp_host="127.0.0.1", - tcp_port=808, - frames=3, - tcp_timeout=1, - rate=0, - linger_ms=0, - post_register_ms=0, - require_register_ack=True, - workers=2, - ) - calls = [] - - def fake_send_phone_sequence(host, port, phone, phone_index, frames, sent_event_time, - timeout, rate, linger_ms, post_register_ms): - calls.append((host, port, phone, phone_index, frames, sent_event_time, - timeout, rate, linger_ms, post_register_ms)) - return b"ack", frames - - with mock.patch.object(smoke, "send_phone_sequence", side_effect=fake_send_phone_sequence): - result = smoke.send_phone_workload( - args, - ["13079962000", "13079962001", "13079962002"], - event_time, - ) - - self.assertEqual(result.sent_registers, 3) - self.assertEqual(result.sent_locations, 9) - self.assertGreater(result.elapsed_seconds, 0) - self.assertEqual( - sorted((call[2], call[3], call[4]) for call in calls), - [ - ("13079962000", 0, 3), - ("13079962001", 1, 3), - ("13079962002", 2, 3), - ], - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_tdengine_smoke.py b/tools/test_tdengine_smoke.py deleted file mode 100644 index 4b89ff9c..00000000 --- a/tools/test_tdengine_smoke.py +++ /dev/null @@ -1,35 +0,0 @@ -import unittest - -from tools import tdengine_smoke - - -class TdengineSmokeTest(unittest.TestCase): - def test_sql_literal_escapes_single_quotes(self): - self.assertEqual(tdengine_smoke.sql_literal("VIN'001"), "'VIN''001'") - - def test_raw_frame_count_sql_filters_by_protocol_vehicle_and_raw_uri(self): - sql = tdengine_smoke.raw_frame_count_sql( - protocol="JT808", - vehicle_key="jt808:13079962000", - raw_uri="archive://2026/06/29/JT808/frame.bin", - ) - - self.assertEqual( - sql, - "SELECT COUNT(*) FROM raw_frames WHERE protocol = 'JT808' " - "AND vehicle_key = 'jt808:13079962000' " - "AND raw_uri = 'archive://2026/06/29/JT808/frame.bin'", - ) - - def test_raw_frame_count_sql_requires_at_least_one_identity_or_raw_uri_filter(self): - with self.assertRaises(ValueError): - tdengine_smoke.raw_frame_count_sql(protocol="GB32960") - - def test_count_from_response_reads_first_cell(self): - response = {"code": 0, "data": [[3]], "rows": 1} - - self.assertEqual(tdengine_smoke.count_from_response(response), 3) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/test_vehicle_ingest_live_verify.py b/tools/test_vehicle_ingest_live_verify.py deleted file mode 100644 index 7b9b5593..00000000 --- a/tools/test_vehicle_ingest_live_verify.py +++ /dev/null @@ -1,142 +0,0 @@ -import unittest - -from tools import vehicle_ingest_live_verify as live_verify - - -class VehicleIngestLiveVerifyTest(unittest.TestCase): - def test_warns_when_formal_gb32960_platform_login_exists_without_vehicle_frames(self): - checks = live_verify.evaluate_counts( - { - "jt808RawFrames": 36090, - "jt808Locations": 8783, - "gb32960PlatformLogins": 3, - "gb32960VehicleRealtimeFrames": 0, - }, - live_verify.Thresholds( - min_jt808_raw_frames=1, - min_jt808_locations=1, - min_gb32960_platform_logins=1, - min_gb32960_vehicle_realtime_frames=1, - require_gb32960_vehicle_realtime=False, - ), - ) - - by_name = {check.name: check for check in checks} - - self.assertEqual(live_verify.overall_status(checks), "warn") - self.assertEqual(by_name["gb32960.vehicle_realtime"].status, "warn") - self.assertIn("尚未收到正式车辆 0x02", by_name["gb32960.vehicle_realtime"].message) - - def test_fails_when_vehicle_frames_are_required_but_missing(self): - checks = live_verify.evaluate_counts( - { - "jt808RawFrames": 10, - "jt808Locations": 2, - "gb32960PlatformLogins": 1, - "gb32960VehicleRealtimeFrames": 0, - }, - live_verify.Thresholds(require_gb32960_vehicle_realtime=True), - ) - - by_name = {check.name: check for check in checks} - - self.assertEqual(live_verify.overall_status(checks), "fail") - self.assertEqual(by_name["gb32960.vehicle_realtime"].status, "fail") - - def test_gb32960_vehicle_realtime_sql_excludes_test_vins(self): - sql = live_verify.gb32960_vehicle_realtime_count_sql( - date_from="2026-06-29 00:00:00", - date_to="2026-06-29 23:59:59", - ) - - self.assertIn("message_id = 2", sql) - self.assertIn("vin <> ''", sql) - self.assertIn("vin NOT LIKE 'LTEST%'", sql) - self.assertIn("ts >= '2026-06-29 00:00:00'", sql) - self.assertIn("ts <= '2026-06-29 23:59:59'", sql) - - def test_gb32960_peer_filter_treats_plain_ip_as_contains_pattern(self): - sql = live_verify.gb32960_platform_login_count_sql( - date_from="2026-06-29 00:00:00", - date_to="2026-06-29 23:59:59", - peer_like="115.29.187.205", - ) - - self.assertIn("peer LIKE '%115.29.187.205%'", sql) - - def test_gb32960_peer_filter_preserves_explicit_like_pattern(self): - sql = live_verify.gb32960_platform_login_count_sql( - date_from="2026-06-29 00:00:00", - date_to="2026-06-29 23:59:59", - peer_like="115.29.187.%", - ) - - self.assertIn("peer LIKE '115.29.187.%'", sql) - - def test_gb32960_command_counts_sql_uses_same_peer_filter(self): - sql = live_verify.gb32960_command_count_sql( - date_from="2026-06-29 00:00:00", - date_to="2026-06-29 23:59:59", - peer_like="115.29.187.205", - ) - - self.assertIn("protocol = 'GB32960'", sql) - self.assertIn("peer LIKE '%115.29.187.205%'", sql) - self.assertIn("GROUP BY message_id", sql) - - def test_gb32960_command_count_rows_include_command_names(self): - response = { - "code": 0, - "data": [ - [2, 9], - [5, 3], - ], - } - - rows = live_verify.command_count_rows(response) - - self.assertEqual(rows, [ - {"messageId": 2, "command": "REALTIME_REPORT", "count": 9}, - {"messageId": 5, "command": "PLATFORM_LOGIN", "count": 3}, - ]) - - def test_gb32960_recent_frame_rows_surface_peer_and_raw_uri(self): - response = { - "code": 0, - "data": [[ - "2026-06-29T12:26:21.385Z", - 5, - "", - "unknown:GB32960:f729d788b943630e", - "115.29.187.205:59630", - "archive://frame.bin", - ]], - } - - rows = live_verify.recent_frame_rows(response) - - self.assertEqual(rows, [{ - "ts": "2026-06-29T12:26:21.385Z", - "messageId": 5, - "command": "PLATFORM_LOGIN", - "vin": "", - "vehicleKey": "unknown:GB32960:f729d788b943630e", - "peer": "115.29.187.205:59630", - "rawUri": "archive://frame.bin", - }]) - - def test_jt808_location_sql_uses_current_vehicle_locations_stable_with_protocol_filter(self): - sql = live_verify.jt808_location_count_sql( - date_from="2026-06-29 00:00:00", - date_to="2026-06-29 23:59:59", - ) - - self.assertEqual( - sql, - "SELECT COUNT(*) FROM vehicle_locations WHERE protocol = 'JT808' " - "AND ts >= '2026-06-29 00:00:00' AND ts <= '2026-06-29 23:59:59'", - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/tools/vehicle_ingest_live_verify.py b/tools/vehicle_ingest_live_verify.py deleted file mode 100755 index dae81b33..00000000 --- a/tools/vehicle_ingest_live_verify.py +++ /dev/null @@ -1,392 +0,0 @@ -#!/usr/bin/env python3 -"""Verify the live 32960/JT808 ingest-to-history chain with production traffic.""" - -from __future__ import annotations - -import argparse -import base64 -import json -import os -import urllib.parse -import urllib.request -from dataclasses import asdict, dataclass -from typing import Any - -try: - from tools import tdengine_smoke -except ModuleNotFoundError: - import tdengine_smoke - - -@dataclass(frozen=True) -class Thresholds: - min_jt808_raw_frames: int = 1 - min_jt808_locations: int = 1 - min_gb32960_platform_logins: int = 1 - min_gb32960_vehicle_realtime_frames: int = 1 - require_gb32960_vehicle_realtime: bool = False - - -@dataclass(frozen=True) -class Check: - name: str - status: str - count: int - minimum: int - message: str - - -def count_sql(table: str, filters: list[str], date_from: str, date_to: str) -> str: - where = list(filters) - if date_from: - where.append("ts >= " + tdengine_smoke.sql_literal(date_from)) - if date_to: - where.append("ts <= " + tdengine_smoke.sql_literal(date_to)) - return "SELECT COUNT(*) FROM " + table + " WHERE " + " AND ".join(where) - - -def jt808_raw_count_sql(date_from: str, date_to: str, peer_like: str = "") -> str: - filters = ["protocol = 'JT808'"] - if peer_like: - filters.append("peer LIKE " + tdengine_smoke.sql_literal(peer_like)) - return count_sql("raw_frames", filters, date_from, date_to) - - -def jt808_location_count_sql(date_from: str, date_to: str) -> str: - return count_sql("vehicle_locations", ["protocol = 'JT808'"], date_from, date_to) - - -def gb32960_platform_login_count_sql(date_from: str, date_to: str, peer_like: str = "") -> str: - filters = ["protocol = 'GB32960'", "message_id = 5"] - if peer_like: - filters.append("peer LIKE " + tdengine_smoke.sql_literal(peer_like_pattern(peer_like))) - return count_sql("raw_frames", filters, date_from, date_to) - - -def gb32960_vehicle_realtime_count_sql(date_from: str, date_to: str, peer_like: str = "") -> str: - filters = [ - "protocol = 'GB32960'", - "message_id = 2", - "vin <> ''", - "vin NOT LIKE 'LTEST%'", - ] - if peer_like: - filters.append("peer LIKE " + tdengine_smoke.sql_literal(peer_like_pattern(peer_like))) - return count_sql("raw_frames", filters, date_from, date_to) - - -def latest_gb32960_platform_raw_uri_sql(date_from: str, date_to: str, peer_like: str = "") -> str: - filters = ["protocol = 'GB32960'", "message_id = 5", "raw_uri <> ''"] - if peer_like: - filters.append("peer LIKE " + tdengine_smoke.sql_literal(peer_like_pattern(peer_like))) - where = list(filters) - if date_from: - where.append("ts >= " + tdengine_smoke.sql_literal(date_from)) - if date_to: - where.append("ts <= " + tdengine_smoke.sql_literal(date_to)) - return "SELECT raw_uri FROM raw_frames WHERE " + " AND ".join(where) + " ORDER BY ts DESC LIMIT 1" - - -def gb32960_command_count_sql(date_from: str, date_to: str, peer_like: str = "") -> str: - filters = ["protocol = 'GB32960'"] - if peer_like: - filters.append("peer LIKE " + tdengine_smoke.sql_literal(peer_like_pattern(peer_like))) - where = list(filters) - if date_from: - where.append("ts >= " + tdengine_smoke.sql_literal(date_from)) - if date_to: - where.append("ts <= " + tdengine_smoke.sql_literal(date_to)) - return ( - "SELECT message_id,COUNT(*) FROM raw_frames WHERE " - + " AND ".join(where) - + " GROUP BY message_id ORDER BY message_id" - ) - - -def gb32960_recent_frame_sql(date_from: str, date_to: str, peer_like: str = "", limit: int = 10) -> str: - filters = ["protocol = 'GB32960'"] - if peer_like: - filters.append("peer LIKE " + tdengine_smoke.sql_literal(peer_like_pattern(peer_like))) - where = list(filters) - if date_from: - where.append("ts >= " + tdengine_smoke.sql_literal(date_from)) - if date_to: - where.append("ts <= " + tdengine_smoke.sql_literal(date_to)) - safe_limit = max(1, min(limit, 100)) - return ( - "SELECT ts,message_id,vin,vehicle_key,peer,raw_uri FROM raw_frames WHERE " - + " AND ".join(where) - + " ORDER BY ts DESC LIMIT " - + str(safe_limit) - ) - - -def peer_like_pattern(value: str) -> str: - value = value.strip() - if "%" in value or "_" in value: - return value - return "%" + value + "%" - - -def command_name(message_id: int) -> str: - return { - 1: "VEHICLE_LOGIN", - 2: "REALTIME_REPORT", - 3: "RESEND_REPORT", - 4: "VEHICLE_LOGOUT", - 5: "PLATFORM_LOGIN", - 6: "PLATFORM_LOGOUT", - 7: "HEARTBEAT", - 8: "TIME_CALIBRATION", - }.get(message_id, "UNKNOWN_0x" + format(message_id, "02X")) - - -def response_rows(response: dict[str, Any]) -> list[list[Any]]: - if response.get("code") != 0: - raise RuntimeError(f"tdengine query failed: {response}") - return response.get("data") or [] - - -def command_count_rows(response: dict[str, Any]) -> list[dict[str, Any]]: - rows = [] - for item in response_rows(response): - message_id = int(item[0]) - rows.append({ - "messageId": message_id, - "command": command_name(message_id), - "count": int(item[1]), - }) - return rows - - -def recent_frame_rows(response: dict[str, Any]) -> list[dict[str, Any]]: - rows = [] - for item in response_rows(response): - message_id = int(item[1]) - rows.append({ - "ts": item[0], - "messageId": message_id, - "command": command_name(message_id), - "vin": item[2], - "vehicleKey": item[3], - "peer": item[4], - "rawUri": item[5], - }) - return rows - - -def evaluate_counts(counts: dict[str, int], thresholds: Thresholds) -> list[Check]: - checks = [ - threshold_check( - "jt808.raw_frames", - counts.get("jt808RawFrames", 0), - thresholds.min_jt808_raw_frames, - "JT808 raw_frames 已有正式报文", - "JT808 raw_frames 未达到最低验收数量", - ), - threshold_check( - "jt808.locations", - counts.get("jt808Locations", 0), - thresholds.min_jt808_locations, - "JT808 位置历史已入 TDengine", - "JT808 位置历史未达到最低验收数量", - ), - threshold_check( - "gb32960.platform_login", - counts.get("gb32960PlatformLogins", 0), - thresholds.min_gb32960_platform_logins, - "GB32960 正式平台登录已入 TDengine", - "GB32960 正式平台登录未达到最低验收数量", - ), - ] - vehicle_count = counts.get("gb32960VehicleRealtimeFrames", 0) - vehicle_minimum = thresholds.min_gb32960_vehicle_realtime_frames - if vehicle_count >= vehicle_minimum: - checks.append(Check( - "gb32960.vehicle_realtime", - "pass", - vehicle_count, - vehicle_minimum, - "GB32960 正式车辆 0x02 实时上报已入 TDengine", - )) - else: - status = "fail" if thresholds.require_gb32960_vehicle_realtime else "warn" - checks.append(Check( - "gb32960.vehicle_realtime", - status, - vehicle_count, - vehicle_minimum, - "尚未收到正式车辆 0x02;平台登录在线不等于车辆数据在线", - )) - return checks - - -def threshold_check(name: str, count: int, minimum: int, ok_message: str, bad_message: str) -> Check: - if count >= minimum: - return Check(name, "pass", count, minimum, ok_message) - return Check(name, "fail", count, minimum, bad_message) - - -def overall_status(checks: list[Check]) -> str: - statuses = {check.status for check in checks} - if "fail" in statuses: - return "fail" - if "warn" in statuses: - return "warn" - return "pass" - - -def query_json(rest_url: str, username: str, password: str, sql: str, timeout: float) -> dict[str, Any]: - request = urllib.request.Request(rest_url, data=sql.encode("utf-8"), method="POST") - if username: - token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("ascii") - request.add_header("Authorization", "Basic " + token) - request.add_header("Content-Type", "text/plain; charset=utf-8") - request.add_header("Accept", "application/json") - with urllib.request.urlopen(request, timeout=timeout) as response: - return json.loads(response.read().decode("utf-8")) - - -def first_cell(response: dict[str, Any]) -> Any: - if response.get("code") != 0: - raise RuntimeError(f"tdengine query failed: {response}") - data = response.get("data") or [] - if not data or not data[0]: - return None - return data[0][0] - - -def query_counts(args: argparse.Namespace) -> dict[str, int]: - queries = { - "jt808RawFrames": jt808_raw_count_sql(args.date_from, args.date_to, args.jt808_peer_like), - "jt808Locations": jt808_location_count_sql(args.date_from, args.date_to), - "gb32960PlatformLogins": gb32960_platform_login_count_sql( - args.date_from, args.date_to, args.gb32960_peer_like), - "gb32960VehicleRealtimeFrames": gb32960_vehicle_realtime_count_sql( - args.date_from, args.date_to, args.gb32960_peer_like), - } - out: dict[str, int] = {} - for key, sql in queries.items(): - out[key] = tdengine_smoke.query_count( - args.tdengine_rest_url, - args.tdengine_username, - args.tdengine_password, - sql, - args.http_timeout, - ) - return out - - -def query_latest_platform_raw_uri(args: argparse.Namespace) -> str: - sql = latest_gb32960_platform_raw_uri_sql(args.date_from, args.date_to, args.gb32960_peer_like) - response = query_json( - args.tdengine_rest_url, - args.tdengine_username, - args.tdengine_password, - sql, - args.http_timeout, - ) - value = first_cell(response) - return value if isinstance(value, str) else "" - - -def query_gb32960_command_counts(args: argparse.Namespace) -> list[dict[str, Any]]: - response = query_json( - args.tdengine_rest_url, - args.tdengine_username, - args.tdengine_password, - gb32960_command_count_sql(args.date_from, args.date_to, args.gb32960_peer_like), - args.http_timeout, - ) - return command_count_rows(response) - - -def query_gb32960_recent_frames(args: argparse.Namespace) -> list[dict[str, Any]]: - response = query_json( - args.tdengine_rest_url, - args.tdengine_username, - args.tdengine_password, - gb32960_recent_frame_sql( - args.date_from, - args.date_to, - args.gb32960_peer_like, - args.gb32960_recent_limit), - args.http_timeout, - ) - return recent_frame_rows(response) - - -def decode_frame(history_base_url: str, raw_uri: str, timeout: float) -> dict[str, Any]: - if not raw_uri: - return {} - params = urllib.parse.urlencode({"rawArchiveUri": raw_uri}) - request = urllib.request.Request( - history_base_url.rstrip("/") + "/api/event-history/gb32960/frame?" + params, - headers={"Accept": "application/json"}, - ) - with urllib.request.urlopen(request, timeout=timeout) as response: - return json.loads(response.read().decode("utf-8")) - - -def run(args: argparse.Namespace) -> dict[str, Any]: - counts = query_counts(args) - thresholds = Thresholds( - min_jt808_raw_frames=args.min_jt808_raw_frames, - min_jt808_locations=args.min_jt808_locations, - min_gb32960_platform_logins=args.min_gb32960_platform_logins, - min_gb32960_vehicle_realtime_frames=args.min_gb32960_vehicle_realtime_frames, - require_gb32960_vehicle_realtime=args.require_gb32960_vehicle_realtime, - ) - checks = evaluate_counts(counts, thresholds) - latest_raw_uri = "" - decoded_platform_frame: dict[str, Any] = {} - command_counts = query_gb32960_command_counts(args) - recent_frames = query_gb32960_recent_frames(args) - if args.history_base_url: - latest_raw_uri = query_latest_platform_raw_uri(args) - decoded_platform_frame = decode_frame(args.history_base_url, latest_raw_uri, args.http_timeout) - return { - "status": overall_status(checks), - "dateFrom": args.date_from, - "dateTo": args.date_to, - "counts": counts, - "checks": [asdict(check) for check in checks], - "gb32960CommandCounts": command_counts, - "gb32960RecentFrames": recent_frames, - "gb32960LatestPlatformRawUri": latest_raw_uri, - "gb32960LatestPlatformCommand": decoded_platform_frame.get("command"), - } - - -def parser() -> argparse.ArgumentParser: - p = argparse.ArgumentParser(description=__doc__) - p.add_argument("--tdengine-rest-url", default=os.environ.get("TDENGINE_REST_URL", "")) - p.add_argument("--tdengine-username", default=os.environ.get("TDENGINE_USERNAME", "root")) - p.add_argument("--tdengine-password", default=os.environ.get("TDENGINE_PASSWORD", "taosdata")) - p.add_argument("--history-base-url", default=os.environ.get("HISTORY_BASE_URL", "http://127.0.0.1:20200")) - p.add_argument("--date-from", required=True) - p.add_argument("--date-to", required=True) - p.add_argument("--jt808-peer-like", default=os.environ.get("JT808_PEER_LIKE", "")) - p.add_argument("--gb32960-peer-like", default=os.environ.get("GB32960_PEER_LIKE", "")) - p.add_argument("--min-jt808-raw-frames", type=int, default=1) - p.add_argument("--min-jt808-locations", type=int, default=1) - p.add_argument("--min-gb32960-platform-logins", type=int, default=1) - p.add_argument("--min-gb32960-vehicle-realtime-frames", type=int, default=1) - p.add_argument("--require-gb32960-vehicle-realtime", action="store_true") - p.add_argument("--gb32960-recent-limit", type=int, default=10) - p.add_argument("--http-timeout", type=float, default=5) - return p - - -def main() -> None: - args = parser().parse_args() - if not args.tdengine_rest_url: - raise SystemExit("--tdengine-rest-url is required") - result = run(args) - print(json.dumps(result, ensure_ascii=False, indent=2)) - if result["status"] == "fail": - raise SystemExit(1) - - -if __name__ == "__main__": - main() diff --git a/woodpecker.yml b/woodpecker.yml deleted file mode 100644 index 73eae03b..00000000 --- a/woodpecker.yml +++ /dev/null @@ -1,78 +0,0 @@ -steps: - - name: maven-build - image: maven:3.9.16-eclipse-temurin-25 - when: - event: - - push - - pull_request - - manual - branch: - - master - - develop - - main - environment: - ACTIVE_APPS: &active_apps "gb32960-ingest-app jt808-ingest-app yutong-mqtt-app vehicle-history-app vehicle-analytics-app" - commands: | - cd $CI_WORKSPACE - - ACTIVE_MODULES="" - for APP_NAME in $ACTIVE_APPS; do - if [ -z "$ACTIVE_MODULES" ]; then - ACTIVE_MODULES=":$APP_NAME" - else - ACTIVE_MODULES="$ACTIVE_MODULES,:$APP_NAME" - fi - done - - mvn -B -ntp -am -pl "$ACTIVE_MODULES" package -Dmaven.test.skip=true - - BRANCH_NAME=$(echo ${CI_COMMIT_BRANCH:-local} | tr / -) - PROJECT_VERSION=$(mvn -q -DforceStdout help:evaluate -Dexpression=project.version) - SHORT_SHA=$(echo ${CI_COMMIT_SHA:-manual} | cut -c1-8) - IMAGE_VERSION="$BRANCH_NAME-$PROJECT_VERSION-$SHORT_SHA" - - echo "Project version: $PROJECT_VERSION" - echo "Image version: $IMAGE_VERSION" - echo "$IMAGE_VERSION" > $CI_WORKSPACE/project_version.txt - - - name: docker-build-apps - image: docker:24.0.5-cli - when: - event: - - push - - pull_request - - manual - branch: - - master - - develop - - main - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - ACTIVE_APPS: *active_apps - ALIYUN_ACR_USERNAME: - from_secret: aliyun_acr_username - ALIYUN_ACR_PASSWORD: - from_secret: aliyun_acr_password - commands: | - cd $CI_WORKSPACE - APP_VERSION=$(cat $CI_WORKSPACE/project_version.txt) - REGISTRY=${LINGNIU_IMAGE_REGISTRY:-crpi-85r4m0ackrm3qpje.cn-shanghai.personal.cr.aliyuncs.com} - NAMESPACE=${LINGNIU_IMAGE_NAMESPACE:-oneos} - - printf '%s' "$ALIYUN_ACR_PASSWORD" | docker login \ - --username "$ALIYUN_ACR_USERNAME" \ - --password-stdin \ - "$REGISTRY" - - for APP_NAME in $ACTIVE_APPS; do - IMAGE=$REGISTRY/$NAMESPACE/$APP_NAME:$APP_VERSION - echo "Building and pushing $IMAGE" - - docker build \ - --build-arg APP_NAME=$APP_NAME \ - --build-arg APP_VERSION=$APP_VERSION \ - -t $IMAGE . - - docker push $IMAGE - done