feat: ack gb32960 after kafka boundary
This commit is contained in:
@@ -122,41 +122,91 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
return;
|
||||
}
|
||||
|
||||
// 鉴权通过:按命令类型回 ACK,写成功后统一 dispatch 到通用管线。
|
||||
// 鉴权通过:先完成命令侧状态变更/日志,再统一 dispatch 到通用管线。
|
||||
// 需要成功 ACK 的已接收帧,等待 dispatch 的持久化边界完成后再回 ACK。
|
||||
// 平台登入鉴权失败会短路 return,不进 dispatch。
|
||||
switch (cmd) {
|
||||
case VEHICLE_LOGIN -> handleVehicleLogin(ctx, msg, rawVin);
|
||||
case VEHICLE_LOGOUT -> handleVehicleLogout(ctx, msg, rawVin);
|
||||
case VEHICLE_LOGIN -> logVehicleLogin(ctx, msg);
|
||||
case VEHICLE_LOGOUT -> logVehicleLogout(ctx, msg);
|
||||
case PLATFORM_LOGIN -> {
|
||||
if (!handlePlatformLogin(ctx, msg, rawVin)) return;
|
||||
}
|
||||
case PLATFORM_LOGOUT -> handlePlatformLogout(ctx, msg, rawVin);
|
||||
case REALTIME_REPORT, RESEND_REPORT, HEARTBEAT -> handleReportOrHeartbeat(ctx, msg, rawVin);
|
||||
case TIME_CALIBRATION -> handleTimeCalibration(ctx, msg, rawVin);
|
||||
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;
|
||||
}
|
||||
|
||||
dispatcher.dispatchAndAwait(rf).whenComplete((ignored, ex) ->
|
||||
ctx.executor().execute(() -> {
|
||||
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);
|
||||
}));
|
||||
}
|
||||
|
||||
private RawFrame rawFrame(ChannelHandlerContext ctx, Gb32960Message msg, byte[] frame) {
|
||||
Map<String, String> sourceMeta = new HashMap<>(4);
|
||||
sourceMeta.put("vin", vin);
|
||||
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 回查的源头;不要在这里裁剪或重编码。
|
||||
RawFrame rf = new RawFrame(
|
||||
return new RawFrame(
|
||||
ProtocolId.GB32960,
|
||||
cmd.code(),
|
||||
msg.header().command().code(),
|
||||
0,
|
||||
msg,
|
||||
frame,
|
||||
sourceMeta,
|
||||
Instant.now());
|
||||
dispatcher.dispatch(rf);
|
||||
}
|
||||
|
||||
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 handleVehicleLogin(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
|
||||
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(),
|
||||
@@ -166,13 +216,14 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
eventOrNow(msg),
|
||||
null,
|
||||
"vehicle-login-ack");
|
||||
log.info("[gb32960] vehicle login peer={} vin={} protocolVersion={}",
|
||||
addr(ctx), msg.header().vin(), msg.header().protocolVersion());
|
||||
}
|
||||
|
||||
/** 0x04 车辆登出:INFO 日志 + 带原采集时间回成功 ACK。 */
|
||||
private void handleVehicleLogout(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
|
||||
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,
|
||||
@@ -189,8 +240,8 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
* <ul>
|
||||
* <li>消息体解析缺失:关闭连接,返回 false 短路
|
||||
* <li>鉴权失败:写 {@code 0x02 OTHER_ERROR} NACK + 关闭连接,返回 false 短路
|
||||
* <li>鉴权通过:把 username 钉到 channel attribute(供后续 vendor profile 路由),
|
||||
* 写成功 ACK + INFO 日志,返回 true 继续 dispatch
|
||||
* <li>鉴权通过:把 username 钉到 channel attribute(供后续 vendor profile 路由),INFO 日志,
|
||||
* 返回 true 继续 dispatch;成功 ACK 等 dispatch 持久化边界完成后再写
|
||||
* </ul>
|
||||
*
|
||||
* @return true 表示鉴权通过应继续 dispatch;false 表示调用方应 short-circuit return
|
||||
@@ -221,6 +272,10 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
addr(ctx), pl.username(), pl.encryptRule(), pl.serialNo(), pl.loginTime(), result);
|
||||
// 把 username 钉到 channel attribute,供后续 0x02/0x03 帧的 vendor profile 路由
|
||||
accessService.bindPlatformAccount(ctx.channel(), pl.username());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writePlatformLoginAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
|
||||
ackService.writeResponse(
|
||||
ctx,
|
||||
msg.header().protocolVersion(),
|
||||
@@ -230,12 +285,14 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
eventOrNow(msg),
|
||||
null,
|
||||
"platform-login-ack");
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 0x06 平台登出:INFO 日志 + 带原采集时间回成功 ACK。 */
|
||||
private void handlePlatformLogout(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
|
||||
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,
|
||||
@@ -247,7 +304,7 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 0x02/0x03/0x07 实时 / 补发 / 心跳:按 §6.3.2 回应答帧,保留原帧采集时间。
|
||||
* 0x02/0x03/0x07 实时 / 补发 / 心跳:记录帧诊断;成功 ACK 在 dispatch 持久化边界后写回。
|
||||
* 部分车端实现严格按规范,没收到应答会停止后续上报或重连。ack-tag 按 cmd.code 区分便于日志筛选。
|
||||
*
|
||||
* <p>注意:应答成功不代表下游已完成持久化,只表示平台已接收并通过 Dispatcher 投递。
|
||||
@@ -264,17 +321,9 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
* </ul>
|
||||
* 帧内字段全量 JSON 仍保留 DEBUG。
|
||||
*/
|
||||
private void handleReportOrHeartbeat(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
|
||||
private void logReportOrHeartbeat(ChannelHandlerContext ctx, Gb32960Message msg) {
|
||||
CommandType cmd = msg.header().command();
|
||||
String platformAccount = accessService.platformAccount(ctx.channel());
|
||||
ackService.writeResponse(ctx,
|
||||
msg.header().protocolVersion(),
|
||||
cmd,
|
||||
ResponseFlag.SUCCESS,
|
||||
rawVin,
|
||||
eventOrNow(msg),
|
||||
null,
|
||||
"report-ack-0x" + Integer.toHexString(cmd.code()));
|
||||
if (cmd == CommandType.HEARTBEAT) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("[gb32960] HEARTBEAT peer={} vin={} platformAccount={}",
|
||||
@@ -304,8 +353,20 @@ public class Gb32960ChannelHandler extends SimpleChannelInboundHandler<byte[]> {
|
||||
}
|
||||
}
|
||||
|
||||
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 handleTimeCalibration(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
|
||||
private void writeTimeCalibrationAck(ChannelHandlerContext ctx, Gb32960Message msg, byte[] rawVin) {
|
||||
ackService.writeResponse(ctx,
|
||||
msg.header().protocolVersion(),
|
||||
CommandType.TIME_CALIBRATION,
|
||||
|
||||
Reference in New Issue
Block a user