소스 검색

feat(gas-service): 初始化可燃气体监测服务项目

- 添加Spring Boot主应用类GasApplication
- 配置开发环境和生产环境的数据库、Redis及Nacos连接信息
- 引入MyBatis Plus、Netty、WebSocket等核心依赖
- 创建气体监测数据实体类GasMonitorData及相关Mapper和服务层- 实现基于Netty的数据接收与解析功能
- 添加CRC16校验工具类DataCheckUtil
- 构建数据解析器DataParser支持多种字段解析- 设置Netty服务端监听指定端口并处理设备通信- 集成Sentinel流量控制和Nacos配置中心- 配置文件支持多环境切换及共享配置加载
林仔 8 달 전
부모
커밋
0572055611

+ 77 - 0
flammable-gas-service/pom.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         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.zksy</groupId>
+        <artifactId>pipe-ner-server</artifactId>
+        <version>1.0.0</version>
+    </parent>
+    <groupId>com.zksy</groupId>
+    <artifactId>flammable-gas-service</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <!--common-->
+        <dependency>
+            <groupId>com.zksy</groupId>
+            <artifactId>zk-common</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <!--数据库-->
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <!--mybatis-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <!--nacos 服务注册发现-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
+        </dependency>
+        <!--负载均衡-->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
+        </dependency>
+        <!--统一配置管理-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
+        </dependency>
+        <!--加载bootstrap-->
+        <dependency>
+            <groupId>org.springframework.cloud</groupId>
+            <artifactId>spring-cloud-starter-bootstrap</artifactId>
+        </dependency>
+        <!--sentinel-->
+        <dependency>
+            <groupId>com.alibaba.cloud</groupId>
+            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
+        </dependency>
+        <!--netty依赖-->
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+        <!--websocket-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-websocket</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 15 - 0
flammable-gas-service/src/main/java/com/zksy/gas/GasApplication.java

@@ -0,0 +1,15 @@
+package com.zksy.gas;
+
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@MapperScan(basePackages = "com.zksy.gas.mapper")
+@SpringBootApplication(scanBasePackages = {"com.zksy.gas","com.zksy.api"})
+public class GasApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(GasApplication.class, args);
+        System.out.println("可燃气体监测服务启动成功");
+    }
+}

+ 40 - 0
flammable-gas-service/src/main/java/com/zksy/gas/config/NettyServer.java

@@ -0,0 +1,40 @@
+package com.zksy.gas.config;
+
+import io.netty.channel.EventLoopGroup;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+@Slf4j
+@Component
+public class NettyServer {
+
+	private static Logger logger = LoggerFactory.getLogger(NettyServer.class);
+	public static EventLoopGroup acceptor;
+	public static EventLoopGroup worker;
+
+	@Autowired
+	private NettyServerThread nettyServerThread;
+
+	@PostConstruct
+	public void init() {
+		new Thread(() -> nettyServerThread.startServer()).start();
+		System.out.println("nettyServer启动");
+		logger.info("nettyServer启动");
+	}
+
+	@PreDestroy
+	public void exit() {
+		if (acceptor != null) {
+			acceptor.shutdownGracefully();
+		}
+		if (worker != null) {
+			worker.shutdownGracefully();
+		}
+	}
+}

+ 77 - 0
flammable-gas-service/src/main/java/com/zksy/gas/config/NettyServerThread.java

