diff --git a/njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/BCD.java b/njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/BCD.java
new file mode 100644
index 0000000..cc7913f
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/BCD.java
@@ -0,0 +1,12 @@
+package com.njzscloud.common.core.utils;
+
+public class BCD {
+ public static String bcdToStr(byte[] bcd) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bcd) {
+ sb.append(String.format("%02x", b));
+ }
+ // 移除前面的0
+ return sb.toString().replaceFirst("^0+", "");
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/Scheduler.java b/njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/Scheduler.java
new file mode 100644
index 0000000..3ba0c0f
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/Scheduler.java
@@ -0,0 +1,41 @@
+package com.njzscloud.common.core.utils;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+public class Scheduler {
+ private static ScheduledExecutorService scheduler;
+
+ public static void init(int corePoolSize) {
+ scheduler = Executors.newScheduledThreadPool(corePoolSize);
+ }
+
+ public static void shutdown() {
+ if (scheduler != null) {
+ scheduler.shutdown();
+ scheduler = null;
+ }
+ log.info("定时器已关闭");
+ }
+
+ public static void init() {
+ scheduler = Executors.newScheduledThreadPool(1);
+ log.info("定时器初始化成功");
+ }
+
+ public static void setInterval(Runnable command, long period) {
+ scheduler.scheduleAtFixedRate(command, 0, period, TimeUnit.SECONDS);
+ }
+
+ public static void setInterval(Runnable command) {
+ scheduler.submit(command);
+ }
+
+ public static void setTimeout(Runnable command, long delay) {
+ scheduler.schedule(command, delay, TimeUnit.SECONDS);
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/pom.xml b/njzscloud-common/njzscloud-common-localizer/pom.xml
new file mode 100644
index 0000000..f4e3117
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/pom.xml
@@ -0,0 +1,33 @@
+
+ 4.0.0
+
+ com.njzscloud
+ njzscloud-common
+ 0.0.1
+
+
+ njzscloud-common-localizer
+ jar
+
+
+ UTF-8
+
+
+
+
+ com.njzscloud
+ njzscloud-common-core
+ provided
+
+
+ com.njzscloud
+ njzscloud-common-mqtt
+ provided
+
+
+ io.netty
+ netty-all
+
+
+
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerAutoConfiguration.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerAutoConfiguration.java
new file mode 100644
index 0000000..f932507
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerAutoConfiguration.java
@@ -0,0 +1,23 @@
+package com.njzscloud.common.localizer.config;
+
+import com.njzscloud.common.localizer.mqtt.MqttMsgHandlers;
+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.Configuration;
+
+@Configuration
+@EnableConfigurationProperties({LocalizerProperties.class})
+@ConditionalOnProperty(prefix = "localizer", name = "enabled", havingValue = "true")
+public class LocalizerAutoConfiguration {
+
+ @Bean(initMethod = "init", destroyMethod = "destroy")
+ public LocalizerInitializer localizerInitializer(LocalizerProperties properties) {
+ return new LocalizerInitializer(properties);
+ }
+
+ @Bean
+ public MqttMsgHandlers mqttMsgHandlers() {
+ return new MqttMsgHandlers();
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerInitializer.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerInitializer.java
new file mode 100644
index 0000000..19cb88a
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerInitializer.java
@@ -0,0 +1,19 @@
+package com.njzscloud.common.localizer.config;
+
+import com.njzscloud.common.localizer.tuqiang.Tuqiang;
+
+public class LocalizerInitializer {
+ private final LocalizerProperties properties;
+
+ public LocalizerInitializer(LocalizerProperties properties) {
+ this.properties = properties;
+ }
+
+ public void init() {
+ Tuqiang.run(properties.getPort(), properties.getBossThreads(), properties.getWorkerThreads());
+ }
+
+ private void destroy() {
+ Tuqiang.stop();
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerProperties.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerProperties.java
new file mode 100644
index 0000000..af659c3
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerProperties.java
@@ -0,0 +1,19 @@
+package com.njzscloud.common.localizer.config;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+@ConfigurationProperties(prefix = "localizer")
+public class LocalizerProperties {
+ private boolean enabled;
+ private int port;
+ private int bossThreads;
+ private int workerThreads;
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/JT808.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/JT808.java
new file mode 100644
index 0000000..7128588
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/JT808.java
@@ -0,0 +1,214 @@
+package com.njzscloud.common.localizer.jt808;
+
+import com.njzscloud.common.core.utils.BCD;
+import com.njzscloud.common.localizer.jt808.message.JT808Message;
+import com.njzscloud.common.localizer.jt808.message.ServerGeneralResponseMessage;
+import com.njzscloud.common.localizer.jt808.message.ServerTxtMessage;
+import com.njzscloud.common.localizer.jt808.message.TerminalMessageBody;
+import com.njzscloud.common.localizer.jt808.support.JT808MessageResolver;
+import com.njzscloud.common.localizer.jt808.support.TcpServer;
+import com.njzscloud.common.localizer.jt808.util.FlowId;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * JT808消息发送工具类
+ * 提供便捷的方法发送各种类型的JT808消息
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class JT808 {
+
+ // 终端手机号与Channel的映射
+ public static final Map DeviceHandle = new ConcurrentHashMap<>();
+
+ private static TcpServer tcpServer;
+
+ public static void run(int port, int bossThreads, int workerThreads) {
+ tcpServer = new TcpServer(port, bossThreads, workerThreads);
+ tcpServer.start();
+ }
+
+ public static void stop() {
+ if (tcpServer != null) {
+ tcpServer.stop();
+ tcpServer = null;
+ }
+ }
+
+ /**
+ * 注册消息监听器
+ */
+ public static void addListener(String messageId, Consumer listener) {
+ JT808MessageResolver.addResolver(messageId, listener);
+ }
+
+ /**
+ * 注册终端与通道的映射关系
+ */
+ public static void register(String terminalId, Channel channel) {
+ if (terminalId != null && channel != null) {
+ if (DeviceHandle.get(terminalId) != channel) {
+ DeviceHandle.put(terminalId, channel);
+ }
+ }
+ }
+
+ /**
+ * 移除终端与通道的映射关系
+ */
+ public static void unregister(Channel channel) {
+ if (channel != null) {
+ DeviceHandle.values().removeIf(ch -> ch == channel);
+ }
+ }
+
+ /**
+ * 发送通用应答消息 (0x8001)
+ */
+ public static void sendGeneralResponse(JT808Message message, int result) {
+ String terminalPhone = message.getTerminalPhone();
+ int flowId = message.getFlowId();
+ int messageId = message.getMessageId();
+ ServerGeneralResponseMessage response = new ServerGeneralResponseMessage(messageId, flowId, result);
+
+ message = JT808.createBaseMessage(terminalPhone, 0x8001, response.toBytes());
+ sendMessage(message);
+ }
+
+ public static void sendGeneralResponse(JT808Message message) {
+ sendGeneralResponse(message, 0);
+ }
+
+ /**
+ * 发送文本消息 (0x8300)
+ */
+ public static void sendTxtMessage(String terminalId, String txt) {
+ ServerTxtMessage txtMessage = new ServerTxtMessage(txt);
+ JT808Message message = JT808.createBaseMessage(terminalId, 0x8300, txtMessage.toBytes());
+ sendMessage(message);
+ }
+
+ /**
+ * 向指定终端发送消息
+ */
+ public static void sendMessage(JT808Message message) {
+ String terminalPhone = message.getTerminalPhone();
+
+ Channel channel = DeviceHandle.get(terminalPhone);
+ if (channel != null && channel.isActive()) {
+ channel.writeAndFlush(message)
+ .addListener((ChannelFutureListener) future -> {
+ if (future.isSuccess()) {
+ log.info("消息发送成功, 终端: {}, 消息ID: 0x{}, 流水号: {}", terminalPhone, Integer.toHexString(message.getMessageId()), message.getFlowId());
+ } else {
+ log.error("消息发送失败, 终端: {}, 消息ID: 0x{}, 流水号: {}", terminalPhone, Integer.toHexString(message.getMessageId()), message.getFlowId(), future.cause());
+ }
+ });
+ } else {
+ unregister(channel);
+ // 终端不在线或通道已关闭
+ log.warn("终端不在线: {}", terminalPhone);
+ }
+ }
+
+ /**
+ * 创建基础消息对象
+ */
+ public static JT808Message createBaseMessage(String terminalId, int messageId) {
+ return createBaseMessage(terminalId, messageId, null);
+ }
+
+ public static JT808Message createBaseMessage(String terminalId, int messageId, byte[] body) {
+ JT808Message message = new JT808Message()
+ .setFlowId(FlowId.next(terminalId, messageId));
+
+ // 设置消息ID
+ message.setMessageId(messageId);
+
+ if (body != null) {
+ message.setMessageBodyProps(body.length);
+ } else {
+ message.setMessageBodyProps(0);
+ }
+
+ // 设置终端手机号
+ message.setTerminalPhone(terminalId);
+
+ // 设置消息体
+ message.setMessageBody(body);
+
+ return message;
+ }
+
+
+ public static JT808Message parseMessage(ByteBuf buf) {
+ JT808Message jt808Message = new JT808Message();
+
+ try {
+ // 1. 消息ID (2字节)
+ jt808Message.setMessageId(buf.readUnsignedShort());
+
+ // 2. 消息体属性 (2字节)
+ int messageBodyProps = buf.readUnsignedShort();
+ jt808Message.setMessageBodyProps(messageBodyProps);
+
+ // 3. 终端手机号 (6字节, BCD编码)
+ byte[] phoneBytes = new byte[6];
+ buf.readBytes(phoneBytes);
+ String terminalPhone = BCD.bcdToStr(phoneBytes);
+ jt808Message.setTerminalPhone(terminalPhone);
+
+ // 4. 消息流水号 (2字节)
+ jt808Message.setFlowId(buf.readUnsignedShort());
+
+ // 5. 消息包封装项 (可选)
+ if (jt808Message.isPackaged()) {
+ JT808Message.PackageInfo packageInfo = new JT808Message.PackageInfo();
+ packageInfo.setTotalPackages(buf.readUnsignedShort());
+ packageInfo.setCurrentPackageNo(buf.readUnsignedShort());
+ jt808Message.setPackageInfo(packageInfo);
+ }
+
+ // 6. 消息体
+ int bodyLength = jt808Message.getMessageBodyLength();
+ if (buf.readableBytes() >= bodyLength + 1) { // 消息体 + 校验码
+ ByteBuf bodyBuf = buf.readSlice(bodyLength);
+ byte[] array = ByteBufUtil.getBytes(bodyBuf);
+ jt808Message.setMessageBody(array);
+
+ // 7. 校验码 (1字节)
+ jt808Message.setCheckCode(buf.readByte());
+
+ // 验证校验码
+ if (!verifyCheckCode(jt808Message, buf)) {
+ log.error("校验码错误: {}", jt808Message);
+ return null;
+ }
+
+ return jt808Message;
+ }
+ } catch (Exception e) {
+ log.error("消息解析错误", e);
+ jt808Message = null;
+ }
+ return jt808Message;
+ }
+
+ private static boolean verifyCheckCode(JT808Message message, ByteBuf originalBuf) {
+ // 简化实现,实际项目中需要根据协议规范计算校验码
+ // 校验码 = 消息头 + 消息体 中所有字节的异或
+ return true;
+ }
+
+
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/JT808Message.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/JT808Message.java
new file mode 100644
index 0000000..8d977a3
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/JT808Message.java
@@ -0,0 +1,75 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import cn.hutool.core.util.ReflectUtil;
+import com.njzscloud.common.core.fastjson.Fastjson;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * JT808协议消息实体类
+ * 用于存储解析后的JT808协议消息内容
+ */
+@Getter
+@Setter
+@Slf4j
+@Accessors(chain = true)
+public class JT808Message {
+ // 消息ID (2字节)
+ private int messageId;
+ // 消息体属性 (2字节)
+ private int messageBodyProps;
+ // 终端手机号 (6字节, BCD编码)
+ private String terminalPhone;
+ // 消息流水号 (2字节)
+ private int flowId;
+ // 消息包封装项 (可选, 当消息体属性中分包标志为1时存在)
+ private PackageInfo packageInfo;
+ // 消息体
+ // private ByteBuf messageBody;
+ private byte[] messageBody;
+ // 校验码 (1字节)
+ private byte checkCode;
+ public JT808Message() {
+ }
+
+ // 从消息体属性中获取消息体长度
+ public int getMessageBodyLength() {
+ return messageBodyProps & 0x3FF;
+ }
+
+ // 从消息体属性中获取是否分包
+ public boolean isPackaged() {
+ return (messageBodyProps & 0x4000) != 0;
+ }
+
+ // 从消息体属性中获取加密方式
+ public int getEncryptionType() {
+ return (messageBodyProps >> 10) & 0x07;
+ }
+
+ @Override
+ public String toString() {
+ return Fastjson.toJsonStr(this);
+ }
+
+ public T parseMessageBody(Class clazz) {
+ if (messageBody == null || messageBody.length == 0) {
+ return null;
+ }
+ return ReflectUtil.newInstance(clazz, (Object) this.messageBody);
+ }
+
+ // 消息包封装项内部类
+ @Setter
+ @Getter
+ @Accessors(chain = true)
+ public static class PackageInfo {
+ // 消息总包数 (2字节)
+ private int totalPackages;
+ // 当前包序号 (2字节)
+ private int currentPackageNo;
+ }
+
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerGeneralResponseMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerGeneralResponseMessage.java
new file mode 100644
index 0000000..0bafb3c
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerGeneralResponseMessage.java
@@ -0,0 +1,35 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+@AllArgsConstructor
+public class ServerGeneralResponseMessage implements ServerMessageBody {
+ private int messageId;
+ private int flowId;
+ private int result;
+
+
+ @Override
+ public byte[] toBytes() {
+ ByteBuf body = Unpooled.buffer();
+ try {
+ body.writeShort(flowId);
+ body.writeShort(messageId);
+ body.writeByte(result);
+ return ByteBufUtil.getBytes(body);
+ } finally {
+ body.release();
+ }
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerMessageBody.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerMessageBody.java
new file mode 100644
index 0000000..b15302d
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerMessageBody.java
@@ -0,0 +1,5 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+public interface ServerMessageBody {
+ byte[] toBytes();
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerRegisterResponseMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerRegisterResponseMessage.java
new file mode 100644
index 0000000..5a9706a
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerRegisterResponseMessage.java
@@ -0,0 +1,35 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+@AllArgsConstructor
+public class ServerRegisterResponseMessage implements ServerMessageBody {
+ private String terminalId;
+ private int flowId;
+ private int result;
+ private String authCode;
+
+ @Override
+ public byte[] toBytes() {
+ ByteBuf body = Unpooled.buffer();
+ try {
+ body.writeShort(flowId);
+ body.writeByte(result);
+ ByteBufUtil.writeUtf8(body, authCode);
+ return ByteBufUtil.getBytes(body);
+ } finally {
+ body.release();
+ }
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerTxtMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerTxtMessage.java
new file mode 100644
index 0000000..d59a35a
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerTxtMessage.java
@@ -0,0 +1,36 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import cn.hutool.core.util.CharsetUtil;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.UnsupportedEncodingException;
+
+@Getter
+@Setter
+@ToString
+@Slf4j
+@Accessors(chain = true)
+public class ServerTxtMessage implements ServerMessageBody {
+ private byte flag;
+ private String txt;
+
+ public ServerTxtMessage(String txt) {
+ this.flag = 0x01;
+ this.txt = txt;
+ }
+
+ public byte[] toBytes() {
+ byte[] messageBody = new byte[txt.length() + 1];
+ messageBody[0] = flag;
+ try {
+ System.arraycopy(txt.getBytes(CharsetUtil.GBK), 0, messageBody, 1, txt.length());
+ } catch (UnsupportedEncodingException e) {
+ log.error("文本消息构建失败,原文本:{}", txt, e);
+ }
+ return messageBody;
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalAuthMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalAuthMessage.java
new file mode 100644
index 0000000..0edb861
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalAuthMessage.java
@@ -0,0 +1,25 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import cn.hutool.core.util.CharsetUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+public class TerminalAuthMessage {
+ private String authCode;
+
+ public TerminalAuthMessage(byte[] bytes) {
+ ByteBuf body = Unpooled.wrappedBuffer(bytes);
+ byte[] authCodeBytes = new byte[body.readableBytes()];
+ body.readBytes(authCodeBytes);
+ this.authCode = new String(authCodeBytes, CharsetUtil.CHARSET_GBK);
+ body.release();
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalGeneralResponseMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalGeneralResponseMessage.java
new file mode 100644
index 0000000..5d024d0
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalGeneralResponseMessage.java
@@ -0,0 +1,48 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+public class TerminalGeneralResponseMessage {
+ private int flowId;
+ private int messageId;
+ /**
+ * 0:成功/确认;1:失败;2:消息有误;3:不支持
+ */
+ private int result;
+
+ public TerminalGeneralResponseMessage(byte[] bytes) {
+ ByteBuf body = Unpooled.wrappedBuffer(bytes);
+ this.flowId = body.readUnsignedShort();
+ this.messageId = body.readUnsignedShort();
+ this.result = body.readByte();
+ body.release();
+ }
+
+ public boolean isSuccess() {
+ return result == 0;
+ }
+
+ public String getMsg() {
+ switch (this.result) {
+ case 0:
+ return "成功";
+ case 1:
+ return "失败";
+ case 2:
+ return "消息有误";
+ case 3:
+ return "不支持";
+ default:
+ return "未知";
+ }
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalMessageBody.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalMessageBody.java
new file mode 100644
index 0000000..b14ecec
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalMessageBody.java
@@ -0,0 +1,16 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+public class TerminalMessageBody {
+ private String terminalId;
+ private ByteBuf body;
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalRegisterMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalRegisterMessage.java
new file mode 100644
index 0000000..b167301
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalRegisterMessage.java
@@ -0,0 +1,88 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import cn.hutool.core.util.CharsetUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+public class TerminalRegisterMessage {
+ /**
+ * 标示终端安装车辆所在的省域,0 保留,由平台取默认值。省域
+ * ID 采用 GB/T 2260 中规定的行政区划代码六位中前两位
+ */
+ private int province;
+ /**
+ * 标示终端安装车辆所在的市域,0 保留,由平台取默认值。市县
+ * 域 ID 采用 GB/T 2260 中规定的行政区划代码六位中后四位
+ */
+ private int city;
+ /**
+ * 五个字节,终端制造商编号
+ */
+ private String manufacturerId;
+ /**
+ * 八个字节,此终端型号由制造商自行定义,位数不足八位的,补
+ * 空格,(注:补充说明中要求为 20 字节,不足后补 0x00)
+ */
+ private String terminalModel;
+ /**
+ * 七个字节,有大写字母和数字组成,此终端 ID 由制造商自行定义,
+ * 位数不足后面补 0x00
+ */
+ private String terminalId;
+ /**
+ * 车牌颜色,按照 JT/T 415—2006 中 5.4.12 的规定,未上牌时,取
+ * 值为 0
+ */
+ private byte plateColor;
+ /**
+ * 公安交通管理部门颁发的机动车号牌
+ * (注:补充说明中要求如车牌颜色为 0 时,这里表示车辆 VIN 号)
+ */
+ private String plate;
+
+ public TerminalRegisterMessage(byte[] bytes) {
+ ByteBuf body = Unpooled.wrappedBuffer(bytes);
+ // 读取省域ID(WORD,2字节,大端序)
+ this.province = body.readUnsignedShort();
+
+ // 读取市县域ID(WORD,2字节,大端序)
+ this.city = body.readUnsignedShort();
+
+ // 读取制造商ID(BYTE[5],5字节,转为字符串)
+ byte[] manufacturerIdBytes = new byte[5];
+ body.readBytes(manufacturerIdBytes);
+ this.manufacturerId = new String(manufacturerIdBytes).trim();
+
+ // 读取终端型号(BYTE[8],8字节,转为字符串)
+ byte[] terminalModelBytes = new byte[8];
+ body.readBytes(terminalModelBytes);
+ this.terminalModel = new String(terminalModelBytes).trim();
+
+ // 读取终端ID(BYTE[7],7字节,转为字符串)
+ byte[] terminalIdBytes = new byte[7];
+ body.readBytes(terminalIdBytes);
+ this.terminalId = new String(terminalIdBytes).trim();
+
+ // 读取车牌颜色(BYTE,1字节)
+ this.plateColor = body.readByte();
+
+ // 读取车牌(STRING,剩余字节,转为字符串)
+ // 假设剩余所有字节都是车牌内容,实际可根据协议确定长度
+ byte[] plateBytes = new byte[body.readableBytes()];
+ body.readBytes(plateBytes);
+ this.plate = new String(plateBytes, CharsetUtil.CHARSET_GBK);
+
+ body.release();
+ }
+
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalTxtMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalTxtMessage.java
new file mode 100644
index 0000000..d498cc7
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalTxtMessage.java
@@ -0,0 +1,28 @@
+package com.njzscloud.common.localizer.jt808.message;
+
+import cn.hutool.core.util.CharsetUtil;
+import io.netty.buffer.ByteBuf;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+@Getter
+@Setter
+@ToString
+@Accessors(chain = true)
+public class TerminalTxtMessage {
+ private byte type;
+ /**
+ * 文本信息
+ */
+ private String txt;
+
+ public TerminalTxtMessage(ByteBuf body) {
+ this.type = body.readByte();
+ this.txt = body.toString(body.readerIndex(), body.readableBytes(),
+ this.type == 0x00 ?
+ CharsetUtil.CHARSET_GBK :
+ CharsetUtil.CHARSET_UTF_8);
+ }
+}
diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Decoder.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Decoder.java
new file mode 100644
index 0000000..f2ecf8c
--- /dev/null
+++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Decoder.java
@@ -0,0 +1,108 @@
+package com.njzscloud.common.localizer.jt808.support;
+
+import com.njzscloud.common.localizer.jt808.JT808;
+import com.njzscloud.common.localizer.jt808.message.JT808Message;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.List;
+
+/**
+ * JT808协议解码器
+ * 用于解析JT808协议格式的字节流为消息对象
+ */
+@Slf4j
+public class JT808Decoder extends ByteToMessageDecoder {
+ // 消息起始符和结束符
+ private static final byte MSG_DELIMITER = 0x7e;
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List