Procházet zdrojové kódy

feat(infrared): 添加红外读电表服务

- 实现了基于 Netty 的服务器,用于接收和处理红外读电表数据
- 添加了数据解析、校验和存储逻辑
- 集成了 MyBatis-Plus 用于数据库操作
- 配置了 Nacos 服务发现和配置管理- 实现了基本的 RESTful API 接口
林仔 před 11 měsíci
rodič
revize
a5f0c237ee
17 změnil soubory, kde provedl 780 přidání a 0 odebrání
  1. 19 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/InfraredApplication.java
  2. 45 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/config/NettyServer.java
  3. 77 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/config/NettyServerThread.java
  4. 14 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/config/RestTemplateConfig.java
  5. 41 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/controller/InfraredReadingMeterController.java
  6. 53 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/domain/InfraredReadingMeter.java
  7. 18 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/mapper/InfraredReadingMeterMapper.java
  8. 13 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/service/InfraredReadingMeterService.java
  9. 22 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/service/impl/InfraredReadingMeterServiceImpl.java
  10. 58 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/DataCheckUtil.java
  11. 63 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/DataParser.java
  12. 36 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/DeviceOfflineCheckTask.java
  13. 234 0
      infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/MessageHandler.java
  14. 20 0
      infrared-reading-meter-service/src/main/resources/application-dev.yaml
  15. 14 0
      infrared-reading-meter-service/src/main/resources/application-prod.yaml
  16. 31 0
      infrared-reading-meter-service/src/main/resources/bootstrap.yaml
  17. 22 0
      infrared-reading-meter-service/src/main/resources/mapper/InfraredReadingMeterMapper.xml

+ 19 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/InfraredApplication.java

@@ -0,0 +1,19 @@
+package com.zksy.infrared;
+
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+/**
+ * @author Administrator
+ */
+@SpringBootApplication
+@MapperScan(basePackages = "com.zksy.infrared.mapper")
+@EnableScheduling
+public class InfraredApplication {
+    public static void main(String[] args) {
+        SpringApplication.run(InfraredApplication.class, args);
+        System.out.println("红外读电表数据服务启动成功");
+    }
+}

+ 45 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/config/NettyServer.java

@@ -0,0 +1,45 @@
+package com.zksy.infrared.config;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+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 SocketChannel sc = null;
+
+	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
infrared-reading-meter-service/src/main/java/com/zksy/infrared/config/NettyServerThread.java

@@ -0,0 +1,77 @@
+package com.zksy.infrared.config;
+
+import com.zksy.infrared.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:8087}")
+	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));// 超时时间,3600s内没有从通道(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();
+			}
+		}
+	}
+}

+ 14 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/config/RestTemplateConfig.java

@@ -0,0 +1,14 @@
+package com.zksy.infrared.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestTemplateConfig {
+
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+}

+ 41 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/controller/InfraredReadingMeterController.java

@@ -0,0 +1,41 @@
+package com.zksy.infrared.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.zksy.common.core.domain.Result;
+import com.zksy.common.utils.SearchUtil;
+import com.zksy.infrared.domain.InfraredReadingMeter;
+import com.zksy.infrared.service.InfraredReadingMeterService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author Administrator
+ * @version 1.0
+ * @project dh-server-micro
+ * @description 红外读电表
+ * @date 2025/2/10 15:00:46
+ */
+@RequestMapping("/infraredReadingMeter")
+@RestController
+public class InfraredReadingMeterController {
+    @Autowired
+    private InfraredReadingMeterService service;
+
+    @GetMapping("/findByPage")
+    @ApiOperation(value = "分页查询")
+    public Result getMinuteDataPage(long pageNum, long pageSize, String conditionJson) throws Exception {
+        Page<InfraredReadingMeter> page = service.page(new Page<>(pageNum, pageSize), SearchUtil.parseWhereSql(conditionJson));
+        return Result.ok(page);
+    }
+    @GetMapping("/getAll")
+    @ApiOperation(value = "查询所有")
+    public Result getAllByConditionJson(String conditionJson) throws Exception {
+        List<InfraredReadingMeter> list = service.list(SearchUtil.parseWhereSql(conditionJson));
+        return Result.ok(list);
+    }
+}

