diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java index bf63672e..d9a7c530 100644 --- a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/codec/Gb32960MessageDecoder.java @@ -66,6 +66,15 @@ public final class Gb32960MessageDecoder implements FrameDecoder @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); @@ -103,7 +112,8 @@ public final class Gb32960MessageDecoder implements FrameDecoder if (dataLength < 6) throw new DecodeException("report body missing timestamp"); Instant eventTime = parseTimestamp(frame, bodyStart); ByteBuffer body = ByteBuffer.wrap(frame, bodyStart + 6, dataLength - 6); - BodyParseResult result = bodyParser.parse(version, body); + 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()); diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java index 68fed881..c3068854 100644 --- a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/config/Gb32960AutoConfiguration.java @@ -28,6 +28,16 @@ import com.lingniu.ingest.protocol.gb32960.codec.parser.v2025.PositionV2025Block 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.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.Gb32960NettyServer; import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper; @@ -79,18 +89,63 @@ public class Gb32960AutoConfiguration { @Bean public InfoBlockParser gb32960V2025SuperCap() { return new SuperCapacitorV2025BlockParser(); } @Bean public InfoBlockParser gb32960V2025SuperCapExtreme() { return new SuperCapacitorExtremeV2025BlockParser(); } - // ================= Core components ================= + // ================= Vendor 扩展套件目录 ================= + /** + * 内置 vendor 扩展套件目录。当前注册: + * + * 未来新增其他厂商扩展时直接在这里加 entry。 + */ @Bean @ConditionalOnMissingBean - public InfoBlockParserRegistry gb32960InfoBlockParserRegistry(List parsers) { - return new InfoBlockParserRegistry(parsers); + 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()))); + } + + // ================= 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()); + } + return new Gb32960ProfileRegistry(parsers, catalog, enabled); + } + + /** Vendor 扩展选择器:按配置 list 顺序首匹。 */ + @Bean + @ConditionalOnMissingBean + public VendorExtensionSelector gb32960VendorExtensionSelector(Gb32960Properties props, + VendorExtensionCatalog catalog) { + if (props.getVendorExtensions() == null || props.getVendorExtensions().isEmpty()) { + return VendorExtensionSelector.NONE; + } + return new RuleBasedVendorExtensionSelector(props.getVendorExtensions(), catalog.names()); } @Bean @ConditionalOnMissingBean - public Gb32960BodyParser gb32960BodyParser(InfoBlockParserRegistry registry) { - return new Gb32960BodyParser(registry); + public Gb32960BodyParser gb32960BodyParser(Gb32960ProfileRegistry profileRegistry, + VendorExtensionSelector selector) { + return new Gb32960BodyParser(profileRegistry, selector); } @Bean diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java index dfd3ab92..021ba60b 100644 --- a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java +++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/inbound/Gb32960ChannelHandler.java @@ -19,6 +19,7 @@ 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; @@ -47,6 +48,14 @@ 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 = + AttributeKey.valueOf("gb32960.platformAccount"); + /** 仅用于调试:把解析后的 Gb32960Message 序列化成 JSON 打到日志。 */ private static final ObjectMapper DEBUG_JSON = new ObjectMapper() .registerModule(new JavaTimeModule()) @@ -94,7 +103,9 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler { protected void channelRead0(ChannelHandlerContext ctx, byte[] frame) { Gb32960Message msg; try { - msg = decoder.decode(ByteBuffer.wrap(frame)); + String platformAccount = ctx.channel().hasAttr(PLATFORM_ACCOUNT_ATTR) + ? ctx.channel().attr(PLATFORM_ACCOUNT_ATTR).get() : null; + msg = decoder.decode(ByteBuffer.wrap(frame), platformAccount); } catch (Exception e) { log.warn("[gb32960] decode failed peer={} len={}", addr(ctx), frame.length, e); return; @@ -164,6 +175,8 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler { } 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 路由 + ctx.channel().attr(PLATFORM_ACCOUNT_ATTR).set(pl.username()); byte[] ack = Gb32960FrameEncoder.buildResponse( msg.header().protocolVersion(), CommandType.PLATFORM_LOGIN,