From dbc31ab6e603a09686c23c8ea384397c6a7ebd62 Mon Sep 17 00:00:00 2001 From: lzq Date: Sun, 28 Sep 2025 19:12:29 +0800 Subject: [PATCH] GPS --- .../com/njzscloud/common/core/utils/BCD.java | 12 + .../common/core/utils/Scheduler.java | 41 ++ .../njzscloud-common-localizer/pom.xml | 33 + .../config/LocalizerAutoConfiguration.java | 23 + .../config/LocalizerInitializer.java | 19 + .../localizer/config/LocalizerProperties.java | 19 + .../common/localizer/jt808/JT808.java | 214 ++++++ .../localizer/jt808/message/JT808Message.java | 75 ++ .../message/ServerGeneralResponseMessage.java | 35 + .../jt808/message/ServerMessageBody.java | 5 + .../ServerRegisterResponseMessage.java | 35 + .../jt808/message/ServerTxtMessage.java | 36 + .../jt808/message/TerminalAuthMessage.java | 25 + .../TerminalGeneralResponseMessage.java | 48 ++ .../jt808/message/TerminalMessageBody.java | 16 + .../message/TerminalRegisterMessage.java | 88 +++ .../jt808/message/TerminalTxtMessage.java | 28 + .../localizer/jt808/support/JT808Decoder.java | 108 +++ .../localizer/jt808/support/JT808Encoder.java | 148 ++++ .../jt808/support/JT808MessageHandler.java | 52 ++ .../jt808/support/JT808MessageResolver.java | 136 ++++ .../localizer/jt808/support/TcpServer.java | 87 +++ .../common/localizer/jt808/util/FlowId.java | 19 + .../localizer/mqtt/MqttMsgHandlers.java | 61 ++ .../localizer/mqtt/param/EnableWarnParam.java | 15 + .../mqtt/param/LocationCurrentParam.java | 14 + .../mqtt/param/LocationTrackParam.java | 15 + .../mqtt/param/ObtainDeviceInfoParam.java | 14 + .../mqtt/param/SetHeartbeatParam.java | 16 + .../mqtt/param/SpeedThresholdParam.java | 15 + .../mqtt/result/RealtimeLocationResult.java | 42 ++ .../common/localizer/tuqiang/Tuqiang.java | 92 +++ .../tuqiang/listener/TuqiangListeners.java | 103 +++ .../message/LocationReportMessage.java | 65 ++ .../message/SearchDeviceInfoMessage.java | 268 +++++++ .../SearchLocationResponseMessage.java | 23 + .../tuqiang/support/TuqiangListener.java | 10 + .../main/resources/META-INF/spring.factories | 2 + .../mqtt/config/MqttAutoConfiguration.java | 6 +- .../{Mqtt.java => MqttCliWrapper.java} | 30 +- .../{MqttHandler.java => MqttListener.java} | 2 +- .../mqtt/util/{MqttUtil.java => Mqtt.java} | 24 +- njzscloud-common/pom.xml | 1 + njzscloud-svr/pom.xml | 34 + .../TruckLocationTrackController.java | 81 +++ .../biz/mapper/TruckLocationTrackMapper.java | 13 + .../pojo/entity/TruckLocationTrackEntity.java | 87 +++ .../service/TruckLocationTrackService.java | 169 +++++ .../controller/DeviceLocalizerController.java | 68 ++ .../drive/barrier/generic/GenericBarrier.java | 8 +- .../voicebox/generic/GenericVoicebox.java | 4 +- .../device/mapper/DeviceLocalizerMapper.java | 13 + .../pojo/entity/DeviceLocalizerEntity.java | 72 ++ .../supervisory/device/pojo/result/.gitkeep | 0 .../device/pojo/result/DeviceInfoResult.java | 111 +++ .../device/pojo/result/HeartbeatResult.java | 16 + .../service/DeviceLocalizerService.java | 95 +++ .../order/controller/OrderInfoController.java | 7 + .../order/mapper/OrderInfoMapper.java | 3 + .../pojo/result/RealtimeLocationResult.java | 42 ++ .../order/service/OrderInfoService.java | 145 +++- .../sys/user/service/UserService.java | 5 +- .../src/main/resources/application-dev.yml | 14 + .../src/main/resources/application.yml | 2 +- .../mapper/order/OrderInfoMapper.xml | 5 + pom.xml | 5 + z-doc/db-model/njzscloud.pdma.json | 679 +++++++++++++++++- {njzscloud-svr => z-doc}/微信支付配置说明.md | 0 68 files changed, 3752 insertions(+), 46 deletions(-) create mode 100644 njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/BCD.java create mode 100644 njzscloud-common/njzscloud-common-core/src/main/java/com/njzscloud/common/core/utils/Scheduler.java create mode 100644 njzscloud-common/njzscloud-common-localizer/pom.xml create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerAutoConfiguration.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerInitializer.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/config/LocalizerProperties.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/JT808.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/JT808Message.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerGeneralResponseMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerMessageBody.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerRegisterResponseMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/ServerTxtMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalAuthMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalGeneralResponseMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalMessageBody.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalRegisterMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/message/TerminalTxtMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Decoder.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Encoder.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageHandler.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageResolver.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/TcpServer.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/util/FlowId.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/MqttMsgHandlers.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/EnableWarnParam.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationCurrentParam.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationTrackParam.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/ObtainDeviceInfoParam.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SetHeartbeatParam.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SpeedThresholdParam.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/result/RealtimeLocationResult.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/Tuqiang.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/listener/TuqiangListeners.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/LocationReportMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchDeviceInfoMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchLocationResponseMessage.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/support/TuqiangListener.java create mode 100644 njzscloud-common/njzscloud-common-localizer/src/main/resources/META-INF/spring.factories rename njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/{Mqtt.java => MqttCliWrapper.java} (79%) rename njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/{MqttHandler.java => MqttListener.java} (90%) rename njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/util/{MqttUtil.java => Mqtt.java} (50%) create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/controller/TruckLocationTrackController.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/mapper/TruckLocationTrackMapper.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/pojo/entity/TruckLocationTrackEntity.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/service/TruckLocationTrackService.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/controller/DeviceLocalizerController.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/mapper/DeviceLocalizerMapper.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/entity/DeviceLocalizerEntity.java delete mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/.gitkeep create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/DeviceInfoResult.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/HeartbeatResult.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/service/DeviceLocalizerService.java create mode 100644 njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/RealtimeLocationResult.java rename {njzscloud-svr => z-doc}/微信支付配置说明.md (100%) 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 out) throws Exception { + // 确保至少有2个字节(起始符+至少一个字节) + if (in.readableBytes() < 2) { + return; + } + + // 寻找消息起始符 + int startIndex = -1; + while (in.readableBytes() > 0) { + if (in.readByte() == MSG_DELIMITER) { + startIndex = in.readerIndex() - 1; + break; + } + } + + if (startIndex == -1) { + // 未找到起始符,清空缓冲区 + in.clear(); + return; + } + + // 寻找消息结束符 + int endIndex = -1; + for (int i = in.readerIndex(); i < in.writerIndex(); i++) { + if (in.getByte(i) == MSG_DELIMITER) { + endIndex = i; + break; + } + } + + if (endIndex == -1) { + // 未找到结束符,等待更多数据 + in.readerIndex(startIndex); + return; + } + + // 读取完整消息(不包含起始符和结束符) + int messageLength = endIndex - startIndex - 1; + ByteBuf messageBuf = in.slice(startIndex + 1, messageLength); + + // 处理转义字符 + ByteBuf unescapedBuf = handleEscape(messageBuf); + + // 解析消息内容 + JT808Message message = JT808.parseMessage(unescapedBuf); + unescapedBuf.release(); + if (message != null) { + out.add(message); + } + + // 移动读指针到结束符之后 + in.readerIndex(endIndex + 1); + } + + /** + * 处理转义字符 + * JT808协议规定:0x7e <-> 0x7d 0x01,0x7d <-> 0x7d 0x02 + */ + private ByteBuf handleEscape(ByteBuf buf) { + ByteBuf out = buf.alloc().buffer(buf.readableBytes()); + + while (buf.readableBytes() > 0) { + byte b = buf.readByte(); + if (b == 0x7d) { + if (buf.readableBytes() > 0) { + byte next = buf.readByte(); + if (next == 0x01) { + out.writeByte(0x7e); + } else if (next == 0x02) { + out.writeByte(0x7d); + } else { + // 无效的转义序列,直接写入 + out.writeByte(b); + out.writeByte(next); + } + } else { + out.writeByte(b); + } + } else { + out.writeByte(b); + } + } + + out.resetReaderIndex(); + return out; + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Encoder.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Encoder.java new file mode 100644 index 0000000..aef9fe7 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808Encoder.java @@ -0,0 +1,148 @@ +package com.njzscloud.common.localizer.jt808.support; + +import com.njzscloud.common.localizer.jt808.message.JT808Message; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +/** + * JT808协议编码器 + * 将JT808Message对象编码为符合协议规范的字节流 + */ +public class JT808Encoder extends MessageToByteEncoder { + // 消息起始符和结束符 + private static final byte MSG_DELIMITER = 0x7e; + + @Override + protected void encode(ChannelHandlerContext ctx, JT808Message msg, ByteBuf out) throws Exception { + // 1. 创建缓冲区存储消息内容(不含起始符和结束符) + ByteBuf contentBuf = ctx.alloc().buffer(); + + try { + // 2. 写入消息ID + contentBuf.writeShort(msg.getMessageId()); + + // 3. 写入消息体属性 + contentBuf.writeShort(msg.getMessageBodyProps()); + + // 4. 写入终端手机号(BCD编码) + byte[] phoneBytes = stringToBcd(msg.getTerminalPhone(), 6); + contentBuf.writeBytes(phoneBytes); + + // 5. 写入消息流水号 + contentBuf.writeShort(msg.getFlowId()); + + // 6. 写入消息包封装项(如果需要) + if (msg.isPackaged() && msg.getPackageInfo() != null) { + contentBuf.writeShort(msg.getPackageInfo().getTotalPackages()); + contentBuf.writeShort(msg.getPackageInfo().getCurrentPackageNo()); + } + + // 7. 写入消息体 + byte[] messageBody = msg.getMessageBody(); + if (messageBody != null && messageBody.length > 0) { + contentBuf.writeBytes(messageBody); + } + + // 8. 计算并写入校验码 + byte checkCode = calculateCheckCode(contentBuf); + contentBuf.writeByte(checkCode); + msg.setCheckCode(checkCode); + + // 9. 处理转义 + ByteBuf escapedBuf = handleEscape(contentBuf); + + // 10. 写入起始符 + out.writeByte(MSG_DELIMITER); + + // 11. 写入转义后的内容 + out.writeBytes(escapedBuf); + + // 12. 写入结束符 + out.writeByte(MSG_DELIMITER); + escapedBuf.release(); + } finally { + // 释放缓冲区 + if (contentBuf.refCnt() > 0) { + contentBuf.release(); + } + } + } + + /** + * 处理转义字符 + */ + private ByteBuf handleEscape(ByteBuf buf) { + ByteBuf out = buf.alloc().buffer(buf.readableBytes() * 2); // 预留足够空间 + + buf.resetReaderIndex(); + while (buf.readableBytes() > 0) { + byte b = buf.readByte(); + if (b == MSG_DELIMITER) { + out.writeByte(0x7d); + out.writeByte(0x01); + } else if (b == 0x7d) { + out.writeByte(0x7d); + out.writeByte(0x02); + } else { + out.writeByte(b); + } + } + + return out; + } + + /** + * 计算校验码 + * 校验码 = 消息头 + 消息体 中所有字节的异或 + */ + private byte calculateCheckCode(ByteBuf buf) { + byte checkCode = 0; + int readerIndex = buf.readerIndex(); + + while (buf.readableBytes() > 0) { + checkCode ^= buf.readByte(); + } + + buf.readerIndex(readerIndex); + return checkCode; + } + + /** + * 字符串转BCD码 + */ + private byte[] stringToBcd(String str, int length) { + if (str == null || str.length() == 0) { + return new byte[length]; + } + + // 补0至偶数长度 + int strLen = str.length(); + if (strLen % 2 != 0) { + str = "0" + str; + } + + // 确保不超过指定长度 + if (str.length() > length * 2) { + str = str.substring(0, length * 2); + } + + byte[] bcd = new byte[length]; + int index = 0; + + for (int i = 0; i < str.length(); i += 2) { + if (index >= length) { + break; + } + + // 高4位 + byte high = (byte) (Character.digit(str.charAt(i), 16) << 4); + // 低4位 + byte low = (byte) Character.digit(str.charAt(i + 1), 16); + + bcd[index++] = (byte) (high | low); + } + + return bcd; + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageHandler.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageHandler.java new file mode 100644 index 0000000..d0feb73 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageHandler.java @@ -0,0 +1,52 @@ +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.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.util.concurrent.GlobalEventExecutor; +import lombok.extern.slf4j.Slf4j; + +/** + * JT808消息处理器,处理解码后的消息并发送响应 + */ +@Slf4j +public class JT808MessageHandler extends ChannelInboundHandlerAdapter { + // 管理所有连接的客户端 + private static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + log.info("客户端连接: {}", ctx.channel().remoteAddress()); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof JT808Message) { + JT808Message message = (JT808Message) msg; + int messageId = message.getMessageId(); + log.info("收到消息, 设备号: {},消息ID: 0x{}, 流水号: {}", message.getTerminalPhone(), Integer.toHexString(messageId), message.getFlowId()); + if (messageId == 0x0100) { + JT808.register(message.getTerminalPhone(), ctx.channel()); + } + JT808MessageResolver.dispatchMsg(messageId, message); + } else { + super.channelRead(ctx, msg); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + log.info("客户端断开连接: {}", ctx.channel().remoteAddress()); + clients.remove(ctx.channel()); + JT808.unregister(ctx.channel()); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + log.error("发生异常: {}", cause.getMessage(), cause); + ctx.close(); + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageResolver.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageResolver.java new file mode 100644 index 0000000..d1efa29 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/JT808MessageResolver.java @@ -0,0 +1,136 @@ +package com.njzscloud.common.localizer.jt808.support; + +import cn.hutool.core.map.MapUtil; +import com.njzscloud.common.localizer.jt808.JT808; +import com.njzscloud.common.localizer.jt808.message.*; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.extern.slf4j.Slf4j; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +@Slf4j +public class JT808MessageResolver { + public static final String Heartbeat = "Heartbeat"; + public static final String LocationReport = "LocationReport"; + public static final String LocationBatchReport = "LocationBatchReport"; + public static final String LocationSearch = "LocationSearch"; + public static final String TxtReport = "TxtReport"; + + private static Map> resolvers = new ConcurrentHashMap<>(); + + private static Map> stdResolvers = MapUtil.>builder() + .put(0x0100, JT808MessageResolver::onTerminalRegister) + .put(0x0102, JT808MessageResolver::onTerminalAuth) + .put(0x0002, JT808MessageResolver::onHeartbeat) + .put(0x0200, JT808MessageResolver::onLocationReport) + .put(0x704, JT808MessageResolver::onLocationBatchUpload) + .put(0x0201, JT808MessageResolver::onSearchLocationResponse) + .put(0x0001, JT808MessageResolver::onTerminalGeneralResponse) + .put(0x6006, JT808MessageResolver::onTerminalTxtReport) + .build(); + + public static void addResolver(String messageId, Consumer resolver) { + JT808MessageResolver.resolvers.put(messageId, resolver); + } + + public static void dispatchMsg(String messageId, JT808Message message) { + Consumer heartbeat = resolvers.get(messageId); + if (heartbeat != null) { + String terminalPhone = message.getTerminalPhone(); + byte[] messageBody = message.getMessageBody(); + ByteBuf byteBuf = Unpooled.wrappedBuffer(messageBody); + try { + heartbeat.accept(new TerminalMessageBody() + .setTerminalId(terminalPhone) + .setBody(byteBuf)); + } catch (Exception e) { + log.error("处理消息时发生异常,消息 Id:{},设备号:{} ", messageId, terminalPhone, e); + } finally { + byteBuf.release(); + } + } + } + + public static void dispatchMsg(Integer messageId, JT808Message message) { + Consumer stdResolver = stdResolvers.get(messageId); + if (stdResolver != null) { + stdResolver.accept(message); + } + } + + /** + * 终端注册消息 + */ + public static void onTerminalRegister(JT808Message message) { + String terminalPhone = message.getTerminalPhone(); + int flowId = message.getFlowId(); + ServerRegisterResponseMessage serverRegisterResponseMessage = new ServerRegisterResponseMessage(terminalPhone, flowId, 0, terminalPhone); + JT808.sendMessage(JT808.createBaseMessage(terminalPhone, 0x8100, serverRegisterResponseMessage.toBytes())); + + TerminalRegisterMessage terminalRegisterMessage = message.parseMessageBody(TerminalRegisterMessage.class); + + log.info("终端注册消息: {}", terminalRegisterMessage); + } + + /** + * 终端鉴权消息 + */ + public static void onTerminalAuth(JT808Message message) { + JT808.sendGeneralResponse(message); + TerminalAuthMessage authMsg = message.parseMessageBody(TerminalAuthMessage.class); + log.info("终端鉴权消息: {}", authMsg); + } + + /** + * 终端通用应答消息 + */ + public static void onTerminalGeneralResponse(JT808Message message) { + TerminalGeneralResponseMessage terminalGeneralResponseMessage = message.parseMessageBody(TerminalGeneralResponseMessage.class); + int messageId = message.getMessageId(); + String terminalPhone = message.getTerminalPhone(); + int result = terminalGeneralResponseMessage.getResult(); + log.info("设备通用响应,设备号:{},消息 Id:{},结果:{}", terminalPhone, Integer.toHexString(messageId), result); + } + + /** + * 终端心跳消息 + */ + public static void onHeartbeat(JT808Message message) { + JT808.sendGeneralResponse(message); + dispatchMsg(Heartbeat, message); + } + + /** + * 终端位置信息汇报消息 + */ + public static void onLocationReport(JT808Message message) { + dispatchMsg(LocationReport, message); + } + + /** + * 定位数据批量上传消息 + */ + public static void onLocationBatchUpload(JT808Message message) { + dispatchMsg(LocationBatchReport, message); + } + + /** + * 查询位置响应消息 + */ + public static void onSearchLocationResponse(JT808Message message) { + JT808.sendGeneralResponse(message); + dispatchMsg(LocationSearch, message); + } + + /** + * 终端文本信息汇报消息 + */ + public static void onTerminalTxtReport(JT808Message message) { + JT808.sendGeneralResponse(message); + dispatchMsg(TxtReport, message); + } + +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/TcpServer.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/TcpServer.java new file mode 100644 index 0000000..06c1ecb --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/support/TcpServer.java @@ -0,0 +1,87 @@ +package com.njzscloud.common.localizer.jt808.support; + +import com.njzscloud.common.core.ex.Exceptions; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +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 lombok.extern.slf4j.Slf4j; + +@Slf4j +public class TcpServer { + private final int port; + private EventLoopGroup bossGroup; + private EventLoopGroup workerGroup; + private ServerBootstrap bootstrap; + private Thread startThread; + ; + + public TcpServer(int port, int bossThreads, int workerThreads) { + if (port <= 0 || bossThreads <= 0 || workerThreads <= 0) { + throw Exceptions.error("port、bossThreads和workerThreads必须大于0"); + } + this.port = port; + buildServer(bossThreads, workerThreads); + } + + + public void buildServer(int bossThreads, int workerThreads) { + try { + // 创建两个EventLoopGroup,bossGroup用于接收客户端连接,workerGroup用于处理连接后的IO操作 + bossGroup = new NioEventLoopGroup(bossThreads); + workerGroup = new NioEventLoopGroup(workerThreads); + // 服务器启动辅助类 + bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + // 指定使用NIO的服务器通道 + .channel(NioServerSocketChannel.class) + // 设置服务器通道的选项 + .option(ChannelOption.SO_BACKLOG, 128) + // 设置客户端通道的选项 + .childOption(ChannelOption.SO_KEEPALIVE, true) + // 设置通道初始化器,用于配置新接入的通道 + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + // 添加JT808协议处理器链 + ch.pipeline() + .addLast(new JT808Decoder()) // 解码器 + .addLast(new JT808Encoder()) // 编码器 + .addLast(new JT808MessageHandler()); // 消息处理器 + } + }); + } catch (Exception e) { + log.error("服务器启动失败", e); + this.stop(); + throw Exceptions.error(e, "服务器构建失败"); + } + } + + public void start() { + try { + ChannelFuture future = bootstrap.bind(port).sync(); + log.info("服务器已启动,监听端口: {}", port); + future.channel().closeFuture().addListener(it -> stop()); + } catch (Exception e) { + log.error("服务器启动失败,端口: {}", port, e); + stop(); + throw Exceptions.error(e, "服务器启动失败"); + } + } + + public void stop() { + if (bossGroup != null) { + bossGroup.shutdownGracefully(); + bossGroup = null; + } + if (workerGroup != null) { + workerGroup.shutdownGracefully(); + workerGroup = null; + } + log.info("服务器已停止"); + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/util/FlowId.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/util/FlowId.java new file mode 100644 index 0000000..bbb81d2 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/jt808/util/FlowId.java @@ -0,0 +1,19 @@ +package com.njzscloud.common.localizer.jt808.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class FlowId { + private static final Map flowId = new ConcurrentHashMap<>(); + + public static synchronized int next(String terminalId, int messageId) { + String key = terminalId + "_" + messageId; + Integer i = flowId.get(key); + if (i == null || i >= 0xFFFF) { + i = 0; + } + i++; + flowId.put(key, i); + return i; + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/MqttMsgHandlers.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/MqttMsgHandlers.java new file mode 100644 index 0000000..5dde2aa --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/MqttMsgHandlers.java @@ -0,0 +1,61 @@ +package com.njzscloud.common.localizer.mqtt; + +import com.njzscloud.common.localizer.mqtt.param.*; +import com.njzscloud.common.localizer.tuqiang.Tuqiang; +import com.njzscloud.common.mqtt.support.MqttListener; +import com.njzscloud.common.mqtt.support.MqttMsg; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class MqttMsgHandlers { + + /** + * 开启追踪 + */ + @MqttListener(topic = "location/track") + public void onMessage(MqttMsg msg) { + LocationTrackParam locationTrackParam = msg.getMsg(LocationTrackParam.class); + Tuqiang.track(locationTrackParam.getTerminalId(), locationTrackParam.getInterval()); + } + + /** + * 获取当前位置 + */ + @MqttListener(topic = "location/current") + public void currentLocation(MqttMsg msg) { + LocationCurrentParam locationCurrentParam = msg.getMsg(LocationCurrentParam.class); + Tuqiang.currentLocation(locationCurrentParam.getTerminalId()); + } + + /** + * 启用报警 + */ + @MqttListener(topic = "location/warn") + public void enableWarn(MqttMsg msg) { + EnableWarnParam enableWarnParam = msg.getMsg(EnableWarnParam.class); + String terminalId = enableWarnParam.getTerminalId(); + boolean enable = enableWarnParam.isEnable(); + Tuqiang.enableWarn(terminalId, enable); + } + + /** + * 设置速度阈值 + */ + @MqttListener(topic = "location/speed_threshold") + public void speedThreshold(MqttMsg msg) { + SpeedThresholdParam speedThresholdParam = msg.getMsg(SpeedThresholdParam.class); + String terminalId = speedThresholdParam.getTerminalId(); + int speed = speedThresholdParam.getSpeed(); + Tuqiang.speedThreshold(terminalId, speed); + } + + /** + * 获取设备信息 + */ + @MqttListener(topic = "location/device_info") + public void obtainDeviceInfo(MqttMsg msg) { + ObtainDeviceInfoParam obtainDeviceInfoParam = msg.getMsg(ObtainDeviceInfoParam.class); + String terminalId = obtainDeviceInfoParam.getTerminalId(); + Tuqiang.obtainDeviceInfo(terminalId); + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/EnableWarnParam.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/EnableWarnParam.java new file mode 100644 index 0000000..59c8a6a --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/EnableWarnParam.java @@ -0,0 +1,15 @@ +package com.njzscloud.common.localizer.mqtt.param; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class EnableWarnParam { + private String terminalId; + private boolean enable; +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationCurrentParam.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationCurrentParam.java new file mode 100644 index 0000000..cadf46a --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationCurrentParam.java @@ -0,0 +1,14 @@ +package com.njzscloud.common.localizer.mqtt.param; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class LocationCurrentParam { + private String terminalId; +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationTrackParam.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationTrackParam.java new file mode 100644 index 0000000..31f2ba6 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/LocationTrackParam.java @@ -0,0 +1,15 @@ +package com.njzscloud.common.localizer.mqtt.param; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class LocationTrackParam { + private String terminalId; + private int interval; +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/ObtainDeviceInfoParam.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/ObtainDeviceInfoParam.java new file mode 100644 index 0000000..eb5156f --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/ObtainDeviceInfoParam.java @@ -0,0 +1,14 @@ +package com.njzscloud.common.localizer.mqtt.param; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class ObtainDeviceInfoParam { + private String terminalId; +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SetHeartbeatParam.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SetHeartbeatParam.java new file mode 100644 index 0000000..3fb1b8a --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SetHeartbeatParam.java @@ -0,0 +1,16 @@ +package com.njzscloud.common.localizer.mqtt.param; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class SetHeartbeatParam { + private String terminalId; + + private int interval; +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SpeedThresholdParam.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SpeedThresholdParam.java new file mode 100644 index 0000000..32d6360 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/param/SpeedThresholdParam.java @@ -0,0 +1,15 @@ +package com.njzscloud.common.localizer.mqtt.param; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class SpeedThresholdParam { + private String terminalId; + private int speed; +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/result/RealtimeLocationResult.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/result/RealtimeLocationResult.java new file mode 100644 index 0000000..d74bd1c --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/mqtt/result/RealtimeLocationResult.java @@ -0,0 +1,42 @@ +package com.njzscloud.common.localizer.mqtt.result; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class RealtimeLocationResult { + private String terminalId; + private int type; + + /** + * 1 1:超速报警 标志维持至报警条件解除 + * 7 1:终端主电源欠压 标志维持至报警条件解除 + * 8 1:声控录音报警 + */ + private long alarmFlag; + /** + * 0 0:ACC 关 1:ACC 开 + * 1 0:未定位 1:定位 + * 2 0:北纬 1:南纬 + * 3 0:东经 1:西经 + */ + private long status; + private double longitude; + private double latitude; + private int altitude; + private double speed; + private int direction; + private String time; + private boolean overspeed; + private boolean powerUnderVoltage; + private boolean soundAlarm; + private boolean accOn; + private boolean position; + private boolean south; + private boolean west; +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/Tuqiang.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/Tuqiang.java new file mode 100644 index 0000000..f861b35 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/Tuqiang.java @@ -0,0 +1,92 @@ +package com.njzscloud.common.localizer.tuqiang; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; +import com.njzscloud.common.localizer.jt808.JT808; +import com.njzscloud.common.localizer.jt808.message.JT808Message; +import com.njzscloud.common.localizer.tuqiang.listener.TuqiangListeners; +import com.njzscloud.common.localizer.tuqiang.support.TuqiangListener; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.lang.reflect.Method; + +@Slf4j +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class Tuqiang { + public static void run(int port, int bossThreads, int workerThreads) { + addListener(new TuqiangListeners()); + JT808.run(port, bossThreads, workerThreads); + } + + public static void stop() { + JT808.stop(); + } + + private static void addListener(Object object) { + Method[] methods = object.getClass().getDeclaredMethods(); + for (Method method : methods) { + if (method.isAnnotationPresent(TuqiangListener.class)) { + TuqiangListener tuqiangListener = method.getAnnotation(TuqiangListener.class); + String messageId = tuqiangListener.messageId(); + JT808.addListener(messageId, (msg) -> { + try { + method.invoke(object, msg); + } catch (Exception e) { + log.error("途强处理消息 {} 时出错", messageId, e); + } + }); + } + } + } + + /** + * 开启追踪 + * + * @param terminalId 终端ID + * @param interval 上报间隔,单位秒,0-3600秒,0表示不追踪 + */ + public static void track(String terminalId, int interval) { + Assert.isTrue(interval >= 0 && interval <= 3600, "上报间隔必须为0-3600秒"); + JT808.sendTxtMessage(terminalId, StrUtil.format("", interval)); + } + + /** + * 获取设备信息 + * + * @param terminalId 终端ID + */ + public static void obtainDeviceInfo(String terminalId) { + JT808.sendTxtMessage(terminalId, ""); + } + + /** + * 启用报警 + * + * @param terminalId 终端ID + */ + public static void enableWarn(String terminalId, boolean enable) { + JT808.sendTxtMessage(terminalId, StrUtil.format("", enable ? 1 : 0)); + } + + /** + * 设置速度报警 + * + * @param terminalId 终端ID + * @param speed 速度,单位km/h + */ + public static void speedThreshold(String terminalId, int speed) { + JT808.sendTxtMessage(terminalId, StrUtil.format("", speed)); + } + + /** + * 获取当前位置 + * + * @param terminalId 终端ID + */ + public static void currentLocation(String terminalId) { + JT808Message baseMessage = JT808.createBaseMessage(terminalId, 0x8201); + JT808.sendMessage(baseMessage); + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/listener/TuqiangListeners.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/listener/TuqiangListeners.java new file mode 100644 index 0000000..c1693b5 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/listener/TuqiangListeners.java @@ -0,0 +1,103 @@ +package com.njzscloud.common.localizer.tuqiang.listener; + +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.map.MapUtil; +import com.njzscloud.common.localizer.jt808.message.TerminalMessageBody; +import com.njzscloud.common.localizer.jt808.message.TerminalTxtMessage; +import com.njzscloud.common.localizer.mqtt.result.RealtimeLocationResult; +import com.njzscloud.common.localizer.tuqiang.message.LocationReportMessage; +import com.njzscloud.common.localizer.tuqiang.message.SearchDeviceInfoMessage; +import com.njzscloud.common.localizer.tuqiang.message.SearchLocationResponseMessage; +import com.njzscloud.common.localizer.tuqiang.support.TuqiangListener; +import com.njzscloud.common.mqtt.util.Mqtt; +import io.netty.buffer.ByteBuf; +import lombok.extern.slf4j.Slf4j; + +import static com.njzscloud.common.localizer.jt808.support.JT808MessageResolver.*; + +@Slf4j +public class TuqiangListeners { + /** + * 终端心跳消息 + */ + @TuqiangListener(messageId = Heartbeat) + public void onHeartbeat(TerminalMessageBody message) { + String terminalId = message.getTerminalId(); + Mqtt.publish(terminalId + "/online", MapUtil.builder() + .put("online", true) + .put("terminalId", terminalId) + .put("time", DateUtil.now()) + .build()); + } + + /** + * 终端位置信息汇报消息 + */ + @TuqiangListener(messageId = LocationReport) + public void onLocationReport(TerminalMessageBody message) { + ByteBuf byteBuf = message.getBody(); + LocationReportMessage locationReportMsg = new LocationReportMessage(byteBuf); + log.info("终端位置信息汇报消息: {}", locationReportMsg); + + String terminalId = message.getTerminalId(); + RealtimeLocationResult realtimeLocationResult = BeanUtil.copyProperties(locationReportMsg, RealtimeLocationResult.class) + .setTerminalId(terminalId); + Mqtt.publish(terminalId + "/track_location", realtimeLocationResult); + + } + + /** + * 定位数据批量上传消息 + */ + @TuqiangListener(messageId = LocationBatchReport) + public void onLocationBatchUpload(TerminalMessageBody message) { + String terminalId = message.getTerminalId(); + ByteBuf body = message.getBody(); + int count = body.readUnsignedShort(); + int type = body.readByte(); + + for (int i = 0; i < count; i++) { + int length = body.readUnsignedShort(); + LocationReportMessage locationReportMsg = new LocationReportMessage(body.slice(body.readerIndex(), length)); + body.skipBytes(length); + RealtimeLocationResult realtimeLocationResult = BeanUtil.copyProperties(locationReportMsg, RealtimeLocationResult.class) + .setTerminalId(terminalId) + .setType(type); + Mqtt.publish(terminalId + "/track_location", realtimeLocationResult); + } + } + + /** + * 查询位置响应消息 + */ + @TuqiangListener(messageId = LocationSearch) + public void onSearchLocationResponse(TerminalMessageBody message) { + String terminalId = message.getTerminalId(); + ByteBuf body = message.getBody(); + SearchLocationResponseMessage searchLocationResponseMsg = new SearchLocationResponseMessage(body); + log.info("查询位置响应消息: {}", searchLocationResponseMsg); + + LocationReportMessage locationReportMsg = searchLocationResponseMsg.getLocationReportMessage(); + RealtimeLocationResult realtimeLocationResult = BeanUtil.copyProperties(locationReportMsg, RealtimeLocationResult.class) + .setTerminalId(terminalId); + Mqtt.publish(terminalId + "/current_location", realtimeLocationResult); + } + + /** + * 终端文本信息汇报消息 + */ + @TuqiangListener(messageId = TxtReport) + public void onTerminalTxtReport(TerminalMessageBody message) { + TerminalTxtMessage terminalTxtReportMsg = new TerminalTxtMessage(message.getBody()); + log.info("终端文本信息汇报消息: {}", terminalTxtReportMsg); + String txt = terminalTxtReportMsg.getTxt(); + SearchDeviceInfoMessage deviceInfo = SearchDeviceInfoMessage.parse(txt); + if (deviceInfo != null) { + String terminalId = message.getTerminalId(); + deviceInfo.setTerminalId(terminalId); + + Mqtt.publish(terminalId + "/device_info", deviceInfo); + } + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/LocationReportMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/LocationReportMessage.java new file mode 100644 index 0000000..c2e599f --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/LocationReportMessage.java @@ -0,0 +1,65 @@ +package com.njzscloud.common.localizer.tuqiang.message; + +import cn.hutool.core.date.DateUtil; +import com.njzscloud.common.core.utils.BCD; +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 LocationReportMessage { + /** + * 1 1:超速报警 标志维持至报警条件解除 + * 7 1:终端主电源欠压 标志维持至报警条件解除 + * 8 1:声控录音报警 + */ + private long alarmFlag; + /** + * 0 0:ACC 关 1:ACC 开 + * 1 0:未定位 1:定位 + * 2 0:北纬 1:南纬 + * 3 0:东经 1:西经 + */ + private long status; + private double longitude; + private double latitude; + private int altitude; + private double speed; + private int direction; + private String time; + + private boolean overspeed; + private boolean powerUnderVoltage; + private boolean soundAlarm; + private boolean accOn; + private boolean position; + private boolean south; + private boolean west; + + public LocationReportMessage(ByteBuf body) { + this.alarmFlag = body.readUnsignedInt(); + this.status = body.readUnsignedInt(); + double latitude = body.readUnsignedInt() * 1.0 / 1000000; + this.latitude = isWest() ? -latitude : latitude; + double longitude = body.readUnsignedInt() * 1.0 / 1000000; + this.longitude = isSouth() ? -longitude : longitude; + this.altitude = body.readUnsignedShort(); + this.speed = body.readUnsignedShort() * 1.0 / 10; + this.direction = body.readUnsignedShort(); + byte[] terminalIdBytes = new byte[6]; + body.readBytes(terminalIdBytes); + this.time = DateUtil.format(DateUtil.parse(BCD.bcdToStr(terminalIdBytes), "yyMMddHHmmss"), "yyyy-MM-dd HH:mm:ss"); + this.overspeed = (alarmFlag & 0x02) == 0x02; + this.powerUnderVoltage = (alarmFlag & 0x40) == 0x40; + this.soundAlarm = (alarmFlag & 0x80) == 0x80; + this.accOn = (status & 0x01) == 0x01; + this.position = (status & 0x02) == 0x02; + this.south = (status & 0x04) == 0x04; + this.west = (status & 0x08) == 0x08; + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchDeviceInfoMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchDeviceInfoMessage.java new file mode 100644 index 0000000..29e0e2c --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchDeviceInfoMessage.java @@ -0,0 +1,268 @@ +package com.njzscloud.common.localizer.tuqiang.message; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class SearchDeviceInfoMessage { + private String terminalId; + // region GPS定位状态 + /** + * 这是 GPS 定位的核心状态,三个参数分别代表: + * 1. 定位有效性:V = Valid(有效定位),若为N则表示 No Valid(无效定位,如无卫星信号); + * 2. 卫星数量:0 = 当前参与定位的卫星数量(此处为 0,可能是瞬时值或信号弱导致,正常定位时通常≥4); + * 3. 定位精度 / 备用参数:0 = 部分设备此处代表定位精度(单位:米),或备用状态码(具体需结合设备型号,途强部分设备此参数为 “定位类型”,0 = 未知,1=GPS,2 = 北斗)。 + */ + private String gpsValid; // 定位有效性(V/N) + private int satelliteCount; // 卫星数量 + private int gpsAccuracy; // 定位精度或备用参数 + /** + * GPS 模块硬件状态:OK = GPS 模块正常工作(供电、硬件无故障);若为ERR则表示 GPS 模块故障(如硬件损坏、供电异常)。 + */ + private String gpsModuleStatus; // GPS模块状态(OK/ERR) + // endregion + + // region 网络与信号状态 + private String serverIp; // 服务器IP + private int serverPort; // 服务器端口 + /** + * 2G/4G 网络注册状态(CGREG 是 GSM/UMTS 网络的 “小区注册状态” 指令): + * 1 = 设备已成功注册到本地移动网络(可正常联网); + * 其他常见值:0= 未注册,2= 正在注册,3= 注册被拒绝(如 SIM 卡欠费、无信号) + */ + private int cgregStatus; // 网络注册状态 + /** + * 网络信号强度(CSQ = Carrier Signal Quality,载波信号质量): + * 取值范围0-31,22属于良好信号(12-20 为中等,21-31 为良好,0 为无信号); + * 对应信号强度约为 -78dBm(CSQ 与 dBm 换算公式:dBm = -113 + (CSQ×2),22 时即 - 113+44=-69dBm,数值越接近 0 信号越强)。 + */ + private int csq; // 信号强度 + // endregion + + // region 硬件与电源状态 + /** + * 设备内置电池电压:单位为 “V(伏特)”,3.5V 属于正常电压范围(途强设备电池电压通常在 3.3V-4.2V 之间,低于 3.3V 可能触发低电报警)。 + */ + private double batteryVoltage; // 电池电压(V) + /** + * 供电模式 / 电池状态: + * 常见含义(途强设备自定义):1= 外接电源供电(如车充、市电),2= 电池供电,0= 供电异常; + * 部分设备此参数代表 “电池健康度”,2= 良好(需结合具体型号确认)。 + */ + private int powerMode; // 供电模式(1=外接电源,2=电池) + /** + * 北斗定位模块状态(BD = BeiDou): + * 0 = 北斗模块未启用 / 未定位(若设备支持北斗,此值为1表示北斗已参与定位);0不代表故障,仅表示当前未使用北斗信号。 + */ + private int beidouStatus; // 北斗模块状态(0=未启用,1=启用) + // endregion + + // region 功能与附加状态 + /** + * 报警状态:* = 无当前报警(途强设备常用 “*” 表示无报警,若有报警会显示具体报警码,如A:1= 低电报警,A:2= 位移报警) + */ + private String alarmStatus; // 报警状态(*) + /** + * 计数器 / 里程相关:常见为 “累计里程计数状态” 或 “指令执行计数”,0通常表示 “无异常” 或 “初始值”(部分设备此为 “充电次数”,需结合型号)。 + */ + private int counter; // 计数器/里程相关 + /** + * 定位上报间隔:单位为 “秒”,300 = 设备每 5 分钟自动上报一次定位数据(途强设备支持远程配置此间隔,如 10 秒、60 秒、300 秒等) + */ + private int reportInterval; // 定位上报间隔(秒) + /** + * 超速阈值 + */ + private int speedThreshold; // 超速阈值(km/h) + /** + * 关闭日志 + */ + private int logOff; // 关闭日志(0=关闭,1=开启) + /** + * 关闭拐点 + */ + private int inflectionPointOff; // 拐点关闭状态(0=关闭,1=开启) + private int heartbeatInterval; // 心跳包间隔(秒) + /** + * 关闭基站 + */ + private int baseStationOff; // 基站关闭状态(1=关闭,0=开启) + private int chargingStatus; // 充电状态(0=未充电,1=充电中) + private int bluetoothStatus; // 蓝牙状态(1=开启,0=关闭) + private int enableWarn; // 蓝牙状态(1=开启,0=关闭) + private String extendStatus; // 扩展状态码(十六进制) + private String temperature; // 温度传感器状态 + private String deviceTime; // 设备本地时间 + private int agpsStatus; // AGPS状态(1=开启,0=关闭) + private String displacement; // 位移距离 + private int speedStatus; // 速度状态(0=静止) + private String deviceId; // 设备ID/IMEI后7位 + private int lowBatteryThreshold; // 低电阈值状态 + private int accelerometerStatus; // 加速度传感器状态 + private int flightMode; // 飞行模式(0=关闭,1=开启) + // endregion + + public static SearchDeviceInfoMessage parse(String str) { + SearchDeviceInfoMessage data = new SearchDeviceInfoMessage(); + if (str == null || str.isEmpty()) { + return null; + } + if (!str.startsWith("<") || !str.endsWith(">")) { + return null; + } + + // 去除首尾的<和> + String content = str.replaceAll("[<>]", ""); + // 按*分割字段 + String[] fields = content.split("\\*"); + + for (String field : fields) { + if (field.contains(":")) { + String[] keyValue = field.split(":", 2); + if (keyValue.length == 2) { + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + parseField(data, key, value); + } + } + } + + return data; + } + + private static void parseField(SearchDeviceInfoMessage data, String key, String value) { + switch (key) { + case "GPS": + parseGpsField(data, value); + break; + case "GP": + data.gpsModuleStatus = value; + break; + case "T": + parseServerField(data, value); + break; + case "CGREG": + data.cgregStatus = parseInt(value); + break; + case "CSQ": + data.csq = parseInt(value); + break; + case "5Y": + data.batteryVoltage = parseDouble(value); + break; + case "B": + data.powerMode = parseInt(value); + break; + case "BD": + data.beidouStatus = parseInt(value); + break; + case "A": + data.alarmStatus = value; + break; + case "C": + data.counter = parseInt(value); + break; + case "O": + data.reportInterval = parseInt(value); + break; + case "CS": + data.speedThreshold = parseInt(value); + break; + case "3U": + data.logOff = parseInt(value); + break; + case "3Z": + data.inflectionPointOff = parseInt(value); + break; + case "H": + data.heartbeatInterval = parseInt(value); + break; + case "2A": + data.baseStationOff = parseInt(value); + break; + case "5C": + data.chargingStatus = parseInt(value); + break; + case "60": + data.bluetoothStatus = parseInt(value); + break; + case "6W": + data.enableWarn = parseInt(value); + break; + case "3E": + data.extendStatus = value; + break; + case "5T": + data.temperature = value; + break; + case "9E": + data.deviceTime = value; + break; + case "1H": + data.agpsStatus = parseInt(value); + break; + case "3D": + data.displacement = value; + break; + case "2S": + data.speedStatus = parseInt(value); + break; + case "I": + data.deviceId = value; + break; + case "LT": + data.lowBatteryThreshold = parseInt(value); + break; + case "AG": + data.accelerometerStatus = parseInt(value); + break; + case "FLY": + data.flightMode = parseInt(value); + break; + // 忽略未知字段 + default: + break; + } + } + + private static void parseGpsField(SearchDeviceInfoMessage data, String value) { + // 格式: (V,0,0) + String cleaned = value.replaceAll("[()]", ""); + String[] parts = cleaned.split(","); + if (parts.length >= 3) { + data.gpsValid = parts[0].trim(); + data.satelliteCount = parseInt(parts[1].trim()); + data.gpsAccuracy = parseInt(parts[2].trim()); + } + } + + private static void parseServerField(SearchDeviceInfoMessage data, String value) { + // 格式: 139.224.54.144,30100 + String[] parts = value.split(","); + if (parts.length >= 2) { + data.serverIp = parts[0].trim(); + data.serverPort = parseInt(parts[1].trim()); + } + } + + private static int parseInt(String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return -1; // 用-1表示解析失败 + } + } + + private static double parseDouble(String value) { + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + return -1; // 用-1表示解析失败 + } + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchLocationResponseMessage.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchLocationResponseMessage.java new file mode 100644 index 0000000..ee87977 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/message/SearchLocationResponseMessage.java @@ -0,0 +1,23 @@ +package com.njzscloud.common.localizer.tuqiang.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 SearchLocationResponseMessage { + // 消息流水号 (2字节) + private int flowId; + private LocationReportMessage locationReportMessage; + + public SearchLocationResponseMessage(ByteBuf body) { + this.flowId = body.readUnsignedShort(); + ByteBuf locationReportMsgBody = body.slice(body.readerIndex(), body.readableBytes()); + this.locationReportMessage = new LocationReportMessage(locationReportMsgBody); + } +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/support/TuqiangListener.java b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/support/TuqiangListener.java new file mode 100644 index 0000000..cdf52a5 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/java/com/njzscloud/common/localizer/tuqiang/support/TuqiangListener.java @@ -0,0 +1,10 @@ +package com.njzscloud.common.localizer.tuqiang.support; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface TuqiangListener { + String messageId(); +} diff --git a/njzscloud-common/njzscloud-common-localizer/src/main/resources/META-INF/spring.factories b/njzscloud-common/njzscloud-common-localizer/src/main/resources/META-INF/spring.factories new file mode 100644 index 0000000..1b84de3 --- /dev/null +++ b/njzscloud-common/njzscloud-common-localizer/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration = \ + com.njzscloud.common.localizer.config.LocalizerAutoConfiguration diff --git a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/config/MqttAutoConfiguration.java b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/config/MqttAutoConfiguration.java index cfd6aaa..3b9046f 100644 --- a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/config/MqttAutoConfiguration.java +++ b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/config/MqttAutoConfiguration.java @@ -1,6 +1,6 @@ package com.njzscloud.common.mqtt.config; -import com.njzscloud.common.mqtt.support.Mqtt; +import com.njzscloud.common.mqtt.support.MqttCliWrapper; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -12,7 +12,7 @@ import org.springframework.context.annotation.Configuration; public class MqttAutoConfiguration { @Bean(destroyMethod = "shutdown") - public Mqtt mqtt(MqttProperties mqttProperties) { - return new Mqtt(mqttProperties); + public MqttCliWrapper mqtt(MqttProperties mqttProperties) { + return new MqttCliWrapper(mqttProperties); } } diff --git a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/Mqtt.java b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttCliWrapper.java similarity index 79% rename from njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/Mqtt.java rename to njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttCliWrapper.java index 9e0d1a8..9e5096a 100644 --- a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/Mqtt.java +++ b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttCliWrapper.java @@ -5,6 +5,7 @@ import com.njzscloud.common.core.jackson.Jackson; import com.njzscloud.common.mqtt.config.MqttProperties; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.*; +import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; @@ -14,18 +15,22 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; @Slf4j -public class Mqtt implements BeanPostProcessor { +public class MqttCliWrapper implements BeanPostProcessor { private static final Map> fn = new ConcurrentHashMap<>(); private MqttClient client; - public Mqtt(MqttProperties mqttProperties) { + public MqttCliWrapper(MqttProperties mqttProperties) { String broker = mqttProperties.getBroker(); String clientId = mqttProperties.getClientId(); String username = mqttProperties.getUsername(); String password = mqttProperties.getPassword(); try { - client = new MqttClient(broker, clientId); + MqttDefaultFilePersistence persistence = new MqttDefaultFilePersistence("logs/mqtt"); + client = new MqttClient(broker, clientId, persistence); MqttConnectOptions options = new MqttConnectOptions(); + options.setAutomaticReconnect(true); + options.setConnectionTimeout(5000); + options.setMaxReconnectDelay(30000); if (StrUtil.isNotBlank(username) && StrUtil.isNotBlank(password)) { options.setUserName(username); options.setPassword(password.toCharArray()); @@ -52,6 +57,7 @@ public class Mqtt implements BeanPostProcessor { public void subscribe(String topic, int qos) { try { client.subscribe(topic, qos); + log.info("mqtt 订阅消息:{} {}", topic, qos); } catch (Exception e) { log.error("mqtt 订阅失败:{} {}", topic, qos, e); } @@ -91,10 +97,10 @@ public class Mqtt implements BeanPostProcessor { public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class clazz = bean.getClass(); for (Method method : clazz.getDeclaredMethods()) { - MqttHandler mqttHandler = method.getAnnotation(MqttHandler.class); - if (mqttHandler == null) continue; - String topic = mqttHandler.topic(); - int qos = mqttHandler.qos(); + MqttListener mqttListener = method.getAnnotation(MqttListener.class); + if (mqttListener == null) continue; + String topic = mqttListener.topic(); + int qos = mqttListener.qos(); this.subscribe(topic, qos); Consumer handler = (msg) -> { try { @@ -108,6 +114,14 @@ public class Mqtt implements BeanPostProcessor { return bean; } + public void unsubscribe(String topic) { + try { + client.unsubscribe(topic); + } catch (Exception e) { + log.error("mqtt 取消订阅失败:{}", topic, e); + } + } + public static class MsgHandler implements MqttCallback { public void messageArrived(String topic, MqttMessage message) throws Exception { Consumer handler = fn.get(topic); @@ -124,7 +138,7 @@ public class Mqtt implements BeanPostProcessor { } public void deliveryComplete(IMqttDeliveryToken token) { - log.info("消息投递结果:{}", token.isComplete()); + // log.info("消息投递结果:{}", token.isComplete()); } } } diff --git a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttHandler.java b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttListener.java similarity index 90% rename from njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttHandler.java rename to njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttListener.java index 763cea7..1a2c58a 100644 --- a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttHandler.java +++ b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/support/MqttListener.java @@ -7,7 +7,7 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) -public @interface MqttHandler { +public @interface MqttListener { String topic(); int qos() default 0; diff --git a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/util/MqttUtil.java b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/util/Mqtt.java similarity index 50% rename from njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/util/MqttUtil.java rename to njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/util/Mqtt.java index 17222de..1d3a1c6 100644 --- a/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/util/MqttUtil.java +++ b/njzscloud-common/njzscloud-common-mqtt/src/main/java/com/njzscloud/common/mqtt/util/Mqtt.java @@ -1,40 +1,44 @@ package com.njzscloud.common.mqtt.util; import cn.hutool.extra.spring.SpringUtil; -import com.njzscloud.common.mqtt.support.Mqtt; +import com.njzscloud.common.mqtt.support.MqttCliWrapper; import com.njzscloud.common.mqtt.support.MqttMsg; import java.util.function.Consumer; -public final class MqttUtil { - private static final Mqtt mqtt; +public final class Mqtt { + private static final MqttCliWrapper MQTT_CLI_WRAPPER; static { - mqtt = SpringUtil.getBean(Mqtt.class); + MQTT_CLI_WRAPPER = SpringUtil.getBean(MqttCliWrapper.class); } public static void subscribe(String topic, int qos, Consumer handler) { - mqtt.subscribe(topic, qos, handler); + MQTT_CLI_WRAPPER.subscribe(topic, qos, handler); } public static void subscribe(String topic, Consumer handler) { - mqtt.subscribe(topic, handler); + MQTT_CLI_WRAPPER.subscribe(topic, handler); } public static void subscribe(String topic, int qos) { - mqtt.subscribe(topic, qos); + MQTT_CLI_WRAPPER.subscribe(topic, qos); } public static void subscribe(String topic) { - mqtt.subscribe(topic); + MQTT_CLI_WRAPPER.subscribe(topic); } public static void publish(String topic, Object msg) { - mqtt.publish(topic, msg); + MQTT_CLI_WRAPPER.publish(topic, msg); } public static void publish(String topic, int qos, Object msg) { - mqtt.publish(topic, qos, msg); + MQTT_CLI_WRAPPER.publish(topic, qos, msg); + } + + public static void unsubscribe(String topic) { + MQTT_CLI_WRAPPER.unsubscribe(topic); } } diff --git a/njzscloud-common/pom.xml b/njzscloud-common/pom.xml index a778414..777e53e 100644 --- a/njzscloud-common/pom.xml +++ b/njzscloud-common/pom.xml @@ -29,6 +29,7 @@ njzscloud-common-mqtt njzscloud-common-http njzscloud-common-wechat + njzscloud-common-localizer diff --git a/njzscloud-svr/pom.xml b/njzscloud-svr/pom.xml index 8489537..8e82f16 100644 --- a/njzscloud-svr/pom.xml +++ b/njzscloud-svr/pom.xml @@ -53,6 +53,10 @@ com.njzscloud njzscloud-common-security + + com.njzscloud + njzscloud-common-localizer + com.njzscloud njzscloud-common-gen @@ -120,6 +124,36 @@ + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + post-package-actions + package + + run + + + + + + + + + + + + + + + + + + + + diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/controller/TruckLocationTrackController.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/controller/TruckLocationTrackController.java new file mode 100644 index 0000000..58cd550 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/controller/TruckLocationTrackController.java @@ -0,0 +1,81 @@ +package com.njzscloud.supervisory.biz.controller; + +import com.njzscloud.common.core.utils.R; +import com.njzscloud.common.mp.support.PageParam; +import com.njzscloud.common.mp.support.PageResult; +import com.njzscloud.supervisory.biz.pojo.entity.TruckLocationTrackEntity; +import com.njzscloud.supervisory.biz.service.TruckLocationTrackService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 车辆定位数据 + */ +@Slf4j +@RestController +@RequestMapping("/truck_location_track") +@RequiredArgsConstructor +public class TruckLocationTrackController { + + private final TruckLocationTrackService truckLocationTrackService; + + /** + * 新增 + */ + @PostMapping("/add") + public R add(@RequestBody TruckLocationTrackEntity truckLocationTrackEntity) { + truckLocationTrackService.add(truckLocationTrackEntity); + return R.success(); + } + + /** + * 修改 + */ + @PostMapping("/modify") + public R modify(@RequestBody TruckLocationTrackEntity truckLocationTrackEntity) { + truckLocationTrackService.modify(truckLocationTrackEntity); + return R.success(); + } + + /** + * 删除 + */ + @PostMapping("/del") + public R del(@RequestBody List ids) { + truckLocationTrackService.del(ids); + return R.success(); + } + + /** + * 详情 + */ + @GetMapping("/detail") + public R detail(@RequestParam Long id) { + return R.success(truckLocationTrackService.detail(id)); + } + + /** + * 分页查询 + */ + @GetMapping("/paging") + public R> paging(PageParam pageParam, TruckLocationTrackEntity truckLocationTrackEntity) { + return R.success(truckLocationTrackService.paging(pageParam, truckLocationTrackEntity)); + } + + /** + * 历史记录 + */ + @GetMapping("/history") + public SseEmitter history(@RequestParam("orderId") Long orderId, + @RequestParam("startTime") LocalDateTime startTime, + @RequestParam("endTime") LocalDateTime endTime) { + return truckLocationTrackService.history(orderId, startTime, endTime); + } + + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/mapper/TruckLocationTrackMapper.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/mapper/TruckLocationTrackMapper.java new file mode 100644 index 0000000..099060e --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/mapper/TruckLocationTrackMapper.java @@ -0,0 +1,13 @@ +package com.njzscloud.supervisory.biz.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njzscloud.supervisory.biz.pojo.entity.TruckLocationTrackEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * 车辆定位数据 + */ +@Mapper +public interface TruckLocationTrackMapper extends BaseMapper { + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/pojo/entity/TruckLocationTrackEntity.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/pojo/entity/TruckLocationTrackEntity.java new file mode 100644 index 0000000..04f4e5e --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/pojo/entity/TruckLocationTrackEntity.java @@ -0,0 +1,87 @@ +package com.njzscloud.supervisory.biz.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 车辆定位数据 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@TableName("truck_location_track") +public class TruckLocationTrackEntity { + + /** + * Id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + /** + * 订单 Id + */ + private Long orderId; + + + /** + * 车辆 Id + */ + private Long truckId; + /** + * 设备号 + */ + private String terminalId; + + /** + * 经度; 单位:度 + */ + private Double longitude; + + /** + * 纬度; 单位:度 + */ + private Double latitude; + + /** + * 海拔; 米 + */ + private Integer altitude; + + /** + * 速度; 千米/小时 + */ + private Double speed; + + /** + * 定位时间 + */ + private LocalDateTime locationTime; + + /** + * 方向; 正北为 0 度 + */ + private Integer direction; + + /** + * 是否超速; 0-->否、1-->是 + */ + private Boolean overspeed; + + /** + * 是否为补偿数据; 0-->否、1-->是 + */ + private Boolean compensate; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/service/TruckLocationTrackService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/service/TruckLocationTrackService.java new file mode 100644 index 0000000..8e1beb3 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/biz/service/TruckLocationTrackService.java @@ -0,0 +1,169 @@ +package com.njzscloud.supervisory.biz.service; + +import cn.hutool.core.thread.ThreadUtil; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njzscloud.common.mp.support.PageParam; +import com.njzscloud.common.mp.support.PageResult; +import com.njzscloud.supervisory.biz.mapper.TruckLocationTrackMapper; +import com.njzscloud.supervisory.biz.pojo.entity.TruckLocationTrackEntity; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 车辆定位数据 + */ +@Slf4j +@Service +public class TruckLocationTrackService extends ServiceImpl implements IService { + + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(200, 200, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1)); + + public static void send(SseEmitter emitter, TruckLocationTrackEntity data) { + try { + emitter.send(SseEmitter.event() + .id(String.valueOf(System.currentTimeMillis())) + .name("historyData") + .data(data)); + } catch (IOException e) { + log.error("发送定位器历史记录失败", e); + } + } + + /** + * 新增 + */ + public void add(TruckLocationTrackEntity truckLocationTrackEntity) { + this.save(truckLocationTrackEntity); + } + + /** + * 修改 + */ + public void modify(TruckLocationTrackEntity truckLocationTrackEntity) { + this.updateById(truckLocationTrackEntity); + } + + /** + * 删除 + */ + @Transactional(rollbackFor = Exception.class) + public void del(List ids) { + this.removeBatchByIds(ids); + } + + /** + * 详情 + */ + public TruckLocationTrackEntity detail(Long id) { + return this.getById(id); + } + + /** + * 分页查询 + */ + public PageResult paging(PageParam pageParam, TruckLocationTrackEntity truckLocationTrackEntity) { + return PageResult.of(this.page(pageParam.toPage(), Wrappers.query(truckLocationTrackEntity))); + } + + public SseEmitter history(Long orderId, LocalDateTime startTime, LocalDateTime endTime) { + SseEmitter emitter = new SseEmitter(0L); + + // 提交异步任务 + CompletableFuture future = CompletableFuture.runAsync(() -> { + try { + // 记录上一条数据的时间,用于计算间隔 + LocalDateTime lastRecordTime = null; + int currentPage = 1; + // 分页查询,每次500条 + while (true) { + // 检查是否被取消 + if (Thread.currentThread().isInterrupted()) { + log.info("任务被取消"); + emitter.complete(); + return; + } + + Page page = new Page<>(currentPage, 500); + page.addOrder(OrderItem.asc("location_time")); + + // 分页查询当前页数据 + IPage resultPage = this.page(page, Wrappers.lambdaQuery(TruckLocationTrackEntity.class) + .eq(TruckLocationTrackEntity::getOrderId, orderId) + .between(TruckLocationTrackEntity::getLocationTime, startTime, endTime)); + + List records = resultPage.getRecords(); + if (records.isEmpty()) { + // 没有更多数据,推送完成信息 + emitter.complete(); + break; + } + + // 处理当前页数据,按时间间隔推送 + for (TruckLocationTrackEntity record : records) { + if (Thread.currentThread().isInterrupted()) { + log.info("任务被取消"); + emitter.complete(); + return; + } + + LocalDateTime currentTime = record.getLocationTime(); + // 计算与上一条数据的时间间隔 + if (lastRecordTime != null && currentTime != null) { + long interval = Duration.between(lastRecordTime, currentTime).toMillis(); + if (interval > 0) { + // 按实际间隔等待 + ThreadUtil.sleep(interval); + } + } + + // 推送当前数据 + send(emitter, record); + + // 更新上一条数据的时间 + lastRecordTime = currentTime; + } + + // 判断是否为最后一页 + if (currentPage >= resultPage.getPages()) { + emitter.complete(); + break; + } + + // 继续查询下一页 + currentPage++; + } + + // 所有数据推送完成,关闭连接 + emitter.complete(); + + } catch (Exception e) { + // 其他异常 + emitter.completeWithError(e); + } + }, threadPoolExecutor); + + // 注册连接关闭回调,中断任务 + emitter.onCompletion(() -> future.cancel(true)); + emitter.onTimeout(() -> future.cancel(true)); + emitter.onError((e) -> future.cancel(true)); + + return emitter; + } + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/controller/DeviceLocalizerController.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/controller/DeviceLocalizerController.java new file mode 100644 index 0000000..83b08c9 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/controller/DeviceLocalizerController.java @@ -0,0 +1,68 @@ +package com.njzscloud.supervisory.device.controller; + +import com.njzscloud.common.core.utils.R; +import com.njzscloud.common.mp.support.PageParam; +import com.njzscloud.common.mp.support.PageResult; +import com.njzscloud.supervisory.device.pojo.entity.DeviceLocalizerEntity; +import com.njzscloud.supervisory.device.service.DeviceLocalizerService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 定位器 + */ +@Slf4j +@RestController +@RequestMapping("/device_localizer") +@RequiredArgsConstructor +public class DeviceLocalizerController { + + private final DeviceLocalizerService deviceLocalizerService; + + /** + * 新增 + */ + @PostMapping("/add") + public R add(@RequestBody DeviceLocalizerEntity deviceLocalizerEntity) { + deviceLocalizerService.add(deviceLocalizerEntity); + return R.success(); + } + + /** + * 修改 + */ + @PostMapping("/modify") + public R modify(@RequestBody DeviceLocalizerEntity deviceLocalizerEntity) { + deviceLocalizerService.modify(deviceLocalizerEntity); + return R.success(); + } + + /** + * 删除 + */ + @PostMapping("/del") + public R del(@RequestBody List ids) { + deviceLocalizerService.del(ids); + return R.success(); + } + + /** + * 详情 + */ + @GetMapping("/detail") + public R detail(@RequestParam Long id) { + return R.success(deviceLocalizerService.detail(id)); + } + + /** + * 分页查询 + */ + @GetMapping("/paging") + public R> paging(PageParam pageParam, DeviceLocalizerEntity deviceLocalizerEntity) { + return R.success(deviceLocalizerService.paging(pageParam, deviceLocalizerEntity)); + } + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/barrier/generic/GenericBarrier.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/barrier/generic/GenericBarrier.java index b459bb2..a1263b2 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/barrier/generic/GenericBarrier.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/barrier/generic/GenericBarrier.java @@ -2,7 +2,7 @@ package com.njzscloud.supervisory.device.drive.barrier.generic; import cn.hutool.core.util.StrUtil; import com.njzscloud.common.mqtt.support.MqttMsg; -import com.njzscloud.common.mqtt.util.MqttUtil; +import com.njzscloud.common.mqtt.util.Mqtt; import com.njzscloud.supervisory.device.drive.barrier.Barrier; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,7 +28,7 @@ public class GenericBarrier extends Barrier { .getBody() .setIo(config.getIo()) .setValue(1); - MqttUtil.publish(topic(Fn.gpio_out), ioParam); + Mqtt.publish(topic(Fn.gpio_out), ioParam); } private void openReply(MqttMsg msg) { @@ -44,7 +44,7 @@ public class GenericBarrier extends Barrier { .getBody() .setIo(config.getIo()) .setValue(0); - MqttUtil.publish(topic(Fn.gpio_out), ioParam); + Mqtt.publish(topic(Fn.gpio_out), ioParam); } @Override @@ -55,7 +55,7 @@ public class GenericBarrier extends Barrier { .getBody() .setIo(config.getIo()) .setValue(2); - MqttUtil.publish(topic(Fn.gpio_out), ioParam); + Mqtt.publish(topic(Fn.gpio_out), ioParam); } private String topic(Fn fn) { diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/voicebox/generic/GenericVoicebox.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/voicebox/generic/GenericVoicebox.java index 291a935..8827c16 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/voicebox/generic/GenericVoicebox.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/drive/voicebox/generic/GenericVoicebox.java @@ -1,7 +1,7 @@ package com.njzscloud.supervisory.device.drive.voicebox.generic; import cn.hutool.core.map.MapUtil; -import com.njzscloud.common.mqtt.util.MqttUtil; +import com.njzscloud.common.mqtt.util.Mqtt; import com.njzscloud.supervisory.device.drive.voicebox.Voicebox; public class GenericVoicebox extends Voicebox { @@ -11,7 +11,7 @@ public class GenericVoicebox extends Voicebox { @Override public void play(String content) { - MqttUtil.publish("zsy/1/voice", MapUtil.builder() + Mqtt.publish("zsy/1/voice", MapUtil.builder() .put("deviceNo", getSn()) .put("text", content) .build()); diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/mapper/DeviceLocalizerMapper.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/mapper/DeviceLocalizerMapper.java new file mode 100644 index 0000000..9c5d7b3 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/mapper/DeviceLocalizerMapper.java @@ -0,0 +1,13 @@ +package com.njzscloud.supervisory.device.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njzscloud.supervisory.device.pojo.entity.DeviceLocalizerEntity; +import org.apache.ibatis.annotations.Mapper; + +/** + * 定位器 + */ +@Mapper +public interface DeviceLocalizerMapper extends BaseMapper { + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/entity/DeviceLocalizerEntity.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/entity/DeviceLocalizerEntity.java new file mode 100644 index 0000000..b57b2e7 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/entity/DeviceLocalizerEntity.java @@ -0,0 +1,72 @@ +package com.njzscloud.supervisory.device.pojo.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.time.LocalDateTime; + +/** + * 定位器 + */ +@Getter +@Setter +@ToString +@Accessors(chain = true) +@TableName("device_localizer") +public class DeviceLocalizerEntity { + + /** + * Id + */ + @TableId(type = IdType.ASSIGN_ID) + private Long id; + + /** + * 设备号 + */ + private String terminalId; + + /** + * 是否在线; 0-->否、1-->是 + */ + private Boolean online; + + /** + * 上次更新时间 + */ + private LocalDateTime lastTime; + + /** + * 创建人 Id; sys_user.id + */ + @TableField(fill = FieldFill.INSERT) + private Long creatorId; + + /** + * 修改人 Id; sys_user.id + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Long modifierId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime modifyTime; + + /** + * 是否删除; 0-->未删除、1-->已删除 + */ + @TableLogic + private Boolean deleted; + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/.gitkeep b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/DeviceInfoResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/DeviceInfoResult.java new file mode 100644 index 0000000..1d947f1 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/DeviceInfoResult.java @@ -0,0 +1,111 @@ +package com.njzscloud.supervisory.device.pojo.result; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class DeviceInfoResult { + private String terminalId; + + // region GPS定位状态 + /** + * 这是 GPS 定位的核心状态,三个参数分别代表: + * 1. 定位有效性:V = Valid(有效定位),若为N则表示 No Valid(无效定位,如无卫星信号); + * 2. 卫星数量:0 = 当前参与定位的卫星数量(此处为 0,可能是瞬时值或信号弱导致,正常定位时通常≥4); + * 3. 定位精度 / 备用参数:0 = 部分设备此处代表定位精度(单位:米),或备用状态码(具体需结合设备型号,途强部分设备此参数为 “定位类型”,0 = 未知,1=GPS,2 = 北斗)。 + */ + private String gpsValid; // 定位有效性(V/N) + private int satelliteCount; // 卫星数量 + private int gpsAccuracy; // 定位精度或备用参数 + /** + * GPS 模块硬件状态:OK = GPS 模块正常工作(供电、硬件无故障);若为ERR则表示 GPS 模块故障(如硬件损坏、供电异常)。 + */ + private String gpsModuleStatus; // GPS模块状态(OK/ERR) + // endregion + + // region 网络与信号状态 + private String serverIp; // 服务器IP + private int serverPort; // 服务器端口 + /** + * 2G/4G 网络注册状态(CGREG 是 GSM/UMTS 网络的 “小区注册状态” 指令): + * 1 = 设备已成功注册到本地移动网络(可正常联网); + * 其他常见值:0= 未注册,2= 正在注册,3= 注册被拒绝(如 SIM 卡欠费、无信号) + */ + private int cgregStatus; // 网络注册状态 + /** + * 网络信号强度(CSQ = Carrier Signal Quality,载波信号质量): + * 取值范围0-31,22属于良好信号(12-20 为中等,21-31 为良好,0 为无信号); + * 对应信号强度约为 -78dBm(CSQ 与 dBm 换算公式:dBm = -113 + (CSQ×2),22 时即 - 113+44=-69dBm,数值越接近 0 信号越强)。 + */ + private int csq; // 信号强度 + // endregion + + // region 硬件与电源状态 + /** + * 设备内置电池电压:单位为 “V(伏特)”,3.5V 属于正常电压范围(途强设备电池电压通常在 3.3V-4.2V 之间,低于 3.3V 可能触发低电报警)。 + */ + private double batteryVoltage; // 电池电压(V) + /** + * 供电模式 / 电池状态: + * 常见含义(途强设备自定义):1= 外接电源供电(如车充、市电),2= 电池供电,0= 供电异常; + * 部分设备此参数代表 “电池健康度”,2= 良好(需结合具体型号确认)。 + */ + private int powerMode; // 供电模式(1=外接电源,2=电池) + /** + * 北斗定位模块状态(BD = BeiDou): + * 0 = 北斗模块未启用 / 未定位(若设备支持北斗,此值为1表示北斗已参与定位);0不代表故障,仅表示当前未使用北斗信号。 + */ + private int beidouStatus; // 北斗模块状态(0=未启用,1=启用) + // endregion + + // region 功能与附加状态 + /** + * 报警状态:* = 无当前报警(途强设备常用 “*” 表示无报警,若有报警会显示具体报警码,如A:1= 低电报警,A:2= 位移报警) + */ + private String alarmStatus; // 报警状态(*) + /** + * 计数器 / 里程相关:常见为 “累计里程计数状态” 或 “指令执行计数”,0通常表示 “无异常” 或 “初始值”(部分设备此为 “充电次数”,需结合型号)。 + */ + private int counter; // 计数器/里程相关 + /** + * 定位上报间隔:单位为 “秒”,300 = 设备每 5 分钟自动上报一次定位数据(途强设备支持远程配置此间隔,如 10 秒、60 秒、300 秒等) + */ + private int reportInterval; // 定位上报间隔(秒) + /** + * 超速阈值 + */ + private int speedThreshold; // 超速阈值(km/h) + /** + * 关闭日志 + */ + private int logOff; // 关闭日志(0=关闭,1=开启) + /** + * 关闭拐点 + */ + private int inflectionPointOff; // 拐点关闭状态(0=关闭,1=开启) + private int heartbeatInterval; // 心跳包间隔(秒) + /** + * 关闭基站 + */ + private int baseStationOff; // 基站关闭状态(1=关闭,0=开启) + private int chargingStatus; // 充电状态(0=未充电,1=充电中) + private int bluetoothStatus; // 蓝牙状态(1=开启,0=关闭) + private int enableWarn; // 蓝牙状态(1=开启,0=关闭) + private String extendStatus; // 扩展状态码(十六进制) + private String temperature; // 温度传感器状态 + private String deviceTime; // 设备本地时间 + private int agpsStatus; // AGPS状态(1=开启,0=关闭) + private String displacement; // 位移距离 + private int speedStatus; // 速度状态(0=静止) + private String deviceId; // 设备ID/IMEI后7位 + private int lowBatteryThreshold; // 低电阈值状态 + private int accelerometerStatus; // 加速度传感器状态 + private int flightMode; // 飞行模式(0=关闭,1=开启) + // endregion + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/HeartbeatResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/HeartbeatResult.java new file mode 100644 index 0000000..4f1cafb --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/pojo/result/HeartbeatResult.java @@ -0,0 +1,16 @@ +package com.njzscloud.supervisory.device.pojo.result; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class HeartbeatResult { + private String terminalId; + private boolean online; + private String time; +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/service/DeviceLocalizerService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/service/DeviceLocalizerService.java new file mode 100644 index 0000000..18be4d5 --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/device/service/DeviceLocalizerService.java @@ -0,0 +1,95 @@ +package com.njzscloud.supervisory.device.service; + +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.IService; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.njzscloud.common.mp.support.PageParam; +import com.njzscloud.common.mp.support.PageResult; +import com.njzscloud.common.mqtt.support.MqttMsg; +import com.njzscloud.common.mqtt.util.Mqtt; +import com.njzscloud.supervisory.device.mapper.DeviceLocalizerMapper; +import com.njzscloud.supervisory.device.pojo.entity.DeviceLocalizerEntity; +import com.njzscloud.supervisory.device.pojo.result.DeviceInfoResult; +import com.njzscloud.supervisory.device.pojo.result.HeartbeatResult; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 定位器 + */ +@Slf4j +@Service +public class DeviceLocalizerService extends ServiceImpl implements IService { + + /** + * 新增 + */ + public void add(DeviceLocalizerEntity deviceLocalizerEntity) { + this.save(deviceLocalizerEntity); + } + + /** + * 修改 + */ + public void modify(DeviceLocalizerEntity deviceLocalizerEntity) { + this.updateById(deviceLocalizerEntity); + } + + /** + * 删除 + */ + @Transactional(rollbackFor = Exception.class) + public void del(List ids) { + this.removeBatchByIds(ids); + } + + /** + * 详情 + */ + public DeviceLocalizerEntity detail(Long id) { + return this.getById(id); + } + + /** + * 分页查询 + */ + public PageResult paging(PageParam pageParam, DeviceLocalizerEntity deviceLocalizerEntity) { + return PageResult.of(this.page(pageParam.toPage(), Wrappers.query(deviceLocalizerEntity))); + } + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationReady() { + List localizerEntities = this.list(); + for (DeviceLocalizerEntity localizerEntity : localizerEntities) { + String terminalId = localizerEntity.getTerminalId(); + Mqtt.subscribe(terminalId + "/online", this::heartbeat); + Mqtt.subscribe(terminalId + "/device_info", this::updateDeviceLocalizerStatus); + } + } + + public void heartbeat(MqttMsg msg) { + HeartbeatResult heartbeatResult = msg.getMsg(HeartbeatResult.class); + if (heartbeatResult == null) return; + this.update(Wrappers.lambdaUpdate(DeviceLocalizerEntity.class) + .set(DeviceLocalizerEntity::getOnline, heartbeatResult.isOnline()) + .set(DeviceLocalizerEntity::getLastTime, heartbeatResult.getTime()) + .eq(DeviceLocalizerEntity::getTerminalId, heartbeatResult.getTerminalId())); + } + + public void updateDeviceLocalizerStatus(MqttMsg msg) { + DeviceInfoResult deviceInfoResult = msg.getMsg(DeviceInfoResult.class); + if (deviceInfoResult == null) return; + this.update(Wrappers.lambdaUpdate(DeviceLocalizerEntity.class) + .set(DeviceLocalizerEntity::getOnline, Boolean.TRUE) + .set(DeviceLocalizerEntity::getLastTime, LocalDateTime.now()) + .eq(DeviceLocalizerEntity::getTerminalId, deviceInfoResult.getTerminalId())); + } + + +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/controller/OrderInfoController.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/controller/OrderInfoController.java index f98d6d5..da66a7c 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/controller/OrderInfoController.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/controller/OrderInfoController.java @@ -108,6 +108,7 @@ public class OrderInfoController { .dispatchPaging(pageParam, orderPagingSearchParam)); } */ + /** * 审核分页查询 */ @@ -175,6 +176,12 @@ public class OrderInfoController { return R.success(); } + @GetMapping("/t1") + public R t1() { + orderInfoService.t1(); + return R.success(); + } + /** * 过磅单 * diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/mapper/OrderInfoMapper.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/mapper/OrderInfoMapper.java index f43b3ad..1a0d83d 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/mapper/OrderInfoMapper.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/mapper/OrderInfoMapper.java @@ -3,6 +3,7 @@ package com.njzscloud.supervisory.order.mapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njzscloud.supervisory.biz.pojo.entity.BizTruckEntity; import com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity; import com.njzscloud.supervisory.order.pojo.result.OrderPagingResult; import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult; @@ -30,4 +31,6 @@ public interface OrderInfoMapper extends BaseMapper { void busyTruck(@Param("truckId") Long truckId, @Param("busy") Boolean busy); PaymentContextResult paymentContext(@Param("orderId") Long orderId); + + BizTruckEntity getTruckInfo(@Param("truckId") Long truckId); } diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/RealtimeLocationResult.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/RealtimeLocationResult.java new file mode 100644 index 0000000..95e054d --- /dev/null +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/pojo/result/RealtimeLocationResult.java @@ -0,0 +1,42 @@ +package com.njzscloud.supervisory.order.pojo.result; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@Getter +@Setter +@ToString +@Accessors(chain = true) +public class RealtimeLocationResult { + private String terminalId; + private int type; + + /** + * 1 1:超速报警 标志维持至报警条件解除 + * 7 1:终端主电源欠压 标志维持至报警条件解除 + * 8 1:声控录音报警 + */ + private long alarmFlag; + /** + * 0 0:ACC 关 1:ACC 开 + * 1 0:未定位 1:定位 + * 2 0:北纬 1:南纬 + * 3 0:东经 1:西经 + */ + private long status; + private double longitude; + private double latitude; + private int altitude; + private double speed; + private int direction; + private String time; + private boolean overspeed; + private boolean powerUnderVoltage; + private boolean soundAlarm; + private boolean accOn; + private boolean position; + private boolean south; + private boolean west; +} diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java index e2295a1..64a3544 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/order/service/OrderInfoService.java @@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.img.ImgUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.qrcode.QrCodeUtil; import cn.hutool.extra.qrcode.QrConfig; @@ -15,34 +16,38 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.njzscloud.common.core.ex.Exceptions; import com.njzscloud.common.mp.support.PageParam; import com.njzscloud.common.mp.support.PageResult; +import com.njzscloud.common.mqtt.util.Mqtt; import com.njzscloud.common.security.support.UserDetail; import com.njzscloud.common.security.util.SecurityUtil; import com.njzscloud.common.sn.support.SnUtil; import com.njzscloud.supervisory.biz.constant.AuditStatus; import com.njzscloud.supervisory.biz.constant.BizObj; import com.njzscloud.supervisory.biz.pojo.entity.BizAuditConfigEntity; +import com.njzscloud.supervisory.biz.pojo.entity.BizTruckEntity; +import com.njzscloud.supervisory.biz.pojo.entity.BizWarnEntity; +import com.njzscloud.supervisory.biz.pojo.entity.TruckLocationTrackEntity; import com.njzscloud.supervisory.biz.service.BizAuditConfigService; +import com.njzscloud.supervisory.biz.service.BizWarnService; +import com.njzscloud.supervisory.biz.service.TruckLocationTrackService; import com.njzscloud.supervisory.constant.Constant; +import com.njzscloud.supervisory.expense.contant.ExpenseItemCategory; +import com.njzscloud.supervisory.expense.contant.Scope; +import com.njzscloud.supervisory.expense.pojo.entity.ExpenseItemsConfigEntity; import com.njzscloud.supervisory.expense.service.ExpenseItemsConfigService; +import com.njzscloud.supervisory.goods.contant.MoneyStrategy; import com.njzscloud.supervisory.order.contant.CheckStatus; import com.njzscloud.supervisory.order.contant.OrderCategory; import com.njzscloud.supervisory.order.contant.OrderStatus; import com.njzscloud.supervisory.order.contant.OrderViewType; import com.njzscloud.supervisory.order.mapper.OrderInfoMapper; import com.njzscloud.supervisory.order.pojo.entity.OrderCargoPlaceEntity; +import com.njzscloud.supervisory.order.pojo.entity.OrderExpenseItemsEntity; import com.njzscloud.supervisory.order.pojo.entity.OrderGoodsEntity; import com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity; import com.njzscloud.supervisory.order.pojo.param.*; -import com.njzscloud.supervisory.order.pojo.result.OrderCertificateResult; -import com.njzscloud.supervisory.order.pojo.result.OrderPagingResult; -import com.njzscloud.supervisory.order.pojo.result.TrainBillResult; -import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult; -import com.njzscloud.supervisory.order.pojo.entity.OrderExpenseItemsEntity; -import com.njzscloud.supervisory.goods.contant.MoneyStrategy; -import com.njzscloud.supervisory.expense.pojo.entity.ExpenseItemsConfigEntity; -import com.njzscloud.supervisory.expense.contant.Scope; -import com.njzscloud.supervisory.expense.contant.ExpenseItemCategory; +import com.njzscloud.supervisory.order.pojo.result.*; import com.njzscloud.supervisory.sys.auth.pojo.result.MyResult; +import com.njzscloud.supervisory.sys.stationletter.constant.WarnCategory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -53,6 +58,7 @@ import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import static com.njzscloud.supervisory.constant.Constant.CERTIFICATE_SN_CODE; @@ -72,6 +78,8 @@ public class OrderInfoService extends ServiceImpl Exceptions.clierr("订单不存在")); Assert.isTrue(orderInfo.getOrderStatus() == OrderStatus.YiJieDan, () -> Exceptions.clierr("当前订单状态,无法开始运输")); Assert.isTrue(orderInfo.getAuditStatus() == AuditStatus.TongGuo, () -> Exceptions.clierr("当前订单未审核完成,无法开始运输")); + Long orderInfoId = orderInfo.getId(); this.updateById(new OrderInfoEntity() - .setId(orderInfo.getId()) + .setId(orderInfoId) .setCargoPhoto(startTransportOrderParam.getCargoPhoto()) .setOrderStatus(OrderStatus.QingYunZhong) .setTransTime(LocalDateTime.now()) ); // TODO 获取 GPS + Long truckId = orderInfo.getTruckId(); + Assert.notNull(truckId, () -> Exceptions.clierr("订单未分配车辆")); + BizTruckEntity truckInfo = baseMapper.getTruckInfo(truckId); + Assert.notNull(truckInfo, () -> Exceptions.clierr("车辆不存在")); + String gpsId = truckInfo.getGps(); + String licensePlate = truckInfo.getLicensePlate(); + + CompletableFuture.runAsync(() -> { + Assert.notEmpty(gpsId, () -> Exceptions.clierr("车辆未绑定GPS")); + Mqtt.subscribe(gpsId + "/track_location", (msg) -> { + RealtimeLocationResult realtimeLocationResult = msg.getMsg(RealtimeLocationResult.class); + if (realtimeLocationResult == null) { + return; + } + if (realtimeLocationResult.isOverspeed()) { + bizWarnService.save(new BizWarnEntity() + .setWarnCategory(WarnCategory.SPEED.getVal()) + .setWarnContent(StrUtil.format("{} 已超速,当前时速 {}km/h", licensePlate, realtimeLocationResult.getSpeed())) + .setOrderId(orderInfoId) + ); + } + log.info("mqtt 收到消息:{} {}", gpsId + "/track_location", realtimeLocationResult); + truckLocationTrackService.save(new TruckLocationTrackEntity() + .setOrderId(orderInfoId) + .setTruckId(truckId) + .setTerminalId(gpsId) + .setLatitude(realtimeLocationResult.getLatitude()) + .setLongitude(realtimeLocationResult.getLongitude()) + .setAltitude(realtimeLocationResult.getAltitude()) + .setSpeed(realtimeLocationResult.getSpeed()) + .setLocationTime(LocalDateTime.now()) + .setDirection(realtimeLocationResult.getDirection()) + .setOverspeed(realtimeLocationResult.isOverspeed()) + .setCompensate(realtimeLocationResult.getType() == 1) + ); + }); + Mqtt.publish("location/track", MapUtil.builder() + .put("terminalId", gpsId) + .put("interval", 1) + .build()); + }).whenComplete((aVoid, throwable) -> { + if (throwable != null) { + log.error("开启GPS失败", throwable); + } + }); + } + + public void t1() { + CompletableFuture.runAsync(() -> { + String gpsId = "61000602070"; + long orderInfoId = 1L; + long truckId = 1L; + Mqtt.subscribe(gpsId + "/track_location", (msg) -> { + RealtimeLocationResult realtimeLocationResult = msg.getMsg(RealtimeLocationResult.class); + if (realtimeLocationResult == null) { + return; + } + if (realtimeLocationResult.isOverspeed()) { + bizWarnService.save(new BizWarnEntity() + .setWarnCategory(WarnCategory.SPEED.getVal()) + .setWarnContent(StrUtil.format("{} 已超速,当前时速 {}km/h", "京AAAAAA", realtimeLocationResult.getSpeed())) + .setOrderId(orderInfoId) + ); + } + log.info("mqtt 收到消息:{} {}", gpsId + "/track_location", realtimeLocationResult); + truckLocationTrackService.save(new TruckLocationTrackEntity() + .setOrderId(orderInfoId) + .setTruckId(truckId) + .setTerminalId(gpsId) + .setLatitude(realtimeLocationResult.getLatitude()) + .setLongitude(realtimeLocationResult.getLongitude()) + .setAltitude(realtimeLocationResult.getAltitude()) + .setSpeed(realtimeLocationResult.getSpeed()) + .setLocationTime(LocalDateTime.now()) + .setDirection(realtimeLocationResult.getDirection()) + .setOverspeed(realtimeLocationResult.isOverspeed()) + .setCompensate(realtimeLocationResult.getType() == 1) + ); + }); + }).whenComplete((aVoid, throwable) -> { + if (throwable != null) { + log.error("开启GPS失败", throwable); + } + }); } public void cancel(Long orderId) { @@ -623,7 +716,22 @@ public class OrderInfoService extends ServiceImpl { + if (StrUtil.isEmpty(gpsId)) return; + Mqtt.publish("location/track", MapUtil.builder() + .put("terminalId", gpsId) + .put("interval", 0) + .build()); + Mqtt.unsubscribe(gpsId + "/track_location"); + }).whenComplete((aVoid, throwable) -> { + if (throwable != null) { + log.error("关闭GPS失败", throwable); + } + }); } @Transactional(rollbackFor = Exception.class) @@ -705,7 +813,20 @@ public class OrderInfoService extends ServiceImpl { + Assert.notEmpty(gpsId, () -> Exceptions.clierr("车辆未绑定GPS")); + Mqtt.publish("location/track", MapUtil.builder() + .put("terminalId", gpsId) + .put("interval", 0) + .build()); + Mqtt.unsubscribe(gpsId + "/track_location"); + }).whenComplete((aVoid, throwable) -> { + if (throwable != null) { + log.error("关闭GPS失败", throwable); + } + }); } /** diff --git a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/sys/user/service/UserService.java b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/sys/user/service/UserService.java index 8550957..a641069 100644 --- a/njzscloud-svr/src/main/java/com/njzscloud/supervisory/sys/user/service/UserService.java +++ b/njzscloud-svr/src/main/java/com/njzscloud/supervisory/sys/user/service/UserService.java @@ -49,10 +49,6 @@ public class UserService extends ServiceImpl implements */ @Transactional(rollbackFor = Exception.class) public Long add(AddUserParam addUserParam) { - String phone = addUserParam.getPhone(); - String nickname = addUserParam.getNickname(); - Assert.notBlank(phone, () -> Exceptions.clierr("手机号不能为空")); - Assert.notBlank(nickname, () -> Exceptions.clierr("用户姓名不能为空")); AddUserAccountParam addUserAccountParam = addUserParam.getAccount(); UserEntity userEntity = BeanUtil.copyProperties(addUserParam, UserEntity.class); @@ -186,6 +182,7 @@ public class UserService extends ServiceImpl implements addUserParam.setAccount(BeanUtil.copyProperties(account, AddUserAccountParam.class)); Long userId = this.add(addUserParam); + if (bizObj == BizObj.GeRen) return; UserRegisterParam.Company company = userRegisterParam.getCompany(); Assert.notNull(company, "公司信息不能为空"); diff --git a/njzscloud-svr/src/main/resources/application-dev.yml b/njzscloud-svr/src/main/resources/application-dev.yml index 4ce2ee6..f9b953a 100644 --- a/njzscloud-svr/src/main/resources/application-dev.yml +++ b/njzscloud-svr/src/main/resources/application-dev.yml @@ -21,6 +21,7 @@ spring: - /district/default_place - /bulletin/detail - /bulletin/paging + - /truck_location_track/** app: default-place: province: 320000 @@ -70,3 +71,16 @@ wechat: private-key-path: classpath:cert/apiclient_key.pem # 支付回调地址 notify-url: https://your-domain.com/api/payment/wechat/callback + +mqtt: + enabled: true + broker: tcp://139.224.54.144:1883 + client-id: njzscloud-svr1 + username: gps + password: TKG4TV3dF7CeazDnUdCF + +localizer: + enabled: true + boss-threads: 1 + worker-threads: 1 + port: 18888 diff --git a/njzscloud-svr/src/main/resources/application.yml b/njzscloud-svr/src/main/resources/application.yml index e727d33..b359469 100644 --- a/njzscloud-svr/src/main/resources/application.yml +++ b/njzscloud-svr/src/main/resources/application.yml @@ -1,5 +1,5 @@ server: - port: ${APP_PORT:8808} + port: ${APP_PORT:10086} tomcat: max-http-form-post-size: 20MB spring: diff --git a/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml b/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml index 16ac130..5586bf1 100644 --- a/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml +++ b/njzscloud-svr/src/main/resources/mapper/order/OrderInfoMapper.xml @@ -203,4 +203,9 @@ LEFT JOIN money_account ma1 ON ma1.user_id = bc.user_id WHERE a.id = #{orderId} + diff --git a/pom.xml b/pom.xml index 19582b7..fdda10a 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,11 @@ njzscloud-common-wechat 0.0.1 + + com.njzscloud + njzscloud-common-localizer + 0.0.1 + com.njzscloud njzscloud-common-http diff --git a/z-doc/db-model/njzscloud.pdma.json b/z-doc/db-model/njzscloud.pdma.json index a89e61a..424038c 100644 --- a/z-doc/db-model/njzscloud.pdma.json +++ b/z-doc/db-model/njzscloud.pdma.json @@ -4,7 +4,7 @@ "avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAEAYAAAD6+a2dAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAACv9JREFUeNrtnHtUVNUex7+/M+ADNBgkynfKDIiEmsrN11UhUYsB35ilBWoODBY+1l2+uHot8uYbCwZGE9EUn3nVQUEx8xFmoiGhYszgVdPsijJoXeR1zr5/4LC8oJnMwMHmfP5hLdj795ove++zZ+8DSEhISEhISNgiJHYAjYVWe9cvvfR5y5ZCaFkKlzdwIPIpn9vfurWgYCuRmp19965Go1CcPSt2nNbG5gTwXLJOZ5ju7s7FVI7ALpUKodSOOgQGUgnccHbQICQgml1q0qRmP7YIDGznzuIZrq7l8ydOBELGe+8qLxc7H0v5EwpgEfua2dk5xzy/pe3Ffv3QjUviClUqtMRcvK1S0WgY2VEvrzqbVwBYM3euKUujUcYtXSp2tpbyzArguT6fz7ww1sVF5lDWyX7osGEslVvN3Q4KotFMhtxhw/AdZrEPXVys7ngFDuKwwWCaotmrjPDwELsOlmIndgBPwiUj/sf8T7y82EnqiCEqFb6iv3KTVSq8WL4Ypf36IYM2Mz87O2rL/sbMncY9hYM38Aq9eecOCiGwXWlpCIOKzqWmMo7dYP8dPZrCKRmvhoRUtz+BYbRIJgOwV+zaWINGIIAdOxiTyeTOhTsuF/Tty66QP3tXpUI2FLgSHMxG4ys25uEhm4WxLwHkYvNTuRkBUP/LlzEMC6BLTSVvxAg79PqibrKcu4nHjgHq8N6dKipwqKq5c7iWM1wPCAAA3Be7RvVHgwmgxRld4o+Jrq5NAnie8/fzYyOxDGeCgrDndoYxIzgYIH90cnKiTgCSAQDGp3LQG8exhufZfhiAU6eoP6YAej0tYa+xdvv2FQVEeipv5eUhGcAoAEBkdd+tDVWFxofVBSA3xl0wDvX2ZqfJDuNHjgTP9cDyoCBS896szNeXAbkgjsMezIFvHRy8ilW0sKgIP7H9+DotDdHUBwl6vfBmWZHss4MH79JM6kTFxQCqHtkCxC1wY8cKAljEGOM45zy3GwUbtVoMpWHMbdo0MiESSiKArcRAAEDu01hlazGDIi9dQlMMwHy9Hj8Ik4XnU1OLo92WKFIyM4EQTyKexwRU/UdPeHYXtGJisQDki18oM5ydPRv9sI/6q9V4eGj9PcajmIIqKtALP7AhJ05QDPPBmNTUytbIpr16/b1xkeWKQ0YjgCMP5uUjYhfrz4jFAmAyakbJU6cSmBZRj2jwe0N23v8N2dsQB+Cq2CWxLSwWADVj+zGrY0cAV8E/9Ac/rMCaJUtMu10/cU9ZuFAashsnnMUWivAeKanWh8nimQ4TbtwAQkKIeL4upiXqH8sFIPFMIwnAxpEEYOM0gq3gKuQliXqjvG9frBN+QpaPj9UMv0WzoRIEbjgXy8/45ps7Z9Vqz/BLl8TOt7EgugDkzvFjC4wDBuBlYa6Qf/w4TIgEI+s9IfyDrYQe4FP4Am5NSYnrCZ0ur7lCcXuJWu11/+ZNsfMXG/GngN34C0vx8kL1zmH9QG/hJUQ5OPBXhdeaDOrcWey0GwuiC6BiFn+E3dqzB2PxLdpkZ1vdQWf44IAgsHRaiMz0dNMW7rDpo9On62wvFz5swUsvyb9NaGucERv7QsDyTTmzHR3FrKEliD4F/Hbug3RlXGEhzgGI69kTwBdWdWA+xfdq9W/CLbJ3Gbl4g+PwBlvAEBVV3r/F+45Tg4NdftDG50+YNq2omybSY+vhww1aRAsQfQR45slk/2Sfd+rERkJLkw4dkodpOxpSNm0yn1gSO7wnIQnAWpjXMHswB76TJnH7KnY1icvJkcu13fNnBwaKHd7jkATwtHjiDK28coUABhYSghmYjOaFhTWbkSebh1/btQOgpvDUVOdYbbwhf8cO88EYsdMwIwmgjhSZNJFKj507hVVIasJ7erIdAMauXfu49rQYBBo3zv4Uf4dTXrjgPD7+lDHhnXfEzkMSgIXcvavRdOxoMhUHaDTKHLUa/blkbnlgIAAdMq5dq9Xh75Cjg5sbHaLv2ZCNG+Ve2q7GgXq9S0mCPt+lbduGjl/0pwAzrXU63c/vOTjcb81vL/VVKNgkjBX87e0ttcudFQZBU1pqUtifKP44Px9Qh/f2raiorzxMqeGn3dceOODy+qfDDdN9fIRVsmByWLaMAqtOStXa7/gF09l6lYoNYj/RmtxcZ2/tTePymTOLL2haK5pv3FjfdRd9BHBy0mrzJ3TuXHqezyjZevUqvYuxvF9ODscBjJ05Y+lP+HLHWPz58865/BdOynPnGuq5vSjtg3Rl3L17xV6R7RSh4eGsA3xwwM/PfK+gVgcjgCi5nH7Gv9j15GTn7xJcDclpaXJnXWKBsUOH+opTdAFwIWSPdwICsBn+LLv+Fkc0EG+jsGvX8iyHfS0iXnmlofMsPqrZpow7dszxfOl/mpV0786O0jf0xbJlCMBEmllZWSve4exD9B8+nBn5JOZ9/rxcntDFMD0iAmCMWXGrXHQBYGdlkEx58iQWoCNkZWX15ucjmHDt1i2+S9P2pXMvXhQr3eurZ51qP/v+/eLuESmKD+fMEQ5iM2L79EFXxNKsnJya7UmJUHa+ZUuAfYAorVY+PaG9MfvIkZbz4nzzPmvVytJ4RF8DmIrff9FdkZsrj9ElFhg9PISvhPYI7dVLNoRdEzbYWRyfsIo1waelpXw+21PpefLkr6emZnnvKioSO28z1beOM3WJZ7J8fZ1j+PbPlc+ZQ80Aah4djY9xFXzTptUdtmA+Wg4ebLdZdsweK1YAyALCwurqX3QBmDEVq8PdFdeuoRcAPFg9W+PmXZ1L09BULU6LowEgJqbqStyXXzItTaTrmzZBg48R1bt3dfN/sEHsxxEjAGy3xKv4U4DEIykKiPT0mJuXJ7yJ74AtW2o1eLBotNSPJAAbRxKAjSMJwMaRBGDjiP4UYN6ZKwt0/NVBvWEDXccSuPTsidtYQF0t3/BgCtKwkMpKmsxCKfPAAVObiA3u38+aBRARMWap/Zo4sfix+dt79KBD1I1GJiTQVrhSeze3WnEFYgZ6lJfTRdwTliQlmRZpmnn0Xr68oepuRnQBlMY6nnHQhYRwA9AF4ePGAVgA04MiZVpun8C0AICVAIvy8Gg1Wnv43z1SUu7sBoCsLGvnQ6EkcOujo2kfXFnPPn0elwftRSwAQI5eZFi6tOoAyfr1905NXd2Q+xSiTwHcABxj6lu36t3Rg7OBFT9DJ/iZTPXmJx6x6FD7fMDjYKfhSKN++80xjO8il5eW1nsdaiD6CGAyaXI8Vu7f7/J6wm1D2ZQpwk4WSqW+vvgWqSyLs1igNJvWIaOykk0XNDCkp98Li9yliDM+3dtHngLmWNZetm7ePDg1S6hM++UXrGMRmNemTa24zqI3+LIyysACbNu27aZarW4zqqSkvutdE9EFYKYoLWKhMi4pCS2wEEhKsqpxNwBheK8h8njoujuAxYsRAM0TO41qiMgejehTgIS4SAKwcSQB2DiSAGwcSQA2jiQAG0cSgI0jCcDGkQRg4zzx27bqb+vGOfZ0XLN7Nx1GBwz390cGNrPVlh/alLAS5jev2kHLUjIzqaDyZSSNGGG+n/C4bk/8AMtcHDwcFWPG0BwcZ3lDh4qdp8Rj2A5npre3BzAfGDxYGCrrSd4jRwJIBzZtely3J04BdBArKODyZbHzk/iDyBEPA2O0QVaI+Y+4gVSDP3zgwumjhIACN39/bh8bJYT41uVF7xL1iQdri72MCZu5YFw7ffouRZCSjh4VOywJCQkJCQkJCYlGyP8A/eZcApAQzfUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDctMjRUMTg6NTY6NTcrMDg6MDCiMaMiAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA3LTI0VDE4OjU2OjU3KzA4OjAw02wbngAAAEx0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fdjh0ZTByYWQxdmQvZ29uZ3NpLTAxLnN2Z1gR1IgAAAAASUVORK5CYII=", "version": "4.9.4", "createdTime": "2023-4-13 11:53:52", - "updatedTime": "2025-9-25 18:09:39", + "updatedTime": "2025-9-28 13:34:44", "dbConns": [], "profile": { "default": { @@ -18141,6 +18141,683 @@ "correlations": [], "indexes": [], "type": "P" + }, + { + "id": "72EF0C80-289A-4147-A90C-472E9B0252C9", + "env": { + "base": { + "nameSpace": "", + "codeRoot": "" + } + }, + "defKey": "truck_location_track", + "defName": "车辆定位数据", + "comment": "", + "properties": {}, + "sysProps": { + "nameTemplate": "{defKey}[{defName}]" + }, + "notes": {}, + "headers": [ + { + "refKey": "hideInGraph", + "hideInGraph": true + }, + { + "refKey": "defKey", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "type", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "len", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "scale", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "defaultValue", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "defName", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "comment", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "notNull", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "primaryKey", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "autoIncrement", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "refDict", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "domain", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "isStandard", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "uiHint", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "extProps", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr1", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr2", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr3", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr4", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr5", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr6", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr7", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr8", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr9", + "freeze": false, + "hideInGraph": true + } + ], + "fields": [ + { + "defKey": "id", + "defName": "Id", + "comment": "", + "type": "BIGINT", + "len": "", + "scale": "", + "primaryKey": true, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "extProps": {}, + "domain": "", + "id": "E7B07765-44C9-4E26-A1B2-764B370A65B4", + "baseType": "9B6B9E10-DB11-4409-878B-5868A19CD9B0" + }, + { + "defKey": "order_id", + "defName": "订单 Id", + "comment": "", + "type": "BIGINT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "baseType": "9B6B9E10-DB11-4409-878B-5868A19CD9B0", + "extProps": {}, + "id": "8C71F5CE-04A4-4229-8B01-0579E51F8BCC" + }, + { + "defKey": "truck_id", + "defName": "车辆 Id", + "comment": "", + "type": "BIGINT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "baseType": "9B6B9E10-DB11-4409-878B-5868A19CD9B0", + "extProps": {}, + "id": "F204C4FA-308B-4CBC-AB3C-BFD2D9505F38" + }, + { + "defKey": "terminal_id", + "defName": "设备号", + "comment": "", + "type": "VARCHAR", + "len": 64, + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "baseType": "AA07828C-4FCB-4EDA-9B51-53A3F264F231", + "extProps": {}, + "id": "7021D3C3-6F58-48CB-A7F7-2C2ADB08EC2D" + }, + { + "defKey": "longitude", + "defName": "经度", + "comment": "单位:度", + "type": "DOUBLE", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": false, + "refDict": "", + "baseType": "D57B7F8D-4728-43D3-9F21-A1953D885CFB", + "extProps": {}, + "id": "E97ABED5-E8B7-4DB6-B0DB-CB39B7D9912E" + }, + { + "defKey": "latitude", + "defName": "纬度", + "comment": "单位:度", + "type": "DOUBLE", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": false, + "refDict": "", + "baseType": "D57B7F8D-4728-43D3-9F21-A1953D885CFB", + "extProps": {}, + "id": "1D5960B0-9E50-4CC5-8380-93BC1806A96A" + }, + { + "defKey": "altitude", + "defName": "海拔", + "comment": "米", + "type": "SMALLINT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": false, + "refDict": "", + "baseType": "4B32336F-1C1F-4019-AB6F-F9B26E610385", + "extProps": {}, + "id": "123BD79C-BED6-4DD8-AC9D-498092AB7411" + }, + { + "defKey": "speed", + "defName": "速度", + "comment": "千米/小时", + "type": "DOUBLE", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": false, + "refDict": "", + "baseType": "D57B7F8D-4728-43D3-9F21-A1953D885CFB", + "extProps": {}, + "id": "D24366E6-5D4F-47D6-93ED-0B4199E5FED5" + }, + { + "defKey": "location_time", + "defName": "定位时间", + "comment": "", + "type": "DATETIME", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "baseType": "A098BA98-4957-43EE-9F06-1CDC26D370E0", + "extProps": {}, + "id": "6E71EA86-5E21-4A94-A468-D6B410250D87" + }, + { + "defKey": "direction", + "defName": "方向", + "comment": "正北为 0 度", + "type": "SMALLINT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": false, + "refDict": "", + "baseType": "4B32336F-1C1F-4019-AB6F-F9B26E610385", + "extProps": {}, + "id": "999DC5F3-3BF3-4CDC-B7B1-AD7375F765D1" + }, + { + "defKey": "overspeed", + "defName": "是否超速", + "comment": "0-->否、1-->是", + "type": "TINYINT", + "len": 1, + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": false, + "refDict": "", + "baseType": "9C056E28-859C-4498-9E07-63480343AFEB", + "extProps": {}, + "id": "C74EAE44-9252-453D-906D-14F2F6169AFB" + }, + { + "defKey": "compensate", + "defName": "是否为补偿数据", + "comment": "0-->否、1-->是", + "type": "TINYINT", + "len": 1, + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": false, + "refDict": "", + "baseType": "9C056E28-859C-4498-9E07-63480343AFEB", + "extProps": {}, + "id": "4F0D9C74-7C3B-4E1D-9B8C-A797D7B1F7F7" + }, + { + "defKey": "create_time", + "defName": "创建时间", + "comment": "", + "type": "DATETIME", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "extProps": {}, + "domain": "", + "id": "A932CC89-D898-4C3C-884C-FB1866B01422", + "baseType": "A098BA98-4957-43EE-9F06-1CDC26D370E0" + } + ], + "correlations": [], + "indexes": [], + "type": "P" + }, + { + "id": "BA0DF4DA-A1D8-4CD3-BBC1-4FF156C73F9A", + "env": { + "base": { + "nameSpace": "", + "codeRoot": "" + } + }, + "defKey": "device_localizer", + "defName": "定位器", + "comment": "", + "properties": {}, + "sysProps": { + "nameTemplate": "{defKey}[{defName}]" + }, + "notes": {}, + "headers": [ + { + "refKey": "hideInGraph", + "hideInGraph": true + }, + { + "refKey": "defKey", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "type", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "len", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "scale", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "defaultValue", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "defName", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "comment", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "notNull", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "primaryKey", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "autoIncrement", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "refDict", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "domain", + "freeze": false, + "hideInGraph": false + }, + { + "refKey": "isStandard", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "uiHint", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "extProps", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr1", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr2", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr3", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr4", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr5", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr6", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr7", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr8", + "freeze": false, + "hideInGraph": true + }, + { + "refKey": "attr9", + "freeze": false, + "hideInGraph": true + } + ], + "fields": [ + { + "defKey": "id", + "defName": "Id", + "comment": "", + "type": "BIGINT", + "len": "", + "scale": "", + "primaryKey": true, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "extProps": {}, + "domain": "", + "id": "AF9A849D-95FD-4F0E-A0CE-DCFFACDC5A4A", + "baseType": "9B6B9E10-DB11-4409-878B-5868A19CD9B0" + }, + { + "defKey": "terminal_id", + "defName": "设备号", + "comment": "", + "type": "VARCHAR", + "len": 64, + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "baseType": "AA07828C-4FCB-4EDA-9B51-53A3F264F231", + "extProps": {}, + "id": "72260EAF-84F3-4AE6-A5B5-F618F113E578" + }, + { + "defKey": "online", + "defName": "是否在线", + "comment": "0-->否、1-->是", + "type": "TINYINT", + "len": 1, + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "baseType": "9C056E28-859C-4498-9E07-63480343AFEB", + "extProps": {}, + "id": "23BAC3A2-FFE9-4461-872B-8DB292326EBE" + }, + { + "defKey": "last_time", + "defName": "上次更新时间", + "comment": "", + "type": "DATETIME", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": false, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": false, + "refDict": "", + "baseType": "A098BA98-4957-43EE-9F06-1CDC26D370E0", + "extProps": {}, + "id": "8E87F393-C52C-4350-B891-F74CF6452FFF" + }, + { + "defKey": "creator_id", + "defName": "创建人 Id", + "comment": "sys_user.id", + "type": "BIGINT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "extProps": {}, + "domain": "", + "id": "2B29AFDA-E61A-48C7-9A69-8CE6BD9BC313", + "baseType": "9B6B9E10-DB11-4409-878B-5868A19CD9B0" + }, + { + "defKey": "modifier_id", + "defName": "修改人 Id", + "comment": "sys_user.id", + "type": "BIGINT", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "extProps": {}, + "domain": "", + "id": "822A0FDD-D6B6-4513-BC88-4246974E989A", + "baseType": "9B6B9E10-DB11-4409-878B-5868A19CD9B0" + }, + { + "defKey": "create_time", + "defName": "创建时间", + "comment": "", + "type": "DATETIME", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "extProps": {}, + "domain": "", + "id": "780B8D60-0F2A-4810-BBD9-48F3A80AC162", + "baseType": "A098BA98-4957-43EE-9F06-1CDC26D370E0" + }, + { + "defKey": "modify_time", + "defName": "修改时间", + "comment": "", + "type": "DATETIME", + "len": "", + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "", + "hideInGraph": true, + "refDict": "", + "extProps": {}, + "domain": "", + "id": "6FF497C9-1224-42BE-AE64-FC5D0A2DD2F0", + "baseType": "A098BA98-4957-43EE-9F06-1CDC26D370E0" + }, + { + "defKey": "deleted", + "defName": "是否删除", + "comment": "0-->未删除、1-->已删除", + "type": "TINYINT", + "len": 1, + "scale": "", + "primaryKey": false, + "notNull": true, + "autoIncrement": false, + "defaultValue": "0", + "hideInGraph": true, + "refDict": "", + "extProps": {}, + "id": "0AEE70C8-AE1C-4975-BB61-B2118F5E668F", + "baseType": "9C056E28-859C-4498-9E07-63480343AFEB" + } + ], + "correlations": [], + "indexes": [], + "type": "P" } ], "views": [], diff --git a/njzscloud-svr/微信支付配置说明.md b/z-doc/微信支付配置说明.md similarity index 100% rename from njzscloud-svr/微信支付配置说明.md rename to z-doc/微信支付配置说明.md