+ 53 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/domain/InfraredReadingMeter.java

@@ -0,0 +1,53 @@
+package com.zksy.infrared.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 红外读电表数据
+ * @TableName infrared_reading_meter
+ */
+@Data
+@TableName(value ="infrared_reading_meter")
+public class InfraredReadingMeter implements Serializable {
+    /**
+     * 主键
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 电表编号
+     */
+    private String electricNumber;
+
+    /**
+     * 地址码
+     */
+    private String addressCode;
+
+    /**
+     * 功能码
+     */
+    private String functionCode;
+
+    /**
+     * 电表年代协议
+     */
+    private String agreement;
+
+    /**
+     * 总电能( Kwh)
+     */
+    private Double electricEnergy;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+}

+ 18 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/mapper/InfraredReadingMeterMapper.java

@@ -0,0 +1,18 @@
+package com.zksy.infrared.mapper;
+
+import com.zksy.infrared.domain.InfraredReadingMeter;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author Administrator
+* @description 针对表【infrared_reading_meter(红外读电表数据)】的数据库操作Mapper
+* @createDate 2025-06-06 10:27:47
+* @Entity com.zksy.infrared.domain.InfraredReadingMeter
+*/
+public interface InfraredReadingMeterMapper extends BaseMapper<InfraredReadingMeter> {
+
+}
+
+
+
+

+ 13 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/service/InfraredReadingMeterService.java

@@ -0,0 +1,13 @@
+package com.zksy.infrared.service;
+
+import com.zksy.infrared.domain.InfraredReadingMeter;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author Administrator
+* @description 针对表【infrared_reading_meter(红外读电表数据)】的数据库操作Service
+* @createDate 2025-06-06 10:27:47
+*/
+public interface InfraredReadingMeterService extends IService<InfraredReadingMeter> {
+
+}

+ 22 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/service/impl/InfraredReadingMeterServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zksy.infrared.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zksy.infrared.domain.InfraredReadingMeter;
+import com.zksy.infrared.service.InfraredReadingMeterService;
+import com.zksy.infrared.mapper.InfraredReadingMeterMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author Administrator
+* @description 针对表【infrared_reading_meter(红外读电表数据)】的数据库操作Service实现
+* @createDate 2025-06-06 10:27:47
+*/
+@Service
+public class InfraredReadingMeterServiceImpl extends ServiceImpl<InfraredReadingMeterMapper, InfraredReadingMeter>
+    implements InfraredReadingMeterService{
+
+}
+
+
+
+

+ 58 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/DataCheckUtil.java

@@ -0,0 +1,58 @@
+package com.zksy.infrared.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);
+    }
+    // 将十六进制字符串转换为整数数组
+    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();
+    }
+}

+ 63 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/DataParser.java

@@ -0,0 +1,63 @@
+package com.zksy.infrared.utils;
+
+import com.zksy.common.utils.DecimalConversion;
+import com.zksy.infrared.domain.InfraredReadingMeter;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+@Slf4j
+public class DataParser {
+    private static Logger logger = LoggerFactory.getLogger(DataParser.class);
+
+    public static InfraredReadingMeter parseMessage(String msgString) {
+        InfraredReadingMeter result = new InfraredReadingMeter();
+        try {
+            List<String> dataParts = new ArrayList<>();
+            for (int i = 0; i < msgString.length(); i += 2) {
+                int end = Math.min(i + 2, msgString.length());
+                dataParts.add(msgString.substring(i, end));
+            }
+            String res1 = getStringList(dataParts,1);
+            result.setAddressCode(String.valueOf(Integer.parseInt(res1, 16)));
+            String res2 = getStringList(dataParts,1);
+            result.setFunctionCode(String.valueOf(Integer.parseInt(res2, 16)));
+            getStringList(dataParts,1);
+            String res3 = getStringList(dataParts,4);
+            result.setElectricEnergy(Integer.parseInt(DecimalConversion.hexToDecimalExample(res3), 16) / 100.00);
+            result.setCreateTime(new Date());
+
+        } catch (Exception e) {
+            logger.error("解析消息时出错", e);
+        }
+        return result;
+    }
+
+    public static String parseMeterNumber(String msgString) {
+        String hexString = msgString.substring(6, msgString.length() - 4);
+        // 转换为BigInteger并获取无符号十进制表示
+       BigInteger bigInteger = new BigInteger(hexString, 16);
+        return bigInteger.toString();
+    }
+    public static String shiftFromList(List<String> list) {
+        if (list == null || list.isEmpty()) {
+            return null; // 或者抛出异常,根据需求决定
+        }
+        return list.remove(0);
+    }
+
+    private static String getStringList(List<String> dataParts,Integer q) {
+        StringBuilder res1 = new StringBuilder();
+        for (int i = 0; i < q; i++) {
+            res1.append(shiftFromList(dataParts));
+        }
+        return res1.toString();
+    }
+}

