Procházet zdrojové kódy

feat(electricity-service): 添加 Netty 服务器和电表数据解析功能

- 新增 NettyServer 和 NettyServerThread 类实现 Netty 服务器功能
- 添加 MessageParseController 和相关服务类处理电表数据解析和存储
- 实现 DataParser 类进行电表数据格式解析
- 新增 SpringContextUtil 工具类获取 Spring 上下文
- 优化 RealTimePreviewVideoController 中的视频查询接口
- 重命名 screen-service 中的请求类
林仔 před 1 rokem
rodič
revize
08ba4c9d7b
20 změnil soubory, kde provedl 867 přidání a 45 odebrání
  1. 45 0
      electricity-service/src/main/java/com/zksy/electricity/config/NettyServer.java
  2. 77 0
      electricity-service/src/main/java/com/zksy/electricity/config/NettyServerThread.java
  3. 34 0
      electricity-service/src/main/java/com/zksy/electricity/controller/MessageParseController.java
  4. 0 15
      electricity-service/src/main/java/com/zksy/electricity/controller/TestController.java
  5. 108 0
      electricity-service/src/main/java/com/zksy/electricity/domain/MessageParseResult.java
  6. 9 0
      electricity-service/src/main/java/com/zksy/electricity/mapper/MessageParseResultMapper.java
  7. 8 0
      electricity-service/src/main/java/com/zksy/electricity/service/MessageParseResultService.java
  8. 25 0
      electricity-service/src/main/java/com/zksy/electricity/service/impl/MessageParseResultServiceImpl.java
  9. 199 0
      electricity-service/src/main/java/com/zksy/electricity/utils/DataParser.java
  10. 175 0
      electricity-service/src/main/java/com/zksy/electricity/utils/MessageHandler.java
  11. 21 0
      electricity-service/src/main/java/com/zksy/electricity/utils/SpringContextUtil.java
  12. 3 0
      electricity-service/src/main/resources/bootstrap.yaml
  13. 1 1
      screen-service/src/main/java/com/zksy/screen/domain/request/InvokeBuildRequest.java
  14. 21 0
      screen-service/src/main/java/com/zksy/screen/domain/request/SwitchScreenRequest.java
  15. 1 1
      screen-service/src/main/java/com/zksy/screen/domain/request/TopWebPageRequest.java
  16. 72 26
      visualization-service/src/main/java/com/zksy/visualization/controller/RealTimePreviewVideoController.java
  17. 25 0
      visualization-service/src/main/java/com/zksy/visualization/domain/request/AlarmInfoRequest.java
  18. 2 2
      visualization-service/src/main/java/com/zksy/visualization/domain/request/VideoRecordDateQueryRequest.java
  19. 34 0
      visualization-service/src/main/java/com/zksy/visualization/domain/request/VideoRecordQueryListRequest.java
  20. 7 0
      zksy-common/src/main/java/com/zksy/common/exception/InvalidMessageException.java

+ 45 - 0
electricity-service/src/main/java/com/zksy/electricity/config/NettyServer.java

@@ -0,0 +1,45 @@
+package com.zksy.electricity.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
electricity-service/src/main/java/com/zksy/electricity/config/NettyServerThread.java

@@ -0,0 +1,77 @@
+package com.zksy.electricity.config;
+
+import com.zksy.electricity.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.Value;
+import org.springframework.stereotype.Component;
+
+@Slf4j
+@Component
+public class NettyServerThread {
+	@Value("${netty.port:20010}")
+	private int port;
+	private static Logger logger = LoggerFactory.getLogger(NettyServerThread.class);
+
+	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 {
+				if (NettyServer.sc == null) {
+					logger.info("来自" + ch.remoteAddress() + "的新连接接入");
+					NettyServer.sc = ch;
+					// 注册handler
+					ch.pipeline().addLast(new ReadTimeoutHandler(10));
+					ch.pipeline().addLast(new MessageHandler());
+				} else {
+					ch.close();
+				}
+			}
+		});
+
+		// 绑定端口,开始接收进来的连接
+		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();
+			}
+		}
+	}
+}

+ 34 - 0
electricity-service/src/main/java/com/zksy/electricity/controller/MessageParseController.java

@@ -0,0 +1,34 @@
+package com.zksy.electricity.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.electricity.domain.MessageParseResult;
+import com.zksy.electricity.service.MessageParseResultService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/messageParse")
+@Api(tags = "电表数据信息", description = "电表数据信息")
+public class MessageParseController {
+    @Autowired
+    private MessageParseResultService service;
+
+    @GetMapping("/findByPage")
+    @ApiOperation(value = "分页查询")
+    public Result getPictureMessagePage(long pageNum, long pageSize, String conditionJson) throws Exception {
+        Page<MessageParseResult> 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<MessageParseResult> list = service.list(SearchUtil.parseWhereSql(conditionJson));
+        return Result.ok(list);
+    }
+}

