From 43fca46111ef62aa751ee67f41d6866c61fdc4b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E5=87=A4=E5=90=89?= Date: Tue, 11 Feb 2025 20:50:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8E=E8=AE=BE=E5=A4=87TCP=E9=80=9A?= =?UTF-8?q?=E4=BF=A1=E5=9F=BA=E7=A1=80=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../java/com/qyft/gd/device/client/TcpClient.java | 87 ++++++++++++++++++++++ .../gd/device/common/constant/DeviceCommands.java | 7 ++ .../java/com/qyft/gd/device/config/TcpConfig.java | 32 ++++++++ .../gd/device/handler/DeviceMessageHandler.java | 37 +++++++++ .../com/qyft/gd/device/model/bo/DeviceStatus.java | 10 +++ .../qyft/gd/device/service/DeviceStateService.java | 24 ++++++ src/main/resources/application.yml | 9 +++ 8 files changed, 207 insertions(+) create mode 100644 src/main/java/com/qyft/gd/device/client/TcpClient.java create mode 100644 src/main/java/com/qyft/gd/device/common/constant/DeviceCommands.java create mode 100644 src/main/java/com/qyft/gd/device/config/TcpConfig.java create mode 100644 src/main/java/com/qyft/gd/device/handler/DeviceMessageHandler.java create mode 100644 src/main/java/com/qyft/gd/device/model/bo/DeviceStatus.java create mode 100644 src/main/java/com/qyft/gd/device/service/DeviceStateService.java diff --git a/build.gradle b/build.gradle index 516b578..6a061e2 100644 --- a/build.gradle +++ b/build.gradle @@ -39,6 +39,7 @@ dependencies { runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.12.6' implementation group: 'com.alibaba', name: 'fastjson', version: '2.0.54' implementation group: 'org.springframework.boot', name: 'spring-boot-starter-websocket', version: '3.4.2' + implementation group: 'io.netty', name: 'netty-all', version: '4.1.117.Final' //++++++++项目级别的放到下面++++++++ diff --git a/src/main/java/com/qyft/gd/device/client/TcpClient.java b/src/main/java/com/qyft/gd/device/client/TcpClient.java new file mode 100644 index 0000000..4f38b2b --- /dev/null +++ b/src/main/java/com/qyft/gd/device/client/TcpClient.java @@ -0,0 +1,87 @@ +package com.qyft.gd.device.client; + +import com.qyft.gd.device.config.TcpConfig; +import com.qyft.gd.device.handler.DeviceMessageHandler; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; + + +@Component +@RequiredArgsConstructor +public class TcpClient { + + private static final Logger log = LoggerFactory.getLogger(TcpClient.class); + private final TcpConfig tcpConfig; + private final DeviceMessageHandler deviceMessageHandler; + + private final EventLoopGroup group = new NioEventLoopGroup(); + private Channel channel; + private Bootstrap bootstrap; + + @PostConstruct + public void init() { + if (tcpConfig.isEnable()) { + connect(); + } + } + + public void connect() { + try { + if (bootstrap == null) { + bootstrap = new Bootstrap(); + bootstrap.group(group) + .channel(NioSocketChannel.class) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, tcpConfig.getTimeout()) + .handler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel ch) { + ch.pipeline().addLast(deviceMessageHandler); + } + }); + } + + log.info("尝试连接到TCP服务 {}:{}", tcpConfig.getHost(), tcpConfig.getPort()); + ChannelFuture future = bootstrap.connect(new InetSocketAddress(tcpConfig.getHost(), tcpConfig.getPort())); + + future.addListener((ChannelFutureListener) f -> { + if (f.isSuccess()) { + channel = f.channel(); + log.info("已链接到TCP服务"); + } else { + log.error("无法连接到TCP服务. {}ms后重试...", tcpConfig.getReconnect()); + f.channel().eventLoop().schedule(this::connect, tcpConfig.getReconnect(), TimeUnit.MILLISECONDS); + } + }); + + } catch (Exception e) { + log.error("尝试连接到TCP服务发生意外错误: {}", e.getMessage(), e); + } + } + + @Scheduled(fixedRateString = "${tcp.reconnect}") + public void checkConnection() { + if (channel == null || !channel.isActive()) { + log.error("TCP服务链接丢失"); + connect(); + } + } + + public void sendCommand(String command) { + if (channel != null && channel.isActive()) { + channel.writeAndFlush(command); + } else { + log.error("TCP服务未连接,无法发送指令: {}", command); + } + } +} diff --git a/src/main/java/com/qyft/gd/device/common/constant/DeviceCommands.java b/src/main/java/com/qyft/gd/device/common/constant/DeviceCommands.java new file mode 100644 index 0000000..08cd38a --- /dev/null +++ b/src/main/java/com/qyft/gd/device/common/constant/DeviceCommands.java @@ -0,0 +1,7 @@ +package com.qyft.gd.device.common.constant; + +/** + * 设备指令 + */ +public class DeviceCommands { +} diff --git a/src/main/java/com/qyft/gd/device/config/TcpConfig.java b/src/main/java/com/qyft/gd/device/config/TcpConfig.java new file mode 100644 index 0000000..5ddb328 --- /dev/null +++ b/src/main/java/com/qyft/gd/device/config/TcpConfig.java @@ -0,0 +1,32 @@ +package com.qyft.gd.device.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "tcp") +public class TcpConfig { + /** + * 是否启用 TCP 连接 + */ + private boolean enable; + /** + * TCP 链接地址 + */ + private String host; + /** + * TCP 端口 + */ + private int port; + /** + * 断线重连间隔(毫秒) + */ + private int reconnect; + /** + * 连接超时时间(毫秒) + */ + private int timeout; +} + diff --git a/src/main/java/com/qyft/gd/device/handler/DeviceMessageHandler.java b/src/main/java/com/qyft/gd/device/handler/DeviceMessageHandler.java new file mode 100644 index 0000000..29f6b7b --- /dev/null +++ b/src/main/java/com/qyft/gd/device/handler/DeviceMessageHandler.java @@ -0,0 +1,37 @@ +package com.qyft.gd.device.handler; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.qyft.gd.device.model.bo.DeviceStatus; +import com.qyft.gd.device.service.DeviceStateService; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +@ChannelHandler.Sharable +@RequiredArgsConstructor +public class DeviceMessageHandler extends SimpleChannelInboundHandler { + + private static final Logger log = LoggerFactory.getLogger(DeviceMessageHandler.class); + + private final DeviceStateService deviceStateService; + @Override + protected void channelRead0(ChannelHandlerContext ctx, String msg) { + try { + // 解析 JSON + JSONObject json = JSONUtil.parseObj(msg); + DeviceStatus deviceStatus = json.toBean(DeviceStatus.class); + // 更新设备状态 + deviceStateService.updateDeviceStatus(deviceStatus); + + log.info("设备状态已更新: {}", json.toStringPretty()); + } catch (Exception e) { + log.error("设备状态更新错误: {}, error: {}", msg, e.getMessage(), e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/qyft/gd/device/model/bo/DeviceStatus.java b/src/main/java/com/qyft/gd/device/model/bo/DeviceStatus.java new file mode 100644 index 0000000..372ba27 --- /dev/null +++ b/src/main/java/com/qyft/gd/device/model/bo/DeviceStatus.java @@ -0,0 +1,10 @@ +package com.qyft.gd.device.model.bo; + +import lombok.Data; + +/** + * 设备当前状态 + */ +@Data +public class DeviceStatus { +} diff --git a/src/main/java/com/qyft/gd/device/service/DeviceStateService.java b/src/main/java/com/qyft/gd/device/service/DeviceStateService.java new file mode 100644 index 0000000..cb598c9 --- /dev/null +++ b/src/main/java/com/qyft/gd/device/service/DeviceStateService.java @@ -0,0 +1,24 @@ +package com.qyft.gd.device.service; + +import cn.hutool.core.bean.BeanUtil; +import com.qyft.gd.device.model.bo.DeviceStatus; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class DeviceStateService { + private final DeviceStatus deviceStatus = new DeviceStatus(); + + public void updateDeviceStatus(DeviceStatus newStatus) { + synchronized (deviceStatus) { + BeanUtil.copyProperties(newStatus, deviceStatus); + } + } + + public DeviceStatus getDeviceStatus() { + synchronized (deviceStatus) { + return BeanUtil.copyProperties(deviceStatus, DeviceStatus.class); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 529c445..fa70249 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -31,3 +31,12 @@ springdoc: jwt: enabled: false # 是否启用权限认证,设置为 true 启用,false 禁用 + +#与设备TCP链接 +tcp: + enable: false # 是否开启 TCP 连接 + host: 192.168.1.100 + port: 9000 + reconnect: 5000 # 断线重连间隔(单位:毫秒) + timeout: 10000 # 连接超时时间(单位:毫秒) +