+ 36 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/DeviceOfflineCheckTask.java

@@ -0,0 +1,36 @@
+/*
+package com.zksy.strainGauge.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());
+            }
+        }
+    }
+}*/

+ 234 - 0
infrared-reading-meter-service/src/main/java/com/zksy/infrared/utils/MessageHandler.java

@@ -0,0 +1,234 @@
+package com.zksy.infrared.utils;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.zksy.common.exception.InvalidMessageException;
+import com.zksy.infrared.domain.InfraredReadingMeter;
+import com.zksy.infrared.service.InfraredReadingMeterService;
+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.Attribute;
+import io.netty.util.AttributeKey;
+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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@ChannelHandler.Sharable
+@Slf4j
+@Component
+public class MessageHandler extends ChannelInboundHandlerAdapter {
+	private static final Logger logger = LoggerFactory.getLogger(MessageHandler.class);
+	private final InfraredReadingMeterService infraredReadingMeterService;
+	private final AttributeKey<CommunicationState> STATE_KEY = AttributeKey.valueOf("communicationState");
+	private final AttributeKey<Long> RECORD_ID_KEY = AttributeKey.valueOf("recordId");
+	private static final String INITIAL_COMMAND = "01 03 00 33 00 01 74 05";
+	private static final String METER_NUMBER_COMMAND = "01 03 00 03 00 03 F5 CB";
+
+	// 通信状态枚举
+	private enum CommunicationState {
+		INIT, // 初始状态
+		WAITING_FOR_DATA, // 等待数据返回
+		WAITING_FOR_METER_NUMBER // 等待表号返回
+	}
+
+	@Autowired
+	public MessageHandler(InfraredReadingMeterService infraredReadingMeterService) {
+		this.infraredReadingMeterService = infraredReadingMeterService;
+	}
+
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		super.channelActive(ctx);
+		ctx.channel().attr(STATE_KEY).set(CommunicationState.INIT);
+		ctx.channel().attr(RECORD_ID_KEY).set(null);
+		sendDataToDevice(ctx, INITIAL_COMMAND);
+	}
+
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) {
+		Attribute<CommunicationState> stateAttr = ctx.channel().attr(STATE_KEY);
+		CommunicationState state = stateAttr.get() != null ? stateAttr.get() : CommunicationState.INIT;
+		Attribute<Long> recordIdAttr = ctx.channel().attr(RECORD_ID_KEY);
+
+		try {
+			ByteBuf msgByteBuf = (ByteBuf) msg;
+			if (msgByteBuf == null || !msgByteBuf.isReadable()) {
+				logger.warn("接收到无效的消息");
+				return;
+			}
+
+			logger.info("接收到 {} 字节的数据,来自: {}", msgByteBuf.readableBytes(), ctx.channel().remoteAddress());
+			byte[] msgBytes = new byte[msgByteBuf.readableBytes()];
+			msgByteBuf.readBytes(msgBytes);
+			String result = printHexBinary(msgBytes);
+			String msgString = result.replaceAll("\\s", "");
+			if("7777772E7573722E636E".equals(msgString)){
+				logger.warn("接收到无效的消息");
+				return;
+			}
+			// 数据校验
+			String CRCString = msgString.substring(msgString.length() - 4);
+			String bodyResult = msgString.substring(0, msgString.length() - 4);
+			String codeString = DataCheckUtil.crc16(bodyResult);
+			logger.info("校验码:{}-----------生成的校验码:{}", CRCString, codeString);
+
+			if (!CRCString.equals(codeString)) {
+				throw new InvalidMessageException("数据校验不成功");
+			}
+
+			switch (state) {
+				case INIT:
+					processInitialResponse(ctx, msgString, stateAttr);
+					break;
+				case WAITING_FOR_DATA:
+					processDeviceData(ctx, msgString, stateAttr, recordIdAttr);
+					break;
+				case WAITING_FOR_METER_NUMBER:
+					processMeterNumber(ctx, msgString, recordIdAttr);
+					stateAttr.set(CommunicationState.INIT); // 处理完表号后回到初始状态
+					break;
+				default:
+					logger.warn("未知通信状态: {}", state);
+			}
+		} catch (InvalidMessageException e) {
+			logger.error("数据处理失败: {}", e.getMessage());
+			ctx.writeAndFlush(Unpooled.copiedBuffer("数据处理失败".getBytes()));
+		} catch (Exception e) {
+			logger.error("处理消息时发生未知异常", e);
+			ctx.close();
+		} finally {
+			ReferenceCountUtil.release(msg);
+		}
+	}
+
+	private void processInitialResponse(ChannelHandlerContext ctx, String msgString,
+										Attribute<CommunicationState> stateAttr) throws InvalidMessageException {
+		String agreement = msgString.substring(6, msgString.length() - 4);
+		switch (agreement) {
+			case "0000":
+				sendDataToDevice(ctx, "01 03 03 E8 00 02 44 7B");
+				stateAttr.set(CommunicationState.WAITING_FOR_DATA);
+				logger.info("协议匹配0000,已发送指令并更新通信状态");
+				break;
+			case "0001":
+				sendDataToDevice(ctx, "01 03 07 D0 00 02 C4 86");
+				stateAttr.set(CommunicationState.WAITING_FOR_DATA);
+				logger.info("协议匹配0001,已发送指令并更新通信状态");
+				break;
+			case "0002":
+				sendDataToDevice(ctx, "01 03 0B B8 00 02 46 0A");
+				stateAttr.set(CommunicationState.WAITING_FOR_DATA);
+				logger.info("协议匹配0002,已发送指令并更新通信状态");
+				break;
+			default:
+				throw new InvalidMessageException("数据协议不匹配,未知协议类型: " + agreement);
+		}
+	}
+
+	private void processDeviceData(ChannelHandlerContext ctx, String msgString,
+								   Attribute<CommunicationState> stateAttr,
+								   Attribute<Long> recordIdAttr) {
+		try {
+			InfraredReadingMeter resultData = DataParser.parseMessage(msgString);
+			boolean savedData = infraredReadingMeterService.save(resultData);
+			if (savedData) {
+				recordIdAttr.set(resultData.getId());
+				logger.info("已保存设备数据,ID: {}", resultData.getId());
+				// 发送获取表号的指令
+				sendDataToDevice(ctx, METER_NUMBER_COMMAND);
+				stateAttr.set(CommunicationState.WAITING_FOR_METER_NUMBER);
+				logger.info("已发送获取表号指令,等待表号返回");
+			} else {
+				logger.info("失败");
+				stateAttr.set(CommunicationState.INIT);
+			}
+		} catch (Exception e) {
+			logger.error("处理设备数据时出错: {}", e.getMessage(), e);
+			stateAttr.set(CommunicationState.INIT); // 出错后重置状态
+		}
+	}
+
+	private void processMeterNumber(ChannelHandlerContext ctx, String msgString,
+									Attribute<Long> recordIdAttr) {
+		Long recordId = recordIdAttr.get();
+		if (recordId == null) {
+			logger.error("没有找到关联的记录ID,无法更新表号");
+			return;
+		}
+
+		try {
+			// 解析表号
+			String meterNumber = DataParser.parseMeterNumber(msgString);
+			if (meterNumber != null && !meterNumber.isEmpty()) {
+				LambdaUpdateWrapper<InfraredReadingMeter> infraredReadingMeterLambdaQueryWrapper = new LambdaUpdateWrapper<>();
+				infraredReadingMeterLambdaQueryWrapper.eq(InfraredReadingMeter::getId, recordId);
+				infraredReadingMeterLambdaQueryWrapper.set(InfraredReadingMeter::getElectricNumber, meterNumber);
+				infraredReadingMeterService.update(infraredReadingMeterLambdaQueryWrapper);
+				logger.info("已更新记录ID {} 的表号为: {}", recordId, meterNumber);
+			} else {
+				logger.warn("无法从消息中提取有效表号: {}", msgString);
+			}
+		} catch (Exception e) {
+			logger.error("处理表号时出错: {}", e.getMessage(), e);
+		} finally {
+			recordIdAttr.set(null); // 处理完后清空记录ID
+		}
+	}
+
+	private String printHexBinary(byte[] bytes) {
+		StringBuilder sb = new StringBuilder();
+		for (byte b : bytes) {
+			sb.append(String.format("%02X ", b));
+		}
+		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 {
+			logger.error("来自{}的连接异常断开", ctx.channel().remoteAddress(), cause);
+		}
+		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.channel().attr(STATE_KEY).set(null);
+		ctx.channel().attr(RECORD_ID_KEY).set(null);
+		ctx.fireChannelUnregistered();
+	}
+
+	// 主动向设备发送数据
+	public void sendDataToDevice(ChannelHandlerContext ctx, String hexData) {
+		try {
+			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());
+		}
+	}
+}