+ 0 - 15
electricity-service/src/main/java/com/zksy/electricity/controller/TestController.java

@@ -1,15 +0,0 @@
-package com.zksy.electricity.controller;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/test")
-public class TestController {
-
-    @GetMapping
-    public String test() {
-        return "性与暴力";
-    }
-}

+ 108 - 0
electricity-service/src/main/java/com/zksy/electricity/domain/MessageParseResult.java

@@ -0,0 +1,108 @@
+package com.zksy.electricity.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+@Data
+@TableName("message_parse_result")
+@EqualsAndHashCode(callSuper = false)
+@ApiModel(value = "读取电表数据")
+public class MessageParseResult {
+    @ApiModelProperty(value = "主键 ID")
+    private Long id;
+
+    @ApiModelProperty(value = "前导")
+    private String leadingString;
+
+    @ApiModelProperty(value = "固定 1")
+    private String fixed1;
+
+    @ApiModelProperty(value = "表号")
+    private String tableNumber;
+
+    @ApiModelProperty(value = "固定 2")
+    private String fixed2;
+
+    @ApiModelProperty(value = "命令码")
+    private String commandCode;
+
+    @ApiModelProperty(value = "数据域长度")
+    private Integer dataFieldLength;
+
+    @ApiModelProperty(value = "数据标识")
+    private String dataIdentification;
+
+    @ApiModelProperty(value = "剩余金额")
+    private String remainingAmount;
+
+    @ApiModelProperty(value = "透支金额")
+    private String overdraftAmount;
+
+    @ApiModelProperty(value = "A 相电压")
+    private String AVoltage;
+
+    @ApiModelProperty(value = "B 相电压")
+    private String BVoltage;
+
+    @ApiModelProperty(value = "C 相电压")
+    private String CVoltage;
+
+    @ApiModelProperty(value = "A 相电流")
+    private String ACurrent;
+
+    @ApiModelProperty(value = "B 相电流")
+    private String BCurrent;
+
+    @ApiModelProperty(value = "C 相电流")
+    private String CCurrent;
+
+    @ApiModelProperty(value = "总有功功率")
+    private String totalActivePower;
+
+    @ApiModelProperty(value = "A 相有功功率")
+    private String AActivePower;
+
+    @ApiModelProperty(value = "B 相有功功率")
+    private String BActivePower;
+
+    @ApiModelProperty(value = "C 相有功功率")
+    private String CActivePower;
+
+    @ApiModelProperty(value = "总功率因素")
+    private String totalPower;
+
+    @ApiModelProperty(value = "A 相功率因素")
+    private String APower;
+
+    @ApiModelProperty(value = "B 相功率因素")
+    private String BPower;
+
+    @ApiModelProperty(value = "C 相功率因素")
+    private String CPower;
+
+    @ApiModelProperty(value = "总用电量")
+    private String totalElectricity;
+
+    @ApiModelProperty(value = "尖有功电量")
+    private String sharpActivePowerConsumption;
+
+    @ApiModelProperty(value = "峰有功电量")
+    private String peakActivePowerConsumption;
+
+    @ApiModelProperty(value = "平有功电量")
+    private String averageActivePowerConsumption;
+
+    @ApiModelProperty(value = "谷有功电")
+    private String valleyActivePower;
+
+    @ApiModelProperty(value = "状态")
+    private String state;
+
+    @ApiModelProperty(value = "创建时间")
+    private Date createTime;
+}

+ 9 - 0
electricity-service/src/main/java/com/zksy/electricity/mapper/MessageParseResultMapper.java

@@ -0,0 +1,9 @@
+package com.zksy.electricity.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zksy.electricity.domain.MessageParseResult;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MessageParseResultMapper extends BaseMapper<MessageParseResult> {
+}

+ 8 - 0
electricity-service/src/main/java/com/zksy/electricity/service/MessageParseResultService.java

@@ -0,0 +1,8 @@
+package com.zksy.electricity.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zksy.electricity.domain.MessageParseResult;
+
+public interface MessageParseResultService extends IService<MessageParseResult> {
+    public Integer saveMessageParseResult(MessageParseResult result);
+}

+ 25 - 0
electricity-service/src/main/java/com/zksy/electricity/service/impl/MessageParseResultServiceImpl.java

@@ -0,0 +1,25 @@
+package com.zksy.electricity.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zksy.electricity.domain.MessageParseResult;
+import com.zksy.electricity.mapper.MessageParseResultMapper;
+import com.zksy.electricity.service.MessageParseResultService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author Administrator
+ * @version 1.0
+ * @project dh-server-micro
+ * @description
+ * @date 2025/2/20 10:55:03
+ */
+@Service
+public class MessageParseResultServiceImpl extends ServiceImpl<MessageParseResultMapper, MessageParseResult> implements MessageParseResultService {
+    @Autowired
+    private MessageParseResultMapper messageParseResultMapper;
+    @Override
+    public Integer saveMessageParseResult(MessageParseResult result) {
+        return messageParseResultMapper.insert(result);
+    }
+}

+ 199 - 0
electricity-service/src/main/java/com/zksy/electricity/utils/DataParser.java

@@ -0,0 +1,199 @@
+package com.zksy.electricity.utils;
+
+import com.zksy.electricity.domain.MessageParseResult;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.DecimalFormat;
+import java.util.Date;
+
+@Slf4j
+public class DataParser {
+    private static Logger logger = LoggerFactory.getLogger(DataParser.class);
+    public static MessageParseResult parseMessage(String msgString) {
+        MessageParseResult result = new MessageParseResult();
+        try {
+            String processedResult = msgString.replaceAll("\\s", "");
+            result.setLeadingString(processedResult.substring(0, 8));
+            result.setFixed1(processedResult.substring(8, 10));
+            result.setTableNumber(bigEndianToLittleEndian(processedResult.substring(10, 22)));
+            result.setFixed2(processedResult.substring(22, 24));
+            result.setCommandCode(processedResult.substring(24, 26));
+            result.setDataFieldLength(Integer.parseUnsignedInt(processedResult.substring(26, 28), 16));
+
+            String dataIdentificationString = processedResult.substring(28, 36);
+            result.setDataIdentification(bigEndianToLittleEndian(subtractFromHex(dataIdentificationString)));
+
+            String remainingAmountString = processedResult.substring(36, 44);
+            result.setRemainingAmount(bigEndianToLittleEndian(subtractFromHex(remainingAmountString)));
+
+            String overdraftAmountString = processedResult.substring(44, 52);
+            result.setOverdraftAmount(bigEndianToLittleEndian(subtractFromHex(overdraftAmountString)));
+
+            String AVoltageString = processedResult.substring(52, 56);
+            result.setAVoltage(dataFormat(bigEndianToLittleEndian(subtractFromHex(AVoltageString)), 10, "0.0", "V"));
+
+            String BVoltageString = processedResult.substring(56, 60);
+            result.setBVoltage(dataFormat(bigEndianToLittleEndian(subtractFromHex(BVoltageString)), 10, "0.0", "V"));
+
+            String CVoltageString = processedResult.substring(60, 64);
+            result.setCVoltage(dataFormat(bigEndianToLittleEndian(subtractFromHex(CVoltageString)), 10, "0.0", "V"));
+
+            String ACurrentString = processedResult.substring(64, 70);
+            result.setACurrent(dataFormat(bigEndianToLittleEndian(subtractFromHex(ACurrentString)), 1000, "0.000", "A"));
+
+            String BCurrentString = processedResult.substring(70, 76);
+            result.setBCurrent(dataFormat(bigEndianToLittleEndian(subtractFromHex(BCurrentString)), 1000, "0.000", "A"));
+
+            String CCurrentString = processedResult.substring(76, 82);
+            result.setCCurrent(dataFormat(bigEndianToLittleEndian(subtractFromHex(CCurrentString)), 1000, "0.000", "A"));
+
+            String totalActivePowerString = processedResult.substring(82, 88);
+            result.setTotalActivePower(dataFormat(bigEndianToLittleEndian(subtractFromHex(totalActivePowerString)), 1000, "0.000", "KWH"));
+
+            String AActivePowerString = processedResult.substring(88, 94);
+            result.setAActivePower(dataFormat(bigEndianToLittleEndian(subtractFromHex(AActivePowerString)), 10000, "0.0000", "KWH"));
+
+            String BActivePowerString = processedResult.substring(94, 100);
+            result.setBActivePower(dataFormat(bigEndianToLittleEndian(subtractFromHex(BActivePowerString)), 10000, "0.0000", "KWH"));
+
+            String CActivePowerString = processedResult.substring(100, 106);
+            result.setCActivePower(dataFormat(bigEndianToLittleEndian(subtractFromHex(CActivePowerString)), 10000, "0.0000", "KWH"));
+
+            String totalPowerString = processedResult.substring(106, 110);
+            result.setTotalPower(dataFormat(bigEndianToLittleEndian(subtractFromHex(totalPowerString)), 1000, "0.000", ""));
+
+            String APowerString = processedResult.substring(110, 114);
+            result.setAPower(dataFormat(bigEndianToLittleEndian(subtractFromHex(APowerString)), 1000, "0.000", ""));
+
+            String BPowerString = processedResult.substring(114, 118);
+            result.setBPower(dataFormat(bigEndianToLittleEndian(subtractFromHex(BPowerString)), 1000, "0.000", ""));
+
+            String CPowerString = processedResult.substring(118, 122);
+            result.setCPower(dataFormat(bigEndianToLittleEndian(subtractFromHex(CPowerString)), 1000, "0.000", ""));
+
+            String totalElectricityString = processedResult.substring(122, 130);
+            result.setTotalElectricity(dataFormat(bigEndianToLittleEndian(subtractFromHex(totalElectricityString)), 100, "0.00", "KWH"));
+
+            String sharpActivePowerConsumptionString = processedResult.substring(130, 138);
+            result.setSharpActivePowerConsumption(dataFormat(bigEndianToLittleEndian(subtractFromHex(sharpActivePowerConsumptionString)), 100, "0.00", "KWH"));
+
+            String peakActivePowerConsumptionString = processedResult.substring(138, 146);
+            result.setPeakActivePowerConsumption(dataFormat(bigEndianToLittleEndian(subtractFromHex(peakActivePowerConsumptionString)), 100, "0.00", "KWH"));
+
+            String averageActivePowerConsumptionString = processedResult.substring(146, 154);
+            result.setAverageActivePowerConsumption(dataFormat(bigEndianToLittleEndian(subtractFromHex(averageActivePowerConsumptionString)), 100, "0.00", "KWH"));
+
+            String valleyActivePowerString = processedResult.substring(154, 162);
+            result.setValleyActivePower(dataFormat(bigEndianToLittleEndian(subtractFromHex(valleyActivePowerString)), 100, "0.00", "KWH"));
+
+            String stateString = processedResult.substring(162, 166);
+            result.setState(bigEndianToLittleEndian(subtractFromHex(stateString)));
+
+            result.setCreateTime(new Date());
+        } catch (Exception e) {
+            logger.error("解析消息时出错", e);
+        }
+        return result;
+    }
+    /**
+     * 将大端表示的十六进制字符串转换为小端表示的十六进制字符串。
+     *
+     * @param bigEndianHexString 大端表示的十六进制字符串(如 "032002250111")
+     * @return 小端表示的十六进制字符串(如 "110125022003")
+     */
+    public static String bigEndianToLittleEndian(String bigEndianHexString) {
+        // 将十六进制字符串转换为字节数组
+        byte[] bigEndianBytes = hexStringToByteArray(bigEndianHexString);
+
+        // 进行大小端转换
+        byte[] littleEndianBytes = reverseBytes(bigEndianBytes);
+
+        // 将字节数组转换回十六进制字符串
+        return bytesToHex(littleEndianBytes, 0, littleEndianBytes.length);
+    }
+
+    /**
+     * 将十六进制字符串转换为字节数组。
+     *
+     * @param s 十六进制字符串
+     * @return 字节数组
+     */
+    private static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                    + Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    /**
+     * 将字节数组转换为十六进制字符串。
+     *
+     * @param bytes 字节数组
+     * @param offset 偏移量
+     * @param length 长度
+     * @return 十六进制字符串
+     */
+    private static String bytesToHex(byte[] bytes, int offset, int length) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < length; i++) {
+            sb.append(String.format("%02X", bytes[offset + i]));
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 反转字节数组以进行大小端转换。
+     *
+     * @param bytes 字节数组
+     * @return 转换后的字节数组
+     */
+    private static byte[] reverseBytes(byte[] bytes) {
+        byte[] reversedBytes = new byte[bytes.length];
+        for (int i = 0; i < bytes.length; i++) {
+            reversedBytes[i] = bytes[bytes.length - 1 - i];
+        }
+        return reversedBytes;
+    }
+    /**
+     * 将十六进制字符串中的每个字节减去指定的十进制数值,并返回新的十六进制字符串。
+     *
+     * @param hexStr 十六进制字符串(例如 "3244B335")
+     * @return 新的十六进制字符串
+     */
+    public static String subtractFromHex(String hexStr) {
+        StringBuilder result = new StringBuilder();
+        // 将十六进制减数转换为十进制整数
+        int subValue = Integer.parseInt("33", 16);
+
+        // 按两位一组遍历十六进制字符串
+        for (int i = 0; i < hexStr.length(); i += 2) {
+            // 提取两位十六进制数
+            String pair = hexStr.substring(i, Math.min(i + 2, hexStr.length()));
+            // 将提取的两位十六进制数转换为十进制整数
+            int pairValue = Integer.parseInt(pair, 16);
+            // 进行减法运算
+            int newVal = pairValue - subValue;
+            // 处理负数情况,保留最后两位十六进制表示
+            newVal = newVal & 0xFF;
+            // 将结果转换为两位十六进制字符串
+            String newHex = String.format("%02X", newVal);
+            // 将结果添加到结果字符串中
+            result.append(newHex);
+        }
+        return result.toString();
+    }
+    public static String dataFormat(String input,int size,String decimalPlaces,String unit) {
+        // 将输入的字符串转换为浮点数
+        double voltage = Double.parseDouble(input) / + size;
+        // 使用DecimalFormat来格式化数字,保留小数
+        DecimalFormat df = new DecimalFormat(decimalPlaces);
+        String formattedVoltage = df.format(voltage);
+        // 添加单位V
+        return formattedVoltage + unit;
+    }
+}

+ 175 - 0
electricity-service/src/main/java/com/zksy/electricity/utils/MessageHandler.java

@@ -0,0 +1,175 @@
+package com.zksy.electricity.utils;
+
+import com.zksy.common.exception.InvalidMessageException;
+import com.zksy.electricity.config.NettyServer;
+import com.zksy.electricity.domain.MessageParseResult;
+import com.zksy.electricity.service.MessageParseResultService;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.math.BigInteger;
+@Slf4j
+public class MessageHandler extends ChannelInboundHandlerAdapter {
+	private static Logger logger = LoggerFactory.getLogger(MessageHandler.class);
+
+	@Autowired
+	private MessageParseResultService messageParseResultService;
+
+	public MessageHandler() {
+		this.messageParseResultService = SpringContextUtil.getBean(MessageParseResultService.class);
+	}
+
+	/**
+	 * 本方法用于读取客户端发送的信息
+	 * 
+	 * @param ctx
+	 * @param msg
+	 * @throws Exception
+	 */
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) {
+		try {
+			ByteBuf msgByteBuf = (ByteBuf) msg;
+			if (msgByteBuf == null || !msgByteBuf.isReadable()) {
+				logger.warn("接收到无效的消息");
+				return;
+			}
+			// 安全的日志记录
+			logger.info("接收到 {} 字节的数据", msgByteBuf.readableBytes());
+			byte[] msgBytes = new byte[msgByteBuf.readableBytes()];
+			msgByteBuf.readBytes(msgBytes);
+			String msgString = printHexBinary(msgBytes);
+
+			if (!validateMessage(msgString)) {
+				throw new InvalidMessageException("数据校验不成功");
+			}else{
+				MessageParseResult result = DataParser.parseMessage(msgString);
+				messageParseResultService.saveMessageParseResult(result);
+			}
+		} catch (InvalidMessageException e) {
+			logger.error("数据校验失败: {}", e.getMessage());
+			ctx.writeAndFlush(Unpooled.copiedBuffer("数据校验失败".getBytes()));
+		}
+	}
+	// 校验消息的方法
+	private static boolean validateMessage(String msgString) {
+		// 去除首尾可能存在的空格
+		msgString = msgString.trim();
+		// 检查是否以 FE FE FE FE 开头,以 16 结尾
+		if (!msgString.startsWith("FE FE FE FE") || !msgString.endsWith("16")) {
+			return false;
+		}
+
+		// 找到第一个 68 的位置
+		int startIndex = msgString.indexOf("68");
+		if (startIndex == -1) {
+			return false;
+		}
+
+		// 找到结束码 16 的位置
+		int endIndex = msgString.lastIndexOf("16");
+
+		// 提取从第一个 68 到校验和之前的数据
+		String dataToSum = msgString.substring(startIndex, msgString.lastIndexOf(" ", endIndex - 3));
+		String[] hexArray = dataToSum.split(" ");
+
+		// 计算校验和
+		BigInteger sum = BigInteger.ZERO;
+		for (String hex : hexArray) {
+			sum = sum.add(new BigInteger(hex, 16));
+		}
+
+		// 取低 8 位,也就是十六进制的后两位
+		sum = sum.and(new BigInteger("FF", 16));
+
+		// 先找到校验和前面的空格位置
+		int checksumStart = msgString.lastIndexOf(" ", endIndex - 3) + 1;
+		// 校验和结束位置应该是 endIndex,因为 substring 方法的结束索引不包含在截取范围内
+		int checksumEnd = endIndex;
+		String checksumHex = msgString.substring(checksumStart, checksumEnd).trim();
+		BigInteger expectedChecksum = new BigInteger(checksumHex, 16);
+
+		// 比较计算的校验和与消息中的校验和
+		return sum.equals(expectedChecksum);
+	}
+	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();
+	}
+
+	/**
+	 * 本方法用作处理异常
+	 * 
+	 * @param ctx
+	 * @param cause
+	 * @throws Exception
+	 */
+	@Override
+	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+		if (cause.getClass() == io.netty.handler.timeout.ReadTimeoutException.class) {
+			logger.info("来自" + NettyServer.sc.remoteAddress() + "的连接超时断开");
+		} else {
+			cause.printStackTrace();
+			logger.info("来自" + NettyServer.sc.remoteAddress() + "的连接异常断开");
+			ctx.close();
+		}
+		NettyServer.sc= null;
+	}
+
+	/**
+	 * 信息获取完毕后操作
+	 *
+	 * @param ctx
+	 * @throws Exception
+	 */
+	@Override
+	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
+		ctx.flush();
+	}
+
+	/**
+	 * 断开连接时操作
+	 *
+	 * @param ctx
+	 * @throws Exception
+	 */
+	@Override
+	public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
+
+		if (NettyServer.sc!= null) {
+			logger.info("来自" + NettyServer.sc.remoteAddress() + "的连接主动断开");
+			NettyServer.sc= null;
+		}
+
+		ctx.fireChannelUnregistered();
+	}
+
+	/**
+	 * 根据信息具体操作的业务方法
+	 * 
+	 * @param msgBytes
+	 * @param ctx
+	 */
+	private void handler(byte[] msgBytes, ChannelHandlerContext ctx) {
+		// 解析接收到的数据
+
+		// 回复客户端
+		/*String responseMessage = "Message received and processed.";
+		ByteBuf echo = ctx.alloc().buffer();
+		echo.writeBytes(responseMessage.getBytes(io.netty.util.CharsetUtil.UTF_8));
+		ctx.writeAndFlush(echo);*/
+	}
+
+
+}

+ 21 - 0
electricity-service/src/main/java/com/zksy/electricity/utils/SpringContextUtil.java

@@ -0,0 +1,21 @@
+package com.zksy.electricity.utils;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class SpringContextUtil implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringContextUtil.applicationContext = applicationContext;
+    }
+
+    public static <T> T getBean(Class<T> clazz) {
+        return applicationContext.getBean(clazz);
+    }
+}

+ 3 - 0
electricity-service/src/main/resources/bootstrap.yaml

@@ -29,3 +29,6 @@ spring:
           - dataId: electricity-service.yaml
           - dataId: zksy-shared-jdbc.yaml
           - dataId: zksy-shared-log.yaml
+netty:
+  # 端口号
+  port: 20010

+ 1 - 1
screen-service/src/main/java/com/zksy/screen/domain/request/InvokeBuild.java → screen-service/src/main/java/com/zksy/screen/domain/request/InvokeBuildRequest.java

@@ -9,7 +9,7 @@ import io.swagger.annotations.ApiModelProperty;
  * @description 滚动文字
  * @date 2025/2/17 15:29:19
  */
-public class InvokeBuild {
+public class InvokeBuildRequest {
     @ApiModelProperty(value = "类型", example = "invokeBuildInJs")
     private String persistent;
     @ApiModelProperty(value = "内置的js方法", example = "scrollMarquee")

+ 21 - 0
screen-service/src/main/java/com/zksy/screen/domain/request/SwitchScreenRequest.java

@@ -0,0 +1,21 @@
+package com.zksy.screen.domain.request;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @author Administrator
+ * @version 1.0
+ * @project dh-server-micro
+ * @description 开关屏请求参数
+ * @date 2025/2/17 17:22:34
+ */
+@Data
+public class SwitchScreenRequest {
+    @ApiModelProperty(value = "类型", example = "callCardService",notes = "重启和开关都是callCardService")
+    private String type;
+    @ApiModelProperty(example = "setScreenOpen",notes = "开关=callCardService,重启=reboot")
+    private String fn;
+    @ApiModelProperty(value = "true为开屏,false为关屏",notes = "开关=true/false,重启=1")
+    private String arg1;
+}

+ 1 - 1
screen-service/src/main/java/com/zksy/screen/domain/request/TopWebPage.java → screen-service/src/main/java/com/zksy/screen/domain/request/TopWebPageRequest.java

@@ -11,7 +11,7 @@ import lombok.Data;
  * @date 2025/2/17 10:44:23
  */
 @Data
-public class TopWebPage {
+public class TopWebPageRequest {
     @ApiModelProperty(value = "类型", example = "loadUrl")
     private String type;
     @ApiModelProperty(value = "网页地址")

+ 72 - 26
visualization-service/src/main/java/com/zksy/visualization/controller/RealTimePreviewVideoController.java

@@ -100,29 +100,69 @@ public class RealTimePreviewVideoController {
         return Result.error("请求失败");
     }
 
-    /**
-     * 获取RTSP流地址
-     * @param rtspUrlRequest
-     * @return
-     */
-/*    public RtspUrlResponse getRtspUrl1(RtspUrlRequest rtspUrlRequest){
-        RtspUrlResponse response=null;
-        OauthConfigUserPwdInfo config = oauthConfigUtil.getOauthConfig();
+    @ApiOperation(value = "查询普通录像信息列表")
+    @PostMapping("/queryOrdinaryList")
+    public Result queryOrdinaryList(VideoRecordQueryListRequest request){
+        String startTime = DateToTimeStamp(request.getData().getStartTime());
+        String endTime = DateToTimeStamp(request.getData().getEndTime());
+        request.getData().setStartTime(startTime);
+        request.getData().setEndTime(endTime);
+        ObjectMapper objectMapper = new ObjectMapper();
+        String jsonParams = null;
         try {
-            log.info("RealTimePreviewDemo,getRtspUrl,request:{}", JSONUtil.toJsonStr(rtspUrlRequest));
-            response = HttpUtils.executeJson("/evo-apigw/admin/API/MTS/Video/StartVideo", rtspUrlRequest,null, Method.POST , config, RtspUrlResponse.class);
-            log.info("RealTimePreviewDemo,getRtspUrl,response:{}", JSONUtil.toJsonStr(response));
-        } catch (ClientException e) {
-            log.error(e.getErrMsg(), e);
+            jsonParams = objectMapper.writeValueAsString(request);
+        } catch (JsonProcessingException e) {
+            log.error("JSON序列化失败: {}", e);
+            return Result.error("请求失败");
         }
-        if(!response.getCode().equals("1000")) {
-            log.info("获取rtsp流地址失败:{}",response.getErrMsg());
+        String authorization = redisTemplate
+                .opsForValue()
+                .get("Authorization:" + iccConfigProperty.getUsername());
+        // 创建请求体
+        RequestBody body = RequestBody.create(jsonParams, JSON);
+        // 构建请求
+        Request requestHttp = new Request.Builder()
+                .url("https://" + iccConfigProperty.getHost() + "/evo-apigw/admin/API/SS/Record/QueryRecords")
+                .addHeader("Authorization", authorization)
+                .post(body)
+                .build();
+        // 发送请求
+        try (Response response = httpClient.newCall(requestHttp).execute()) {
+            if (!response.isSuccessful()) {
+                return Result.error("请求失败");
+            }
+            ResponseBody responseBody = response.body();
+            if (responseBody != null) {
+                String responseString = responseBody.string();
+                JsonNode rootNode = objectMapper.readTree(responseString);
+                JsonNode success = rootNode.path("success");
+                if("true".equals(success.asText())) {
+                    JsonNode dataNode = rootNode.path("data");
+                    if (dataNode != null) {
+                        JsonNode records = dataNode.path("records");
+                        if(records != null){
+                            return Result.ok(records);
+                        }else{
+                            return Result.error("无数据");
+                        }
+                    } else {
+                        return Result.ok(null);
+                    }
+                }else {
+                    return Result.error("查询普通录像信息列表失败");
+                }
+            }
+        } catch (IOException e) {
+            log.error("请求失败: {}", e);
         }
-        return response;
-    }*/
-/*    @ApiOperation(value = "rtsp以文件形式回放录像")
-    @PostMapping("/getRTSPFileReplay")
-    public Result getRTSPFileReplay(VideoRecordQueryRequest request){
+        return Result.error("请求失败");
+    }
+
+    @ApiOperation(value = "查询报警录像信息列表")
+    @PostMapping("/queryWarnList")
+    public Result queryWarnList(AlarmInfoRequest request){
+        String alarmDate = DateToTimeStamp(request.getData().getAlarmDate());
+        request.getData().setAlarmDate(alarmDate);
         ObjectMapper objectMapper = new ObjectMapper();
         String jsonParams = null;
         try {
@@ -138,7 +178,7 @@ public class RealTimePreviewVideoController {
         RequestBody body = RequestBody.create(jsonParams, JSON);
         // 构建请求
         Request requestHttp = new Request.Builder()
-                .url("https://" + iccConfigProperty.getHost() + "/evo-apigw/admin/API/SS/Playback/StartPlaybackByFile")
+                .url("https://" + iccConfigProperty.getHost() + "/evo-apigw/admin/API/SS/Record/GetAlarmRecords")
                 .addHeader("Authorization", authorization)
                 .post(body)
                 .build();
@@ -155,26 +195,32 @@ public class RealTimePreviewVideoController {
                 if("true".equals(success.asText())) {
                     JsonNode dataNode = rootNode.path("data");
                     if (dataNode != null) {
-                        return Result.ok(dataNode);
+                        JsonNode records = dataNode.path("records");
+                        if(records != null){
+                            return Result.ok(records);
+                        }else{
+                            return Result.error("无数据");
+                        }
                     } else {
                         return Result.ok(null);
                     }
                 }else {
-                    return Result.error("rtsp以文件形式回放录像失败");
+                    return Result.error("查询报警录像信息列表失败");
                 }
             }
         } catch (IOException e) {
             log.error("请求失败: {}", e);
         }
         return Result.error("请求失败");
-    }*/
+    }
+
     @ApiOperation(value = "rtsp以时间形式回放录像")
     @PostMapping("/getRTSPDateReplay")
     public Result getRTSPDateReplay(VideoRecordDateQueryRequest request){
-        String startTime = DateToTimeStamp(request.getData().getStartTime());
+        /*String startTime = DateToTimeStamp(request.getData().getStartTime());
         String endTime = DateToTimeStamp(request.getData().getEndTime());
         request.getData().setStartTime(startTime);
-        request.getData().setEndTime(endTime);
+        request.getData().setEndTime(endTime);*/
         ObjectMapper objectMapper = new ObjectMapper();
         String jsonParams = null;
         try {

+ 25 - 0
visualization-service/src/main/java/com/zksy/visualization/domain/request/AlarmInfoRequest.java

@@ -0,0 +1,25 @@
+package com.zksy.visualization.domain.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(description = "查询报警录像信息列表请求参数")
+public class AlarmInfoRequest {
+
+    @ApiModelProperty(value = "对象信息", required = true)
+    private Data data;
+
+    @lombok.Data
+    public static class Data {
+        @ApiModelProperty(value = "报警编码", required = true)
+        private String alarmCode;
+
+        @ApiModelProperty(value = "报警联动id,设备录像必填;调alarm事件详情接口返回的linkids")
+        private String linkId;
+
+        @ApiModelProperty(value = "报警时间戳,单位秒,设备录像必填;调alarm事件详情接口返回的alarmDate")
+        private String alarmDate;
+    }
+}

+ 2 - 2
visualization-service/src/main/java/com/zksy/visualization/domain/request/VideoRecordDateQueryRequest.java

@@ -19,10 +19,10 @@ public class VideoRecordDateQueryRequest {
         @ApiModelProperty(value = "录像来源:2=设备,3=中心", required = true, allowableValues = "2,3")
         private String recordSource;
 
-        @ApiModelProperty(value = "开始时间, 格式:yyyy-MM-dd HH:mm:ss, 时间必须大于等于在实际录像文件的开始时间", required = true)
+        @ApiModelProperty(value = "开始时间, 时间戳, 时间必须大于等于在实际录像文件的开始时间", required = true)
         private String startTime;
 
-        @ApiModelProperty(value = "结束时间, 格式:yyyy-MM-dd HH:mm:ss,时间必须小于等于在实际录像文件的结束时间,startTime与endTime不能跨文件", required = true)
+        @ApiModelProperty(value = "结束时间, 时间戳,时间必须小于等于在实际录像文件的结束时间,startTime与endTime不能跨文件", required = true)
         private String endTime;
 
         @ApiModelProperty(value = "码流类型:1=主码流, 2=辅码流,3=辅码流2", required = true, allowableValues = "1,2,3")

+ 34 - 0
visualization-service/src/main/java/com/zksy/visualization/domain/request/VideoRecordQueryListRequest.java

@@ -0,0 +1,34 @@
+package com.zksy.visualization.domain.request;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(description = "查询普通录像信息列表请求参数")
+public class VideoRecordQueryListRequest {
+
+    @ApiModelProperty(value = "Json对象", required = true)
+    private Data data;
+
+    @lombok.Data
+    public static class Data {
+        @ApiModelProperty(value = "视频通道编码,第一个$后数字代表通道类型,必须是1", required = true)
+        private String channelId;
+
+        @ApiModelProperty(value = "录像来源:1=全部,2=设备,3=中心", required = true, allowableValues = "1,2,3")
+        private String recordSource;
+
+        @ApiModelProperty(value = "开始时间(时间戳:单位秒)", required = true)
+        private String startTime;
+
+        @ApiModelProperty(value = "结束时间(时间戳:单位秒)", required = true)
+        private String endTime;
+
+        @ApiModelProperty(value = "码流类型:0=所有码流,1=主码流, 2=辅码流,3=辅码流2", required = true, allowableValues = "0,1,2,3")
+        private String streamType;
+
+        @ApiModelProperty(value = "录像类型:0=全部录像", required = true, allowableValues = "0")
+        private String recordType;
+    }
+}

+ 7 - 0
zksy-common/src/main/java/com/zksy/common/exception/InvalidMessageException.java

@@ -0,0 +1,7 @@
+package com.zksy.common.exception;
+
+public class InvalidMessageException extends RuntimeException {
+    public InvalidMessageException(String message) {
+        super(message);
+    }
+}