GPS
parent
ee4247503a
commit
dbc31ab6e6
|
|
@ -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+", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>njzscloud-common-localizer</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-core</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-mqtt</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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<String, Channel> 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<TerminalMessageBody> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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> T parseMessageBody(Class<T> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.njzscloud.common.localizer.jt808.message;
|
||||||
|
|
||||||
|
public interface ServerMessageBody {
|
||||||
|
byte[] toBytes();
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 "未知";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<JT808Message> {
|
||||||
|
// 消息起始符和结束符
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<String, Consumer<TerminalMessageBody>> resolvers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private static Map<Integer, Consumer<JT808Message>> stdResolvers = MapUtil.<Integer, Consumer<JT808Message>>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<TerminalMessageBody> resolver) {
|
||||||
|
JT808MessageResolver.resolvers.put(messageId, resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dispatchMsg(String messageId, JT808Message message) {
|
||||||
|
Consumer<TerminalMessageBody> 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<JT808Message> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<SocketChannel>() {
|
||||||
|
@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("服务器已停止");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<String, Integer> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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("<SPBSJ*P:BSJGPS*C:{}*H:300>", interval));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备信息
|
||||||
|
*
|
||||||
|
* @param terminalId 终端ID
|
||||||
|
*/
|
||||||
|
public static void obtainDeviceInfo(String terminalId) {
|
||||||
|
JT808.sendTxtMessage(terminalId, "<CKBSJ>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用报警
|
||||||
|
*
|
||||||
|
* @param terminalId 终端ID
|
||||||
|
*/
|
||||||
|
public static void enableWarn(String terminalId, boolean enable) {
|
||||||
|
JT808.sendTxtMessage(terminalId, StrUtil.format("<SPBSJ*P:BSJGPS*6W:{}>", enable ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置速度报警
|
||||||
|
*
|
||||||
|
* @param terminalId 终端ID
|
||||||
|
* @param speed 速度,单位km/h
|
||||||
|
*/
|
||||||
|
public static void speedThreshold(String terminalId, int speed) {
|
||||||
|
JT808.sendTxtMessage(terminalId, StrUtil.format("<SPBSJ*P:BSJGPS*CS:{}*H:300>", speed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前位置
|
||||||
|
*
|
||||||
|
* @param terminalId 终端ID
|
||||||
|
*/
|
||||||
|
public static void currentLocation(String terminalId) {
|
||||||
|
JT808Message baseMessage = JT808.createBaseMessage(terminalId, 0x8201);
|
||||||
|
JT808.sendMessage(baseMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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表示解析失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
|
||||||
|
com.njzscloud.common.localizer.config.LocalizerAutoConfiguration
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.njzscloud.common.mqtt.config;
|
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.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
@ -12,7 +12,7 @@ import org.springframework.context.annotation.Configuration;
|
||||||
public class MqttAutoConfiguration {
|
public class MqttAutoConfiguration {
|
||||||
|
|
||||||
@Bean(destroyMethod = "shutdown")
|
@Bean(destroyMethod = "shutdown")
|
||||||
public Mqtt mqtt(MqttProperties mqttProperties) {
|
public MqttCliWrapper mqtt(MqttProperties mqttProperties) {
|
||||||
return new Mqtt(mqttProperties);
|
return new MqttCliWrapper(mqttProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.njzscloud.common.core.jackson.Jackson;
|
||||||
import com.njzscloud.common.mqtt.config.MqttProperties;
|
import com.njzscloud.common.mqtt.config.MqttProperties;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.paho.client.mqttv3.*;
|
import org.eclipse.paho.client.mqttv3.*;
|
||||||
|
import org.eclipse.paho.client.mqttv3.persist.MqttDefaultFilePersistence;
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||||
|
|
||||||
|
|
@ -14,18 +15,22 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Mqtt implements BeanPostProcessor {
|
public class MqttCliWrapper implements BeanPostProcessor {
|
||||||
private static final Map<String, Consumer<MqttMsg>> fn = new ConcurrentHashMap<>();
|
private static final Map<String, Consumer<MqttMsg>> fn = new ConcurrentHashMap<>();
|
||||||
private MqttClient client;
|
private MqttClient client;
|
||||||
|
|
||||||
public Mqtt(MqttProperties mqttProperties) {
|
public MqttCliWrapper(MqttProperties mqttProperties) {
|
||||||
String broker = mqttProperties.getBroker();
|
String broker = mqttProperties.getBroker();
|
||||||
String clientId = mqttProperties.getClientId();
|
String clientId = mqttProperties.getClientId();
|
||||||
String username = mqttProperties.getUsername();
|
String username = mqttProperties.getUsername();
|
||||||
String password = mqttProperties.getPassword();
|
String password = mqttProperties.getPassword();
|
||||||
try {
|
try {
|
||||||
client = new MqttClient(broker, clientId);
|
MqttDefaultFilePersistence persistence = new MqttDefaultFilePersistence("logs/mqtt");
|
||||||
|
client = new MqttClient(broker, clientId, persistence);
|
||||||
MqttConnectOptions options = new MqttConnectOptions();
|
MqttConnectOptions options = new MqttConnectOptions();
|
||||||
|
options.setAutomaticReconnect(true);
|
||||||
|
options.setConnectionTimeout(5000);
|
||||||
|
options.setMaxReconnectDelay(30000);
|
||||||
if (StrUtil.isNotBlank(username) && StrUtil.isNotBlank(password)) {
|
if (StrUtil.isNotBlank(username) && StrUtil.isNotBlank(password)) {
|
||||||
options.setUserName(username);
|
options.setUserName(username);
|
||||||
options.setPassword(password.toCharArray());
|
options.setPassword(password.toCharArray());
|
||||||
|
|
@ -52,6 +57,7 @@ public class Mqtt implements BeanPostProcessor {
|
||||||
public void subscribe(String topic, int qos) {
|
public void subscribe(String topic, int qos) {
|
||||||
try {
|
try {
|
||||||
client.subscribe(topic, qos);
|
client.subscribe(topic, qos);
|
||||||
|
log.info("mqtt 订阅消息:{} {}", topic, qos);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("mqtt 订阅失败:{} {}", topic, qos, 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 {
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||||
Class<?> clazz = bean.getClass();
|
Class<?> clazz = bean.getClass();
|
||||||
for (Method method : clazz.getDeclaredMethods()) {
|
for (Method method : clazz.getDeclaredMethods()) {
|
||||||
MqttHandler mqttHandler = method.getAnnotation(MqttHandler.class);
|
MqttListener mqttListener = method.getAnnotation(MqttListener.class);
|
||||||
if (mqttHandler == null) continue;
|
if (mqttListener == null) continue;
|
||||||
String topic = mqttHandler.topic();
|
String topic = mqttListener.topic();
|
||||||
int qos = mqttHandler.qos();
|
int qos = mqttListener.qos();
|
||||||
this.subscribe(topic, qos);
|
this.subscribe(topic, qos);
|
||||||
Consumer<MqttMsg> handler = (msg) -> {
|
Consumer<MqttMsg> handler = (msg) -> {
|
||||||
try {
|
try {
|
||||||
|
|
@ -108,6 +114,14 @@ public class Mqtt implements BeanPostProcessor {
|
||||||
return bean;
|
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 static class MsgHandler implements MqttCallback {
|
||||||
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
public void messageArrived(String topic, MqttMessage message) throws Exception {
|
||||||
Consumer<MqttMsg> handler = fn.get(topic);
|
Consumer<MqttMsg> handler = fn.get(topic);
|
||||||
|
|
@ -124,7 +138,7 @@ public class Mqtt implements BeanPostProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deliveryComplete(IMqttDeliveryToken token) {
|
public void deliveryComplete(IMqttDeliveryToken token) {
|
||||||
log.info("消息投递结果:{}", token.isComplete());
|
// log.info("消息投递结果:{}", token.isComplete());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target({ElementType.METHOD})
|
@Target({ElementType.METHOD})
|
||||||
public @interface MqttHandler {
|
public @interface MqttListener {
|
||||||
String topic();
|
String topic();
|
||||||
|
|
||||||
int qos() default 0;
|
int qos() default 0;
|
||||||
|
|
@ -1,40 +1,44 @@
|
||||||
package com.njzscloud.common.mqtt.util;
|
package com.njzscloud.common.mqtt.util;
|
||||||
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
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 com.njzscloud.common.mqtt.support.MqttMsg;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public final class MqttUtil {
|
public final class Mqtt {
|
||||||
private static final Mqtt mqtt;
|
private static final MqttCliWrapper MQTT_CLI_WRAPPER;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
mqtt = SpringUtil.getBean(Mqtt.class);
|
MQTT_CLI_WRAPPER = SpringUtil.getBean(MqttCliWrapper.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void subscribe(String topic, int qos, Consumer<MqttMsg> handler) {
|
public static void subscribe(String topic, int qos, Consumer<MqttMsg> handler) {
|
||||||
mqtt.subscribe(topic, qos, handler);
|
MQTT_CLI_WRAPPER.subscribe(topic, qos, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void subscribe(String topic, Consumer<MqttMsg> handler) {
|
public static void subscribe(String topic, Consumer<MqttMsg> handler) {
|
||||||
mqtt.subscribe(topic, handler);
|
MQTT_CLI_WRAPPER.subscribe(topic, handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void subscribe(String topic, int qos) {
|
public static void subscribe(String topic, int qos) {
|
||||||
mqtt.subscribe(topic, qos);
|
MQTT_CLI_WRAPPER.subscribe(topic, qos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void subscribe(String topic) {
|
public static void subscribe(String topic) {
|
||||||
mqtt.subscribe(topic);
|
MQTT_CLI_WRAPPER.subscribe(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void publish(String topic, Object msg) {
|
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) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
<module>njzscloud-common-mqtt</module>
|
<module>njzscloud-common-mqtt</module>
|
||||||
<module>njzscloud-common-http</module>
|
<module>njzscloud-common-http</module>
|
||||||
<module>njzscloud-common-wechat</module>
|
<module>njzscloud-common-wechat</module>
|
||||||
|
<module>njzscloud-common-localizer</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@
|
||||||
<groupId>com.njzscloud</groupId>
|
<groupId>com.njzscloud</groupId>
|
||||||
<artifactId>njzscloud-common-security</artifactId>
|
<artifactId>njzscloud-common-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-localizer</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.njzscloud</groupId>
|
<groupId>com.njzscloud</groupId>
|
||||||
<artifactId>njzscloud-common-gen</artifactId>
|
<artifactId>njzscloud-common-gen</artifactId>
|
||||||
|
|
@ -120,6 +124,36 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>post-package-actions</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>run</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<target>
|
||||||
|
<exec executable="cmd" osfamily="windows" failonerror="false">
|
||||||
|
<arg value="/c"/>
|
||||||
|
<arg value="explorer"/>
|
||||||
|
<arg value="${project.build.directory}"/>
|
||||||
|
</exec>
|
||||||
|
<exec executable="open" osfamily="mac" failonerror="false">
|
||||||
|
<arg value="${project.build.directory}"/>
|
||||||
|
</exec>
|
||||||
|
|
||||||
|
<exec executable="xdg-open" osfamily="unix" failonerror="false">
|
||||||
|
<arg value="${project.build.directory}"/>
|
||||||
|
</exec>
|
||||||
|
</target>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -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<Long> ids) {
|
||||||
|
truckLocationTrackService.del(ids);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
public R<TruckLocationTrackEntity> detail(@RequestParam Long id) {
|
||||||
|
return R.success(truckLocationTrackService.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
@GetMapping("/paging")
|
||||||
|
public R<PageResult<TruckLocationTrackEntity>> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<TruckLocationTrackEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<TruckLocationTrackMapper, TruckLocationTrackEntity> implements IService<TruckLocationTrackEntity> {
|
||||||
|
|
||||||
|
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<Long> ids) {
|
||||||
|
this.removeBatchByIds(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
public TruckLocationTrackEntity detail(Long id) {
|
||||||
|
return this.getById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
public PageResult<TruckLocationTrackEntity> paging(PageParam pageParam, TruckLocationTrackEntity truckLocationTrackEntity) {
|
||||||
|
return PageResult.of(this.page(pageParam.toPage(), Wrappers.<TruckLocationTrackEntity>query(truckLocationTrackEntity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SseEmitter history(Long orderId, LocalDateTime startTime, LocalDateTime endTime) {
|
||||||
|
SseEmitter emitter = new SseEmitter(0L);
|
||||||
|
|
||||||
|
// 提交异步任务
|
||||||
|
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
||||||
|
try {
|
||||||
|
// 记录上一条数据的时间,用于计算间隔
|
||||||
|
LocalDateTime lastRecordTime = null;
|
||||||
|
int currentPage = 1;
|
||||||
|
// 分页查询,每次500条
|
||||||
|
while (true) {
|
||||||
|
// 检查是否被取消
|
||||||
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
|
log.info("任务被取消");
|
||||||
|
emitter.complete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<TruckLocationTrackEntity> page = new Page<>(currentPage, 500);
|
||||||
|
page.addOrder(OrderItem.asc("location_time"));
|
||||||
|
|
||||||
|
// 分页查询当前页数据
|
||||||
|
IPage<TruckLocationTrackEntity> resultPage = this.page(page, Wrappers.lambdaQuery(TruckLocationTrackEntity.class)
|
||||||
|
.eq(TruckLocationTrackEntity::getOrderId, orderId)
|
||||||
|
.between(TruckLocationTrackEntity::getLocationTime, startTime, endTime));
|
||||||
|
|
||||||
|
List<TruckLocationTrackEntity> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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<Long> ids) {
|
||||||
|
deviceLocalizerService.del(ids);
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail")
|
||||||
|
public R<DeviceLocalizerEntity> detail(@RequestParam Long id) {
|
||||||
|
return R.success(deviceLocalizerService.detail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
@GetMapping("/paging")
|
||||||
|
public R<PageResult<DeviceLocalizerEntity>> paging(PageParam pageParam, DeviceLocalizerEntity deviceLocalizerEntity) {
|
||||||
|
return R.success(deviceLocalizerService.paging(pageParam, deviceLocalizerEntity));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package com.njzscloud.supervisory.device.drive.barrier.generic;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.njzscloud.common.mqtt.support.MqttMsg;
|
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 com.njzscloud.supervisory.device.drive.barrier.Barrier;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
@ -28,7 +28,7 @@ public class GenericBarrier extends Barrier {
|
||||||
.getBody()
|
.getBody()
|
||||||
.setIo(config.getIo())
|
.setIo(config.getIo())
|
||||||
.setValue(1);
|
.setValue(1);
|
||||||
MqttUtil.publish(topic(Fn.gpio_out), ioParam);
|
Mqtt.publish(topic(Fn.gpio_out), ioParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openReply(MqttMsg msg) {
|
private void openReply(MqttMsg msg) {
|
||||||
|
|
@ -44,7 +44,7 @@ public class GenericBarrier extends Barrier {
|
||||||
.getBody()
|
.getBody()
|
||||||
.setIo(config.getIo())
|
.setIo(config.getIo())
|
||||||
.setValue(0);
|
.setValue(0);
|
||||||
MqttUtil.publish(topic(Fn.gpio_out), ioParam);
|
Mqtt.publish(topic(Fn.gpio_out), ioParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -55,7 +55,7 @@ public class GenericBarrier extends Barrier {
|
||||||
.getBody()
|
.getBody()
|
||||||
.setIo(config.getIo())
|
.setIo(config.getIo())
|
||||||
.setValue(2);
|
.setValue(2);
|
||||||
MqttUtil.publish(topic(Fn.gpio_out), ioParam);
|
Mqtt.publish(topic(Fn.gpio_out), ioParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String topic(Fn fn) {
|
private String topic(Fn fn) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.njzscloud.supervisory.device.drive.voicebox.generic;
|
package com.njzscloud.supervisory.device.drive.voicebox.generic;
|
||||||
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
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;
|
import com.njzscloud.supervisory.device.drive.voicebox.Voicebox;
|
||||||
|
|
||||||
public class GenericVoicebox extends Voicebox {
|
public class GenericVoicebox extends Voicebox {
|
||||||
|
|
@ -11,7 +11,7 @@ public class GenericVoicebox extends Voicebox {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void play(String content) {
|
public void play(String content) {
|
||||||
MqttUtil.publish("zsy/1/voice", MapUtil.builder()
|
Mqtt.publish("zsy/1/voice", MapUtil.builder()
|
||||||
.put("deviceNo", getSn())
|
.put("deviceNo", getSn())
|
||||||
.put("text", content)
|
.put("text", content)
|
||||||
.build());
|
.build());
|
||||||
|
|
|
||||||
|
|
@ -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<DeviceLocalizerEntity> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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<DeviceLocalizerMapper, DeviceLocalizerEntity> implements IService<DeviceLocalizerEntity> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增
|
||||||
|
*/
|
||||||
|
public void add(DeviceLocalizerEntity deviceLocalizerEntity) {
|
||||||
|
this.save(deviceLocalizerEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改
|
||||||
|
*/
|
||||||
|
public void modify(DeviceLocalizerEntity deviceLocalizerEntity) {
|
||||||
|
this.updateById(deviceLocalizerEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除
|
||||||
|
*/
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void del(List<Long> ids) {
|
||||||
|
this.removeBatchByIds(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 详情
|
||||||
|
*/
|
||||||
|
public DeviceLocalizerEntity detail(Long id) {
|
||||||
|
return this.getById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询
|
||||||
|
*/
|
||||||
|
public PageResult<DeviceLocalizerEntity> paging(PageParam pageParam, DeviceLocalizerEntity deviceLocalizerEntity) {
|
||||||
|
return PageResult.of(this.page(pageParam.toPage(), Wrappers.<DeviceLocalizerEntity>query(deviceLocalizerEntity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void onApplicationReady() {
|
||||||
|
List<DeviceLocalizerEntity> 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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -108,6 +108,7 @@ public class OrderInfoController {
|
||||||
.dispatchPaging(pageParam, orderPagingSearchParam));
|
.dispatchPaging(pageParam, orderPagingSearchParam));
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 审核分页查询
|
* 审核分页查询
|
||||||
*/
|
*/
|
||||||
|
|
@ -175,6 +176,12 @@ public class OrderInfoController {
|
||||||
return R.success();
|
return R.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/t1")
|
||||||
|
public R<?> t1() {
|
||||||
|
orderInfoService.t1();
|
||||||
|
return R.success();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 过磅单
|
* 过磅单
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.njzscloud.supervisory.order.mapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
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.entity.OrderInfoEntity;
|
||||||
import com.njzscloud.supervisory.order.pojo.result.OrderPagingResult;
|
import com.njzscloud.supervisory.order.pojo.result.OrderPagingResult;
|
||||||
import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult;
|
import com.njzscloud.supervisory.order.pojo.result.PaymentContextResult;
|
||||||
|
|
@ -30,4 +31,6 @@ public interface OrderInfoMapper extends BaseMapper<OrderInfoEntity> {
|
||||||
void busyTruck(@Param("truckId") Long truckId, @Param("busy") Boolean busy);
|
void busyTruck(@Param("truckId") Long truckId, @Param("busy") Boolean busy);
|
||||||
|
|
||||||
PaymentContextResult paymentContext(@Param("orderId") Long orderId);
|
PaymentContextResult paymentContext(@Param("orderId") Long orderId);
|
||||||
|
|
||||||
|
BizTruckEntity getTruckInfo(@Param("truckId") Long truckId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.img.ImgUtil;
|
import cn.hutool.core.img.ImgUtil;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.extra.qrcode.QrCodeUtil;
|
import cn.hutool.extra.qrcode.QrCodeUtil;
|
||||||
import cn.hutool.extra.qrcode.QrConfig;
|
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.core.ex.Exceptions;
|
||||||
import com.njzscloud.common.mp.support.PageParam;
|
import com.njzscloud.common.mp.support.PageParam;
|
||||||
import com.njzscloud.common.mp.support.PageResult;
|
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.support.UserDetail;
|
||||||
import com.njzscloud.common.security.util.SecurityUtil;
|
import com.njzscloud.common.security.util.SecurityUtil;
|
||||||
import com.njzscloud.common.sn.support.SnUtil;
|
import com.njzscloud.common.sn.support.SnUtil;
|
||||||
import com.njzscloud.supervisory.biz.constant.AuditStatus;
|
import com.njzscloud.supervisory.biz.constant.AuditStatus;
|
||||||
import com.njzscloud.supervisory.biz.constant.BizObj;
|
import com.njzscloud.supervisory.biz.constant.BizObj;
|
||||||
import com.njzscloud.supervisory.biz.pojo.entity.BizAuditConfigEntity;
|
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.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.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.expense.service.ExpenseItemsConfigService;
|
||||||
|
import com.njzscloud.supervisory.goods.contant.MoneyStrategy;
|
||||||
import com.njzscloud.supervisory.order.contant.CheckStatus;
|
import com.njzscloud.supervisory.order.contant.CheckStatus;
|
||||||
import com.njzscloud.supervisory.order.contant.OrderCategory;
|
import com.njzscloud.supervisory.order.contant.OrderCategory;
|
||||||
import com.njzscloud.supervisory.order.contant.OrderStatus;
|
import com.njzscloud.supervisory.order.contant.OrderStatus;
|
||||||
import com.njzscloud.supervisory.order.contant.OrderViewType;
|
import com.njzscloud.supervisory.order.contant.OrderViewType;
|
||||||
import com.njzscloud.supervisory.order.mapper.OrderInfoMapper;
|
import com.njzscloud.supervisory.order.mapper.OrderInfoMapper;
|
||||||
import com.njzscloud.supervisory.order.pojo.entity.OrderCargoPlaceEntity;
|
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.OrderGoodsEntity;
|
||||||
import com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity;
|
import com.njzscloud.supervisory.order.pojo.entity.OrderInfoEntity;
|
||||||
import com.njzscloud.supervisory.order.pojo.param.*;
|
import com.njzscloud.supervisory.order.pojo.param.*;
|
||||||
import com.njzscloud.supervisory.order.pojo.result.OrderCertificateResult;
|
import com.njzscloud.supervisory.order.pojo.result.*;
|
||||||
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.sys.auth.pojo.result.MyResult;
|
import com.njzscloud.supervisory.sys.auth.pojo.result.MyResult;
|
||||||
|
import com.njzscloud.supervisory.sys.stationletter.constant.WarnCategory;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
@ -53,6 +58,7 @@ import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static com.njzscloud.supervisory.constant.Constant.CERTIFICATE_SN_CODE;
|
import static com.njzscloud.supervisory.constant.Constant.CERTIFICATE_SN_CODE;
|
||||||
|
|
@ -72,6 +78,8 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
private final OrderCarInOutService orderCarInOutService;
|
private final OrderCarInOutService orderCarInOutService;
|
||||||
private final OrderExpenseItemsService orderExpenseItemsService;
|
private final OrderExpenseItemsService orderExpenseItemsService;
|
||||||
private final ExpenseItemsConfigService expenseItemsConfigService;
|
private final ExpenseItemsConfigService expenseItemsConfigService;
|
||||||
|
private final TruckLocationTrackService truckLocationTrackService;
|
||||||
|
private final BizWarnService bizWarnService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增
|
* 新增
|
||||||
|
|
@ -602,13 +610,98 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
Assert.notNull(orderInfo, () -> Exceptions.clierr("订单不存在"));
|
Assert.notNull(orderInfo, () -> Exceptions.clierr("订单不存在"));
|
||||||
Assert.isTrue(orderInfo.getOrderStatus() == OrderStatus.YiJieDan, () -> Exceptions.clierr("当前订单状态,无法开始运输"));
|
Assert.isTrue(orderInfo.getOrderStatus() == OrderStatus.YiJieDan, () -> Exceptions.clierr("当前订单状态,无法开始运输"));
|
||||||
Assert.isTrue(orderInfo.getAuditStatus() == AuditStatus.TongGuo, () -> Exceptions.clierr("当前订单未审核完成,无法开始运输"));
|
Assert.isTrue(orderInfo.getAuditStatus() == AuditStatus.TongGuo, () -> Exceptions.clierr("当前订单未审核完成,无法开始运输"));
|
||||||
|
Long orderInfoId = orderInfo.getId();
|
||||||
this.updateById(new OrderInfoEntity()
|
this.updateById(new OrderInfoEntity()
|
||||||
.setId(orderInfo.getId())
|
.setId(orderInfoId)
|
||||||
.setCargoPhoto(startTransportOrderParam.getCargoPhoto())
|
.setCargoPhoto(startTransportOrderParam.getCargoPhoto())
|
||||||
.setOrderStatus(OrderStatus.QingYunZhong)
|
.setOrderStatus(OrderStatus.QingYunZhong)
|
||||||
.setTransTime(LocalDateTime.now())
|
.setTransTime(LocalDateTime.now())
|
||||||
);
|
);
|
||||||
// TODO 获取 GPS
|
// 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) {
|
public void cancel(Long orderId) {
|
||||||
|
|
@ -623,7 +716,22 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
.setId(orderInfo.getId())
|
.setId(orderInfo.getId())
|
||||||
.setOrderStatus(OrderStatus.YiQuXiao)
|
.setOrderStatus(OrderStatus.YiQuXiao)
|
||||||
);
|
);
|
||||||
// TODO 关闭 GPS
|
// TODO 关闭 GPS 定位
|
||||||
|
Long truckId = orderInfo.getTruckId();
|
||||||
|
BizTruckEntity truckInfo = baseMapper.getTruckInfo(truckId);
|
||||||
|
String gpsId = truckInfo.getGps();
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
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)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
|
@ -705,7 +813,20 @@ public class OrderInfoService extends ServiceImpl<OrderInfoMapper, OrderInfoEnti
|
||||||
// 3) 合并集合,计算 total_money 与 settle_money,并批量落库。
|
// 3) 合并集合,计算 total_money 与 settle_money,并批量落库。
|
||||||
settleForTransCompany(orderInfoEntity, settleWeight);
|
settleForTransCompany(orderInfoEntity, settleWeight);
|
||||||
// TODO 关闭 GPS
|
// TODO 关闭 GPS
|
||||||
|
BizTruckEntity truckInfo = baseMapper.getTruckInfo(truckId);
|
||||||
|
String gpsId = truckInfo.getGps();
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,6 @@ public class UserService extends ServiceImpl<UserMapper, UserEntity> implements
|
||||||
*/
|
*/
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public Long add(AddUserParam addUserParam) {
|
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();
|
AddUserAccountParam addUserAccountParam = addUserParam.getAccount();
|
||||||
|
|
||||||
UserEntity userEntity = BeanUtil.copyProperties(addUserParam, UserEntity.class);
|
UserEntity userEntity = BeanUtil.copyProperties(addUserParam, UserEntity.class);
|
||||||
|
|
@ -186,6 +182,7 @@ public class UserService extends ServiceImpl<UserMapper, UserEntity> implements
|
||||||
addUserParam.setAccount(BeanUtil.copyProperties(account, AddUserAccountParam.class));
|
addUserParam.setAccount(BeanUtil.copyProperties(account, AddUserAccountParam.class));
|
||||||
Long userId = this.add(addUserParam);
|
Long userId = this.add(addUserParam);
|
||||||
|
|
||||||
|
if (bizObj == BizObj.GeRen) return;
|
||||||
UserRegisterParam.Company company = userRegisterParam.getCompany();
|
UserRegisterParam.Company company = userRegisterParam.getCompany();
|
||||||
Assert.notNull(company, "公司信息不能为空");
|
Assert.notNull(company, "公司信息不能为空");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ spring:
|
||||||
- /district/default_place
|
- /district/default_place
|
||||||
- /bulletin/detail
|
- /bulletin/detail
|
||||||
- /bulletin/paging
|
- /bulletin/paging
|
||||||
|
- /truck_location_track/**
|
||||||
app:
|
app:
|
||||||
default-place:
|
default-place:
|
||||||
province: 320000
|
province: 320000
|
||||||
|
|
@ -70,3 +71,16 @@ wechat:
|
||||||
private-key-path: classpath:cert/apiclient_key.pem
|
private-key-path: classpath:cert/apiclient_key.pem
|
||||||
# 支付回调地址
|
# 支付回调地址
|
||||||
notify-url: https://your-domain.com/api/payment/wechat/callback
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
server:
|
server:
|
||||||
port: ${APP_PORT:8808}
|
port: ${APP_PORT:10086}
|
||||||
tomcat:
|
tomcat:
|
||||||
max-http-form-post-size: 20MB
|
max-http-form-post-size: 20MB
|
||||||
spring:
|
spring:
|
||||||
|
|
|
||||||
|
|
@ -203,4 +203,9 @@
|
||||||
LEFT JOIN money_account ma1 ON ma1.user_id = bc.user_id
|
LEFT JOIN money_account ma1 ON ma1.user_id = bc.user_id
|
||||||
WHERE a.id = #{orderId}
|
WHERE a.id = #{orderId}
|
||||||
</select>
|
</select>
|
||||||
|
<select id="getTruckInfo" resultType="com.njzscloud.supervisory.biz.pojo.entity.BizTruckEntity">
|
||||||
|
SELECT *
|
||||||
|
FROM biz_truck
|
||||||
|
WHERE id = #{truckId}
|
||||||
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|
|
||||||
5
pom.xml
5
pom.xml
|
|
@ -62,6 +62,11 @@
|
||||||
<artifactId>njzscloud-common-wechat</artifactId>
|
<artifactId>njzscloud-common-wechat</artifactId>
|
||||||
<version>0.0.1</version>
|
<version>0.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njzscloud</groupId>
|
||||||
|
<artifactId>njzscloud-common-localizer</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.njzscloud</groupId>
|
<groupId>com.njzscloud</groupId>
|
||||||
<artifactId>njzscloud-common-http</artifactId>
|
<artifactId>njzscloud-common-http</artifactId>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"avatar": "data:image/png;base64,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",
|
"avatar": "data:image/png;base64,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",
|
||||||
"version": "4.9.4",
|
"version": "4.9.4",
|
||||||
"createdTime": "2023-4-13 11:53:52",
|
"createdTime": "2023-4-13 11:53:52",
|
||||||
"updatedTime": "2025-9-25 18:09:39",
|
"updatedTime": "2025-9-28 13:34:44",
|
||||||
"dbConns": [],
|
"dbConns": [],
|
||||||
"profile": {
|
"profile": {
|
||||||
"default": {
|
"default": {
|
||||||
|
|
@ -18141,6 +18141,683 @@
|
||||||
"correlations": [],
|
"correlations": [],
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"type": "P"
|
"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": [],
|
"views": [],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue