MessageHandler.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package com.zksy.radar.utils;
  2. import static com.zksy.common.utils.CollectionSideEffects.*;
  3. import com.zksy.api.domain.AlarmData;
  4. import com.zksy.api.domain.WarningThreshold;
  5. import com.zksy.api.service.AlarmDataService;
  6. import com.zksy.api.service.EarlyWarningAutoService;
  7. import com.zksy.api.service.WarningThresholdService;
  8. import com.zksy.api.utils.AlarmLevelUtil;
  9. import com.zksy.api.utils.SmsUtil;
  10. import com.zksy.common.exception.InvalidMessageException;
  11. import com.zksy.radar.domain.RadarData;
  12. import com.zksy.radar.service.RadarDataService;
  13. import com.zksy.api.service.DeviceContactService;
  14. import io.netty.buffer.ByteBuf;
  15. import io.netty.util.ReferenceCountUtil;
  16. import io.netty.buffer.Unpooled;
  17. import io.netty.channel.ChannelHandler;
  18. import io.netty.channel.ChannelHandlerContext;
  19. import io.netty.channel.ChannelInboundHandlerAdapter;
  20. import io.netty.handler.timeout.ReadTimeoutException;
  21. import lombok.extern.slf4j.Slf4j;
  22. import org.slf4j.Logger;
  23. import org.slf4j.LoggerFactory;
  24. import org.springframework.beans.factory.annotation.Autowired;
  25. import org.springframework.beans.factory.annotation.Value;
  26. import org.springframework.http.HttpEntity;
  27. import org.springframework.http.HttpHeaders;
  28. import org.springframework.http.MediaType;
  29. import org.springframework.stereotype.Component;
  30. import org.springframework.web.client.RestTemplate;
  31. import java.math.BigDecimal;
  32. import java.time.LocalDateTime;
  33. import java.util.HashMap;
  34. import java.util.List;
  35. import java.util.Map;
  36. @ChannelHandler.Sharable
  37. @Slf4j
  38. @Component
  39. public class MessageHandler extends ChannelInboundHandlerAdapter {
  40. private static Logger logger = LoggerFactory.getLogger(MessageHandler.class);
  41. private final RadarDataService service;
  42. private final WarningThresholdService warningThresholdService;
  43. private final AlarmDataService alarmDataService;
  44. private final EarlyWarningAutoService earlyWarningAutoService;
  45. private final SmsUtil smsUtil;
  46. private final DeviceContactService devicePhoneFetchUtil;
  47. private final RestTemplate restTemplate;
  48. @Autowired
  49. private DeviceOfflineCheckTask deviceOfflineCheckTask;
  50. @Value("${device.status.api.url:http://zk-api-service/equipmentStatus/updateAlarmStatus}")
  51. private String deviceStatusApiUrl;
  52. @Autowired
  53. public MessageHandler(RadarDataService RadarDataService, WarningThresholdService warningThresholdService,
  54. AlarmDataService alarmDataService, EarlyWarningAutoService earlyWarningAutoService,
  55. SmsUtil smsUtil, DeviceContactService devicePhoneFetchUtil,
  56. RestTemplate restTemplate) {
  57. this.service = RadarDataService;
  58. this.warningThresholdService = warningThresholdService;
  59. this.alarmDataService = alarmDataService;
  60. this.earlyWarningAutoService = earlyWarningAutoService;
  61. this.smsUtil = smsUtil;
  62. this.devicePhoneFetchUtil = devicePhoneFetchUtil;
  63. this.restTemplate = restTemplate;
  64. }
  65. @Override
  66. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  67. super.channelActive(ctx);
  68. //sendDataToDevice(ctx);
  69. }
  70. @Override
  71. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  72. try {
  73. ByteBuf in = (ByteBuf) msg;
  74. byte[] msgBytes = new byte[in.readableBytes()];
  75. in.readBytes(msgBytes);
  76. logger.debug("接收到的原始数据帧: {}", printHexBinary(msgBytes));
  77. // 提取帧类型
  78. byte frameType = msgBytes[6];
  79. logger.debug("帧类型: 0x{}", String.format("%02X", frameType));
  80. // 1. 完整协议校验
  81. DataParser.validateMessage(msgBytes);
  82. logger.info("数据帧校验通过");
  83. // 2. 数据解析
  84. RadarData resultData = DataParser.parseMessage(msgBytes);
  85. if (resultData.getSystemIdentifier() == null) {
  86. resultData.setSystemIdentifier("123456");
  87. }
  88. // 3. 根据帧类型处理
  89. if (frameType == 0x31) {
  90. if (msgBytes.length > 34) {
  91. byte flagByte = msgBytes[34];
  92. String binary = String.format("%8s", Integer.toBinaryString(flagByte & 0xFF)).replace(' ', '0');
  93. boolean isEnd = (flagByte & 0x08) != 0;
  94. logger.debug("结束位检测: 字节=0x{}, 二进制={}, 第5位(结束标识)={}",
  95. String.format("%02X", flagByte), binary, isEnd ? "1(结束)" : "0(未结束)");
  96. } else {
  97. logger.warn("数据长度不足,无法检测结束位");
  98. }
  99. // 入库
  100. requireSaved(service.save(resultData));
  101. String deviceCode = resultData.getDeviceCode();
  102. run(deviceCode, "接收时间更新", () ->
  103. DeviceOfflineCheckTask.deviceLastReceiveTimeMap.put(deviceCode, new java.util.Date()));
  104. run(deviceCode, "采集应答", () -> {
  105. byte[] response = ProtocolUtils.buildCustomReplyFrame(msgBytes);
  106. ctx.writeAndFlush(Unpooled.copiedBuffer(response));
  107. if (Boolean.TRUE.equals(resultData.getIsLastPacket())) {
  108. byte[] endResponse = ProtocolUtils.buildEndReplyFrame(msgBytes);
  109. ctx.executor().schedule(() -> run(deviceCode, "结束应答", () -> {
  110. if (ctx.channel().isActive()) {
  111. ctx.writeAndFlush(Unpooled.copiedBuffer(endResponse));
  112. }
  113. }), 1, java.util.concurrent.TimeUnit.SECONDS);
  114. }
  115. });
  116. run(deviceCode, "在线状态同步", () -> deviceOfflineCheckTask.markDeviceOnline(deviceCode));
  117. run(deviceCode, "报警预警评估", () -> handleRadarAlarm(resultData));
  118. } else if (frameType == 0x34) {
  119. /*logger.info("收到结束通讯帧,准备发送回应包");
  120. byte[] response = ProtocolUtils.buildShutdownAckPacket(msgBytes);
  121. ctx.writeAndFlush(Unpooled.copiedBuffer(response));
  122. logger.debug("已发送结束通讯回应包");*/
  123. } else {
  124. logger.warn("收到未知帧类型: 0x{}", String.format("%02X", frameType));
  125. }
  126. } catch (InvalidMessageException e) {
  127. logger.error("数据校验失败,不入库: {}", e.getMessage());
  128. sendErrorResponse(ctx, "数据校验失败");
  129. } catch (Exception e) {
  130. logger.error("数据解析或入库异常", e);
  131. sendErrorResponse(ctx, "数据处理异常");
  132. } finally {
  133. ReferenceCountUtil.release(msg);
  134. }
  135. }
  136. // 工具方法:发送错误响应
  137. private void sendErrorResponse(ChannelHandlerContext ctx, String msg) {
  138. ctx.writeAndFlush(Unpooled.copiedBuffer(("数据处理失败: " + msg).getBytes()));
  139. }
  140. private String printHexBinary(byte[] bytes) {
  141. StringBuilder sb = new StringBuilder();
  142. for (byte b : bytes) {
  143. sb.append(String.format("%02X ", b));
  144. }
  145. System.out.println("Received raw data: " + sb.toString());
  146. logger.info("Received raw data: " + sb.toString());
  147. return sb.toString();
  148. }
  149. @Override
  150. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  151. if (cause instanceof ReadTimeoutException) {
  152. logger.info("来自" + ctx.channel().remoteAddress() + "的连接超时断开");
  153. } else {
  154. logger.error("设备连接异常", cause);
  155. logger.info("来自" + ctx.channel().remoteAddress() + "的连接异常断开");
  156. ctx.close();
  157. }
  158. }
  159. @Override
  160. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  161. ctx.flush();
  162. }
  163. @Override
  164. public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
  165. logger.info("来自" + ctx.channel().remoteAddress() + "的连接主动断开");
  166. ctx.fireChannelUnregistered();
  167. }
  168. // 主动向设备发送数据
  169. public void sendDataToDevice(ChannelHandlerContext ctx) {
  170. try {
  171. // 将字符串形式的十六进制数据转换为字节数组
  172. String hexData = "01 03 00 00 00 01 84 0A ";
  173. String[] hexArray = hexData.split(" ");
  174. byte[] dataBytes = new byte[hexArray.length];
  175. for (int i = 0; i < hexArray.length; i++) {
  176. dataBytes[i] = (byte) Integer.parseInt(hexArray[i], 16);
  177. }
  178. ByteBuf byteBuf = Unpooled.copiedBuffer(dataBytes);
  179. ctx.writeAndFlush(byteBuf);
  180. logger.info("已向设备发送数据: {}", printHexBinary(dataBytes));
  181. } catch (Exception e) {
  182. logger.error("发送数据失败: {}", e.getMessage());
  183. }
  184. }
  185. /**
  186. * 处理雷达设备告警
  187. */
  188. private void handleRadarAlarm(RadarData resultData) {
  189. String deviceCode = resultData.getDeviceCode();
  190. Integer status = alarmStatus(
  191. handleAlarm(deviceCode, "WARN_INSTANT_FLOW", "瞬时流量", resultData.getMeter1InstantFlow()),
  192. handleAlarm(deviceCode, "WARN_FLOW_SPEED", "流速", resultData.getFlowSpeed()));
  193. if (status != null) {
  194. updateDeviceAlarmStatus(deviceCode, status);
  195. }
  196. }
  197. /**
  198. * 处理单个告警
  199. * @return 是否触发告警
  200. */
  201. private Boolean handleAlarm(String deviceCode, String warningCode, String defaultType, String valueStr) {
  202. if (valueStr == null) {
  203. return null;
  204. }
  205. return evaluate(deviceCode, warningCode, () -> {
  206. BigDecimal actual = new BigDecimal(valueStr);
  207. WarningThreshold threshold = warningThresholdService.getWarningThresholdByDeviceAndCode(deviceCode, warningCode);
  208. if (threshold == null || (threshold.getMinValue() == null && threshold.getMaxValue() == null)) {
  209. throw new IllegalStateException("阈值缺失,不能判断设备已恢复正常");
  210. }
  211. String type = defaultType;
  212. BigDecimal min = threshold.getMinValue() == null ? null : BigDecimal.valueOf(threshold.getMinValue());
  213. BigDecimal max = threshold.getMaxValue() == null ? null : BigDecimal.valueOf(threshold.getMaxValue());
  214. boolean over = (min != null && actual.compareTo(min) <= 0) || (max != null && actual.compareTo(max) >= 0);
  215. if (over) {
  216. run(deviceCode, warningCode + ":报警入库及通知", () -> saveAlarmData(deviceCode, type, warningCode, min, max, actual, null));
  217. run(deviceCode, warningCode + ":关闭预警", () -> earlyWarningAutoService.closeAutoWarning(deviceCode, warningCode, "已达报警阈值,转报警处理"));
  218. } else {
  219. run(deviceCode, warningCode + ":告警恢复", () -> alarmDataService.recoverThresholdAlarm(deviceCode, warningCode, min, max, actual));
  220. run(deviceCode, warningCode + ":生成预警", () -> earlyWarningAutoService.checkAndGenerate(deviceCode, type, warningCode, min, max, actual, null));
  221. }
  222. return over;
  223. });
  224. }
  225. /**
  226. * 保存告警数据到 alarm_data 表
  227. */
  228. private void saveAlarmData(String deviceCode, String warningType, String warningCode,
  229. BigDecimal minValue, BigDecimal maxValue, BigDecimal actualValue, String remark) {
  230. try {
  231. AlarmData alarmData = new AlarmData();
  232. alarmData.setDeviceCode(deviceCode);
  233. alarmData.setWarningCode(warningCode);
  234. alarmData.setMinValue(minValue);
  235. alarmData.setMaxValue(maxValue);
  236. alarmData.setActualValue(actualValue);
  237. int alarmLevel = AlarmLevelUtil.calculateLevel(actualValue, minValue, maxValue);
  238. BigDecimal deviationRatio = AlarmLevelUtil.calculateDeviationRatio(actualValue, minValue, maxValue);
  239. alarmData.setAlarmLevel(alarmLevel);
  240. alarmData.setDeviationRatio(deviationRatio);
  241. alarmData.setAlarmStatus(0);
  242. alarmData.setCreateTime(LocalDateTime.now());
  243. alarmData.setCreateTime(LocalDateTime.now());
  244. boolean saved = run(deviceCode, warningCode + ":报警入库", () -> requireSaved(alarmDataService.saveAlarmData(alarmData)));
  245. if (saved) { logger.info("告警数据入库成功: deviceCode={}, warningCode={}, level={}级", deviceCode, warningCode, alarmLevel); }
  246. if (alarmLevel == 1) {
  247. logger.warn("【1级紧急告警】deviceCode={}, warningCode={}, deviationRatio={}%",
  248. deviceCode, warningCode, deviationRatio);
  249. sendAlarmSms(deviceCode, warningType, actualValue, minValue, maxValue);
  250. }
  251. } catch (Exception e) {
  252. logger.error("保存告警数据失败", e);
  253. }
  254. }
  255. private void sendAlarmSms(String deviceCode, String warningType, BigDecimal actualValue,
  256. BigDecimal minValue, BigDecimal maxValue) {
  257. try {
  258. List<String> phoneList = devicePhoneFetchUtil.getPhoneListByDeviceCode(deviceCode);
  259. if (phoneList == null || phoneList.isEmpty()) {
  260. logger.warn("报警短信发送失败:未找到设备{}的维护人员手机号", deviceCode);
  261. return;
  262. }
  263. com.alibaba.fastjson.JSONObject params = new com.alibaba.fastjson.JSONObject();
  264. params.put("deviceNo", deviceCode);
  265. params.put("alarmType", warningType);
  266. params.put("location", "当前值:" + actualValue + ",正常范围:" + minValue + "~" + maxValue);
  267. Map<String, Boolean> result = smsUtil.sendBatchSms(phoneList, params.toJSONString());
  268. long successCount = result.values().stream().filter(Boolean::booleanValue).count();
  269. logger.info("1级告警短信发送完成,成功{}条,失败{}条", successCount, phoneList.size() - successCount);
  270. } catch (Exception e) {
  271. logger.error("发送报警短信异常: {}", e.getMessage());
  272. }
  273. }
  274. /**
  275. * 更新设备报警状态
  276. * @param deviceCode 设备编码
  277. * @param alarmStatus 报警状态:0-正常,1-报警
  278. */
  279. private void updateDeviceAlarmStatus(String deviceCode, int alarmStatus) {
  280. try {
  281. Map<String, Object> params = new HashMap<>();
  282. params.put("deviceCode", deviceCode);
  283. params.put("alarmStatus", alarmStatus);
  284. HttpHeaders headers = new HttpHeaders();
  285. headers.setContentType(MediaType.APPLICATION_JSON);
  286. HttpEntity<Map<String, Object>> request = new HttpEntity<>(params, headers);
  287. Map<?, ?> response = restTemplate.postForObject(deviceStatusApiUrl, request, Map.class);
  288. if (response == null || !Boolean.TRUE.equals(response.get("success"))) {
  289. logger.warn("设备报警状态更新未成功: deviceCode={}, alarmStatus={}, response={}", deviceCode, alarmStatus, response);
  290. return;
  291. }
  292. logger.info("设备报警状态更新成功: deviceCode={}, alarmStatus={}", deviceCode, alarmStatus);
  293. } catch (Exception e) {
  294. logger.error("更新设备报警状态失败: deviceCode={}, alarmStatus={}", deviceCode, alarmStatus, e);
  295. }
  296. }
  297. }