@@ -0,0 +1,77 @@
+package com.zksy.gas.config;
+
+import com.zksy.gas.utils.MessageHandler;
+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 io.netty.handler.timeout.ReadTimeoutHandler;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class NettyServerThread {
+	@Value("${netty.port:20014}")
+	private int port;
+	private static Logger logger = LoggerFactory.getLogger(NettyServerThread.class);
+	private final MessageHandler messageHandler;
+	@Autowired
+	public NettyServerThread(MessageHandler messageHandler) {
+		this.messageHandler = messageHandler;
+	}
+	public void startServer() {
+		System.out.println("Netty服务启动端口号" + port);
+		EventLoopGroup acceptor = new NioEventLoopGroup();
+		EventLoopGroup worker = new NioEventLoopGroup();
+		NettyServer.acceptor = acceptor;
+		NettyServer.worker = worker;
+		ServerBootstrap bootstrap = new ServerBootstrap();
+
+		// 添加boss和worker组
+		bootstrap.group(acceptor, worker);
+		//这句是指定允许等待accept的最大连接数量,我只需要连一个客户端,这里就关掉了,java默认是50个
+		// bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
+		bootstrap.option(ChannelOption.TCP_NODELAY, true);
+		// 用于构造socketchannel工厂
+		bootstrap.channel(NioServerSocketChannel.class);
+
+		/**
+		 * 传入自定义客户端Handle(处理消息)
+		 */
+		bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
+			@Override
+			public void initChannel(SocketChannel ch) throws Exception {
+				logger.info("来自" + ch.remoteAddress() + "的新连接接入");
+				// 注册handler
+				ch.pipeline().addLast(new ReadTimeoutHandler(3600));// 超时时间,1小时内没有从通道(Channel)读取到任何数据
+				ch.pipeline().addLast(messageHandler);
+			}
+		});
+
+		// 绑定端口,开始接收进来的连接
+		ChannelFuture f;
+		try {
+			f = bootstrap.bind(port).sync();
+			// 等待服务器 socket 关闭 。
+			f.channel().closeFuture().sync();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		} finally {
+			if (acceptor != null) {
+				acceptor.shutdownGracefully();
+			}
+			if (worker != null) {
+				worker.shutdownGracefully();
+			}
+		}
+	}
+}

+ 132 - 0
flammable-gas-service/src/main/java/com/zksy/gas/domain/GasMonitorData.java

