localizer
lzq 2025-09-28 19:12:29 +08:00
parent ee4247503a
commit dbc31ab6e6
68 changed files with 3752 additions and 46 deletions

View File

@ -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+", "");
}
}

View File

@ -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);
}
}

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,5 @@
package com.njzscloud.common.localizer.jt808.message;
public interface ServerMessageBody {
byte[] toBytes();
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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/123
*/
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 "未知";
}
}
}

View File

@ -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;
}

View File

@ -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 4152006 5.4.12 ,,
* 0
*/
private byte plateColor;
/**
*
* (: 0 , VIN )
*/
private String plate;
public TerminalRegisterMessage(byte[] bytes) {
ByteBuf body = Unpooled.wrappedBuffer(bytes);
// 读取省域IDWORD2字节大端序
this.province = body.readUnsignedShort();
// 读取市县域IDWORD2字节大端序
this.city = body.readUnsignedShort();
// 读取制造商IDBYTE[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();
// 读取终端IDBYTE[7]7字节转为字符串
byte[] terminalIdBytes = new byte[7];
body.readBytes(terminalIdBytes);
this.terminalId = new String(terminalIdBytes).trim();
// 读取车牌颜色BYTE1字节
this.plateColor = body.readByte();
// 读取车牌STRING剩余字节转为字符串
// 假设剩余所有字节都是车牌内容,实际可根据协议确定长度
byte[] plateBytes = new byte[body.readableBytes()];
body.readBytes(plateBytes);
this.plate = new String(plateBytes, CharsetUtil.CHARSET_GBK);
body.release();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
/**
*
* JT8080x7e <-> 0x7d 0x010x7d <-> 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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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 {
// 创建两个EventLoopGroupbossGroup用于接收客户端连接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("服务器已停止");
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 0ACC 1ACC
* 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;
}

View File

@ -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-36000
*/
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);
}
}

View File

@ -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);
}
}
}

View File

@ -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 0ACC 1ACC
* 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;
}
}

View File

@ -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 = ValidN No Valid
* 2. 0 = 04
* 3. / 0 = 0 = 1=GPS2 =
*/
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-312212-20 21-31 0
* -78dBmCSQ dBm dBm = -113 + (CSQ×2)22 - 113+44=-69dBm 0
*/
private int csq; // 信号强度
// endregion
// region 硬件与电源状态
/**
* V3.5V 3.3V-4.2V 3.3V
*/
private double batteryVoltage; // 电池电压(V)
/**
* /
* 1= 2= 0=
* 2=
*/
private int powerMode; // 供电模式(1=外接电源,2=电池)
/**
* BD = BeiDou
* 0 = / 10使
*/
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表示解析失败
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration = \
com.njzscloud.common.localizer.config.LocalizerAutoConfiguration

View File

@ -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);
} }
} }

View File

@ -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());
} }
} }
} }

View File

@ -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;

View File

@ -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);
} }
} }

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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> {
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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) {

View File

@ -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());

View File

@ -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> {
}

View File

@ -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;
}

View File

@ -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 = ValidN No Valid
* 2. 0 = 04
* 3. / 0 = 0 = 1=GPS2 =
*/
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-312212-20 21-31 0
* -78dBmCSQ dBm dBm = -113 + (CSQ×2)22 - 113+44=-69dBm 0
*/
private int csq; // 信号强度
// endregion
// region 硬件与电源状态
/**
* V3.5V 3.3V-4.2V 3.3V
*/
private double batteryVoltage; // 电池电压(V)
/**
* /
* 1= 2= 0=
* 2=
*/
private int powerMode; // 供电模式(1=外接电源,2=电池)
/**
* BD = BeiDou
* 0 = / 10使
*/
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
}

View File

@ -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;
}

View File

@ -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()));
}
}

View File

@ -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();
}
/** /**
* *
* *

View File

@ -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);
} }

View File

@ -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 0ACC 1ACC
* 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;
}

View File

@ -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);
}
});
} }
/** /**

View File

@ -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, "公司信息不能为空");

View File

@ -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

View File

@ -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:

View File

@ -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>

View File

@ -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>

View File

@ -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": [],