+ 20 - 0
infrared-reading-meter-service/src/main/resources/application-dev.yaml

@@ -0,0 +1,20 @@
+zksy:
+  db:
+    host: 192.168.110.30
+    un: root
+    pw: 123
+    port: 3307
+    database: zhongkeshengyang
+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
+netty:
+  # 端口号
+  port: 8087

+ 14 - 0
infrared-reading-meter-service/src/main/resources/application-prod.yaml

@@ -0,0 +1,14 @@
+zksy:
+  db:
+    host: 172.16.102.51
+    un: root
+    pw: d$3%#*(jnhUDGHB]z0x876c~
+    port: 3306
+    database: zhongkeshengyang
+spring:
+  redis:
+    host: 172.16.102.52
+    port: 6379
+netty:
+  # 端口号
+  port: 8087

+ 31 - 0
infrared-reading-meter-service/src/main/resources/bootstrap.yaml

@@ -0,0 +1,31 @@
+spring:
+  application:
+    name: infrared-reading-meter-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
+      config:
+        #  server-addr: 172.16.102.52:8848
+        #  namespace: 99a7b129-faed-4b32-bcbf-54acd7ffd372
+        server-addr: 192.168.110.30:8848
+        file-extension: yaml
+        shared-configs:
+          - dataId: infrared-reading-meter-service.yaml
+          - dataId: zksy-shared-jdbc.yaml
+          - dataId: zksy-shared-log.yaml

+ 22 - 0
infrared-reading-meter-service/src/main/resources/mapper/InfraredReadingMeterMapper.xml

@@ -0,0 +1,22 @@
+<?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.infrared.mapper.InfraredReadingMeterMapper">
+
+    <resultMap id="BaseResultMap" type="com.zksy.infrared.domain.InfraredReadingMeter">
+            <id property="id" column="id" jdbcType="BIGINT"/>
+            <result property="electricNumber" column="electric_number" jdbcType="VARCHAR"/>
+            <result property="addressCode" column="address_code" jdbcType="VARCHAR"/>
+            <result property="functionCode" column="function_code" jdbcType="VARCHAR"/>
+            <result property="agreement" column="agreement" jdbcType="VARCHAR"/>
+            <result property="electricEnergy" column="electric_energy" jdbcType="VARCHAR"/>
+            <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,electric_number,address_code,
+        function_code,agreement,electric_energy,
+        create_time
+    </sql>
+</mapper>