@@ -0,0 +1,132 @@
+package com.zksy.gas.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+/**
+ * 可燃气体监测仪数据表
+ * @TableName gas_monitor_data
+ */
+@TableName(value ="gas_monitor_data")
+@Data
+public class GasMonitorData implements Serializable {
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id", type = IdType.ASSIGN_UUID)
+    private String id;
+
+    /**
+     * 帧类型
+     */
+    @TableField(value = "frame_type")
+    private Integer frameType;
+
+    /**
+     * MAC地址
+     */
+    @TableField(value = "mac_address")
+    private String macAddress;
+
+    /**
+     * 16位短地址
+     */
+    @TableField(value = "short_address")
+    private String shortAddress;
+
+    /**
+     * 自动应答选项
+     */
+    @TableField(value = "auto_reply_option")
+    private Integer autoReplyOption;
+
+    /**
+     * 数据帧序号
+     */
+    @TableField(value = "sequence_no")
+    private Integer sequenceNo;
+
+    /**
+     * 命令字
+     */
+    @TableField(value = "command")
+    private Integer command;
+
+    /**
+     * 设备属性
+     */
+    @TableField(value = "device_attr")
+    private Integer deviceAttr;
+
+    /**
+     * 协议版本
+     */
+    @TableField(value = "protocol_version")
+    private String protocolVersion;
+
+    /**
+     * 报警信息(解析后二进制)
+     */
+    @TableField(value = "alarm_info")
+    private Integer alarmInfo;
+
+    /**
+     * 设备上报时间戳
+     */
+    @TableField(value = "report_time")
+    private LocalDateTime reportTime;
+
+    /**
+     * 气体浓度(%LEL)
+     */
+    @TableField(value = "gas_concentration")
+    private BigDecimal gasConcentration;
+
+    /**
+     * 气体类型(如CH4)
+     */
+    @TableField(value = "gas_type")
+    private String gasType;
+
+    /**
+     * 电池电量
+     */
+    @TableField(value = "battery")
+    private BigDecimal battery;
+
+    /**
+     * 信号强度
+     */
+    @TableField(value = "signal_strength")
+    private Float signalStrength;
+
+    /**
+     * 经度
+     */
+    @TableField(value = "longitude")
+    private BigDecimal longitude;
+
+    /**
+     * 纬度
+     */
+    @TableField(value = "latitude")
+    private BigDecimal latitude;
+
+    /**
+     * 入库时间
+     */
+    @TableField(value = "create_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+}

+ 18 - 0
flammable-gas-service/src/main/java/com/zksy/gas/mapper/GasMonitorDataMapper.java

@@ -0,0 +1,18 @@
+package com.zksy.gas.mapper;
+
+import com.zksy.gas.domain.GasMonitorData;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author Administrator
+* @description 针对表【gas_monitor_data(可燃气体监测仪数据表)】的数据库操作Mapper
+* @createDate 2025-09-19 14:51:16
+* @Entity com.zksy.gas.domain.GasMonitorData
+*/
+public interface GasMonitorDataMapper extends BaseMapper<GasMonitorData> {
+
+}
+
+
+
+

+ 13 - 0
flammable-gas-service/src/main/java/com/zksy/gas/service/GasMonitorDataService.java

@@ -0,0 +1,13 @@
+package com.zksy.gas.service;
+
+import com.zksy.gas.domain.GasMonitorData;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author Administrator
+* @description 针对表【gas_monitor_data(可燃气体监测仪数据表)】的数据库操作Service
+* @createDate 2025-09-19 14:51:16
+*/
+public interface GasMonitorDataService extends IService<GasMonitorData> {
+
+}

+ 22 - 0
flammable-gas-service/src/main/java/com/zksy/gas/service/impl/GasMonitorDataServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zksy.gas.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zksy.gas.domain.GasMonitorData;
+import com.zksy.gas.service.GasMonitorDataService;
+import com.zksy.gas.mapper.GasMonitorDataMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author Administrator
+* @description 针对表【gas_monitor_data(可燃气体监测仪数据表)】的数据库操作Service实现
+* @createDate 2025-09-19 14:51:16
+*/
+@Service
+public class GasMonitorDataServiceImpl extends ServiceImpl<GasMonitorDataMapper, GasMonitorData>
+    implements GasMonitorDataService{
+
+}
+
+
+
+

+ 97 - 0
flammable-gas-service/src/main/java/com/zksy/gas/utils/DataCheckUtil.java

@@ -0,0 +1,97 @@
+package com.zksy.gas.utils;
+
+/**
+ * @Description
+ * @Date 2025- 03-06-上午 9:02
+ * @auther tuDouSi
+ */
+public class DataCheckUtil {
+    /**
+     *
+     * @description: crc16校验,输入一个数据,返回一个数组.
+     * @param str
+     * @return 返回两个校验码,低字节在前,高字节在后
+     *
+     */
+    public static String crc16(String str) {
+        int[] data = hexStringToIntArray(str);
+        int xda, xdapoly;
+        int i, j, xdabit;
+        xda = 0xFFFF;
+        xdapoly = 0xA001;
+        for (i = 0; i < data.length; i++) {
+            xda ^= data[i];
+            for (j = 0; j < 8; j++) {
+                xdabit = (int) (xda & 0x01);
+                xda >>= 1;
+                if (xdabit == 1) {
+                    xda ^= xdapoly;
+                }
+            }
+        }
+        byte[] temdata = new byte[2];
+        temdata[0] = (byte) (xda & 0xFF);
+        temdata[1] = (byte) (xda >> 8);
+        return bytesToHexString(temdata);
+    }
+    /**
+     *
+     * @description: crc16校验,
+     * @param str
+     * @return 返回两个校验码,高字节在前,低字节在后
+     *
+     */
+    public static String crc16Tall(String str) {
+        int[] data = hexStringToIntArray(str);
+        int xda, xdapoly;
+        int i, j, xdabit;
+        xda = 0xFFFF;
+        xdapoly = 0xA001;
+        for (i = 0; i < data.length; i++) {
+            xda ^= data[i];
+            for (j = 0; j < 8; j++) {
+                xdabit = (int) (xda & 0x01);
+                xda >>= 1;
+                if (xdabit == 1) {
+                    xda ^= xdapoly;
+                }
+            }
+        }
+
+        // 关键修改:先放高位字节,再放低位字节
+        byte[] temdata = new byte[2];
+        temdata[0] = (byte) (xda >> 8);  // 高位字节
+        temdata[1] = (byte) (xda & 0xFF); // 低位字节
+
+        return bytesToHexString(temdata);
+    }
+    // 将十六进制字符串转换为整数数组
+    private static int[] hexStringToIntArray(String str) {
+        if (str.length() % 2 != 0) {
+            throw new IllegalArgumentException("Hex string length must be even");
+        }
+
+        int[] result = new int[str.length() / 2];
+        for (int i = 0; i < str.length(); i += 2) {
+            result[i / 2] = Integer.parseInt(str.substring(i, i + 2), 16);
+        }
+        return result;
+    }
+
+    // 将字节数组转换为十六进制字符串
+    private static String bytesToHexString(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02X", b));
+        }
+        return sb.toString();
+    }
+
+    public static int calcChecksum(byte[] data, int offset, int length) {
+        int sum = 0;
+        for (int i = offset; i < offset + length; i++) {
+            sum += (data[i] & 0xFF);
+        }
+        return (0xFF - (sum & 0xFF)) & 0xFF;
+    }
+}

+ 116 - 0
flammable-gas-service/src/main/java/com/zksy/gas/utils/DataParser.java

@@ -0,0 +1,116 @@
+package com.zksy.gas.utils;
+
+import com.zksy.gas.domain.GasMonitorData;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+public class DataParser {
+
+    public static GasMonitorData parseMessage(byte[] frame) {
+        GasMonitorData data = new GasMonitorData();
+
+        // 帧类型
+        data.setFrameType(frame[2] & 0xFF);
+
+        // MAC 地址
+        byte[] macBytes = new byte[8];
+        System.arraycopy(frame, 3, macBytes, 0, 8);
+        data.setMacAddress(ProtocolUtils.bytesToHex(macBytes));
+
+        // 短地址
+        data.setShortAddress(String.format("%02X%02X", frame[11], frame[12]));
+
+        // 自动应答
+        data.setAutoReplyOption(frame[13] & 0xFF);
+
+        // 序号
+        data.setSequenceNo(frame[15] & 0xFF);
+
+        // 命令
+        data.setCommand(frame[16] & 0xFF);
+
+        // 设备属性
+        data.setDeviceAttr(frame[17] & 0xFF);
+
+        // 协议版本
+        data.setProtocolVersion(String.format("%02X", frame[18]));
+
+        // 报警信息(小端序 00 04 -> 0x0400)
+        int alarmInfo = ((frame[20] & 0xFF) << 8) | (frame[19] & 0xFF);
+        data.setAlarmInfo(alarmInfo);
+
+        // 时间戳(BCD)
+        LocalDateTime reportTime = ProtocolUtils.parseTimestamp(frame, 21);
+        data.setReportTime(reportTime);
+
+        // 数据个数
+        int dataCount = frame[27] & 0xFF;
+
+        // ------- 数据段开始(例子里有 5 个数据) -------
+        int offset = 28;
+
+        // 1. 气体浓度 + 气体类型
+        // 格式:04 10 00 00 00 00 -> 1004 (浓度=100, 类型=CH4)
+        int gasValue = ((frame[offset + 1] & 0xFF) << 8) | (frame[offset] & 0xFF);
+        int gasTypeCode = ((frame[offset + 3] & 0xFF) << 8) | (frame[offset + 2] & 0xFF);
+        data.setGasConcentration(BigDecimal.valueOf(gasValue));
+        data.setGasType(gasTypeCode == 0x0004 ? "CH4" : "未知");
+
+        offset += 6;
+
+        // 2. 电池电量
+        float battery = ProtocolUtils.bytesToFloatLE(frame, offset + 2);
+        data.setBattery(BigDecimal.valueOf(battery));
+        offset += 6;
+
+        // 3. 信号强度
+        float signal = ProtocolUtils.bytesToFloatLE(frame, offset + 2);
+        data.setSignalStrength(signal);
+        offset += 6;
+
+        // 4. 经度
+        float lon = ProtocolUtils.bytesToFloatLE(frame, offset + 2);
+        data.setLongitude(BigDecimal.valueOf(lon));
+        offset += 6;
+
+        // 5. 纬度
+        float lat = ProtocolUtils.bytesToFloatLE(frame, offset + 2);
+        data.setLatitude(BigDecimal.valueOf(lat));
+        offset += 6;
+
+        // 入库时间
+        data.setCreateTime(LocalDateTime.now());
+
+        return data;
+    }
+
+    public static void main(String[] args) {
+        // 示例报文
+        String hex = "7E003A902509040070010000FFFE01AA2B00010813000425091908473305"
+                + "041000000000F96F00000000F76F0000F841FA6F00000000FB6F0000000007";
+
+        byte[] frame = ProtocolUtils.hexStringToBytes(hex);
+
+        GasMonitorData parsed = parseMessage(frame);
+
+        System.out.println("========== 解析结果 ==========");
+        System.out.println("帧类型: " + parsed.getFrameType());
+        System.out.println("MAC地址: " + parsed.getMacAddress());
+        System.out.println("短地址: " + parsed.getShortAddress());
+        System.out.println("自动应答: " + parsed.getAutoReplyOption());
+        System.out.println("序号: " + parsed.getSequenceNo());
+        System.out.println("命令: " + parsed.getCommand());
+        System.out.println("设备属性: " + parsed.getDeviceAttr());
+        System.out.println("协议版本: " + parsed.getProtocolVersion());
+        System.out.println("报警信息: " + parsed.getAlarmInfo());
+        System.out.println("时间戳: " + parsed.getReportTime());
+        System.out.println("气体浓度: " + parsed.getGasConcentration() + " %LEL");
+        System.out.println("气体类型: " + parsed.getGasType());
+        System.out.println("电池电量: " + parsed.getBattery());
+        System.out.println("信号强度: " + parsed.getSignalStrength());
+        System.out.println("经度: " + parsed.getLongitude());
+        System.out.println("纬度: " + parsed.getLatitude());
+        System.out.println("入库时间: " + parsed.getCreateTime());
+    }
+}

+ 36 - 0
flammable-gas-service/src/main/java/com/zksy/gas/utils/DeviceOfflineCheckTask.java

@@ -0,0 +1,36 @@
+package com.zksy.gas.utils;/*
+package com.zksy.gasTransmitter.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@Component
+public class DeviceOfflineCheckTask {
+    private static final Logger logger = LoggerFactory.getLogger(DeviceOfflineCheckTask.class);
+
+    // 用于存储设备编号及其最后一次接收数据的时间
+    public static ConcurrentHashMap<String, Date> deviceLastReceiveTimeMap = new ConcurrentHashMap<>();
+    @Autowired
+    private BaseDevicesService baseDevicesService;
+
+    // 定时检查设备是否离线
+    @Scheduled(fixedRate = 24 * 60 * 60 * 1000) // 每24小时执行一次
+    public void checkDeviceOffline() {
+        Date now = new Date();
+        for (Map.Entry<String, Date> entry : deviceLastReceiveTimeMap.entrySet()) {
+            long diff = now.getTime() - entry.getValue().getTime();
+            // 如果设备在 23 小时内没有接收数据,则认为设备离线
+            if (diff > 23 * 60 * 60 * 1000) {
+                baseDevicesService.getByDeviceNumberStatus(entry.getKey(), 1, 0);
+                logger.info("设备 {} 已离线", entry.getKey());
+            }
+        }
+    }
+}*/

+ 121 - 0
flammable-gas-service/src/main/java/com/zksy/gas/utils/MessageHandler.java

@@ -0,0 +1,121 @@
+package com.zksy.gas.utils;
+
+import cn.hutool.core.lang.UUID;
+import com.zksy.common.exception.InvalidMessageException;
+import com.zksy.gas.domain.GasMonitorData;
+import com.zksy.gas.service.GasMonitorDataService;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.timeout.ReadTimeoutException;
+import io.netty.util.ReferenceCountUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+@ChannelHandler.Sharable
+@Slf4j
+@Component
+public class MessageHandler extends ChannelInboundHandlerAdapter {
+	private static Logger logger = LoggerFactory.getLogger(MessageHandler.class);
+	private final GasMonitorDataService service;
+	@Autowired
+	public MessageHandler(GasMonitorDataService firefightingPressureService) {
+		this.service = firefightingPressureService;
+	}
+
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		super.channelActive(ctx);
+		//sendDataToDevice(ctx);
+	}
+
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) {
+		try {
+			ByteBuf msgByteBuf = (ByteBuf) msg;
+			if (msgByteBuf == null || !msgByteBuf.isReadable()) {
+				logger.warn("接收到无效的消息");
+				return;
+			}
+			int readable = msgByteBuf.readableBytes();
+			byte[] msgBytes = new byte[readable];
+			msgByteBuf.readBytes(msgBytes);
+			logger.info("接收到 {} 字节的数据: {}, 来自: {}",
+					readable, ProtocolUtils.bytesToHex(msgBytes), ctx.channel().remoteAddress());
+			//计算校验和
+			int check = DataCheckUtil.calcChecksum(msgBytes, 2, msgBytes.length - 3);
+			int checksum = msgBytes[msgBytes.length - 2] & 0xFF;
+
+			if (check != checksum) {
+				throw new InvalidMessageException("数据校验失败,本地计算=" + check + ",报文携带=" + checksum);
+			}
+			GasMonitorData resultData = DataParser.parseMessage(msgBytes);
+			resultData.setId(UUID.randomUUID().toString());
+			service.save(resultData);
+			logger.info("数据解析入库成功: {}", resultData);
+		} catch (InvalidMessageException e) {
+			logger.error("数据校验失败: {}", e.getMessage());
+			ctx.writeAndFlush(Unpooled.copiedBuffer("数据校验失败".getBytes()));
+		} catch (Exception e) {
+			logger.error("数据解析/入库异常", e);
+			ctx.writeAndFlush(Unpooled.copiedBuffer("数据处理异常".getBytes()));
+		} finally {
+			ReferenceCountUtil.release(msg);
+		}
+	}
+
+
+	private String printHexBinary(byte[] bytes) {
+		StringBuilder sb = new StringBuilder();
+		for (byte b : bytes) {
+			sb.append(String.format("%02X ", b));
+		}
+		System.out.println("Received raw data: " + sb.toString());
+		logger.info("Received raw data: " + sb.toString());
+		return sb.toString();
+	}
+
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+		if (cause instanceof ReadTimeoutException) {
+			logger.info("来自" + ctx.channel().remoteAddress() + "的连接超时断开");
+		} else {
+			cause.printStackTrace();
+			logger.info("来自" + ctx.channel().remoteAddress() + "的连接异常断开");
+			ctx.close();
+		}
+	}
+
+	@Override
+	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+		ctx.flush();
+	}
+
+	@Override
+	public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
+		logger.info("来自" + ctx.channel().remoteAddress() + "的连接主动断开");
+		ctx.fireChannelUnregistered();
+	}
+
+	// 主动向设备发送数据
+	public void sendDataToDevice(ChannelHandlerContext ctx) {
+		try {
+			// 将字符串形式的十六进制数据转换为字节数组
+			String hexData = "01 03 00 00 00 01 84 0A ";
+			String[] hexArray = hexData.split(" ");
+			byte[] dataBytes = new byte[hexArray.length];
+			for (int i = 0; i < hexArray.length; i++) {
+				dataBytes[i] = (byte) Integer.parseInt(hexArray[i], 16);
+			}
+			ByteBuf byteBuf = Unpooled.copiedBuffer(dataBytes);
+			ctx.writeAndFlush(byteBuf);
+			logger.info("已向设备发送数据: {}", printHexBinary(dataBytes));
+		} catch (Exception e) {
+			logger.error("发送数据失败: {}", e.getMessage());
+		}
+	}
+}

+ 17 - 0
flammable-gas-service/src/main/resources/application-dev.yaml

@@ -0,0 +1,17 @@
+zksy:
+  db:
+    host: 192.168.110.30
+    un: root
+    pw: 123
+    port: 3307
+    database: pipe-ner
+spring:
+  redis:
+    host: 192.168.110.30
+    port: 6379
+  cloud:
+    nacos:
+      discovery:
+        server-addr: 192.168.110.30:8848
+      config:
+        server-addr: 192.168.110.30:8848

+ 11 - 0
flammable-gas-service/src/main/resources/application-prod.yaml

@@ -0,0 +1,11 @@
+zksy:
+  db:
+    host: 47.107.107.47
+    un: root
+    pw: d$3%#*(jnhUDGHB]z0x876c~
+    port: 3306
+    database: pipe-ner
+spring:
+  redis:
+    host: 47.107.107.47
+    port: 6379

+ 35 - 0
flammable-gas-service/src/main/resources/bootstrap.yaml

@@ -0,0 +1,35 @@
+spring:
+  application:
+    name: gas-service
+  profiles:
+    active: dev
+  servlet:
+    multipart:
+      max-file-size: 100MB
+      max-request-size: 100MB
+  main:
+    allow-bean-definition-overriding: true
+  cloud:
+    sentinel:
+      transport:
+#        dashboard: 172.16.102.52:8090
+        dashboard: 192.168.110.30:8090
+      http-method-specify: true
+    nacos:
+      discovery:
+#        server-addr: 172.16.102.52:8848
+#        namespace: 99a7b129-faed-4b32-bcbf-54acd7ffd372
+         server-addr: 192.168.110.30:8848
+         namespace: 9ce240d6-ae6b-45a3-8566-e75ac25c9d71
+      config:
+#        server-addr: 172.16.102.52:8848
+#        namespace: 99a7b129-faed-4b32-bcbf-54acd7ffd372
+        server-addr: 192.168.110.30:8848
+        namespace: 9ce240d6-ae6b-45a3-8566-e75ac25c9d71
+        file-extension: yaml
+        shared-configs:
+          - dataId: gas-service.yaml
+          - dataId: zksy-shared-jdbc.yaml
+          - dataId: zksy-shared-log.yaml
+netty:
+  port: 8312

