#init
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package com.maibu.netty;
|
||||
|
||||
import com.fastbee.memory.MiddleGlobalMemory;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.VertxOptions;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 系统初始化资源类
|
||||
* 实现CommandLineRunner接口,在Spring Boot启动时自动执行
|
||||
*/
|
||||
@Getter
|
||||
@Component
|
||||
public class InitResource implements CommandLineRunner {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InitResource.class);
|
||||
|
||||
@Override
|
||||
public void run(String... args) throws Exception {
|
||||
logger.info("InitResource 开始初始化系统资源...");
|
||||
|
||||
// 初始化Vert.x
|
||||
initVertx();
|
||||
logger.info("InitResource 系统资源初始化完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Vert.x
|
||||
*/
|
||||
private void initVertx() {
|
||||
try {
|
||||
VertxOptions options = new VertxOptions()
|
||||
.setWorkerPoolSize(20)
|
||||
.setEventLoopPoolSize(10)
|
||||
.setWarningExceptionTime(5000000000L);
|
||||
|
||||
MiddleGlobalMemory.vertx = Vertx.vertx(options);
|
||||
logger.info("Vert.x 初始化成功");
|
||||
} catch (Exception e) {
|
||||
logger.error("Vert.x 初始化失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package com.maibu.netty;
|
||||
|
||||
import com.fastbee.common.MiddleConstant;
|
||||
import com.fastbee.netty.handler.*;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.timeout.IdleStateHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Netty客户端实现类
|
||||
* 提供与TCP服务器的连接管理、消息发送、重连机制和心跳检测功能
|
||||
*/
|
||||
public class NettyClient {
|
||||
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
|
||||
|
||||
// private static volatile NettyClient instance;
|
||||
|
||||
public String token;
|
||||
public String userName;
|
||||
|
||||
public String requestDeviceId; //前端需要连接的设备
|
||||
public String currentDeviceId; //当前连接的设备
|
||||
|
||||
public Long heartBeatTimer;
|
||||
|
||||
public String key; //userName + web + token
|
||||
|
||||
public String host;
|
||||
public int port;
|
||||
public int reconnectDelay = 5;
|
||||
public int heartbeatInterval = 5;
|
||||
|
||||
public EventLoopGroup group;
|
||||
public Bootstrap bootstrap;
|
||||
public Channel channel;
|
||||
|
||||
public Long lastSwitchTime = -1L;
|
||||
|
||||
public Long Interval = 5000L;
|
||||
|
||||
public static final byte[] HEARTBEAT_PACKET = new byte[]{(byte) 0xAB, (byte) 0xAA, (byte) 0xFF, (byte) 0xAA, (byte) 0xAB};
|
||||
|
||||
|
||||
// /**
|
||||
// * 获取单例实例(无回调)
|
||||
// */
|
||||
// public static NettyClient getInstance() {
|
||||
// if (instance == null) {
|
||||
// synchronized (NettyClient.class) {
|
||||
// if (instance == null) {
|
||||
// instance = new NettyClient();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return instance;
|
||||
// }
|
||||
|
||||
/**
|
||||
* 初始化并连接到服务器
|
||||
*/
|
||||
public void initConnect(String host, int port) {
|
||||
if (channel != null && channel.isActive()) {
|
||||
logger.info("Netty客户端已连接,不重复初始化");
|
||||
return;
|
||||
}
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Netty客户端
|
||||
*/
|
||||
public void initClient() {
|
||||
group = new NioEventLoopGroup();
|
||||
bootstrap = new Bootstrap();
|
||||
bootstrap.group(group)
|
||||
.channel(NioSocketChannel.class)
|
||||
.option(ChannelOption.SO_KEEPALIVE, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||
.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
// 设置共享属性
|
||||
ch.attr(MiddleConstant.ATT_MASTER_KEY).set(key);
|
||||
// 空闲检测处理器(心跳)
|
||||
pipeline.addLast(new IdleStateHandler(0, heartbeatInterval, 0, TimeUnit.SECONDS));
|
||||
pipeline.addLast(new MiddleHeaderFooterDecoder());
|
||||
// 编解码器
|
||||
pipeline.addLast(new MiddleHexDecoder());
|
||||
pipeline.addLast(new MiddleHexEncoder());
|
||||
// 自定义处理器
|
||||
pipeline.addLast(new ClientHandler());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接到服务器
|
||||
*/
|
||||
public void connect() {
|
||||
|
||||
if (bootstrap == null || host == null || port <= 0) {
|
||||
logger.error("Netty客户端未正确初始化");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("正在连接到Netty服务器: {}:{}", host, port);
|
||||
bootstrap.connect(host, port).addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
channel = future.channel();
|
||||
logger.info("Netty客户端连接成功: {}:{}", host, port);
|
||||
// notifyConnectionStatus(true);
|
||||
} else {
|
||||
logger.error("Netty客户端连接失败,{}秒后重试: {}", reconnectDelay, future.cause().getMessage());
|
||||
// notifyConnectionStatus(false);
|
||||
// 重连
|
||||
group.schedule(this::connect, reconnectDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
public void disconnect() {
|
||||
if (channel != null) {
|
||||
channel.close();
|
||||
channel = null;
|
||||
}
|
||||
// notifyConnectionStatus(false);
|
||||
logger.info("Netty客户端已断开连接");
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据
|
||||
*/
|
||||
public void sendData(byte[] data) {
|
||||
if (channel != null && channel.isActive()) {
|
||||
channel.writeAndFlush(data).addListener((ChannelFutureListener) future -> {
|
||||
if (future.isSuccess()) {
|
||||
logger.debug("Netty客户端发送数据成功: {}", bytesToHex(data));
|
||||
} else {
|
||||
logger.error("Netty客户端发送数据失败: {}", future.cause().getMessage());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.error("Netty客户端未连接,发送数据失败");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送十六进制字符串数据
|
||||
*/
|
||||
public void sendHexData(String hexData) {
|
||||
try {
|
||||
byte[] data = hexStringToBytes(hexData);
|
||||
sendData(data);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("无效的十六进制字符串: {}", hexData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 十六进制字符串转字节数组
|
||||
*/
|
||||
private static byte[] hexStringToBytes(String hexString) {
|
||||
hexString = hexString.replaceAll("\\s+", "");
|
||||
if (hexString.length() % 2 != 0) {
|
||||
throw new IllegalArgumentException("十六进制字符串长度必须为偶数");
|
||||
}
|
||||
byte[] result = new byte[hexString.length() / 2];
|
||||
for (int i = 0; i < hexString.length(); i += 2) {
|
||||
result[i / 2] = (byte) Integer.parseInt(hexString.substring(i, i + 2), 16);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 字节数组转十六进制字符串
|
||||
*/
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端是否已连接
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return channel != null && channel.isActive();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置重连延迟时间(秒)
|
||||
*/
|
||||
public void setReconnectDelay(int reconnectDelay) {
|
||||
if (reconnectDelay > 0) {
|
||||
this.reconnectDelay = reconnectDelay;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置心跳间隔时间(秒)
|
||||
*/
|
||||
public void setHeartbeatInterval(int heartbeatInterval) {
|
||||
if (heartbeatInterval > 0) {
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
package com.maibu.netty.handler;
|
||||
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.fastbee.common.MiddleCommandConstant;
|
||||
import com.fastbee.common.MiddleConstant;
|
||||
import com.fastbee.common.utils.json.JsonUtils;
|
||||
import com.fastbee.common.utils.spring.SpringUtils;
|
||||
import com.fastbee.dto.*;
|
||||
import com.fastbee.memory.MiddleGlobalMemory;
|
||||
import com.fastbee.netty.NettyClient;
|
||||
import com.fastbee.utils.HttpService;
|
||||
import com.fastbee.websocket.WebsocketHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.java_websocket.WebSocket;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Netty客户端处理器
|
||||
*/
|
||||
@Slf4j
|
||||
public class ClientHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);
|
||||
|
||||
private static final HttpService httpService = SpringUtils.getBean(HttpService.class);
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
try {
|
||||
byte[] data = (byte[]) msg;
|
||||
logger.debug("channelRead2: {}", Arrays.toString(data));
|
||||
//假如是心跳包
|
||||
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
|
||||
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(key);
|
||||
if (isHeartbeat(data)) {
|
||||
//websocket 不在了停止发 心跳
|
||||
if (webSocket != null && webSocket.isOpen()) {
|
||||
ctx.writeAndFlush(data);
|
||||
}
|
||||
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
|
||||
if (nettyClient != null && StringUtils.isEmpty(nettyClient.currentDeviceId)) {
|
||||
if (nettyClient.lastSwitchTime == -1 || System.currentTimeMillis() - nettyClient.lastSwitchTime > nettyClient.Interval) {
|
||||
if (!StringUtils.isEmpty(nettyClient.requestDeviceId) && !StringUtils.isEmpty(key)) {
|
||||
switchDevice(nettyClient, key);
|
||||
nettyClient.lastSwitchTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isPushMsg(data)) {
|
||||
try {
|
||||
int dataStart = 3; // 跳过 0:AB 1:AA 2:cmd
|
||||
|
||||
// 数据体结束位置(不含CRC和帧尾)
|
||||
int dataEnd = data.length - 4; // 去掉 CRC_L、CRC_H、AA、AB
|
||||
|
||||
int dataLen = dataEnd - dataStart;
|
||||
if (dataLen <= 0) {
|
||||
System.out.println("数据体长度异常");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] jsonBytes = Arrays.copyOfRange(data, dataStart, dataEnd);
|
||||
String json = new String(jsonBytes, StandardCharsets.UTF_8);
|
||||
|
||||
logger.debug("收到推送2: {}", json);
|
||||
if (StringUtils.isNotBlank(json)) {
|
||||
JSONObject obj = new JSONObject(json);
|
||||
if (obj.get("event") != null) {
|
||||
//状态变化 上下线
|
||||
String eventType = obj.get("event").toString();
|
||||
String deviceId = obj.get("deviceId").toString();
|
||||
if ("device_status_changed".equals(eventType)) {
|
||||
String status = obj.get("status").toString();
|
||||
WebOlineStatusMessageDTO dto = new WebOlineStatusMessageDTO();
|
||||
dto.setDeviceId(deviceId);
|
||||
dto.setType(MesType.device_status_changed);
|
||||
if (status.equals("offline")) {
|
||||
dto.setOnlineStatus(0);
|
||||
} else {
|
||||
dto.setOnlineStatus(1);
|
||||
}
|
||||
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
|
||||
} else if ("device_error_push".equals(eventType)) {
|
||||
//todo 更新推送的错误
|
||||
String details = obj.get("details").toString();
|
||||
WebOlineStatusMessageDTO dto = new WebOlineStatusMessageDTO();
|
||||
dto.setDeviceId(deviceId);
|
||||
dto.setType(MesType.device_error_push);
|
||||
dto.setHasError(1);
|
||||
dto.setDescription("");
|
||||
dto.setErrorCode("");
|
||||
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
|
||||
}
|
||||
} else {
|
||||
/* 判断 respond 字段 */
|
||||
if (obj.get("request") != null) {
|
||||
String deviceId = obj.get("deviceId").toString();
|
||||
String userName = obj.get("userId").toString();
|
||||
String platform = obj.get("platform").toString();
|
||||
//直接转发 websocket 申请
|
||||
WebAuthResponseDTO request = new WebAuthResponseDTO();
|
||||
request.setType(MesType.switchPermission);
|
||||
request.setUserName(userName);
|
||||
request.setDeviceId(deviceId);
|
||||
request.setPlatform(platform);
|
||||
logger.debug("json: {}", JsonUtils.toJsonString(request));
|
||||
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(request));
|
||||
} else {
|
||||
if (obj.get("respond") != null) {
|
||||
if ("have_logged_in".equals(obj.get("respond").toString())) {
|
||||
/* 如果回复 have_logged_in 则调用logout */
|
||||
//System.out.println("检测到重复登录,执行 logout");
|
||||
//NettyClient nettyClient = GlobalMemory.nettyClientMap.get(key);
|
||||
//WebHasLoginDTO dto = new WebHasLoginDTO();
|
||||
//dto.setToken(nettyClient.token);
|
||||
//dto.setUserName(nettyClient.userName);
|
||||
//dto.setType(MesType.have_logged_in);
|
||||
//WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
|
||||
} else {
|
||||
//todo respond netty_server返回结果处理
|
||||
DeviceRespondDTO respond = JsonUtils.parseObject(obj.get("respond").toString(), DeviceRespondDTO.class);
|
||||
if (respond != null) {
|
||||
//直接转发 websocket 申请
|
||||
WebSwitchControlResponseDTO responseDTO = new WebSwitchControlResponseDTO();
|
||||
responseDTO.setType(MesType.switchResult);
|
||||
responseDTO.setRespond(respond);
|
||||
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(responseDTO));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (obj.get("type").toString() != null && "detectionReport".equals(obj.get("type").toString())) {
|
||||
WebsocketHandler.sendMessageToClient(webSocket, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//error 推送
|
||||
}
|
||||
|
||||
//TODO web 无切换控制请求
|
||||
} catch (Exception e) {
|
||||
logger.error("事件推送解析失败:{}", e.getMessage());
|
||||
}
|
||||
} else if (isStatusDate(data)) {
|
||||
//状态消息推送给前端
|
||||
WebStatusMessageDTO dto = createWebDeviceStatusMessage(data);
|
||||
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
|
||||
} else if (isPath(data)) {
|
||||
//已到达
|
||||
if (data[5] == (byte) 0x01) {
|
||||
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("channelRead error:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public WebStatusMessageDTO createWebDeviceStatusMessage(byte[] data) {
|
||||
WebStatusMessageDTO webStatusMessageDTO = new WebStatusMessageDTO();
|
||||
if (data[2] == MiddleCommandConstant.status) {
|
||||
String deviceStatus = new String(data, 2, data.length - 4, StandardCharsets.UTF_8).trim();
|
||||
String[] split = deviceStatus.split(",");
|
||||
List<DeviceStatusDetail> transferData = transferStatusData(split);
|
||||
webStatusMessageDTO.setData(transferData);
|
||||
webStatusMessageDTO.setType(MesType.deviceInfo);
|
||||
}
|
||||
return webStatusMessageDTO;
|
||||
}
|
||||
|
||||
private List<DeviceStatusDetail> transferStatusData(String[] split) {
|
||||
List<DeviceStatusDetail> transferData = new ArrayList<>();
|
||||
|
||||
DeviceStatusDetail detail0 = new DeviceStatusDetail();
|
||||
detail0.setName("voltage");
|
||||
detail0.setValue(split[0]);
|
||||
detail0.setUnit("");
|
||||
transferData.add(detail0);
|
||||
|
||||
DeviceStatusDetail detail1 = new DeviceStatusDetail();
|
||||
detail1.setName("leftTargetSpeed");
|
||||
detail1.setValue(split[1]);
|
||||
detail1.setUnit("");
|
||||
transferData.add(detail1);
|
||||
|
||||
DeviceStatusDetail detail2 = new DeviceStatusDetail();
|
||||
detail2.setName("rightTargetSpeed");
|
||||
detail2.setValue(split[2]);
|
||||
detail2.setUnit("");
|
||||
transferData.add(detail2);
|
||||
|
||||
DeviceStatusDetail detail3 = new DeviceStatusDetail();
|
||||
detail3.setName("leftMeasureSpeed");
|
||||
detail3.setValue(split[3]);
|
||||
detail3.setUnit("");
|
||||
transferData.add(detail3);
|
||||
|
||||
DeviceStatusDetail detail4 = new DeviceStatusDetail();
|
||||
detail4.setName("rightMeasureSpeed");
|
||||
detail4.setValue(split[4]);
|
||||
detail4.setUnit("");
|
||||
transferData.add(detail4);
|
||||
|
||||
DeviceStatusDetail detail5 = new DeviceStatusDetail();
|
||||
detail5.setName("leftCurrent");
|
||||
detail5.setValue(split[5]);
|
||||
detail5.setUnit("");
|
||||
transferData.add(detail5);
|
||||
|
||||
DeviceStatusDetail detail6 = new DeviceStatusDetail();
|
||||
detail6.setName("rightCurrent");
|
||||
detail6.setValue(split[6]);
|
||||
detail6.setUnit("");
|
||||
transferData.add(detail6);
|
||||
|
||||
DeviceStatusDetail detail7 = new DeviceStatusDetail();
|
||||
detail7.setName("leftMotorTemp");
|
||||
detail7.setValue(split[7]);
|
||||
detail7.setUnit("");
|
||||
transferData.add(detail7);
|
||||
|
||||
DeviceStatusDetail detail8 = new DeviceStatusDetail();
|
||||
detail8.setName("rightMotorTemp");
|
||||
detail8.setValue(split[8]);
|
||||
detail8.setUnit("");
|
||||
transferData.add(detail8);
|
||||
|
||||
DeviceStatusDetail detail9 = new DeviceStatusDetail();
|
||||
detail9.setName("chipTemp");
|
||||
detail9.setValue(split[9]);
|
||||
detail9.setUnit("");
|
||||
transferData.add(detail9);
|
||||
|
||||
DeviceStatusDetail detail10 = new DeviceStatusDetail();
|
||||
detail10.setName("yaw");
|
||||
detail10.setValue(split[10]);
|
||||
detail10.setUnit("");
|
||||
transferData.add(detail10);
|
||||
|
||||
DeviceStatusDetail detail11 = new DeviceStatusDetail();
|
||||
detail11.setName("pitch");
|
||||
detail11.setValue(split[11]);
|
||||
detail11.setUnit("");
|
||||
transferData.add(detail11);
|
||||
|
||||
DeviceStatusDetail detail12 = new DeviceStatusDetail();
|
||||
detail12.setName("roll");
|
||||
detail12.setValue(split[12]);
|
||||
detail12.setUnit("");
|
||||
transferData.add(detail12);
|
||||
|
||||
DeviceStatusDetail detail13 = new DeviceStatusDetail();
|
||||
detail13.setName("satelliteCnt");
|
||||
detail13.setValue(split[13]);
|
||||
detail13.setUnit("");
|
||||
transferData.add(detail13);
|
||||
|
||||
DeviceStatusDetail detail14 = new DeviceStatusDetail();
|
||||
detail14.setName("headingStatus");
|
||||
detail14.setValue(split[14]);
|
||||
detail14.setUnit("");
|
||||
transferData.add(detail14);
|
||||
|
||||
DeviceStatusDetail detail15 = new DeviceStatusDetail();
|
||||
detail15.setName("headingStatus");
|
||||
detail15.setValue(split[15]);
|
||||
detail15.setUnit("");
|
||||
transferData.add(detail15);
|
||||
|
||||
DeviceStatusDetail detail16 = new DeviceStatusDetail();
|
||||
detail16.setName("latitude");
|
||||
detail16.setValue(split[16]);
|
||||
detail16.setUnit("");
|
||||
transferData.add(detail16);
|
||||
|
||||
DeviceStatusDetail detail17 = new DeviceStatusDetail();
|
||||
detail17.setName("longitude");
|
||||
detail17.setValue(split[17]);
|
||||
detail17.setUnit("");
|
||||
transferData.add(detail17);
|
||||
|
||||
//18 时间暂未给值
|
||||
|
||||
DeviceStatusDetail detail19 = new DeviceStatusDetail();
|
||||
detail19.setName("cuttingSpeed");
|
||||
detail19.setValue(split[19]);
|
||||
detail19.setUnit("");
|
||||
transferData.add(detail19);
|
||||
|
||||
DeviceStatusDetail detail20 = new DeviceStatusDetail();
|
||||
detail20.setName("controlMode");
|
||||
detail20.setValue(split[20]);
|
||||
detail20.setUnit("");
|
||||
transferData.add(detail20);
|
||||
|
||||
DeviceStatusDetail detail21 = new DeviceStatusDetail();
|
||||
detail21.setName("battery");
|
||||
detail21.setValue(split[21]);
|
||||
detail21.setUnit("");
|
||||
transferData.add(detail21);
|
||||
|
||||
DeviceStatusDetail detail22 = new DeviceStatusDetail();
|
||||
detail22.setName("workingArea");
|
||||
detail22.setValue(split[22]);
|
||||
detail22.setUnit("");
|
||||
transferData.add(detail22);
|
||||
|
||||
DeviceStatusDetail detail23 = new DeviceStatusDetail();
|
||||
detail23.setName("obstacleSign");
|
||||
detail23.setValue(split[23]);
|
||||
detail23.setUnit("");
|
||||
transferData.add(detail23);
|
||||
|
||||
return transferData;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
|
||||
logger.debug("连接开始channelActive:ChannelId:{} ", ctx.channel().id().asLongText());
|
||||
|
||||
//websocket连上后连接netty成功后发送登录认证包
|
||||
// key username + : web + token
|
||||
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
|
||||
System.out.println(key);
|
||||
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
|
||||
if (nettyClient != null) {
|
||||
// 构造并发送认证包
|
||||
byte[] deviceNameBytes = key.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] packet = new byte[3 + deviceNameBytes.length + 4];
|
||||
packet[0] = (byte) 0xAB;
|
||||
packet[1] = (byte) 0xAA;
|
||||
packet[2] = 0x03;
|
||||
System.arraycopy(deviceNameBytes, 0, packet, 3, deviceNameBytes.length);
|
||||
int tailStart = 3 + deviceNameBytes.length;
|
||||
packet[tailStart] = 0x00;
|
||||
packet[tailStart + 1] = 0x00;
|
||||
packet[tailStart + 2] = (byte) 0xAA;
|
||||
packet[tailStart + 3] = (byte) 0xAB;
|
||||
|
||||
ctx.writeAndFlush(packet);
|
||||
// startHeartbeat(nettyClient, ctx, key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void switchDevice(NettyClient nettyClient, String key) {
|
||||
try {
|
||||
// 2. 调用 POST 接口:传入 Token B(与 Token A 不同)
|
||||
String token = nettyClient.token;
|
||||
String postUrl = MiddleConstant.nettyApiUrl + "/forward/device/switchDevice";
|
||||
NettySwitchDeviceDTO dto = new NettySwitchDeviceDTO();
|
||||
dto.setDeviceId(nettyClient.requestDeviceId);
|
||||
dto.setPlatform("web");
|
||||
String result = httpService.doPost(postUrl, token, dto);
|
||||
ResultDTO resultDTO = JsonUtils.parseObject(result, ResultDTO.class);
|
||||
if (resultDTO != null && resultDTO.getData() != null) {
|
||||
if ((boolean) resultDTO.getData()) {
|
||||
logger.debug("用户:{}切换设备:{}成功", key, nettyClient.requestDeviceId);
|
||||
//切换成功推送 websocket
|
||||
nettyClient.currentDeviceId = nettyClient.requestDeviceId;
|
||||
// nettyClient.requestDeviceId = null;
|
||||
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(key);
|
||||
if (webSocket != null && webSocket.isOpen()) {
|
||||
WebAuthResponseDTO responseDTO = new WebAuthResponseDTO();
|
||||
responseDTO.setDeviceId(nettyClient.currentDeviceId);
|
||||
responseDTO.setUserName(nettyClient.userName);
|
||||
responseDTO.setType(MesType.AUTH);
|
||||
responseDTO.setIsAuth(true);
|
||||
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(responseDTO));
|
||||
}
|
||||
} else {
|
||||
logger.debug("用户:{}切换设备:{}失败", key, nettyClient.requestDeviceId);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("切换设备,switchDevice发生错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 启动心跳检测
|
||||
*/
|
||||
private void startHeartbeat(NettyClient nettyClient, ChannelHandlerContext ctx, String key) {
|
||||
stopHeartbeat(nettyClient);
|
||||
MiddleGlobalMemory.vertx.setPeriodic(200, timerId -> {
|
||||
try {
|
||||
nettyClient.heartBeatTimer = timerId;
|
||||
if (ctx.channel() != null && ctx.channel().isActive()) {
|
||||
ctx.channel().writeAndFlush(NettyClient.HEARTBEAT_PACKET);
|
||||
logger.debug("Netty客户端:{}发送心跳包", nettyClient.key);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("循环任务执行异常:" + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭心跳检测
|
||||
*/
|
||||
private void stopHeartbeat(NettyClient nettyClient) {
|
||||
if (nettyClient != null && nettyClient.heartBeatTimer != null) {
|
||||
MiddleGlobalMemory.vertx.cancelTimer(nettyClient.heartBeatTimer);
|
||||
nettyClient.heartBeatTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelInactive(ChannelHandlerContext ctx) {
|
||||
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
|
||||
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
|
||||
// stopHeartbeat(nettyClient);
|
||||
nettyClient.currentDeviceId = null;
|
||||
logger.debug("连接断开channelInactive,ChannelId:{} ", ctx.channel().id().asLongText());
|
||||
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(key);
|
||||
if (webSocket != null && webSocket.isOpen()) {
|
||||
ctx.channel().eventLoop().schedule(nettyClient::connect, nettyClient.reconnectDelay, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
|
||||
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
|
||||
// stopHeartbeat(nettyClient);
|
||||
cause.printStackTrace();
|
||||
logger.error("异常断开exceptionCaught:{}", cause.getMessage());
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
private boolean isHeartbeat(byte[] data) {
|
||||
return data.length == 5 &&
|
||||
data[0] == (byte) 0xAB &&
|
||||
data[1] == (byte) 0xAA &&
|
||||
data[2] == (byte) 0xFF &&
|
||||
data[3] == (byte) 0xAA &&
|
||||
data[4] == (byte) 0xAB;
|
||||
}
|
||||
|
||||
/// 服务器重新请求下位机连接指令。即重新发送连接码
|
||||
private boolean isReconnectCommand(byte[] data) {
|
||||
return data.length == 9 &&
|
||||
data[0] == (byte) 0xAB &&
|
||||
data[1] == (byte) 0xAA &&
|
||||
data[2] == (byte) 0x03 &&
|
||||
data[3] == (byte) 0x00 &&
|
||||
data[4] == (byte) 0x00 &&
|
||||
data[5] == (byte) 0x00 &&
|
||||
data[6] == (byte) 0x00 &&
|
||||
data[7] == (byte) 0xAA &&
|
||||
data[8] == (byte) 0xAB;
|
||||
}
|
||||
|
||||
private boolean isPushMsg(byte[] data) {
|
||||
return data[0] == (byte) 0xAB &&
|
||||
data[1] == (byte) 0xAA &&
|
||||
data[2] == (byte) 0x12 &&
|
||||
data[data.length - 2] == (byte) 0xAA &&
|
||||
data[data.length - 1] == (byte) 0xAB;
|
||||
|
||||
// // 只要是以 '{' 开头、以 '}' 结尾,就认为是事件 JSON
|
||||
// if (data == null || data.length < 2) return false;
|
||||
// return data[0] == '{' && data[data.length - 1] == '}';
|
||||
}
|
||||
|
||||
private boolean isStatusDate(byte[] data) {
|
||||
return data[0] == (byte) 0xAB &&
|
||||
data[1] == (byte) 0xAA &&
|
||||
data[2] == (byte) 0x02 &&
|
||||
data[data.length - 2] == (byte) 0xAA &&
|
||||
data[data.length - 1] == (byte) 0xAB;
|
||||
}
|
||||
|
||||
private boolean isPath(byte[] data) {
|
||||
return data[0] == (byte) 0xAB &&
|
||||
data[1] == (byte) 0xAA &&
|
||||
data[2] == (byte) 0x01 &&
|
||||
data[data.length - 2] == (byte) 0xAA &&
|
||||
data[data.length - 1] == (byte) 0xAB;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package com.maibu.netty.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import org.apache.commons.codec.binary.Hex;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MiddleHeaderFooterDecoder extends ByteToMessageDecoder {
|
||||
private static final byte[] HEADER = {(byte) 0xAB, (byte) 0xAA};
|
||||
private static final byte[] FOOTER = {(byte) 0xAA, (byte) 0xAB};
|
||||
private static final int MAX_FRAME_LENGTH = 500;
|
||||
private static final boolean DEBUG = true; // 可控制是否打印日志
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
if (DEBUG) {
|
||||
byte[] raw = new byte[in.readableBytes()];
|
||||
in.getBytes(in.readerIndex(), raw);
|
||||
System.out.println("收到原始数据:" + Hex.encodeHexString(raw).toUpperCase());
|
||||
}
|
||||
|
||||
int readerIndex = in.readerIndex();
|
||||
|
||||
while (readerIndex <= in.writerIndex() - HEADER.length) {
|
||||
// 找帧头
|
||||
int headerPos = findPattern(in, HEADER, readerIndex);
|
||||
if (headerPos == -1) {
|
||||
// 没找到帧头,丢弃无效数据
|
||||
in.readerIndex(in.writerIndex() - (HEADER.length - 1));
|
||||
return;
|
||||
}
|
||||
|
||||
// 从 header 之后找帧尾
|
||||
int searchStart = headerPos + HEADER.length;
|
||||
int footerPos = -1;
|
||||
while (searchStart <= in.writerIndex() - FOOTER.length) {
|
||||
footerPos = findPattern(in, FOOTER, searchStart);
|
||||
if (footerPos == -1) break; // 没找到尾,等待更多数据
|
||||
|
||||
// 检查是否有嵌套帧头
|
||||
int nestedHeader = findPattern(in, HEADER, searchStart);
|
||||
if (nestedHeader != -1 && nestedHeader < footerPos) {
|
||||
// 有嵌套帧头,从新头重新搜索
|
||||
headerPos = nestedHeader;
|
||||
searchStart = headerPos + HEADER.length;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (footerPos == -1) {
|
||||
// 第一帧未完整,保留数据,等待更多
|
||||
in.readerIndex(headerPos);
|
||||
return;
|
||||
}
|
||||
|
||||
int frameLength = footerPos + FOOTER.length - headerPos;
|
||||
if (frameLength > MAX_FRAME_LENGTH) {
|
||||
// 超长帧,跳过异常数据
|
||||
in.readerIndex(headerPos + 1); // 从下一个字节重新找
|
||||
readerIndex = in.readerIndex();
|
||||
continue;
|
||||
}
|
||||
|
||||
// 提取完整帧
|
||||
in.readerIndex(headerPos);
|
||||
ByteBuf frame = in.readRetainedSlice(frameLength);
|
||||
out.add(frame);
|
||||
|
||||
// 下次从 footer 后面继续解析
|
||||
readerIndex = in.readerIndex();
|
||||
}
|
||||
|
||||
// 更新 readerIndex
|
||||
in.readerIndex(readerIndex);
|
||||
}
|
||||
|
||||
private int findPattern(ByteBuf buf, byte[] pattern, int fromIndex) {
|
||||
for (int i = fromIndex; i <= buf.writerIndex() - pattern.length; i++) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < pattern.length; j++) {
|
||||
if (buf.getByte(i + j) != pattern[j]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.maibu.netty.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MiddleHexDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
byte[] data = new byte[in.readableBytes()];
|
||||
in.readBytes(data);
|
||||
out.add(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.maibu.netty.handler;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
||||
public class MiddleHexEncoder extends MessageToByteEncoder<byte[]> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) {
|
||||
out.writeBytes(msg);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user