+ 36 - 0
flammable-gas-service/src/main/resources/mapper/GasMonitorDataMapper.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zksy.gas.mapper.GasMonitorDataMapper">
+
+    <resultMap id="BaseResultMap" type="com.zksy.gas.domain.GasMonitorData">
+            <id property="id" column="id" jdbcType="VARCHAR"/>
+            <result property="frameType" column="frame_type" jdbcType="TINYINT"/>
+            <result property="macAddress" column="mac_address" jdbcType="VARCHAR"/>
+            <result property="shortAddress" column="short_address" jdbcType="VARCHAR"/>
+            <result property="autoReplyOption" column="auto_reply_option" jdbcType="TINYINT"/>
+            <result property="sequenceNo" column="sequence_no" jdbcType="INTEGER"/>
+            <result property="command" column="command" jdbcType="TINYINT"/>
+            <result property="deviceAttr" column="device_attr" jdbcType="TINYINT"/>
+            <result property="protocolVersion" column="protocol_version" jdbcType="VARCHAR"/>
+            <result property="alarmInfo" column="alarm_info" jdbcType="INTEGER"/>
+            <result property="reportTime" column="report_time" jdbcType="TIMESTAMP"/>
+            <result property="gasConcentration" column="gas_concentration" jdbcType="DECIMAL"/>
+            <result property="gasType" column="gas_type" jdbcType="VARCHAR"/>
+            <result property="battery" column="battery" jdbcType="DECIMAL"/>
+            <result property="signalStrength" column="signal_strength" jdbcType="FLOAT"/>
+            <result property="longitude" column="longitude" jdbcType="DECIMAL"/>
+            <result property="latitude" column="latitude" jdbcType="DECIMAL"/>
+            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,frame_type,mac_address,
+        short_address,auto_reply_option,sequence_no,
+        command,device_attr,protocol_version,
+        alarm_info,report_time,gas_concentration,
+        gas_type,battery,signal_strength,
+        longitude,latitude,create_time
+    </sql>
+</mapper>